UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

1 lines 25.9 kB
{"version":3,"file":"use-magic-link.cjs","sources":["../../../src/hooks/use-magic-link.ts"],"sourcesContent":["/**\n * @frank-auth/react - useMagicLink Hook\n *\n * Magic link authentication hook that provides passwordless email-based\n * authentication with customizable email templates and verification flow.\n */\n\nimport {useCallback, useEffect, useMemo, useState} from 'react';\n\nimport type {MagicLinkRequest,} from '@frank-auth/client';\n\nimport {useAuth} from './use-auth';\nimport {useConfig} from '../provider/config-provider';\n\nimport type {AuthError} from '../provider/types';\n\n// ============================================================================\n// Magic Link Hook Interface\n// ============================================================================\n\nexport interface UseMagicLinkReturn {\n // Magic link state\n isLoading: boolean;\n error: AuthError | null;\n lastSentEmail: string | null;\n lastSentAt: Date | null;\n canResend: boolean;\n timeUntilResend: number; // seconds\n\n // Magic link operations\n sendMagicLink: (email: string, options?: MagicLinkOptions) => Promise<MagicLinkSendResult>;\n verifyMagicLink: (token: string) => Promise<MagicLinkVerifyResult>;\n resendMagicLink: () => Promise<MagicLinkSendResult>;\n\n // Magic link verification (for URL-based verification)\n verifyFromUrl: (url?: string) => Promise<MagicLinkVerifyResult>;\n extractTokenFromUrl: (url?: string) => string | null;\n\n // Utility methods\n isValidEmail: (email: string) => boolean;\n clearState: () => void;\n}\n\nexport interface MagicLinkOptions {\n redirectUrl?: string;\n organizationId?: string;\n customData?: Record<string, any>;\n template?: string;\n expiresIn?: number; // seconds\n locale?: string;\n}\n\nexport interface MagicLinkSendResult {\n success: boolean;\n email: string;\n message: string;\n expiresAt: Date;\n error?: string;\n}\n\nexport interface MagicLinkVerifyResult {\n success: boolean;\n user?: any;\n session?: any;\n error?: string;\n requiresAdditionalVerification?: boolean;\n mfaToken?: string;\n}\n\n// ============================================================================\n// Magic Link Configurations\n// ============================================================================\n\nexport const MAGIC_LINK_CONFIG = {\n // Default expiration time (15 minutes)\n DEFAULT_EXPIRES_IN: 15 * 60,\n\n // Minimum time between sends (60 seconds)\n RESEND_COOLDOWN: 60,\n\n // Email validation regex\n EMAIL_REGEX: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n\n // Default templates\n TEMPLATES: {\n SIGN_IN: 'magic-link-sign-in',\n SIGN_UP: 'magic-link-sign-up',\n VERIFY_EMAIL: 'magic-link-verify-email',\n PASSWORD_RESET: 'magic-link-password-reset',\n },\n\n // URL parameter names\n URL_PARAMS: {\n TOKEN: 'token',\n EMAIL: 'email',\n TYPE: 'type',\n REDIRECT: 'redirect_to',\n },\n} as const;\n\n// ============================================================================\n// Main useMagicLink Hook\n// ============================================================================\n\n/**\n * Magic link authentication hook for passwordless email authentication\n *\n * @example Basic magic link sign-in\n * ```tsx\n * import { useMagicLink } from '@frank-auth/react';\n *\n * function MagicLinkSignIn() {\n * const {\n * sendMagicLink,\n * isLoading,\n * error,\n * lastSentEmail,\n * canResend,\n * resendMagicLink,\n * isValidEmail\n * } = useMagicLink();\n *\n * const [email, setEmail] = useState('');\n * const [sent, setSent] = useState(false);\n *\n * const handleSend = async () => {\n * if (!isValidEmail(email)) {\n * alert('Please enter a valid email address');\n * return;\n * }\n *\n * try {\n * const result = await sendMagicLink(email, {\n * redirectUrl: '/dashboard',\n * template: 'sign-in'\n * });\n *\n * if (result.success) {\n * setSent(true);\n * }\n * } catch (error) {\n * console.error('Failed to send magic link:', error);\n * }\n * };\n *\n * const handleResend = async () => {\n * try {\n * await resendMagicLink();\n * } catch (error) {\n * console.error('Failed to resend magic link:', error);\n * }\n * };\n *\n * if (sent) {\n * return (\n * <div>\n * <h3>Check your email</h3>\n * <p>We sent a magic link to {lastSentEmail}</p>\n * <p>Click the link in your email to sign in.</p>\n *\n * {canResend ? (\n * <button onClick={handleResend} disabled={isLoading}>\n * Resend magic link\n * </button>\n * ) : (\n * <p>You can resend the link in a few seconds</p>\n * )}\n *\n * {error && <p style={{color: 'red'}}>{error.message}</p>}\n * </div>\n * );\n * }\n *\n * return (\n * <div>\n * <h3>Sign in with magic link</h3>\n * <input\n * type=\"email\"\n * value={email}\n * onChange={(e) => setEmail(e.target.value)}\n * placeholder=\"Enter your email address\"\n * disabled={isLoading}\n * />\n * <button onClick={handleSend} disabled={isLoading || !email}>\n * {isLoading ? 'Sending...' : 'Send magic link'}\n * </button>\n *\n * {error && <p style={{color: 'red'}}>{error.message}</p>}\n * </div>\n * );\n * }\n * ```\n *\n * @example Magic link verification page\n * ```tsx\n * import { useEffect, useState } from 'react';\n * import { useMagicLink } from '@frank-auth/react';\n * import { useSearchParams, useNavigate } from 'react-router-dom';\n *\n * function MagicLinkVerify() {\n * const { verifyFromUrl, isLoading } = useMagicLink();\n * const [searchParams] = useSearchParams();\n * const navigate = useNavigate();\n * const [status, setStatus] = useState('verifying');\n *\n * useEffect(() => {\n * const verify = async () => {\n * try {\n * const result = await verifyFromUrl();\n *\n * if (result.success) {\n * setStatus('success');\n *\n * // Check for MFA requirement\n * if (result.requiresAdditionalVerification) {\n * navigate('/mfa', { state: { mfaToken: result.mfaToken } });\n * } else {\n * // Redirect to dashboard or intended page\n * const redirectTo = searchParams.get('redirect_to') || '/dashboard';\n * navigate(redirectTo);\n * }\n * } else {\n * setStatus('error');\n * }\n * } catch (error) {\n * console.error('Magic link verification failed:', error);\n * setStatus('error');\n * }\n * };\n *\n * verify();\n * }, [verifyFromUrl, navigate, searchParams]);\n *\n * if (isLoading || status === 'verifying') {\n * return <div>Verifying magic link...</div>;\n * }\n *\n * if (status === 'success') {\n * return <div>Success! Redirecting...</div>;\n * }\n *\n * return (\n * <div>\n * <h3>Invalid or expired magic link</h3>\n * <p>The magic link you clicked is invalid or has expired.</p>\n * <button onClick={() => navigate('/sign-in')}>\n * Try again\n * </button>\n * </div>\n * );\n * }\n * ```\n *\n * @example Magic link with organization invitation\n * ```tsx\n * function OrganizationInvite({ invitationToken, organizationId }) {\n * const { sendMagicLink } = useMagicLink();\n * const [email, setEmail] = useState('');\n *\n * const handleAcceptInvite = async () => {\n * try {\n * await sendMagicLink(email, {\n * organizationId,\n * customData: {\n * invitationToken,\n * action: 'accept_invitation'\n * },\n * template: 'organization-invite',\n * redirectUrl: `/organizations/${organizationId}/welcome`\n * });\n * } catch (error) {\n * console.error('Failed to send invitation magic link:', error);\n * }\n * };\n *\n * return (\n * <div>\n * <h3>Join Organization</h3>\n * <p>Enter your email to receive a secure link to join the organization.</p>\n * <input\n * type=\"email\"\n * value={email}\n * onChange={(e) => setEmail(e.target.value)}\n * placeholder=\"Enter your email address\"\n * />\n * <button onClick={handleAcceptInvite}>\n * Send invitation link\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useMagicLink(): UseMagicLinkReturn {\n const {activeOrganization, reload, userType, sdk} = useAuth();\n const {apiUrl, publishableKey, features, linksPath, frontendUrl} = useConfig();\n\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n const [lastSentEmail, setLastSentEmail] = useState<string | null>(null);\n const [lastSentAt, setLastSentAt] = useState<Date | null>(null);\n\n // Check if magic links are available\n const isMagicLinkAvailable = useMemo(() => features.magicLink, [features.magicLink]);\n\n // Calculate resend availability\n const canResend = useMemo(() => {\n if (!lastSentAt) return false;\n const timeSinceLastSend = (Date.now() - lastSentAt.getTime()) / 1000;\n return timeSinceLastSend >= MAGIC_LINK_CONFIG.RESEND_COOLDOWN;\n }, [lastSentAt]);\n\n const timeUntilResend = useMemo(() => {\n if (!lastSentAt || canResend) return 0;\n const timeSinceLastSend = (Date.now() - lastSentAt.getTime()) / 1000;\n return Math.max(0, MAGIC_LINK_CONFIG.RESEND_COOLDOWN - timeSinceLastSend);\n }, [lastSentAt, canResend]);\n\n // Update countdown timer\n useEffect(() => {\n if (!lastSentAt || canResend) return;\n\n const interval = setInterval(() => {\n // This will trigger the useMemo recalculation\n }, 1000);\n\n return () => clearInterval(interval);\n }, [lastSentAt, canResend]);\n\n // Error handler\n const handleError = useCallback((err: any) => {\n const authError: AuthError = {\n code: err.code || 'UNKNOWN_ERROR',\n message: err.message || 'An unknown error occurred',\n details: err.details,\n field: err.field,\n };\n setError(authError);\n throw authError;\n }, []);\n\n // Email validation\n const isValidEmail = useCallback((email: string): boolean => {\n return MAGIC_LINK_CONFIG.EMAIL_REGEX.test(email);\n }, []);\n\n // Extract token from URL\n const extractTokenFromUrl = useCallback((url?: string): string | null => {\n const urlToCheck = url || window.location.href;\n\n try {\n const urlObj = new URL(urlToCheck);\n return urlObj.searchParams.get(MAGIC_LINK_CONFIG.URL_PARAMS.TOKEN);\n } catch {\n return null;\n }\n }, []);\n\n // Send magic link\n const sendMagicLink = useCallback(async (\n email: string,\n options?: MagicLinkOptions\n ): Promise<MagicLinkSendResult> => {\n if (!isMagicLinkAvailable) throw new Error('Magic links not available');\n if (!isValidEmail(email)) throw new Error('Invalid email address');\n\n try {\n setIsLoading(true);\n setError(null);\n\n const magicLinkRequest: MagicLinkRequest = {\n email,\n redirectUrl: options?.redirectUrl || `${frontendUrl ?? window.location.origin}${linksPath?.magicLink}`,\n // organizationId: options?.organizationId || activeOrganization?.id,\n // customData: options?.customData,\n expiresIn: options?.expiresIn || MAGIC_LINK_CONFIG.DEFAULT_EXPIRES_IN,\n // locale: options?.locale,\n };\n\n const response = await sdk.auth.sendMagicLink(magicLinkRequest);\n\n // Update state\n setLastSentEmail(email);\n setLastSentAt(new Date());\n\n return {\n success: response.success,\n email,\n message: response.message,\n expiresAt: new Date(Date.now() + (magicLinkRequest.expiresIn! * 1000)),\n };\n } catch (err) {\n return {\n success: false,\n email,\n expiresAt: new Date(),\n error: err.message || 'Failed to send magic link',\n };\n } finally {\n setIsLoading(false);\n }\n }, [sdk.auth, isMagicLinkAvailable, isValidEmail, activeOrganization]);\n\n // Verify magic link\n const verifyMagicLink = useCallback(async (token: string): Promise<MagicLinkVerifyResult> => {\n if (!isMagicLinkAvailable) throw new Error('Magic links not available');\n if (!token) throw new Error('Verification token is required');\n\n try {\n setIsLoading(true);\n setError(null);\n\n const response = await sdk.auth.verifyMagicLink(token);\n\n if (response.session) {\n // Reload auth state with new user session\n await reload();\n\n return {\n success: true,\n user: response.user,\n session: response.session,\n requiresAdditionalVerification: response.mfaRequired,\n mfaToken: response.mfaToken,\n };\n } else {\n return {\n success: false,\n error: response.error || 'Magic link verification failed',\n };\n }\n } catch (err) {\n return {\n success: false,\n error: err.message || 'Magic link verification failed',\n };\n } finally {\n setIsLoading(false);\n }\n }, [sdk.auth, isMagicLinkAvailable, reload]);\n\n // Verify magic link from URL\n const verifyFromUrl = useCallback(async (url?: string): Promise<MagicLinkVerifyResult> => {\n const token = extractTokenFromUrl(url);\n\n if (!token) {\n return {\n success: false,\n error: 'No verification token found in URL',\n };\n }\n\n return verifyMagicLink(token);\n }, [extractTokenFromUrl, verifyMagicLink]);\n\n // Resend magic link\n const resendMagicLink = useCallback(async (): Promise<MagicLinkSendResult> => {\n if (!lastSentEmail) {\n throw new Error('No previous magic link to resend');\n }\n\n if (!canResend) {\n throw new Error(`Please wait ${Math.ceil(timeUntilResend)} seconds before resending`);\n }\n\n return sendMagicLink(lastSentEmail);\n }, [lastSentEmail, canResend, timeUntilResend, sendMagicLink]);\n\n // Clear state\n const clearState = useCallback(() => {\n setError(null);\n setLastSentEmail(null);\n setLastSentAt(null);\n }, []);\n\n return {\n // Magic link state\n isLoading,\n error,\n lastSentEmail,\n lastSentAt,\n canResend,\n timeUntilResend,\n\n // Magic link operations\n sendMagicLink,\n verifyMagicLink,\n resendMagicLink,\n\n // Magic link verification\n verifyFromUrl,\n extractTokenFromUrl,\n\n // Utility methods\n isValidEmail,\n clearState,\n };\n}\n\n// ============================================================================\n// Specialized Magic Link Hooks\n// ============================================================================\n\n/**\n * Hook for magic link sign-in flow\n */\nexport function useMagicLinkSignIn() {\n const {\n sendMagicLink,\n isLoading,\n error,\n lastSentEmail,\n canResend,\n resendMagicLink,\n isValidEmail,\n clearState,\n } = useMagicLink();\n\n const [signInState, setSignInState] = useState<'idle' | 'email_sent' | 'verified'>('idle');\n\n const signIn = useCallback(async (email: string, redirectUrl?: string) => {\n if (!isValidEmail(email)) {\n throw new Error('Please enter a valid email address');\n }\n\n try {\n const result = await sendMagicLink(email, {\n redirectUrl: redirectUrl || '/dashboard',\n template: MAGIC_LINK_CONFIG.TEMPLATES.SIGN_IN,\n });\n\n if (result.success) {\n setSignInState('email_sent');\n }\n\n return result;\n } catch (error) {\n setSignInState('idle');\n throw error;\n }\n }, [sendMagicLink, isValidEmail]);\n\n const reset = useCallback(() => {\n setSignInState('idle');\n clearState();\n }, [clearState]);\n\n return {\n signIn,\n resend: resendMagicLink,\n reset,\n state: signInState,\n sentTo: lastSentEmail,\n canResend,\n isLoading,\n error,\n isValidEmail,\n };\n}\n\n/**\n * Hook for magic link verification flow\n */\nexport function useMagicLinkVerification() {\n const {\n verifyFromUrl,\n verifyMagicLink,\n extractTokenFromUrl,\n isLoading,\n error,\n } = useMagicLink();\n\n const [verificationState, setVerificationState] = useState<'idle' | 'verifying' | 'success' | 'error'>('idle');\n const [verificationResult, setVerificationResult] = useState<MagicLinkVerifyResult | null>(null);\n\n // Auto-verify if token is in URL\n useEffect(() => {\n const token = extractTokenFromUrl();\n if (token && verificationState === 'idle') {\n verify(token);\n }\n }, [extractTokenFromUrl, verificationState]);\n\n const verify = useCallback(async (token?: string) => {\n try {\n setVerificationState('verifying');\n\n const result = token\n ? await verifyMagicLink(token)\n : await verifyFromUrl();\n\n setVerificationResult(result);\n setVerificationState(result.success ? 'success' : 'error');\n\n return result;\n } catch (error) {\n setVerificationState('error');\n setVerificationResult({\n success: false,\n error: error.message,\n });\n throw error;\n }\n }, [verifyMagicLink, verifyFromUrl]);\n\n return {\n verify,\n state: verificationState,\n result: verificationResult,\n isVerifying: verificationState === 'verifying' || isLoading,\n isSuccess: verificationState === 'success',\n isError: verificationState === 'error',\n error: error || verificationResult?.error,\n requiresMFA: verificationResult?.requiresAdditionalVerification,\n mfaToken: verificationResult?.mfaToken,\n };\n}\n\n/**\n * Hook for magic link password reset flow\n */\nexport function useMagicLinkPasswordReset() {\n const {sendMagicLink, isValidEmail} = useMagicLink();\n\n const sendResetLink = useCallback(async (email: string) => {\n if (!isValidEmail(email)) {\n throw new Error('Please enter a valid email address');\n }\n\n return sendMagicLink(email, {\n template: MAGIC_LINK_CONFIG.TEMPLATES.PASSWORD_RESET,\n redirectUrl: `${window.location.origin}/auth/reset-password`,\n });\n }, [sendMagicLink, isValidEmail]);\n\n return {\n sendResetLink,\n isValidEmail,\n };\n}"],"names":["MAGIC_LINK_CONFIG","useMagicLink","activeOrganization","reload","userType","sdk","useAuth","apiUrl","publishableKey","features","linksPath","frontendUrl","useConfig","isLoading","setIsLoading","useState","error","setError","lastSentEmail","setLastSentEmail","lastSentAt","setLastSentAt","isMagicLinkAvailable","useMemo","canResend","timeUntilResend","timeSinceLastSend","useEffect","interval","useCallback","err","authError","isValidEmail","email","extractTokenFromUrl","url","urlToCheck","sendMagicLink","options","magicLinkRequest","response","verifyMagicLink","token","verifyFromUrl","resendMagicLink","clearState","useMagicLinkSignIn","signInState","setSignInState","signIn","redirectUrl","result","reset","useMagicLinkVerification","verificationState","setVerificationState","verificationResult","setVerificationResult","verify","useMagicLinkPasswordReset"],"mappings":"kLAyEaA,EAAoB,CAE7B,mBAAoB,GAAK,GAGzB,gBAAiB,GAGjB,YAAa,6BAGb,UAAW,CACP,QAAS,qBACT,QAAS,qBACT,aAAc,0BACd,eAAgB,2BACpB,EAGA,WAAY,CACR,MAAO,QACP,MAAO,QACP,KAAM,OACN,SAAU,aAAA,CAElB,EAmMO,SAASC,GAAmC,CAC/C,KAAM,CAAC,mBAAAC,EAAoB,OAAAC,EAAQ,SAAAC,EAAU,IAAAC,CAAA,EAAOC,EAAAA,QAAQ,EACtD,CAAC,OAAAC,EAAQ,eAAAC,EAAgB,SAAAC,EAAU,UAAAC,EAAW,YAAAC,GAAeC,YAAU,EAEvE,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAS,EAAK,EAC1C,CAACC,EAAOC,CAAQ,EAAIF,EAAAA,SAA2B,IAAI,EACnD,CAACG,EAAeC,CAAgB,EAAIJ,EAAAA,SAAwB,IAAI,EAChE,CAACK,EAAYC,CAAa,EAAIN,EAAAA,SAAsB,IAAI,EAGxDO,EAAuBC,EAAAA,QAAQ,IAAMd,EAAS,UAAW,CAACA,EAAS,SAAS,CAAC,EAG7Ee,EAAYD,EAAAA,QAAQ,IACjBH,GACsB,KAAK,IAAQ,EAAAA,EAAW,WAAa,KACpCpB,EAAkB,gBAFtB,GAGzB,CAACoB,CAAU,CAAC,EAETK,EAAkBF,EAAAA,QAAQ,IAAM,CAC9B,GAAA,CAACH,GAAcI,EAAkB,MAAA,GACrC,MAAME,GAAqB,KAAK,IAAQ,EAAAN,EAAW,WAAa,IAChE,OAAO,KAAK,IAAI,EAAGpB,EAAkB,gBAAkB0B,CAAiB,CAAA,EACzE,CAACN,EAAYI,CAAS,CAAC,EAG1BG,EAAAA,UAAU,IAAM,CACR,GAAA,CAACP,GAAcI,EAAW,OAExB,MAAAI,EAAW,YAAY,IAAM,GAEhC,GAAI,EAEA,MAAA,IAAM,cAAcA,CAAQ,CAAA,EACpC,CAACR,EAAYI,CAAS,CAAC,EAGNK,EAAY,YAACC,GAAa,CAC1C,MAAMC,EAAuB,CACzB,KAAMD,EAAI,MAAQ,gBAClB,QAASA,EAAI,SAAW,4BACxB,QAASA,EAAI,QACb,MAAOA,EAAI,KACf,EACA,MAAAb,EAASc,CAAS,EACZA,CAAA,EACP,CAAE,CAAA,EAGC,MAAAC,EAAeH,cAAaI,GACvBjC,EAAkB,YAAY,KAAKiC,CAAK,EAChD,EAAE,EAGCC,EAAsBL,cAAaM,GAAgC,CAC/D,MAAAC,EAAaD,GAAO,OAAO,SAAS,KAEtC,GAAA,CAEA,OADe,IAAI,IAAIC,CAAU,EACnB,aAAa,IAAIpC,EAAkB,WAAW,KAAK,CAAA,MAC7D,CACG,OAAA,IAAA,CAEf,EAAG,EAAE,EAGCqC,EAAgBR,EAAAA,YAAY,MAC9BI,EACAK,IAC+B,CAC/B,GAAI,CAAChB,EAA4B,MAAA,IAAI,MAAM,2BAA2B,EACtE,GAAI,CAACU,EAAaC,CAAK,EAAS,MAAA,IAAI,MAAM,uBAAuB,EAE7D,GAAA,CACAnB,EAAa,EAAI,EACjBG,EAAS,IAAI,EAEb,MAAMsB,EAAqC,CACvC,MAAAN,EACA,YAAaK,GAAS,aAAe,GAAG3B,GAAe,OAAO,SAAS,MAAM,GAAGD,GAAW,SAAS,GAGpG,UAAW4B,GAAS,WAAatC,EAAkB,kBAEvD,EAEMwC,EAAW,MAAMnC,EAAI,KAAK,cAAckC,CAAgB,EAG9D,OAAApB,EAAiBc,CAAK,EACRZ,EAAA,IAAI,IAAM,EAEjB,CACH,QAASmB,EAAS,QAClB,MAAAP,EACA,QAASO,EAAS,QAClB,UAAW,IAAI,KAAK,KAAK,MAASD,EAAiB,UAAa,GAAK,CACzE,QACKT,EAAK,CACH,MAAA,CACH,QAAS,GACT,MAAAG,EACA,cAAe,KACf,MAAOH,EAAI,SAAW,2BAC1B,CAAA,QACF,CACEhB,EAAa,EAAK,CAAA,CACtB,EACD,CAACT,EAAI,KAAMiB,EAAsBU,EAAc9B,CAAkB,CAAC,EAG/DuC,EAAkBZ,cAAY,MAAOa,GAAkD,CACzF,GAAI,CAACpB,EAA4B,MAAA,IAAI,MAAM,2BAA2B,EACtE,GAAI,CAACoB,EAAa,MAAA,IAAI,MAAM,gCAAgC,EAExD,GAAA,CACA5B,EAAa,EAAI,EACjBG,EAAS,IAAI,EAEb,MAAMuB,EAAW,MAAMnC,EAAI,KAAK,gBAAgBqC,CAAK,EAErD,OAAIF,EAAS,SAET,MAAMrC,EAAO,EAEN,CACH,QAAS,GACT,KAAMqC,EAAS,KACf,QAASA,EAAS,QAClB,+BAAgCA,EAAS,YACzC,SAAUA,EAAS,QACvB,GAEO,CACH,QAAS,GACT,MAAOA,EAAS,OAAS,gCAC7B,QAECV,EAAK,CACH,MAAA,CACH,QAAS,GACT,MAAOA,EAAI,SAAW,gCAC1B,CAAA,QACF,CACEhB,EAAa,EAAK,CAAA,GAEvB,CAACT,EAAI,KAAMiB,EAAsBnB,CAAM,CAAC,EAGrCwC,EAAgBd,cAAY,MAAOM,GAAiD,CAChF,MAAAO,EAAQR,EAAoBC,CAAG,EAErC,OAAKO,EAOED,EAAgBC,CAAK,EANjB,CACH,QAAS,GACT,MAAO,oCACX,CAGwB,EAC7B,CAACR,EAAqBO,CAAe,CAAC,EAGnCG,EAAkBf,EAAAA,YAAY,SAA0C,CAC1E,GAAI,CAACX,EACK,MAAA,IAAI,MAAM,kCAAkC,EAGtD,GAAI,CAACM,EACD,MAAM,IAAI,MAAM,eAAe,KAAK,KAAKC,CAAe,CAAC,2BAA2B,EAGxF,OAAOY,EAAcnB,CAAa,GACnC,CAACA,EAAeM,EAAWC,EAAiBY,CAAa,CAAC,EAGvDQ,EAAahB,EAAAA,YAAY,IAAM,CACjCZ,EAAS,IAAI,EACbE,EAAiB,IAAI,EACrBE,EAAc,IAAI,CACtB,EAAG,EAAE,EAEE,MAAA,CAEH,UAAAR,EACA,MAAAG,EACA,cAAAE,EACA,WAAAE,EACA,UAAAI,EACA,gBAAAC,EAGA,cAAAY,EACA,gBAAAI,EACA,gBAAAG,EAGA,cAAAD,EACA,oBAAAT,EAGA,aAAAF,EACA,WAAAa,CACJ,CACJ,CASO,SAASC,GAAqB,CAC3B,KAAA,CACF,cAAAT,EACA,UAAAxB,EACA,MAAAG,EACA,cAAAE,EACA,UAAAM,EACA,gBAAAoB,EACA,aAAAZ,EACA,WAAAa,GACA5C,EAAa,EAEX,CAAC8C,EAAaC,CAAc,EAAIjC,EAAAA,SAA6C,MAAM,EAEnFkC,EAASpB,EAAAA,YAAY,MAAOI,EAAeiB,IAAyB,CAClE,GAAA,CAAClB,EAAaC,CAAK,EACb,MAAA,IAAI,MAAM,oCAAoC,EAGpD,GAAA,CACM,MAAAkB,EAAS,MAAMd,EAAcJ,EAAO,CACtC,YAAaiB,GAAe,aAC5B,SAAUlD,EAAkB,UAAU,OAAA,CACzC,EAED,OAAImD,EAAO,SACPH,EAAe,YAAY,EAGxBG,QACFnC,EAAO,CACZ,MAAAgC,EAAe,MAAM,EACfhC,CAAA,CACV,EACD,CAACqB,EAAeL,CAAY,CAAC,EAE1BoB,EAAQvB,EAAAA,YAAY,IAAM,CAC5BmB,EAAe,MAAM,EACVH,EAAA,CAAA,EACZ,CAACA,CAAU,CAAC,EAER,MAAA,CACH,OAAAI,EACA,OAAQL,EACR,MAAAQ,EACA,MAAOL,EACP,OAAQ7B,EACR,UAAAM,EACA,UAAAX,EACA,MAAAG,EACA,aAAAgB,CACJ,CACJ,CAKO,SAASqB,GAA2B,CACjC,KAAA,CACF,cAAAV,EACA,gBAAAF,EACA,oBAAAP,EACA,UAAArB,EACA,MAAAG,GACAf,EAAa,EAEX,CAACqD,EAAmBC,CAAoB,EAAIxC,EAAAA,SAAqD,MAAM,EACvG,CAACyC,EAAoBC,CAAqB,EAAI1C,EAAAA,SAAuC,IAAI,EAG/FY,EAAAA,UAAU,IAAM,CACZ,MAAMe,EAAQR,EAAoB,EAC9BQ,GAASY,IAAsB,QAC/BI,EAAOhB,CAAK,CAChB,EACD,CAACR,EAAqBoB,CAAiB,CAAC,EAErC,MAAAI,EAAS7B,cAAY,MAAOa,GAAmB,CAC7C,GAAA,CACAa,EAAqB,WAAW,EAEhC,MAAMJ,EAAST,EACT,MAAMD,EAAgBC,CAAK,EAC3B,MAAMC,EAAc,EAE1B,OAAAc,EAAsBN,CAAM,EACPI,EAAAJ,EAAO,QAAU,UAAY,OAAO,EAElDA,QACFnC,EAAO,CACZ,MAAAuC,EAAqB,OAAO,EACNE,EAAA,CAClB,QAAS,GACT,MAAOzC,EAAM,OAAA,CAChB,EACKA,CAAA,CACV,EACD,CAACyB,EAAiBE,CAAa,CAAC,EAE5B,MAAA,CACH,OAAAe,EACA,MAAOJ,EACP,OAAQE,EACR,YAAaF,IAAsB,aAAezC,EAClD,UAAWyC,IAAsB,UACjC,QAASA,IAAsB,QAC/B,MAAOtC,GAASwC,GAAoB,MACpC,YAAaA,GAAoB,+BACjC,SAAUA,GAAoB,QAClC,CACJ,CAKO,SAASG,GAA4B,CACxC,KAAM,CAAC,cAAAtB,EAAe,aAAAL,CAAY,EAAI/B,EAAa,EAa5C,MAAA,CACH,cAZkB4B,cAAY,MAAOI,GAAkB,CACnD,GAAA,CAACD,EAAaC,CAAK,EACb,MAAA,IAAI,MAAM,oCAAoC,EAGxD,OAAOI,EAAcJ,EAAO,CACxB,SAAUjC,EAAkB,UAAU,eACtC,YAAa,GAAG,OAAO,SAAS,MAAM,sBAAA,CACzC,CAAA,EACF,CAACqC,EAAeL,CAAY,CAAC,EAI5B,aAAAA,CACJ,CACJ"}