@cedros/login-react 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AuthenticationSettings-BF_7Ea6Z.js → AuthenticationSettings-CheE3j7w.js} +1 -1
- package/dist/{AuthenticationSettings-BF_7Ea6Z.js.map → AuthenticationSettings-CheE3j7w.js.map} +1 -1
- package/dist/{AuthenticationSettings-Dk1LX0CK.cjs → AuthenticationSettings-Dl41GbJL.cjs} +1 -1
- package/dist/{AuthenticationSettings-Dk1LX0CK.cjs.map → AuthenticationSettings-Dl41GbJL.cjs.map} +1 -1
- package/dist/{AuthenticationSettings-DUXpyiJ5.js → AuthenticationSettings-DwSxgjbH.js} +1 -1
- package/dist/{AuthenticationSettings-DUXpyiJ5.js.map → AuthenticationSettings-DwSxgjbH.js.map} +1 -1
- package/dist/{AuthenticationSettings-BMDrCVSf.cjs → AuthenticationSettings-JxHsBst9.cjs} +1 -1
- package/dist/{AuthenticationSettings-BMDrCVSf.cjs.map → AuthenticationSettings-JxHsBst9.cjs.map} +1 -1
- package/dist/AutosaveStatus-BMXjH1XN.cjs +1 -0
- package/dist/AutosaveStatus-BMXjH1XN.cjs.map +1 -0
- package/dist/{AutosaveStatus-CSZsp6w7.js → AutosaveStatus-DNuCl59W.js} +798 -319
- package/dist/AutosaveStatus-DNuCl59W.js.map +1 -0
- package/dist/{CreditSystemSettings-CLImarX-.cjs → CreditSystemSettings-BVgl6uUg.cjs} +1 -1
- package/dist/{CreditSystemSettings-CLImarX-.cjs.map → CreditSystemSettings-BVgl6uUg.cjs.map} +1 -1
- package/dist/{CreditSystemSettings-Buu7Y-7I.js → CreditSystemSettings-C-ksysSx.js} +12 -7
- package/dist/CreditSystemSettings-C-ksysSx.js.map +1 -0
- package/dist/{CreditSystemSettings-Cj21_Ug8.js → CreditSystemSettings-HSdF2_CY.js} +1 -1
- package/dist/{CreditSystemSettings-Cj21_Ug8.js.map → CreditSystemSettings-HSdF2_CY.js.map} +1 -1
- package/dist/CreditSystemSettings-LvA8rb17.cjs +1 -0
- package/dist/CreditSystemSettings-LvA8rb17.cjs.map +1 -0
- package/dist/EmailRegisterForm-p2X5QP58.js +750 -0
- package/dist/EmailRegisterForm-p2X5QP58.js.map +1 -0
- package/dist/EmailRegisterForm-xFb6MaVA.cjs +1 -0
- package/dist/EmailRegisterForm-xFb6MaVA.cjs.map +1 -0
- package/dist/{EmailSettings-CmxxnrA9.js → EmailSettings-Cy1cuVUq.js} +1 -1
- package/dist/{EmailSettings-CmxxnrA9.js.map → EmailSettings-Cy1cuVUq.js.map} +1 -1
- package/dist/EmailSettings-DC_zT4nI.cjs +1 -0
- package/dist/EmailSettings-DC_zT4nI.cjs.map +1 -0
- package/dist/{EmailSettings-9sdEAONl.cjs → EmailSettings-QBMzpbxv.cjs} +1 -1
- package/dist/{EmailSettings-9sdEAONl.cjs.map → EmailSettings-QBMzpbxv.cjs.map} +1 -1
- package/dist/EmailSettings-hIhJzux0.js +86 -0
- package/dist/EmailSettings-hIhJzux0.js.map +1 -0
- package/dist/{EmbeddedWalletSettings-DGq-kXbw.cjs → EmbeddedWalletSettings-4qC9KBwh.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-DGq-kXbw.cjs.map → EmbeddedWalletSettings-4qC9KBwh.cjs.map} +1 -1
- package/dist/{EmbeddedWalletSettings-BuLN_Uqc.cjs → EmbeddedWalletSettings-C81QQMWz.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-BuLN_Uqc.cjs.map → EmbeddedWalletSettings-C81QQMWz.cjs.map} +1 -1
- package/dist/{EmbeddedWalletSettings-CHkkCjyR.js → EmbeddedWalletSettings-CvvTnRvt.js} +1 -1
- package/dist/{EmbeddedWalletSettings-CHkkCjyR.js.map → EmbeddedWalletSettings-CvvTnRvt.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-M-D5N0eY.js → EmbeddedWalletSettings-Cwiug0vR.js} +1 -1
- package/dist/{EmbeddedWalletSettings-M-D5N0eY.js.map → EmbeddedWalletSettings-Cwiug0vR.js.map} +1 -1
- package/dist/GoogleLoginButton-2zNTIKMm.cjs +1 -0
- package/dist/GoogleLoginButton-2zNTIKMm.cjs.map +1 -0
- package/dist/{GoogleLoginButton-qf4A_A3G.js → GoogleLoginButton-C1WNu7W3.js} +41 -40
- package/dist/GoogleLoginButton-C1WNu7W3.js.map +1 -0
- package/dist/LoadingSpinner-6vml-zwr.js.map +1 -1
- package/dist/LoadingSpinner-d6sSxgQN.cjs.map +1 -1
- package/dist/{PermissionsSection-DNzOL1xW.js → PermissionsSection-BDDiEfho.js} +69 -61
- package/dist/{PermissionsSection-DNzOL1xW.js.map → PermissionsSection-BDDiEfho.js.map} +1 -1
- package/dist/PermissionsSection-CSB_Ikj9.cjs +1 -0
- package/dist/{PermissionsSection-DEMVp7X3.cjs.map → PermissionsSection-CSB_Ikj9.cjs.map} +1 -1
- package/dist/{ServerSettings-CMmH5pZv.cjs → ServerSettings-BV0SipW1.cjs} +1 -1
- package/dist/{ServerSettings-CMmH5pZv.cjs.map → ServerSettings-BV0SipW1.cjs.map} +1 -1
- package/dist/{ServerSettings-DfimU7ay.cjs → ServerSettings-Bf7gFE8r.cjs} +1 -1
- package/dist/{ServerSettings-DfimU7ay.cjs.map → ServerSettings-Bf7gFE8r.cjs.map} +1 -1
- package/dist/{ServerSettings-qxi8aZO7.js → ServerSettings-DPqHtsgV.js} +1 -1
- package/dist/{ServerSettings-qxi8aZO7.js.map → ServerSettings-DPqHtsgV.js.map} +1 -1
- package/dist/{ServerSettings-DQemMrNv.js → ServerSettings-Sfr0CG6K.js} +1 -1
- package/dist/{ServerSettings-DQemMrNv.js.map → ServerSettings-Sfr0CG6K.js.map} +1 -1
- package/dist/SolanaLoginButton-CqdzSSeJ.cjs +1 -0
- package/dist/SolanaLoginButton-CqdzSSeJ.cjs.map +1 -0
- package/dist/{SolanaLoginButton-B04dib6X.js → SolanaLoginButton-CyeX35eU.js} +41 -40
- package/dist/SolanaLoginButton-CyeX35eU.js.map +1 -0
- package/dist/{TeamSection-CoMXyFtz.js → TeamSection-BhsBEckR.js} +1 -1
- package/dist/{TeamSection-CoMXyFtz.js.map → TeamSection-BhsBEckR.js.map} +1 -1
- package/dist/{TeamSection-DopbZClq.cjs → TeamSection-DLxtRmta.cjs} +1 -1
- package/dist/{TeamSection-DopbZClq.cjs.map → TeamSection-DLxtRmta.cjs.map} +1 -1
- package/dist/{UsersSection-C7aRNkK2.cjs → UsersSection-BEKfbhQ4.cjs} +1 -1
- package/dist/{UsersSection-C7aRNkK2.cjs.map → UsersSection-BEKfbhQ4.cjs.map} +1 -1
- package/dist/{UsersSection--PAE1XRh.js → UsersSection-DbGkmxty.js} +1 -1
- package/dist/{UsersSection--PAE1XRh.js.map → UsersSection-DbGkmxty.js.map} +1 -1
- package/dist/{WebhookSettings-B6Y3Tnjv.cjs → WebhookSettings-BPCKv5Or.cjs} +1 -1
- package/dist/{WebhookSettings-B6Y3Tnjv.cjs.map → WebhookSettings-BPCKv5Or.cjs.map} +1 -1
- package/dist/{WebhookSettings-8QAqvkkO.js → WebhookSettings-CMROMCFT.js} +1 -1
- package/dist/{WebhookSettings-8QAqvkkO.js.map → WebhookSettings-CMROMCFT.js.map} +1 -1
- package/dist/{WebhookSettings-0sgWRI3U.cjs → WebhookSettings-Cj-iELGa.cjs} +1 -1
- package/dist/{WebhookSettings-0sgWRI3U.cjs.map → WebhookSettings-Cj-iELGa.cjs.map} +1 -1
- package/dist/{WebhookSettings-B8hAwhZ2.js → WebhookSettings-D4mKAWAg.js} +1 -1
- package/dist/{WebhookSettings-B8hAwhZ2.js.map → WebhookSettings-D4mKAWAg.js.map} +1 -1
- package/dist/admin-only.cjs +1 -1
- package/dist/admin-only.js +1 -1
- package/dist/email-only.cjs +1 -1
- package/dist/email-only.d.ts +7 -1
- package/dist/email-only.js +2 -2
- package/dist/google-only.cjs +1 -1
- package/dist/google-only.d.ts +6 -0
- package/dist/google-only.js +2 -2
- package/dist/index.cjs +13 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +414 -4
- package/dist/index.js +6070 -4807
- package/dist/index.js.map +1 -1
- package/dist/login-react.css +1 -1
- package/dist/plugin-BHGg7ius.cjs +1 -0
- package/dist/plugin-BHGg7ius.cjs.map +1 -0
- package/dist/{plugin-C_NDZ2-D.js → plugin-CK2d7aP5.js} +3 -2
- package/dist/plugin-CK2d7aP5.js.map +1 -0
- package/dist/solana-only.cjs +1 -1
- package/dist/solana-only.d.ts +6 -0
- package/dist/solana-only.js +2 -2
- package/dist/useAuth-B1yS_YiD.cjs +1 -0
- package/dist/{useAuth-U5CYsHEU.cjs.map → useAuth-B1yS_YiD.cjs.map} +1 -1
- package/dist/{useAuth-C-Vw-ggy.js → useAuth-l-itM5am.js} +440 -433
- package/dist/{useAuth-C-Vw-ggy.js.map → useAuth-l-itM5am.js.map} +1 -1
- package/dist/useUsersStatsSummary-9HQDKBU5.js +1879 -0
- package/dist/useUsersStatsSummary-9HQDKBU5.js.map +1 -0
- package/dist/useUsersStatsSummary-DiRC8sGs.cjs +1 -0
- package/dist/useUsersStatsSummary-DiRC8sGs.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/AutosaveStatus-BKsCIvPj.cjs +0 -1
- package/dist/AutosaveStatus-BKsCIvPj.cjs.map +0 -1
- package/dist/AutosaveStatus-CSZsp6w7.js.map +0 -1
- package/dist/CreditSystemSettings-Buu7Y-7I.js.map +0 -1
- package/dist/CreditSystemSettings-C2HkyMXy.cjs +0 -1
- package/dist/CreditSystemSettings-C2HkyMXy.cjs.map +0 -1
- package/dist/EmailRegisterForm-CNjYrqU6.cjs +0 -1
- package/dist/EmailRegisterForm-CNjYrqU6.cjs.map +0 -1
- package/dist/EmailRegisterForm-D2VaJouj.js +0 -750
- package/dist/EmailRegisterForm-D2VaJouj.js.map +0 -1
- package/dist/EmailSettings-DRfOF0Sf.js +0 -78
- package/dist/EmailSettings-DRfOF0Sf.js.map +0 -1
- package/dist/EmailSettings-eLlzzI5D.cjs +0 -1
- package/dist/EmailSettings-eLlzzI5D.cjs.map +0 -1
- package/dist/GoogleLoginButton-JtRViYWS.cjs +0 -1
- package/dist/GoogleLoginButton-JtRViYWS.cjs.map +0 -1
- package/dist/GoogleLoginButton-qf4A_A3G.js.map +0 -1
- package/dist/PermissionsSection-DEMVp7X3.cjs +0 -1
- package/dist/SolanaLoginButton-B04dib6X.js.map +0 -1
- package/dist/SolanaLoginButton-nSJHVFpZ.cjs +0 -1
- package/dist/SolanaLoginButton-nSJHVFpZ.cjs.map +0 -1
- package/dist/plugin-CUxpAjL-.cjs +0 -1
- package/dist/plugin-CUxpAjL-.cjs.map +0 -1
- package/dist/plugin-C_NDZ2-D.js.map +0 -1
- package/dist/useAuth-U5CYsHEU.cjs +0 -1
- package/dist/useUsersStatsSummary-5DJwzntC.js +0 -1246
- package/dist/useUsersStatsSummary-5DJwzntC.js.map +0 -1
- package/dist/useUsersStatsSummary-DgKaUIfs.cjs +0 -1
- package/dist/useUsersStatsSummary-DgKaUIfs.cjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EmailRegisterForm-D2VaJouj.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) => 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): 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 data = await apiClient.post<AuthResponse>('/register', { email, password, name });\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'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 { 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\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}: EmailRegisterFormProps) {\n const { config } = useCedrosLogin();\n const { register, isLoading, error, clearError } = useEmailAuth();\n const [name, setName] = useState('');\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [confirmPassword, setConfirmPassword] = useState('');\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 canSubmit =\n email &&\n password &&\n confirmPassword &&\n passwordsMatch &&\n isPasswordValid &&\n termsValid &&\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(email, password, name || undefined);\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 {/* 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","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","setName","confirmPassword","setConfirmPassword","passwordValidation","setPasswordValidation","termsError","setTermsError","termsConfig","emailOptInConfig","showTerms","termsRequired","termsDefaultChecked","termsLabel","termsUrl","safeTermsUrl","sanitizeExternalUrl","showEmailOptIn","emailOptInDefaultChecked","emailOptInLabel","termsAccepted","setTermsAccepted","emailOptIn","setEmailOptIn","passwordsMatch","isPasswordValid","canSubmit","combinedError","combinedClearError"],"mappings":"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"}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { jsxs as a, jsx as s } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect as y, useMemo as L } from "react";
|
|
3
|
-
import { L as P } from "./LoadingSpinner-6vml-zwr.js";
|
|
4
|
-
import { E as w } from "./ErrorMessage-CcEK0pYO.js";
|
|
5
|
-
import { u as M, A, S as I } from "./AutosaveStatus-CSZsp6w7.js";
|
|
6
|
-
const N = {
|
|
7
|
-
mailgun: "smtp.mailgun.org",
|
|
8
|
-
sendgrid: "smtp.sendgrid.net",
|
|
9
|
-
postmark: "smtp.postmarkapp.com",
|
|
10
|
-
ses: "email-smtp.us-east-1.amazonaws.com",
|
|
11
|
-
resend: "smtp.resend.com"
|
|
12
|
-
}, k = [
|
|
13
|
-
"email_provider",
|
|
14
|
-
"email_smtp_password",
|
|
15
|
-
"email_from_address",
|
|
16
|
-
"email_from_name"
|
|
17
|
-
], C = [
|
|
18
|
-
"email_provider",
|
|
19
|
-
"email_smtp_host",
|
|
20
|
-
"email_smtp_port",
|
|
21
|
-
"email_smtp_user",
|
|
22
|
-
"email_smtp_password",
|
|
23
|
-
"email_smtp_tls",
|
|
24
|
-
"email_from_address",
|
|
25
|
-
"email_from_name"
|
|
26
|
-
];
|
|
27
|
-
function F({ className: n }) {
|
|
28
|
-
const {
|
|
29
|
-
settings: o,
|
|
30
|
-
edits: _,
|
|
31
|
-
isLoading: u,
|
|
32
|
-
autosaveStatus: f,
|
|
33
|
-
autosaveError: h,
|
|
34
|
-
error: d,
|
|
35
|
-
fetchSettings: l,
|
|
36
|
-
handleChange: i,
|
|
37
|
-
getEffectiveValue: c
|
|
38
|
-
} = M();
|
|
39
|
-
y(() => {
|
|
40
|
-
l();
|
|
41
|
-
}, [l]);
|
|
42
|
-
const m = (c("email_provider") || "custom") === "custom", g = c("email_smtp_host"), S = !m || g != null && g !== "", p = L(() => {
|
|
43
|
-
const r = o.email ?? [], e = m ? C : k;
|
|
44
|
-
return r.filter((t) => e.includes(t.key)).sort((t, E) => e.indexOf(t.key) - e.indexOf(E.key));
|
|
45
|
-
}, [o, m]), v = (r, e) => {
|
|
46
|
-
if (i(r, e), r === "email_provider" && e !== "custom") {
|
|
47
|
-
const t = N[e];
|
|
48
|
-
t && (i("email_smtp_host", t), i("email_smtp_port", "587"), i("email_smtp_tls", "true"));
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
return u && Object.keys(o).length === 0 ? /* @__PURE__ */ a("div", { className: `cedros-system-settings cedros-system-settings-loading ${n ?? ""}`, children: [
|
|
52
|
-
/* @__PURE__ */ s(P, {}),
|
|
53
|
-
/* @__PURE__ */ s("span", { children: "Loading settings..." })
|
|
54
|
-
] }) : d ? /* @__PURE__ */ s("div", { className: `cedros-system-settings ${n ?? ""}`, children: /* @__PURE__ */ s(w, { error: d.message }) }) : /* @__PURE__ */ a("div", { className: `cedros-system-settings ${n ?? ""}`, children: [
|
|
55
|
-
/* @__PURE__ */ a("div", { className: "cedros-settings-page-header", children: [
|
|
56
|
-
/* @__PURE__ */ a("div", { className: "cedros-settings-page-header-content", children: [
|
|
57
|
-
/* @__PURE__ */ s("h2", { className: "cedros-settings-page-title", children: "Email & SMTP" }),
|
|
58
|
-
/* @__PURE__ */ s("p", { className: "cedros-settings-page-description", children: "Configure email delivery for verification emails, password resets, and instant link login." })
|
|
59
|
-
] }),
|
|
60
|
-
/* @__PURE__ */ s(A, { status: f, error: h })
|
|
61
|
-
] }),
|
|
62
|
-
!S && /* @__PURE__ */ s("div", { className: "cedros-settings-warning-banner", children: "Email features (verification, password reset, instant link) are disabled until SMTP is configured. Select a provider or enter custom SMTP settings below." }),
|
|
63
|
-
p.length === 0 ? /* @__PURE__ */ s("div", { className: "cedros-system-settings-empty", children: /* @__PURE__ */ s("p", { children: "No settings found for this section." }) }) : /* @__PURE__ */ s(
|
|
64
|
-
I,
|
|
65
|
-
{
|
|
66
|
-
settings: p,
|
|
67
|
-
edits: _,
|
|
68
|
-
onChange: v
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
] });
|
|
72
|
-
}
|
|
73
|
-
export {
|
|
74
|
-
C as A,
|
|
75
|
-
F as E,
|
|
76
|
-
N as P,
|
|
77
|
-
k as S
|
|
78
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EmailSettings-DRfOF0Sf.js","sources":["../src/components/admin/settings/EmailSettings.tsx"],"sourcesContent":["/**\n * Email settings page - SMTP configuration with provider-aware auto-fill\n */\nimport { useEffect, useMemo } from 'react';\nimport { LoadingSpinner } from '../../shared/LoadingSpinner';\nimport { ErrorMessage } from '../../shared/ErrorMessage';\nimport { useSettingsAutosave } from '../../../hooks/useSettingsAutosave';\nimport { SettingsSection } from './settingsInputs';\nimport { AutosaveStatus } from './AutosaveStatus';\n\n/** SMTP hosts auto-filled when selecting a known provider */\nexport const PROVIDER_SMTP_HOSTS: Record<string, string> = {\n mailgun: 'smtp.mailgun.org',\n sendgrid: 'smtp.sendgrid.net',\n postmark: 'smtp.postmarkapp.com',\n ses: 'email-smtp.us-east-1.amazonaws.com',\n resend: 'smtp.resend.com',\n};\n\n/** Simplified fields for known providers (host/port/tls auto-configured) */\nexport const SIMPLIFIED_EMAIL_FIELDS = [\n 'email_provider',\n 'email_smtp_password',\n 'email_from_address',\n 'email_from_name',\n];\n\n/** All fields for custom SMTP */\nexport const ALL_EMAIL_FIELDS = [\n 'email_provider',\n 'email_smtp_host',\n 'email_smtp_port',\n 'email_smtp_user',\n 'email_smtp_password',\n 'email_smtp_tls',\n 'email_from_address',\n 'email_from_name',\n];\n\nexport interface EmailSettingsProps {\n className?: string;\n}\n\nexport function EmailSettings({ className }: EmailSettingsProps) {\n const {\n settings,\n edits,\n isLoading,\n autosaveStatus,\n autosaveError,\n error,\n fetchSettings,\n handleChange,\n getEffectiveValue,\n } = useSettingsAutosave();\n\n useEffect(() => {\n fetchSettings();\n }, [fetchSettings]);\n\n const emailProvider = getEffectiveValue('email_provider') || 'custom';\n const isCustomProvider = emailProvider === 'custom';\n\n // Check if SMTP is configured\n const smtpHost = getEffectiveValue('email_smtp_host');\n const isSmtpConfigured = !isCustomProvider || (smtpHost != null && smtpHost !== '');\n\n // Filter and order fields based on provider mode\n const currentSettings = useMemo(() => {\n const categorySettings = settings['email'] ?? [];\n const fieldsToShow = isCustomProvider ? ALL_EMAIL_FIELDS : SIMPLIFIED_EMAIL_FIELDS;\n return categorySettings\n .filter((s) => fieldsToShow.includes(s.key))\n .sort((a, b) => fieldsToShow.indexOf(a.key) - fieldsToShow.indexOf(b.key));\n }, [settings, isCustomProvider]);\n\n // Auto-set SMTP host/port/tls when provider changes\n const handleProviderAwareChange = (key: string, value: string) => {\n handleChange(key, value);\n if (key === 'email_provider' && value !== 'custom') {\n const host = PROVIDER_SMTP_HOSTS[value];\n if (host) {\n handleChange('email_smtp_host', host);\n handleChange('email_smtp_port', '587');\n handleChange('email_smtp_tls', 'true');\n }\n }\n };\n\n if (isLoading && Object.keys(settings).length === 0) {\n return (\n <div className={`cedros-system-settings cedros-system-settings-loading ${className ?? ''}`}>\n <LoadingSpinner />\n <span>Loading settings...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className={`cedros-system-settings ${className ?? ''}`}>\n <ErrorMessage error={error.message} />\n </div>\n );\n }\n\n return (\n <div className={`cedros-system-settings ${className ?? ''}`}>\n <div className=\"cedros-settings-page-header\">\n <div className=\"cedros-settings-page-header-content\">\n <h2 className=\"cedros-settings-page-title\">Email & SMTP</h2>\n <p className=\"cedros-settings-page-description\">\n Configure email delivery for verification emails, password resets, and instant link\n login.\n </p>\n </div>\n <AutosaveStatus status={autosaveStatus} error={autosaveError} />\n </div>\n\n {!isSmtpConfigured && (\n <div className=\"cedros-settings-warning-banner\">\n Email features (verification, password reset, instant link) are disabled until SMTP is\n configured. Select a provider or enter custom SMTP settings below.\n </div>\n )}\n\n {currentSettings.length === 0 ? (\n <div className=\"cedros-system-settings-empty\">\n <p>No settings found for this section.</p>\n </div>\n ) : (\n <SettingsSection\n settings={currentSettings}\n edits={edits}\n onChange={handleProviderAwareChange}\n />\n )}\n </div>\n );\n}\n"],"names":["PROVIDER_SMTP_HOSTS","SIMPLIFIED_EMAIL_FIELDS","ALL_EMAIL_FIELDS","EmailSettings","className","settings","edits","isLoading","autosaveStatus","autosaveError","error","fetchSettings","handleChange","getEffectiveValue","useSettingsAutosave","useEffect","isCustomProvider","smtpHost","isSmtpConfigured","currentSettings","useMemo","categorySettings","fieldsToShow","s","a","b","handleProviderAwareChange","key","value","host","jsx","LoadingSpinner","ErrorMessage","jsxs","AutosaveStatus","SettingsSection"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const s=require("react/jsx-runtime"),p=require("react"),M=require("./LoadingSpinner-d6sSxgQN.cjs"),P=require("./ErrorMessage-CHbYbVi2.cjs"),m=require("./AutosaveStatus-BKsCIvPj.cjs"),u={mailgun:"smtp.mailgun.org",sendgrid:"smtp.sendgrid.net",postmark:"smtp.postmarkapp.com",ses:"email-smtp.us-east-1.amazonaws.com",resend:"smtp.resend.com"},S=["email_provider","email_smtp_password","email_from_address","email_from_name"],h=["email_provider","email_smtp_host","email_smtp_port","email_smtp_user","email_smtp_password","email_smtp_tls","email_from_address","email_from_name"];function y({className:n}){const{settings:a,edits:f,isLoading:v,autosaveStatus:E,autosaveError:x,error:d,fetchSettings:c,handleChange:i,getEffectiveValue:l}=m.useSettingsAutosave();p.useEffect(()=>{c()},[c]);const o=(l("email_provider")||"custom")==="custom",g=l("email_smtp_host"),L=!o||g!=null&&g!=="",_=p.useMemo(()=>{const r=a.email??[],e=o?h:S;return r.filter(t=>e.includes(t.key)).sort((t,I)=>e.indexOf(t.key)-e.indexOf(I.key))},[a,o]),j=(r,e)=>{if(i(r,e),r==="email_provider"&&e!=="custom"){const t=u[e];t&&(i("email_smtp_host",t),i("email_smtp_port","587"),i("email_smtp_tls","true"))}};return v&&Object.keys(a).length===0?s.jsxs("div",{className:`cedros-system-settings cedros-system-settings-loading ${n??""}`,children:[s.jsx(M.LoadingSpinner,{}),s.jsx("span",{children:"Loading settings..."})]}):d?s.jsx("div",{className:`cedros-system-settings ${n??""}`,children:s.jsx(P.ErrorMessage,{error:d.message})}):s.jsxs("div",{className:`cedros-system-settings ${n??""}`,children:[s.jsxs("div",{className:"cedros-settings-page-header",children:[s.jsxs("div",{className:"cedros-settings-page-header-content",children:[s.jsx("h2",{className:"cedros-settings-page-title",children:"Email & SMTP"}),s.jsx("p",{className:"cedros-settings-page-description",children:"Configure email delivery for verification emails, password resets, and instant link login."})]}),s.jsx(m.AutosaveStatus,{status:E,error:x})]}),!L&&s.jsx("div",{className:"cedros-settings-warning-banner",children:"Email features (verification, password reset, instant link) are disabled until SMTP is configured. Select a provider or enter custom SMTP settings below."}),_.length===0?s.jsx("div",{className:"cedros-system-settings-empty",children:s.jsx("p",{children:"No settings found for this section."})}):s.jsx(m.SettingsSection,{settings:_,edits:f,onChange:j})]})}exports.ALL_EMAIL_FIELDS=h;exports.EmailSettings=y;exports.PROVIDER_SMTP_HOSTS=u;exports.SIMPLIFIED_EMAIL_FIELDS=S;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EmailSettings-eLlzzI5D.cjs","sources":["../src/components/admin/settings/EmailSettings.tsx"],"sourcesContent":["/**\n * Email settings page - SMTP configuration with provider-aware auto-fill\n */\nimport { useEffect, useMemo } from 'react';\nimport { LoadingSpinner } from '../../shared/LoadingSpinner';\nimport { ErrorMessage } from '../../shared/ErrorMessage';\nimport { useSettingsAutosave } from '../../../hooks/useSettingsAutosave';\nimport { SettingsSection } from './settingsInputs';\nimport { AutosaveStatus } from './AutosaveStatus';\n\n/** SMTP hosts auto-filled when selecting a known provider */\nexport const PROVIDER_SMTP_HOSTS: Record<string, string> = {\n mailgun: 'smtp.mailgun.org',\n sendgrid: 'smtp.sendgrid.net',\n postmark: 'smtp.postmarkapp.com',\n ses: 'email-smtp.us-east-1.amazonaws.com',\n resend: 'smtp.resend.com',\n};\n\n/** Simplified fields for known providers (host/port/tls auto-configured) */\nexport const SIMPLIFIED_EMAIL_FIELDS = [\n 'email_provider',\n 'email_smtp_password',\n 'email_from_address',\n 'email_from_name',\n];\n\n/** All fields for custom SMTP */\nexport const ALL_EMAIL_FIELDS = [\n 'email_provider',\n 'email_smtp_host',\n 'email_smtp_port',\n 'email_smtp_user',\n 'email_smtp_password',\n 'email_smtp_tls',\n 'email_from_address',\n 'email_from_name',\n];\n\nexport interface EmailSettingsProps {\n className?: string;\n}\n\nexport function EmailSettings({ className }: EmailSettingsProps) {\n const {\n settings,\n edits,\n isLoading,\n autosaveStatus,\n autosaveError,\n error,\n fetchSettings,\n handleChange,\n getEffectiveValue,\n } = useSettingsAutosave();\n\n useEffect(() => {\n fetchSettings();\n }, [fetchSettings]);\n\n const emailProvider = getEffectiveValue('email_provider') || 'custom';\n const isCustomProvider = emailProvider === 'custom';\n\n // Check if SMTP is configured\n const smtpHost = getEffectiveValue('email_smtp_host');\n const isSmtpConfigured = !isCustomProvider || (smtpHost != null && smtpHost !== '');\n\n // Filter and order fields based on provider mode\n const currentSettings = useMemo(() => {\n const categorySettings = settings['email'] ?? [];\n const fieldsToShow = isCustomProvider ? ALL_EMAIL_FIELDS : SIMPLIFIED_EMAIL_FIELDS;\n return categorySettings\n .filter((s) => fieldsToShow.includes(s.key))\n .sort((a, b) => fieldsToShow.indexOf(a.key) - fieldsToShow.indexOf(b.key));\n }, [settings, isCustomProvider]);\n\n // Auto-set SMTP host/port/tls when provider changes\n const handleProviderAwareChange = (key: string, value: string) => {\n handleChange(key, value);\n if (key === 'email_provider' && value !== 'custom') {\n const host = PROVIDER_SMTP_HOSTS[value];\n if (host) {\n handleChange('email_smtp_host', host);\n handleChange('email_smtp_port', '587');\n handleChange('email_smtp_tls', 'true');\n }\n }\n };\n\n if (isLoading && Object.keys(settings).length === 0) {\n return (\n <div className={`cedros-system-settings cedros-system-settings-loading ${className ?? ''}`}>\n <LoadingSpinner />\n <span>Loading settings...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className={`cedros-system-settings ${className ?? ''}`}>\n <ErrorMessage error={error.message} />\n </div>\n );\n }\n\n return (\n <div className={`cedros-system-settings ${className ?? ''}`}>\n <div className=\"cedros-settings-page-header\">\n <div className=\"cedros-settings-page-header-content\">\n <h2 className=\"cedros-settings-page-title\">Email & SMTP</h2>\n <p className=\"cedros-settings-page-description\">\n Configure email delivery for verification emails, password resets, and instant link\n login.\n </p>\n </div>\n <AutosaveStatus status={autosaveStatus} error={autosaveError} />\n </div>\n\n {!isSmtpConfigured && (\n <div className=\"cedros-settings-warning-banner\">\n Email features (verification, password reset, instant link) are disabled until SMTP is\n configured. Select a provider or enter custom SMTP settings below.\n </div>\n )}\n\n {currentSettings.length === 0 ? (\n <div className=\"cedros-system-settings-empty\">\n <p>No settings found for this section.</p>\n </div>\n ) : (\n <SettingsSection\n settings={currentSettings}\n edits={edits}\n onChange={handleProviderAwareChange}\n />\n )}\n </div>\n );\n}\n"],"names":["PROVIDER_SMTP_HOSTS","SIMPLIFIED_EMAIL_FIELDS","ALL_EMAIL_FIELDS","EmailSettings","className","settings","edits","isLoading","autosaveStatus","autosaveError","error","fetchSettings","handleChange","getEffectiveValue","useSettingsAutosave","useEffect","isCustomProvider","smtpHost","isSmtpConfigured","currentSettings","useMemo","categorySettings","fieldsToShow","s","a","b","handleProviderAwareChange","key","value","host","jsx","LoadingSpinner","ErrorMessage","jsxs","AutosaveStatus","SettingsSection"],"mappings":"oMAWaA,EAA8C,CACzD,QAAS,mBACT,SAAU,oBACV,SAAU,uBACV,IAAK,qCACL,OAAQ,iBACV,EAGaC,EAA0B,CACrC,iBACA,sBACA,qBACA,iBACF,EAGaC,EAAmB,CAC9B,iBACA,kBACA,kBACA,kBACA,sBACA,iBACA,qBACA,iBACF,EAMO,SAASC,EAAc,CAAE,UAAAC,GAAiC,CAC/D,KAAM,CACJ,SAAAC,EACA,MAAAC,EACA,UAAAC,EACA,eAAAC,EACA,cAAAC,EACA,MAAAC,EACA,cAAAC,EACA,aAAAC,EACA,kBAAAC,CAAA,EACEC,sBAAA,EAEJC,EAAAA,UAAU,IAAM,CACdJ,EAAA,CACF,EAAG,CAACA,CAAa,CAAC,EAGlB,MAAMK,GADgBH,EAAkB,gBAAgB,GAAK,YAClB,SAGrCI,EAAWJ,EAAkB,iBAAiB,EAC9CK,EAAmB,CAACF,GAAqBC,GAAY,MAAQA,IAAa,GAG1EE,EAAkBC,EAAAA,QAAQ,IAAM,CACpC,MAAMC,EAAmBhB,EAAS,OAAY,CAAA,EACxCiB,EAAeN,EAAmBd,EAAmBD,EAC3D,OAAOoB,EACJ,OAAQE,GAAMD,EAAa,SAASC,EAAE,GAAG,CAAC,EAC1C,KAAK,CAACC,EAAGC,IAAMH,EAAa,QAAQE,EAAE,GAAG,EAAIF,EAAa,QAAQG,EAAE,GAAG,CAAC,CAC7E,EAAG,CAACpB,EAAUW,CAAgB,CAAC,EAGzBU,EAA4B,CAACC,EAAaC,IAAkB,CAEhE,GADAhB,EAAae,EAAKC,CAAK,EACnBD,IAAQ,kBAAoBC,IAAU,SAAU,CAClD,MAAMC,EAAO7B,EAAoB4B,CAAK,EAClCC,IACFjB,EAAa,kBAAmBiB,CAAI,EACpCjB,EAAa,kBAAmB,KAAK,EACrCA,EAAa,iBAAkB,MAAM,EAEzC,CACF,EAEA,OAAIL,GAAa,OAAO,KAAKF,CAAQ,EAAE,SAAW,SAE7C,MAAA,CAAI,UAAW,yDAAyDD,GAAa,EAAE,GACtF,SAAA,CAAA0B,EAAAA,IAACC,EAAAA,eAAA,EAAe,EAChBD,EAAAA,IAAC,QAAK,SAAA,qBAAA,CAAmB,CAAA,EAC3B,EAIApB,EAEAoB,EAAAA,IAAC,MAAA,CAAI,UAAW,0BAA0B1B,GAAa,EAAE,GACvD,SAAA0B,EAAAA,IAACE,EAAAA,aAAA,CAAa,MAAOtB,EAAM,OAAA,CAAS,EACtC,SAKD,MAAA,CAAI,UAAW,0BAA0BN,GAAa,EAAE,GACvD,SAAA,CAAA6B,EAAAA,KAAC,MAAA,CAAI,UAAU,8BACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,sCACb,SAAA,CAAAH,EAAAA,IAAC,KAAA,CAAG,UAAU,6BAA6B,SAAA,eAAY,EACvDA,EAAAA,IAAC,IAAA,CAAE,UAAU,mCAAmC,SAAA,4FAAA,CAGhD,CAAA,EACF,EACAA,EAAAA,IAACI,EAAAA,eAAA,CAAe,OAAQ1B,EAAgB,MAAOC,CAAA,CAAe,CAAA,EAChE,EAEC,CAACS,GACAY,EAAAA,IAAC,MAAA,CAAI,UAAU,iCAAiC,SAAA,4JAGhD,EAGDX,EAAgB,SAAW,EAC1BW,EAAAA,IAAC,MAAA,CAAI,UAAU,+BACb,SAAAA,EAAAA,IAAC,IAAA,CAAE,SAAA,qCAAA,CAAmC,CAAA,CACxC,EAEAA,EAAAA,IAACK,EAAAA,gBAAA,CACC,SAAUhB,EACV,MAAAb,EACA,SAAUoB,CAAA,CAAA,CACZ,EAEJ,CAEJ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const u=require("react/jsx-runtime"),n=require("react"),E=require("./useCedrosLogin-DtJorrE7.cjs"),S=require("./LoadingSpinner-d6sSxgQN.cjs"),_={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(o=>o.resolve()),this.callbacks=[]):g.addEventListener("load",()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(o=>o.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(o=>o.resolve()),this.callbacks=[]},s.onerror=()=>{this.loading=!1,s.remove();const o=new Error("Failed to load Google Sign-In script");this.callbacks.forEach(f=>f.reject(o)),this.callbacks=[]},document.head.appendChild(s)}))},_reset(){this.loading=!1,this.loaded=!1,this.error=null,this.callbacks=[]}};function I(){const{config:t,_internal:c}=E.useCedrosLogin(),[g,s]=n.useState(!1),[o,f]=n.useState(!1),[b,l]=n.useState(null),[m,h]=n.useState(null),a=n.useRef(null),p=n.useRef(t),d=n.useRef(null),k=n.useMemo(()=>new E.ApiClient({baseUrl:t.serverUrl,timeoutMs:t.requestTimeout,retryAttempts:t.retryAttempts}),[t.serverUrl,t.requestTimeout,t.retryAttempts]);n.useEffect(()=>{p.current=t},[t]);const w=n.useCallback(async e=>{const r=a.current;if(r){if(e.error){const i={code:"SERVER_ERROR",message:e.error==="access_denied"?"Google sign-in was cancelled.":"Unable to sign in with Google. Please try again."};l(i),s(!1),a.current=null,r.reject(i);return}try{const i=await k.post("/google",{accessToken:e.access_token});p.current.callbacks?.onLoginSuccess?.(i.user,"google"),c?.handleLoginSuccess(i.user,i.tokens),s(!1),r.resolve(i)}catch(i){const R=E.handleApiError(i,"Unable to sign in with Google. Please try again.");R.code==="ACCOUNT_LINK_REQUIRED"&&h(e.access_token??null),l(R),s(!1),r.reject(R)}finally{a.current=null}}},[k,c]),C=n.useCallback(e=>{const r=a.current;if(!r)return;const i={code:"SERVER_ERROR",message:e.type==="popup_failed_to_open"?"Google sign-in popup was blocked. Please allow popups for this site.":"Google sign-in was cancelled."};l(i),s(!1),a.current=null,r.reject(i)},[]);n.useEffect(()=>{if(!t.googleClientId)return;let e=!0;return _.load().then(()=>{if(!e)return;const r=window.google?.accounts?.oauth2?.initTokenClient({client_id:t.googleClientId,scope:"openid email profile",callback:w,error_callback:C});r&&(d.current=r,f(!0))}).catch(()=>{e&&l({code:"SERVER_ERROR",message:"Unable to load Google sign-in. Please refresh and try again."})}),()=>{e=!1,d.current=null}},[t.googleClientId,w,C]);const y=n.useCallback(async()=>{if(!t.googleClientId){const e={code:"VALIDATION_ERROR",message:"Google Client ID not configured"};throw l(e),e}if(!o){const e={code:"VALIDATION_ERROR",message:"Google sign-in is not ready yet. Please wait a moment and try again."};throw l(e),e}if(a.current){const e={code:"VALIDATION_ERROR",message:"Google sign-in is already in progress."};throw l(e),e}return s(!0),l(null),new Promise((e,r)=>{a.current={resolve:e,reject:r},d.current?.requestAccessToken()})},[t.googleClientId,o]),A=n.useCallback(()=>l(null),[]),L=n.useCallback(()=>h(null),[]);return{signIn:y,isLoading:g,isInitialized:o,error:b,clearError:A,pendingLinkToken:m,clearPendingLink:L}}function G({onSuccess:t,onError:c,className:g="",variant:s="default",size:o="md",disabled:f=!1}){const{signIn:b,isLoading:l,isInitialized:m}=I(),h=async()=>{try{await b(),t?.()}catch(d){const k=d instanceof Error?d:new Error(String(d));c?.(k)}},a={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},p={default:"cedros-button-social",outline:"cedros-button-social-outline"};return u.jsxs("button",{type:"button",className:`cedros-button ${p[s]} ${a[o]} ${g}`,onClick:h,disabled:f||!m||l,"aria-label":"Sign in with Google",children:[l?u.jsx(S.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=I;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-JtRViYWS.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 signIn: () => 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\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 });\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 }\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 (): Promise<AuthResponse> => {\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}\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}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\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","apiClient","useMemo","ApiClient","useEffect","handleTokenResponse","useCallback","response","callbacks","err","data","authError","handleApiError","handleTokenError","isMounted","client","signIn","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","variantClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-qf4A_A3G.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 signIn: () => 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\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 });\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 }\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 (): Promise<AuthResponse> => {\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}\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}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\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","apiClient","useMemo","ApiClient","useEffect","handleTokenResponse","useCallback","response","callbacks","err","data","authError","handleApiError","handleTokenError","isMounted","client","signIn","clearError","clearPendingLink","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("react/jsx-runtime"),r=require("react"),k=require("./LoadingSpinner-d6sSxgQN.cjs"),$=require("./ErrorMessage-CHbYbVi2.cjs"),D=require("./sanitization-Bo_tn-L2.cjs"),q=require("./validation-BuGQrA-K.cjs"),A=require("./useCedrosLogin-DtJorrE7.cjs"),F=require("./useOrgs-CNqfn-fk.cjs"),P=require("./useSystemSettings-B2jY51ob.cjs"),U=["owner","admin","member"];function z({members:s,currentUserId:t,isLoading:a=!1,error:i,canManage:n=!1,canChangeRoles:o=!1,onUpdateRole:h,onRemove:m,className:f=""}){const[l,j]=r.useState("name"),[d,y]=r.useState("asc"),p=u=>{l===u?y(d==="asc"?"desc":"asc"):(j(u),y("asc"))},c=r.useMemo(()=>{const u={owner:0,admin:1,member:2};return[...s].sort((g,b)=>{let S,w;switch(l){case"name":S=(g.user.name||g.user.email||"").toLowerCase(),w=(b.user.name||b.user.email||"").toLowerCase();break;case"role":S=u[g.role]??99,w=u[b.role]??99;break;case"joinedAt":S=new Date(g.joinedAt).getTime(),w=new Date(b.joinedAt).getTime();break;default:return 0}return S<w?d==="asc"?-1:1:S>w?d==="asc"?1:-1:0})},[s,l,d]);return a&&s.length===0?e.jsxs("div",{className:`cedros-member-list cedros-member-list-loading ${f}`,children:[e.jsx(k.LoadingSpinner,{}),e.jsx("span",{children:"Loading members..."})]}):i?e.jsx("div",{className:`cedros-member-list ${f}`,children:e.jsx($.ErrorMessage,{error:i})}):s.length===0?e.jsx("div",{className:`cedros-member-list cedros-member-list-empty ${f}`,children:e.jsx("p",{children:"No members found."})}):e.jsx("div",{className:`cedros-member-list ${f}`,children:e.jsxs("table",{className:"cedros-member-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${l==="name"?"cedros-admin-sort-active":""}`,onClick:()=>p("name"),children:["Member"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:l==="name"?d==="asc"?"↑":"↓":"↕"})]})}),e.jsx("th",{children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${l==="role"?"cedros-admin-sort-active":""}`,onClick:()=>p("role"),children:["Role"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:l==="role"?d==="asc"?"↑":"↓":"↕"})]})}),e.jsx("th",{children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${l==="joinedAt"?"cedros-admin-sort-active":""}`,onClick:()=>p("joinedAt"),children:["Joined"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:l==="joinedAt"?d==="asc"?"↑":"↓":"↕"})]})}),(n||o)&&e.jsx("th",{children:"Actions"})]})}),e.jsx("tbody",{children:c.map(u=>e.jsx(B,{member:u,isCurrentUser:u.userId===t,canManage:n,canChangeRoles:o,onUpdateRole:h,onRemove:m},u.id))})]})})}function B({member:s,isCurrentUser:t,canManage:a,canChangeRoles:i,onUpdateRole:n,onRemove:o}){const[h,m]=r.useState(!1),[f,l]=r.useState(s.role),j=r.useCallback(async c=>{if(!(!n||c===s.role)){m(!0);try{await n(s.userId,c),l(c)}catch{l(s.role)}finally{m(!1)}}},[s.userId,s.role,n]),d=r.useCallback(async()=>{if(!(!o||!window.confirm(`Are you sure you want to remove ${s.user.name||s.user.email} from this organization?`))){m(!0);try{await o(s.userId)}finally{m(!1)}}},[s.userId,s.user.name,s.user.email,o]),y=s.role==="owner",p=!t&&!y;return e.jsxs("tr",{className:`cedros-member-row ${t?"cedros-member-row-current":""}`,children:[e.jsxs("td",{className:"cedros-member-info",children:[e.jsx(W,{user:s.user}),e.jsxs("div",{className:"cedros-member-details",children:[e.jsxs("span",{className:"cedros-member-name",children:[s.user.name||"Unknown",t&&e.jsx("span",{className:"cedros-member-you",children:"(you)"})]}),e.jsx("span",{className:"cedros-member-email",children:s.user.email})]})]}),e.jsx("td",{className:"cedros-member-role",children:i&&p&&n?e.jsx("select",{value:f,onChange:c=>j(c.target.value),disabled:h,className:"cedros-role-select",children:U.map(c=>e.jsx("option",{value:c,children:c.charAt(0).toUpperCase()+c.slice(1)},c))}):e.jsx("span",{className:`cedros-role-badge cedros-role-badge-${s.role}`,children:s.role.charAt(0).toUpperCase()+s.role.slice(1)})}),e.jsx("td",{className:"cedros-member-joined",children:H(s.joinedAt)}),(a||i)&&e.jsx("td",{className:"cedros-member-actions",children:a&&p&&o&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-danger cedros-button-sm",onClick:d,disabled:h,"aria-label":`Remove ${s.user.name||s.user.email}`,children:h?e.jsx(k.LoadingSpinner,{size:"sm"}):"Remove"})})]})}function W({user:s}){const t=D.sanitizeImageUrl(s.picture);if(t)return e.jsx("img",{src:t,alt:s.name||s.email||"Member",className:"cedros-member-avatar",referrerPolicy:"no-referrer"});const a=(s.name?.[0]||s.email?.[0]||"?").toUpperCase();return e.jsx("div",{className:"cedros-member-avatar-placeholder",children:a})}function H(s){return new Date(s).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}const V=["admin","member"];function K({onSubmit:s,isLoading:t=!1,error:a,availableRoles:i=V,defaultRole:n="member",className:o=""}){const[h,m]=r.useState(""),[f,l]=r.useState(n),[j,d]=r.useState(null),[y,p]=r.useState(!1),c=r.useRef(null),u=r.useRef(!0);r.useEffect(()=>(u.current=!0,()=>{u.current=!1,c.current!==null&&(window.clearTimeout(c.current),c.current=null)}),[]);const g=r.useCallback(async b=>{b.preventDefault(),d(null),p(!1);const S=h.trim();if(!S){d("Email is required");return}if(!q.validateEmail(S)){d("Please enter a valid email address");return}try{await s(S,f),m(""),l(n),p(!0),c.current!==null&&window.clearTimeout(c.current),c.current=window.setTimeout(()=>{u.current&&p(!1),c.current=null},3e3)}catch{}},[h,f,n,s]);return e.jsxs("form",{className:`cedros-invite-form ${o}`,onSubmit:g,children:[(a||j)&&e.jsx($.ErrorMessage,{error:j??a??null}),y&&e.jsxs("div",{className:"cedros-invite-success",role:"status",children:[e.jsx(G,{}),e.jsx("span",{children:"Invitation sent successfully!"})]}),e.jsxs("div",{className:"cedros-invite-form-row",children:[e.jsxs("div",{className:"cedros-form-group cedros-invite-email-group",children:[e.jsx("label",{htmlFor:"invite-email",className:"cedros-form-label",children:"Email Address"}),e.jsx("input",{id:"invite-email",type:"email",className:"cedros-form-input",value:h,onChange:b=>m(b.target.value),placeholder:"colleague@example.com",disabled:t,autoComplete:"email"})]}),e.jsxs("div",{className:"cedros-form-group cedros-invite-role-group",children:[e.jsx("label",{htmlFor:"invite-role",className:"cedros-form-label",children:"Role"}),e.jsx("select",{id:"invite-role",className:"cedros-form-select",value:f,onChange:b=>l(b.target.value),disabled:t,children:i.map(b=>e.jsx("option",{value:b,children:b.charAt(0).toUpperCase()+b.slice(1)},b))})]}),e.jsx("button",{type:"submit",className:"cedros-button cedros-button-primary cedros-invite-submit",disabled:t||!h.trim(),children:t?e.jsx(k.LoadingSpinner,{size:"sm"}):"Send Invite"})]}),e.jsx("p",{className:"cedros-form-hint",children:"The invited user will receive an email with a link to join your organization."})]})}function G(){return e.jsx("svg",{className:"cedros-invite-check",width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M3 8L6 11L13 5",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})}function J({invites:s,isLoading:t=!1,error:a,canManage:i=!1,onCancel:n,onResend:o,className:h=""}){return t&&s.length===0?e.jsxs("div",{className:`cedros-invite-list cedros-invite-list-loading ${h}`,children:[e.jsx(k.LoadingSpinner,{}),e.jsx("span",{children:"Loading invites..."})]}):a?e.jsx("div",{className:`cedros-invite-list ${h}`,children:e.jsx($.ErrorMessage,{error:a})}):s.length===0?e.jsx("div",{className:`cedros-invite-list cedros-invite-list-empty ${h}`,children:e.jsx("p",{children:"No pending invites."})}):e.jsx("div",{className:`cedros-invite-list ${h}`,children:e.jsx("ul",{className:"cedros-invite-items",children:s.map(m=>e.jsx(Y,{invite:m,canManage:i,onCancel:n,onResend:o},m.id))})})}function Y({invite:s,canManage:t,onCancel:a,onResend:i}){const[n,o]=r.useState(!1),[h,m]=r.useState(!1),f=r.useRef(null),l=new Date(s.expiresAt)<new Date,j=r.useCallback(async()=>{if(!(!a||!window.confirm(`Are you sure you want to cancel the invite for ${s.email}?`))){o(!0);try{await a(s.id)}finally{o(!1)}}},[s.id,s.email,a]),d=r.useCallback(async()=>{if(i){o(!0),m(!1);try{await i(s.id),m(!0),f.current!==null&&window.clearTimeout(f.current),f.current=window.setTimeout(()=>{m(!1),f.current=null},3e3)}finally{o(!1)}}},[s.id,i]);return r.useEffect(()=>()=>{f.current!==null&&(window.clearTimeout(f.current),f.current=null)},[]),e.jsxs("li",{className:`cedros-invite-item ${l?"cedros-invite-item-expired":""}`,children:[e.jsxs("div",{className:"cedros-invite-item-info",children:[e.jsxs("div",{className:"cedros-invite-item-main",children:[e.jsx("span",{className:"cedros-invite-item-email",children:s.email}),e.jsx("span",{className:`cedros-role-badge cedros-role-badge-${s.role}`,children:s.role.charAt(0).toUpperCase()+s.role.slice(1)}),l&&e.jsx("span",{className:"cedros-invite-expired-badge",children:"Expired"})]}),e.jsxs("div",{className:"cedros-invite-item-meta",children:[e.jsxs("span",{className:"cedros-invite-item-date",children:["Invited ",M(s.createdAt)]}),!l&&e.jsxs("span",{className:"cedros-invite-item-expires",children:["Expires ",Q(s.expiresAt)]})]})]}),t&&e.jsxs("div",{className:"cedros-invite-item-actions",children:[h&&e.jsx("span",{className:"cedros-invite-resend-success",children:"Sent!"}),i&&!l&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:d,disabled:n,"aria-label":`Resend invite to ${s.email}`,children:n?e.jsx(k.LoadingSpinner,{size:"sm"}):"Resend"}),a&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-danger cedros-button-sm",onClick:j,disabled:n,"aria-label":`Cancel invite for ${s.email}`,children:"Cancel"})]})]})}function M(s){return new Date(s).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Q(s){const t=new Date(s),a=new Date,i=t.getTime()-a.getTime(),n=Math.ceil(i/(1e3*60*60*24));return n<0?"expired":n===0?"today":n===1?"tomorrow":n<7?`in ${n} days`:M(s)}class X{client;constructor(t,a,i,n){this.client=new A.ApiClient({baseUrl:t,timeoutMs:a,retryAttempts:i,getAccessToken:n})}async listMembers(t,a=50,i=0){try{const n=await this.client.get(`/orgs/${t}/members?limit=${a}&offset=${i}`);return{members:n.members.map(o=>({id:o.id,userId:o.userId,orgId:t,role:o.role,joinedAt:o.joinedAt,user:{id:o.userId,email:o.email,name:o.name}})),total:n.total}}catch(n){throw A.handleApiError(n,"Failed to list members")}}async updateMemberRole(t,a,i){try{return await this.client.patch(`/orgs/${t}/members/${a}`,i)}catch(n){throw A.handleApiError(n,"Failed to update member role")}}async removeMember(t,a){try{await this.client.delete(`/orgs/${t}/members/${a}`)}catch(i){throw A.handleApiError(i,"Failed to remove member")}}}function Z(s){const{config:t,authState:a,_internal:i}=A.useCedrosLogin(),[n,o]=r.useState([]),[h,m]=r.useState(0),[f,l]=r.useState(!1),[j,d]=r.useState(null),y=r.useRef(void 0),p=r.useRef(0),c=r.useMemo(()=>new X(t.serverUrl,t.requestTimeout,t.retryAttempts,i?.getAccessToken),[t.serverUrl,t.requestTimeout,t.retryAttempts,i]),u=r.useRef(c);u.current=c;const g=r.useCallback(async w=>{if(!s||a!=="authenticated"){o([]),m(0);return}l(!0),d(null);const x=++p.current;try{const{limit:v=50,offset:N=0}=w??{},C=await u.current.listMembers(s,v,N);if(x!==p.current)return;o(C.members),m(C.total)}catch(v){if(x!==p.current)return;d(v)}finally{x===p.current&&l(!1)}},[s,a]);r.useEffect(()=>{if(a!=="authenticated"){y.current=void 0;return}s!==y.current&&(y.current=s,g())},[s,a,g]);const b=r.useCallback(async(w,x)=>{if(!s)throw new Error("No organization selected");l(!0),d(null);try{await u.current.updateMemberRole(s,w,{role:x}),await g()}catch(v){throw d(v),v}finally{l(!1)}},[s,g]),S=r.useCallback(async w=>{if(!s)throw new Error("No organization selected");l(!0),d(null);try{await u.current.removeMember(s,w),await g()}catch(x){throw d(x),x}finally{l(!1)}},[s,g]);return{members:n,total:h,isLoading:f,error:j,fetchMembers:g,updateMemberRole:b,removeMember:S}}class ee{client;constructor(t,a,i,n){this.client=new A.ApiClient({baseUrl:t,timeoutMs:a,retryAttempts:i,getAccessToken:n})}async listInvites(t,a=50,i=0){try{const n=await this.client.get(`/orgs/${t}/invites?limit=${a}&offset=${i}`);return{invites:n.invites.map(o=>({id:o.id,orgId:o.orgId,email:o.email,role:o.role,invitedBy:o.invitedBy,createdAt:o.createdAt,expiresAt:o.expiresAt})),total:n.total}}catch(n){throw A.handleApiError(n,"Failed to list invites")}}async createInvite(t,a){try{return await this.client.post(`/orgs/${t}/invites`,a)}catch(i){throw A.handleApiError(i,"Failed to create invite")}}async cancelInvite(t,a){try{await this.client.delete(`/orgs/${t}/invites/${a}`)}catch(i){throw A.handleApiError(i,"Failed to cancel invite")}}async resendInvite(t,a){try{await this.client.post(`/orgs/${t}/invites/${a}/resend`,{})}catch(i){throw A.handleApiError(i,"Failed to resend invite")}}async acceptInvite(t){try{return await this.client.post("/invites/accept",t)}catch(a){throw A.handleApiError(a,"Failed to accept invite")}}}function se(s){const{config:t,authState:a,_internal:i}=A.useCedrosLogin(),[n,o]=r.useState([]),[h,m]=r.useState(0),[f,l]=r.useState(!1),[j,d]=r.useState(null),y=r.useRef(void 0),p=r.useRef(0),c=r.useMemo(()=>new ee(t.serverUrl,t.requestTimeout,t.retryAttempts,i?.getAccessToken),[t.serverUrl,t.requestTimeout,t.retryAttempts,i]),u=r.useRef(c);u.current=c;const g=r.useCallback(async v=>{if(!s||a!=="authenticated"){o([]),m(0);return}l(!0),d(null);const N=++p.current;try{const{limit:C=50,offset:O=0}=v??{},L=await u.current.listInvites(s,C,O);if(N!==p.current)return;o(L.invites),m(L.total)}catch(C){if(N!==p.current)return;d(C)}finally{N===p.current&&l(!1)}},[s,a]);r.useEffect(()=>{if(a!=="authenticated"){y.current=void 0;return}s!==y.current&&(y.current=s,g())},[s,a,g]);const b=r.useCallback(async(v,N="member")=>{if(!s)throw new Error("No organization selected");l(!0),d(null);try{await u.current.createInvite(s,{email:v,role:N}),await g()}catch(C){throw d(C),C}finally{l(!1)}},[s,g]),S=r.useCallback(async v=>{if(!s)throw new Error("No organization selected");l(!0),d(null);try{await u.current.cancelInvite(s,v),await g()}catch(N){throw d(N),N}finally{l(!1)}},[s,g]),w=r.useCallback(async v=>{if(!s)throw new Error("No organization selected");l(!0),d(null);try{await u.current.resendInvite(s,v)}catch(N){throw d(N),N}finally{l(!1)}},[s]),x=r.useCallback(async v=>{l(!0),d(null);try{return await u.current.acceptInvite({token:v})}catch(N){throw d(N),N}finally{l(!1)}},[]);return{invites:n,total:h,isLoading:f,error:j,fetchInvites:g,createInvite:b,cancelInvite:S,resendInvite:w,acceptInvite:x}}const te={organizations:!1,sso:!1,mfa:!1,mfaRequired:!1,walletSigning:!1,credits:!1,userWithdrawals:!1,cedrosPay:!1};function _(){const{settings:s,isLoading:t,error:a,fetchSettings:i,getValue:n}=P.useSystemSettings(),[o,h]=r.useState(!1);r.useEffect(()=>{o||(i(),h(!0))},[i,o]);const m=r.useCallback(d=>d===void 0?!1:d==="true"||d==="1",[]),f=r.useMemo(()=>Object.keys(s).length===0?te:{organizations:m(n("feature_organizations")),sso:m(n("feature_sso")),mfa:m(n("feature_mfa")),mfaRequired:m(n("security_require_mfa")),walletSigning:m(n("feature_wallet_signing")),credits:m(n("feature_credits")),userWithdrawals:m(n("feature_user_withdrawals")),cedrosPay:m(n("feature_cedros_pay"))},[s,n,m]),l=r.useCallback(async()=>{await i()},[i]),j=r.useCallback(d=>f[d],[f]);return{features:f,isLoading:t,error:a,refetch:l,isEnabled:j}}const re=["users","team","deposits","withdrawals","settings-wallet","settings-auth","settings-messaging","settings-credits","settings-server"],ne=["pay-products","pay-subscriptions","pay-transactions","pay-coupons","pay-refunds","pay-storefront","pay-ai","pay-payment","pay-messaging","pay-settings"],E={users:"Users",team:"Team",deposits:"Deposits",withdrawals:"Withdrawals","settings-wallet":"Wallet Settings","settings-auth":"Auth Settings","settings-messaging":"Messages Settings","settings-credits":"Credits Settings","settings-server":"Server Settings","pay-products":"Products","pay-subscriptions":"Subscriptions","pay-transactions":"Transactions","pay-coupons":"Coupons","pay-refunds":"Refunds","pay-storefront":"Storefront","pay-ai":"Store AI","pay-payment":"Payment Options","pay-messaging":"Store Messages","pay-settings":"Store Server"},R={admin:{users:!0,team:!0,deposits:!0,withdrawals:!0,"settings-wallet":!0,"settings-auth":!0,"settings-messaging":!0,"settings-credits":!0,"settings-server":!0,"pay-products":!0,"pay-subscriptions":!0,"pay-transactions":!0,"pay-coupons":!0,"pay-refunds":!0,"pay-storefront":!0,"pay-ai":!0,"pay-payment":!0,"pay-messaging":!0,"pay-settings":!0},member:{users:!1,team:!0,deposits:!1,withdrawals:!1,"settings-wallet":!1,"settings-auth":!1,"settings-messaging":!1,"settings-credits":!1,"settings-server":!1,"pay-products":!1,"pay-subscriptions":!1,"pay-transactions":!1,"pay-coupons":!1,"pay-refunds":!1,"pay-storefront":!1,"pay-ai":!1,"pay-payment":!1,"pay-messaging":!1,"pay-settings":!1}};function I(){const{config:s,authState:t,_internal:a}=A.useCedrosLogin(),{activeOrg:i,role:n}=F.useOrgs(),[o,h]=r.useState(R),[m,f]=r.useState(!1),[l,j]=r.useState(!1),[d,y]=r.useState(null),p=r.useRef(0),c=r.useMemo(()=>new A.ApiClient({baseUrl:s.serverUrl,timeoutMs:s.requestTimeout,retryAttempts:s.retryAttempts,getAccessToken:a?.getAccessToken}),[s.serverUrl,s.requestTimeout,s.retryAttempts,a]),u=r.useRef(c);u.current=c;const g=r.useCallback(async()=>{if(t!=="authenticated"||!i){h(R);return}f(!0),y(null);const w=++p.current;try{const x=await u.current.get("/admin/dashboard-permissions");if(w!==p.current)return;h(x.permissions)}catch(x){if(w!==p.current)return;if(x instanceof Error&&x.message.includes("404"))h(R);else{const v=x instanceof Error?x.message:"Failed to fetch permissions";y({code:"NETWORK_ERROR",message:v}),h(R)}}finally{w===p.current&&f(!1)}},[t,i]),b=r.useCallback(async w=>{if(t!=="authenticated"||!i)throw new Error("Not authenticated");if(n!=="owner")throw new Error("Only owners can modify dashboard permissions");j(!0),y(null);try{await u.current.request({method:"PUT",path:"/admin/dashboard-permissions",body:w}),h(w)}catch(x){const v=x instanceof Error?x.message:"Failed to update permissions";throw y({code:"NETWORK_ERROR",message:v}),new Error(v)}finally{j(!1)}},[t,i,n]),S=r.useCallback(w=>!i||!n||n==="owner"?!0:o[n]?.[w]??!1,[i,n,o]);return r.useEffect(()=>{i?.id&&g()},[i?.id,g]),{permissions:o,canAccess:S,updatePermissions:b,isLoading:m,isUpdating:l,error:d,fetchPermissions:g}}function T({checked:s,onChange:t,disabled:a,label:i}){return e.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,disabled:a,className:`cedros-toggle cedros-toggle-sm ${s?"cedros-toggle-on":"cedros-toggle-off"} ${a?"cedros-toggle-disabled":""}`,onClick:()=>!a&&t(!s),children:e.jsx("span",{className:"cedros-toggle-track",children:e.jsx("span",{className:"cedros-toggle-thumb"})})})}function ae({userRole:s}){const{permissions:t,updatePermissions:a,isLoading:i,isUpdating:n,error:o}=I(),{features:h,isLoading:m}=_(),f=r.useRef(null),l=r.useRef(null),j=s==="owner",d=h.cedrosPay,y=r.useCallback(c=>{l.current=c,f.current&&clearTimeout(f.current),f.current=setTimeout(()=>{l.current&&(a(l.current).catch(()=>{}),l.current=null)},500)},[a]);r.useEffect(()=>()=>{f.current&&clearTimeout(f.current)},[]);const p=r.useCallback((c,u,g)=>{const b={...t,[c]:{...t[c],[u]:g}};y(b)},[t,y]);return i||m?e.jsx("div",{className:"cedros-dashboard__section",children:e.jsx("div",{className:"cedros-dashboard__loading",children:"Loading permissions..."})}):j?e.jsxs("div",{className:"cedros-dashboard__section cedros-permissions-section",children:[e.jsxs("div",{className:"cedros-permissions-header",children:[e.jsx("p",{className:"cedros-permissions-description",children:"Configure which dashboard sections each role can access. Owners always have full access."}),o&&e.jsx("div",{className:"cedros-permissions-error",children:o.message}),n&&e.jsx("span",{className:"cedros-permissions-saving",children:"Saving..."})]}),e.jsx("div",{className:"cedros-permissions-matrix",children:e.jsx("table",{className:"cedros-permissions-table",children:e.jsxs("tbody",{children:[e.jsxs("tr",{className:"cedros-permissions-group-header",children:[e.jsx("th",{className:"cedros-permissions-section-header",children:"Cedros Login"}),e.jsx("th",{className:"cedros-permissions-role-header",children:"Admin"}),e.jsx("th",{className:"cedros-permissions-role-header",children:"Member"})]}),re.map(c=>e.jsxs("tr",{className:"cedros-permissions-row",children:[e.jsx("td",{className:"cedros-permissions-section-label",children:E[c]}),e.jsx("td",{className:"cedros-permissions-toggle-cell",children:e.jsx(T,{checked:t.admin[c]??!1,onChange:u=>p("admin",c,u),disabled:n,label:`Admin access to ${E[c]}`})}),e.jsx("td",{className:"cedros-permissions-toggle-cell",children:e.jsx(T,{checked:t.member[c]??!1,onChange:u=>p("member",c,u),disabled:n,label:`Member access to ${E[c]}`})})]},c)),d&&e.jsxs(e.Fragment,{children:[e.jsxs("tr",{className:"cedros-permissions-group-header",children:[e.jsx("th",{className:"cedros-permissions-section-header",children:"Cedros Pay"}),e.jsx("th",{className:"cedros-permissions-role-header",children:"Admin"}),e.jsx("th",{className:"cedros-permissions-role-header",children:"Member"})]}),ne.map(c=>e.jsxs("tr",{className:"cedros-permissions-row",children:[e.jsx("td",{className:"cedros-permissions-section-label",children:E[c]}),e.jsx("td",{className:"cedros-permissions-toggle-cell",children:e.jsx(T,{checked:t.admin[c]??!1,onChange:u=>p("admin",c,u),disabled:n,label:`Admin access to ${E[c]}`})}),e.jsx("td",{className:"cedros-permissions-toggle-cell",children:e.jsx(T,{checked:t.member[c]??!1,onChange:u=>p("member",c,u),disabled:n,label:`Member access to ${E[c]}`})})]},c))]})]})})})]}):e.jsx("div",{className:"cedros-dashboard__section",children:e.jsx("div",{className:"cedros-dashboard__empty",children:"Only organization owners can configure dashboard permissions."})})}exports.InviteForm=K;exports.InviteList=J;exports.MemberList=z;exports.PermissionsSection=ae;exports.useDashboardPermissions=I;exports.useInvites=se;exports.useMembers=Z;exports.useServerFeatures=_;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SolanaLoginButton-B04dib6X.js","sources":["../src/hooks/useSolanaAuth.ts","../src/utils/mobileWalletAdapter.ts","../src/utils/walletDetection.ts","../src/components/solana/SolanaLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { validateSolanaPublicKey } from '../utils/validation';\nimport type { AuthResponse, AuthError, ChallengeResponse } from '../types';\n\nexport interface UseSolanaAuthReturn {\n requestChallenge: (publicKey: string) => Promise<ChallengeResponse>;\n signIn: (publicKey: string, signature: string, message: string) => Promise<AuthResponse>;\n isLoading: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\n/**\n * Hook for Solana wallet authentication.\n *\n * @example\n * ```tsx\n * function SolanaLogin() {\n * const { requestChallenge, signIn, isLoading } = useSolanaAuth();\n * const { publicKey, signMessage } = useWallet();\n *\n * const handleLogin = async () => {\n * const challenge = await requestChallenge(publicKey.toBase58());\n * const signature = await signMessage(new TextEncoder().encode(challenge.message));\n * const result = await signIn(\n * publicKey.toBase58(),\n * Buffer.from(signature).toString('base64'),\n * challenge.message\n * );\n * };\n * }\n * ```\n */\nexport function useSolanaAuth(): UseSolanaAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\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 requestChallenge = useCallback(\n async (publicKey: string): Promise<ChallengeResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<ChallengeResponse>(\n '/solana/challenge',\n { publicKey },\n { credentials: 'omit' }\n );\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to start wallet verification. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient]\n );\n\n const signIn = useCallback(\n async (publicKey: string, signature: string, message: string): Promise<AuthResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<AuthResponse>('/solana', {\n publicKey,\n signature,\n message,\n });\n config.callbacks?.onLoginSuccess?.(data.user, 'solana');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Unable to sign in with your wallet. Please try again.');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, config.callbacks, _internal]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n requestChallenge,\n signIn,\n isLoading,\n error,\n clearError,\n };\n}\n","/**\n * Mobile Wallet Adapter (MWA) registration for web.\n *\n * On Android Chrome, MWA lets users authenticate with their installed Solana\n * wallet app (e.g., Phantom, Solflare) via Android Intents — no browser\n * extension needed.\n *\n * Once registered, MWA appears as a wallet option in the wallet adapter's\n * wallet list (alongside browser extension wallets). Users see it as\n * \"Use Installed Wallet\" in the wallet selector.\n *\n * Requires the optional peer dependency: @solana-mobile/wallet-standard-mobile\n *\n * @see https://docs.solanamobile.com/get-started/web/installation\n */\n\nexport interface MobileWalletConfig {\n /** App name shown in the wallet's authorization dialog */\n name?: string;\n /** App URI for identity verification */\n uri?: string;\n /** App icon path/URL shown in the wallet dialog */\n icon?: string;\n /** Solana cluster(s) to support. Default: ['solana:mainnet'] */\n chains?: string[];\n}\n\n/**\n * Register Mobile Wallet Adapter as a wallet-standard wallet.\n *\n * Call this once at your application root (before rendering). After registration,\n * MWA automatically appears as \"Use Installed Wallet\" for users browsing on\n * Android Chrome with a Solana wallet app installed.\n *\n * Must be called in a non-SSR context (browser only). For Next.js, call in a\n * Client Component with `'use client'`.\n *\n * @example\n * ```tsx\n * import { registerMobileWallet, CedrosLoginProvider } from '@cedros/login-react';\n *\n * // Register before provider mounts\n * registerMobileWallet({ name: 'My App', uri: 'https://myapp.com' });\n *\n * function App() {\n * return (\n * <CedrosLoginProvider config={{ serverUrl: '...' }}>\n * <LoginForm />\n * </CedrosLoginProvider>\n * );\n * }\n * ```\n *\n * @returns Promise resolving to true if registration succeeded, false otherwise\n */\nexport async function registerMobileWallet(config?: MobileWalletConfig): Promise<boolean> {\n if (typeof window === 'undefined') {\n return false;\n }\n\n try {\n // Dynamic import() — works in browser ESM (unlike require() which only\n // works in CJS/Node). The package is externalized in Vite library mode,\n // so the consumer must have it installed for this to resolve.\n const mwa = await import('@solana-mobile/wallet-standard-mobile');\n\n const chains = config?.chains ?? ['solana:mainnet'];\n\n mwa.registerMwa({\n appIdentity: {\n name: config?.name,\n uri: config?.uri,\n icon: config?.icon,\n },\n chains: chains as [`${string}:${string}`, ...`${string}:${string}`[]],\n authorizationCache: mwa.createDefaultAuthorizationCache(),\n chainSelector: mwa.createDefaultChainSelector(),\n onWalletNotFound: mwa.createDefaultWalletNotFoundHandler(),\n });\n return true;\n } catch {\n // @solana-mobile/wallet-standard-mobile not installed or import failed\n return false;\n }\n}\n","/**\n * Wallet detection utilities for Solana browser wallets\n */\n\n/**\n * Type for window with potential Solana wallet extensions\n */\nexport interface WindowWithWallets extends Window {\n phantom?: { solana?: unknown };\n solflare?: { solana?: unknown };\n backpack?: { solana?: unknown };\n glow?: { solana?: unknown };\n slope?: { solana?: unknown };\n sollet?: { solana?: unknown };\n coin98?: { solana?: unknown };\n clover?: { solana?: unknown };\n mathWallet?: { solana?: unknown };\n ledger?: { solana?: unknown };\n torus?: { solana?: unknown };\n walletconnect?: { solana?: unknown };\n solana?: unknown;\n}\n\n/**\n * Known Solana wallet provider names to check for on the window object\n */\ntype WalletProviderName = Exclude<keyof WindowWithWallets, keyof Window>;\n\nexport const WALLET_PROVIDERS: WalletProviderName[] = [\n 'phantom',\n 'solflare',\n 'backpack',\n 'glow',\n 'slope',\n 'sollet',\n 'coin98',\n 'clover',\n 'mathWallet',\n 'ledger',\n 'torus',\n 'walletconnect',\n];\n\n/**\n * UI-9 FIX: Validates that a wallet provider object has expected Solana wallet methods.\n * Prevents spoofed wallet objects from being accepted.\n */\nexport function isValidSolanaProvider(provider: unknown): boolean {\n if (!provider || typeof provider !== 'object') return false;\n const wallet = provider as Record<string, unknown>;\n // Check for at least one expected wallet method/property\n // Real wallets have connect, signMessage, signTransaction, etc.\n return (\n typeof wallet.connect === 'function' ||\n typeof wallet.signMessage === 'function' ||\n typeof wallet.signTransaction === 'function' ||\n 'isConnected' in wallet\n );\n}\n\n/**\n * Detect Android Chrome specifically (MWA only works on Chrome, not Brave/Firefox/Opera).\n * Brave's UA includes \"Chrome\" but exposes `navigator.brave` which Chrome does not.\n * @see https://docs.solanamobile.com/get-started/web/apps\n */\nexport function isAndroidChrome(): boolean {\n if (typeof navigator === 'undefined') return false;\n const ua = navigator.userAgent;\n const isBrave = 'brave' in navigator;\n return /Android/i.test(ua) && /Chrome\\/\\d+/.test(ua) && !isBrave;\n}\n\n/**\n * Detects if any Solana wallet extensions are installed in the browser.\n * Checks for common wallet adapters like Phantom, Solflare, Backpack, etc.\n *\n * @returns true if at least one Solana wallet is detected\n *\n * @example\n * ```tsx\n * if (detectSolanaWallets()) {\n * // Show Solana login button\n * }\n * ```\n */\nexport function detectSolanaWallets(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n const win = window as WindowWithWallets;\n\n // Check window object for wallet injections\n // UI-9: Validate wallet has expected methods to reject spoofed providers\n for (const provider of WALLET_PROVIDERS) {\n const walletObj = win[provider];\n if (\n walletObj &&\n typeof walletObj === 'object' &&\n 'solana' in walletObj &&\n isValidSolanaProvider(walletObj.solana)\n ) {\n return true;\n }\n }\n\n // Check for generic Solana provider (e.g., from some mobile wallet browsers)\n // UI-9: Also validate generic provider\n if (isValidSolanaProvider(win.solana)) {\n return true;\n }\n\n // On Android Chrome, MWA connects to native wallet apps (e.g., Phantom, Solflare)\n // via Android intents — no browser extension needed. MWA only works on Chrome,\n // not Brave/Firefox/Opera (per Solana Mobile docs).\n if (isAndroidChrome()) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Returns list of detected Solana wallet names (for debugging/display)\n *\n * @returns Array of detected wallet provider names\n */\nexport function getDetectedWalletNames(): string[] {\n if (typeof window === 'undefined') {\n return [];\n }\n\n const win = window as WindowWithWallets;\n const detected: string[] = [];\n\n // UI-9: Use validation to reject spoofed providers\n for (const provider of WALLET_PROVIDERS) {\n const walletObj = win[provider];\n if (\n walletObj &&\n typeof walletObj === 'object' &&\n 'solana' in walletObj &&\n isValidSolanaProvider(walletObj.solana)\n ) {\n detected.push(provider);\n }\n }\n\n if (isValidSolanaProvider(win.solana) && detected.length === 0) {\n detected.push('solana');\n }\n\n return detected;\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { WalletProvider, useWallet } from '@solana/wallet-adapter-react';\nimport { WalletModalProvider, useWalletModal } from '@solana/wallet-adapter-react-ui';\nimport type { WalletName } from '@solana/wallet-adapter-base';\nimport { useSolanaAuth } from '../../hooks/useSolanaAuth';\nimport { registerMobileWallet } from '../../utils/mobileWalletAdapter';\nimport { isAndroidChrome } from '../../utils/walletDetection';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\n// Auto-register Mobile Wallet Adapter on Android Chrome only.\n// MWA uses Android Intents which only work in Chrome (not Brave/Firefox/Opera).\n// Fire-and-forget: the WalletProvider picks up MWA via wallet-standard events once it resolves.\nif (isAndroidChrome()) {\n void registerMobileWallet();\n}\n\nexport interface SolanaLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /**\n * Hide the button if no Solana wallets are detected.\n * When true (default), button renders nothing if no wallets are installed.\n * When false, button always renders (useful for showing \"install wallet\" prompts).\n * @default true\n */\n hideIfNoWallet?: boolean;\n /** Called when the button's loading state changes (connecting, signing, etc.). */\n onLoadingChange?: (loading: boolean) => void;\n /**\n * Solana wallet adapter context. Pass this from @solana/wallet-adapter-react's useWallet().\n * When provided, the component assumes a WalletProvider exists in the React tree and\n * uses the consumer's wallet context for wallet discovery and connection.\n * When omitted, the component provides its own WalletProvider with wallet-standard discovery.\n */\n walletContext?: {\n publicKey: { toBase58: () => string } | null;\n signMessage: ((message: Uint8Array) => Promise<Uint8Array>) | null;\n connected: boolean;\n connecting: boolean;\n connect: () => Promise<void>;\n wallet: { adapter: { name: string } } | null;\n select: (walletName: string) => void;\n wallets: Array<{\n adapter: {\n name: string;\n icon: string;\n readyState: string;\n };\n }>;\n };\n}\n\n/** Stable empty array to avoid re-renders in self-contained WalletProvider. */\nconst EMPTY_ADAPTERS: [] = [];\n\n/**\n * Solana wallet login button with one-click authentication.\n *\n * Uses the standard wallet adapter modal for wallet selection, which provides\n * real brand icons and discovers all wallet-standard-compliant wallets.\n *\n * When `walletContext` is provided, assumes a WalletProvider exists in the tree.\n * Otherwise, wraps itself with WalletProvider for self-contained operation.\n */\nexport function SolanaLoginButton(props: SolanaLoginButtonProps) {\n if (props.walletContext) {\n // Consumer has their own WalletProvider; just add modal capability\n return (\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n );\n }\n\n // Self-contained: provide wallet-standard discovery + modal\n return (\n <WalletProvider wallets={EMPTY_ADAPTERS} localStorageKey=\"cedros-walletName\">\n <WalletModalProvider>\n <SolanaLoginInner {...props} />\n </WalletModalProvider>\n </WalletProvider>\n );\n}\n\nfunction SolanaLoginInner({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n hideIfNoWallet = true,\n onLoadingChange,\n walletContext,\n}: SolanaLoginButtonProps) {\n const { requestChallenge, signIn, isLoading: isAuthLoading } = useSolanaAuth();\n const adapterWallet = useWallet();\n const { visible: modalVisible, setVisible: setModalVisible } = useWalletModal();\n const [pendingLogin, setPendingLogin] = useState(false);\n const [triggerConnect, setTriggerConnect] = useState(false);\n const isProcessingRef = useRef(false);\n const modalWasOpen = useRef(false);\n const signRejectedRef = useRef(false);\n\n // Use walletContext if provided, otherwise use adapter's useWallet()\n const connected = walletContext?.connected ?? adapterWallet.connected;\n const connecting = walletContext?.connecting ?? adapterWallet.connecting;\n const publicKey = walletContext?.publicKey ?? adapterWallet.publicKey;\n const signMessage = walletContext?.signMessage ?? adapterWallet.signMessage;\n const wallet = walletContext?.wallet ?? adapterWallet.wallet;\n const wallets = walletContext?.wallets ?? adapterWallet.wallets;\n const select = walletContext\n ? walletContext.select\n : (name: string) => adapterWallet.select(name as WalletName);\n const connect = walletContext?.connect ?? adapterWallet.connect;\n\n // Get installed/ready wallets\n const installedWallets = wallets.filter(\n (w) => w.adapter.readyState === 'Installed' || w.adapter.readyState === 'Loadable'\n );\n\n // Execute the sign-in flow (challenge → sign → verify)\n const executeSignIn = useCallback(async () => {\n if (isProcessingRef.current) return;\n if (!publicKey || !signMessage) {\n onError?.(new Error('Wallet not ready'));\n return;\n }\n\n isProcessingRef.current = true;\n try {\n const pubKeyString = publicKey.toBase58();\n\n const challenge = await requestChallenge(pubKeyString);\n\n const messageBytes = new TextEncoder().encode(challenge.message);\n const signatureBytes = await signMessage(messageBytes);\n\n if (!(signatureBytes instanceof Uint8Array) || signatureBytes.length === 0) {\n throw new Error('Wallet returned invalid signature');\n }\n\n let signature: string;\n try {\n signature = btoa(String.fromCharCode(...signatureBytes));\n } catch {\n throw new Error('Failed to encode signature');\n }\n\n await signIn(pubKeyString, signature, challenge.message);\n signRejectedRef.current = false;\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n signRejectedRef.current = true;\n onError?.(error);\n } finally {\n isProcessingRef.current = false;\n setPendingLogin(false);\n }\n }, [publicKey, signMessage, requestChallenge, signIn, onSuccess, onError]);\n\n // Auto-connect when wallet is selected and triggerConnect is set\n useEffect(() => {\n if (triggerConnect && wallet && !connected && !connecting) {\n setTriggerConnect(false);\n connect().catch((err) => {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n setPendingLogin(false);\n });\n }\n }, [triggerConnect, wallet, connected, connecting, connect, onError]);\n\n // Auto-execute sign-in when connected with pending login\n useEffect(() => {\n if (pendingLogin && connected && publicKey && signMessage && !isProcessingRef.current) {\n executeSignIn().catch(() => {\n /* Errors already passed to onError callback inside executeSignIn */\n });\n }\n }, [pendingLogin, connected, publicKey, signMessage, executeSignIn]);\n\n // When modal closes: connect if a wallet was selected, else reset.\n // Wallet is always deselected before opening, so any non-null wallet = user chose one.\n useEffect(() => {\n if (modalVisible) {\n modalWasOpen.current = true;\n } else if (modalWasOpen.current) {\n modalWasOpen.current = false;\n if (pendingLogin && !connected && wallet && !connecting) {\n setTriggerConnect(true);\n } else if (pendingLogin && !connected) {\n setPendingLogin(false);\n }\n }\n }, [modalVisible, pendingLogin, connected, wallet, connecting]);\n\n // Hide button if no wallets detected\n if (hideIfNoWallet && installedWallets.length === 0) {\n return null;\n }\n\n const handleClick = async () => {\n if (disabled || isAuthLoading || connecting) return;\n\n if (connected && publicKey && signMessage && !signRejectedRef.current) {\n // Already connected, previous attempt wasn't rejected — sign immediately\n setPendingLogin(true);\n await executeSignIn();\n } else if (installedWallets.length === 1 && !wallet) {\n // Single installed wallet, nothing remembered — auto-select + connect\n select(installedWallets[0].adapter.name);\n setPendingLogin(true);\n setTriggerConnect(true);\n } else {\n // Show wallet picker — deselect any remembered wallet first so that\n // every selection in the modal registers as \"new\" and dismissing\n // leaves wallet as null (no accidental auto-connect).\n signRejectedRef.current = false;\n if (wallet) {\n adapterWallet.select(null as unknown as WalletName);\n }\n setModalVisible(true);\n setPendingLogin(true);\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 const isLoading = isAuthLoading || connecting || (pendingLogin && !connected);\n\n // Notify parent of loading state changes\n useEffect(() => {\n onLoadingChange?.(isLoading);\n }, [isLoading, onLoadingChange]);\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || isLoading}\n aria-label=\"Continue with Solana\"\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 128 128\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z\" />\n <path d=\"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z\" />\n <path d=\"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z\" />\n </svg>\n )}\n <span>Continue with Solana</span>\n </button>\n );\n}\n"],"names":["useSolanaAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","error","setError","apiClient","useMemo","ApiClient","requestChallenge","useCallback","publicKey","validateSolanaPublicKey","authError","err","handleApiError","signIn","signature","message","data","clearError","registerMobileWallet","mwa","chains","WALLET_PROVIDERS","isValidSolanaProvider","provider","wallet","isAndroidChrome","ua","isBrave","detectSolanaWallets","win","walletObj","EMPTY_ADAPTERS","SolanaLoginButton","props","WalletModalProvider","jsx","SolanaLoginInner","WalletProvider","onSuccess","onError","className","variant","size","disabled","hideIfNoWallet","onLoadingChange","walletContext","isAuthLoading","adapterWallet","useWallet","modalVisible","setModalVisible","useWalletModal","pendingLogin","setPendingLogin","triggerConnect","setTriggerConnect","isProcessingRef","useRef","modalWasOpen","signRejectedRef","connected","connecting","signMessage","wallets","select","name","connect","installedWallets","w","executeSignIn","pubKeyString","challenge","messageBytes","signatureBytes","useEffect","handleClick","sizeClasses","variantClasses","jsxs","LoadingSpinner"],"mappings":"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"}
|