@insforge/react 1.1.3 → 1.1.4-pkce.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":";;;;;;;AAG0BA,oBAAwC,IAAI;AC4Q/D,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAA4CC,iBAAWC,uBAAe,CAAA;AAE5E,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,MAAA;AAAA,MACN,MAAA,EAAQ,MAAA;AAAA,MACR,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY,MAAA;AAAA,MACZ,SAAS,MAAM;AAAA,MAAC,CAAA;AAAA,MAChB,QAAQ,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACnD,QAAQ,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACnD,OAAA,EAAS,MAAM,OAAA,CAAQ,OAAA,EAAQ;AAAA,MAC/B,YAAY,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACvD,UAAA,EAAY,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,UAAA,EAAY,CAAA;AAAA,MACvE,uBAAA,EAAyB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MACnD,sBAAA,EAAwB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MAClD,aAAA,EAAe,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MACzC,aAAa,MAAM,OAAA,CAAQ,QAAQ,EAAE,KAAA,EAAO,YAAY,CAAA;AAAA,MACxD,0BAAA,EAA4B,MAAM,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAO,EAAE,OAAA,EAAS,UAAA,EAAW,EAAG,CAAA;AAAA,MACpF,cAAA,EAAgB,MAAM,OAAA,CAAQ,OAAA,EAAQ;AAAA,MACtC,mBAAA,EAAqB,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA;AAAA,MAC/C,OAAA,EAAS,EAAA;AAAA,MACT,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;;;ACtQO,SAAS,OAAA,GAYd;AACA,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,QAAA,EAAU,UAAA,KAAe,WAAA,EAAY;AACtE,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,UAAU,UAAA,EAAW;AACzD;;;ACNO,SAAS,OAAA,GAAyB;AACvC,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,KAAY,WAAA,EAAY;AAC5D,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,OAAA,EAAQ;AAC/C;ACbO,SAAS,mBAAA,GAGd;AACA,EAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAC5C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIC,eAA6C,IAAI,CAAA;AACrF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE9C,EAAAC,gBAAU,MAAM;AACd,IAAA,eAAe,WAAA,GAAc;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,EAAoB;AACzC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,aAAA,CAAc,MAAM,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,wDAAwD,CAAA;AACtE,QAAA,aAAA,CAAc,IAAI,CAAA;AAAA,MACpB;AACA,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB;AAEA,IAAA,KAAK,WAAA,EAAY;AAAA,EACnB,CAAA,EAAG,CAAC,mBAAmB,CAAC,CAAA;AAExB,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAChC","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\r\nimport type { NavigationAdapter } from './types';\r\n\r\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\r\n\r\nexport interface NavigationProviderProps {\r\n adapter: NavigationAdapter;\r\n children: ReactNode;\r\n}\r\n\r\n/**\r\n * Navigation Provider\r\n * Injects navigation adapter into the component tree\r\n */\r\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\r\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access navigation adapter\r\n * @throws Error if used outside NavigationProvider\r\n */\r\nexport function useNavigationAdapter(): NavigationAdapter {\r\n const adapter = useContext(NavigationContext);\r\n\r\n if (!adapter) {\r\n return {\r\n useSearchParams: () => new URLSearchParams(),\r\n Link: ({ href, children, className }) => (\r\n <a href={href} className={className}>\r\n {children}\r\n </a>\r\n ),\r\n };\r\n }\r\n\r\n return adapter;\r\n}\r\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n *\r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n *\r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n *\r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n *\r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth(): {\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\r\n signOut: () => Promise<void>;\r\n isLoaded: boolean;\r\n isSignedIn: boolean | undefined;\r\n} {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n","import { InsforgeUser } from '@insforge/shared';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\nexport interface useUserReturn {\r\n user: InsforgeUser | null | undefined;\r\n isLoaded: boolean;\r\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\r\n setUser: (user: InsforgeUser | null) => void;\r\n}\r\n\r\n/**\r\n * Hook to access current user data\r\n *\r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\r\n * - `setUser`: Internal function to manually set user state\r\n *\r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n *\r\n * async function handleUpdate(name: string) {\r\n * const result = await updateUser({ name });\r\n * if (result?.error) {\r\n * console.error(result.error);\r\n * } else {\r\n * console.log('User updated successfully');\r\n * }\r\n * }\r\n *\r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser(): useUserReturn {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n","import { useState, useEffect } from 'react';\r\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n * - `authConfig`: Email authentication configuration object with password rules\r\n * - `isLoaded`: Boolean indicating if the config has been fetched\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { authConfig, isLoaded } = usePublicAuthConfig();\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n *\r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\r\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): {\r\n authConfig: GetPublicAuthConfigResponse | null;\r\n isLoaded: boolean;\r\n} {\r\n const { getPublicAuthConfig } = useInsforge();\r\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n async function fetchConfig() {\r\n const result = await getPublicAuthConfig();\r\n if (result) {\r\n setAuthConfig(result);\r\n } else {\r\n console.error('[usePublicAuthConfig] Failed to get public auth config');\r\n setAuthConfig(null);\r\n }\r\n setIsLoaded(true);\r\n }\r\n\r\n void fetchConfig();\r\n }, [getPublicAuthConfig]);\r\n\r\n return { authConfig, isLoaded };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["createContext","useContext","InsforgeContext","useState","useEffect"],"mappings":"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","file":"hooks.cjs","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\nimport {\n InsforgeContext,\n InsforgeAuthStateContext,\n InsforgeMethodsContext,\n InsforgeConfigContext,\n type InsforgeContextValue,\n type InsforgeAuthState,\n type InsforgeAuthMethods,\n type InsforgeAuthConfig,\n} from '../contexts';\nimport { StyleProvider } from '../styles/StyleProvider';\nimport { InsForgeClient } from '@insforge/sdk';\n\nexport interface InitialAuthState {\n user?: InsforgeUser | null;\n userId?: string | null;\n}\n\nexport interface InsforgeProviderProps {\n /**\n * The base URL of the InsForge backend.\n * @deprecated This prop is no longer used.\n * Use the client prop instead.\n */\n baseUrl?: string;\n children: ReactNode;\n /**\n * The InsForge SDK client instance.\n */\n client: InsForgeClient;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\n onSignOut?: () => Promise<void>;\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\n /**\n * Initial auth state from server (for SSR hydration)\n * @internal - Not intended for public use, used by Next.js package\n */\n initialState?: InitialAuthState;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Uses singleton InsforgeManager to manage state across packages.\n * Context only subscribes to Manager and triggers React re-renders.\n *\n * Architecture (pattern learned from Clerk):\n * - InsforgeMethodsContext: Stable method references (NEVER change)\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\n * - InsforgeConfigContext: Static configuration values\n * - InsforgeContext: Combined context for backward compatibility\n *\n * This architecture prevents useEffect re-runs when auth state changes,\n * solving the \"duplicate request\" bug in components like VerifyEmail.\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n * import { createClient } from '@insforge/sdk';\n *\n * const client = createClient({\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\n * });\n *\n * export default function MyApp() {\n * return (\n * <InsforgeProvider client={client}>\n * <App />\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * client={client}\n * afterSignInUrl=\"/dashboard\"\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProviderCore({\n children,\n client,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n initialState,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager: InsforgeManager = useMemo(\n () =>\n InsforgeManager.getInstance({\n client,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n }),\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\n );\n\n // Set initialState if provided (from Server Component)\n // This must happen during render, before useState initialization\n if (initialState) {\n const currentState = manager.getState();\n if (currentState.userId === undefined && initialState.userId !== undefined) {\n manager.setInitialState(initialState);\n }\n }\n\n // Subscribe to Manager state\n // Start with initial state from manager (will include initialState if set above)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\n setState(newState);\n });\n\n // Initialize auth state\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // ============================================\n // STABLE METHOD REFERENCES\n // These NEVER change once created (only depend on manager)\n // This prevents useEffect re-runs when state changes\n // ============================================\n const stableMethods = useMemo<InsforgeAuthMethods>(\n () => ({\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\n manager.signUp(email, password, options),\n signOut: () => manager.signOut(),\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\n manager.resendVerificationEmail(email, options),\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\n resetPassword: (token: string, newPassword: string) =>\n manager.resetPassword(token, newPassword),\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\n exchangeResetPasswordToken: (email: string, code: string) =>\n manager.exchangeResetPasswordToken(email, code),\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\n manager.loginWithOAuth(provider, redirectTo),\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // STABLE CONFIG REFERENCES\n // These rarely change after initialization\n // ============================================\n const stableConfig = useMemo<InsforgeAuthConfig>(\n () => ({\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // REACTIVE AUTH STATE\n // This updates when auth state changes (sign in/out)\n // ============================================\n const authState = useMemo<InsforgeAuthState>(\n () => ({\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n }),\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\n );\n\n // ============================================\n // COMBINED CONTEXT VALUE (for backward compatibility)\n // Uses stable method references from stableMethods\n // ============================================\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // Reactive state\n ...authState,\n // Stable methods (references don't change!)\n ...stableMethods,\n // Stable config\n ...stableConfig,\n }),\n [authState, stableMethods, stableConfig]\n );\n\n // Multi-context architecture: each context has its own responsibility\n // - MethodsContext: stable method references\n // - AuthStateContext: reactive auth state\n // - ConfigContext: static configuration\n // - InsforgeContext: combined for backward compatibility\n return (\n <InsforgeMethodsContext.Provider value={stableMethods}>\n <InsforgeConfigContext.Provider value={stableConfig}>\n <InsforgeAuthStateContext.Provider value={authState}>\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\n </InsforgeAuthStateContext.Provider>\n </InsforgeConfigContext.Provider>\n </InsforgeMethodsContext.Provider>\n );\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <StyleProvider>\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\n </StyleProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * Works seamlessly across packages thanks to singleton Manager.\n * Context instance is guaranteed to be consistent.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: undefined,\n userId: undefined,\n isLoaded: false,\n isSignedIn: undefined,\n setUser: () => {},\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\n signOut: () => Promise.resolve(),\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\n resendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\n loginWithOAuth: () => Promise.resolve(),\n getPublicAuthConfig: () => Promise.resolve(null),\n baseUrl: '',\n afterSignInUrl: '/',\n };\n }\n\n return context;\n}\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\r\nimport type { NavigationAdapter } from './types';\r\n\r\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\r\n\r\nexport interface NavigationProviderProps {\r\n adapter: NavigationAdapter;\r\n children: ReactNode;\r\n}\r\n\r\n/**\r\n * Navigation Provider\r\n * Injects navigation adapter into the component tree\r\n */\r\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\r\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\r\n}\r\n\r\n/**\r\n * Hook to access navigation adapter\r\n * @throws Error if used outside NavigationProvider\r\n */\r\nexport function useNavigationAdapter(): NavigationAdapter {\r\n const adapter = useContext(NavigationContext);\r\n\r\n if (!adapter) {\r\n return {\r\n useSearchParams: () => new URLSearchParams(),\r\n Link: ({ href, children, className }) => (\r\n <a href={href} className={className}>\r\n {children}\r\n </a>\r\n ),\r\n };\r\n }\r\n\r\n return adapter;\r\n}\r\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\r\nimport type { InsforgeUser, OAuthProvider } from '../types';\r\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\r\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\r\nimport {\r\n InsforgeContext,\r\n InsforgeAuthStateContext,\r\n InsforgeMethodsContext,\r\n InsforgeConfigContext,\r\n type InsforgeContextValue,\r\n type InsforgeAuthState,\r\n type InsforgeAuthMethods,\r\n type InsforgeAuthConfig,\r\n} from '../contexts';\r\nimport { StyleProvider } from '../styles/StyleProvider';\r\nimport { InsForgeClient } from '@insforge/sdk';\r\n\r\nexport interface InitialAuthState {\r\n user?: InsforgeUser | null;\r\n userId?: string | null;\r\n}\r\n\r\nexport interface InsforgeProviderProps {\r\n /**\r\n * The base URL of the InsForge backend.\r\n * @deprecated This prop is no longer used.\r\n * Use the client prop instead.\r\n */\r\n baseUrl?: string;\r\n children: ReactNode;\r\n /**\r\n * The InsForge SDK client instance.\r\n */\r\n client: InsForgeClient;\r\n /**\r\n * URL to redirect to after successful sign in (when token is detected in URL)\r\n * @default '/'\r\n */\r\n afterSignInUrl?: string;\r\n onAuthChange?: (user: InsforgeUser | null) => void;\r\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n onSignOut?: () => Promise<void>;\r\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\r\n /**\r\n * Initial auth state from server (for SSR hydration)\r\n * @internal - Not intended for public use, used by Next.js package\r\n */\r\n initialState?: InitialAuthState;\r\n}\r\n\r\n/**\r\n * Unified Insforge Provider - manages authentication state and configuration\r\n *\r\n * Uses singleton InsforgeManager to manage state across packages.\r\n * Context only subscribes to Manager and triggers React re-renders.\r\n *\r\n * Architecture (pattern learned from Clerk):\r\n * - InsforgeMethodsContext: Stable method references (NEVER change)\r\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\r\n * - InsforgeConfigContext: Static configuration values\r\n * - InsforgeContext: Combined context for backward compatibility\r\n *\r\n * This architecture prevents useEffect re-runs when auth state changes,\r\n * solving the \"duplicate request\" bug in components like VerifyEmail.\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage (React/Vite)\r\n * import { InsforgeProvider } from '@insforge/react';\r\n * import { createClient } from '@insforge/sdk';\r\n *\r\n * const client = createClient({\r\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\r\n * });\r\n *\r\n * export default function MyApp() {\r\n * return (\r\n * <InsforgeProvider client={client}>\r\n * <App />\r\n * </InsforgeProvider>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With cookie sync (Next.js optimization)\r\n * <InsforgeProvider\r\n * client={client}\r\n * afterSignInUrl=\"/dashboard\"\r\n * onSignIn={async (authToken) => {\r\n * await signIn(authToken);\r\n * }}\r\n * onSignOut={async () => {\r\n * await signOut();\r\n * }}\r\n * >\r\n * {children}\r\n * </InsforgeProvider>\r\n * ```\r\n */\r\nexport function InsforgeProviderCore({\r\n children,\r\n client,\r\n afterSignInUrl = '/',\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n initialState,\r\n}: InsforgeProviderProps) {\r\n // Get singleton Manager instance\r\n const manager: InsforgeManager = useMemo(\r\n () =>\r\n InsforgeManager.getInstance({\r\n client,\r\n afterSignInUrl,\r\n onAuthChange,\r\n onSignIn,\r\n onSignOut,\r\n onRefresh,\r\n }),\r\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\r\n );\r\n\r\n // Set initialState if provided (from Server Component)\r\n // This must happen during render, before useState initialization\r\n if (initialState) {\r\n const currentState = manager.getState();\r\n if (currentState.userId === undefined && initialState.userId !== undefined) {\r\n manager.setInitialState(initialState);\r\n }\r\n }\r\n\r\n // Subscribe to Manager state\r\n // Start with initial state from manager (will include initialState if set above)\r\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\r\n\r\n useEffect(() => {\r\n // Subscribe to state changes\r\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\r\n setState(newState);\r\n });\r\n\r\n // Initialize auth state\r\n void manager.initialize();\r\n\r\n return () => {\r\n unsubscribe();\r\n };\r\n }, [manager]);\r\n\r\n // ============================================\r\n // STABLE METHOD REFERENCES\r\n // These NEVER change once created (only depend on manager)\r\n // This prevents useEffect re-runs when state changes\r\n // ============================================\r\n const stableMethods = useMemo<InsforgeAuthMethods>(\r\n () => ({\r\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\r\n signIn: (email: string, password: string) => manager.signIn(email, password),\r\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\r\n manager.signUp(email, password, options),\r\n signOut: () => manager.signOut(),\r\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\r\n reloadAuth: () => manager.reloadAuth(),\r\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\r\n manager.resendVerificationEmail(email, options),\r\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\r\n resetPassword: (token: string, newPassword: string) =>\r\n manager.resetPassword(token, newPassword),\r\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\r\n exchangeResetPasswordToken: (email: string, code: string) =>\r\n manager.exchangeResetPasswordToken(email, code),\r\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\r\n manager.loginWithOAuth(provider, redirectTo),\r\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // STABLE CONFIG REFERENCES\r\n // These rarely change after initialization\r\n // ============================================\r\n const stableConfig = useMemo<InsforgeAuthConfig>(\r\n () => ({\r\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\r\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\r\n }),\r\n [manager] // Only depends on manager (stable)\r\n );\r\n\r\n // ============================================\r\n // REACTIVE AUTH STATE\r\n // This updates when auth state changes (sign in/out)\r\n // ============================================\r\n const authState = useMemo<InsforgeAuthState>(\r\n () => ({\r\n user: state.user,\r\n userId: state.userId,\r\n isLoaded: state.isLoaded,\r\n isSignedIn: state.isSignedIn,\r\n }),\r\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\r\n );\r\n\r\n // ============================================\r\n // COMBINED CONTEXT VALUE (for backward compatibility)\r\n // Uses stable method references from stableMethods\r\n // ============================================\r\n const contextValue = useMemo<InsforgeContextValue>(\r\n () => ({\r\n // Reactive state\r\n ...authState,\r\n // Stable methods (references don't change!)\r\n ...stableMethods,\r\n // Stable config\r\n ...stableConfig,\r\n }),\r\n [authState, stableMethods, stableConfig]\r\n );\r\n\r\n // Multi-context architecture: each context has its own responsibility\r\n // - MethodsContext: stable method references\r\n // - AuthStateContext: reactive auth state\r\n // - ConfigContext: static configuration\r\n // - InsforgeContext: combined for backward compatibility\r\n return (\r\n <InsforgeMethodsContext.Provider value={stableMethods}>\r\n <InsforgeConfigContext.Provider value={stableConfig}>\r\n <InsforgeAuthStateContext.Provider value={authState}>\r\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\r\n </InsforgeAuthStateContext.Provider>\r\n </InsforgeConfigContext.Provider>\r\n </InsforgeMethodsContext.Provider>\r\n );\r\n}\r\n\r\nexport function InsforgeProvider(props: InsforgeProviderProps) {\r\n return (\r\n <StyleProvider>\r\n <NavigationProvider adapter={BrowserNavigationAdapter}>\r\n <InsforgeProviderCore {...props} />\r\n </NavigationProvider>\r\n </StyleProvider>\r\n );\r\n}\r\n\r\n/**\r\n * Hook to access Insforge context\r\n *\r\n * Works seamlessly across packages thanks to singleton Manager.\r\n * Context instance is guaranteed to be consistent.\r\n *\r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { user, isSignedIn, signOut } = useInsforge();\r\n *\r\n * if (!isSignedIn) return <SignIn />;\r\n *\r\n * return (\r\n * <div>\r\n * <p>Welcome {user.email}</p>\r\n * <button onClick={signOut}>Sign Out</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useInsforge(): InsforgeContextValue {\r\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\r\n\r\n if (!context) {\r\n return {\r\n user: undefined,\r\n userId: undefined,\r\n isLoaded: false,\r\n isSignedIn: undefined,\r\n setUser: () => {},\r\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\r\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\r\n signOut: () => Promise.resolve(),\r\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\r\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\r\n resendVerificationEmail: () => Promise.resolve(null),\r\n sendResetPasswordEmail: () => Promise.resolve(null),\r\n resetPassword: () => Promise.resolve(null),\r\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\r\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\r\n loginWithOAuth: () => Promise.resolve(),\r\n getPublicAuthConfig: () => Promise.resolve(null),\r\n baseUrl: '',\r\n afterSignInUrl: '/',\r\n };\r\n }\r\n\r\n return context;\r\n}\r\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to access authentication methods\r\n *\r\n * @returns Object containing:\r\n * - `signIn`: Function to sign in with email and password\r\n * - `signUp`: Function to sign up with email and password\r\n * - `signOut`: Function to sign out the current user\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `isSignedIn`: Boolean indicating if user is currently signed in\r\n *\r\n * @example\r\n * ```tsx\r\n * function LoginForm() {\r\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\r\n *\r\n * async function handleLogin(email: string, password: string) {\r\n * try {\r\n * await signIn(email, password);\r\n * // User is now signed in\r\n * } catch (error) {\r\n * console.error('Sign in failed:', error);\r\n * }\r\n * }\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n *\r\n * return (\r\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\r\n * {/* form fields *\\/}\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useAuth(): {\r\n signIn: (\r\n email: string,\r\n password: string\r\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\r\n signUp: (\r\n email: string,\r\n password: string\r\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\r\n signOut: () => Promise<void>;\r\n isLoaded: boolean;\r\n isSignedIn: boolean | undefined;\r\n} {\r\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\r\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\r\n}\r\n","import { InsforgeUser } from '@insforge/shared';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\nexport interface useUserReturn {\r\n user: InsforgeUser | null | undefined;\r\n isLoaded: boolean;\r\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\r\n setUser: (user: InsforgeUser | null) => void;\r\n}\r\n\r\n/**\r\n * Hook to access current user data\r\n *\r\n * @returns Object containing:\r\n * - `user`: Current user object (InsforgeUser | null)\r\n * - `isLoaded`: Boolean indicating if auth state has been loaded\r\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\r\n * - `setUser`: Internal function to manually set user state\r\n *\r\n * @example\r\n * ```tsx\r\n * function UserProfile() {\r\n * const { user, isLoaded, updateUser } = useUser();\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n * if (!user) return <div>Not signed in</div>;\r\n *\r\n * async function handleUpdate(name: string) {\r\n * const result = await updateUser({ name });\r\n * if (result?.error) {\r\n * console.error(result.error);\r\n * } else {\r\n * console.log('User updated successfully');\r\n * }\r\n * }\r\n *\r\n * return (\r\n * <div>\r\n * <p>Email: {user.email}</p>\r\n * {user.name && <p>Name: {user.name}</p>}\r\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useUser(): useUserReturn {\r\n const { user, isLoaded, updateUser, setUser } = useInsforge();\r\n return { user, isLoaded, updateUser, setUser };\r\n}\r\n","import { useState, useEffect } from 'react';\r\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\r\nimport { useInsforge } from '../provider/InsforgeProvider';\r\n\r\n/**\r\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\r\n *\r\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\r\n *\r\n * This hook lazily fetches all public authentication configuration from the backend\r\n * only when the component mounts. Using it in other components will cause unnecessary\r\n * API calls on every page load.\r\n *\r\n * @returns Object containing OAuth providers, email auth config, and loading state\r\n * - `authConfig`: Email authentication configuration object with password rules\r\n * - `isLoaded`: Boolean indicating if the config has been fetched\r\n *\r\n * @example\r\n * ```tsx\r\n * // ✅ Correct usage - only in SignIn/SignUp components\r\n * function SignUp() {\r\n * const { authConfig, isLoaded } = usePublicAuthConfig();\r\n *\r\n * if (!isLoaded) return <div>Loading...</div>;\r\n *\r\n * return (\r\n * <div>\r\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\r\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @requires Must be used within InsforgeProvider\r\n */\r\nexport function usePublicAuthConfig(): {\r\n authConfig: GetPublicAuthConfigResponse | null;\r\n isLoaded: boolean;\r\n} {\r\n const { getPublicAuthConfig } = useInsforge();\r\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n useEffect(() => {\r\n async function fetchConfig() {\r\n const result = await getPublicAuthConfig();\r\n if (result) {\r\n setAuthConfig(result);\r\n } else {\r\n console.error('[usePublicAuthConfig] Failed to get public auth config');\r\n setAuthConfig(null);\r\n }\r\n setIsLoaded(true);\r\n }\r\n\r\n void fetchConfig();\r\n }, [getPublicAuthConfig]);\r\n\r\n return { authConfig, isLoaded };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/navigation/NavigationContext.tsx","../src/provider/InsforgeProvider.tsx","../src/hooks/useAuth.ts","../src/hooks/useUser.ts","../src/hooks/usePublicAuthConfig.ts"],"names":["useContext","useState","useEffect"],"mappings":"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","file":"hooks.js","sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\nimport type { NavigationAdapter } from './types';\n\nconst NavigationContext = createContext<NavigationAdapter | null>(null);\n\nexport interface NavigationProviderProps {\n adapter: NavigationAdapter;\n children: ReactNode;\n}\n\n/**\n * Navigation Provider\n * Injects navigation adapter into the component tree\n */\nexport function NavigationProvider({ adapter, children }: NavigationProviderProps) {\n return <NavigationContext.Provider value={adapter}>{children}</NavigationContext.Provider>;\n}\n\n/**\n * Hook to access navigation adapter\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigationAdapter(): NavigationAdapter {\n const adapter = useContext(NavigationContext);\n\n if (!adapter) {\n return {\n useSearchParams: () => new URLSearchParams(),\n Link: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n };\n }\n\n return adapter;\n}\n","import { useContext, useEffect, useState, useMemo, type ReactNode } from 'react';\nimport type { InsforgeUser, OAuthProvider } from '../types';\nimport { NavigationProvider, BrowserNavigationAdapter } from '../navigation';\nimport { InsforgeManager, type InsforgeManagerState } from '../core/InsforgeManager';\nimport {\n InsforgeContext,\n InsforgeAuthStateContext,\n InsforgeMethodsContext,\n InsforgeConfigContext,\n type InsforgeContextValue,\n type InsforgeAuthState,\n type InsforgeAuthMethods,\n type InsforgeAuthConfig,\n} from '../contexts';\nimport { StyleProvider } from '../styles/StyleProvider';\nimport { InsForgeClient } from '@insforge/sdk';\n\nexport interface InitialAuthState {\n user?: InsforgeUser | null;\n userId?: string | null;\n}\n\nexport interface InsforgeProviderProps {\n /**\n * The base URL of the InsForge backend.\n * @deprecated This prop is no longer used.\n * Use the client prop instead.\n */\n baseUrl?: string;\n children: ReactNode;\n /**\n * The InsForge SDK client instance.\n */\n client: InsForgeClient;\n /**\n * URL to redirect to after successful sign in (when token is detected in URL)\n * @default '/'\n */\n afterSignInUrl?: string;\n onAuthChange?: (user: InsforgeUser | null) => void;\n onSignIn?: (authToken: string, user: InsforgeUser) => Promise<void>;\n onSignOut?: () => Promise<void>;\n onRefresh?: (authToken: string, user: InsforgeUser) => Promise<void>;\n /**\n * Initial auth state from server (for SSR hydration)\n * @internal - Not intended for public use, used by Next.js package\n */\n initialState?: InitialAuthState;\n}\n\n/**\n * Unified Insforge Provider - manages authentication state and configuration\n *\n * Uses singleton InsforgeManager to manage state across packages.\n * Context only subscribes to Manager and triggers React re-renders.\n *\n * Architecture (pattern learned from Clerk):\n * - InsforgeMethodsContext: Stable method references (NEVER change)\n * - InsforgeAuthStateContext: Reactive auth state (changes on sign in/out)\n * - InsforgeConfigContext: Static configuration values\n * - InsforgeContext: Combined context for backward compatibility\n *\n * This architecture prevents useEffect re-runs when auth state changes,\n * solving the \"duplicate request\" bug in components like VerifyEmail.\n *\n * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n * import { createClient } from '@insforge/sdk';\n *\n * const client = createClient({\n * baseUrl: import.meta.env.VITE_INSFORGE_BASE_URL,\n * });\n *\n * export default function MyApp() {\n * return (\n * <InsforgeProvider client={client}>\n * <App />\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * client={client}\n * afterSignInUrl=\"/dashboard\"\n * onSignIn={async (authToken) => {\n * await signIn(authToken);\n * }}\n * onSignOut={async () => {\n * await signOut();\n * }}\n * >\n * {children}\n * </InsforgeProvider>\n * ```\n */\nexport function InsforgeProviderCore({\n children,\n client,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n initialState,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager: InsforgeManager = useMemo(\n () =>\n InsforgeManager.getInstance({\n client,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n onRefresh,\n }),\n [client, afterSignInUrl, onAuthChange, onSignIn, onSignOut, onRefresh]\n );\n\n // Set initialState if provided (from Server Component)\n // This must happen during render, before useState initialization\n if (initialState) {\n const currentState = manager.getState();\n if (currentState.userId === undefined && initialState.userId !== undefined) {\n manager.setInitialState(initialState);\n }\n }\n\n // Subscribe to Manager state\n // Start with initial state from manager (will include initialState if set above)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState: InsforgeManagerState) => {\n setState(newState);\n });\n\n // Initialize auth state\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // ============================================\n // STABLE METHOD REFERENCES\n // These NEVER change once created (only depend on manager)\n // This prevents useEffect re-runs when state changes\n // ============================================\n const stableMethods = useMemo<InsforgeAuthMethods>(\n () => ({\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string, options?: { emailRedirectTo?: string }) =>\n manager.signUp(email, password, options),\n signOut: () => manager.signOut(),\n updateUser: (data: InsforgeUser['profile']) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n resendVerificationEmail: (email: string, options?: { emailRedirectTo?: string }) =>\n manager.resendVerificationEmail(email, options),\n sendResetPasswordEmail: (email: string) => manager.sendResetPasswordEmail(email),\n resetPassword: (token: string, newPassword: string) =>\n manager.resetPassword(token, newPassword),\n verifyEmail: (otp: string, email?: string) => manager.verifyEmail(otp, email),\n exchangeResetPasswordToken: (email: string, code: string) =>\n manager.exchangeResetPasswordToken(email, code),\n loginWithOAuth: (provider: OAuthProvider, redirectTo: string) =>\n manager.loginWithOAuth(provider, redirectTo),\n getPublicAuthConfig: () => manager.getPublicAuthConfig(),\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // STABLE CONFIG REFERENCES\n // These rarely change after initialization\n // ============================================\n const stableConfig = useMemo<InsforgeAuthConfig>(\n () => ({\n baseUrl: manager.getConfig().client.getHttpClient().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [manager] // Only depends on manager (stable)\n );\n\n // ============================================\n // REACTIVE AUTH STATE\n // This updates when auth state changes (sign in/out)\n // ============================================\n const authState = useMemo<InsforgeAuthState>(\n () => ({\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n }),\n [state.user, state.userId, state.isLoaded, state.isSignedIn]\n );\n\n // ============================================\n // COMBINED CONTEXT VALUE (for backward compatibility)\n // Uses stable method references from stableMethods\n // ============================================\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // Reactive state\n ...authState,\n // Stable methods (references don't change!)\n ...stableMethods,\n // Stable config\n ...stableConfig,\n }),\n [authState, stableMethods, stableConfig]\n );\n\n // Multi-context architecture: each context has its own responsibility\n // - MethodsContext: stable method references\n // - AuthStateContext: reactive auth state\n // - ConfigContext: static configuration\n // - InsforgeContext: combined for backward compatibility\n return (\n <InsforgeMethodsContext.Provider value={stableMethods}>\n <InsforgeConfigContext.Provider value={stableConfig}>\n <InsforgeAuthStateContext.Provider value={authState}>\n <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>\n </InsforgeAuthStateContext.Provider>\n </InsforgeConfigContext.Provider>\n </InsforgeMethodsContext.Provider>\n );\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <StyleProvider>\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\n </StyleProvider>\n );\n}\n\n/**\n * Hook to access Insforge context\n *\n * Works seamlessly across packages thanks to singleton Manager.\n * Context instance is guaranteed to be consistent.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { user, isSignedIn, signOut } = useInsforge();\n *\n * if (!isSignedIn) return <SignIn />;\n *\n * return (\n * <div>\n * <p>Welcome {user.email}</p>\n * <button onClick={signOut}>Sign Out</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInsforge(): InsforgeContextValue {\n const context: InsforgeContextValue | undefined = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: undefined,\n userId: undefined,\n isLoaded: false,\n isSignedIn: undefined,\n setUser: () => {},\n signIn: () => Promise.resolve({ error: 'SSR mode' }),\n signUp: () => Promise.resolve({ error: 'SSR mode' }),\n signOut: () => Promise.resolve(),\n updateUser: () => Promise.resolve({ error: 'SSR mode' }),\n reloadAuth: () => Promise.resolve({ success: false, error: 'SSR mode' }),\n resendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve({ error: 'SSR mode' }),\n exchangeResetPasswordToken: () => Promise.resolve({ error: { message: 'SSR mode' } }),\n loginWithOAuth: () => Promise.resolve(),\n getPublicAuthConfig: () => Promise.resolve(null),\n baseUrl: '',\n afterSignInUrl: '/',\n };\n }\n\n return context;\n}\n","import { CreateSessionResponse, CreateUserResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to access authentication methods\n *\n * @returns Object containing:\n * - `signIn`: Function to sign in with email and password\n * - `signUp`: Function to sign up with email and password\n * - `signOut`: Function to sign out the current user\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `isSignedIn`: Boolean indicating if user is currently signed in\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { signIn, signUp, signOut, isLoaded, isSignedIn } = useAuth();\n *\n * async function handleLogin(email: string, password: string) {\n * try {\n * await signIn(email, password);\n * // User is now signed in\n * } catch (error) {\n * console.error('Sign in failed:', error);\n * }\n * }\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); handleLogin(email, password); }}>\n * {/* form fields *\\/}\n * </form>\n * );\n * }\n * ```\n */\nexport function useAuth(): {\n signIn: (\n email: string,\n password: string\n ) => Promise<CreateSessionResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signUp: (\n email: string,\n password: string\n ) => Promise<CreateUserResponse | { error: string; statusCode?: number; errorCode?: string }>;\n signOut: () => Promise<void>;\n isLoaded: boolean;\n isSignedIn: boolean | undefined;\n} {\n const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { InsforgeUser } from '@insforge/shared';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\nexport interface useUserReturn {\n user: InsforgeUser | null | undefined;\n isLoaded: boolean;\n updateUser: (data: InsforgeUser['profile']) => Promise<{ error: string } | null>;\n setUser: (user: InsforgeUser | null) => void;\n}\n\n/**\n * Hook to access current user data\n *\n * @returns Object containing:\n * - `user`: Current user object (InsforgeUser | null)\n * - `isLoaded`: Boolean indicating if auth state has been loaded\n * - `updateUser`: Function to update user profile data (returns { error: string } | null)\n * - `setUser`: Internal function to manually set user state\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { user, isLoaded, updateUser } = useUser();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n * if (!user) return <div>Not signed in</div>;\n *\n * async function handleUpdate(name: string) {\n * const result = await updateUser({ name });\n * if (result?.error) {\n * console.error(result.error);\n * } else {\n * console.log('User updated successfully');\n * }\n * }\n *\n * return (\n * <div>\n * <p>Email: {user.email}</p>\n * {user.name && <p>Name: {user.name}</p>}\n * {user.avatarUrl && <img src={user.avatarUrl} alt=\"Avatar\" />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useUser(): useUserReturn {\n const { user, isLoaded, updateUser, setUser } = useInsforge();\n return { user, isLoaded, updateUser, setUser };\n}\n","import { useState, useEffect } from 'react';\nimport type { GetPublicAuthConfigResponse } from '@insforge/shared-schemas';\nimport { useInsforge } from '../provider/InsforgeProvider';\n\n/**\n * Hook to get all public authentication configuration (OAuth + Email) from Insforge backend\n *\n * **IMPORTANT: This hook should ONLY be used in SignIn and SignUp components.**\n *\n * This hook lazily fetches all public authentication configuration from the backend\n * only when the component mounts. Using it in other components will cause unnecessary\n * API calls on every page load.\n *\n * @returns Object containing OAuth providers, email auth config, and loading state\n * - `authConfig`: Email authentication configuration object with password rules\n * - `isLoaded`: Boolean indicating if the config has been fetched\n *\n * @example\n * ```tsx\n * // ✅ Correct usage - only in SignIn/SignUp components\n * function SignUp() {\n * const { authConfig, isLoaded } = usePublicAuthConfig();\n *\n * if (!isLoaded) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <p>OAuth providers: {authConfig?.oauthProviders.length}</p>\n * <p>Password min length: {authConfig?.passwordMinLength}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @requires Must be used within InsforgeProvider\n */\nexport function usePublicAuthConfig(): {\n authConfig: GetPublicAuthConfigResponse | null;\n isLoaded: boolean;\n} {\n const { getPublicAuthConfig } = useInsforge();\n const [authConfig, setAuthConfig] = useState<GetPublicAuthConfigResponse | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n async function fetchConfig() {\n const result = await getPublicAuthConfig();\n if (result) {\n setAuthConfig(result);\n } else {\n console.error('[usePublicAuthConfig] Failed to get public auth config');\n setAuthConfig(null);\n }\n setIsLoaded(true);\n }\n\n void fetchConfig();\n }, [getPublicAuthConfig]);\n\n return { authConfig, isLoaded };\n}\n"]}
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
5
5
  if (!document.getElementById(styleId)) {
6
6
  const style = document.createElement('style');
7
7
  style.id = styleId;
8
- style.textContent = "/**\r\n * InsForge React Component Library Styles\r\n * Traditional CSS with scoped class names (no Tailwind)\r\n */\r\n\r\n/* ============================================\r\n FONTS\r\n ============================================ */\r\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');\r\n@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap');\r\n\r\n/* ============================================\r\n CSS VARIABLES\r\n ============================================ */\r\n:root {\r\n /* Colors */\r\n --if-color-primary: #000000;\r\n --if-color-primary-hover: #1f1f1f;\r\n --if-color-text: #000000;\r\n --if-color-text-secondary: #828282;\r\n --if-color-text-muted: #a3a3a3;\r\n --if-color-border: #d4d4d4;\r\n --if-color-border-focus: #000000;\r\n --if-color-bg-white: #ffffff;\r\n --if-color-bg-light: #fafafa;\r\n --if-color-bg-hover: #f9fafb;\r\n --if-color-error: #dc2626;\r\n --if-color-error-bg: #fee2e2;\r\n --if-color-success: #16a34a;\r\n\r\n /* Spacing */\r\n --if-space-1: 0.25rem; /* 4px */\r\n --if-space-2: 0.5rem; /* 8px */\r\n --if-space-3: 0.75rem; /* 12px */\r\n --if-space-4: 1rem; /* 16px */\r\n --if-space-6: 1.5rem; /* 24px */\r\n --if-space-8: 2rem; /* 32px */\r\n\r\n /* Border Radius */\r\n --if-radius-xs: 0.125rem; /* 2px */\r\n --if-radius-sm: 0.25rem; /* 4px */\r\n --if-radius-md: 0.375rem; /* 6px */\r\n --if-radius-lg: 0.5rem; /* 8px */\r\n --if-radius-xl: 0.75rem; /* 12px */\r\n\r\n /* Typography */\r\n --if-font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;\r\n --if-font-family-manrope:\r\n 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;\r\n --if-font-size-xs: 0.75rem; /* 12px */\r\n --if-font-size-sm: 0.875rem; /* 14px */\r\n --if-font-size-base: 1rem; /* 16px */\r\n --if-font-size-lg: 1.125rem; /* 18px */\r\n --if-font-size-xl: 1.25rem; /* 20px */\r\n --if-font-size-2xl: 1.5rem; /* 24px */\r\n\r\n /* Shadows */\r\n --if-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\r\n --if-shadow-md: 0 1px 2px 0 rgba(0, 0, 0, 0.1);\r\n --if-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\r\n\r\n /* Transitions */\r\n --if-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);\r\n --if-transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* ============================================\r\n AUTH CONTAINER\r\n ============================================ */\r\n.if-authContainer {\r\n width: 100%;\r\n max-width: 400px;\r\n border-radius: var(--if-radius-xl);\r\n overflow: hidden;\r\n box-shadow: var(--if-shadow-lg);\r\n}\r\n\r\n.if-authCard {\r\n background-color: var(--if-color-bg-white);\r\n padding: var(--if-space-6);\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: stretch;\r\n gap: var(--if-space-6);\r\n}\r\n\r\n/* ============================================\r\n AUTH HEADER\r\n ============================================ */\r\n.if-authHeader {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: flex-start;\r\n align-items: flex-start;\r\n gap: var(--if-space-2);\r\n}\r\n\r\n.if-authHeader-title {\r\n font-size: var(--if-font-size-2xl);\r\n font-weight: 600;\r\n color: var(--if-color-text);\r\n line-height: 2rem;\r\n margin: 0;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-authHeader-subtitle {\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n color: var(--if-color-text-secondary);\r\n line-height: 1.5rem;\r\n margin: 0;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n/* ============================================\r\n FORM FIELD\r\n ============================================ */\r\n.if-formField {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: stretch;\r\n gap: var(--if-space-1);\r\n}\r\n\r\n.if-formField-label {\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n color: var(--if-color-text);\r\n line-height: 1.5rem;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-formField-input {\r\n width: 100%;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--if-space-2);\r\n align-self: stretch;\r\n padding: var(--if-space-2) var(--if-space-3);\r\n border-radius: var(--if-radius-sm);\r\n border: 1px solid var(--if-color-border);\r\n background-color: var(--if-color-bg-white);\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n line-height: 1.25rem;\r\n color: var(--if-color-text);\r\n font-family: var(--if-font-family);\r\n transition: border-color var(--if-transition-base);\r\n}\r\n\r\n.if-formField-input::placeholder {\r\n color: var(--if-color-text-muted);\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n}\r\n\r\n.if-formField-input:focus {\r\n outline: none;\r\n border-color: var(--if-color-border-focus);\r\n}\r\n\r\n/* ============================================\r\n PASSWORD FIELD\r\n ============================================ */\r\n.if-passwordField {\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: stretch;\r\n gap: var(--if-space-1);\r\n}\r\n\r\n.if-passwordField-labelRow {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n\r\n.if-passwordField-label {\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n color: var(--if-color-text);\r\n line-height: 1.5rem;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-passwordField-forgotLink {\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n color: var(--if-color-text-secondary);\r\n text-decoration: none;\r\n transition: color var(--if-transition-fast);\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-passwordField-inputWrapper {\r\n position: relative;\r\n width: 100%;\r\n}\r\n\r\n.if-passwordField-input {\r\n width: 100%;\r\n display: flex;\r\n align-items: center;\r\n align-self: stretch;\r\n padding: var(--if-space-2) var(--if-space-3);\r\n padding-right: 2.5rem; /* Space for toggle button */\r\n border-radius: var(--if-radius-sm);\r\n border: 1px solid var(--if-color-border);\r\n background-color: var(--if-color-bg-white);\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 400;\r\n line-height: 1.25rem;\r\n color: var(--if-color-text);\r\n font-family: var(--if-font-family);\r\n transition: border-color var(--if-transition-base);\r\n}\r\n\r\n.if-passwordField-input::placeholder {\r\n color: var(--if-color-text-muted);\r\n}\r\n\r\n.if-passwordField-input:focus {\r\n outline: none;\r\n border-color: var(--if-color-border-focus);\r\n}\r\n\r\n.if-passwordField-toggleButton {\r\n position: absolute;\r\n right: var(--if-space-1);\r\n top: 50%;\r\n transform: translateY(-50%);\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: var(--if-space-1);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--if-color-text-secondary);\r\n transition: color var(--if-transition-fast);\r\n}\r\n\r\n/* ============================================\r\n SUBMIT BUTTON\r\n ============================================ */\r\n.if-submitButton {\r\n border-radius: var(--if-radius-sm);\r\n background-color: var(--if-color-primary);\r\n height: 2.5rem;\r\n width: 100%;\r\n display: flex;\r\n margin-top: var(--if-space-4);\r\n padding: var(--if-space-2) var(--if-space-4);\r\n justify-content: center;\r\n align-items: center;\r\n gap: 0.625rem;\r\n align-self: stretch;\r\n color: var(--if-color-bg-white);\r\n font-weight: 600;\r\n font-family: var(--if-font-family-manrope);\r\n font-size: var(--if-font-size-base);\r\n line-height: normal;\r\n border: none;\r\n cursor: pointer;\r\n transition: background-color var(--if-transition-base);\r\n}\r\n\r\n.if-submitButton:hover:not(:disabled) {\r\n background-color: var(--if-color-primary-hover);\r\n}\r\n\r\n.if-submitButton:disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n}\r\n\r\n.if-submitButton-icon {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n}\r\n\r\n/* Spinner animation */\r\n@keyframes if-spin {\r\n from {\r\n transform: rotate(0deg);\r\n }\r\n to {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n\r\n.if-submitButton-spinner {\r\n animation: if-spin 1s linear infinite;\r\n}\r\n\r\n/* ============================================\r\n OAUTH BUTTON\r\n ============================================ */\r\n.if-oauthButton {\r\n display: flex;\r\n width: 100%;\r\n height: 2.25rem;\r\n padding: var(--if-space-2) var(--if-space-3);\r\n flex-direction: row;\r\n justify-content: center;\r\n align-items: center;\r\n gap: var(--if-space-3);\r\n border-radius: var(--if-radius-md);\r\n border: 1px solid #e4e4e7;\r\n background-color: var(--if-color-bg-white);\r\n box-shadow: var(--if-shadow-md);\r\n color: #09090b;\r\n text-align: center;\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 500;\r\n line-height: 1.25rem;\r\n cursor: pointer;\r\n transition: all var(--if-transition-base);\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-oauthButton:hover:not(:disabled) {\r\n background-color: var(--if-color-bg-hover);\r\n border-color: #9ca3af;\r\n}\r\n\r\n.if-oauthButton:disabled {\r\n opacity: 0.6;\r\n cursor: not-allowed;\r\n}\r\n\r\n.if-oauthButton-icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n}\r\n\r\n.if-oauthButton-full {\r\n justify-content: center;\r\n}\r\n\r\n.if-oauthButton-short {\r\n justify-content: center;\r\n padding: var(--if-space-2);\r\n gap: var(--if-space-2);\r\n}\r\n\r\n.if-oauthButton-icon-only {\r\n justify-content: center;\r\n gap: 0;\r\n}\r\n\r\n/* ============================================\r\n OAUTH PROVIDERS CONTAINER\r\n ============================================ */\r\n.if-oauthProviders {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--if-space-3);\r\n width: 100%;\r\n}\r\n\r\n/* ============================================\r\n AUTH LINK\r\n ============================================ */\r\n.if-authLink {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n gap: var(--if-space-1);\r\n font-size: var(--if-font-size-sm);\r\n color: var(--if-color-text-secondary);\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-authLink-text {\r\n font-weight: 400;\r\n}\r\n\r\n.if-authLink-link {\r\n font-weight: 600;\r\n color: var(--if-color-text);\r\n text-decoration: none;\r\n transition: opacity var(--if-transition-fast);\r\n}\r\n\r\n/* ============================================\r\n AUTH DIVIDER\r\n ============================================ */\r\n.if-authDivider {\r\n display: flex;\r\n align-items: center;\r\n text-align: center;\r\n width: 100%;\r\n gap: var(--if-space-3);\r\n}\r\n\r\n.if-authDivider-line {\r\n flex: 1;\r\n border-top: 1px solid var(--if-color-border);\r\n}\r\n\r\n.if-authDivider-text {\r\n font-size: var(--if-font-size-sm);\r\n color: var(--if-color-text-secondary);\r\n font-weight: 400;\r\n font-family: var(--if-font-family-manrope);\r\n}\r\n\r\n/* ============================================\r\n ERROR BANNER\r\n ============================================ */\r\n.if-errorBanner {\r\n padding: var(--if-space-3);\r\n background-color: var(--if-color-error-bg);\r\n border-radius: var(--if-radius-md);\r\n border: 1px solid var(--if-color-error);\r\n}\r\n\r\n.if-errorBanner-content {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--if-space-2);\r\n}\r\n\r\n.if-errorBanner-icon {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n flex-shrink: 0;\r\n color: var(--if-color-error);\r\n}\r\n\r\n.if-errorBanner-text {\r\n font-size: var(--if-font-size-sm);\r\n color: var(--if-color-error);\r\n font-weight: 400;\r\n font-family: var(--if-font-family);\r\n margin: 0;\r\n}\r\n\r\n/* ============================================\r\n AUTH BRANDING\r\n ============================================ */\r\n.if-authBranding {\r\n background-color: var(--if-color-bg-light);\r\n padding: var(--if-space-4) var(--if-space-2);\r\n display: flex;\r\n flex-direction: row;\r\n justify-content: center;\r\n align-items: center;\r\n gap: var(--if-space-1);\r\n}\r\n\r\n.if-authBranding-text {\r\n font-size: var(--if-font-size-xs);\r\n font-weight: 500;\r\n color: var(--if-color-text);\r\n font-family: var(--if-font-family-manrope);\r\n margin: 0;\r\n}\r\n\r\n/* ============================================\r\n VERIFICATION CODE INPUT\r\n ============================================ */\r\n.if-verificationCode-inputContainer {\r\n display: flex;\r\n gap: var(--if-space-3);\r\n justify-content: center;\r\n}\r\n\r\n.if-verificationCode-input {\r\n width: 3rem;\r\n height: 3rem;\r\n text-align: center;\r\n font-size: var(--if-font-size-base);\r\n font-weight: 600;\r\n border: 1px solid var(--if-color-border);\r\n border-radius: var(--if-radius-sm);\r\n background-color: var(--if-color-bg-white);\r\n color: var(--if-color-text);\r\n transition: border-color var(--if-transition-base);\r\n font-family: var(--if-font-family-manrope);\r\n}\r\n\r\n.if-verificationCode-input:focus {\r\n outline: none;\r\n border-color: var(--if-color-border-focus);\r\n}\r\n\r\n/* ============================================\r\n VERIFICATION STEP\r\n ============================================ */\r\n.if-verificationStep {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--if-space-6);\r\n align-items: stretch;\r\n}\r\n\r\n.if-verificationStep-descriptionContainer {\r\n width: 100%;\r\n background-color: #f5f5f5;\r\n border-radius: var(--if-radius-lg);\r\n padding: var(--if-space-3) var(--if-space-3) var(--if-space-6) var(--if-space-3);\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--if-space-3);\r\n}\r\n\r\n.if-verificationStep-descriptionTitle {\r\n color: var(--black, #000);\r\n font-family: var(--if-font-family);\r\n font-size: var(--if-font-size-base);\r\n font-style: normal;\r\n font-weight: 600;\r\n line-height: 24px;\r\n}\r\n\r\n.if-verificationStep-description,\r\n.if-verificationStep-codeDescription {\r\n font-size: var(--if-font-size-sm);\r\n color: #525252;\r\n text-align: left;\r\n font-family: var(--if-font-family);\r\n margin: 0;\r\n}\r\n\r\n.if-verificationLink-email,\r\n.if-verificationCode-email {\r\n font-weight: 500;\r\n color: var(--if-color-text);\r\n}\r\n\r\n.if-verificationStep-codeContainer {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 40px;\r\n}\r\n\r\n.if-verificationStep-codeInputWrapper {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--if-space-6);\r\n}\r\n\r\n.if-verificationStep-verifyingText {\r\n font-size: var(--if-font-size-sm);\r\n color: var(--if-color-text-secondary);\r\n text-align: center;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-verificationStep-resendContainer {\r\n width: 100%;\r\n font-size: var(--if-font-size-sm);\r\n text-align: center;\r\n color: var(--if-color-text-secondary);\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-verificationStep-resendButton {\r\n color: var(--if-color-text);\r\n font-weight: 500;\r\n transition: all var(--if-transition-base);\r\n background: none;\r\n border: none;\r\n padding: 0;\r\n font-family: var(--if-font-family);\r\n font-size: var(--if-font-size-sm);\r\n}\r\n\r\n.if-verificationStep-resendButton:not(:disabled) {\r\n cursor: pointer;\r\n}\r\n\r\n.if-verificationStep-resendButton:disabled {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n/* ============================================\r\n PASSWORD STRENGTH INDICATOR\r\n ============================================ */\r\n.if-passwordStrength {\r\n margin-top: var(--if-space-2);\r\n}\r\n\r\n.if-passwordStrength-fill {\r\n height: 100%;\r\n transition:\r\n width var(--if-transition-base),\r\n background-color var(--if-transition-base);\r\n}\r\n\r\n.if-passwordStrength-text {\r\n font-size: var(--if-font-size-xs);\r\n color: var(--if-color-text-secondary);\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-passwordStrength-requirements {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--if-space-2);\r\n font-size: var(--if-font-size-sm);\r\n color: #525252;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-passwordStrength-requirement {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--if-space-2);\r\n}\r\n\r\n/* ============================================\r\n FORM CONTAINER\r\n ============================================ */\r\n.if-form {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: stretch;\r\n justify-content: center;\r\n gap: var(--if-space-6);\r\n}\r\n\r\n/* ============================================\r\n USER BUTTON\r\n ============================================ */\r\n.if-userButton-container {\r\n position: relative;\r\n display: inline-block;\r\n}\r\n\r\n.if-userButton {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--if-space-2);\r\n padding: var(--if-space-2);\r\n border-radius: 9999px;\r\n background-color: transparent;\r\n border: none;\r\n cursor: pointer;\r\n transition: all var(--if-transition-base);\r\n}\r\n\r\n.if-userButton:hover {\r\n opacity: 0.8;\r\n}\r\n\r\n.if-userButton-detailed {\r\n background-color: var(--if-color-bg-white);\r\n border: 1px solid var(--if-color-border);\r\n border-radius: var(--if-radius-sm);\r\n padding: var(--if-space-2);\r\n}\r\n\r\n.if-userButton-detailed:hover {\r\n background-color: var(--if-color-bg-light);\r\n opacity: 1;\r\n}\r\n\r\n.if-userButton-avatar {\r\n width: 2rem;\r\n height: 2rem;\r\n border-radius: 9999px;\r\n background-color: var(--if-color-primary);\r\n color: var(--if-color-bg-white);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-weight: 600;\r\n font-size: var(--if-font-size-sm);\r\n font-family: var(--if-font-family);\r\n overflow: hidden;\r\n}\r\n\r\n.if-userButton-avatarImage {\r\n border-radius: 9999px;\r\n object-fit: cover;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.if-userButton-avatarInitials {\r\n color: var(--if-color-bg-white);\r\n font-weight: 600;\r\n font-size: var(--if-font-size-sm);\r\n}\r\n\r\n.if-userButton-info {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-start;\r\n gap: 0.125rem;\r\n}\r\n\r\n.if-userButton-name {\r\n font-size: var(--if-font-size-sm);\r\n font-weight: 600;\r\n color: var(--if-color-text);\r\n line-height: 1.25rem;\r\n text-align: left;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-userButton-email {\r\n font-size: var(--if-font-size-xs);\r\n color: var(--if-color-text-secondary);\r\n line-height: 1rem;\r\n text-align: left;\r\n font-family: var(--if-font-family);\r\n}\r\n\r\n.if-userButton-menu {\r\n position: absolute;\r\n margin-top: var(--if-space-2);\r\n background-color: var(--if-color-bg-white);\r\n border: 1px solid var(--if-color-border);\r\n border-radius: var(--if-radius-md);\r\n box-shadow: var(--if-shadow-lg);\r\n padding: var(--if-space-2);\r\n min-width: 200px;\r\n z-index: 50;\r\n}\r\n\r\n.if-userButton-menuItem {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--if-space-2);\r\n padding: var(--if-space-2);\r\n border-radius: var(--if-radius-sm);\r\n cursor: pointer;\r\n transition: background-color var(--if-transition-fast);\r\n font-size: var(--if-font-size-sm);\r\n color: var(--if-color-text);\r\n font-family: var(--if-font-family);\r\n background: none;\r\n border: none;\r\n width: 100%;\r\n text-align: left;\r\n}\r\n\r\n.if-userButton-menuItem:hover {\r\n background-color: var(--if-color-bg-light);\r\n}\r\n\r\n.if-userButton-menuItem-signout {\r\n color: var(--if-color-error);\r\n}\r\n\r\n.if-userButton-menuItem-icon {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n}\r\n\r\n/* ============================================\r\n EMAIL VERIFICATION STATUS\r\n ============================================ */\r\n.if-verifyStatus-container {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--if-space-6);\r\n}\r\n\r\n.if-verifyStatus-container-stretch {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: stretch;\r\n justify-content: center;\r\n gap: var(--if-space-6);\r\n}\r\n\r\n.if-verifyStatus-spinner {\r\n border-radius: 9999px;\r\n height: 3rem;\r\n width: 3rem;\r\n border-bottom: 2px solid var(--if-color-primary);\r\n}\r\n\r\n.if-verifyStatus-successContent {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: var(--if-space-4);\r\n}\r\n\r\n.if-verifyStatus-successIcon {\r\n width: 4rem;\r\n height: 4rem;\r\n border-radius: 9999px;\r\n background-color: #d1fae5;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.if-verifyStatus-successIconSvg {\r\n width: 2rem;\r\n height: 2rem;\r\n color: #059669;\r\n}\r\n\r\n.if-verifyStatus-textCenter {\r\n text-align: center;\r\n}\r\n\r\n/* ============================================\r\n UTILITY CLASSES\r\n ============================================ */\r\n.if-hidden {\r\n display: none;\r\n}\r\n\r\n.if-visuallyHidden {\r\n position: absolute;\r\n width: 1px;\r\n height: 1px;\r\n padding: 0;\r\n margin: -1px;\r\n overflow: hidden;\r\n clip: rect(0, 0, 0, 0);\r\n white-space: nowrap;\r\n border-width: 0;\r\n}\r\n";
8
+ style.textContent = "/**\n * InsForge React Component Library Styles\n * Traditional CSS with scoped class names (no Tailwind)\n */\n\n/* ============================================\n FONTS\n ============================================ */\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');\n@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap');\n\n/* ============================================\n CSS VARIABLES\n ============================================ */\n:root {\n /* Colors */\n --if-color-primary: #000000;\n --if-color-primary-hover: #1f1f1f;\n --if-color-text: #000000;\n --if-color-text-secondary: #828282;\n --if-color-text-muted: #a3a3a3;\n --if-color-border: #d4d4d4;\n --if-color-border-focus: #000000;\n --if-color-bg-white: #ffffff;\n --if-color-bg-light: #fafafa;\n --if-color-bg-hover: #f9fafb;\n --if-color-error: #dc2626;\n --if-color-error-bg: #fee2e2;\n --if-color-success: #16a34a;\n\n /* Spacing */\n --if-space-1: 0.25rem; /* 4px */\n --if-space-2: 0.5rem; /* 8px */\n --if-space-3: 0.75rem; /* 12px */\n --if-space-4: 1rem; /* 16px */\n --if-space-6: 1.5rem; /* 24px */\n --if-space-8: 2rem; /* 32px */\n\n /* Border Radius */\n --if-radius-xs: 0.125rem; /* 2px */\n --if-radius-sm: 0.25rem; /* 4px */\n --if-radius-md: 0.375rem; /* 6px */\n --if-radius-lg: 0.5rem; /* 8px */\n --if-radius-xl: 0.75rem; /* 12px */\n\n /* Typography */\n --if-font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;\n --if-font-family-manrope:\n 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;\n --if-font-size-xs: 0.75rem; /* 12px */\n --if-font-size-sm: 0.875rem; /* 14px */\n --if-font-size-base: 1rem; /* 16px */\n --if-font-size-lg: 1.125rem; /* 18px */\n --if-font-size-xl: 1.25rem; /* 20px */\n --if-font-size-2xl: 1.5rem; /* 24px */\n\n /* Shadows */\n --if-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n --if-shadow-md: 0 1px 2px 0 rgba(0, 0, 0, 0.1);\n --if-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\n\n /* Transitions */\n --if-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n --if-transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* ============================================\n AUTH CONTAINER\n ============================================ */\n.if-authContainer {\n width: 100%;\n max-width: 400px;\n border-radius: var(--if-radius-xl);\n overflow: hidden;\n box-shadow: var(--if-shadow-lg);\n}\n\n.if-authCard {\n background-color: var(--if-color-bg-white);\n padding: var(--if-space-6);\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: var(--if-space-6);\n}\n\n/* ============================================\n AUTH HEADER\n ============================================ */\n.if-authHeader {\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n gap: var(--if-space-2);\n}\n\n.if-authHeader-title {\n font-size: var(--if-font-size-2xl);\n font-weight: 600;\n color: var(--if-color-text);\n line-height: 2rem;\n margin: 0;\n font-family: var(--if-font-family);\n}\n\n.if-authHeader-subtitle {\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n color: var(--if-color-text-secondary);\n line-height: 1.5rem;\n margin: 0;\n font-family: var(--if-font-family);\n}\n\n/* ============================================\n FORM FIELD\n ============================================ */\n.if-formField {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: var(--if-space-1);\n}\n\n.if-formField-label {\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n color: var(--if-color-text);\n line-height: 1.5rem;\n font-family: var(--if-font-family);\n}\n\n.if-formField-input {\n width: 100%;\n display: flex;\n align-items: center;\n gap: var(--if-space-2);\n align-self: stretch;\n padding: var(--if-space-2) var(--if-space-3);\n border-radius: var(--if-radius-sm);\n border: 1px solid var(--if-color-border);\n background-color: var(--if-color-bg-white);\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n line-height: 1.25rem;\n color: var(--if-color-text);\n font-family: var(--if-font-family);\n transition: border-color var(--if-transition-base);\n}\n\n.if-formField-input::placeholder {\n color: var(--if-color-text-muted);\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n}\n\n.if-formField-input:focus {\n outline: none;\n border-color: var(--if-color-border-focus);\n}\n\n/* ============================================\n PASSWORD FIELD\n ============================================ */\n.if-passwordField {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: stretch;\n gap: var(--if-space-1);\n}\n\n.if-passwordField-labelRow {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.if-passwordField-label {\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n color: var(--if-color-text);\n line-height: 1.5rem;\n font-family: var(--if-font-family);\n}\n\n.if-passwordField-forgotLink {\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n color: var(--if-color-text-secondary);\n text-decoration: none;\n transition: color var(--if-transition-fast);\n font-family: var(--if-font-family);\n}\n\n.if-passwordField-inputWrapper {\n position: relative;\n width: 100%;\n}\n\n.if-passwordField-input {\n width: 100%;\n display: flex;\n align-items: center;\n align-self: stretch;\n padding: var(--if-space-2) var(--if-space-3);\n padding-right: 2.5rem; /* Space for toggle button */\n border-radius: var(--if-radius-sm);\n border: 1px solid var(--if-color-border);\n background-color: var(--if-color-bg-white);\n font-size: var(--if-font-size-sm);\n font-weight: 400;\n line-height: 1.25rem;\n color: var(--if-color-text);\n font-family: var(--if-font-family);\n transition: border-color var(--if-transition-base);\n}\n\n.if-passwordField-input::placeholder {\n color: var(--if-color-text-muted);\n}\n\n.if-passwordField-input:focus {\n outline: none;\n border-color: var(--if-color-border-focus);\n}\n\n.if-passwordField-toggleButton {\n position: absolute;\n right: var(--if-space-1);\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n cursor: pointer;\n padding: var(--if-space-1);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--if-color-text-secondary);\n transition: color var(--if-transition-fast);\n}\n\n/* ============================================\n SUBMIT BUTTON\n ============================================ */\n.if-submitButton {\n border-radius: var(--if-radius-sm);\n background-color: var(--if-color-primary);\n height: 2.5rem;\n width: 100%;\n display: flex;\n margin-top: var(--if-space-4);\n padding: var(--if-space-2) var(--if-space-4);\n justify-content: center;\n align-items: center;\n gap: 0.625rem;\n align-self: stretch;\n color: var(--if-color-bg-white);\n font-weight: 600;\n font-family: var(--if-font-family-manrope);\n font-size: var(--if-font-size-base);\n line-height: normal;\n border: none;\n cursor: pointer;\n transition: background-color var(--if-transition-base);\n}\n\n.if-submitButton:hover:not(:disabled) {\n background-color: var(--if-color-primary-hover);\n}\n\n.if-submitButton:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.if-submitButton-icon {\n width: 1.25rem;\n height: 1.25rem;\n}\n\n/* Spinner animation */\n@keyframes if-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n.if-submitButton-spinner {\n animation: if-spin 1s linear infinite;\n}\n\n/* ============================================\n OAUTH BUTTON\n ============================================ */\n.if-oauthButton {\n display: flex;\n width: 100%;\n height: 2.25rem;\n padding: var(--if-space-2) var(--if-space-3);\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: var(--if-space-3);\n border-radius: var(--if-radius-md);\n border: 1px solid #e4e4e7;\n background-color: var(--if-color-bg-white);\n box-shadow: var(--if-shadow-md);\n color: #09090b;\n text-align: center;\n font-size: var(--if-font-size-sm);\n font-weight: 500;\n line-height: 1.25rem;\n cursor: pointer;\n transition: all var(--if-transition-base);\n font-family: var(--if-font-family);\n}\n\n.if-oauthButton:hover:not(:disabled) {\n background-color: var(--if-color-bg-hover);\n border-color: #9ca3af;\n}\n\n.if-oauthButton:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.if-oauthButton-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 1.125rem;\n height: 1.125rem;\n}\n\n.if-oauthButton-full {\n justify-content: center;\n}\n\n.if-oauthButton-short {\n justify-content: center;\n padding: var(--if-space-2);\n gap: var(--if-space-2);\n}\n\n.if-oauthButton-icon-only {\n justify-content: center;\n gap: 0;\n}\n\n/* ============================================\n OAUTH PROVIDERS CONTAINER\n ============================================ */\n.if-oauthProviders {\n display: flex;\n flex-direction: column;\n gap: var(--if-space-3);\n width: 100%;\n}\n\n/* ============================================\n AUTH LINK\n ============================================ */\n.if-authLink {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: var(--if-space-1);\n font-size: var(--if-font-size-sm);\n color: var(--if-color-text-secondary);\n font-family: var(--if-font-family);\n}\n\n.if-authLink-text {\n font-weight: 400;\n}\n\n.if-authLink-link {\n font-weight: 600;\n color: var(--if-color-text);\n text-decoration: none;\n transition: opacity var(--if-transition-fast);\n}\n\n/* ============================================\n AUTH DIVIDER\n ============================================ */\n.if-authDivider {\n display: flex;\n align-items: center;\n text-align: center;\n width: 100%;\n gap: var(--if-space-3);\n}\n\n.if-authDivider-line {\n flex: 1;\n border-top: 1px solid var(--if-color-border);\n}\n\n.if-authDivider-text {\n font-size: var(--if-font-size-sm);\n color: var(--if-color-text-secondary);\n font-weight: 400;\n font-family: var(--if-font-family-manrope);\n}\n\n/* ============================================\n ERROR BANNER\n ============================================ */\n.if-errorBanner {\n padding: var(--if-space-3);\n background-color: var(--if-color-error-bg);\n border-radius: var(--if-radius-md);\n border: 1px solid var(--if-color-error);\n}\n\n.if-errorBanner-content {\n display: flex;\n align-items: center;\n gap: var(--if-space-2);\n}\n\n.if-errorBanner-icon {\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n color: var(--if-color-error);\n}\n\n.if-errorBanner-text {\n font-size: var(--if-font-size-sm);\n color: var(--if-color-error);\n font-weight: 400;\n font-family: var(--if-font-family);\n margin: 0;\n}\n\n/* ============================================\n AUTH BRANDING\n ============================================ */\n.if-authBranding {\n background-color: var(--if-color-bg-light);\n padding: var(--if-space-4) var(--if-space-2);\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: var(--if-space-1);\n}\n\n.if-authBranding-text {\n font-size: var(--if-font-size-xs);\n font-weight: 500;\n color: var(--if-color-text);\n font-family: var(--if-font-family-manrope);\n margin: 0;\n}\n\n/* ============================================\n VERIFICATION CODE INPUT\n ============================================ */\n.if-verificationCode-inputContainer {\n display: flex;\n gap: var(--if-space-3);\n justify-content: center;\n}\n\n.if-verificationCode-input {\n width: 3rem;\n height: 3rem;\n text-align: center;\n font-size: var(--if-font-size-base);\n font-weight: 600;\n border: 1px solid var(--if-color-border);\n border-radius: var(--if-radius-sm);\n background-color: var(--if-color-bg-white);\n color: var(--if-color-text);\n transition: border-color var(--if-transition-base);\n font-family: var(--if-font-family-manrope);\n}\n\n.if-verificationCode-input:focus {\n outline: none;\n border-color: var(--if-color-border-focus);\n}\n\n/* ============================================\n VERIFICATION STEP\n ============================================ */\n.if-verificationStep {\n display: flex;\n flex-direction: column;\n gap: var(--if-space-6);\n align-items: stretch;\n}\n\n.if-verificationStep-descriptionContainer {\n width: 100%;\n background-color: #f5f5f5;\n border-radius: var(--if-radius-lg);\n padding: var(--if-space-3) var(--if-space-3) var(--if-space-6) var(--if-space-3);\n display: flex;\n flex-direction: column;\n gap: var(--if-space-3);\n}\n\n.if-verificationStep-descriptionTitle {\n color: var(--black, #000);\n font-family: var(--if-font-family);\n font-size: var(--if-font-size-base);\n font-style: normal;\n font-weight: 600;\n line-height: 24px;\n}\n\n.if-verificationStep-description,\n.if-verificationStep-codeDescription {\n font-size: var(--if-font-size-sm);\n color: #525252;\n text-align: left;\n font-family: var(--if-font-family);\n margin: 0;\n}\n\n.if-verificationLink-email,\n.if-verificationCode-email {\n font-weight: 500;\n color: var(--if-color-text);\n}\n\n.if-verificationStep-codeContainer {\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: 40px;\n}\n\n.if-verificationStep-codeInputWrapper {\n display: flex;\n flex-direction: column;\n gap: var(--if-space-6);\n}\n\n.if-verificationStep-verifyingText {\n font-size: var(--if-font-size-sm);\n color: var(--if-color-text-secondary);\n text-align: center;\n font-family: var(--if-font-family);\n}\n\n.if-verificationStep-resendContainer {\n width: 100%;\n font-size: var(--if-font-size-sm);\n text-align: center;\n color: var(--if-color-text-secondary);\n font-family: var(--if-font-family);\n}\n\n.if-verificationStep-resendButton {\n color: var(--if-color-text);\n font-weight: 500;\n transition: all var(--if-transition-base);\n background: none;\n border: none;\n padding: 0;\n font-family: var(--if-font-family);\n font-size: var(--if-font-size-sm);\n}\n\n.if-verificationStep-resendButton:not(:disabled) {\n cursor: pointer;\n}\n\n.if-verificationStep-resendButton:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* ============================================\n PASSWORD STRENGTH INDICATOR\n ============================================ */\n.if-passwordStrength {\n margin-top: var(--if-space-2);\n}\n\n.if-passwordStrength-fill {\n height: 100%;\n transition:\n width var(--if-transition-base),\n background-color var(--if-transition-base);\n}\n\n.if-passwordStrength-text {\n font-size: var(--if-font-size-xs);\n color: var(--if-color-text-secondary);\n font-family: var(--if-font-family);\n}\n\n.if-passwordStrength-requirements {\n display: flex;\n flex-direction: column;\n gap: var(--if-space-2);\n font-size: var(--if-font-size-sm);\n color: #525252;\n font-family: var(--if-font-family);\n}\n\n.if-passwordStrength-requirement {\n display: flex;\n align-items: center;\n gap: var(--if-space-2);\n}\n\n/* ============================================\n FORM CONTAINER\n ============================================ */\n.if-form {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n justify-content: center;\n gap: var(--if-space-6);\n}\n\n/* ============================================\n USER BUTTON\n ============================================ */\n.if-userButton-container {\n position: relative;\n display: inline-block;\n}\n\n.if-userButton {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--if-space-2);\n padding: var(--if-space-2);\n border-radius: 9999px;\n background-color: transparent;\n border: none;\n cursor: pointer;\n transition: all var(--if-transition-base);\n}\n\n.if-userButton:hover {\n opacity: 0.8;\n}\n\n.if-userButton-detailed {\n background-color: var(--if-color-bg-white);\n border: 1px solid var(--if-color-border);\n border-radius: var(--if-radius-sm);\n padding: var(--if-space-2);\n}\n\n.if-userButton-detailed:hover {\n background-color: var(--if-color-bg-light);\n opacity: 1;\n}\n\n.if-userButton-avatar {\n width: 2rem;\n height: 2rem;\n border-radius: 9999px;\n background-color: var(--if-color-primary);\n color: var(--if-color-bg-white);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: var(--if-font-size-sm);\n font-family: var(--if-font-family);\n overflow: hidden;\n}\n\n.if-userButton-avatarImage {\n border-radius: 9999px;\n object-fit: cover;\n width: 100%;\n height: 100%;\n}\n\n.if-userButton-avatarInitials {\n color: var(--if-color-bg-white);\n font-weight: 600;\n font-size: var(--if-font-size-sm);\n}\n\n.if-userButton-info {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 0.125rem;\n}\n\n.if-userButton-name {\n font-size: var(--if-font-size-sm);\n font-weight: 600;\n color: var(--if-color-text);\n line-height: 1.25rem;\n text-align: left;\n font-family: var(--if-font-family);\n}\n\n.if-userButton-email {\n font-size: var(--if-font-size-xs);\n color: var(--if-color-text-secondary);\n line-height: 1rem;\n text-align: left;\n font-family: var(--if-font-family);\n}\n\n.if-userButton-menu {\n position: absolute;\n margin-top: var(--if-space-2);\n background-color: var(--if-color-bg-white);\n border: 1px solid var(--if-color-border);\n border-radius: var(--if-radius-md);\n box-shadow: var(--if-shadow-lg);\n padding: var(--if-space-2);\n min-width: 200px;\n z-index: 50;\n}\n\n.if-userButton-menuItem {\n display: flex;\n align-items: center;\n gap: var(--if-space-2);\n padding: var(--if-space-2);\n border-radius: var(--if-radius-sm);\n cursor: pointer;\n transition: background-color var(--if-transition-fast);\n font-size: var(--if-font-size-sm);\n color: var(--if-color-text);\n font-family: var(--if-font-family);\n background: none;\n border: none;\n width: 100%;\n text-align: left;\n}\n\n.if-userButton-menuItem:hover {\n background-color: var(--if-color-bg-light);\n}\n\n.if-userButton-menuItem-signout {\n color: var(--if-color-error);\n}\n\n.if-userButton-menuItem-icon {\n width: 1.25rem;\n height: 1.25rem;\n}\n\n/* ============================================\n EMAIL VERIFICATION STATUS\n ============================================ */\n.if-verifyStatus-container {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--if-space-6);\n}\n\n.if-verifyStatus-container-stretch {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n justify-content: center;\n gap: var(--if-space-6);\n}\n\n.if-verifyStatus-spinner {\n border-radius: 9999px;\n height: 3rem;\n width: 3rem;\n border-bottom: 2px solid var(--if-color-primary);\n}\n\n.if-verifyStatus-successContent {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--if-space-4);\n}\n\n.if-verifyStatus-successIcon {\n width: 4rem;\n height: 4rem;\n border-radius: 9999px;\n background-color: #d1fae5;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.if-verifyStatus-successIconSvg {\n width: 2rem;\n height: 2rem;\n color: #059669;\n}\n\n.if-verifyStatus-textCenter {\n text-align: center;\n}\n\n/* ============================================\n UTILITY CLASSES\n ============================================ */\n.if-hidden {\n display: none;\n}\n\n.if-visuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n";
9
9
  if (document.head) {
10
10
  document.head.appendChild(style);
11
11
  }
@@ -4186,16 +4186,9 @@ function SignIn({ onError, ...uiProps }) {
4186
4186
  }
4187
4187
  throw new Error(result.error);
4188
4188
  }
4189
- const { user, accessToken, redirectTo, csrfToken } = result;
4189
+ const { user, redirectTo } = result;
4190
4190
  if (user) {
4191
4191
  const finalUrl = new URL(redirectTo || redirectUrl || "", window.location.origin);
4192
- finalUrl.searchParams.set("access_token", accessToken);
4193
- finalUrl.searchParams.set("user_id", user.id);
4194
- finalUrl.searchParams.set("email", user.email);
4195
- finalUrl.searchParams.set("name", user.profile?.name || "");
4196
- if (csrfToken) {
4197
- finalUrl.searchParams.set("csrf_token", csrfToken);
4198
- }
4199
4192
  window.location.href = finalUrl.toString();
4200
4193
  }
4201
4194
  } catch (err) {
@@ -4216,13 +4209,6 @@ function SignIn({ onError, ...uiProps }) {
4216
4209
  throw new Error("Verification failed");
4217
4210
  }
4218
4211
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4219
- finalUrl.searchParams.set("access_token", result.accessToken);
4220
- finalUrl.searchParams.set("user_id", result.user.id);
4221
- finalUrl.searchParams.set("email", result.user.email);
4222
- finalUrl.searchParams.set("name", result.user.profile?.name || "");
4223
- if (result.csrfToken) {
4224
- finalUrl.searchParams.set("csrf_token", result.csrfToken);
4225
- }
4226
4212
  window.location.href = finalUrl.toString();
4227
4213
  } catch (err) {
4228
4214
  const errorMessage = err instanceof Error ? err.message : "Invalid verification code";
@@ -4477,15 +4463,7 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
4477
4463
  return;
4478
4464
  }
4479
4465
  if (result.accessToken && result.user) {
4480
- const csrfToken = result.csrfToken;
4481
4466
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4482
- finalUrl.searchParams.set("access_token", result.accessToken);
4483
- finalUrl.searchParams.set("user_id", result.user.id);
4484
- finalUrl.searchParams.set("email", result.user.email);
4485
- finalUrl.searchParams.set("name", result.user.profile?.name || "");
4486
- if (csrfToken) {
4487
- finalUrl.searchParams.set("csrf_token", csrfToken);
4488
- }
4489
4467
  window.location.href = finalUrl.toString();
4490
4468
  }
4491
4469
  } catch (err) {
@@ -4506,13 +4484,6 @@ function SignUp({ onError, emailRedirectTo, ...uiProps }) {
4506
4484
  throw new Error("Verification failed");
4507
4485
  }
4508
4486
  const finalUrl = new URL(result.redirectTo || redirectUrl || "", window.location.origin);
4509
- finalUrl.searchParams.set("access_token", result.accessToken);
4510
- finalUrl.searchParams.set("user_id", result.user.id);
4511
- finalUrl.searchParams.set("email", result.user.email);
4512
- finalUrl.searchParams.set("name", result.user.profile?.name || "");
4513
- if (result.csrfToken) {
4514
- finalUrl.searchParams.set("csrf_token", result.csrfToken);
4515
- }
4516
4487
  window.location.href = finalUrl.toString();
4517
4488
  } catch (err) {
4518
4489
  const errorMessage = err instanceof Error ? err.message : "Invalid verification code";