@insforge/react 0.6.3 → 0.6.5

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.
package/dist/hooks.cjs CHANGED
@@ -11,9 +11,10 @@ function useInsforge() {
11
11
  const context = react.useContext(react$1.InsforgeContext);
12
12
  if (!context) {
13
13
  return {
14
- user: null,
14
+ user: void 0,
15
+ userId: void 0,
15
16
  isLoaded: false,
16
- isSignedIn: false,
17
+ isSignedIn: void 0,
17
18
  setUser: () => {
18
19
  },
19
20
  signIn: () => Promise.resolve({ error: "SSR mode" }),
@@ -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":"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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 }) => <a href={href}>{children}</a>,\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 { InsforgeContext, type InsforgeContextValue } from '../contexts';\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\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 * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager = useMemo(\n () =>\n InsforgeManager.getInstance({\n baseUrl,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n }),\n [baseUrl, afterSignInUrl, onAuthChange, onSignIn, onSignOut]\n );\n\n // Subscribe to Manager state\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n const unsubscribe = manager.subscribe((newState) => {\n setState(newState);\n });\n\n void manager.initialize();\n\n return unsubscribe;\n }, [manager]);\n\n // Handle OAuth callback\n useEffect(() => {\n if (typeof window !== 'undefined') {\n manager.handleOAuthCallback(state.isLoaded, state.user);\n }\n }, [manager, state.isLoaded, state.user]);\n\n // Create stable method references that delegate to manager\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // State from Manager\n user: state.user,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n\n // Methods delegated to Manager\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string) => manager.signUp(email, password),\n signOut: () => manager.signOut(),\n updateUser: (data: Partial<InsforgeUser>) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n sendVerificationEmail: (email: string) => manager.sendVerificationEmail(email),\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 // Config\n baseUrl: manager.getConfig().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [state, manager]\n );\n\n return <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>;\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\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 = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\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 sendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve(null),\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 { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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"]}
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 }) => <a href={href}>{children}</a>,\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 { InsforgeContext, type InsforgeContextValue } from '../contexts';\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\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 * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager = useMemo(\n () =>\n InsforgeManager.getInstance({\n baseUrl,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n }),\n [baseUrl, afterSignInUrl, onAuthChange, onSignIn, onSignOut]\n );\n\n // Subscribe to Manager state\n // Start with initial state from manager (will be undefined on first render)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState) => {\n setState(newState);\n });\n\n // Initialize auth state only on client side (after hydration)\n // This prevents hydration mismatches by ensuring SSR and client initial render match\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // Handle auth redirect after successful authentication\n useEffect(() => {\n if (typeof window !== 'undefined') {\n manager.handleAuthRedirect(state.isLoaded, state.user);\n }\n }, [manager, state.isLoaded, state.user]);\n\n // Create stable method references that delegate to manager\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // State from Manager\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n\n // Methods delegated to Manager\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string) => manager.signUp(email, password),\n signOut: () => manager.signOut(),\n updateUser: (data: Partial<InsforgeUser>) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n sendVerificationEmail: (email: string) => manager.sendVerificationEmail(email),\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 // Config\n baseUrl: manager.getConfig().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [state, manager]\n );\n\n return <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>;\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\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 = 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 sendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve(null),\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 { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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.d.cts CHANGED
@@ -49,7 +49,7 @@ declare function useAuth(): {
49
49
  }>;
50
50
  signOut: () => Promise<void>;
51
51
  isLoaded: boolean;
52
- isSignedIn: boolean;
52
+ isSignedIn: boolean | undefined;
53
53
  };
54
54
 
55
55
  /**
@@ -89,7 +89,7 @@ declare function useAuth(): {
89
89
  * ```
90
90
  */
91
91
  declare function useUser(): {
92
- user: _insforge_shared.InsforgeUser | null;
92
+ user: _insforge_shared.InsforgeUser | null | undefined;
93
93
  isLoaded: boolean;
94
94
  updateUser: (data: Partial<_insforge_shared.InsforgeUser>) => Promise<{
95
95
  error: string;
package/dist/hooks.d.ts CHANGED
@@ -49,7 +49,7 @@ declare function useAuth(): {
49
49
  }>;
50
50
  signOut: () => Promise<void>;
51
51
  isLoaded: boolean;
52
- isSignedIn: boolean;
52
+ isSignedIn: boolean | undefined;
53
53
  };
54
54
 
55
55
  /**
@@ -89,7 +89,7 @@ declare function useAuth(): {
89
89
  * ```
90
90
  */
91
91
  declare function useUser(): {
92
- user: _insforge_shared.InsforgeUser | null;
92
+ user: _insforge_shared.InsforgeUser | null | undefined;
93
93
  isLoaded: boolean;
94
94
  updateUser: (data: Partial<_insforge_shared.InsforgeUser>) => Promise<{
95
95
  error: string;
package/dist/hooks.js CHANGED
@@ -9,9 +9,10 @@ function useInsforge() {
9
9
  const context = useContext(InsforgeContext);
10
10
  if (!context) {
11
11
  return {
12
- user: null,
12
+ user: void 0,
13
+ userId: void 0,
13
14
  isLoaded: false,
14
- isSignedIn: false,
15
+ isSignedIn: void 0,
15
16
  setUser: () => {
16
17
  },
17
18
  signIn: () => Promise.resolve({ error: "SSR mode" }),
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';\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 }) => <a href={href}>{children}</a>,\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 { InsforgeContext, type InsforgeContextValue } from '../contexts';\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\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 * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager = useMemo(\n () =>\n InsforgeManager.getInstance({\n baseUrl,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n }),\n [baseUrl, afterSignInUrl, onAuthChange, onSignIn, onSignOut]\n );\n\n // Subscribe to Manager state\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n const unsubscribe = manager.subscribe((newState) => {\n setState(newState);\n });\n\n void manager.initialize();\n\n return unsubscribe;\n }, [manager]);\n\n // Handle OAuth callback\n useEffect(() => {\n if (typeof window !== 'undefined') {\n manager.handleOAuthCallback(state.isLoaded, state.user);\n }\n }, [manager, state.isLoaded, state.user]);\n\n // Create stable method references that delegate to manager\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // State from Manager\n user: state.user,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n\n // Methods delegated to Manager\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string) => manager.signUp(email, password),\n signOut: () => manager.signOut(),\n updateUser: (data: Partial<InsforgeUser>) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n sendVerificationEmail: (email: string) => manager.sendVerificationEmail(email),\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 // Config\n baseUrl: manager.getConfig().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [state, manager]\n );\n\n return <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>;\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\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 = useContext(InsforgeContext);\n\n if (!context) {\n return {\n user: null,\n isLoaded: false,\n isSignedIn: false,\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 sendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve(null),\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 { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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"]}
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 }) => <a href={href}>{children}</a>,\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 { InsforgeContext, type InsforgeContextValue } from '../contexts';\n\nexport interface InsforgeProviderProps {\n children: ReactNode;\n baseUrl: string;\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) => Promise<void>;\n onSignOut?: () => Promise<void>;\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 * @example\n * ```tsx\n * // Basic usage (React/Vite)\n * import { InsforgeProvider } from '@insforge/react';\n *\n * export default function App() {\n * return (\n * <InsforgeProvider\n * baseUrl={import.meta.env.VITE_INSFORGE_BASE_URL}\n * >\n * {children}\n * </InsforgeProvider>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With cookie sync (Next.js optimization)\n * <InsforgeProvider\n * baseUrl={baseUrl}\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 baseUrl,\n afterSignInUrl = '/',\n onAuthChange,\n onSignIn,\n onSignOut,\n}: InsforgeProviderProps) {\n // Get singleton Manager instance\n const manager = useMemo(\n () =>\n InsforgeManager.getInstance({\n baseUrl,\n afterSignInUrl,\n onAuthChange,\n onSignIn,\n onSignOut,\n }),\n [baseUrl, afterSignInUrl, onAuthChange, onSignIn, onSignOut]\n );\n\n // Subscribe to Manager state\n // Start with initial state from manager (will be undefined on first render)\n const [state, setState] = useState<InsforgeManagerState>(() => manager.getState());\n\n useEffect(() => {\n // Subscribe to state changes\n const unsubscribe = manager.subscribe((newState) => {\n setState(newState);\n });\n\n // Initialize auth state only on client side (after hydration)\n // This prevents hydration mismatches by ensuring SSR and client initial render match\n void manager.initialize();\n\n return () => {\n unsubscribe();\n };\n }, [manager]);\n\n // Handle auth redirect after successful authentication\n useEffect(() => {\n if (typeof window !== 'undefined') {\n manager.handleAuthRedirect(state.isLoaded, state.user);\n }\n }, [manager, state.isLoaded, state.user]);\n\n // Create stable method references that delegate to manager\n const contextValue = useMemo<InsforgeContextValue>(\n () => ({\n // State from Manager\n user: state.user,\n userId: state.userId,\n isLoaded: state.isLoaded,\n isSignedIn: state.isSignedIn,\n\n // Methods delegated to Manager\n setUser: (user: InsforgeUser | null) => manager.setUser(user),\n signIn: (email: string, password: string) => manager.signIn(email, password),\n signUp: (email: string, password: string) => manager.signUp(email, password),\n signOut: () => manager.signOut(),\n updateUser: (data: Partial<InsforgeUser>) => manager.updateUser(data),\n reloadAuth: () => manager.reloadAuth(),\n sendVerificationEmail: (email: string) => manager.sendVerificationEmail(email),\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 // Config\n baseUrl: manager.getConfig().baseUrl,\n afterSignInUrl: manager.getConfig().afterSignInUrl || '/',\n }),\n [state, manager]\n );\n\n return <InsforgeContext.Provider value={contextValue}>{children}</InsforgeContext.Provider>;\n}\n\nexport function InsforgeProvider(props: InsforgeProviderProps) {\n return (\n <NavigationProvider adapter={BrowserNavigationAdapter}>\n <InsforgeProviderCore {...props} />\n </NavigationProvider>\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 = 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 sendVerificationEmail: () => Promise.resolve(null),\n sendResetPasswordEmail: () => Promise.resolve(null),\n resetPassword: () => Promise.resolve(null),\n verifyEmail: () => Promise.resolve(null),\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 { 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 const { signIn, signUp, signOut, isLoaded, isSignedIn } = useInsforge();\n return { signIn, signUp, signOut, isLoaded, isSignedIn };\n}\n","import { useInsforge } from '../provider/InsforgeProvider';\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() {\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
@@ -69,8 +69,9 @@ var InsforgeManager = class _InsforgeManager {
69
69
  // Static private instance
70
70
  static instance = null;
71
71
  // State storage
72
- user = null;
72
+ user = void 0;
73
73
  isLoaded = false;
74
+ isInitializing = false;
74
75
  sdk;
75
76
  listeners = /* @__PURE__ */ new Set();
76
77
  // Config
@@ -81,6 +82,8 @@ var InsforgeManager = class _InsforgeManager {
81
82
  constructor(config) {
82
83
  this.config = config;
83
84
  this.sdk = sdk.createClient({ baseUrl: config.baseUrl });
85
+ this.user = void 0;
86
+ this.isLoaded = false;
84
87
  }
85
88
  // Public access method (Singleton core)
86
89
  static getInstance(config) {
@@ -104,16 +107,28 @@ var InsforgeManager = class _InsforgeManager {
104
107
  }
105
108
  // Public initialization method
106
109
  async initialize() {
107
- if (!this.isLoaded) {
110
+ if (this.isLoaded) {
111
+ return;
112
+ }
113
+ if (this.isInitializing) {
114
+ return;
115
+ }
116
+ this.isInitializing = true;
117
+ try {
108
118
  await this.loadAuthState();
119
+ } finally {
120
+ this.isInitializing = false;
109
121
  }
110
122
  }
111
123
  // Get current state
112
124
  getState() {
125
+ const userId = this.user === void 0 ? void 0 : this.user === null ? null : this.user.id;
126
+ const isSignedIn = this.user === void 0 ? void 0 : this.user !== null;
113
127
  return {
114
128
  user: this.user,
129
+ userId,
115
130
  isLoaded: this.isLoaded,
116
- isSignedIn: !!this.user
131
+ isSignedIn
117
132
  };
118
133
  }
119
134
  // Subscription mechanism
@@ -414,22 +429,22 @@ var InsforgeManager = class _InsforgeManager {
414
429
  getSDK() {
415
430
  return this.sdk;
416
431
  }
417
- // Handle OAuth callback
418
- handleOAuthCallback(isLoaded, user) {
432
+ // Handle auth redirect after successful authentication
433
+ // Works for all auth sources: OAuth providers, cloud hosting sign-in, email verification
434
+ handleAuthRedirect(isLoaded, user) {
419
435
  if (!isLoaded || this.hasProcessedCallbackRef) {
420
436
  return false;
421
437
  }
422
- const searchParams = new URLSearchParams(window.location.search);
423
- const accessToken = searchParams.get("access_token");
424
- if (accessToken && !!user) {
425
- this.hasProcessedCallbackRef = true;
426
- const url = new URL(window.location.href);
427
- url.search = "";
428
- window.history.replaceState({}, "", url.toString());
429
- setTimeout(() => {
430
- window.location.href = this.config.afterSignInUrl || "/";
431
- }, 100);
432
- return true;
438
+ if (user && this.config.afterSignInUrl) {
439
+ const currentPath = window.location.pathname + window.location.search;
440
+ const targetPath = this.config.afterSignInUrl;
441
+ if (currentPath !== targetPath && !this.hasProcessedCallbackRef) {
442
+ this.hasProcessedCallbackRef = true;
443
+ setTimeout(() => {
444
+ window.location.href = targetPath;
445
+ }, 100);
446
+ return true;
447
+ }
433
448
  }
434
449
  return false;
435
450
  }
@@ -466,17 +481,20 @@ function InsforgeProviderCore({
466
481
  setState(newState);
467
482
  });
468
483
  void manager.initialize();
469
- return unsubscribe;
484
+ return () => {
485
+ unsubscribe();
486
+ };
470
487
  }, [manager]);
471
488
  react.useEffect(() => {
472
489
  if (typeof window !== "undefined") {
473
- manager.handleOAuthCallback(state.isLoaded, state.user);
490
+ manager.handleAuthRedirect(state.isLoaded, state.user);
474
491
  }
475
492
  }, [manager, state.isLoaded, state.user]);
476
493
  const contextValue = react.useMemo(
477
494
  () => ({
478
495
  // State from Manager
479
496
  user: state.user,
497
+ userId: state.userId,
480
498
  isLoaded: state.isLoaded,
481
499
  isSignedIn: state.isSignedIn,
482
500
  // Methods delegated to Manager
@@ -508,9 +526,10 @@ function useInsforge() {
508
526
  const context = react.useContext(react$1.InsforgeContext);
509
527
  if (!context) {
510
528
  return {
511
- user: null,
529
+ user: void 0,
530
+ userId: void 0,
512
531
  isLoaded: false,
513
- isSignedIn: false,
532
+ isSignedIn: void 0,
514
533
  setUser: () => {
515
534
  },
516
535
  signIn: () => Promise.resolve({ error: "SSR mode" }),
@@ -2490,16 +2509,16 @@ function Protect({
2490
2509
  condition,
2491
2510
  onRedirect
2492
2511
  }) {
2493
- const { isSignedIn, isLoaded, user } = useInsforge();
2512
+ const { userId, user } = useInsforge();
2494
2513
  const resolvedRedirectTo = react.useMemo(() => resolveAuthPath(redirectTo), [redirectTo]);
2495
2514
  react.useEffect(() => {
2496
- if (isLoaded && !isSignedIn) {
2515
+ if (userId === null) {
2497
2516
  if (onRedirect) {
2498
2517
  onRedirect(resolvedRedirectTo);
2499
2518
  } else {
2500
2519
  window.location.href = resolvedRedirectTo;
2501
2520
  }
2502
- } else if (isLoaded && isSignedIn && condition && user) {
2521
+ } else if (userId && condition && user) {
2503
2522
  if (!condition(user)) {
2504
2523
  if (onRedirect) {
2505
2524
  onRedirect(resolvedRedirectTo);
@@ -2508,11 +2527,11 @@ function Protect({
2508
2527
  }
2509
2528
  }
2510
2529
  }
2511
- }, [isLoaded, isSignedIn, resolvedRedirectTo, condition, user, onRedirect]);
2512
- if (!isLoaded) {
2530
+ }, [userId, resolvedRedirectTo, condition, user, onRedirect]);
2531
+ if (userId === void 0) {
2513
2532
  return fallback || /* @__PURE__ */ jsxRuntime.jsx("div", { className: "insforge-loading", children: "Loading..." });
2514
2533
  }
2515
- if (!isSignedIn) {
2534
+ if (userId === null) {
2516
2535
  return fallback || null;
2517
2536
  }
2518
2537
  if (condition && user && !condition(user)) {
@@ -2521,24 +2540,18 @@ function Protect({
2521
2540
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2522
2541
  }
2523
2542
  function SignedIn({ children }) {
2524
- const { isSignedIn, isLoaded } = useInsforge();
2525
- if (!isLoaded) {
2526
- return null;
2543
+ const { userId } = useInsforge();
2544
+ if (userId) {
2545
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2527
2546
  }
2528
- if (!isSignedIn) {
2529
- return null;
2530
- }
2531
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2547
+ return null;
2532
2548
  }
2533
2549
  function SignedOut({ children }) {
2534
- const { isSignedIn, isLoaded } = useInsforge();
2535
- if (!isLoaded) {
2536
- return null;
2537
- }
2538
- if (isSignedIn) {
2539
- return null;
2550
+ const { userId } = useInsforge();
2551
+ if (userId === null) {
2552
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2540
2553
  }
2541
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2554
+ return null;
2542
2555
  }
2543
2556
  function SignInButton({ children, className }) {
2544
2557
  const { afterSignInUrl, baseUrl } = useInsforge();