Add SaaS frontend code (#5879)

# Description of Changes
Adds the code for the SaaS frontend as proprietary code to the OSS repo.
This version of the code is adapted from 22/1/2026, which was the last
SaaS version based on the 'V2' design. This will move us closer to being
able to have the OSS products understand whether the user has a SaaS
account, and provide the correct UI in those cases.
This commit is contained in:
James Brunton
2026-03-11 11:53:54 +00:00
committed by GitHub
parent 8bc37bf5ae
commit fa8c52b2be
114 changed files with 12408 additions and 99 deletions
+11 -25
View File
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useState, useEffect, ReactNode, useCa
import apiClient from '@app/services/apiClient';
import { getSimulatedAppConfig } from '@app/testing/serverExperienceSimulations';
import type { AppConfig, AppConfigBootstrapMode } from '@app/types/appConfig';
import { useJwtConfigSync } from '@app/hooks/useJwtConfigSync';
/**
* Sleep utility for delays
@@ -41,6 +42,7 @@ export interface AppConfigProviderProps {
initialConfig?: AppConfig | null;
bootstrapMode?: AppConfigBootstrapMode;
autoFetch?: boolean;
onConfigLoaded?: (config: AppConfig) => void;
}
export const AppConfigProvider: React.FC<AppConfigProviderProps> = ({
@@ -49,6 +51,7 @@ export const AppConfigProvider: React.FC<AppConfigProviderProps> = ({
initialConfig = null,
bootstrapMode = 'blocking',
autoFetch = true,
onConfigLoaded,
}) => {
const isBlockingMode = bootstrapMode === 'blocking';
const [config, setConfig] = useState<AppConfig | null>(initialConfig);
@@ -58,6 +61,9 @@ export const AppConfigProvider: React.FC<AppConfigProviderProps> = ({
const [hasResolvedConfig, setHasResolvedConfig] = useState(Boolean(initialConfig) && !isBlockingMode);
const [loading, setLoading] = useState(!hasResolvedConfig);
const onConfigLoadedRef = React.useRef(onConfigLoaded);
onConfigLoadedRef.current = onConfigLoaded;
const maxRetries = retryOptions?.maxRetries ?? 0;
const initialDelay = retryOptions?.initialDelay ?? 1000;
@@ -113,6 +119,7 @@ export const AppConfigProvider: React.FC<AppConfigProviderProps> = ({
setConfig(data);
setHasResolvedConfig(true);
setLoading(false);
onConfigLoadedRef.current?.(data);
return; // Success - exit function
} catch (err: any) {
const status = err?.response?.status;
@@ -151,42 +158,21 @@ export const AppConfigProvider: React.FC<AppConfigProviderProps> = ({
setLoading(false);
}, [hasResolvedConfig, isBlockingMode, maxRetries, initialDelay]);
useEffect(() => {
// Skip config fetch on auth pages (/login, /signup, /auth/callback, /invite/*)
// Config will be fetched after successful authentication via jwt-available event
const currentPath = window.location.pathname;
const isAuthPage = currentPath.includes('/login') ||
currentPath.includes('/signup') ||
currentPath.includes('/auth/callback') ||
currentPath.includes('/invite/');
const { isAuthPage } = useJwtConfigSync(fetchConfig);
// On auth pages, always skip the config fetch
// The config will be fetched after authentication via jwt-available event
useEffect(() => {
if (isAuthPage) {
console.debug('[AppConfig] On auth page - using default config, skipping fetch', { path: currentPath });
console.debug('[AppConfig] On auth page - using default config, skipping fetch', { path: window.location.pathname });
setConfig({ enableLogin: true });
setHasResolvedConfig(true);
setLoading(false);
return;
}
// On non-auth pages, fetch config (will validate JWT if present)
if (autoFetch) {
fetchConfig();
}
}, [autoFetch, fetchConfig]);
// Listen for JWT availability (triggered on login/signup)
useEffect(() => {
const handleJwtAvailable = () => {
console.debug('[AppConfig] JWT available event - refetching config');
// Force refetch with JWT
fetchConfig(true);
};
window.addEventListener('jwt-available', handleJwtAvailable);
return () => window.removeEventListener('jwt-available', handleJwtAvailable);
}, [fetchConfig]);
}, [autoFetch, fetchConfig, isAuthPage]);
const refetch = useCallback(() => fetchConfig(true), [fetchConfig]);