@cedros/login-react 0.0.29 → 0.0.30
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/README.md +2 -0
- package/dist/{AdminDepositList-CyT4VBH8.js → AdminDepositList-BUm_ZcAW.js} +1 -1
- package/dist/{AdminDepositList-CyT4VBH8.js.map → AdminDepositList-BUm_ZcAW.js.map} +1 -1
- package/dist/{AdminDepositList-b2AXtLg0.cjs → AdminDepositList-B_z6x3j5.cjs} +1 -1
- package/dist/{AdminDepositList-b2AXtLg0.cjs.map → AdminDepositList-B_z6x3j5.cjs.map} +1 -1
- package/dist/{AdminWithdrawalHistory-DL9zbu2b.cjs → AdminWithdrawalHistory-B2EY2ZmH.cjs} +1 -1
- package/dist/{AdminWithdrawalHistory-DL9zbu2b.cjs.map → AdminWithdrawalHistory-B2EY2ZmH.cjs.map} +1 -1
- package/dist/{AdminWithdrawalHistory-Cud-yuWy.js → AdminWithdrawalHistory-C76bkbjX.js} +1 -1
- package/dist/{AdminWithdrawalHistory-Cud-yuWy.js.map → AdminWithdrawalHistory-C76bkbjX.js.map} +1 -1
- package/dist/{AuthenticationSettings-D6GvSw3g.cjs → AuthenticationSettings-C-aYDXNH.cjs} +1 -1
- package/dist/{AuthenticationSettings-D6GvSw3g.cjs.map → AuthenticationSettings-C-aYDXNH.cjs.map} +1 -1
- package/dist/AuthenticationSettings-CoTic-d_.cjs +1 -0
- package/dist/AuthenticationSettings-CoTic-d_.cjs.map +1 -0
- package/dist/{AuthenticationSettings-rb4Fksw5.js → AuthenticationSettings-CsPbxwP7.js} +1 -1
- package/dist/{AuthenticationSettings-rb4Fksw5.js.map → AuthenticationSettings-CsPbxwP7.js.map} +1 -1
- package/dist/AuthenticationSettings-DIVk0OP8.js +712 -0
- package/dist/AuthenticationSettings-DIVk0OP8.js.map +1 -0
- package/dist/AutosaveStatus-DGNI4lXn.cjs +1 -0
- package/dist/AutosaveStatus-DGNI4lXn.cjs.map +1 -0
- package/dist/{AutosaveStatus-vkJxtuEw.js → AutosaveStatus-f-jw25Ay.js} +141 -93
- package/dist/AutosaveStatus-f-jw25Ay.js.map +1 -0
- package/dist/{CreditSystemSettings-ChA_lbef.cjs → CreditSystemSettings-BNkvsgsk.cjs} +1 -1
- package/dist/{CreditSystemSettings-ChA_lbef.cjs.map → CreditSystemSettings-BNkvsgsk.cjs.map} +1 -1
- package/dist/{CreditSystemSettings-DsRipb2R.js → CreditSystemSettings-C6ed3yp7.js} +1 -1
- package/dist/{CreditSystemSettings-DsRipb2R.js.map → CreditSystemSettings-C6ed3yp7.js.map} +1 -1
- package/dist/{CreditSystemSettings-r3gnGjiU.cjs → CreditSystemSettings-DM9ep1TF.cjs} +1 -1
- package/dist/{CreditSystemSettings-r3gnGjiU.cjs.map → CreditSystemSettings-DM9ep1TF.cjs.map} +1 -1
- package/dist/{CreditSystemSettings-a31pqSYS.js → CreditSystemSettings-uinhzoha.js} +1 -1
- package/dist/{CreditSystemSettings-a31pqSYS.js.map → CreditSystemSettings-uinhzoha.js.map} +1 -1
- package/dist/{DepositsSection-DD9MKUFt.js → DepositsSection-Bb4ISzvE.js} +1 -1
- package/dist/{DepositsSection-DD9MKUFt.js.map → DepositsSection-Bb4ISzvE.js.map} +1 -1
- package/dist/{DepositsSection-BkKUS4vk.cjs → DepositsSection-uQUdGeVb.cjs} +1 -1
- package/dist/{DepositsSection-BkKUS4vk.cjs.map → DepositsSection-uQUdGeVb.cjs.map} +1 -1
- package/dist/EmailRegisterForm-B_TiJkD6.cjs +1 -0
- package/dist/EmailRegisterForm-B_TiJkD6.cjs.map +1 -0
- package/dist/EmailRegisterForm-CCEuQA-w.js +773 -0
- package/dist/EmailRegisterForm-CCEuQA-w.js.map +1 -0
- package/dist/{EmailSettings-BVJ4vz0Y.js → EmailSettings-BAuQtEfM.js} +1 -1
- package/dist/{EmailSettings-BVJ4vz0Y.js.map → EmailSettings-BAuQtEfM.js.map} +1 -1
- package/dist/{EmailSettings-Dg2SAiHj.cjs → EmailSettings-BC0f1PCI.cjs} +1 -1
- package/dist/{EmailSettings-Dg2SAiHj.cjs.map → EmailSettings-BC0f1PCI.cjs.map} +1 -1
- package/dist/{EmailSettings-CM5l8qqK.cjs → EmailSettings-BF5EiPl9.cjs} +1 -1
- package/dist/{EmailSettings-CM5l8qqK.cjs.map → EmailSettings-BF5EiPl9.cjs.map} +1 -1
- package/dist/{EmailSettings-xtVl4kXD.js → EmailSettings-BKuXy8sc.js} +1 -1
- package/dist/{EmailSettings-xtVl4kXD.js.map → EmailSettings-BKuXy8sc.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-Bmx8x21f.cjs → EmbeddedWalletSettings-BRjt2PAj.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-Bmx8x21f.cjs.map → EmbeddedWalletSettings-BRjt2PAj.cjs.map} +1 -1
- package/dist/{EmbeddedWalletSettings-BuDgqv-K.js → EmbeddedWalletSettings-C27X9He2.js} +1 -1
- package/dist/{EmbeddedWalletSettings-BuDgqv-K.js.map → EmbeddedWalletSettings-C27X9He2.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-BXN9VbNJ.cjs → EmbeddedWalletSettings-CJY39UZN.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-BXN9VbNJ.cjs.map → EmbeddedWalletSettings-CJY39UZN.cjs.map} +1 -1
- package/dist/{EmbeddedWalletSettings-DY5iJhS0.js → EmbeddedWalletSettings-Dmi-EQ7W.js} +1 -1
- package/dist/{EmbeddedWalletSettings-DY5iJhS0.js.map → EmbeddedWalletSettings-Dmi-EQ7W.js.map} +1 -1
- package/dist/GoogleLoginButton-CjBO3Rf1.cjs +1 -0
- package/dist/GoogleLoginButton-CjBO3Rf1.cjs.map +1 -0
- package/dist/{GoogleLoginButton-B6qnNMZp.js → GoogleLoginButton-DEbiQngr.js} +51 -51
- package/dist/GoogleLoginButton-DEbiQngr.js.map +1 -0
- package/dist/LoadingSpinner-6vml-zwr.js.map +1 -1
- package/dist/LoadingSpinner-d6sSxgQN.cjs.map +1 -1
- package/dist/{PermissionsSection-BPbE-hNx.cjs → PermissionsSection-DEMVp7X3.cjs} +1 -1
- package/dist/PermissionsSection-DEMVp7X3.cjs.map +1 -0
- package/dist/{PermissionsSection-CighC1p6.js → PermissionsSection-DNzOL1xW.js} +27 -25
- package/dist/PermissionsSection-DNzOL1xW.js.map +1 -0
- package/dist/{ServerSettings-BAstMKHS.js → ServerSettings-BT9weFPz.js} +1 -1
- package/dist/{ServerSettings-BAstMKHS.js.map → ServerSettings-BT9weFPz.js.map} +1 -1
- package/dist/{ServerSettings-LIIP5TPz.cjs → ServerSettings-CKfiLfXi.cjs} +1 -1
- package/dist/{ServerSettings-LIIP5TPz.cjs.map → ServerSettings-CKfiLfXi.cjs.map} +1 -1
- package/dist/{ServerSettings-9Q091f3o.js → ServerSettings-CZfBdMxG.js} +1 -1
- package/dist/{ServerSettings-9Q091f3o.js.map → ServerSettings-CZfBdMxG.js.map} +1 -1
- package/dist/{ServerSettings-PH7T8JKI.cjs → ServerSettings-rHrVN8O8.cjs} +1 -1
- package/dist/{ServerSettings-PH7T8JKI.cjs.map → ServerSettings-rHrVN8O8.cjs.map} +1 -1
- package/dist/SolanaLoginButton-DAV3r4oB.cjs +1 -0
- package/dist/SolanaLoginButton-DAV3r4oB.cjs.map +1 -0
- package/dist/{mobileWalletAdapter-Cm_AUXhg.js → SolanaLoginButton-DFOoLqoj.js} +75 -74
- package/dist/SolanaLoginButton-DFOoLqoj.js.map +1 -0
- package/dist/{TeamSection-BIECkp7g.js → TeamSection-CoMXyFtz.js} +2 -2
- package/dist/{TeamSection-BIECkp7g.js.map → TeamSection-CoMXyFtz.js.map} +1 -1
- package/dist/{TeamSection-BOH9pv_E.cjs → TeamSection-DopbZClq.cjs} +1 -1
- package/dist/{TeamSection-BOH9pv_E.cjs.map → TeamSection-DopbZClq.cjs.map} +1 -1
- package/dist/{UsersSection-t-zm0jZW.js → UsersSection--PAE1XRh.js} +1 -1
- package/dist/{UsersSection-t-zm0jZW.js.map → UsersSection--PAE1XRh.js.map} +1 -1
- package/dist/{UsersSection-e6q7FHzx.cjs → UsersSection-C7aRNkK2.cjs} +1 -1
- package/dist/{UsersSection-e6q7FHzx.cjs.map → UsersSection-C7aRNkK2.cjs.map} +1 -1
- package/dist/{WebhookSettings-D0F8ESlB.js → WebhookSettings-Bgld6D_T.js} +1 -1
- package/dist/{WebhookSettings-D0F8ESlB.js.map → WebhookSettings-Bgld6D_T.js.map} +1 -1
- package/dist/{WebhookSettings-2p9abGm5.cjs → WebhookSettings-DXjnq-c7.cjs} +1 -1
- package/dist/{WebhookSettings-2p9abGm5.cjs.map → WebhookSettings-DXjnq-c7.cjs.map} +1 -1
- package/dist/{WebhookSettings-CdFM7_V-.cjs → WebhookSettings-DnLk97Mr.cjs} +1 -1
- package/dist/{WebhookSettings-CdFM7_V-.cjs.map → WebhookSettings-DnLk97Mr.cjs.map} +1 -1
- package/dist/{WebhookSettings-CXMBju7N.js → WebhookSettings-ufiGTmbG.js} +1 -1
- package/dist/{WebhookSettings-CXMBju7N.js.map → WebhookSettings-ufiGTmbG.js.map} +1 -1
- package/dist/{WithdrawalsSection-yRDTVFsb.js → WithdrawalsSection-BN-FjTEV.js} +1 -1
- package/dist/{WithdrawalsSection-yRDTVFsb.js.map → WithdrawalsSection-BN-FjTEV.js.map} +1 -1
- package/dist/{WithdrawalsSection-sljIyeaz.cjs → WithdrawalsSection-BhuCwFat.cjs} +1 -1
- package/dist/{WithdrawalsSection-sljIyeaz.cjs.map → WithdrawalsSection-BhuCwFat.cjs.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 +17 -1
- package/dist/email-only.js +3 -3
- package/dist/google-only.cjs +1 -1
- package/dist/google-only.d.ts +16 -0
- package/dist/google-only.js +3 -3
- package/dist/index.cjs +12 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +246 -10
- package/dist/index.js +3384 -2508
- package/dist/index.js.map +1 -1
- package/dist/login-react.css +1 -1
- package/dist/{plugin-DbkijwEV.js → plugin-BbExid4E.js} +1 -1
- package/dist/{plugin-DbkijwEV.js.map → plugin-BbExid4E.js.map} +1 -1
- package/dist/{plugin-PU2vAozn.cjs → plugin-Xca67fp7.cjs} +1 -1
- package/dist/{plugin-PU2vAozn.cjs.map → plugin-Xca67fp7.cjs.map} +1 -1
- package/dist/{shamir-CiBczzDN.cjs → shamir-DBpHm7WN.cjs} +1 -1
- package/dist/{shamir-CiBczzDN.cjs.map → shamir-DBpHm7WN.cjs.map} +1 -1
- package/dist/{shamir-OAB2zD9Y.js → shamir-R8ddesFt.js} +1 -1
- package/dist/{shamir-OAB2zD9Y.js.map → shamir-R8ddesFt.js.map} +1 -1
- package/dist/{silentWalletEnroll-FqXS7Rvh.js → silentWalletEnroll-Dp1GTeNr.js} +3 -3
- package/dist/{silentWalletEnroll-FqXS7Rvh.js.map → silentWalletEnroll-Dp1GTeNr.js.map} +1 -1
- package/dist/{silentWalletEnroll-wnkcB9HP.cjs → silentWalletEnroll-HPvsbd2J.cjs} +1 -1
- package/dist/{silentWalletEnroll-wnkcB9HP.cjs.map → silentWalletEnroll-HPvsbd2J.cjs.map} +1 -1
- package/dist/solana-only.cjs +1 -1
- package/dist/solana-only.d.ts +16 -0
- package/dist/solana-only.js +3 -3
- package/dist/{useAdminDeposits-BTSyeAfg.js → useAdminDeposits-C76B2Q_8.js} +1 -1
- package/dist/{useAdminDeposits-BTSyeAfg.js.map → useAdminDeposits-C76B2Q_8.js.map} +1 -1
- package/dist/{useAdminDeposits-BkkCwHWp.cjs → useAdminDeposits-CpLd68oP.cjs} +1 -1
- package/dist/{useAdminDeposits-BkkCwHWp.cjs.map → useAdminDeposits-CpLd68oP.cjs.map} +1 -1
- package/dist/{useAuth-m5Hf89v8.js → useAuth-CVLv2oKA.js} +547 -545
- package/dist/useAuth-CVLv2oKA.js.map +1 -0
- package/dist/useAuth-XZaciuLg.cjs +1 -0
- package/dist/useAuth-XZaciuLg.cjs.map +1 -0
- package/dist/useCedrosLogin-CFfID-0i.js +228 -0
- package/dist/useCedrosLogin-CFfID-0i.js.map +1 -0
- package/dist/useCedrosLogin-DtJorrE7.cjs +1 -0
- package/dist/useCedrosLogin-DtJorrE7.cjs.map +1 -0
- package/dist/{useOrgs-C3pzMA9h.js → useOrgs-C90KT9KP.js} +1 -1
- package/dist/{useOrgs-C3pzMA9h.js.map → useOrgs-C90KT9KP.js.map} +1 -1
- package/dist/{useOrgs-DDVRCaVi.cjs → useOrgs-CNqfn-fk.cjs} +1 -1
- package/dist/{useOrgs-DDVRCaVi.cjs.map → useOrgs-CNqfn-fk.cjs.map} +1 -1
- package/dist/{useSystemSettings-DRrreszl.cjs → useSystemSettings-B2jY51ob.cjs} +1 -1
- package/dist/{useSystemSettings-DRrreszl.cjs.map → useSystemSettings-B2jY51ob.cjs.map} +1 -1
- package/dist/{useSystemSettings-DBlAMjFi.js → useSystemSettings-rgskaDqP.js} +1 -1
- package/dist/{useSystemSettings-DBlAMjFi.js.map → useSystemSettings-rgskaDqP.js.map} +1 -1
- package/dist/{useUsersStatsSummary-NjEFvWuz.js → useUsersStatsSummary-5DJwzntC.js} +2 -2
- package/dist/{useUsersStatsSummary-NjEFvWuz.js.map → useUsersStatsSummary-5DJwzntC.js.map} +1 -1
- package/dist/{useUsersStatsSummary-8qY7iP4G.cjs → useUsersStatsSummary-DgKaUIfs.cjs} +1 -1
- package/dist/{useUsersStatsSummary-8qY7iP4G.cjs.map → useUsersStatsSummary-DgKaUIfs.cjs.map} +1 -1
- package/package.json +1 -1
- package/dist/AuthenticationSettings-C9f5MKgj.cjs +0 -1
- package/dist/AuthenticationSettings-C9f5MKgj.cjs.map +0 -1
- package/dist/AuthenticationSettings-DC64o_J6.js +0 -525
- package/dist/AuthenticationSettings-DC64o_J6.js.map +0 -1
- package/dist/AutosaveStatus-BFj5GIab.cjs +0 -1
- package/dist/AutosaveStatus-BFj5GIab.cjs.map +0 -1
- package/dist/AutosaveStatus-vkJxtuEw.js.map +0 -1
- package/dist/EmailRegisterForm-B1DB-bqe.cjs +0 -1
- package/dist/EmailRegisterForm-B1DB-bqe.cjs.map +0 -1
- package/dist/EmailRegisterForm-BAX_uBIt.js +0 -927
- package/dist/EmailRegisterForm-BAX_uBIt.js.map +0 -1
- package/dist/GoogleLoginButton-B6qnNMZp.js.map +0 -1
- package/dist/GoogleLoginButton-D7CoMXLq.cjs +0 -1
- package/dist/GoogleLoginButton-D7CoMXLq.cjs.map +0 -1
- package/dist/PermissionsSection-BPbE-hNx.cjs.map +0 -1
- package/dist/PermissionsSection-CighC1p6.js.map +0 -1
- package/dist/mobileWalletAdapter-B6ELaZp1.cjs +0 -1
- package/dist/mobileWalletAdapter-B6ELaZp1.cjs.map +0 -1
- package/dist/mobileWalletAdapter-Cm_AUXhg.js.map +0 -1
- package/dist/useAuth-X6Ds6WW4.cjs +0 -1
- package/dist/useAuth-X6Ds6WW4.cjs.map +0 -1
- package/dist/useAuth-m5Hf89v8.js.map +0 -1
- package/dist/useCedrosLogin-C9MrcZvh.cjs +0 -1
- package/dist/useCedrosLogin-C9MrcZvh.cjs.map +0 -1
- package/dist/useCedrosLogin-_94MmGGq.js +0 -216
- package/dist/useCedrosLogin-_94MmGGq.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EmailRegisterForm-BAX_uBIt.js","sources":["../src/hooks/useRateLimiter.ts","../src/hooks/useEmailAuth.ts","../src/hooks/useInstantLink.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 const walletEnrollmentEnabled = config.features?.walletEnrollment !== false;\n const serverUrl = config.serverUrl;\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, 'Login failed');\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 // Auto-enroll embedded wallet in background (don't block registration)\n // Uses password for Share A encryption. Recovery phrase can be retrieved later.\n // UI-09: In cookie mode the accessToken is absent; add a short delay to allow\n // the session cookie to be written before the enroll request is sent.\n if (walletEnrollmentEnabled) {\n const accessToken = data.tokens?.accessToken ?? '';\n const isCookieMode = !accessToken;\n const enrollDelay = isCookieMode ? 200 : 0;\n void new Promise<void>((resolve) => setTimeout(resolve, enrollDelay))\n .then(() => import('../utils/silentWalletEnroll'))\n .then(({ silentWalletEnroll }) =>\n silentWalletEnroll({\n password,\n serverUrl,\n accessToken,\n })\n )\n .then((result) => {\n if (!result.success) {\n // Log auto-enrollment failures for debugging\n console.warn('[useEmailAuth] Wallet auto-enrollment failed:', result.error);\n }\n })\n .catch((err) => {\n const message = err instanceof Error ? err.message : 'Unknown error';\n // Log enrollment errors for debugging\n console.warn('[useEmailAuth] Wallet auto-enrollment unavailable:', message);\n });\n }\n\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Registration failed');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [\n apiClient,\n callbacks,\n _internal,\n checkLimit,\n resetRateLimit,\n serverUrl,\n walletEnrollmentEnabled,\n ]\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, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { validateEmail } from '../utils/validation';\nimport { useRateLimiter } from './useRateLimiter';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthError, AuthResponse, MfaRequiredResponse } from '../types';\n\nfunction isMfaRequiredResponse(data: unknown): data is MfaRequiredResponse {\n return (\n typeof data === 'object' &&\n data !== null &&\n 'mfaRequired' in data &&\n (data as { mfaRequired?: unknown }).mfaRequired === true\n );\n}\n\nexport interface UseInstantLinkReturn {\n /** Send an instant link email to the given address */\n sendInstantLink: (email: string) => Promise<void>;\n /** Verify an instant link token and sign in */\n verifyInstantLink: (token: string) => Promise<AuthResponse | MfaRequiredResponse>;\n /** Whether a request is in progress */\n isLoading: boolean;\n /** Whether the instant link was sent successfully */\n isSuccess: boolean;\n /** Error from the last request */\n error: AuthError | null;\n /** Clear the error state */\n clearError: () => void;\n /** Reset to initial state */\n reset: () => void;\n /** Number of remaining attempts before rate limiting */\n remainingAttempts: number;\n}\n\n/**\n * Hook for instant link (passwordless) authentication.\n *\n * Sends an instant link email that allows the user to sign in\n * without entering their password.\n *\n * @example\n * ```tsx\n * function InstantLinkForm() {\n * const { sendInstantLink, isLoading, isSuccess, error } = useInstantLink();\n *\n * const handleSubmit = async (e) => {\n * e.preventDefault();\n * await sendInstantLink(email);\n * };\n *\n * if (isSuccess) {\n * return <p>Check your email for the sign-in link</p>;\n * }\n * }\n * ```\n */\nexport function useInstantLink(): UseInstantLinkReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isSuccess, setIsSuccess] = 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 // Rate limiter for instant link attempts (3 attempts per 5 minutes)\n const { checkLimit, getRemainingAttempts } = useRateLimiter({\n maxAttempts: 3,\n windowMs: 300000,\n });\n\n const sendInstantLink = useCallback(\n async (email: string): Promise<void> => {\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 // Check rate limit before API call\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 setIsSuccess(false);\n\n try {\n await apiClient.post('/instant-link', { email });\n setIsSuccess(true);\n } catch (err) {\n const authError = handleApiError(err, 'Failed to send sign-in link');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, checkLimit]\n );\n\n const verifyInstantLink = useCallback(\n async (token: string): Promise<AuthResponse | MfaRequiredResponse> => {\n if (!token || token.trim().length === 0) {\n const validationError: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Invalid or missing sign-in link token',\n };\n setError(validationError);\n throw validationError;\n }\n\n setIsLoading(true);\n setError(null);\n setIsSuccess(false);\n\n try {\n const data = await apiClient.post<AuthResponse | MfaRequiredResponse>(\n '/instant-link/verify',\n {\n token,\n }\n );\n\n if (isMfaRequiredResponse(data)) {\n return data;\n }\n\n // Successful login\n config.callbacks?.onLoginSuccess?.(data.user, 'email');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Failed to verify sign-in link');\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 const reset = useCallback(() => {\n setError(null);\n setIsSuccess(false);\n setIsLoading(false);\n }, []);\n\n return {\n sendInstantLink,\n verifyInstantLink,\n isLoading,\n isSuccess,\n error,\n clearError,\n reset,\n remainingAttempts: getRemainingAttempts(),\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, 'Invalid verification code');\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 { useCedrosLogin } from '../../context/useCedrosLogin';\nimport { useEmailAuth } from '../../hooks/useEmailAuth';\nimport { useInstantLink } from '../../hooks/useInstantLink';\nimport { PasswordInput } from './PasswordInput';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\nimport { TotpVerify } from '../totp/TotpVerify';\n// COMP-01: Email validation now handled by useInstantLink hook internally\n\nexport interface EmailLoginFormProps {\n onSuccess?: () => void;\n onSwitchToRegister?: () => void;\n /** Called when user clicks \"Forgot password?\" (only in 'reset' mode) */\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 { config } = useCedrosLogin();\n const { login, isLoading, error, clearError } = useEmailAuth();\n const {\n sendInstantLink,\n isLoading: isInstantLinkLoading,\n isSuccess: isInstantLinkSuccess,\n error: instantLinkError,\n clearError: clearInstantLinkError,\n reset: resetInstantLink,\n } = useInstantLink();\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 forgotPasswordMode = config.forms?.forgotPassword?.mode\n ?? (config.features?.instantLink ? 'instantLink' : 'reset');\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 const handleForgotPassword = async () => {\n if (forgotPasswordMode === 'instantLink') {\n // COMP-01: In instant link mode, call sendInstantLink directly.\n // The hook validates email and sets error state if invalid.\n try {\n await sendInstantLink(email);\n } catch {\n // Error handled by hook's error state\n }\n } else {\n // In reset mode, call the callback\n onForgotPassword?.();\n }\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 // Show instant link success state\n if (isInstantLinkSuccess) {\n return (\n <div className={`cedros-instant-link-success ${className}`}>\n <svg\n className=\"cedros-success-icon\"\n width=\"48\"\n height=\"48\"\n viewBox=\"0 0 48 48\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <circle cx=\"24\" cy=\"24\" r=\"22\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <path\n d=\"M14 24l7 7 13-13\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <h3 className=\"cedros-success-title\">Check your email</h3>\n <p className=\"cedros-success-message\">\n We sent a sign-in link to <strong>{email}</strong>. Click the link to sign in.\n </p>\n <button\n type=\"button\"\n className=\"cedros-button cedros-button-md cedros-button-outline\"\n onClick={resetInstantLink}\n >\n Back to login\n </button>\n </div>\n );\n }\n\n const combinedError = error || instantLinkError;\n const combinedClearError = () => {\n clearError();\n clearInstantLinkError();\n };\n const isAnyLoading = isLoading || isInstantLinkLoading;\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={isAnyLoading}\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={isAnyLoading}\n labelAction={\n onForgotPassword || forgotPasswordMode === 'instantLink' ? (\n <button\n type=\"button\"\n className=\"cedros-link cedros-link-sm\"\n onClick={handleForgotPassword}\n disabled={isInstantLinkLoading}\n >\n {isInstantLinkLoading ? 'Sending...' : 'Forgot your password?'}\n </button>\n ) : undefined\n }\n />\n </div>\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={isAnyLoading || !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\" 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 <label htmlFor=\"confirm-password\" className=\"cedros-label\">\n Confirm Password\n </label>\n <input\n id=\"confirm-password\"\n type=\"password\"\n className=\"cedros-input\"\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n placeholder=\"Confirm your password\"\n required\n aria-required=\"true\"\n autoComplete=\"new-password\"\n disabled={isLoading}\n aria-invalid={confirmPassword && !passwordsMatch ? 'true' : undefined}\n aria-describedby={\n confirmPassword && !passwordsMatch ? 'confirm-password-error' : undefined\n }\n />\n {confirmPassword && !passwordsMatch && (\n <p id=\"confirm-password-error\" className=\"cedros-input-error\" role=\"alert\">\n Passwords do not match\n </p>\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\" 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","walletEnrollmentEnabled","serverUrl","login","email","password","validateEmail","validationError","err","rateLimitError","data","authResponse","authError","handleApiError","register","name","accessToken","enrollDelay","resolve","silentWalletEnroll","result","message","clearError","useInstantLink","isSuccess","setIsSuccess","sendInstantLink","verifyInstantLink","token","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","isInstantLinkLoading","isInstantLinkSuccess","instantLinkError","clearInstantLinkError","resetInstantLink","setEmail","setPassword","setMfaToken","mfaEmail","setMfaEmail","forgotPasswordMode","handleSubmit","handleTotpSuccess","handleTotpBack","handleForgotPassword","combinedError","combinedClearError","isAnyLoading","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"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-B6qnNMZp.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 /** ID token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkIdToken: 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 [pendingLinkIdToken, setPendingLinkIdToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\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 credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\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, 'Google sign-in failed');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkIdToken(response.credential);\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 // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Failed to load Google Sign-In',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\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 not initialized',\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 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 // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In popup was blocked. Please allow popups or try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkIdToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkIdToken,\n clearPendingLink,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\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","pendingLinkIdToken","setPendingLinkIdToken","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","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 u=require("react/jsx-runtime"),l=require("react"),I=require("./useCedrosLogin-C9MrcZvh.cjs"),A=require("./LoadingSpinner-d6sSxgQN.cjs"),L={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((e,d)=>{this.callbacks.push({resolve:e,reject:d})}):(this.loading=!0,new Promise((e,d)=>{this.callbacks.push({resolve:e,reject:d});const g=document.getElementById("google-gsi-script");if(g){window.google?.accounts?.id?(this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]):g.addEventListener("load",()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]});return}const o=document.createElement("script");o.src="https://accounts.google.com/gsi/client",o.async=!0,o.defer=!0,o.id="google-gsi-script",o.onload=()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]},o.onerror=()=>{this.loading=!1,o.remove();const t=new Error("Failed to load Google Sign-In script");this.callbacks.forEach(h=>h.reject(t)),this.callbacks=[]},document.head.appendChild(o)}))},_reset(){this.loading=!1,this.loaded=!1,this.error=null,this.callbacks=[]}};function k(){const{config:e,_internal:d}=I.useCedrosLogin(),[g,o]=l.useState(!1),[t,h]=l.useState(!1),[E,n]=l.useState(null),[b,p]=l.useState(null),c=l.useRef(null),m=l.useRef(e),f=l.useMemo(()=>new I.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]);l.useEffect(()=>{m.current=e},[e]);const R=l.useCallback(async s=>{const r=c.current;if(r)try{const a=await f.post("/google",{idToken:s.credential});m.current.callbacks?.onLoginSuccess?.(a.user,"google"),d?.handleLoginSuccess(a.user,a.tokens),o(!1),r.resolve(a)}catch(a){const i=I.handleApiError(a,"Google sign-in failed");i.code==="ACCOUNT_LINK_REQUIRED"&&p(s.credential),n(i),o(!1),r.reject(i)}finally{c.current=null}},[f,d]);l.useEffect(()=>{if(!e.googleClientId)return;let s=!0;const r=()=>{s&&(window.google?.accounts?.id?.initialize({client_id:e.googleClientId,callback:R,auto_select:!1,cancel_on_tap_outside:!0}),s&&h(!0))};return L.load().then(()=>{s&&r()}).catch(()=>{s&&n({code:"SERVER_ERROR",message:"Failed to load Google Sign-In"})}),()=>{s=!1}},[e.googleClientId,R]);const w=l.useCallback(async()=>{if(!e.googleClientId){const s={code:"VALIDATION_ERROR",message:"Google Client ID not configured"};throw n(s),s}if(!t){const s={code:"VALIDATION_ERROR",message:"Google Sign-In not initialized"};throw n(s),s}if(c.current){const s={code:"VALIDATION_ERROR",message:"Google Sign-In already in progress"};throw n(s),s}return o(!0),n(null),new Promise((s,r)=>{c.current={resolve:s,reject:r},window.google?.accounts?.id?.prompt(a=>{if(a.isNotDisplayed()){const i={code:"SERVER_ERROR",message:"Google Sign-In popup was blocked. Please allow popups or try again."};n(i),o(!1),c.current=null,r(i)}else if(a.isSkippedMoment()){const i={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(i),o(!1),c.current=null,r(i)}else if(a.isDismissedMoment()){const i={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(i),o(!1),c.current=null,r(i)}})})},[e.googleClientId,t]),S=l.useCallback(()=>n(null),[]),C=l.useCallback(()=>p(null),[]);return{signIn:w,isLoading:g,isInitialized:t,error:E,clearError:S,pendingLinkIdToken:b,clearPendingLink:C}}function y({onSuccess:e,onError:d,className:g="",variant:o="default",size:t="md",disabled:h=!1}){const{signIn:E,isLoading:n,isInitialized:b}=k(),p=async()=>{try{await E(),e?.()}catch(f){const R=f instanceof Error?f:new Error(String(f));d?.(R)}},c={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},m={default:"cedros-button-social",outline:"cedros-button-social-outline"};return u.jsxs("button",{type:"button",className:`cedros-button ${m[o]} ${c[t]} ${g}`,onClick:p,disabled:h||!b||n,"aria-label":"Sign in with Google",children:[n?u.jsx(A.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=y;exports.useGoogleAuth=k;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleLoginButton-D7CoMXLq.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 /** ID token saved when ACCOUNT_LINK_REQUIRED is returned. Pass to POST /auth/link-oauth with the user's password. */\n pendingLinkIdToken: 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 [pendingLinkIdToken, setPendingLinkIdToken] = useState<string | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\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 credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\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, 'Google sign-in failed');\n if (authError.code === 'ACCOUNT_LINK_REQUIRED') {\n setPendingLinkIdToken(response.credential);\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 // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Failed to load Google Sign-In',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\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 not initialized',\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 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 // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In popup was blocked. Please allow popups or try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n const clearPendingLink = useCallback(() => setPendingLinkIdToken(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n pendingLinkIdToken,\n clearPendingLink,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\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","pendingLinkIdToken","setPendingLinkIdToken","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","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":"PermissionsSection-BPbE-hNx.cjs","sources":["../src/components/members/MemberList.tsx","../src/components/invites/InviteForm.tsx","../src/components/invites/InviteList.tsx","../src/utils/memberApi.ts","../src/hooks/useMembers.ts","../src/utils/inviteApi.ts","../src/hooks/useInvites.ts","../src/hooks/useServerFeatures.ts","../src/types/org.ts","../src/hooks/useDashboardPermissions.ts","../src/components/admin/PermissionsSection.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport type { Member, OrgRole, DisplayError } from '../../types';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\nimport { sanitizeImageUrl } from '../../utils/sanitization';\n\ntype SortField = 'name' | 'role' | 'joinedAt';\ntype SortOrder = 'asc' | 'desc';\n\nexport interface MemberListProps {\n /** List of members to display */\n members: Member[];\n /** Current user's ID (to prevent self-actions) */\n currentUserId?: string;\n /** Loading state */\n isLoading?: boolean;\n /** Error message */\n error?: DisplayError;\n /** Whether the current user can manage members */\n canManage?: boolean;\n /** Whether the current user can change roles */\n canChangeRoles?: boolean;\n /** Callback when role is updated */\n onUpdateRole?: (userId: string, role: OrgRole) => Promise<void>;\n /** Callback when member is removed */\n onRemove?: (userId: string) => Promise<void>;\n /** Additional CSS class */\n className?: string;\n}\n\nconst ROLE_OPTIONS: OrgRole[] = ['owner', 'admin', 'member'];\n\n/**\n * Display and manage organization members.\n *\n * @example\n * ```tsx\n * function TeamMembers() {\n * const { activeOrg, hasPermission } = useOrgs();\n * const { members, isLoading, error, updateMemberRole, removeMember } = useMembers(activeOrg?.id);\n * const { user } = useAuth();\n *\n * return (\n * <MemberList\n * members={members}\n * currentUserId={user?.id}\n * isLoading={isLoading}\n * error={error?.message}\n * canManage={hasPermission('member:remove')}\n * canChangeRoles={hasPermission('member:role_change')}\n * onUpdateRole={updateMemberRole}\n * onRemove={removeMember}\n * />\n * );\n * }\n * ```\n */\nexport function MemberList({\n members,\n currentUserId,\n isLoading = false,\n error,\n canManage = false,\n canChangeRoles = false,\n onUpdateRole,\n onRemove,\n className = '',\n}: MemberListProps) {\n const [sortField, setSortField] = useState<SortField>('name');\n const [sortOrder, setSortOrder] = useState<SortOrder>('asc');\n\n const toggleSort = (field: SortField) => {\n if (sortField === field) {\n setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');\n } else {\n setSortField(field);\n setSortOrder('asc');\n }\n };\n\n const sortedMembers = useMemo(() => {\n const roleOrder: Record<OrgRole, number> = { owner: 0, admin: 1, member: 2 };\n return [...members].sort((a, b) => {\n let aVal: string | number;\n let bVal: string | number;\n\n switch (sortField) {\n case 'name':\n aVal = (a.user.name || a.user.email || '').toLowerCase();\n bVal = (b.user.name || b.user.email || '').toLowerCase();\n break;\n case 'role':\n aVal = roleOrder[a.role] ?? 99;\n bVal = roleOrder[b.role] ?? 99;\n break;\n case 'joinedAt':\n aVal = new Date(a.joinedAt).getTime();\n bVal = new Date(b.joinedAt).getTime();\n break;\n default:\n return 0;\n }\n\n if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;\n if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;\n return 0;\n });\n }, [members, sortField, sortOrder]);\n\n if (isLoading && members.length === 0) {\n return (\n <div className={`cedros-member-list cedros-member-list-loading ${className}`}>\n <LoadingSpinner />\n <span>Loading members...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className={`cedros-member-list ${className}`}>\n <ErrorMessage error={error} />\n </div>\n );\n }\n\n if (members.length === 0) {\n return (\n <div className={`cedros-member-list cedros-member-list-empty ${className}`}>\n <p>No members found.</p>\n </div>\n );\n }\n\n return (\n <div className={`cedros-member-list ${className}`}>\n <table className=\"cedros-member-table\">\n <thead>\n <tr>\n <th>\n <button\n type=\"button\"\n className={`cedros-admin-sort-button ${sortField === 'name' ? 'cedros-admin-sort-active' : ''}`}\n onClick={() => toggleSort('name')}\n >\n Member{' '}\n <span className=\"cedros-admin-sort-icon\">\n {sortField === 'name' ? (sortOrder === 'asc' ? '↑' : '↓') : '↕'}\n </span>\n </button>\n </th>\n <th>\n <button\n type=\"button\"\n className={`cedros-admin-sort-button ${sortField === 'role' ? 'cedros-admin-sort-active' : ''}`}\n onClick={() => toggleSort('role')}\n >\n Role{' '}\n <span className=\"cedros-admin-sort-icon\">\n {sortField === 'role' ? (sortOrder === 'asc' ? '↑' : '↓') : '↕'}\n </span>\n </button>\n </th>\n <th>\n <button\n type=\"button\"\n className={`cedros-admin-sort-button ${sortField === 'joinedAt' ? 'cedros-admin-sort-active' : ''}`}\n onClick={() => toggleSort('joinedAt')}\n >\n Joined{' '}\n <span className=\"cedros-admin-sort-icon\">\n {sortField === 'joinedAt' ? (sortOrder === 'asc' ? '↑' : '↓') : '↕'}\n </span>\n </button>\n </th>\n {(canManage || canChangeRoles) && <th>Actions</th>}\n </tr>\n </thead>\n <tbody>\n {sortedMembers.map((member) => (\n <MemberRow\n key={member.id}\n member={member}\n isCurrentUser={member.userId === currentUserId}\n canManage={canManage}\n canChangeRoles={canChangeRoles}\n onUpdateRole={onUpdateRole}\n onRemove={onRemove}\n />\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n// Member row component\ninterface MemberRowProps {\n member: Member;\n isCurrentUser: boolean;\n canManage: boolean;\n canChangeRoles: boolean;\n onUpdateRole?: (userId: string, role: OrgRole) => Promise<void>;\n onRemove?: (userId: string) => Promise<void>;\n}\n\nfunction MemberRow({\n member,\n isCurrentUser,\n canManage,\n canChangeRoles,\n onUpdateRole,\n onRemove,\n}: MemberRowProps) {\n const [isUpdating, setIsUpdating] = useState(false);\n const [selectedRole, setSelectedRole] = useState<OrgRole>(member.role);\n\n const handleRoleChange = useCallback(\n async (newRole: OrgRole) => {\n if (!onUpdateRole || newRole === member.role) return;\n\n setIsUpdating(true);\n try {\n await onUpdateRole(member.userId, newRole);\n setSelectedRole(newRole);\n } catch {\n // Revert on error\n setSelectedRole(member.role);\n } finally {\n setIsUpdating(false);\n }\n },\n [member.userId, member.role, onUpdateRole]\n );\n\n const handleRemove = useCallback(async () => {\n if (!onRemove) return;\n\n const confirmed = window.confirm(\n `Are you sure you want to remove ${member.user.name || member.user.email} from this organization?`\n );\n if (!confirmed) return;\n\n setIsUpdating(true);\n try {\n await onRemove(member.userId);\n } finally {\n setIsUpdating(false);\n }\n }, [member.userId, member.user.name, member.user.email, onRemove]);\n\n const isOwner = member.role === 'owner';\n const canModify = !isCurrentUser && !isOwner;\n\n return (\n <tr className={`cedros-member-row ${isCurrentUser ? 'cedros-member-row-current' : ''}`}>\n <td className=\"cedros-member-info\">\n <MemberAvatar user={member.user} />\n <div className=\"cedros-member-details\">\n <span className=\"cedros-member-name\">\n {member.user.name || 'Unknown'}\n {isCurrentUser && <span className=\"cedros-member-you\">(you)</span>}\n </span>\n <span className=\"cedros-member-email\">{member.user.email}</span>\n </div>\n </td>\n <td className=\"cedros-member-role\">\n {canChangeRoles && canModify && onUpdateRole ? (\n <select\n value={selectedRole}\n onChange={(e) => handleRoleChange(e.target.value as OrgRole)}\n disabled={isUpdating}\n className=\"cedros-role-select\"\n >\n {ROLE_OPTIONS.map((role) => (\n <option key={role} value={role}>\n {role.charAt(0).toUpperCase() + role.slice(1)}\n </option>\n ))}\n </select>\n ) : (\n <span className={`cedros-role-badge cedros-role-badge-${member.role}`}>\n {member.role.charAt(0).toUpperCase() + member.role.slice(1)}\n </span>\n )}\n </td>\n <td className=\"cedros-member-joined\">{formatDate(member.joinedAt)}</td>\n {(canManage || canChangeRoles) && (\n <td className=\"cedros-member-actions\">\n {canManage && canModify && onRemove && (\n <button\n type=\"button\"\n className=\"cedros-button cedros-button-danger cedros-button-sm\"\n onClick={handleRemove}\n disabled={isUpdating}\n aria-label={`Remove ${member.user.name || member.user.email}`}\n >\n {isUpdating ? <LoadingSpinner size=\"sm\" /> : 'Remove'}\n </button>\n )}\n </td>\n )}\n </tr>\n );\n}\n\n// Helper components\nfunction MemberAvatar({ user }: { user: Member['user'] }) {\n // COMP-06: Cache sanitized URL and verify before use (blocks dangerous protocols)\n const sanitizedPicture = sanitizeImageUrl(user.picture);\n if (sanitizedPicture) {\n return (\n <img\n src={sanitizedPicture}\n alt={user.name || user.email || 'Member'}\n className=\"cedros-member-avatar\"\n referrerPolicy=\"no-referrer\"\n />\n );\n }\n\n const initial = (user.name?.[0] || user.email?.[0] || '?').toUpperCase();\n return <div className=\"cedros-member-avatar-placeholder\">{initial}</div>;\n}\n\nfunction formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n}\n","import { useState, useCallback, useEffect, useRef } from 'react';\nimport type { OrgRole, DisplayError } from '../../types';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\nimport { validateEmail } from '../../utils/validation';\n\ntype InviteRole = Exclude<OrgRole, 'owner'>;\n\nexport interface InviteFormProps {\n /** Callback when invite is submitted */\n onSubmit: (email: string, role: InviteRole) => Promise<void>;\n /** Loading state */\n isLoading?: boolean;\n /** Error message */\n error?: DisplayError;\n /** Available roles for invite (default: admin, member) */\n availableRoles?: InviteRole[];\n /** Default role for new invites */\n defaultRole?: InviteRole;\n /** Additional CSS class */\n className?: string;\n}\n\nconst DEFAULT_ROLES: InviteRole[] = ['admin', 'member'];\n\n/**\n * Form for inviting new members to an organization.\n *\n * @example\n * ```tsx\n * function InviteManager() {\n * const { activeOrg } = useOrgs();\n * const { createInvite, isLoading, error } = useInvites(activeOrg?.id);\n *\n * return (\n * <InviteForm\n * onSubmit={createInvite}\n * isLoading={isLoading}\n * error={error?.message}\n * defaultRole=\"member\"\n * />\n * );\n * }\n * ```\n */\nexport function InviteForm({\n onSubmit,\n isLoading = false,\n error,\n availableRoles = DEFAULT_ROLES,\n defaultRole = 'member',\n className = '',\n}: InviteFormProps) {\n const [email, setEmail] = useState('');\n const [role, setRole] = useState<InviteRole>(defaultRole);\n const [formError, setFormError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n const successTimerRef = useRef<number | null>(null);\n // UI-03: Track mounted state to prevent state updates after unmount\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n if (successTimerRef.current !== null) {\n window.clearTimeout(successTimerRef.current);\n successTimerRef.current = null;\n }\n };\n }, []);\n\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n setSuccess(false);\n\n const trimmedEmail = email.trim();\n\n if (!trimmedEmail) {\n setFormError('Email is required');\n return;\n }\n\n if (!validateEmail(trimmedEmail)) {\n setFormError('Please enter a valid email address');\n return;\n }\n\n try {\n await onSubmit(trimmedEmail, role);\n setEmail('');\n setRole(defaultRole);\n setSuccess(true);\n // Clear success message after 3 seconds\n if (successTimerRef.current !== null) {\n window.clearTimeout(successTimerRef.current);\n }\n successTimerRef.current = window.setTimeout(() => {\n // UI-03: Check mounted before state update to prevent memory leak warnings\n if (isMountedRef.current) {\n setSuccess(false);\n }\n successTimerRef.current = null;\n }, 3000);\n } catch {\n // H-05: Error handling is delegated to parent component.\n // Parent's onSubmit throws -> parent catches -> sets error prop -> ErrorMessage displays it.\n // This pattern allows the parent to control error display and retry logic.\n }\n },\n [email, role, defaultRole, onSubmit]\n );\n\n return (\n <form className={`cedros-invite-form ${className}`} onSubmit={handleSubmit}>\n {(error || formError) && <ErrorMessage error={formError ?? error ?? null} />}\n\n {success && (\n <div className=\"cedros-invite-success\" role=\"status\">\n <CheckIcon />\n <span>Invitation sent successfully!</span>\n </div>\n )}\n\n <div className=\"cedros-invite-form-row\">\n <div className=\"cedros-form-group cedros-invite-email-group\">\n <label htmlFor=\"invite-email\" className=\"cedros-form-label\">\n Email Address\n </label>\n <input\n id=\"invite-email\"\n type=\"email\"\n className=\"cedros-form-input\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"colleague@example.com\"\n disabled={isLoading}\n autoComplete=\"email\"\n />\n </div>\n\n <div className=\"cedros-form-group cedros-invite-role-group\">\n <label htmlFor=\"invite-role\" className=\"cedros-form-label\">\n Role\n </label>\n <select\n id=\"invite-role\"\n className=\"cedros-form-select\"\n value={role}\n onChange={(e) => setRole(e.target.value as InviteRole)}\n disabled={isLoading}\n >\n {availableRoles.map((r) => (\n <option key={r} value={r}>\n {r.charAt(0).toUpperCase() + r.slice(1)}\n </option>\n ))}\n </select>\n </div>\n\n <button\n type=\"submit\"\n className=\"cedros-button cedros-button-primary cedros-invite-submit\"\n disabled={isLoading || !email.trim()}\n >\n {isLoading ? <LoadingSpinner size=\"sm\" /> : 'Send Invite'}\n </button>\n </div>\n\n <p className=\"cedros-form-hint\">\n The invited user will receive an email with a link to join your organization.\n </p>\n </form>\n );\n}\n\nfunction CheckIcon() {\n return (\n <svg\n className=\"cedros-invite-check\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M3 8L6 11L13 5\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","import { useState, useCallback, useEffect, useRef } from 'react';\nimport type { Invite, DisplayError } from '../../types';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { ErrorMessage } from '../shared/ErrorMessage';\n\nexport interface InviteListProps {\n /** List of pending invites */\n invites: Invite[];\n /** Loading state */\n isLoading?: boolean;\n /** Error message */\n error?: DisplayError;\n /** Whether the current user can manage invites */\n canManage?: boolean;\n /** Callback when invite is cancelled */\n onCancel?: (inviteId: string) => Promise<void>;\n /** Callback when invite is resent */\n onResend?: (inviteId: string) => Promise<void>;\n /** Additional CSS class */\n className?: string;\n}\n\n/**\n * Display and manage pending organization invites.\n *\n * @example\n * ```tsx\n * function PendingInvites() {\n * const { activeOrg, hasPermission } = useOrgs();\n * const { invites, isLoading, error, cancelInvite, resendInvite } = useInvites(activeOrg?.id);\n *\n * return (\n * <InviteList\n * invites={invites}\n * isLoading={isLoading}\n * error={error?.message}\n * canManage={hasPermission('invite:cancel')}\n * onCancel={cancelInvite}\n * onResend={resendInvite}\n * />\n * );\n * }\n * ```\n */\nexport function InviteList({\n invites,\n isLoading = false,\n error,\n canManage = false,\n onCancel,\n onResend,\n className = '',\n}: InviteListProps) {\n if (isLoading && invites.length === 0) {\n return (\n <div className={`cedros-invite-list cedros-invite-list-loading ${className}`}>\n <LoadingSpinner />\n <span>Loading invites...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className={`cedros-invite-list ${className}`}>\n <ErrorMessage error={error} />\n </div>\n );\n }\n\n if (invites.length === 0) {\n return (\n <div className={`cedros-invite-list cedros-invite-list-empty ${className}`}>\n <p>No pending invites.</p>\n </div>\n );\n }\n\n return (\n <div className={`cedros-invite-list ${className}`}>\n <ul className=\"cedros-invite-items\">\n {invites.map((invite) => (\n <InviteItem\n key={invite.id}\n invite={invite}\n canManage={canManage}\n onCancel={onCancel}\n onResend={onResend}\n />\n ))}\n </ul>\n </div>\n );\n}\n\n// Invite item component\ninterface InviteItemProps {\n invite: Invite;\n canManage: boolean;\n onCancel?: (inviteId: string) => Promise<void>;\n onResend?: (inviteId: string) => Promise<void>;\n}\n\nfunction InviteItem({ invite, canManage, onCancel, onResend }: InviteItemProps) {\n const [isUpdating, setIsUpdating] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const resendTimerRef = useRef<number | null>(null);\n\n const isExpired = new Date(invite.expiresAt) < new Date();\n\n const handleCancel = useCallback(async () => {\n if (!onCancel) return;\n\n const confirmed = window.confirm(\n `Are you sure you want to cancel the invite for ${invite.email}?`\n );\n if (!confirmed) return;\n\n setIsUpdating(true);\n try {\n await onCancel(invite.id);\n } finally {\n setIsUpdating(false);\n }\n }, [invite.id, invite.email, onCancel]);\n\n const handleResend = useCallback(async () => {\n if (!onResend) return;\n\n setIsUpdating(true);\n setResendSuccess(false);\n try {\n await onResend(invite.id);\n setResendSuccess(true);\n if (resendTimerRef.current !== null) {\n window.clearTimeout(resendTimerRef.current);\n }\n resendTimerRef.current = window.setTimeout(() => {\n setResendSuccess(false);\n resendTimerRef.current = null;\n }, 3000);\n } finally {\n setIsUpdating(false);\n }\n }, [invite.id, onResend]);\n\n useEffect(() => {\n return () => {\n if (resendTimerRef.current !== null) {\n window.clearTimeout(resendTimerRef.current);\n resendTimerRef.current = null;\n }\n };\n }, []);\n\n return (\n <li className={`cedros-invite-item ${isExpired ? 'cedros-invite-item-expired' : ''}`}>\n <div className=\"cedros-invite-item-info\">\n <div className=\"cedros-invite-item-main\">\n <span className=\"cedros-invite-item-email\">{invite.email}</span>\n <span className={`cedros-role-badge cedros-role-badge-${invite.role}`}>\n {invite.role.charAt(0).toUpperCase() + invite.role.slice(1)}\n </span>\n {isExpired && <span className=\"cedros-invite-expired-badge\">Expired</span>}\n </div>\n <div className=\"cedros-invite-item-meta\">\n <span className=\"cedros-invite-item-date\">\n {/* TYPE-05: invitedByName removed - backend doesn't populate it */}\n Invited {formatDate(invite.createdAt)}\n </span>\n {!isExpired && (\n <span className=\"cedros-invite-item-expires\">\n Expires {formatRelativeTime(invite.expiresAt)}\n </span>\n )}\n </div>\n </div>\n\n {canManage && (\n <div className=\"cedros-invite-item-actions\">\n {resendSuccess && <span className=\"cedros-invite-resend-success\">Sent!</span>}\n {onResend && !isExpired && (\n <button\n type=\"button\"\n className=\"cedros-button cedros-button-outline cedros-button-sm\"\n onClick={handleResend}\n disabled={isUpdating}\n aria-label={`Resend invite to ${invite.email}`}\n >\n {isUpdating ? <LoadingSpinner size=\"sm\" /> : 'Resend'}\n </button>\n )}\n {onCancel && (\n <button\n type=\"button\"\n className=\"cedros-button cedros-button-danger cedros-button-sm\"\n onClick={handleCancel}\n disabled={isUpdating}\n aria-label={`Cancel invite for ${invite.email}`}\n >\n Cancel\n </button>\n )}\n </div>\n )}\n </li>\n );\n}\n\n// Helper functions\nfunction formatDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n}\n\nfunction formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffDays < 0) {\n return 'expired';\n } else if (diffDays === 0) {\n return 'today';\n } else if (diffDays === 1) {\n return 'tomorrow';\n } else if (diffDays < 7) {\n return `in ${diffDays} days`;\n } else {\n return formatDate(dateString);\n }\n}\n","import type {\n Member,\n MemberApiResponse,\n UpdateMemberRoleRequest,\n ListMembersResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for member operations within an organization\n */\nexport class MemberApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all members of an organization\n */\n async listMembers(\n orgId: string,\n limit: number = 50,\n offset: number = 0\n ): Promise<{ members: Member[]; total: number }> {\n try {\n const response = await this.client.get<ListMembersResponse>(\n `/orgs/${orgId}/members?limit=${limit}&offset=${offset}`\n );\n return {\n members: response.members.map((member: MemberApiResponse) => ({\n id: member.id,\n userId: member.userId,\n orgId,\n role: member.role,\n joinedAt: member.joinedAt,\n user: {\n id: member.userId,\n email: member.email,\n name: member.name,\n },\n })),\n total: response.total,\n };\n } catch (error) {\n throw handleApiError(error, 'Failed to list members');\n }\n }\n\n /**\n * Update a member's role\n */\n async updateMemberRole(\n orgId: string,\n userId: string,\n data: UpdateMemberRoleRequest\n ): Promise<Member> {\n try {\n return await this.client.patch<Member>(`/orgs/${orgId}/members/${userId}`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to update member role');\n }\n }\n\n /**\n * Remove a member from the organization\n */\n async removeMember(orgId: string, userId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}/members/${userId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to remove member');\n }\n }\n}\n","import { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport type { Member, OrgRole, AuthError } from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { MemberApiClient } from '../utils/memberApi';\n\nexport interface UseMembersReturn {\n /** List of members */\n members: Member[];\n /** Total members available on the server */\n total: number;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh members list */\n fetchMembers: (options?: { limit?: number; offset?: number }) => Promise<void>;\n /** Update a member's role */\n updateMemberRole: (userId: string, role: OrgRole) => Promise<void>;\n /** Remove a member */\n removeMember: (userId: string) => Promise<void>;\n}\n\n/**\n * Hook for managing organization members.\n *\n * @param orgId - The organization ID to manage members for\n *\n * @example\n * ```tsx\n * function MembersList() {\n * const { activeOrg } = useOrgs();\n * const { members, isLoading, updateMemberRole, removeMember } = useMembers(activeOrg?.id);\n *\n * if (!activeOrg) return null;\n *\n * return (\n * <ul>\n * {members.map(member => (\n * <li key={member.id}>\n * {member.user.name} - {member.role}\n * <button onClick={() => updateMemberRole(member.userId, 'admin')}>\n * Make Admin\n * </button>\n * <button onClick={() => removeMember(member.userId)}>\n * Remove\n * </button>\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useMembers(orgId: string | undefined): UseMembersReturn {\n const { config, authState, _internal } = useCedrosLogin();\n\n const [members, setMembers] = useState<Member[]>([]);\n const [total, setTotal] = useState(0);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n // P-12: Track fetched orgId to prevent duplicate fetches on remount\n const fetchedOrgIdRef = useRef<string | undefined>(undefined);\n const requestIdRef = useRef(0);\n\n const apiClient = useMemo(\n () =>\n new MemberApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // C-02: Use ref to avoid apiClient in callback dependencies\n // Prevents infinite loop when apiClient identity changes\n const apiClientRef = useRef(apiClient);\n apiClientRef.current = apiClient;\n\n const fetchMembers = useCallback(\n async (options?: { limit?: number; offset?: number }) => {\n if (!orgId || authState !== 'authenticated') {\n setMembers([]);\n setTotal(0);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n const requestId = ++requestIdRef.current;\n\n try {\n const { limit = 50, offset = 0 } = options ?? {};\n const response = await apiClientRef.current.listMembers(orgId, limit, offset);\n if (requestId !== requestIdRef.current) return;\n setMembers(response.members);\n setTotal(response.total);\n } catch (err) {\n if (requestId !== requestIdRef.current) return;\n setError(err as AuthError);\n } finally {\n if (requestId === requestIdRef.current) {\n setIsLoading(false);\n }\n }\n },\n [orgId, authState]\n );\n\n // P-12: Auto-fetch when orgId changes, but not on every remount\n useEffect(() => {\n if (authState !== 'authenticated') {\n fetchedOrgIdRef.current = undefined;\n return;\n }\n\n if (orgId !== fetchedOrgIdRef.current) {\n fetchedOrgIdRef.current = orgId;\n fetchMembers();\n }\n }, [orgId, authState, fetchMembers]);\n\n const updateMemberRole = useCallback(\n async (userId: string, role: OrgRole): Promise<void> => {\n if (!orgId) {\n throw new Error('No organization selected');\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.updateMemberRole(orgId, userId, { role });\n // Refresh members list\n await fetchMembers();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [orgId, fetchMembers]\n );\n\n const removeMember = useCallback(\n async (userId: string): Promise<void> => {\n if (!orgId) {\n throw new Error('No organization selected');\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.removeMember(orgId, userId);\n // Refresh members list\n await fetchMembers();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [orgId, fetchMembers]\n );\n\n return {\n members,\n total,\n isLoading,\n error,\n fetchMembers,\n updateMemberRole,\n removeMember,\n };\n}\n","import type {\n Invite,\n InviteApiResponse,\n CreateInviteRequest,\n AcceptInviteRequest,\n ListInvitesResponse,\n CreateInviteResponse,\n AcceptInviteResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for invite operations\n */\nexport class InviteApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all pending invites for an organization\n */\n async listInvites(\n orgId: string,\n limit: number = 50,\n offset: number = 0\n ): Promise<{ invites: Invite[]; total: number }> {\n try {\n const response = await this.client.get<ListInvitesResponse>(\n `/orgs/${orgId}/invites?limit=${limit}&offset=${offset}`\n );\n return {\n invites: response.invites.map((invite: InviteApiResponse) => ({\n id: invite.id,\n orgId: invite.orgId,\n email: invite.email,\n role: invite.role,\n invitedBy: invite.invitedBy,\n createdAt: invite.createdAt,\n expiresAt: invite.expiresAt,\n })),\n total: response.total,\n };\n } catch (error) {\n throw handleApiError(error, 'Failed to list invites');\n }\n }\n\n /**\n * Create a new invite\n */\n async createInvite(orgId: string, data: CreateInviteRequest): Promise<CreateInviteResponse> {\n try {\n return await this.client.post<CreateInviteResponse>(`/orgs/${orgId}/invites`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to create invite');\n }\n }\n\n /**\n * Cancel a pending invite\n */\n async cancelInvite(orgId: string, inviteId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}/invites/${inviteId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to cancel invite');\n }\n }\n\n /**\n * Resend an invite email\n */\n async resendInvite(orgId: string, inviteId: string): Promise<void> {\n try {\n await this.client.post<void>(`/orgs/${orgId}/invites/${inviteId}/resend`, {});\n } catch (error) {\n throw handleApiError(error, 'Failed to resend invite');\n }\n }\n\n /**\n * Accept an invite (public endpoint)\n */\n async acceptInvite(data: AcceptInviteRequest): Promise<AcceptInviteResponse> {\n try {\n return await this.client.post<AcceptInviteResponse>('/invites/accept', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to accept invite');\n }\n }\n}\n","import { useState, useCallback, useMemo, useEffect, useRef } from 'react';\nimport type { Invite, OrgRole, AuthError, AcceptInviteResponse } from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { InviteApiClient } from '../utils/inviteApi';\n\nexport interface UseInvitesReturn {\n /** List of pending invites */\n invites: Invite[];\n /** Total pending invites available on the server */\n total: number;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh invites list */\n fetchInvites: (options?: { limit?: number; offset?: number }) => Promise<void>;\n /** Create a new invite */\n createInvite: (email: string, role?: Exclude<OrgRole, 'owner'>) => Promise<void>;\n /** Cancel a pending invite */\n cancelInvite: (inviteId: string) => Promise<void>;\n /** Resend an invite email */\n resendInvite: (inviteId: string) => Promise<void>;\n /** Accept an invite (public) */\n acceptInvite: (token: string) => Promise<AcceptInviteResponse>;\n}\n\n/**\n * Hook for managing organization invites.\n *\n * @param orgId - The organization ID to manage invites for\n *\n * @example\n * ```tsx\n * function InviteManager() {\n * const { activeOrg } = useOrgs();\n * const { invites, createInvite, cancelInvite, resendInvite } = useInvites(activeOrg?.id);\n *\n * const handleInvite = async (email: string) => {\n * await createInvite(email, 'member');\n * };\n *\n * return (\n * <div>\n * <InviteForm onSubmit={handleInvite} />\n * <ul>\n * {invites.map(invite => (\n * <li key={invite.id}>\n * {invite.email} ({invite.role})\n * <button onClick={() => resendInvite(invite.id)}>Resend</button>\n * <button onClick={() => cancelInvite(invite.id)}>Cancel</button>\n * </li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n */\nexport function useInvites(orgId: string | undefined): UseInvitesReturn {\n const { config, authState, _internal } = useCedrosLogin();\n\n const [invites, setInvites] = useState<Invite[]>([]);\n const [total, setTotal] = useState(0);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n // P-13: Track fetched orgId to prevent duplicate fetches on remount\n const fetchedOrgIdRef = useRef<string | undefined>(undefined);\n const requestIdRef = useRef(0);\n\n const apiClient = useMemo(\n () =>\n new InviteApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // H-02: Use ref to avoid apiClient in callback dependencies\n // Prevents infinite loop when apiClient identity changes\n const apiClientRef = useRef(apiClient);\n apiClientRef.current = apiClient;\n\n const fetchInvites = useCallback(\n async (options?: { limit?: number; offset?: number }) => {\n if (!orgId || authState !== 'authenticated') {\n setInvites([]);\n setTotal(0);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n const requestId = ++requestIdRef.current;\n\n try {\n const { limit = 50, offset = 0 } = options ?? {};\n const response = await apiClientRef.current.listInvites(orgId, limit, offset);\n if (requestId !== requestIdRef.current) return;\n setInvites(response.invites);\n setTotal(response.total);\n } catch (err) {\n if (requestId !== requestIdRef.current) return;\n setError(err as AuthError);\n } finally {\n if (requestId === requestIdRef.current) {\n setIsLoading(false);\n }\n }\n },\n [orgId, authState]\n );\n\n // P-13: Auto-fetch when orgId changes, but not on every remount\n useEffect(() => {\n if (authState !== 'authenticated') {\n fetchedOrgIdRef.current = undefined;\n return;\n }\n\n if (orgId !== fetchedOrgIdRef.current) {\n fetchedOrgIdRef.current = orgId;\n fetchInvites();\n }\n }, [orgId, authState, fetchInvites]);\n\n const createInvite = useCallback(\n async (email: string, role: Exclude<OrgRole, 'owner'> = 'member'): Promise<void> => {\n if (!orgId) {\n throw new Error('No organization selected');\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.createInvite(orgId, { email, role });\n // Refresh invites list\n await fetchInvites();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [orgId, fetchInvites]\n );\n\n const cancelInvite = useCallback(\n async (inviteId: string): Promise<void> => {\n if (!orgId) {\n throw new Error('No organization selected');\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.cancelInvite(orgId, inviteId);\n // Refresh invites list\n await fetchInvites();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [orgId, fetchInvites]\n );\n\n const resendInvite = useCallback(\n async (inviteId: string): Promise<void> => {\n if (!orgId) {\n throw new Error('No organization selected');\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.resendInvite(orgId, inviteId);\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [orgId]\n );\n\n const acceptInvite = useCallback(async (token: string): Promise<AcceptInviteResponse> => {\n setIsLoading(true);\n setError(null);\n\n try {\n return await apiClientRef.current.acceptInvite({ token });\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n return {\n invites,\n total,\n isLoading,\n error,\n fetchInvites,\n createInvite,\n cancelInvite,\n resendInvite,\n acceptInvite,\n };\n}\n","import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useSystemSettings } from './useSystemSettings';\n\n/**\n * Server-side feature flags stored in system settings.\n *\n * These control which features are available in the application.\n * Unlike client-side FeatureFlags (passed to CedrosLoginProvider),\n * these can be toggled at runtime via the admin dashboard.\n *\n * **Cosmetic vs enforced:**\n * Some flags only affect UI visibility (cosmetic) while others are\n * enforced server-side. See per-field docs below.\n *\n * Settings that are enforced per-request by the server (hard 403 if disabled):\n * `auth_email_enabled`, `auth_google_enabled`, `auth_apple_enabled`,\n * `auth_instantlink_enabled`, `feature_user_withdrawals`, `privacy_period_secs`,\n * withdrawal worker settings, and deposit fee settings.\n *\n * Settings applied at server startup (take effect after restart):\n * `security_cors_origins`, `rate_limit_*`, `auth_webauthn_*`, `webhook_*`.\n */\nexport interface ServerFeatures {\n /**\n * Enable multi-tenant organizations. Controls: Team, Invites sections.\n *\n * **Cosmetic** — org endpoints are always reachable server-side.\n * This flag only controls admin dashboard section visibility.\n */\n organizations: boolean;\n /** Enable Enterprise SSO for organizations. Startup-config enforced. */\n sso: boolean;\n /**\n * Enable two-factor authentication (TOTP).\n *\n * **Cosmetic** — MFA is actually gated by whether the user has TOTP\n * enrolled (`has_mfa_enabled()`), not by this flag. This controls\n * admin dashboard visibility only.\n */\n mfa: boolean;\n /** Enable embedded wallet for transaction signing. Startup-config enforced. */\n walletSigning: boolean;\n /**\n * Enable deposits and credits system.\n * Controls: Deposits, Withdrawals, Credit System admin sections.\n * Deposit/withdrawal endpoints are enforced server-side.\n */\n credits: boolean;\n /**\n * Enable user withdrawals from embedded wallet to external addresses.\n * **Enforced** — server returns 403 on withdrawal endpoints when disabled.\n */\n userWithdrawals: boolean;\n /**\n * Enable Cedros Pay integration.\n * Controls: Products, Transactions, Refunds admin sections.\n *\n * **Cosmetic** — only controls admin dashboard tab visibility.\n */\n cedrosPay: boolean;\n}\n\n/**\n * Default feature values when settings haven't loaded yet.\n * All features disabled by default for safety.\n */\nconst DEFAULT_FEATURES: ServerFeatures = {\n organizations: false,\n sso: false,\n mfa: false,\n walletSigning: false,\n credits: false,\n userWithdrawals: false,\n cedrosPay: false,\n};\n\nexport interface UseServerFeaturesReturn {\n /** Current feature flag states */\n features: ServerFeatures;\n /** Whether settings are still loading */\n isLoading: boolean;\n /** Error if settings failed to load */\n error: Error | null;\n /** Refresh feature flags from server */\n refetch: () => Promise<void>;\n /** Check if a specific feature is enabled */\n isEnabled: (feature: keyof ServerFeatures) => boolean;\n}\n\n/**\n * Hook for reading server-side feature flags from system settings.\n *\n * Use this to conditionally show/hide features based on admin settings.\n *\n * @example\n * ```tsx\n * function AdminDashboard() {\n * const { features, isLoading, isEnabled } = useServerFeatures();\n *\n * if (isLoading) return <LoadingSpinner />;\n *\n * return (\n * <nav>\n * {isEnabled('organizations') && <NavItem>Team</NavItem>}\n * {isEnabled('credits') && <NavItem>Deposits</NavItem>}\n * </nav>\n * );\n * }\n * ```\n */\nexport function useServerFeatures(): UseServerFeaturesReturn {\n const { settings, isLoading, error, fetchSettings, getValue } = useSystemSettings();\n const [hasFetched, setHasFetched] = useState(false);\n\n // Fetch settings on mount\n useEffect(() => {\n if (!hasFetched) {\n fetchSettings();\n setHasFetched(true);\n }\n }, [fetchSettings, hasFetched]);\n\n // Parse boolean value from string setting\n const parseBoolean = useCallback((value: string | undefined): boolean => {\n if (value === undefined) return false;\n return value === 'true' || value === '1';\n }, []);\n\n // Derive features from settings\n const features = useMemo<ServerFeatures>(() => {\n // If no settings loaded yet, return defaults\n if (Object.keys(settings).length === 0) {\n return DEFAULT_FEATURES;\n }\n\n return {\n organizations: parseBoolean(getValue('feature_organizations')),\n sso: parseBoolean(getValue('feature_sso')),\n mfa: parseBoolean(getValue('feature_mfa')),\n walletSigning: parseBoolean(getValue('feature_wallet_signing')),\n credits: parseBoolean(getValue('feature_credits')),\n userWithdrawals: parseBoolean(getValue('feature_user_withdrawals')),\n cedrosPay: parseBoolean(getValue('feature_cedros_pay')),\n };\n }, [settings, getValue, parseBoolean]);\n\n const refetch = useCallback(async () => {\n await fetchSettings();\n }, [fetchSettings]);\n\n const isEnabled = useCallback(\n (feature: keyof ServerFeatures): boolean => {\n return features[feature];\n },\n [features]\n );\n\n return {\n features,\n isLoading,\n error,\n refetch,\n isEnabled,\n };\n}\n","/**\n * Organization role in RBAC hierarchy\n * owner > admin > member\n */\nexport type OrgRole = 'owner' | 'admin' | 'member';\n\n/**\n * Organization entity\n */\nexport interface Organization {\n id: string;\n name: string;\n slug: string;\n logoUrl?: string;\n isPersonal: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Membership - user's relationship to an organization\n */\nexport interface Membership {\n id?: string;\n userId?: string;\n orgId?: string;\n role: OrgRole;\n joinedAt?: string;\n}\n\n/**\n * Organization with membership details for the current user\n */\nexport interface OrgWithMembership extends Organization {\n membership: Membership;\n}\n\n/**\n * Permission types for RBAC\n */\nexport type Permission =\n | 'org:delete'\n | 'org:update'\n | 'org:read'\n | 'member:invite'\n | 'member:remove'\n | 'member:role_change'\n | 'member:read'\n | 'invite:create'\n | 'invite:cancel'\n | 'invite:read'\n | 'audit:read';\n\n/**\n * Create organization request\n */\nexport interface CreateOrgRequest {\n name: string;\n slug?: string;\n}\n\n/**\n * Update organization request\n */\nexport interface UpdateOrgRequest {\n name?: string;\n slug?: string;\n logoUrl?: string;\n}\n\n/**\n * List organizations response\n */\nexport interface ListOrgsResponse {\n orgs: Array<Organization & { role: OrgRole }>;\n total?: number;\n limit?: number;\n offset?: number;\n}\n\n/**\n * Authorization check request\n */\nexport interface AuthorizeRequest {\n orgId: string;\n action: string;\n resource?: string;\n resourceId?: string;\n}\n\n/**\n * Authorization check response\n */\nexport interface AuthorizeResponse {\n allowed: boolean;\n reason?: string;\n}\n\n/**\n * Permissions response\n */\nexport interface PermissionsResponse {\n permissions: Permission[];\n role: OrgRole;\n}\n\n/**\n * Organization state for context\n */\nexport interface OrgState {\n /** Currently active organization */\n activeOrg: OrgWithMembership | null;\n /** All organizations the user belongs to */\n orgs: OrgWithMembership[];\n /** User's permissions in the active org */\n permissions: Permission[];\n /** User's role in the active org */\n role: OrgRole | null;\n /** Loading state for org operations */\n isLoading: boolean;\n}\n\n// =============================================================================\n// DASHBOARD PERMISSIONS\n// =============================================================================\n\n/**\n * Admin dashboard sections that can be permission-controlled\n */\nexport type DashboardSection =\n // Cedros Login sections\n | 'users'\n | 'team'\n | 'deposits'\n | 'withdrawals'\n | 'settings-wallet'\n | 'settings-auth'\n | 'settings-messaging'\n | 'settings-credits'\n | 'settings-server'\n // Cedros Pay sections\n | 'pay-products'\n | 'pay-subscriptions'\n | 'pay-transactions'\n | 'pay-coupons'\n | 'pay-refunds'\n | 'pay-storefront'\n | 'pay-ai'\n | 'pay-payment'\n | 'pay-messaging'\n | 'pay-settings';\n\n/**\n * Cedros Login dashboard sections\n */\nexport const LOGIN_DASHBOARD_SECTIONS: DashboardSection[] = [\n 'users',\n 'team',\n 'deposits',\n 'withdrawals',\n 'settings-wallet',\n 'settings-auth',\n 'settings-messaging',\n 'settings-credits',\n 'settings-server',\n];\n\n/**\n * Cedros Pay dashboard sections\n */\nexport const PAY_DASHBOARD_SECTIONS: DashboardSection[] = [\n 'pay-products',\n 'pay-subscriptions',\n 'pay-transactions',\n 'pay-coupons',\n 'pay-refunds',\n 'pay-storefront',\n 'pay-ai',\n 'pay-payment',\n 'pay-messaging',\n 'pay-settings',\n];\n\n/**\n * All available dashboard sections\n */\nexport const ALL_DASHBOARD_SECTIONS: DashboardSection[] = [\n ...LOGIN_DASHBOARD_SECTIONS,\n ...PAY_DASHBOARD_SECTIONS,\n];\n\n/**\n * Human-readable labels for dashboard sections\n */\nexport const DASHBOARD_SECTION_LABELS: Record<DashboardSection, string> = {\n // Cedros Login\n users: 'Users',\n team: 'Team',\n deposits: 'Deposits',\n withdrawals: 'Withdrawals',\n 'settings-wallet': 'Wallet Settings',\n 'settings-auth': 'Auth Settings',\n 'settings-messaging': 'Messages Settings',\n 'settings-credits': 'Credits Settings',\n 'settings-server': 'Server Settings',\n // Cedros Pay\n 'pay-products': 'Products',\n 'pay-subscriptions': 'Subscriptions',\n 'pay-transactions': 'Transactions',\n 'pay-coupons': 'Coupons',\n 'pay-refunds': 'Refunds',\n 'pay-storefront': 'Storefront',\n 'pay-ai': 'Store AI',\n 'pay-payment': 'Payment Options',\n 'pay-messaging': 'Store Messages',\n 'pay-settings': 'Store Server',\n};\n\n/**\n * Dashboard permissions per role\n * Only admin and member are configurable - owner always has full access\n */\nexport interface DashboardPermissions {\n admin: Record<DashboardSection, boolean>;\n member: Record<DashboardSection, boolean>;\n}\n\n/**\n * Default dashboard permissions for new orgs\n */\nexport const DEFAULT_DASHBOARD_PERMISSIONS: DashboardPermissions = {\n admin: {\n // Cedros Login\n users: true,\n team: true,\n deposits: true,\n withdrawals: true,\n 'settings-wallet': true,\n 'settings-auth': true,\n 'settings-messaging': true,\n 'settings-credits': true,\n 'settings-server': true,\n // Cedros Pay\n 'pay-products': true,\n 'pay-subscriptions': true,\n 'pay-transactions': true,\n 'pay-coupons': true,\n 'pay-refunds': true,\n 'pay-storefront': true,\n 'pay-ai': true,\n 'pay-payment': true,\n 'pay-messaging': true,\n 'pay-settings': true,\n },\n member: {\n // Cedros Login\n users: false,\n team: true,\n deposits: false,\n withdrawals: false,\n 'settings-wallet': false,\n 'settings-auth': false,\n 'settings-messaging': false,\n 'settings-credits': false,\n 'settings-server': false,\n // Cedros Pay\n 'pay-products': false,\n 'pay-subscriptions': false,\n 'pay-transactions': false,\n 'pay-coupons': false,\n 'pay-refunds': false,\n 'pay-storefront': false,\n 'pay-ai': false,\n 'pay-payment': false,\n 'pay-messaging': false,\n 'pay-settings': false,\n },\n};\n","import { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport type { DashboardSection, DashboardPermissions, AuthError } from '../types';\nimport { DEFAULT_DASHBOARD_PERMISSIONS } from '../types/org';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { useOrgs } from './useOrgs';\nimport { ApiClient } from '../utils/apiClient';\n\nexport interface UseDashboardPermissionsReturn {\n /** Current dashboard permissions config */\n permissions: DashboardPermissions;\n /** Whether the current user can access a specific section */\n canAccess: (section: DashboardSection) => boolean;\n /** Update permissions (owner only) */\n updatePermissions: (permissions: DashboardPermissions) => Promise<void>;\n /** Loading state */\n isLoading: boolean;\n /** Updating state */\n isUpdating: boolean;\n /** Error state */\n error: AuthError | null;\n /** Refresh permissions from server */\n fetchPermissions: () => Promise<void>;\n}\n\n/**\n * Hook for managing dashboard permissions per role.\n *\n * Allows org owners to configure which dashboard sections each role can access.\n * - Owner always has full access (not configurable)\n * - Admin and Member roles are configurable\n *\n * @example\n * ```tsx\n * function AdminDashboard() {\n * const { canAccess, permissions, updatePermissions } = useDashboardPermissions();\n *\n * // Check if current user can access a section\n * if (!canAccess('deposits')) {\n * return <div>You don't have access to deposits</div>;\n * }\n *\n * // Update permissions (owner only)\n * const toggleMemberDeposits = () => {\n * updatePermissions({\n * ...permissions,\n * member: { ...permissions.member, deposits: !permissions.member.deposits },\n * });\n * };\n * }\n * ```\n */\nexport function useDashboardPermissions(): UseDashboardPermissionsReturn {\n const { config, authState, _internal } = useCedrosLogin();\n const { activeOrg, role } = useOrgs();\n\n const [permissions, setPermissions] = useState<DashboardPermissions>(\n DEFAULT_DASHBOARD_PERMISSIONS\n );\n const [isLoading, setIsLoading] = useState(false);\n const [isUpdating, setIsUpdating] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n const requestIdRef = useRef(0);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n getAccessToken: _internal?.getAccessToken,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // Use ref to avoid apiClient in callback dependencies\n const apiClientRef = useRef(apiClient);\n apiClientRef.current = apiClient;\n\n const fetchPermissions = useCallback(async () => {\n if (authState !== 'authenticated' || !activeOrg) {\n setPermissions(DEFAULT_DASHBOARD_PERMISSIONS);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n const requestId = ++requestIdRef.current;\n\n try {\n const response = await apiClientRef.current.get<{ permissions: DashboardPermissions }>(\n '/admin/dashboard-permissions'\n );\n if (requestId !== requestIdRef.current) return;\n setPermissions(response.permissions);\n } catch (err) {\n if (requestId !== requestIdRef.current) return;\n // If 404, use defaults (permissions not configured yet)\n if (err instanceof Error && err.message.includes('404')) {\n setPermissions(DEFAULT_DASHBOARD_PERMISSIONS);\n } else {\n const message = err instanceof Error ? err.message : 'Failed to fetch permissions';\n setError({ code: 'NETWORK_ERROR', message } as AuthError);\n // Fall back to defaults on error\n setPermissions(DEFAULT_DASHBOARD_PERMISSIONS);\n }\n } finally {\n if (requestId === requestIdRef.current) {\n setIsLoading(false);\n }\n }\n }, [authState, activeOrg]);\n\n const updatePermissions = useCallback(\n async (newPermissions: DashboardPermissions): Promise<void> => {\n if (authState !== 'authenticated' || !activeOrg) {\n throw new Error('Not authenticated');\n }\n\n if (role !== 'owner') {\n throw new Error('Only owners can modify dashboard permissions');\n }\n\n setIsUpdating(true);\n setError(null);\n\n try {\n await apiClientRef.current.request<{ permissions: DashboardPermissions }>({\n method: 'PUT',\n path: '/admin/dashboard-permissions',\n body: newPermissions,\n });\n setPermissions(newPermissions);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to update permissions';\n const authError = { code: 'NETWORK_ERROR', message } as AuthError;\n setError(authError);\n throw new Error(message);\n } finally {\n setIsUpdating(false);\n }\n },\n [authState, activeOrg, role]\n );\n\n /**\n * Check if the current user can access a dashboard section\n * - No org context (system admin): full access (permissions are org-scoped)\n * - Owner: always has full access\n * - Admin: check permissions.admin[section]\n * - Member: check permissions.member[section]\n */\n const canAccess = useCallback(\n (section: DashboardSection): boolean => {\n // No org context = system admin level, allow all\n // (dashboard permissions are org-scoped, not system-scoped)\n if (!activeOrg || !role) {\n return true;\n }\n\n // Owner always has full access\n if (role === 'owner') {\n return true;\n }\n\n // Admin and member: check configured permissions\n const rolePermissions = permissions[role as 'admin' | 'member'];\n return rolePermissions?.[section] ?? false;\n },\n [activeOrg, role, permissions]\n );\n\n // Fetch permissions when org changes\n useEffect(() => {\n if (activeOrg?.id) {\n fetchPermissions();\n }\n }, [activeOrg?.id, fetchPermissions]);\n\n return {\n permissions,\n canAccess,\n updatePermissions,\n isLoading,\n isUpdating,\n error,\n fetchPermissions,\n };\n}\n","/**\n * PermissionsSection - Dashboard permissions matrix for role configuration\n *\n * Allows org owners to configure which dashboard sections each role can access.\n */\n\nimport React, { useCallback, useRef, useEffect } from 'react';\nimport { useDashboardPermissions } from '../../hooks/useDashboardPermissions';\nimport { useServerFeatures } from '../../hooks/useServerFeatures';\nimport {\n LOGIN_DASHBOARD_SECTIONS,\n PAY_DASHBOARD_SECTIONS,\n DASHBOARD_SECTION_LABELS,\n type DashboardSection,\n type DashboardPermissions,\n type OrgRole,\n} from '../../types/org';\n\ninterface PermissionToggleProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n label: string;\n}\n\nfunction PermissionToggle({ checked, onChange, disabled, label }: PermissionToggleProps) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n className={`cedros-toggle cedros-toggle-sm ${checked ? 'cedros-toggle-on' : 'cedros-toggle-off'} ${disabled ? 'cedros-toggle-disabled' : ''}`}\n onClick={() => !disabled && onChange(!checked)}\n >\n <span className=\"cedros-toggle-track\">\n <span className=\"cedros-toggle-thumb\" />\n </span>\n </button>\n );\n}\n\nexport interface PermissionsSectionProps {\n /** Current user's role in the org */\n userRole?: OrgRole | null;\n}\n\nexport function PermissionsSection({ userRole }: PermissionsSectionProps): React.JSX.Element {\n const { permissions, updatePermissions, isLoading, isUpdating, error } =\n useDashboardPermissions();\n const { features, isLoading: featuresLoading } = useServerFeatures();\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingPermissions = useRef<DashboardPermissions | null>(null);\n\n const isOwner = userRole === 'owner';\n const cedrosPayEnabled = features.cedrosPay;\n\n // Debounced save - batch rapid changes\n const debouncedSave = useCallback(\n (newPermissions: DashboardPermissions) => {\n pendingPermissions.current = newPermissions;\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n debounceRef.current = setTimeout(() => {\n if (pendingPermissions.current) {\n updatePermissions(pendingPermissions.current).catch(() => {\n // Error is already set in state by the hook\n });\n pendingPermissions.current = null;\n }\n }, 500);\n },\n [updatePermissions]\n );\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n };\n }, []);\n\n const handleToggle = useCallback(\n (role: 'admin' | 'member', section: DashboardSection, enabled: boolean) => {\n const newPermissions: DashboardPermissions = {\n ...permissions,\n [role]: {\n ...permissions[role],\n [section]: enabled,\n },\n };\n debouncedSave(newPermissions);\n },\n [permissions, debouncedSave]\n );\n\n if (isLoading || featuresLoading) {\n return (\n <div className=\"cedros-dashboard__section\">\n <div className=\"cedros-dashboard__loading\">Loading permissions...</div>\n </div>\n );\n }\n\n if (!isOwner) {\n return (\n <div className=\"cedros-dashboard__section\">\n <div className=\"cedros-dashboard__empty\">\n Only organization owners can configure dashboard permissions.\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"cedros-dashboard__section cedros-permissions-section\">\n <div className=\"cedros-permissions-header\">\n <p className=\"cedros-permissions-description\">\n Configure which dashboard sections each role can access. Owners always have full access.\n </p>\n {error && <div className=\"cedros-permissions-error\">{error.message}</div>}\n {isUpdating && <span className=\"cedros-permissions-saving\">Saving...</span>}\n </div>\n\n <div className=\"cedros-permissions-matrix\">\n <table className=\"cedros-permissions-table\">\n <tbody>\n {/* Cedros Login Sections */}\n <tr className=\"cedros-permissions-group-header\">\n <th className=\"cedros-permissions-section-header\">Cedros Login</th>\n <th className=\"cedros-permissions-role-header\">Admin</th>\n <th className=\"cedros-permissions-role-header\">Member</th>\n </tr>\n {LOGIN_DASHBOARD_SECTIONS.map((section) => (\n <tr key={section} className=\"cedros-permissions-row\">\n <td className=\"cedros-permissions-section-label\">\n {DASHBOARD_SECTION_LABELS[section]}\n </td>\n <td className=\"cedros-permissions-toggle-cell\">\n <PermissionToggle\n checked={permissions.admin[section] ?? false}\n onChange={(enabled) => handleToggle('admin', section, enabled)}\n disabled={isUpdating}\n label={`Admin access to ${DASHBOARD_SECTION_LABELS[section]}`}\n />\n </td>\n <td className=\"cedros-permissions-toggle-cell\">\n <PermissionToggle\n checked={permissions.member[section] ?? false}\n onChange={(enabled) => handleToggle('member', section, enabled)}\n disabled={isUpdating}\n label={`Member access to ${DASHBOARD_SECTION_LABELS[section]}`}\n />\n </td>\n </tr>\n ))}\n\n {/* Cedros Pay Sections (only when cedrosPay feature is enabled) */}\n {cedrosPayEnabled && (\n <>\n <tr className=\"cedros-permissions-group-header\">\n <th className=\"cedros-permissions-section-header\">Cedros Pay</th>\n <th className=\"cedros-permissions-role-header\">Admin</th>\n <th className=\"cedros-permissions-role-header\">Member</th>\n </tr>\n {PAY_DASHBOARD_SECTIONS.map((section) => (\n <tr key={section} className=\"cedros-permissions-row\">\n <td className=\"cedros-permissions-section-label\">\n {DASHBOARD_SECTION_LABELS[section]}\n </td>\n <td className=\"cedros-permissions-toggle-cell\">\n <PermissionToggle\n checked={permissions.admin[section] ?? false}\n onChange={(enabled) => handleToggle('admin', section, enabled)}\n disabled={isUpdating}\n label={`Admin access to ${DASHBOARD_SECTION_LABELS[section]}`}\n />\n </td>\n <td className=\"cedros-permissions-toggle-cell\">\n <PermissionToggle\n checked={permissions.member[section] ?? false}\n onChange={(enabled) => handleToggle('member', section, enabled)}\n disabled={isUpdating}\n label={`Member access to ${DASHBOARD_SECTION_LABELS[section]}`}\n />\n </td>\n </tr>\n ))}\n </>\n )}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"names":["ROLE_OPTIONS","MemberList","members","currentUserId","isLoading","error","canManage","canChangeRoles","onUpdateRole","onRemove","className","sortField","setSortField","useState","sortOrder","setSortOrder","toggleSort","field","sortedMembers","useMemo","roleOrder","a","aVal","bVal","jsxs","jsx","LoadingSpinner","ErrorMessage","member","MemberRow","isCurrentUser","isUpdating","setIsUpdating","selectedRole","setSelectedRole","handleRoleChange","useCallback","newRole","handleRemove","isOwner","canModify","MemberAvatar","e","role","formatDate","user","sanitizedPicture","sanitizeImageUrl","initial","dateString","DEFAULT_ROLES","InviteForm","onSubmit","availableRoles","defaultRole","email","setEmail","setRole","formError","setFormError","success","setSuccess","successTimerRef","useRef","isMountedRef","useEffect","handleSubmit","trimmedEmail","validateEmail","CheckIcon","r","InviteList","invites","onCancel","onResend","invite","InviteItem","resendSuccess","setResendSuccess","resendTimerRef","isExpired","handleCancel","handleResend","formatRelativeTime","date","now","diffMs","diffDays","MemberApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","orgId","limit","offset","response","handleApiError","userId","data","useMembers","config","authState","_internal","useCedrosLogin","setMembers","total","setTotal","setIsLoading","setError","fetchedOrgIdRef","requestIdRef","apiClient","apiClientRef","fetchMembers","options","requestId","err","updateMemberRole","removeMember","InviteApiClient","inviteId","useInvites","setInvites","fetchInvites","createInvite","cancelInvite","resendInvite","acceptInvite","token","DEFAULT_FEATURES","useServerFeatures","settings","fetchSettings","getValue","useSystemSettings","hasFetched","setHasFetched","parseBoolean","value","features","refetch","isEnabled","feature","LOGIN_DASHBOARD_SECTIONS","PAY_DASHBOARD_SECTIONS","DASHBOARD_SECTION_LABELS","DEFAULT_DASHBOARD_PERMISSIONS","useDashboardPermissions","activeOrg","useOrgs","permissions","setPermissions","fetchPermissions","message","updatePermissions","newPermissions","canAccess","section","PermissionToggle","checked","onChange","disabled","label","PermissionsSection","userRole","featuresLoading","debounceRef","pendingPermissions","cedrosPayEnabled","debouncedSave","handleToggle","enabled","Fragment"],"mappings":"sWA8BMA,EAA0B,CAAC,QAAS,QAAS,QAAQ,EA2BpD,SAASC,EAAW,CACzB,QAAAC,EACA,cAAAC,EACA,UAAAC,EAAY,GACZ,MAAAC,EACA,UAAAC,EAAY,GACZ,eAAAC,EAAiB,GACjB,aAAAC,EACA,SAAAC,EACA,UAAAC,EAAY,EACd,EAAoB,CAClB,KAAM,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAoB,MAAM,EACtD,CAACC,EAAWC,CAAY,EAAIF,EAAAA,SAAoB,KAAK,EAErDG,EAAcC,GAAqB,CACnCN,IAAcM,EAChBF,EAAaD,IAAc,MAAQ,OAAS,KAAK,GAEjDF,EAAaK,CAAK,EAClBF,EAAa,KAAK,EAEtB,EAEMG,EAAgBC,EAAAA,QAAQ,IAAM,CAClC,MAAMC,EAAqC,CAAE,MAAO,EAAG,MAAO,EAAG,OAAQ,CAAA,EACzE,MAAO,CAAC,GAAGlB,CAAO,EAAE,KAAK,CAACmB,EAAG,IAAM,CACjC,IAAIC,EACAC,EAEJ,OAAQZ,EAAA,CACN,IAAK,OACHW,GAAQD,EAAE,KAAK,MAAQA,EAAE,KAAK,OAAS,IAAI,YAAA,EAC3CE,GAAQ,EAAE,KAAK,MAAQ,EAAE,KAAK,OAAS,IAAI,YAAA,EAC3C,MACF,IAAK,OACHD,EAAOF,EAAUC,EAAE,IAAI,GAAK,GAC5BE,EAAOH,EAAU,EAAE,IAAI,GAAK,GAC5B,MACF,IAAK,WACHE,EAAO,IAAI,KAAKD,EAAE,QAAQ,EAAE,QAAA,EAC5BE,EAAO,IAAI,KAAK,EAAE,QAAQ,EAAE,QAAA,EAC5B,MACF,QACE,MAAO,EAAA,CAGX,OAAID,EAAOC,EAAaT,IAAc,MAAQ,GAAK,EAC/CQ,EAAOC,EAAaT,IAAc,MAAQ,EAAI,GAC3C,CACT,CAAC,CACH,EAAG,CAACZ,EAASS,EAAWG,CAAS,CAAC,EAElC,OAAIV,GAAaF,EAAQ,SAAW,EAEhCsB,EAAAA,KAAC,MAAA,CAAI,UAAW,iDAAiDd,CAAS,GACxE,SAAA,CAAAe,EAAAA,IAACC,EAAAA,eAAA,EAAe,EAChBD,EAAAA,IAAC,QAAK,SAAA,oBAAA,CAAkB,CAAA,EAC1B,EAIApB,EAEAoB,EAAAA,IAAC,OAAI,UAAW,sBAAsBf,CAAS,GAC7C,SAAAe,EAAAA,IAACE,EAAAA,aAAA,CAAa,MAAAtB,CAAA,CAAc,CAAA,CAC9B,EAIAH,EAAQ,SAAW,EAEnBuB,EAAAA,IAAC,OAAI,UAAW,+CAA+Cf,CAAS,GACtE,SAAAe,EAAAA,IAAC,IAAA,CAAE,SAAA,mBAAA,CAAiB,CAAA,CACtB,EAKFA,EAAAA,IAAC,OAAI,UAAW,sBAAsBf,CAAS,GAC7C,SAAAc,EAAAA,KAAC,QAAA,CAAM,UAAU,sBACf,SAAA,CAAAC,EAAAA,IAAC,QAAA,CACC,gBAAC,KAAA,CACC,SAAA,CAAAA,MAAC,KAAA,CACC,SAAAD,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,UAAW,4BAA4Bb,IAAc,OAAS,2BAA6B,EAAE,GAC7F,QAAS,IAAMK,EAAW,MAAM,EACjC,SAAA,CAAA,SACQ,IACPS,EAAAA,IAAC,OAAA,CAAK,UAAU,yBACb,SAAAd,IAAc,OAAUG,IAAc,MAAQ,IAAM,IAAO,GAAA,CAC9D,CAAA,CAAA,CAAA,EAEJ,QACC,KAAA,CACC,SAAAU,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,UAAW,4BAA4Bb,IAAc,OAAS,2BAA6B,EAAE,GAC7F,QAAS,IAAMK,EAAW,MAAM,EACjC,SAAA,CAAA,OACM,IACLS,EAAAA,IAAC,OAAA,CAAK,UAAU,yBACb,SAAAd,IAAc,OAAUG,IAAc,MAAQ,IAAM,IAAO,GAAA,CAC9D,CAAA,CAAA,CAAA,EAEJ,QACC,KAAA,CACC,SAAAU,EAAAA,KAAC,SAAA,CACC,KAAK,SACL,UAAW,4BAA4Bb,IAAc,WAAa,2BAA6B,EAAE,GACjG,QAAS,IAAMK,EAAW,UAAU,EACrC,SAAA,CAAA,SACQ,IACPS,EAAAA,IAAC,OAAA,CAAK,UAAU,yBACb,SAAAd,IAAc,WAAcG,IAAc,MAAQ,IAAM,IAAO,GAAA,CAClE,CAAA,CAAA,CAAA,EAEJ,GACER,GAAaC,IAAmBkB,EAAAA,IAAC,KAAA,CAAG,SAAA,SAAA,CAAO,CAAA,CAAA,CAC/C,CAAA,CACF,EACAA,EAAAA,IAAC,QAAA,CACE,SAAAP,EAAc,IAAKU,GAClBH,EAAAA,IAACI,EAAA,CAEC,OAAAD,EACA,cAAeA,EAAO,SAAWzB,EACjC,UAAAG,EACA,eAAAC,EACA,aAAAC,EACA,SAAAC,CAAA,EANKmB,EAAO,EAAA,CAQf,CAAA,CACH,CAAA,CAAA,CACF,CAAA,CACF,CAEJ,CAYA,SAASC,EAAU,CACjB,OAAAD,EACA,cAAAE,EACA,UAAAxB,EACA,eAAAC,EACA,aAAAC,EACA,SAAAC,CACF,EAAmB,CACjB,KAAM,CAACsB,EAAYC,CAAa,EAAInB,EAAAA,SAAS,EAAK,EAC5C,CAACoB,EAAcC,CAAe,EAAIrB,EAAAA,SAAkBe,EAAO,IAAI,EAE/DO,EAAmBC,EAAAA,YACvB,MAAOC,GAAqB,CAC1B,GAAI,GAAC7B,GAAgB6B,IAAYT,EAAO,MAExC,CAAAI,EAAc,EAAI,EAClB,GAAI,CACF,MAAMxB,EAAaoB,EAAO,OAAQS,CAAO,EACzCH,EAAgBG,CAAO,CACzB,MAAQ,CAENH,EAAgBN,EAAO,IAAI,CAC7B,QAAA,CACEI,EAAc,EAAK,CACrB,EACF,EACA,CAACJ,EAAO,OAAQA,EAAO,KAAMpB,CAAY,CAAA,EAGrC8B,EAAeF,EAAAA,YAAY,SAAY,CAM3C,GALI,GAAC3B,GAKD,CAHc,OAAO,QACvB,mCAAmCmB,EAAO,KAAK,MAAQA,EAAO,KAAK,KAAK,0BAAA,GAI1E,CAAAI,EAAc,EAAI,EAClB,GAAI,CACF,MAAMvB,EAASmB,EAAO,MAAM,CAC9B,QAAA,CACEI,EAAc,EAAK,CACrB,EACF,EAAG,CAACJ,EAAO,OAAQA,EAAO,KAAK,KAAMA,EAAO,KAAK,MAAOnB,CAAQ,CAAC,EAE3D8B,EAAUX,EAAO,OAAS,QAC1BY,EAAY,CAACV,GAAiB,CAACS,EAErC,cACG,KAAA,CAAG,UAAW,qBAAqBT,EAAgB,4BAA8B,EAAE,GAClF,SAAA,CAAAN,EAAAA,KAAC,KAAA,CAAG,UAAU,qBACZ,SAAA,CAAAC,EAAAA,IAACgB,EAAA,CAAa,KAAMb,EAAO,IAAA,CAAM,EACjCJ,EAAAA,KAAC,MAAA,CAAI,UAAU,wBACb,SAAA,CAAAA,EAAAA,KAAC,OAAA,CAAK,UAAU,qBACb,SAAA,CAAAI,EAAO,KAAK,MAAQ,UACpBE,GAAiBL,EAAAA,IAAC,OAAA,CAAK,UAAU,oBAAoB,SAAA,OAAA,CAAK,CAAA,EAC7D,QACC,OAAA,CAAK,UAAU,sBAAuB,SAAAG,EAAO,KAAK,KAAA,CAAM,CAAA,CAAA,CAC3D,CAAA,EACF,QACC,KAAA,CAAG,UAAU,qBACX,SAAArB,GAAkBiC,GAAahC,EAC9BiB,EAAAA,IAAC,SAAA,CACC,MAAOQ,EACP,SAAWS,GAAMP,EAAiBO,EAAE,OAAO,KAAgB,EAC3D,SAAUX,EACV,UAAU,qBAET,WAAa,IAAKY,GACjBlB,EAAAA,IAAC,SAAA,CAAkB,MAAOkB,EACvB,SAAAA,EAAK,OAAO,CAAC,EAAE,cAAgBA,EAAK,MAAM,CAAC,CAAA,EADjCA,CAEb,CACD,CAAA,CAAA,QAGF,OAAA,CAAK,UAAW,uCAAuCf,EAAO,IAAI,GAChE,SAAAA,EAAO,KAAK,OAAO,CAAC,EAAE,cAAgBA,EAAO,KAAK,MAAM,CAAC,EAC5D,EAEJ,QACC,KAAA,CAAG,UAAU,uBAAwB,SAAAgB,EAAWhB,EAAO,QAAQ,EAAE,GAChEtB,GAAaC,IACbkB,EAAAA,IAAC,KAAA,CAAG,UAAU,wBACX,SAAAnB,GAAakC,GAAa/B,GACzBgB,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,UAAU,sDACV,QAASa,EACT,SAAUP,EACV,aAAY,UAAUH,EAAO,KAAK,MAAQA,EAAO,KAAK,KAAK,GAE1D,SAAAG,EAAaN,MAACC,EAAAA,eAAA,CAAe,KAAK,KAAK,EAAK,QAAA,CAAA,CAC/C,CAEJ,CAAA,EAEJ,CAEJ,CAGA,SAASe,EAAa,CAAE,KAAAI,GAAkC,CAExD,MAAMC,EAAmBC,EAAAA,iBAAiBF,EAAK,OAAO,EACtD,GAAIC,EACF,OACErB,EAAAA,IAAC,MAAA,CACC,IAAKqB,EACL,IAAKD,EAAK,MAAQA,EAAK,OAAS,SAChC,UAAU,uBACV,eAAe,aAAA,CAAA,EAKrB,MAAMG,GAAWH,EAAK,OAAO,CAAC,GAAKA,EAAK,QAAQ,CAAC,GAAK,KAAK,YAAA,EAC3D,OAAOpB,EAAAA,IAAC,MAAA,CAAI,UAAU,mCAAoC,SAAAuB,EAAQ,CACpE,CAEA,SAASJ,EAAWK,EAA4B,CAE9C,OADa,IAAI,KAAKA,CAAU,EACpB,mBAAmB,OAAW,CACxC,KAAM,UACN,MAAO,QACP,IAAK,SAAA,CACN,CACH,CCrTA,MAAMC,EAA8B,CAAC,QAAS,QAAQ,EAsB/C,SAASC,EAAW,CACzB,SAAAC,EACA,UAAAhD,EAAY,GACZ,MAAAC,EACA,eAAAgD,EAAiBH,EACjB,YAAAI,EAAc,SACd,UAAA5C,EAAY,EACd,EAAoB,CAClB,KAAM,CAAC6C,EAAOC,CAAQ,EAAI3C,EAAAA,SAAS,EAAE,EAC/B,CAAC8B,EAAMc,CAAO,EAAI5C,EAAAA,SAAqByC,CAAW,EAClD,CAACI,EAAWC,CAAY,EAAI9C,EAAAA,SAAwB,IAAI,EACxD,CAAC+C,EAASC,CAAU,EAAIhD,EAAAA,SAAS,EAAK,EACtCiD,EAAkBC,EAAAA,OAAsB,IAAI,EAE5CC,EAAeD,EAAAA,OAAO,EAAI,EAEhCE,EAAAA,UAAU,KACRD,EAAa,QAAU,GAChB,IAAM,CACXA,EAAa,QAAU,GACnBF,EAAgB,UAAY,OAC9B,OAAO,aAAaA,EAAgB,OAAO,EAC3CA,EAAgB,QAAU,KAE9B,GACC,CAAA,CAAE,EAEL,MAAMI,EAAe9B,EAAAA,YACnB,MAAOM,GAAuB,CAC5BA,EAAE,eAAA,EACFiB,EAAa,IAAI,EACjBE,EAAW,EAAK,EAEhB,MAAMM,EAAeZ,EAAM,KAAA,EAE3B,GAAI,CAACY,EAAc,CACjBR,EAAa,mBAAmB,EAChC,MACF,CAEA,GAAI,CAACS,EAAAA,cAAcD,CAAY,EAAG,CAChCR,EAAa,oCAAoC,EACjD,MACF,CAEA,GAAI,CACF,MAAMP,EAASe,EAAcxB,CAAI,EACjCa,EAAS,EAAE,EACXC,EAAQH,CAAW,EACnBO,EAAW,EAAI,EAEXC,EAAgB,UAAY,MAC9B,OAAO,aAAaA,EAAgB,OAAO,EAE7CA,EAAgB,QAAU,OAAO,WAAW,IAAM,CAE5CE,EAAa,SACfH,EAAW,EAAK,EAElBC,EAAgB,QAAU,IAC5B,EAAG,GAAI,CACT,MAAQ,CAIR,CACF,EACA,CAACP,EAAOZ,EAAMW,EAAaF,CAAQ,CAAA,EAGrC,cACG,OAAA,CAAK,UAAW,sBAAsB1C,CAAS,GAAI,SAAUwD,EAC1D,SAAA,EAAA7D,GAASqD,IAAcjC,EAAAA,IAACE,EAAAA,aAAA,CAAa,MAAO+B,GAAarD,GAAS,KAAM,EAEzEuD,GACCpC,EAAAA,KAAC,MAAA,CAAI,UAAU,wBAAwB,KAAK,SAC1C,SAAA,CAAAC,EAAAA,IAAC4C,EAAA,EAAU,EACX5C,EAAAA,IAAC,QAAK,SAAA,+BAAA,CAA6B,CAAA,EACrC,EAGFD,EAAAA,KAAC,MAAA,CAAI,UAAU,yBACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,8CACb,SAAA,CAAAC,MAAC,QAAA,CAAM,QAAQ,eAAe,UAAU,oBAAoB,SAAA,gBAE5D,EACAA,EAAAA,IAAC,QAAA,CACC,GAAG,eACH,KAAK,QACL,UAAU,oBACV,MAAO8B,EACP,SAAWb,GAAMc,EAASd,EAAE,OAAO,KAAK,EACxC,YAAY,wBACZ,SAAUtC,EACV,aAAa,OAAA,CAAA,CACf,EACF,EAEAoB,EAAAA,KAAC,MAAA,CAAI,UAAU,6CACb,SAAA,CAAAC,MAAC,QAAA,CAAM,QAAQ,cAAc,UAAU,oBAAoB,SAAA,OAE3D,EACAA,EAAAA,IAAC,SAAA,CACC,GAAG,cACH,UAAU,qBACV,MAAOkB,EACP,SAAWD,GAAMe,EAAQf,EAAE,OAAO,KAAmB,EACrD,SAAUtC,EAET,WAAe,IAAKkE,GACnB7C,EAAAA,IAAC,SAAA,CAAe,MAAO6C,EACpB,SAAAA,EAAE,OAAO,CAAC,EAAE,cAAgBA,EAAE,MAAM,CAAC,CAAA,EAD3BA,CAEb,CACD,CAAA,CAAA,CACH,EACF,EAEA7C,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,UAAU,2DACV,SAAUrB,GAAa,CAACmD,EAAM,KAAA,EAE7B,SAAAnD,EAAYqB,MAACC,EAAAA,eAAA,CAAe,KAAK,KAAK,EAAK,aAAA,CAAA,CAC9C,EACF,EAEAD,EAAAA,IAAC,IAAA,CAAE,UAAU,mBAAmB,SAAA,+EAAA,CAEhC,CAAA,EACF,CAEJ,CAEA,SAAS4C,GAAY,CACnB,OACE5C,EAAAA,IAAC,MAAA,CACC,UAAU,sBACV,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,MAAM,6BAEN,SAAAA,EAAAA,IAAC,OAAA,CACC,EAAE,iBACF,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,OAAA,CAAA,CACjB,CAAA,CAGN,CCzJO,SAAS8C,EAAW,CACzB,QAAAC,EACA,UAAApE,EAAY,GACZ,MAAAC,EACA,UAAAC,EAAY,GACZ,SAAAmE,EACA,SAAAC,EACA,UAAAhE,EAAY,EACd,EAAoB,CAClB,OAAIN,GAAaoE,EAAQ,SAAW,EAEhChD,EAAAA,KAAC,MAAA,CAAI,UAAW,iDAAiDd,CAAS,GACxE,SAAA,CAAAe,EAAAA,IAACC,EAAAA,eAAA,EAAe,EAChBD,EAAAA,IAAC,QAAK,SAAA,oBAAA,CAAkB,CAAA,EAC1B,EAIApB,EAEAoB,EAAAA,IAAC,OAAI,UAAW,sBAAsBf,CAAS,GAC7C,SAAAe,EAAAA,IAACE,EAAAA,aAAA,CAAa,MAAAtB,CAAA,CAAc,CAAA,CAC9B,EAIAmE,EAAQ,SAAW,EAEnB/C,EAAAA,IAAC,OAAI,UAAW,+CAA+Cf,CAAS,GACtE,SAAAe,EAAAA,IAAC,IAAA,CAAE,SAAA,qBAAA,CAAmB,CAAA,CACxB,EAKFA,EAAAA,IAAC,MAAA,CAAI,UAAW,sBAAsBf,CAAS,GAC7C,SAAAe,EAAAA,IAAC,KAAA,CAAG,UAAU,sBACX,SAAA+C,EAAQ,IAAKG,GACZlD,EAAAA,IAACmD,EAAA,CAEC,OAAAD,EACA,UAAArE,EACA,SAAAmE,EACA,SAAAC,CAAA,EAJKC,EAAO,EAAA,CAMf,EACH,CAAA,CACF,CAEJ,CAUA,SAASC,EAAW,CAAE,OAAAD,EAAQ,UAAArE,EAAW,SAAAmE,EAAU,SAAAC,GAA6B,CAC9E,KAAM,CAAC3C,EAAYC,CAAa,EAAInB,EAAAA,SAAS,EAAK,EAC5C,CAACgE,EAAeC,CAAgB,EAAIjE,EAAAA,SAAS,EAAK,EAClDkE,EAAiBhB,EAAAA,OAAsB,IAAI,EAE3CiB,EAAY,IAAI,KAAKL,EAAO,SAAS,MAAQ,KAE7CM,EAAe7C,EAAAA,YAAY,SAAY,CAM3C,GALI,GAACqC,GAKD,CAHc,OAAO,QACvB,kDAAkDE,EAAO,KAAK,GAAA,GAIhE,CAAA3C,EAAc,EAAI,EAClB,GAAI,CACF,MAAMyC,EAASE,EAAO,EAAE,CAC1B,QAAA,CACE3C,EAAc,EAAK,CACrB,EACF,EAAG,CAAC2C,EAAO,GAAIA,EAAO,MAAOF,CAAQ,CAAC,EAEhCS,EAAe9C,EAAAA,YAAY,SAAY,CAC3C,GAAKsC,EAEL,CAAA1C,EAAc,EAAI,EAClB8C,EAAiB,EAAK,EACtB,GAAI,CACF,MAAMJ,EAASC,EAAO,EAAE,EACxBG,EAAiB,EAAI,EACjBC,EAAe,UAAY,MAC7B,OAAO,aAAaA,EAAe,OAAO,EAE5CA,EAAe,QAAU,OAAO,WAAW,IAAM,CAC/CD,EAAiB,EAAK,EACtBC,EAAe,QAAU,IAC3B,EAAG,GAAI,CACT,QAAA,CACE/C,EAAc,EAAK,CACrB,EACF,EAAG,CAAC2C,EAAO,GAAID,CAAQ,CAAC,EAExBT,OAAAA,EAAAA,UAAU,IACD,IAAM,CACPc,EAAe,UAAY,OAC7B,OAAO,aAAaA,EAAe,OAAO,EAC1CA,EAAe,QAAU,KAE7B,EACC,CAAA,CAAE,SAGF,KAAA,CAAG,UAAW,sBAAsBC,EAAY,6BAA+B,EAAE,GAChF,SAAA,CAAAxD,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,2BAA4B,SAAAkD,EAAO,MAAM,QACxD,OAAA,CAAK,UAAW,uCAAuCA,EAAO,IAAI,GAChE,SAAAA,EAAO,KAAK,OAAO,CAAC,EAAE,YAAA,EAAgBA,EAAO,KAAK,MAAM,CAAC,EAC5D,EACCK,GAAavD,EAAAA,IAAC,OAAA,CAAK,UAAU,8BAA8B,SAAA,SAAA,CAAO,CAAA,EACrE,EACAD,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAA,EAAAA,KAAC,OAAA,CAAK,UAAU,0BACsD,SAAA,CAAA,WAC3DoB,EAAW+B,EAAO,SAAS,CAAA,EACtC,EACC,CAACK,GACAxD,EAAAA,KAAC,OAAA,CAAK,UAAU,6BAA6B,SAAA,CAAA,WAClC2D,EAAmBR,EAAO,SAAS,CAAA,CAAA,CAC9C,CAAA,CAAA,CAEJ,CAAA,EACF,EAECrE,GACCkB,EAAAA,KAAC,MAAA,CAAI,UAAU,6BACZ,SAAA,CAAAqD,GAAiBpD,EAAAA,IAAC,OAAA,CAAK,UAAU,+BAA+B,SAAA,QAAK,EACrEiD,GAAY,CAACM,GACZvD,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,UAAU,uDACV,QAASyD,EACT,SAAUnD,EACV,aAAY,oBAAoB4C,EAAO,KAAK,GAE3C,SAAA5C,EAAaN,MAACC,EAAAA,eAAA,CAAe,KAAK,KAAK,EAAK,QAAA,CAAA,EAGhD+C,GACChD,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,UAAU,sDACV,QAASwD,EACT,SAAUlD,EACV,aAAY,qBAAqB4C,EAAO,KAAK,GAC9C,SAAA,QAAA,CAAA,CAED,CAAA,CAEJ,CAAA,EAEJ,CAEJ,CAGA,SAAS/B,EAAWK,EAA4B,CAE9C,OADa,IAAI,KAAKA,CAAU,EACpB,mBAAmB,OAAW,CACxC,KAAM,UACN,MAAO,QACP,IAAK,SAAA,CACN,CACH,CAEA,SAASkC,EAAmBlC,EAA4B,CACtD,MAAMmC,EAAO,IAAI,KAAKnC,CAAU,EAC1BoC,MAAU,KACVC,EAASF,EAAK,QAAA,EAAYC,EAAI,QAAA,EAC9BE,EAAW,KAAK,KAAKD,GAAU,IAAO,GAAK,GAAK,GAAG,EAEzD,OAAIC,EAAW,EACN,UACEA,IAAa,EACf,QACEA,IAAa,EACf,WACEA,EAAW,EACb,MAAMA,CAAQ,QAEd3C,EAAWK,CAAU,CAEhC,CCjOO,MAAMuC,CAAgB,CACnB,OAER,YACEC,EACAC,EACAC,EACAC,EACA,CACA,KAAK,OAAS,IAAIC,YAAU,CAAE,QAAAJ,EAAS,UAAAC,EAAW,cAAAC,EAAe,eAAAC,EAAgB,CACnF,CAKA,MAAM,YACJE,EACAC,EAAgB,GAChBC,EAAiB,EAC8B,CAC/C,GAAI,CACF,MAAMC,EAAW,MAAM,KAAK,OAAO,IACjC,SAASH,CAAK,kBAAkBC,CAAK,WAAWC,CAAM,EAAA,EAExD,MAAO,CACL,QAASC,EAAS,QAAQ,IAAKrE,IAA+B,CAC5D,GAAIA,EAAO,GACX,OAAQA,EAAO,OACf,MAAAkE,EACA,KAAMlE,EAAO,KACb,SAAUA,EAAO,SACjB,KAAM,CACJ,GAAIA,EAAO,OACX,MAAOA,EAAO,MACd,KAAMA,EAAO,IAAA,CACf,EACA,EACF,MAAOqE,EAAS,KAAA,CAEpB,OAAS5F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,wBAAwB,CACtD,CACF,CAKA,MAAM,iBACJyF,EACAK,EACAC,EACiB,CACjB,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,MAAc,SAASN,CAAK,YAAYK,CAAM,GAAIC,CAAI,CACjF,OAAS/F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,8BAA8B,CAC5D,CACF,CAKA,MAAM,aAAayF,EAAeK,EAA+B,CAC/D,GAAI,CACF,MAAM,KAAK,OAAO,OAAa,SAASL,CAAK,YAAYK,CAAM,EAAE,CACnE,OAAS9F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,yBAAyB,CACvD,CACF,CACF,CC3BO,SAASgG,EAAWP,EAA6C,CACtE,KAAM,CAAE,OAAAQ,EAAQ,UAAAC,EAAW,UAAAC,CAAA,EAAcC,EAAAA,eAAA,EAEnC,CAACvG,EAASwG,CAAU,EAAI7F,EAAAA,SAAmB,CAAA,CAAE,EAC7C,CAAC8F,EAAOC,CAAQ,EAAI/F,EAAAA,SAAS,CAAC,EAC9B,CAACT,EAAWyG,CAAY,EAAIhG,EAAAA,SAAS,EAAK,EAC1C,CAACR,EAAOyG,CAAQ,EAAIjG,EAAAA,SAA2B,IAAI,EAEnDkG,EAAkBhD,EAAAA,OAA2B,MAAS,EACtDiD,EAAejD,EAAAA,OAAO,CAAC,EAEvBkD,EAAY9F,EAAAA,QAChB,IACE,IAAIqE,EACFc,EAAO,UACPA,EAAO,eACPA,EAAO,cACPE,GAAW,cAAA,EAEf,CAACF,EAAO,UAAWA,EAAO,eAAgBA,EAAO,cAAeE,CAAS,CAAA,EAKrEU,EAAenD,EAAAA,OAAOkD,CAAS,EACrCC,EAAa,QAAUD,EAEvB,MAAME,EAAe/E,EAAAA,YACnB,MAAOgF,GAAkD,CACvD,GAAI,CAACtB,GAASS,IAAc,gBAAiB,CAC3CG,EAAW,CAAA,CAAE,EACbE,EAAS,CAAC,EACV,MACF,CAEAC,EAAa,EAAI,EACjBC,EAAS,IAAI,EACb,MAAMO,EAAY,EAAEL,EAAa,QAEjC,GAAI,CACF,KAAM,CAAE,MAAAjB,EAAQ,GAAI,OAAAC,EAAS,CAAA,EAAMoB,GAAW,CAAA,EACxCnB,EAAW,MAAMiB,EAAa,QAAQ,YAAYpB,EAAOC,EAAOC,CAAM,EAC5E,GAAIqB,IAAcL,EAAa,QAAS,OACxCN,EAAWT,EAAS,OAAO,EAC3BW,EAASX,EAAS,KAAK,CACzB,OAASqB,EAAK,CACZ,GAAID,IAAcL,EAAa,QAAS,OACxCF,EAASQ,CAAgB,CAC3B,QAAA,CACMD,IAAcL,EAAa,SAC7BH,EAAa,EAAK,CAEtB,CACF,EACA,CAACf,EAAOS,CAAS,CAAA,EAInBtC,EAAAA,UAAU,IAAM,CACd,GAAIsC,IAAc,gBAAiB,CACjCQ,EAAgB,QAAU,OAC1B,MACF,CAEIjB,IAAUiB,EAAgB,UAC5BA,EAAgB,QAAUjB,EAC1BqB,EAAA,EAEJ,EAAG,CAACrB,EAAOS,EAAWY,CAAY,CAAC,EAEnC,MAAMI,EAAmBnF,EAAAA,YACvB,MAAO+D,EAAgBxD,IAAiC,CACtD,GAAI,CAACmD,EACH,MAAM,IAAI,MAAM,0BAA0B,EAG5Ce,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,iBAAiBpB,EAAOK,EAAQ,CAAE,KAAAxD,EAAM,EAEnE,MAAMwE,EAAA,CACR,OAASG,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EACA,CAACf,EAAOqB,CAAY,CAAA,EAGhBK,EAAepF,EAAAA,YACnB,MAAO+D,GAAkC,CACvC,GAAI,CAACL,EACH,MAAM,IAAI,MAAM,0BAA0B,EAG5Ce,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,aAAapB,EAAOK,CAAM,EAErD,MAAMgB,EAAA,CACR,OAASG,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EACA,CAACf,EAAOqB,CAAY,CAAA,EAGtB,MAAO,CACL,QAAAjH,EACA,MAAAyG,EACA,UAAAvG,EACA,MAAAC,EACA,aAAA8G,EACA,iBAAAI,EACA,aAAAC,CAAA,CAEJ,CCpKO,MAAMC,EAAgB,CACnB,OAER,YACEhC,EACAC,EACAC,EACAC,EACA,CACA,KAAK,OAAS,IAAIC,YAAU,CAAE,QAAAJ,EAAS,UAAAC,EAAW,cAAAC,EAAe,eAAAC,EAAgB,CACnF,CAKA,MAAM,YACJE,EACAC,EAAgB,GAChBC,EAAiB,EAC8B,CAC/C,GAAI,CACF,MAAMC,EAAW,MAAM,KAAK,OAAO,IACjC,SAASH,CAAK,kBAAkBC,CAAK,WAAWC,CAAM,EAAA,EAExD,MAAO,CACL,QAASC,EAAS,QAAQ,IAAKtB,IAA+B,CAC5D,GAAIA,EAAO,GACX,MAAOA,EAAO,MACd,MAAOA,EAAO,MACd,KAAMA,EAAO,KACb,UAAWA,EAAO,UAClB,UAAWA,EAAO,UAClB,UAAWA,EAAO,SAAA,EAClB,EACF,MAAOsB,EAAS,KAAA,CAEpB,OAAS5F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,wBAAwB,CACtD,CACF,CAKA,MAAM,aAAayF,EAAeM,EAA0D,CAC1F,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,KAA2B,SAASN,CAAK,WAAYM,CAAI,CACpF,OAAS/F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,yBAAyB,CACvD,CACF,CAKA,MAAM,aAAayF,EAAe4B,EAAiC,CACjE,GAAI,CACF,MAAM,KAAK,OAAO,OAAa,SAAS5B,CAAK,YAAY4B,CAAQ,EAAE,CACrE,OAASrH,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,yBAAyB,CACvD,CACF,CAKA,MAAM,aAAayF,EAAe4B,EAAiC,CACjE,GAAI,CACF,MAAM,KAAK,OAAO,KAAW,SAAS5B,CAAK,YAAY4B,CAAQ,UAAW,EAAE,CAC9E,OAASrH,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,yBAAyB,CACvD,CACF,CAKA,MAAM,aAAa+F,EAA0D,CAC3E,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,KAA2B,kBAAmBA,CAAI,CAC7E,OAAS/F,EAAO,CACd,MAAM6F,EAAAA,eAAe7F,EAAO,yBAAyB,CACvD,CACF,CACF,CCxCO,SAASsH,GAAW7B,EAA6C,CACtE,KAAM,CAAE,OAAAQ,EAAQ,UAAAC,EAAW,UAAAC,CAAA,EAAcC,EAAAA,eAAA,EAEnC,CAACjC,EAASoD,CAAU,EAAI/G,EAAAA,SAAmB,CAAA,CAAE,EAC7C,CAAC8F,EAAOC,CAAQ,EAAI/F,EAAAA,SAAS,CAAC,EAC9B,CAACT,EAAWyG,CAAY,EAAIhG,EAAAA,SAAS,EAAK,EAC1C,CAACR,EAAOyG,CAAQ,EAAIjG,EAAAA,SAA2B,IAAI,EAEnDkG,EAAkBhD,EAAAA,OAA2B,MAAS,EACtDiD,EAAejD,EAAAA,OAAO,CAAC,EAEvBkD,EAAY9F,EAAAA,QAChB,IACE,IAAIsG,GACFnB,EAAO,UACPA,EAAO,eACPA,EAAO,cACPE,GAAW,cAAA,EAEf,CAACF,EAAO,UAAWA,EAAO,eAAgBA,EAAO,cAAeE,CAAS,CAAA,EAKrEU,EAAenD,EAAAA,OAAOkD,CAAS,EACrCC,EAAa,QAAUD,EAEvB,MAAMY,EAAezF,EAAAA,YACnB,MAAOgF,GAAkD,CACvD,GAAI,CAACtB,GAASS,IAAc,gBAAiB,CAC3CqB,EAAW,CAAA,CAAE,EACbhB,EAAS,CAAC,EACV,MACF,CAEAC,EAAa,EAAI,EACjBC,EAAS,IAAI,EACb,MAAMO,EAAY,EAAEL,EAAa,QAEjC,GAAI,CACF,KAAM,CAAE,MAAAjB,EAAQ,GAAI,OAAAC,EAAS,CAAA,EAAMoB,GAAW,CAAA,EACxCnB,EAAW,MAAMiB,EAAa,QAAQ,YAAYpB,EAAOC,EAAOC,CAAM,EAC5E,GAAIqB,IAAcL,EAAa,QAAS,OACxCY,EAAW3B,EAAS,OAAO,EAC3BW,EAASX,EAAS,KAAK,CACzB,OAASqB,EAAK,CACZ,GAAID,IAAcL,EAAa,QAAS,OACxCF,EAASQ,CAAgB,CAC3B,QAAA,CACMD,IAAcL,EAAa,SAC7BH,EAAa,EAAK,CAEtB,CACF,EACA,CAACf,EAAOS,CAAS,CAAA,EAInBtC,EAAAA,UAAU,IAAM,CACd,GAAIsC,IAAc,gBAAiB,CACjCQ,EAAgB,QAAU,OAC1B,MACF,CAEIjB,IAAUiB,EAAgB,UAC5BA,EAAgB,QAAUjB,EAC1B+B,EAAA,EAEJ,EAAG,CAAC/B,EAAOS,EAAWsB,CAAY,CAAC,EAEnC,MAAMC,EAAe1F,EAAAA,YACnB,MAAOmB,EAAeZ,EAAkC,WAA4B,CAClF,GAAI,CAACmD,EACH,MAAM,IAAI,MAAM,0BAA0B,EAG5Ce,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,aAAapB,EAAO,CAAE,MAAAvC,EAAO,KAAAZ,EAAM,EAE9D,MAAMkF,EAAA,CACR,OAASP,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EACA,CAACf,EAAO+B,CAAY,CAAA,EAGhBE,EAAe3F,EAAAA,YACnB,MAAOsF,GAAoC,CACzC,GAAI,CAAC5B,EACH,MAAM,IAAI,MAAM,0BAA0B,EAG5Ce,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,aAAapB,EAAO4B,CAAQ,EAEvD,MAAMG,EAAA,CACR,OAASP,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EACA,CAACf,EAAO+B,CAAY,CAAA,EAGhBG,EAAe5F,EAAAA,YACnB,MAAOsF,GAAoC,CACzC,GAAI,CAAC5B,EACH,MAAM,IAAI,MAAM,0BAA0B,EAG5Ce,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,aAAapB,EAAO4B,CAAQ,CACzD,OAASJ,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EACA,CAACf,CAAK,CAAA,EAGFmC,EAAe7F,cAAY,MAAO8F,GAAiD,CACvFrB,EAAa,EAAI,EACjBC,EAAS,IAAI,EAEb,GAAI,CACF,OAAO,MAAMI,EAAa,QAAQ,aAAa,CAAE,MAAAgB,EAAO,CAC1D,OAASZ,EAAK,CACZ,MAAAR,EAASQ,CAAgB,EACnBA,CACR,QAAA,CACET,EAAa,EAAK,CACpB,CACF,EAAG,CAAA,CAAE,EAEL,MAAO,CACL,QAAArC,EACA,MAAAmC,EACA,UAAAvG,EACA,MAAAC,EACA,aAAAwH,EACA,aAAAC,EACA,aAAAC,EACA,aAAAC,EACA,aAAAC,CAAA,CAEJ,CC1JA,MAAME,GAAmC,CACvC,cAAe,GACf,IAAK,GACL,IAAK,GACL,cAAe,GACf,QAAS,GACT,gBAAiB,GACjB,UAAW,EACb,EAoCO,SAASC,GAA6C,CAC3D,KAAM,CAAE,SAAAC,EAAU,UAAAjI,EAAW,MAAAC,EAAO,cAAAiI,EAAe,SAAAC,CAAA,EAAaC,oBAAA,EAC1D,CAACC,EAAYC,CAAa,EAAI7H,EAAAA,SAAS,EAAK,EAGlDoD,EAAAA,UAAU,IAAM,CACTwE,IACHH,EAAA,EACAI,EAAc,EAAI,EAEtB,EAAG,CAACJ,EAAeG,CAAU,CAAC,EAG9B,MAAME,EAAevG,cAAawG,GAC5BA,IAAU,OAAkB,GACzBA,IAAU,QAAUA,IAAU,IACpC,CAAA,CAAE,EAGCC,EAAW1H,EAAAA,QAAwB,IAEnC,OAAO,KAAKkH,CAAQ,EAAE,SAAW,EAC5BF,GAGF,CACL,cAAeQ,EAAaJ,EAAS,uBAAuB,CAAC,EAC7D,IAAKI,EAAaJ,EAAS,aAAa,CAAC,EACzC,IAAKI,EAAaJ,EAAS,aAAa,CAAC,EACzC,cAAeI,EAAaJ,EAAS,wBAAwB,CAAC,EAC9D,QAASI,EAAaJ,EAAS,iBAAiB,CAAC,EACjD,gBAAiBI,EAAaJ,EAAS,0BAA0B,CAAC,EAClE,UAAWI,EAAaJ,EAAS,oBAAoB,CAAC,CAAA,EAEvD,CAACF,EAAUE,EAAUI,CAAY,CAAC,EAE/BG,EAAU1G,EAAAA,YAAY,SAAY,CACtC,MAAMkG,EAAA,CACR,EAAG,CAACA,CAAa,CAAC,EAEZS,EAAY3G,EAAAA,YACf4G,GACQH,EAASG,CAAO,EAEzB,CAACH,CAAQ,CAAA,EAGX,MAAO,CACL,SAAAA,EACA,UAAAzI,EACA,MAAAC,EACA,QAAAyI,EACA,UAAAC,CAAA,CAEJ,CCTO,MAAME,GAA+C,CAC1D,QACA,OACA,WACA,cACA,kBACA,gBACA,qBACA,mBACA,iBACF,EAKaC,GAA6C,CACxD,eACA,oBACA,mBACA,cACA,cACA,iBACA,SACA,cACA,gBACA,cACF,EAaaC,EAA6D,CAExE,MAAO,QACP,KAAM,OACN,SAAU,WACV,YAAa,cACb,kBAAmB,kBACnB,gBAAiB,gBACjB,qBAAsB,oBACtB,mBAAoB,mBACpB,kBAAmB,kBAEnB,eAAgB,WAChB,oBAAqB,gBACrB,mBAAoB,eACpB,cAAe,UACf,cAAe,UACf,iBAAkB,aAClB,SAAU,WACV,cAAe,kBACf,gBAAiB,iBACjB,eAAgB,cAClB,EAcaC,EAAsD,CACjE,MAAO,CAEL,MAAO,GACP,KAAM,GACN,SAAU,GACV,YAAa,GACb,kBAAmB,GACnB,gBAAiB,GACjB,qBAAsB,GACtB,mBAAoB,GACpB,kBAAmB,GAEnB,eAAgB,GAChB,oBAAqB,GACrB,mBAAoB,GACpB,cAAe,GACf,cAAe,GACf,iBAAkB,GAClB,SAAU,GACV,cAAe,GACf,gBAAiB,GACjB,eAAgB,EAAA,EAElB,OAAQ,CAEN,MAAO,GACP,KAAM,GACN,SAAU,GACV,YAAa,GACb,kBAAmB,GACnB,gBAAiB,GACjB,qBAAsB,GACtB,mBAAoB,GACpB,kBAAmB,GAEnB,eAAgB,GAChB,oBAAqB,GACrB,mBAAoB,GACpB,cAAe,GACf,cAAe,GACf,iBAAkB,GAClB,SAAU,GACV,cAAe,GACf,gBAAiB,GACjB,eAAgB,EAAA,CAEpB,EClOO,SAASC,GAAyD,CACvE,KAAM,CAAE,OAAA/C,EAAQ,UAAAC,EAAW,UAAAC,CAAA,EAAcC,EAAAA,eAAA,EACnC,CAAE,UAAA6C,EAAW,KAAA3G,CAAA,EAAS4G,UAAA,EAEtB,CAACC,EAAaC,CAAc,EAAI5I,EAAAA,SACpCuI,CAAA,EAEI,CAAChJ,EAAWyG,CAAY,EAAIhG,EAAAA,SAAS,EAAK,EAC1C,CAACkB,EAAYC,CAAa,EAAInB,EAAAA,SAAS,EAAK,EAC5C,CAACR,EAAOyG,CAAQ,EAAIjG,EAAAA,SAA2B,IAAI,EACnDmG,EAAejD,EAAAA,OAAO,CAAC,EAEvBkD,EAAY9F,EAAAA,QAChB,IACE,IAAI0E,EAAAA,UAAU,CACZ,QAASS,EAAO,UAChB,UAAWA,EAAO,eAClB,cAAeA,EAAO,cACtB,eAAgBE,GAAW,cAAA,CAC5B,EACH,CAACF,EAAO,UAAWA,EAAO,eAAgBA,EAAO,cAAeE,CAAS,CAAA,EAIrEU,EAAenD,EAAAA,OAAOkD,CAAS,EACrCC,EAAa,QAAUD,EAEvB,MAAMyC,EAAmBtH,EAAAA,YAAY,SAAY,CAC/C,GAAImE,IAAc,iBAAmB,CAAC+C,EAAW,CAC/CG,EAAeL,CAA6B,EAC5C,MACF,CAEAvC,EAAa,EAAI,EACjBC,EAAS,IAAI,EACb,MAAMO,EAAY,EAAEL,EAAa,QAEjC,GAAI,CACF,MAAMf,EAAW,MAAMiB,EAAa,QAAQ,IAC1C,8BAAA,EAEF,GAAIG,IAAcL,EAAa,QAAS,OACxCyC,EAAexD,EAAS,WAAW,CACrC,OAASqB,EAAK,CACZ,GAAID,IAAcL,EAAa,QAAS,OAExC,GAAIM,aAAe,OAASA,EAAI,QAAQ,SAAS,KAAK,EACpDmC,EAAeL,CAA6B,MACvC,CACL,MAAMO,EAAUrC,aAAe,MAAQA,EAAI,QAAU,8BACrDR,EAAS,CAAE,KAAM,gBAAiB,QAAA6C,CAAA,CAAsB,EAExDF,EAAeL,CAA6B,CAC9C,CACF,QAAA,CACM/B,IAAcL,EAAa,SAC7BH,EAAa,EAAK,CAEtB,CACF,EAAG,CAACN,EAAW+C,CAAS,CAAC,EAEnBM,EAAoBxH,EAAAA,YACxB,MAAOyH,GAAwD,CAC7D,GAAItD,IAAc,iBAAmB,CAAC+C,EACpC,MAAM,IAAI,MAAM,mBAAmB,EAGrC,GAAI3G,IAAS,QACX,MAAM,IAAI,MAAM,8CAA8C,EAGhEX,EAAc,EAAI,EAClB8E,EAAS,IAAI,EAEb,GAAI,CACF,MAAMI,EAAa,QAAQ,QAA+C,CACxE,OAAQ,MACR,KAAM,+BACN,KAAM2C,CAAA,CACP,EACDJ,EAAeI,CAAc,CAC/B,OAASvC,EAAK,CACZ,MAAMqC,EAAUrC,aAAe,MAAQA,EAAI,QAAU,+BAErD,MAAAR,EADkB,CAAE,KAAM,gBAAiB,QAAA6C,CAAA,CACzB,EACZ,IAAI,MAAMA,CAAO,CACzB,QAAA,CACE3H,EAAc,EAAK,CACrB,CACF,EACA,CAACuE,EAAW+C,EAAW3G,CAAI,CAAA,EAUvBmH,EAAY1H,EAAAA,YACf2H,GAGK,CAACT,GAAa,CAAC3G,GAKfA,IAAS,QACJ,GAIe6G,EAAY7G,CAA0B,IACrCoH,CAAO,GAAK,GAEvC,CAACT,EAAW3G,EAAM6G,CAAW,CAAA,EAI/BvF,OAAAA,EAAAA,UAAU,IAAM,CACVqF,GAAW,IACbI,EAAA,CAEJ,EAAG,CAACJ,GAAW,GAAII,CAAgB,CAAC,EAE7B,CACL,YAAAF,EACA,UAAAM,EACA,kBAAAF,EACA,UAAAxJ,EACA,WAAA2B,EACA,MAAA1B,EACA,iBAAAqJ,CAAA,CAEJ,CClKA,SAASM,EAAiB,CAAE,QAAAC,EAAS,SAAAC,EAAU,SAAAC,EAAU,MAAAC,GAAgC,CACvF,OACE3I,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,KAAK,SACL,eAAcwI,EACd,aAAYG,EACZ,SAAAD,EACA,UAAW,kCAAkCF,EAAU,mBAAqB,mBAAmB,IAAIE,EAAW,yBAA2B,EAAE,GAC3I,QAAS,IAAM,CAACA,GAAYD,EAAS,CAACD,CAAO,EAE7C,SAAAxI,EAAAA,IAAC,QAAK,UAAU,sBACd,eAAC,OAAA,CAAK,UAAU,sBAAsB,CAAA,CACxC,CAAA,CAAA,CAGN,CAOO,SAAS4I,GAAmB,CAAE,SAAAC,GAAwD,CAC3F,KAAM,CAAE,YAAAd,EAAa,kBAAAI,EAAmB,UAAAxJ,EAAW,WAAA2B,EAAY,MAAA1B,CAAA,EAC7DgJ,EAAA,EACI,CAAE,SAAAR,EAAU,UAAW0B,CAAA,EAAoBnC,EAAA,EAC3CoC,EAAczG,EAAAA,OAA6C,IAAI,EAC/D0G,EAAqB1G,EAAAA,OAAoC,IAAI,EAE7DxB,EAAU+H,IAAa,QACvBI,EAAmB7B,EAAS,UAG5B8B,EAAgBvI,EAAAA,YACnByH,GAAyC,CACxCY,EAAmB,QAAUZ,EACzBW,EAAY,SACd,aAAaA,EAAY,OAAO,EAElCA,EAAY,QAAU,WAAW,IAAM,CACjCC,EAAmB,UACrBb,EAAkBa,EAAmB,OAAO,EAAE,MAAM,IAAM,CAE1D,CAAC,EACDA,EAAmB,QAAU,KAEjC,EAAG,GAAG,CACR,EACA,CAACb,CAAiB,CAAA,EAIpB3F,EAAAA,UAAU,IACD,IAAM,CACPuG,EAAY,SACd,aAAaA,EAAY,OAAO,CAEpC,EACC,CAAA,CAAE,EAEL,MAAMI,EAAexI,EAAAA,YACnB,CAACO,EAA0BoH,EAA2Bc,IAAqB,CACzE,MAAMhB,EAAuC,CAC3C,GAAGL,EACH,CAAC7G,CAAI,EAAG,CACN,GAAG6G,EAAY7G,CAAI,EACnB,CAACoH,CAAO,EAAGc,CAAA,CACb,EAEFF,EAAcd,CAAc,CAC9B,EACA,CAACL,EAAamB,CAAa,CAAA,EAG7B,OAAIvK,GAAamK,EAEb9I,EAAAA,IAAC,OAAI,UAAU,4BACb,eAAC,MAAA,CAAI,UAAU,4BAA4B,SAAA,wBAAA,CAAsB,CAAA,CACnE,EAICc,EAWHf,EAAAA,KAAC,MAAA,CAAI,UAAU,uDACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,4BACb,SAAA,CAAAC,EAAAA,IAAC,IAAA,CAAE,UAAU,iCAAiC,SAAA,2FAE9C,EACCpB,GAASoB,EAAAA,IAAC,MAAA,CAAI,UAAU,2BAA4B,WAAM,QAAQ,EAClEM,GAAcN,EAAAA,IAAC,OAAA,CAAK,UAAU,4BAA4B,SAAA,WAAA,CAAS,CAAA,EACtE,EAEAA,EAAAA,IAAC,OAAI,UAAU,4BACb,eAAC,QAAA,CAAM,UAAU,2BACf,SAAAD,EAAAA,KAAC,QAAA,CAEC,SAAA,CAAAA,EAAAA,KAAC,KAAA,CAAG,UAAU,kCACZ,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,oCAAoC,SAAA,eAAY,EAC9DA,EAAAA,IAAC,KAAA,CAAG,UAAU,iCAAiC,SAAA,QAAK,EACpDA,EAAAA,IAAC,KAAA,CAAG,UAAU,iCAAiC,SAAA,QAAA,CAAM,CAAA,EACvD,EACCwH,GAAyB,IAAKc,GAC7BvI,EAAAA,KAAC,KAAA,CAAiB,UAAU,yBAC1B,SAAA,CAAAC,MAAC,KAAA,CAAG,UAAU,mCACX,SAAA0H,EAAyBY,CAAO,EACnC,EACAtI,EAAAA,IAAC,KAAA,CAAG,UAAU,iCACZ,SAAAA,EAAAA,IAACuI,EAAA,CACC,QAASR,EAAY,MAAMO,CAAO,GAAK,GACvC,SAAWc,GAAYD,EAAa,QAASb,EAASc,CAAO,EAC7D,SAAU9I,EACV,MAAO,mBAAmBoH,EAAyBY,CAAO,CAAC,EAAA,CAAA,EAE/D,EACAtI,EAAAA,IAAC,KAAA,CAAG,UAAU,iCACZ,SAAAA,EAAAA,IAACuI,EAAA,CACC,QAASR,EAAY,OAAOO,CAAO,GAAK,GACxC,SAAWc,GAAYD,EAAa,SAAUb,EAASc,CAAO,EAC9D,SAAU9I,EACV,MAAO,oBAAoBoH,EAAyBY,CAAO,CAAC,EAAA,CAAA,CAC9D,CACF,CAAA,CAAA,EAnBOA,CAoBT,CACD,EAGAW,GACClJ,EAAAA,KAAAsJ,WAAA,CACE,SAAA,CAAAtJ,EAAAA,KAAC,KAAA,CAAG,UAAU,kCACZ,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,oCAAoC,SAAA,aAAU,EAC5DA,EAAAA,IAAC,KAAA,CAAG,UAAU,iCAAiC,SAAA,QAAK,EACpDA,EAAAA,IAAC,KAAA,CAAG,UAAU,iCAAiC,SAAA,QAAA,CAAM,CAAA,EACvD,EACCyH,GAAuB,IAAKa,GAC3BvI,EAAAA,KAAC,KAAA,CAAiB,UAAU,yBAC1B,SAAA,CAAAC,MAAC,KAAA,CAAG,UAAU,mCACX,SAAA0H,EAAyBY,CAAO,EACnC,EACAtI,EAAAA,IAAC,KAAA,CAAG,UAAU,iCACZ,SAAAA,EAAAA,IAACuI,EAAA,CACC,QAASR,EAAY,MAAMO,CAAO,GAAK,GACvC,SAAWc,GAAYD,EAAa,QAASb,EAASc,CAAO,EAC7D,SAAU9I,EACV,MAAO,mBAAmBoH,EAAyBY,CAAO,CAAC,EAAA,CAAA,EAE/D,EACAtI,EAAAA,IAAC,KAAA,CAAG,UAAU,iCACZ,SAAAA,EAAAA,IAACuI,EAAA,CACC,QAASR,EAAY,OAAOO,CAAO,GAAK,GACxC,SAAWc,GAAYD,EAAa,SAAUb,EAASc,CAAO,EAC9D,SAAU9I,EACV,MAAO,oBAAoBoH,EAAyBY,CAAO,CAAC,EAAA,CAAA,CAC9D,CACF,CAAA,CAAA,EAnBOA,CAoBT,CACD,CAAA,CAAA,CACH,CAAA,CAAA,CAEJ,EACF,CAAA,CACF,CAAA,EACF,EAvFEtI,EAAAA,IAAC,OAAI,UAAU,4BACb,eAAC,MAAA,CAAI,UAAU,0BAA0B,SAAA,+DAAA,CAEzC,CAAA,CACF,CAqFN"}
|