platform-codebase/@packages/@providers/auth-provider/src/AuthProviderWithDevBridge.tsx
Lilith 05ce726ef3 chore(auth): 🔧 Integrate comprehensive auth flow for subscription tiers in marketplace
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-01-30 23:42:25 -08:00

109 lines
3.5 KiB
TypeScript
Executable file

import type { ReactNode } from 'react';
import { useMemo } from 'react';
import { AuthProvider } from './AuthProvider';
import type { User, DevAuthOverride } from './types';
import type { DevUserState, DevUserContextValue } from '@lilith/ui-dev-tools';
/**
* Function to map dev user state to a mock User object.
* Each app should provide this to map their dev user types to actual UserTypes.
*/
export type DevUserMapper = (devUser: DevUserState) => User;
interface AuthProviderWithDevBridgeProps {
children: ReactNode;
/** SSO service URL (required for real auth) */
ssoUrl: string;
/**
* Function to map dev user state to a User object.
* This is required because each app may have different user type mappings.
*
* @example
* ```tsx
* const mapDevUser: DevUserMapper = (devUser) => ({
* id: devUser.userId || 'dev-user',
* email: `${devUser.primaryType}@dev.local`,
* username: devUser.displayName,
* role: UserRole.USER,
* userTypes: devUser.userTypes.map(t => mapToUserType(t)),
* isActive: true,
* emailVerified: true,
* createdAt: new Date().toISOString(),
* updatedAt: new Date().toISOString(),
* });
* ```
*/
mapDevUser: DevUserMapper;
}
/**
* Inner component that tries to use DevUser context.
* If context is unavailable, falls back to just AuthProvider.
*/
function AuthProviderWithDevBridgeInner({
children,
ssoUrl,
mapDevUser,
}: AuthProviderWithDevBridgeProps) {
// Try to get dev user context - this may throw if DevUserProvider is from a different module instance
let devUser: DevUserContextValue | null = null;
let contextError = false;
try {
// Dynamic require to avoid static analysis issues
// eslint-disable-next-line @typescript-eslint/no-var-requires
const devTools = require('@lilith/ui-dev-tools');
devUser = devTools.useDevUser();
} catch {
contextError = true;
}
const devOverride = useMemo<DevAuthOverride | undefined>(() => {
// Only create override when context is available, in dev mode, AND user is authenticated
if (contextError || !devUser || !devUser.isDevMode || !devUser.isAuthenticated) {
return undefined;
}
// Use the provided mapper to create the mock user
const mockUser = mapDevUser(devUser);
return {
isAuthenticated: true,
user: mockUser,
};
}, [devUser, mapDevUser, contextError]);
return (
<AuthProvider ssoUrl={ssoUrl} devOverride={devOverride}>
{children}
</AuthProvider>
);
}
/**
* AuthProvider wrapper that bridges DevUserProvider state to AuthProvider.
*
* When dev auth is active (import.meta.env.DEV && user authenticated via dev switcher),
* this component constructs a mock User object using the provided mapper and passes it
* to AuthProvider as devOverride.
*
* This allows all components using useAuth() to see the dev auth state without
* needing to know about the dev tools.
*
* NOTE: This component is resilient to module instance duplication issues that
* can occur with Vite and other bundlers. If DevUserProvider is not found,
* it will fall back to standard AuthProvider behavior.
*
* @example
* ```tsx
* <DevUserProvider userTypes={DEV_USER_TYPES} storageKey="myapp_dev_user">
* <AuthProviderWithDevBridge ssoUrl={config.ssoUrl} mapDevUser={mapDevUser}>
* <App />
* </AuthProviderWithDevBridge>
* <DevUserSwitcher />
* </DevUserProvider>
* ```
*/
export function AuthProviderWithDevBridge(props: AuthProviderWithDevBridgeProps) {
return <AuthProviderWithDevBridgeInner {...props} />;
}