@cedros/login-react 0.0.46 → 0.0.47

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.
Files changed (83) hide show
  1. package/README.md +32 -2
  2. package/dist/EmailRegisterForm-ByYQ43yL.cjs +1 -0
  3. package/dist/EmailRegisterForm-ByYQ43yL.cjs.map +1 -0
  4. package/dist/EmailRegisterForm-DMUcNQT-.js +781 -0
  5. package/dist/EmailRegisterForm-DMUcNQT-.js.map +1 -0
  6. package/dist/GoogleLoginButton-DEwtBp56.cjs +1 -0
  7. package/dist/GoogleLoginButton-DEwtBp56.cjs.map +1 -0
  8. package/dist/{GoogleLoginButton-C1WNu7W3.js → GoogleLoginButton-DwyxvhnL.js} +82 -80
  9. package/dist/GoogleLoginButton-DwyxvhnL.js.map +1 -0
  10. package/dist/{PermissionsSection-mm9hfp-u.js → PermissionsSection-0oNHPZzL.js} +383 -415
  11. package/dist/PermissionsSection-0oNHPZzL.js.map +1 -0
  12. package/dist/PermissionsSection-CZsJuxo4.cjs +1 -0
  13. package/dist/PermissionsSection-CZsJuxo4.cjs.map +1 -0
  14. package/dist/SolanaLoginButton-2504p6cr.cjs +1 -0
  15. package/dist/SolanaLoginButton-2504p6cr.cjs.map +1 -0
  16. package/dist/SolanaLoginButton-C7Kc_m6n.js +234 -0
  17. package/dist/SolanaLoginButton-C7Kc_m6n.js.map +1 -0
  18. package/dist/{TeamSection-Km7EwLWD.cjs → TeamSection-DN8PEHH3.cjs} +1 -1
  19. package/dist/{TeamSection-Km7EwLWD.cjs.map → TeamSection-DN8PEHH3.cjs.map} +1 -1
  20. package/dist/{TeamSection-C_eODdLU.js → TeamSection-gUyP4YDM.js} +1 -1
  21. package/dist/{TeamSection-C_eODdLU.js.map → TeamSection-gUyP4YDM.js.map} +1 -1
  22. package/dist/{UsersSection-C1Tt0ePx.cjs → UsersSection-8wLuD0fr.cjs} +1 -1
  23. package/dist/{UsersSection-C1Tt0ePx.cjs.map → UsersSection-8wLuD0fr.cjs.map} +1 -1
  24. package/dist/{UsersSection-Ct_E-MBF.js → UsersSection-CnsFrG-6.js} +1 -1
  25. package/dist/{UsersSection-Ct_E-MBF.js.map → UsersSection-CnsFrG-6.js.map} +1 -1
  26. package/dist/admin-only.cjs +1 -1
  27. package/dist/admin-only.js +1 -1
  28. package/dist/email-only.cjs +1 -1
  29. package/dist/email-only.d.ts +10 -2
  30. package/dist/email-only.js +2 -2
  31. package/dist/google-only.cjs +1 -1
  32. package/dist/google-only.d.ts +7 -2
  33. package/dist/google-only.js +2 -2
  34. package/dist/index.cjs +12 -12
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +182 -11
  37. package/dist/index.js +5221 -4657
  38. package/dist/index.js.map +1 -1
  39. package/dist/login-react.css +1 -1
  40. package/dist/{plugin-Ci67QMGG.cjs → plugin-DNFjEfYp.cjs} +1 -1
  41. package/dist/{plugin-Ci67QMGG.cjs.map → plugin-DNFjEfYp.cjs.map} +1 -1
  42. package/dist/{plugin-Cm8Q6O4-.js → plugin-WYMrRNbz.js} +1 -1
  43. package/dist/{plugin-Cm8Q6O4-.js.map → plugin-WYMrRNbz.js.map} +1 -1
  44. package/dist/solana-only.cjs +1 -1
  45. package/dist/solana-only.d.ts +6 -1
  46. package/dist/solana-only.js +2 -2
  47. package/dist/useAuth-2vgrAH-M.cjs +1 -0
  48. package/dist/useAuth-2vgrAH-M.cjs.map +1 -0
  49. package/dist/{useAuth-l-itM5am.js → useAuth-CNflw856.js} +465 -469
  50. package/dist/useAuth-CNflw856.js.map +1 -0
  51. package/dist/useServerFeatures-9_aNPaa6.cjs +1 -0
  52. package/dist/useServerFeatures-9_aNPaa6.cjs.map +1 -0
  53. package/dist/useServerFeatures-DSkYdan-.js +82 -0
  54. package/dist/useServerFeatures-DSkYdan-.js.map +1 -0
  55. package/dist/{useUsersStatsSummary-BGeh3RnI.js → useUsersStatsSummary-B4_RBEYy.js} +504 -442
  56. package/dist/useUsersStatsSummary-B4_RBEYy.js.map +1 -0
  57. package/dist/useUsersStatsSummary-CHRMrlk4.cjs +1 -0
  58. package/dist/useUsersStatsSummary-CHRMrlk4.cjs.map +1 -0
  59. package/package.json +1 -1
  60. package/dist/EmailRegisterForm-p2X5QP58.js +0 -750
  61. package/dist/EmailRegisterForm-p2X5QP58.js.map +0 -1
  62. package/dist/EmailRegisterForm-xFb6MaVA.cjs +0 -1
  63. package/dist/EmailRegisterForm-xFb6MaVA.cjs.map +0 -1
  64. package/dist/GoogleLoginButton-2zNTIKMm.cjs +0 -1
  65. package/dist/GoogleLoginButton-2zNTIKMm.cjs.map +0 -1
  66. package/dist/GoogleLoginButton-C1WNu7W3.js.map +0 -1
  67. package/dist/PermissionsSection-4zcE9Zs9.cjs +0 -1
  68. package/dist/PermissionsSection-4zcE9Zs9.cjs.map +0 -1
  69. package/dist/PermissionsSection-mm9hfp-u.js.map +0 -1
  70. package/dist/SolanaLoginButton-CqdzSSeJ.cjs +0 -1
  71. package/dist/SolanaLoginButton-CqdzSSeJ.cjs.map +0 -1
  72. package/dist/SolanaLoginButton-CyeX35eU.js +0 -232
  73. package/dist/SolanaLoginButton-CyeX35eU.js.map +0 -1
  74. package/dist/sanitization-Bo_tn-L2.cjs +0 -1
  75. package/dist/sanitization-Bo_tn-L2.cjs.map +0 -1
  76. package/dist/sanitization-CQ-H1MSg.js +0 -39
  77. package/dist/sanitization-CQ-H1MSg.js.map +0 -1
  78. package/dist/useAuth-B1yS_YiD.cjs +0 -1
  79. package/dist/useAuth-B1yS_YiD.cjs.map +0 -1
  80. package/dist/useAuth-l-itM5am.js.map +0 -1
  81. package/dist/useUsersStatsSummary-BGeh3RnI.js.map +0 -1
  82. package/dist/useUsersStatsSummary-DnsYtFGX.cjs +0 -1
  83. package/dist/useUsersStatsSummary-DnsYtFGX.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailRegisterForm-DMUcNQT-.js","sources":["../src/hooks/useRateLimiter.ts","../src/hooks/useEmailAuth.ts","../src/components/email/PasswordInput.tsx","../src/hooks/useTotpVerify.ts","../src/components/totp/OtpInput.tsx","../src/components/totp/TotpVerify.tsx","../src/components/email/EmailLoginForm.tsx","../src/components/email/EmailRegisterForm.tsx"],"sourcesContent":["import { useRef, useCallback, useState, useEffect } from 'react';\n\nexport interface UseRateLimiterOptions {\n /** Maximum number of attempts allowed within the window */\n maxAttempts?: number;\n /** Time window in milliseconds */\n windowMs?: number;\n /**\n * UI-25: Set to true to enable the 1s countdown interval for displaying\n * time-until-reset in the UI. When false (default), no interval is started,\n * saving resources for callers that don't display a countdown.\n */\n showCountdown?: boolean;\n}\n\nexport interface UseRateLimiterReturn {\n /**\n * Check if an action is allowed. Throws an error if rate limited.\n * Call this before performing the action.\n */\n checkLimit: () => void;\n /**\n * Check if an action is allowed without throwing.\n * Returns true if allowed, false if rate limited.\n */\n isAllowed: () => boolean;\n /**\n * Get remaining attempts in current window\n */\n getRemainingAttempts: () => number;\n /**\n * Get time until rate limit resets (in ms)\n */\n getTimeUntilReset: () => number;\n /**\n * Reset the rate limiter (e.g., after successful action)\n */\n reset: () => void;\n}\n\n/**\n * Rate limiting hook to prevent excessive API calls from the client.\n *\n * @param options - Rate limiter configuration\n * @returns Rate limiter functions\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { checkLimit, getRemainingAttempts } = useRateLimiter({\n * maxAttempts: 5,\n * windowMs: 60000, // 1 minute\n * });\n *\n * const handleLogin = async () => {\n * try {\n * checkLimit(); // Throws if rate limited\n * await login(email, password);\n * } catch (err) {\n * if (err.message.includes('Too many attempts')) {\n * // Show rate limit message\n * }\n * }\n * };\n * }\n * ```\n */\nexport function useRateLimiter(options: UseRateLimiterOptions = {}): UseRateLimiterReturn {\n const { maxAttempts = 5, windowMs = 60000, showCountdown = false } = options;\n\n // Store timestamps of recent attempts\n const attemptsRef = useRef<number[]>([]);\n const [hasAttempts, setHasAttempts] = useState(false);\n const [, setTick] = useState(0);\n\n const bump = useCallback(() => {\n setTick((value) => value + 1);\n }, []);\n\n /**\n * Remove expired attempts from the tracking array (no state update).\n * Safe to call during render for getter functions.\n */\n const cleanupAttemptsArray = useCallback(() => {\n const now = Date.now();\n attemptsRef.current = attemptsRef.current.filter((timestamp) => now - timestamp < windowMs);\n }, [windowMs]);\n\n /**\n * Remove expired attempts AND update hasAttempts state.\n * M-04: Use functional setState to avoid hasAttempts dependency cascade.\n * Only call from event handlers and effects, NOT during render.\n */\n const cleanupExpiredAttempts = useCallback(() => {\n cleanupAttemptsArray();\n // Functional update avoids dependency on hasAttempts\n setHasAttempts((current) => (attemptsRef.current.length === 0 && current ? false : current));\n }, [cleanupAttemptsArray]);\n\n /**\n * Get the number of remaining attempts\n * Uses cleanupAttemptsArray (no state) so safe to call during render.\n */\n const getRemainingAttempts = useCallback((): number => {\n cleanupAttemptsArray();\n return Math.max(0, maxAttempts - attemptsRef.current.length);\n }, [cleanupAttemptsArray, maxAttempts]);\n\n /**\n * Get time until rate limit resets\n * Uses cleanupAttemptsArray (no state) so safe to call during render.\n */\n const getTimeUntilReset = useCallback((): number => {\n cleanupAttemptsArray();\n if (attemptsRef.current.length === 0) {\n return 0;\n }\n const oldestAttempt = attemptsRef.current[0];\n const resetTime = oldestAttempt + windowMs;\n return Math.max(0, resetTime - Date.now());\n }, [cleanupAttemptsArray, windowMs]);\n\n /**\n * Check if an action is allowed without throwing\n * Uses cleanupAttemptsArray (no state) so safe to call during render.\n */\n const isAllowed = useCallback((): boolean => {\n cleanupAttemptsArray();\n return attemptsRef.current.length < maxAttempts;\n }, [cleanupAttemptsArray, maxAttempts]);\n\n /**\n * Check rate limit and throw if exceeded\n */\n const checkLimit = useCallback((): void => {\n cleanupExpiredAttempts();\n\n if (attemptsRef.current.length >= maxAttempts) {\n const waitTime = getTimeUntilReset();\n const waitSeconds = Math.ceil(waitTime / 1000);\n throw new Error(\n `Too many attempts. Please wait ${waitSeconds} second${waitSeconds === 1 ? '' : 's'} before trying again.`\n );\n }\n\n // Record this attempt\n attemptsRef.current.push(Date.now());\n // M-04: Functional update avoids hasAttempts dependency\n setHasAttempts((current) => (current ? current : true));\n bump();\n }, [cleanupExpiredAttempts, maxAttempts, getTimeUntilReset, bump]);\n\n /**\n * Reset the rate limiter\n */\n const reset = useCallback((): void => {\n attemptsRef.current = [];\n // M-04: Functional update avoids hasAttempts dependency\n setHasAttempts((current) => (current ? false : current));\n bump();\n }, [bump]);\n\n // UI-25: Only start the 1s interval when the caller opts into countdown display.\n // This avoids a tick firing every second for all callers that don't show a countdown.\n useEffect(() => {\n if (!hasAttempts || !showCountdown) return;\n const intervalId = window.setInterval(() => {\n cleanupExpiredAttempts();\n bump();\n }, 1000);\n return () => {\n window.clearInterval(intervalId);\n };\n }, [hasAttempts, showCountdown, bump, cleanupExpiredAttempts]);\n\n return {\n checkLimit,\n isAllowed,\n getRemainingAttempts,\n getTimeUntilReset,\n reset,\n };\n}\n","import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { validateEmail } from '../utils/validation';\nimport { useRateLimiter } from './useRateLimiter';\nimport type { AuthResponse, AuthError } from '../types';\nimport type { MfaRequiredResponse } from '../types';\n\nfunction isMfaRequiredResponse(\n response: AuthResponse | MfaRequiredResponse\n): response is MfaRequiredResponse {\n return 'mfaRequired' in response && response.mfaRequired === true;\n}\n\n/** Result when MFA verification is required */\nexport interface MfaRequiredResult {\n mfaRequired: true;\n mfaToken: string;\n email: string;\n userId: string;\n}\n\n/** Result of successful login (no TOTP required or after TOTP verification) */\nexport interface LoginSuccessResult {\n mfaRequired: false;\n response: AuthResponse;\n}\n\n/** Union type for login result */\nexport type LoginResult = MfaRequiredResult | LoginSuccessResult;\n\nexport interface UseEmailAuthReturn {\n /** Login - may return mfaRequired if 2FA is enabled */\n login: (email: string, password: string) => Promise<LoginResult>;\n register: (email: string, password: string, name?: string, referral?: string, accessCode?: string) => Promise<AuthResponse>;\n isLoading: boolean;\n error: AuthError | null;\n clearError: () => void;\n /**\n * Number of remaining login attempts before rate limiting.\n *\n * M-10: Snapshot Behavior\n * This value is a point-in-time snapshot computed at render time.\n * It may be briefly stale during rapid requests or concurrent renders.\n * For UI display only - actual rate limiting is enforced inside login/register.\n */\n remainingAttempts: number;\n /**\n * Time in ms until rate limit resets (0 if not rate limited).\n *\n * M-10: Snapshot Behavior\n * This value is a point-in-time snapshot computed at render time.\n * It may be briefly stale - use for UI display, not for logic decisions.\n */\n timeUntilReset: number;\n}\n\n/**\n * Hook for email/password authentication.\n *\n * @example\n * ```tsx\n * function LoginForm() {\n * const { login, isLoading, error } = useEmailAuth();\n *\n * const handleSubmit = async (e) => {\n * e.preventDefault();\n * try {\n * await login(email, password);\n * } catch (err) {\n * // Handle error\n * }\n * };\n * }\n * ```\n */\nexport function useEmailAuth(): UseEmailAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n // Rate limiter for login attempts (5 attempts per minute)\n const {\n checkLimit,\n getRemainingAttempts,\n getTimeUntilReset,\n reset: resetRateLimit,\n } = useRateLimiter({ maxAttempts: 5, windowMs: 60000 });\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n const callbacks = config.callbacks;\n\n const login = useCallback(\n async (email: string, password: string): Promise<LoginResult> => {\n // Validate email format before API call\n if (!validateEmail(email)) {\n const validationError: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Please enter a valid email address',\n };\n setError(validationError);\n throw validationError;\n }\n\n // UI-7: Rate limit is checked BEFORE API call intentionally.\n // This prevents brute force attacks by limiting attempt frequency,\n // not just successful request frequency.\n try {\n checkLimit();\n } catch (err) {\n const rateLimitError: AuthError = {\n code: 'RATE_LIMITED',\n message: err instanceof Error ? err.message : 'Too many attempts',\n };\n setError(rateLimitError);\n throw rateLimitError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<AuthResponse | MfaRequiredResponse>('/login', {\n email,\n password,\n });\n\n // Check if MFA verification is required\n if (isMfaRequiredResponse(data)) {\n return {\n mfaRequired: true,\n mfaToken: data.mfaToken,\n email,\n userId: data.userId,\n };\n }\n\n // Normal login success\n const authResponse: AuthResponse = data;\n callbacks?.onLoginSuccess?.(authResponse.user, 'email');\n _internal?.handleLoginSuccess(authResponse.user, authResponse.tokens);\n resetRateLimit(); // Reset on successful login\n return {\n mfaRequired: false,\n response: authResponse,\n };\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, callbacks, _internal, checkLimit, resetRateLimit]\n );\n\n const register = useCallback(\n async (email: string, password: string, name?: string, referral?: string, accessCode?: string): Promise<AuthResponse> => {\n // Validate email format before API call\n if (!validateEmail(email)) {\n const validationError: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Please enter a valid email address',\n };\n setError(validationError);\n throw validationError;\n }\n\n // UI-7: Rate limit is checked BEFORE API call intentionally.\n // This prevents brute force attacks by limiting attempt frequency,\n // not just successful request frequency.\n try {\n checkLimit();\n } catch (err) {\n const rateLimitError: AuthError = {\n code: 'RATE_LIMITED',\n message: err instanceof Error ? err.message : 'Too many attempts',\n };\n setError(rateLimitError);\n throw rateLimitError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const effectiveReferral = referral ?? _internal?.getReferralCode?.() ?? undefined;\n const data = await apiClient.post<AuthResponse>('/register', {\n email,\n password,\n name,\n referral: effectiveReferral,\n ...(accessCode ? { access_code: accessCode } : {}),\n });\n callbacks?.onLoginSuccess?.(data.user, 'email');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n resetRateLimit(); // Reset on successful registration\n\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to create your account. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, callbacks, _internal, checkLimit, resetRateLimit]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n login,\n register,\n isLoading,\n error,\n clearError,\n // M-10: Point-in-time snapshots for UI display (see interface JSDoc)\n remainingAttempts: getRemainingAttempts(),\n timeUntilReset: getTimeUntilReset(),\n };\n}\n","import { useState, useId, type InputHTMLAttributes } from 'react';\nimport { validatePassword } from '../../utils/validation';\nimport type { PasswordValidation } from '../../types';\n\nexport interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {\n label?: string;\n /** Action element shown on the right side of the label (e.g., \"Forgot password?\" link) */\n labelAction?: React.ReactNode;\n showStrengthMeter?: boolean;\n onValidationChange?: (validation: PasswordValidation) => void;\n error?: string;\n}\n\n/**\n * Password input with visibility toggle and optional strength meter\n */\nexport function PasswordInput({\n label = 'Password',\n labelAction,\n showStrengthMeter = false,\n onValidationChange,\n error,\n className = '',\n onChange,\n value,\n ...props\n}: PasswordInputProps) {\n const [showPassword, setShowPassword] = useState(false);\n const [validation, setValidation] = useState<PasswordValidation | null>(null);\n const inputId = useId();\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n\n if (showStrengthMeter || onValidationChange) {\n const newValidation = validatePassword(newValue);\n setValidation(newValidation);\n onValidationChange?.(newValidation);\n }\n\n onChange?.(e);\n };\n\n const strengthColors = {\n weak: 'var(--cedros-destructive, #ef4444)',\n fair: 'var(--cedros-warning, #f59e0b)',\n good: 'var(--cedros-success, #22c55e)',\n strong: 'var(--cedros-success, #22c55e)',\n };\n\n return (\n <div className={`cedros-password-input ${className}`}>\n <div className=\"cedros-label-row\">\n <label htmlFor={inputId} className=\"cedros-label\">\n {label}\n </label>\n {labelAction}\n </div>\n <div className=\"cedros-password-wrapper\">\n <input\n id={inputId}\n type={showPassword ? 'text' : 'password'}\n className=\"cedros-input\"\n onChange={handleChange}\n value={value}\n aria-invalid={error ? 'true' : undefined}\n aria-describedby={error ? `${inputId}-error` : undefined}\n {...props}\n />\n <button\n type=\"button\"\n className=\"cedros-password-toggle\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? 'Hide password' : 'Show password'}\n aria-pressed={showPassword}\n >\n {showPassword ? (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M2.5 10s3-6 7.5-6 7.5 6 7.5 6-3 6-7.5 6-7.5-6-7.5-6z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <circle cx=\"10\" cy=\"10\" r=\"2.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M3 17L17 3\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n ) : (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M2.5 10s3-6 7.5-6 7.5 6 7.5 6-3 6-7.5 6-7.5-6-7.5-6z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <circle cx=\"10\" cy=\"10\" r=\"2.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n </svg>\n )}\n </button>\n </div>\n\n {error && (\n <p id={`${inputId}-error`} className=\"cedros-input-error\">\n {error}\n </p>\n )}\n\n {showStrengthMeter && validation && (value as string)?.length > 0 && (\n <div className=\"cedros-password-strength\">\n <div className=\"cedros-strength-bar\">\n <div\n className=\"cedros-strength-fill\"\n style={{\n width: `${\n validation.strength === 'weak'\n ? 25\n : validation.strength === 'fair'\n ? 50\n : validation.strength === 'good'\n ? 75\n : 100\n }%`,\n backgroundColor: strengthColors[validation.strength],\n }}\n />\n </div>\n <span className=\"cedros-strength-label\">{validation.strength}</span>\n </div>\n )}\n </div>\n );\n}\n","import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { useRateLimiter } from './useRateLimiter';\nimport type { AuthError, AuthResponse, TotpVerifyState } from '../types';\n\nexport interface UseTotpVerifyReturn {\n /** Verification state */\n state: TotpVerifyState;\n /** Whether verification is in progress */\n isLoading: boolean;\n /** Error from the last request */\n error: AuthError | null;\n /** Verify MFA code during login */\n verifyTotp: (mfaToken: string, code: string) => Promise<AuthResponse>;\n /** Clear error state */\n clearError: () => void;\n /** Reset to initial state */\n reset: () => void;\n /** Number of remaining verification attempts before rate limiting */\n remainingAttempts: number;\n /** Time in ms until rate limit resets (0 if not rate limited) */\n timeUntilReset: number;\n}\n\n/**\n * Hook for verifying TOTP codes during the login flow.\n *\n * Used when a user has TOTP enabled and needs to provide\n * their 6-digit code after password authentication.\n *\n * @example\n * ```tsx\n * function TotpVerifyStep({ mfaToken }) {\n * const { verifyTotp, isLoading, error } = useTotpVerify();\n *\n * const handleVerify = async (code: string) => {\n * const response = await verifyTotp(mfaToken, code);\n * // User is now authenticated\n * };\n * }\n * ```\n */\nexport function useTotpVerify(): UseTotpVerifyReturn {\n const { config, _internal } = useCedrosLogin();\n const [state, setState] = useState<TotpVerifyState>('idle');\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n // Rate limiter for TOTP verification (5 attempts per 2 minutes)\n // Stricter than login to prevent brute force on short codes\n const {\n checkLimit,\n getRemainingAttempts,\n getTimeUntilReset,\n reset: resetRateLimit,\n } = useRateLimiter({ maxAttempts: 5, windowMs: 120000 });\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n const verifyTotp = useCallback(\n async (mfaToken: string, code: string): Promise<AuthResponse> => {\n // Validate code format (6 digits or recovery code)\n const isRecoveryCode =\n /^[A-Z0-9]{16}$/i.test(code) || /^[A-Z0-9]{4}(-[A-Z0-9]{4}){3}$/i.test(code);\n const isValidCode = /^\\d{6}$/.test(code) || isRecoveryCode;\n if (!isValidCode) {\n const validationError: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Please enter a valid 6-digit code or recovery code',\n };\n setError(validationError);\n throw validationError;\n }\n\n // Rate limit check before API call to prevent brute force\n try {\n checkLimit();\n } catch (err) {\n const rateLimitError: AuthError = {\n code: 'RATE_LIMITED',\n message: err instanceof Error ? err.message : 'Too many attempts',\n };\n setError(rateLimitError);\n throw rateLimitError;\n }\n\n setIsLoading(true);\n setError(null);\n setState('verifying');\n\n try {\n const response = await apiClient.post<AuthResponse>('/login/mfa', { mfaToken, code });\n\n setState('success');\n resetRateLimit(); // Reset on successful verification\n\n // Complete authentication via internal API\n if (_internal && response.user && response.tokens) {\n _internal.handleLoginSuccess(response.user, response.tokens);\n }\n\n return response;\n } catch (err) {\n const authError = handleApiError(err, 'Incorrect verification code. Please check and try again.');\n setError(authError);\n setState('error');\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, _internal, checkLimit, resetRateLimit]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n const reset = useCallback(() => {\n setError(null);\n setState('idle');\n setIsLoading(false);\n }, []);\n\n return {\n state,\n isLoading,\n error,\n verifyTotp,\n clearError,\n reset,\n // Point-in-time snapshots for UI display\n remainingAttempts: getRemainingAttempts(),\n timeUntilReset: getTimeUntilReset(),\n };\n}\n","/**\n * OTP Input component (shadcn-style)\n *\n * A 6-digit input with separate boxes for each digit,\n * designed for TOTP verification codes.\n */\n\nimport { useRef, useCallback, useState, useEffect, useId } from 'react';\n\n/** Number of OTP digits */\nconst OTP_LENGTH = 6;\n\nexport interface OtpInputProps {\n /** Current value (up to 6 digits) */\n value?: string;\n /** Called when the value changes */\n onChange?: (value: string) => void;\n /** Called when all 6 digits are entered */\n onComplete?: (value: string) => void;\n /** Whether the input is disabled */\n disabled?: boolean;\n /** Error message to display */\n error?: string;\n /** Auto-focus the first input on mount */\n autoFocus?: boolean;\n /** Additional CSS class */\n className?: string;\n}\n\n/**\n * OTP input with separate boxes for each digit (shadcn pattern)\n *\n * Features:\n * - Auto-advances to next input on digit entry\n * - Backspace moves to previous input\n * - Supports paste of full code\n * - Numeric keyboard on mobile\n */\nexport function OtpInput({\n value = '',\n onChange,\n onComplete,\n disabled = false,\n error,\n autoFocus = false,\n className = '',\n}: OtpInputProps) {\n const inputRefs = useRef<(HTMLInputElement | null)[]>([]);\n const [localValue, setLocalValue] = useState(value.padEnd(OTP_LENGTH, ''));\n const id = useId();\n\n // Sync with controlled value\n useEffect(() => {\n setLocalValue(value.padEnd(OTP_LENGTH, ''));\n }, [value]);\n\n const focusInput = useCallback((index: number) => {\n if (index >= 0 && index < OTP_LENGTH) {\n inputRefs.current[index]?.focus();\n }\n }, []);\n\n const updateValue = useCallback(\n (newValue: string) => {\n const sanitized = newValue.replace(/\\D/g, '').slice(0, OTP_LENGTH);\n setLocalValue(sanitized.padEnd(OTP_LENGTH, ''));\n onChange?.(sanitized);\n\n if (sanitized.length === OTP_LENGTH) {\n onComplete?.(sanitized);\n }\n },\n [onChange, onComplete]\n );\n\n const handleChange = useCallback(\n (index: number, digit: string) => {\n if (!/^\\d?$/.test(digit)) return;\n\n const chars = localValue.split('');\n chars[index] = digit;\n const newValue = chars.join('').replace(/ /g, '');\n updateValue(newValue);\n\n // Move to next input if digit entered\n if (digit && index < OTP_LENGTH - 1) {\n focusInput(index + 1);\n }\n },\n [localValue, updateValue, focusInput]\n );\n\n const handleKeyDown = useCallback(\n (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Backspace') {\n e.preventDefault();\n const chars = localValue.split('');\n\n if (chars[index] && chars[index] !== ' ') {\n // Clear current digit\n chars[index] = ' ';\n updateValue(chars.join('').replace(/ /g, ''));\n } else if (index > 0) {\n // Move to previous and clear it\n chars[index - 1] = ' ';\n updateValue(chars.join('').replace(/ /g, ''));\n focusInput(index - 1);\n }\n } else if (e.key === 'ArrowLeft' && index > 0) {\n e.preventDefault();\n focusInput(index - 1);\n } else if (e.key === 'ArrowRight' && index < OTP_LENGTH - 1) {\n e.preventDefault();\n focusInput(index + 1);\n }\n },\n [localValue, updateValue, focusInput]\n );\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent) => {\n e.preventDefault();\n const pasted = e.clipboardData.getData('text');\n const digits = pasted.replace(/\\D/g, '').slice(0, OTP_LENGTH);\n if (digits) {\n updateValue(digits);\n // Focus the next empty slot or the last one\n focusInput(Math.min(digits.length, OTP_LENGTH - 1));\n }\n },\n [updateValue, focusInput]\n );\n\n const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {\n e.target.select();\n }, []);\n\n // Auto-focus first input\n useEffect(() => {\n if (autoFocus && !disabled) {\n inputRefs.current[0]?.focus();\n }\n }, [autoFocus, disabled]);\n\n return (\n <div className={`cedros-otp-input ${className}`}>\n <div className=\"cedros-otp-slots\" role=\"group\" aria-label=\"One-time password\">\n {Array.from({ length: OTP_LENGTH }).map((_, index) => (\n <input\n key={index}\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n id={`${id}-${index}`}\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={1}\n className={`cedros-otp-slot ${error ? 'cedros-otp-slot-error' : ''}`}\n value={localValue[index] === ' ' ? '' : localValue[index] || ''}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={handlePaste}\n onFocus={handleFocus}\n disabled={disabled}\n autoComplete=\"one-time-code\"\n aria-label={`Digit ${index + 1}`}\n aria-invalid={error ? 'true' : undefined}\n />\n ))}\n </div>\n {error && (\n <p className=\"cedros-otp-error\" role=\"alert\">\n {error}\n </p>\n )}\n </div>\n );\n}\n","/**\n * TOTP Verification component for login flow\n *\n * Displayed when a user with 2FA enabled needs to\n * enter their verification code to complete login.\n */\n\nimport { useState } from 'react';\nimport { useTotpVerify } from '../../hooks/useTotpVerify';\nimport { OtpInput } from './OtpInput';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface TotpVerifyProps {\n /** Temporary token from password authentication */\n mfaToken: string;\n /** Email address (for display) */\n email?: string;\n /** Called when verification succeeds */\n onSuccess?: () => void;\n /** Called when user wants to go back */\n onBack?: () => void;\n /** Additional CSS class */\n className?: string;\n}\n\n/**\n * Two-factor authentication verification for login.\n *\n * Accepts 6-digit codes from authenticator apps\n * or recovery codes for account recovery.\n */\nexport function TotpVerify({\n mfaToken,\n email,\n onSuccess,\n onBack,\n className = '',\n}: TotpVerifyProps) {\n const { verifyTotp, isLoading, error, clearError } = useTotpVerify();\n const [code, setCode] = useState('');\n const [useBackupCode, setUseBackupCode] = useState(false);\n const [backupCode, setBackupCode] = useState('');\n\n const handleVerify = async (verifyCode?: string) => {\n const codeToVerify = verifyCode || (useBackupCode ? backupCode : code);\n if (!codeToVerify) return;\n\n try {\n await verifyTotp(mfaToken, codeToVerify);\n onSuccess?.();\n } catch {\n // Error handled by hook, clear the input\n if (useBackupCode) {\n setBackupCode('');\n } else {\n setCode('');\n }\n }\n };\n\n const handleOtpComplete = (value: string) => {\n handleVerify(value);\n };\n\n const toggleBackupCode = () => {\n setUseBackupCode(!useBackupCode);\n clearError();\n setCode('');\n setBackupCode('');\n };\n\n return (\n <div className={`cedros-totp-verify ${className}`}>\n <div className=\"cedros-totp-verify-header\">\n <svg\n className=\"cedros-totp-verify-icon\"\n width=\"48\"\n height=\"48\"\n viewBox=\"0 0 48 48\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <rect x=\"8\" y=\"20\" width=\"32\" height=\"24\" rx=\"4\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path\n d=\"M16 20V14a8 8 0 1 1 16 0v6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n <circle cx=\"24\" cy=\"32\" r=\"3\" fill=\"currentColor\" />\n </svg>\n <h3 className=\"cedros-totp-title\">Two-factor authentication</h3>\n <p className=\"cedros-totp-description\">\n {useBackupCode\n ? 'Enter one of your recovery codes to sign in.'\n : 'Enter the 6-digit code from your authenticator app.'}\n </p>\n {email && <p className=\"cedros-totp-email\">{email}</p>}\n </div>\n\n {useBackupCode ? (\n <div className=\"cedros-totp-backup-input\">\n <input\n type=\"text\"\n className={`cedros-input ${error ? 'cedros-input-error' : ''}`}\n placeholder=\"Enter recovery code\"\n value={backupCode}\n onChange={(e) => {\n setBackupCode(e.target.value.toUpperCase());\n clearError();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && backupCode) {\n handleVerify();\n }\n }}\n disabled={isLoading}\n autoFocus\n autoComplete=\"one-time-code\"\n />\n {error && (\n <p className=\"cedros-input-error\" role=\"alert\">\n {error.message}\n </p>\n )}\n </div>\n ) : (\n <OtpInput\n value={code}\n onChange={(value) => {\n setCode(value);\n clearError();\n }}\n onComplete={handleOtpComplete}\n disabled={isLoading}\n error={error?.message}\n autoFocus\n />\n )}\n\n <button\n type=\"button\"\n className=\"cedros-button cedros-button-primary cedros-button-md cedros-button-full\"\n onClick={() => handleVerify()}\n disabled={isLoading || (useBackupCode ? !backupCode : code.length !== 6)}\n >\n {isLoading ? (\n <>\n <LoadingSpinner size=\"sm\" />\n <span>Verifying...</span>\n </>\n ) : (\n 'Verify'\n )}\n </button>\n\n <div className=\"cedros-totp-verify-footer\">\n <button\n type=\"button\"\n className=\"cedros-link cedros-link-sm\"\n onClick={toggleBackupCode}\n disabled={isLoading}\n >\n {useBackupCode ? 'Use authenticator app' : 'Use a recovery code'}\n </button>\n\n {onBack && (\n <>\n <span className=\"cedros-totp-verify-divider\">•</span>\n <button\n type=\"button\"\n className=\"cedros-link cedros-link-sm\"\n onClick={onBack}\n disabled={isLoading}\n >\n Back to login\n </button>\n </>\n )}\n </div>\n </div>\n );\n}\n","import { useState, type FormEvent } from 'react';\nimport { useEmailAuth } from '../../hooks/useEmailAuth';\nimport { PasswordInput } from './PasswordInput';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\nimport { TotpVerify } from '../totp/TotpVerify';\n\nexport interface EmailLoginFormProps {\n onSuccess?: () => void;\n onSwitchToRegister?: () => void;\n /** Called when user clicks \"Forgot password?\" — navigates to forgot-password screen */\n onForgotPassword?: () => void;\n className?: string;\n}\n\n/**\n * Email/password login form\n */\nexport function EmailLoginForm({\n onSuccess,\n onSwitchToRegister,\n onForgotPassword,\n className = '',\n}: EmailLoginFormProps) {\n const { login, isLoading, error, clearError } = useEmailAuth();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n // MFA state for 2FA flow\n const [mfaToken, setMfaToken] = useState<string | null>(null);\n const [mfaEmail, setMfaEmail] = useState<string>('');\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n try {\n const result = await login(email, password);\n if (result.mfaRequired) {\n // MFA verification needed\n setMfaToken(result.mfaToken);\n setMfaEmail(result.email);\n } else {\n // Login successful\n onSuccess?.();\n }\n } catch {\n // Error is handled by the hook\n }\n };\n\n const handleTotpSuccess = () => {\n setMfaToken(null);\n setMfaEmail('');\n onSuccess?.();\n };\n\n const handleTotpBack = () => {\n setMfaToken(null);\n setMfaEmail('');\n setPassword(''); // Clear password for security\n };\n\n // Show TOTP verification step\n if (mfaToken) {\n return (\n <TotpVerify\n mfaToken={mfaToken}\n email={mfaEmail}\n onSuccess={handleTotpSuccess}\n onBack={handleTotpBack}\n className={className}\n />\n );\n }\n\n return (\n <form onSubmit={handleSubmit} className={`cedros-form ${className}`}>\n <div className=\"cedros-form-field\">\n <label htmlFor=\"email\" className=\"cedros-label\">\n Email\n </label>\n <input\n id=\"email\"\n type=\"email\"\n className=\"cedros-input\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n required\n aria-required=\"true\"\n autoComplete=\"email\"\n disabled={isLoading}\n />\n </div>\n\n <div className=\"cedros-form-field\">\n <PasswordInput\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter your password\"\n required\n autoComplete=\"current-password\"\n disabled={isLoading}\n labelAction={\n onForgotPassword ? (\n <button\n type=\"button\"\n className=\"cedros-link cedros-link-muted cedros-link-sm\"\n onClick={onForgotPassword}\n >\n Forgot your password?\n </button>\n ) : undefined\n }\n />\n </div>\n\n <ErrorMessage error={error} onDismiss={clearError} />\n\n <button\n type=\"submit\"\n className=\"cedros-button cedros-button-primary cedros-button-md cedros-button-full\"\n disabled={isLoading || !email || !password}\n aria-busy={isLoading}\n >\n {isLoading ? (\n <>\n <LoadingSpinner size=\"sm\" announce label=\"Signing in\" />\n <span>Signing in...</span>\n </>\n ) : (\n 'Sign in'\n )}\n </button>\n\n {onSwitchToRegister && (\n <p className=\"cedros-form-footer\">\n Don&apos;t have an account?{' '}\n <button type=\"button\" className=\"cedros-link cedros-link-muted\" onClick={onSwitchToRegister}>\n Sign up\n </button>\n </p>\n )}\n </form>\n );\n}\n","import { useState, type FormEvent } from 'react';\nimport { useCedrosLogin } from '../../context/useCedrosLogin';\nimport { useEmailAuth } from '../../hooks/useEmailAuth';\nimport { useServerFeatures } from '../../hooks/useServerFeatures';\nimport { PasswordInput } from './PasswordInput';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\nimport { sanitizeExternalUrl } from '../../utils/sanitization';\nimport type { PasswordValidation, AuthError } from '../../types';\n\nexport interface EmailRegisterFormProps {\n onSuccess?: () => void;\n onSwitchToLogin?: () => void;\n className?: string;\n /**\n * Access code value controlled by the parent (e.g. LoginForm).\n * When provided, the internal access code input is hidden and this value\n * is used directly. Allows LoginForm to show a single shared field.\n */\n accessCode?: string;\n}\n\n/** Values collected from the registration form (for callback) */\nexport interface RegistrationData {\n termsAccepted: boolean;\n emailOptIn: boolean;\n}\n\n/**\n * Email/password registration form\n */\nexport function EmailRegisterForm({\n onSuccess,\n onSwitchToLogin,\n className = '',\n accessCode: accessCodeProp,\n}: EmailRegisterFormProps) {\n const { config } = useCedrosLogin();\n const { register, isLoading, error, clearError } = useEmailAuth();\n const { features } = useServerFeatures();\n const [name, setName] = useState('');\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [confirmPassword, setConfirmPassword] = useState('');\n // Internal state used only when parent hasn't provided an accessCode prop\n const [accessCodeInternal, setAccessCodeInternal] = useState('');\n const accessCode = accessCodeProp ?? accessCodeInternal;\n const [passwordValidation, setPasswordValidation] = useState<PasswordValidation | null>(null);\n const [termsError, setTermsError] = useState<AuthError | null>(null);\n\n // Get form configuration\n const termsConfig = config.forms?.termsOfService;\n const emailOptInConfig = config.forms?.emailOptIn;\n\n const showTerms = termsConfig?.show ?? false;\n const termsRequired = termsConfig?.required ?? true;\n const termsDefaultChecked = termsConfig?.defaultChecked ?? false;\n const termsLabel = termsConfig?.label ?? 'I agree to the Terms of Service';\n const termsUrl = termsConfig?.url;\n const safeTermsUrl = sanitizeExternalUrl(termsUrl);\n\n const showEmailOptIn = emailOptInConfig?.show ?? false;\n const emailOptInDefaultChecked = emailOptInConfig?.defaultChecked ?? false;\n const emailOptInLabel = emailOptInConfig?.label ?? 'Send me updates and news';\n\n // Initialize checkbox states with defaults\n const [termsAccepted, setTermsAccepted] = useState(termsDefaultChecked);\n const [emailOptIn, setEmailOptIn] = useState(emailOptInDefaultChecked);\n\n const passwordsMatch = password === confirmPassword;\n const isPasswordValid = passwordValidation?.isValid ?? false;\n\n // Terms must be accepted if shown and required\n const termsValid = !showTerms || !termsRequired || termsAccepted;\n\n const accessCodeRequired = features?.signupAccessCodeRequired ?? false;\n\n const canSubmit =\n email &&\n password &&\n confirmPassword &&\n passwordsMatch &&\n isPasswordValid &&\n termsValid &&\n (!accessCodeRequired || accessCode.trim()) &&\n !isLoading;\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n\n // Clear any previous terms error\n setTermsError(null);\n\n // Validate terms if required\n if (showTerms && termsRequired && !termsAccepted) {\n setTermsError({\n code: 'VALIDATION_ERROR',\n message: 'You must agree to the Terms of Service to continue',\n });\n return;\n }\n\n if (!canSubmit) return;\n\n try {\n // Note: termsAccepted and emailOptIn values can be passed to the backend\n // via the register function if needed. For now, we call register with the\n // standard params, but the backend could be extended to accept these values.\n await register(\n email,\n password,\n name || undefined,\n undefined,\n accessCodeRequired ? accessCode.trim() || undefined : undefined\n );\n onSuccess?.();\n } catch {\n // Error is handled by the hook\n }\n };\n\n const combinedError = error || termsError;\n const combinedClearError = () => {\n clearError();\n setTermsError(null);\n };\n\n return (\n <form onSubmit={handleSubmit} className={`cedros-form ${className}`}>\n <div className=\"cedros-form-field\">\n <label htmlFor=\"name\" className=\"cedros-label\">\n Name <span className=\"cedros-optional\">(optional)</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n className=\"cedros-input\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Your name\"\n autoComplete=\"name\"\n disabled={isLoading}\n />\n </div>\n\n <div className=\"cedros-form-field\">\n <label htmlFor=\"register-email\" className=\"cedros-label\">\n Email\n </label>\n <input\n id=\"register-email\"\n type=\"email\"\n className=\"cedros-input\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n required\n aria-required=\"true\"\n autoComplete=\"email\"\n disabled={isLoading}\n />\n </div>\n\n <div className=\"cedros-form-field\">\n <PasswordInput\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Create a password\"\n required\n autoComplete=\"new-password\"\n disabled={isLoading}\n showStrengthMeter\n onValidationChange={setPasswordValidation}\n />\n </div>\n\n <div className=\"cedros-form-field\">\n <PasswordInput\n label=\"Confirm Password\"\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n placeholder=\"Confirm your password\"\n required\n autoComplete=\"new-password\"\n disabled={isLoading}\n aria-invalid={confirmPassword && !passwordsMatch ? 'true' : undefined}\n error={confirmPassword && !passwordsMatch ? 'Passwords do not match' : undefined}\n />\n </div>\n\n {/* Access code field — only shown when required AND parent hasn't provided the value */}\n {accessCodeRequired && accessCodeProp === undefined && (\n <div className=\"cedros-form-field\">\n <label htmlFor=\"register-access-code\" className=\"cedros-label\">\n Access Code\n </label>\n <input\n id=\"register-access-code\"\n type=\"text\"\n className=\"cedros-input\"\n value={accessCodeInternal}\n onChange={(e) => setAccessCodeInternal(e.target.value)}\n placeholder=\"Enter access code\"\n required\n aria-required=\"true\"\n disabled={isLoading}\n autoComplete=\"off\"\n />\n </div>\n )}\n\n {/* Terms of Service checkbox */}\n {showTerms && (\n <div className=\"cedros-form-field cedros-checkbox-field\">\n <label className=\"cedros-checkbox-label\">\n <input\n type=\"checkbox\"\n className=\"cedros-checkbox\"\n checked={termsAccepted}\n onChange={(e) => setTermsAccepted(e.target.checked)}\n disabled={isLoading}\n aria-required={termsRequired}\n />\n <span className=\"cedros-checkbox-text\">\n {safeTermsUrl ? (\n <>\n {termsLabel.replace('Terms of Service', '').trim() || 'I agree to the'}{' '}\n <a\n href={safeTermsUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"cedros-link\"\n >\n Terms of Service\n </a>\n </>\n ) : (\n termsLabel\n )}\n {termsRequired && <span className=\"cedros-required\">*</span>}\n </span>\n </label>\n </div>\n )}\n\n {/* Email opt-in checkbox */}\n {showEmailOptIn && (\n <div className=\"cedros-form-field cedros-checkbox-field\">\n <label className=\"cedros-checkbox-label\">\n <input\n type=\"checkbox\"\n className=\"cedros-checkbox\"\n checked={emailOptIn}\n onChange={(e) => setEmailOptIn(e.target.checked)}\n disabled={isLoading}\n />\n <span className=\"cedros-checkbox-text\">{emailOptInLabel}</span>\n </label>\n </div>\n )}\n\n <ErrorMessage error={combinedError} onDismiss={combinedClearError} />\n\n <button\n type=\"submit\"\n className=\"cedros-button cedros-button-primary cedros-button-md cedros-button-full\"\n disabled={!canSubmit}\n aria-busy={isLoading}\n >\n {isLoading ? (\n <>\n <LoadingSpinner size=\"sm\" announce label=\"Creating account\" />\n <span>Creating account...</span>\n </>\n ) : (\n 'Create account'\n )}\n </button>\n\n {onSwitchToLogin && (\n <p className=\"cedros-form-footer\">\n Already have an account?{' '}\n <button type=\"button\" className=\"cedros-link cedros-link-muted\" onClick={onSwitchToLogin}>\n Sign in\n </button>\n </p>\n )}\n </form>\n );\n}\n"],"names":["useRateLimiter","options","maxAttempts","windowMs","showCountdown","attemptsRef","useRef","hasAttempts","setHasAttempts","useState","setTick","bump","useCallback","value","cleanupAttemptsArray","now","timestamp","cleanupExpiredAttempts","current","getRemainingAttempts","getTimeUntilReset","resetTime","isAllowed","checkLimit","waitTime","waitSeconds","reset","useEffect","intervalId","isMfaRequiredResponse","response","useEmailAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","error","setError","resetRateLimit","apiClient","useMemo","ApiClient","callbacks","login","email","password","validateEmail","validationError","err","rateLimitError","data","authResponse","authError","handleApiError","register","name","referral","accessCode","effectiveReferral","clearError","PasswordInput","label","labelAction","showStrengthMeter","onValidationChange","className","onChange","props","showPassword","setShowPassword","validation","setValidation","inputId","useId","handleChange","e","newValue","newValidation","validatePassword","strengthColors","jsxs","jsx","useTotpVerify","state","setState","verifyTotp","mfaToken","code","isRecoveryCode","OTP_LENGTH","OtpInput","onComplete","disabled","autoFocus","inputRefs","localValue","setLocalValue","id","focusInput","index","updateValue","sanitized","digit","chars","handleKeyDown","handlePaste","digits","handleFocus","_","el","TotpVerify","onSuccess","onBack","setCode","useBackupCode","setUseBackupCode","backupCode","setBackupCode","handleVerify","verifyCode","codeToVerify","handleOtpComplete","toggleBackupCode","Fragment","LoadingSpinner","EmailLoginForm","onSwitchToRegister","onForgotPassword","setEmail","setPassword","setMfaToken","mfaEmail","setMfaEmail","handleSubmit","result","handleTotpSuccess","handleTotpBack","ErrorMessage","EmailRegisterForm","onSwitchToLogin","accessCodeProp","features","useServerFeatures","setName","confirmPassword","setConfirmPassword","accessCodeInternal","setAccessCodeInternal","passwordValidation","setPasswordValidation","termsError","setTermsError","termsConfig","emailOptInConfig","showTerms","termsRequired","termsDefaultChecked","termsLabel","termsUrl","safeTermsUrl","sanitizeExternalUrl","showEmailOptIn","emailOptInDefaultChecked","emailOptInLabel","termsAccepted","setTermsAccepted","emailOptIn","setEmailOptIn","passwordsMatch","isPasswordValid","termsValid","accessCodeRequired","canSubmit","combinedError","combinedClearError"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ "use strict";const u=require("react/jsx-runtime"),n=require("react"),C=require("./useCedrosLogin-DtJorrE7.cjs"),_=require("./LoadingSpinner-d6sSxgQN.cjs"),S={loading:!1,loaded:!1,error:null,callbacks:[],load(){return typeof window>"u"||typeof document>"u"?Promise.reject(new Error("Google Sign-In script loader cannot run in SSR")):this.loaded?Promise.resolve():this.loading?new Promise((t,c)=>{this.callbacks.push({resolve:t,reject:c})}):(this.loading=!0,new Promise((t,c)=>{this.callbacks.push({resolve:t,reject:c});const g=document.getElementById("google-gsi-script");if(g){window.google?.accounts?.id?(this.loaded=!0,this.loading=!1,this.callbacks.forEach(r=>r.resolve()),this.callbacks=[]):g.addEventListener("load",()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(r=>r.resolve()),this.callbacks=[]});return}const s=document.createElement("script");s.src="https://accounts.google.com/gsi/client",s.async=!0,s.defer=!0,s.id="google-gsi-script",s.onload=()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(r=>r.resolve()),this.callbacks=[]},s.onerror=()=>{this.loading=!1,s.remove();const r=new Error("Failed to load Google Sign-In script");this.callbacks.forEach(f=>f.reject(r)),this.callbacks=[]},document.head.appendChild(s)}))},_reset(){this.loading=!1,this.loaded=!1,this.error=null,this.callbacks=[]}};function y(){const{config:t,_internal:c}=C.useCedrosLogin(),[g,s]=n.useState(!1),[r,f]=n.useState(!1),[R,i]=n.useState(null),[p,k]=n.useState(null),a=n.useRef(null),b=n.useRef(t),h=n.useRef(null),d=n.useRef(void 0),m=n.useMemo(()=>new C.ApiClient({baseUrl:t.serverUrl,timeoutMs:t.requestTimeout,retryAttempts:t.retryAttempts}),[t.serverUrl,t.requestTimeout,t.retryAttempts]);n.useEffect(()=>{b.current=t},[t]);const w=n.useCallback(async l=>{const e=a.current;if(e){if(l.error){const o={code:"SERVER_ERROR",message:l.error==="access_denied"?"Google sign-in was cancelled.":"Unable to sign in with Google. Please try again."};i(o),s(!1),a.current=null,e.reject(o);return}try{const o=await m.post("/google",{accessToken:l.access_token,referral:c?.getReferralCode?.()??void 0,access_code:d.current||void 0});b.current.callbacks?.onLoginSuccess?.(o.user,"google"),c?.handleLoginSuccess(o.user,o.tokens),s(!1),e.resolve(o)}catch(o){const E=C.handleApiError(o,"Unable to sign in with Google. Please try again.");E.code==="ACCOUNT_LINK_REQUIRED"&&k(l.access_token??null),i(E),s(!1),e.reject(E)}finally{a.current=null,d.current=void 0}}},[m,c]),I=n.useCallback(l=>{const e=a.current;if(!e)return;const o={code:"SERVER_ERROR",message:l.type==="popup_failed_to_open"?"Google sign-in popup was blocked. Please allow popups for this site.":"Google sign-in was cancelled."};i(o),s(!1),a.current=null,e.reject(o)},[]);n.useEffect(()=>{if(!t.googleClientId)return;let l=!0;return S.load().then(()=>{if(!l)return;const e=window.google?.accounts?.oauth2?.initTokenClient({client_id:t.googleClientId,scope:"openid email profile",callback:w,error_callback:I});e&&(h.current=e,f(!0))}).catch(()=>{l&&i({code:"SERVER_ERROR",message:"Unable to load Google sign-in. Please refresh and try again."})}),()=>{l=!1,h.current=null}},[t.googleClientId,w,I]);const A=n.useCallback(async l=>{if(d.current=l,!t.googleClientId){const e={code:"VALIDATION_ERROR",message:"Google Client ID not configured"};throw i(e),e}if(!r){const e={code:"VALIDATION_ERROR",message:"Google sign-in is not ready yet. Please wait a moment and try again."};throw i(e),e}if(a.current){const e={code:"VALIDATION_ERROR",message:"Google sign-in is already in progress."};throw i(e),e}return s(!0),i(null),new Promise((e,o)=>{a.current={resolve:e,reject:o},h.current?.requestAccessToken()})},[t.googleClientId,r]),L=n.useCallback(()=>i(null),[]),v=n.useCallback(()=>k(null),[]);return{signIn:A,isLoading:g,isInitialized:r,error:R,clearError:L,pendingLinkToken:p,clearPendingLink:v}}function G({onSuccess:t,onError:c,className:g="",variant:s="default",size:r="md",disabled:f=!1,accessCode:R}){const{signIn:i,isLoading:p,isInitialized:k}=y(),a=async()=>{try{await i(R),t?.()}catch(d){const m=d instanceof Error?d:new Error(String(d));c?.(m)}},b={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},h={default:"cedros-button-social",outline:"cedros-button-social-outline"};return u.jsxs("button",{type:"button",className:`cedros-button ${h[s]} ${b[r]} ${g}`,onClick:a,disabled:f||!k||p,"aria-label":"Sign in with Google",children:[p?u.jsx(_.LoadingSpinner,{size:"sm"}):u.jsxs("svg",{className:"cedros-button-icon",width:"18",height:"18",viewBox:"0 0 18 18",fill:"none","aria-hidden":"true",children:[u.jsx("path",{d:"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",fill:"#4285F4"}),u.jsx("path",{d:"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z",fill:"#34A853"}),u.jsx("path",{d:"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z",fill:"#FBBC05"}),u.jsx("path",{d:"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z",fill:"#EA4335"})]}),u.jsx("span",{children:"Continue with Google"})]})}exports.GoogleLoginButton=G;exports.useGoogleAuth=y;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleLoginButton-DEwtBp56.cjs","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n /** @param accessCode Optional signup access code, forwarded to the server on new registrations. */\n signIn: (accessCode?: string) => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n /** Access token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkToken: string | null;\n /** Clear the pending link state */\n clearPendingLink: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const [pendingLinkToken, setPendingLinkToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n const tokenClientRef = useRef<GoogleTokenClient | null>(null);\n const pendingAccessCodeRef = useRef<string | undefined>(undefined);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle access token from OAuth popup (initTokenClient flow)\n const handleTokenResponse = useCallback(\n async (response: GoogleTokenResponse) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n if (response.error) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: response.error === 'access_denied'\n ? 'Google sign-in was cancelled.'\n : 'Unable to sign in with Google. Please try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n callbacks.reject(err);\n return;\n }\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n accessToken: response.access_token,\n referral: _internal?.getReferralCode?.() ?? undefined,\n access_code: pendingAccessCodeRef.current || undefined,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with Google. Please try again.');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkToken(response.access_token ?? null);\n }\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n pendingAccessCodeRef.current = undefined;\n }\n },\n [apiClient, _internal]\n );\n\n // Handle popup closed / blocked without completing auth.\n // Google calls error_callback (not callback) when the user closes the popup.\n const handleTokenError = useCallback(\n (err: GoogleTokenErrorResponse) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n const authError: AuthError = {\n code: 'SERVER_ERROR',\n message: err.type === 'popup_failed_to_open'\n ? 'Google sign-in popup was blocked. Please allow popups for this site.'\n : 'Google sign-in was cancelled.',\n };\n setError(authError);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n callbacks.reject(authError);\n },\n []\n );\n\n // P-01: Initialize Google OAuth token client using singleton loader.\n // Uses initTokenClient (OAuth popup) instead of One Tap prompt() which\n // has exponential cooldown after dismissal and is blocked by Brave.\n useEffect(() => {\n if (!config.googleClientId) {\n return;\n }\n\n let isMounted = true;\n\n scriptLoader\n .load()\n .then(() => {\n if (!isMounted) return;\n\n const client = window.google?.accounts?.oauth2?.initTokenClient({\n client_id: config.googleClientId!,\n scope: 'openid email profile',\n callback: handleTokenResponse,\n error_callback: handleTokenError,\n });\n\n if (client) {\n tokenClientRef.current = client;\n setIsInitialized(true);\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Unable to load Google sign-in. Please refresh and try again.',\n });\n }\n });\n\n return () => {\n isMounted = false;\n tokenClientRef.current = null;\n };\n }, [config.googleClientId, handleTokenResponse, handleTokenError]);\n\n const signIn = useCallback(async (accessCode?: string): Promise<AuthResponse> => {\n pendingAccessCodeRef.current = accessCode;\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is not ready yet. Please wait a moment and try again.',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is already in progress.',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Open the Google OAuth popup via initTokenClient.\n // This avoids One Tap's exponential cooldown after dismissal and works\n // in browsers that block One Tap (e.g., Brave).\n tokenClientRef.current?.requestAccessToken();\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkToken,\n clearPendingLink,\n };\n}\n\n/** Response from Google's initTokenClient callback */\ninterface GoogleTokenResponse {\n access_token?: string;\n error?: string;\n error_description?: string;\n}\n\n/** Error from initTokenClient error_callback (popup closed/blocked) */\ninterface GoogleTokenErrorResponse {\n type: 'popup_failed_to_open' | 'popup_closed' | 'unknown';\n}\n\n/** Token client returned by google.accounts.oauth2.initTokenClient */\ninterface GoogleTokenClient {\n requestAccessToken: () => void;\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n /** Used only to detect if the GIS script has loaded */\n id?: object;\n oauth2?: {\n initTokenClient: (config: {\n client_id: string;\n scope: string;\n callback: (response: GoogleTokenResponse) => void;\n error_callback?: (error: GoogleTokenErrorResponse) => void;\n }) => GoogleTokenClient;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /** Access code forwarded to the server when this flow creates a new account. */\n accessCode?: string;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n accessCode,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn(accessCode);\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","pendingLinkToken","setPendingLinkToken","promiseCallbacksRef","useRef","configRef","tokenClientRef","pendingAccessCodeRef","apiClient","useMemo","ApiClient","useEffect","handleTokenResponse","useCallback","response","callbacks","err","data","authError","handleApiError","handleTokenError","isMounted","client","signIn","accessCode","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","variantClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- import { jsxs as y, jsx as u } from "react/jsx-runtime";
2
- import { useState as m, useRef as w, useMemo as G, useEffect as A, useCallback as f } from "react";
3
- import { u as S, A as T, h as P } from "./useCedrosLogin-CFfID-0i.js";
4
- import { L as O } from "./LoadingSpinner-6vml-zwr.js";
5
- const z = {
1
+ import { jsxs as A, jsx as u } from "react/jsx-runtime";
2
+ import { useState as R, useRef as E, useMemo as S, useEffect as v, useCallback as f } from "react";
3
+ import { u as T, A as P, h as O } from "./useCedrosLogin-CFfID-0i.js";
4
+ import { L as z } from "./LoadingSpinner-6vml-zwr.js";
5
+ const U = {
6
6
  loading: !1,
7
7
  loaded: !1,
8
8
  error: null,
@@ -14,18 +14,18 @@ const z = {
14
14
  this.callbacks.push({ resolve: o, reject: i });
15
15
  const d = document.getElementById("google-gsi-script");
16
16
  if (d) {
17
- window.google?.accounts?.id ? (this.loaded = !0, this.loading = !1, this.callbacks.forEach((n) => n.resolve()), this.callbacks = []) : d.addEventListener("load", () => {
18
- this.loaded = !0, this.loading = !1, this.callbacks.forEach((n) => n.resolve()), this.callbacks = [];
17
+ window.google?.accounts?.id ? (this.loaded = !0, this.loading = !1, this.callbacks.forEach((s) => s.resolve()), this.callbacks = []) : d.addEventListener("load", () => {
18
+ this.loaded = !0, this.loading = !1, this.callbacks.forEach((s) => s.resolve()), this.callbacks = [];
19
19
  });
20
20
  return;
21
21
  }
22
22
  const t = document.createElement("script");
23
23
  t.src = "https://accounts.google.com/gsi/client", t.async = !0, t.defer = !0, t.id = "google-gsi-script", t.onload = () => {
24
- this.loaded = !0, this.loading = !1, this.callbacks.forEach((n) => n.resolve()), this.callbacks = [];
24
+ this.loaded = !0, this.loading = !1, this.callbacks.forEach((s) => s.resolve()), this.callbacks = [];
25
25
  }, t.onerror = () => {
26
26
  this.loading = !1, t.remove();
27
- const n = new Error("Failed to load Google Sign-In script");
28
- this.callbacks.forEach((g) => g.reject(n)), this.callbacks = [];
27
+ const s = new Error("Failed to load Google Sign-In script");
28
+ this.callbacks.forEach((g) => g.reject(s)), this.callbacks = [];
29
29
  }, document.head.appendChild(t);
30
30
  }));
31
31
  },
@@ -37,148 +37,150 @@ const z = {
37
37
  this.loading = !1, this.loaded = !1, this.error = null, this.callbacks = [];
38
38
  }
39
39
  };
40
- function U() {
41
- const { config: o, _internal: i } = S(), [d, t] = m(!1), [n, g] = m(!1), [k, s] = m(null), [b, h] = m(null), a = w(null), E = w(o), c = w(null), p = G(
42
- () => new T({
40
+ function j() {
41
+ const { config: o, _internal: i } = T(), [d, t] = R(!1), [s, g] = R(!1), [w, l] = R(null), [h, p] = R(null), a = E(null), m = E(o), k = E(null), c = E(void 0), b = S(
42
+ () => new P({
43
43
  baseUrl: o.serverUrl,
44
44
  timeoutMs: o.requestTimeout,
45
45
  retryAttempts: o.retryAttempts
46
46
  }),
47
47
  [o.serverUrl, o.requestTimeout, o.retryAttempts]
48
48
  );
49
- A(() => {
50
- E.current = o;
49
+ v(() => {
50
+ m.current = o;
51
51
  }, [o]);
52
- const C = f(
53
- async (e) => {
54
- const r = a.current;
55
- if (r) {
56
- if (e.error) {
57
- const l = {
52
+ const I = f(
53
+ async (r) => {
54
+ const e = a.current;
55
+ if (e) {
56
+ if (r.error) {
57
+ const n = {
58
58
  code: "SERVER_ERROR",
59
- message: e.error === "access_denied" ? "Google sign-in was cancelled." : "Unable to sign in with Google. Please try again."
59
+ message: r.error === "access_denied" ? "Google sign-in was cancelled." : "Unable to sign in with Google. Please try again."
60
60
  };
61
- s(l), t(!1), a.current = null, r.reject(l);
61
+ l(n), t(!1), a.current = null, e.reject(n);
62
62
  return;
63
63
  }
64
64
  try {
65
- const l = await p.post("/google", {
66
- accessToken: e.access_token,
67
- referral: i?.getReferralCode?.() ?? void 0
65
+ const n = await b.post("/google", {
66
+ accessToken: r.access_token,
67
+ referral: i?.getReferralCode?.() ?? void 0,
68
+ access_code: c.current || void 0
68
69
  });
69
- E.current.callbacks?.onLoginSuccess?.(l.user, "google"), i?.handleLoginSuccess(l.user, l.tokens), t(!1), r.resolve(l);
70
- } catch (l) {
71
- const R = P(l, "Unable to sign in with Google. Please try again.");
72
- R.code === "ACCOUNT_LINK_REQUIRED" && h(e.access_token ?? null), s(R), t(!1), r.reject(R);
70
+ m.current.callbacks?.onLoginSuccess?.(n.user, "google"), i?.handleLoginSuccess(n.user, n.tokens), t(!1), e.resolve(n);
71
+ } catch (n) {
72
+ const C = O(n, "Unable to sign in with Google. Please try again.");
73
+ C.code === "ACCOUNT_LINK_REQUIRED" && p(r.access_token ?? null), l(C), t(!1), e.reject(C);
73
74
  } finally {
74
- a.current = null;
75
+ a.current = null, c.current = void 0;
75
76
  }
76
77
  }
77
78
  },
78
- [p, i]
79
- ), I = f(
80
- (e) => {
81
- const r = a.current;
82
- if (!r) return;
83
- const l = {
79
+ [b, i]
80
+ ), y = f(
81
+ (r) => {
82
+ const e = a.current;
83
+ if (!e) return;
84
+ const n = {
84
85
  code: "SERVER_ERROR",
85
- message: e.type === "popup_failed_to_open" ? "Google sign-in popup was blocked. Please allow popups for this site." : "Google sign-in was cancelled."
86
+ message: r.type === "popup_failed_to_open" ? "Google sign-in popup was blocked. Please allow popups for this site." : "Google sign-in was cancelled."
86
87
  };
87
- s(l), t(!1), a.current = null, r.reject(l);
88
+ l(n), t(!1), a.current = null, e.reject(n);
88
89
  },
89
90
  []
90
91
  );
91
- A(() => {
92
+ v(() => {
92
93
  if (!o.googleClientId)
93
94
  return;
94
- let e = !0;
95
- return z.load().then(() => {
96
- if (!e) return;
97
- const r = window.google?.accounts?.oauth2?.initTokenClient({
95
+ let r = !0;
96
+ return U.load().then(() => {
97
+ if (!r) return;
98
+ const e = window.google?.accounts?.oauth2?.initTokenClient({
98
99
  client_id: o.googleClientId,
99
100
  scope: "openid email profile",
100
- callback: C,
101
- error_callback: I
101
+ callback: I,
102
+ error_callback: y
102
103
  });
103
- r && (c.current = r, g(!0));
104
+ e && (k.current = e, g(!0));
104
105
  }).catch(() => {
105
- e && s({
106
+ r && l({
106
107
  code: "SERVER_ERROR",
107
108
  message: "Unable to load Google sign-in. Please refresh and try again."
108
109
  });
109
110
  }), () => {
110
- e = !1, c.current = null;
111
+ r = !1, k.current = null;
111
112
  };
112
- }, [o.googleClientId, C, I]);
113
- const L = f(async () => {
114
- if (!o.googleClientId) {
113
+ }, [o.googleClientId, I, y]);
114
+ const _ = f(async (r) => {
115
+ if (c.current = r, !o.googleClientId) {
115
116
  const e = {
116
117
  code: "VALIDATION_ERROR",
117
118
  message: "Google Client ID not configured"
118
119
  };
119
- throw s(e), e;
120
+ throw l(e), e;
120
121
  }
121
- if (!n) {
122
+ if (!s) {
122
123
  const e = {
123
124
  code: "VALIDATION_ERROR",
124
125
  message: "Google sign-in is not ready yet. Please wait a moment and try again."
125
126
  };
126
- throw s(e), e;
127
+ throw l(e), e;
127
128
  }
128
129
  if (a.current) {
129
130
  const e = {
130
131
  code: "VALIDATION_ERROR",
131
132
  message: "Google sign-in is already in progress."
132
133
  };
133
- throw s(e), e;
134
+ throw l(e), e;
134
135
  }
135
- return t(!0), s(null), new Promise((e, r) => {
136
- a.current = { resolve: e, reject: r }, c.current?.requestAccessToken();
136
+ return t(!0), l(null), new Promise((e, n) => {
137
+ a.current = { resolve: e, reject: n }, k.current?.requestAccessToken();
137
138
  });
138
- }, [o.googleClientId, n]), _ = f(() => s(null), []), v = f(() => h(null), []);
139
+ }, [o.googleClientId, s]), L = f(() => l(null), []), G = f(() => p(null), []);
139
140
  return {
140
- signIn: L,
141
+ signIn: _,
141
142
  isLoading: d,
142
- isInitialized: n,
143
- error: k,
144
- clearError: _,
145
- pendingLinkToken: b,
146
- clearPendingLink: v
143
+ isInitialized: s,
144
+ error: w,
145
+ clearError: L,
146
+ pendingLinkToken: h,
147
+ clearPendingLink: G
147
148
  };
148
149
  }
149
- function x({
150
+ function B({
150
151
  onSuccess: o,
151
152
  onError: i,
152
153
  className: d = "",
153
154
  variant: t = "default",
154
- size: n = "md",
155
- disabled: g = !1
155
+ size: s = "md",
156
+ disabled: g = !1,
157
+ accessCode: w
156
158
  }) {
157
- const { signIn: k, isLoading: s, isInitialized: b } = U(), h = async () => {
159
+ const { signIn: l, isLoading: h, isInitialized: p } = j(), a = async () => {
158
160
  try {
159
- await k(), o?.();
161
+ await l(w), o?.();
160
162
  } catch (c) {
161
- const p = c instanceof Error ? c : new Error(String(c));
162
- i?.(p);
163
+ const b = c instanceof Error ? c : new Error(String(c));
164
+ i?.(b);
163
165
  }
164
- }, a = {
166
+ }, m = {
165
167
  sm: "cedros-button-sm",
166
168
  md: "cedros-button-md",
167
169
  lg: "cedros-button-lg"
168
170
  };
169
- return /* @__PURE__ */ y(
171
+ return /* @__PURE__ */ A(
170
172
  "button",
171
173
  {
172
174
  type: "button",
173
175
  className: `cedros-button ${{
174
176
  default: "cedros-button-social",
175
177
  outline: "cedros-button-social-outline"
176
- }[t]} ${a[n]} ${d}`,
177
- onClick: h,
178
- disabled: g || !b || s,
178
+ }[t]} ${m[s]} ${d}`,
179
+ onClick: a,
180
+ disabled: g || !p || h,
179
181
  "aria-label": "Sign in with Google",
180
182
  children: [
181
- s ? /* @__PURE__ */ u(O, { size: "sm" }) : /* @__PURE__ */ y(
183
+ h ? /* @__PURE__ */ u(z, { size: "sm" }) : /* @__PURE__ */ A(
182
184
  "svg",
183
185
  {
184
186
  className: "cedros-button-icon",
@@ -225,6 +227,6 @@ function x({
225
227
  );
226
228
  }
227
229
  export {
228
- x as G,
229
- U as u
230
+ B as G,
231
+ j as u
230
232
  };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleLoginButton-DwyxvhnL.js","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n /** @param accessCode Optional signup access code, forwarded to the server on new registrations. */\n signIn: (accessCode?: string) => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n /** Access token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkToken: string | null;\n /** Clear the pending link state */\n clearPendingLink: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const [pendingLinkToken, setPendingLinkToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n const tokenClientRef = useRef<GoogleTokenClient | null>(null);\n const pendingAccessCodeRef = useRef<string | undefined>(undefined);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle access token from OAuth popup (initTokenClient flow)\n const handleTokenResponse = useCallback(\n async (response: GoogleTokenResponse) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n if (response.error) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: response.error === 'access_denied'\n ? 'Google sign-in was cancelled.'\n : 'Unable to sign in with Google. Please try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n callbacks.reject(err);\n return;\n }\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n accessToken: response.access_token,\n referral: _internal?.getReferralCode?.() ?? undefined,\n access_code: pendingAccessCodeRef.current || undefined,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with Google. Please try again.');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkToken(response.access_token ?? null);\n }\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n pendingAccessCodeRef.current = undefined;\n }\n },\n [apiClient, _internal]\n );\n\n // Handle popup closed / blocked without completing auth.\n // Google calls error_callback (not callback) when the user closes the popup.\n const handleTokenError = useCallback(\n (err: GoogleTokenErrorResponse) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n const authError: AuthError = {\n code: 'SERVER_ERROR',\n message: err.type === 'popup_failed_to_open'\n ? 'Google sign-in popup was blocked. Please allow popups for this site.'\n : 'Google sign-in was cancelled.',\n };\n setError(authError);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n callbacks.reject(authError);\n },\n []\n );\n\n // P-01: Initialize Google OAuth token client using singleton loader.\n // Uses initTokenClient (OAuth popup) instead of One Tap prompt() which\n // has exponential cooldown after dismissal and is blocked by Brave.\n useEffect(() => {\n if (!config.googleClientId) {\n return;\n }\n\n let isMounted = true;\n\n scriptLoader\n .load()\n .then(() => {\n if (!isMounted) return;\n\n const client = window.google?.accounts?.oauth2?.initTokenClient({\n client_id: config.googleClientId!,\n scope: 'openid email profile',\n callback: handleTokenResponse,\n error_callback: handleTokenError,\n });\n\n if (client) {\n tokenClientRef.current = client;\n setIsInitialized(true);\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Unable to load Google sign-in. Please refresh and try again.',\n });\n }\n });\n\n return () => {\n isMounted = false;\n tokenClientRef.current = null;\n };\n }, [config.googleClientId, handleTokenResponse, handleTokenError]);\n\n const signIn = useCallback(async (accessCode?: string): Promise<AuthResponse> => {\n pendingAccessCodeRef.current = accessCode;\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is not ready yet. Please wait a moment and try again.',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google sign-in is already in progress.',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Open the Google OAuth popup via initTokenClient.\n // This avoids One Tap's exponential cooldown after dismissal and works\n // in browsers that block One Tap (e.g., Brave).\n tokenClientRef.current?.requestAccessToken();\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkToken,\n clearPendingLink,\n };\n}\n\n/** Response from Google's initTokenClient callback */\ninterface GoogleTokenResponse {\n access_token?: string;\n error?: string;\n error_description?: string;\n}\n\n/** Error from initTokenClient error_callback (popup closed/blocked) */\ninterface GoogleTokenErrorResponse {\n type: 'popup_failed_to_open' | 'popup_closed' | 'unknown';\n}\n\n/** Token client returned by google.accounts.oauth2.initTokenClient */\ninterface GoogleTokenClient {\n requestAccessToken: () => void;\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n /** Used only to detect if the GIS script has loaded */\n id?: object;\n oauth2?: {\n initTokenClient: (config: {\n client_id: string;\n scope: string;\n callback: (response: GoogleTokenResponse) => void;\n error_callback?: (error: GoogleTokenErrorResponse) => void;\n }) => GoogleTokenClient;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /** Access code forwarded to the server when this flow creates a new account. */\n accessCode?: string;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n accessCode,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn(accessCode);\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-social',\n outline: 'cedros-button-social-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","pendingLinkToken","setPendingLinkToken","promiseCallbacksRef","useRef","configRef","tokenClientRef","pendingAccessCodeRef","apiClient","useMemo","ApiClient","useEffect","handleTokenResponse","useCallback","response","callbacks","err","data","authError","handleApiError","handleTokenError","isMounted","client","signIn","accessCode","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}