@reblu/site-next 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.cjs CHANGED
@@ -546,6 +546,7 @@ function AuthLogo(props) {
546
546
  variant = "expanded",
547
547
  className,
548
548
  copy,
549
+ headingLevel = 1,
549
550
  hideTextFallback = false
550
551
  } = props;
551
552
  const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY4.fallbackLabel;
@@ -560,22 +561,26 @@ function AuthLogo(props) {
560
561
  }, [logoUrl]);
561
562
  const fallbackText = siteName || fallbackLabel;
562
563
  const altText = copy?.altText !== void 0 ? copy.altText : siteName || fallbackLabel;
564
+ const Heading = `h${headingLevel}`;
563
565
  if (logoUrl && !imgError) {
564
566
  const { width, height } = VARIANT_INTRINSIC_SIZE[variant];
565
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
567
+ const bare = hideTextFallback || altText === "";
568
+ const image = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
566
569
  "img",
567
570
  {
568
571
  src: logoUrl,
569
572
  alt: altText,
570
573
  width,
571
574
  height,
572
- className: (0, import_site_ui3.cn)(VARIANT_CLASS[variant], className),
575
+ className: (0, import_site_ui3.cn)(VARIANT_CLASS[variant], "max-w-full", bare && className),
573
576
  onError: () => setImgError(true)
574
577
  }
575
578
  );
579
+ if (bare) return image;
580
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Heading, { className: (0, import_site_ui3.cn)("inline-flex items-center max-w-full", className), children: image });
576
581
  }
577
582
  if (hideTextFallback) return null;
578
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: (0, import_site_ui3.cn)("text-lg font-semibold text-foreground", className), children: fallbackText });
583
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Heading, { className: (0, import_site_ui3.cn)("text-2xl font-bold tracking-tight text-foreground", className), children: fallbackText });
579
584
  }
580
585
 
581
586
  // src/auth/reset-password-request.tsx
package/dist/auth.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/index.ts","../src/auth/login-form.tsx","../src/auth/safe-redirect.ts","../src/auth/auth-callback.tsx","../src/auth/login-errors.ts","../src/auth/auth-logo.tsx","../src/auth/reset-password-request.tsx","../src/auth/reset-password-confirm.tsx","../src/auth/set-password.tsx","../src/auth/auth-confirm.tsx"],"sourcesContent":["'use client'\n\n/**\n * `@reblu/site-next/auth` — client auth components (REB-510 / design doc\n * \"site-auth-package\").\n *\n * This barrel MUST begin with its OWN leading `'use client'` directive: tsup\n * only preserves a directive prologue declared by the entry file itself, not\n * one merely re-exported from a child module (see the `cta-buttons` R4\n * precedent in `tsup.config.ts`). Without it, `auth.js`/`auth.cjs` would ship\n * as a directive-less bundle and every consuming SITE would silently lose the\n * client boundary.\n *\n * `LoginForm` + `AuthCallback` were MOVED here verbatim from `@reblu/ui`\n * (private, non-distributable) — this is their new public home.\n * `mapLoginError` was MOVED here from demo-site's SITE-local util, now\n * copy-injectable so any SITE can reuse it.\n *\n * `AuthLogo` + the 4 net-new components (`ResetPasswordRequest`,\n * `ResetPasswordConfirm`, `SetPassword`, `AuthConfirm`) were added directly\n * here (REB-510 slice 2b) — copy-injectable with Spanish defaults, same\n * pattern as `LoginForm`/`AuthCallback`.\n */\nexport { LoginForm, type LoginFormProps, type LoginFormCopy } from './login-form'\nexport { AuthCallback, type AuthCallbackProps, type AuthCallbackCopy } from './auth-callback'\nexport {\n mapLoginError,\n type LoginErrorInfo,\n type LoginErrorCopy,\n} from './login-errors'\nexport { AuthLogo, type AuthLogoProps, type AuthLogoCopy } from './auth-logo'\nexport {\n ResetPasswordRequest,\n type ResetPasswordRequestProps,\n type ResetPasswordRequestCopy,\n} from './reset-password-request'\nexport {\n ResetPasswordConfirm,\n type ResetPasswordConfirmProps,\n type ResetPasswordConfirmCopy,\n} from './reset-password-confirm'\nexport { SetPassword, type SetPasswordProps, type SetPasswordCopy } from './set-password'\nexport { AuthConfirm, type AuthConfirmProps, type AuthConfirmCopy } from './auth-confirm'\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@reblu/site-ui/tabs'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface LoginFormCopy {\n // Tab labels\n magicLinkTabLabel: string\n passwordTabLabel: string\n\n // Shared\n emailLabel: string\n emailPlaceholder: string\n\n // Password tab\n passwordLabel: string\n passwordPlaceholder: string\n passwordSubmitCta: string\n forgotPasswordCta: string\n\n // Magic-link tab\n magicLinkSubmitCta: string\n\n // Magic-link success\n magicLinkSuccessTitle: string\n magicLinkSuccessMessage: string\n\n // MFA prompt\n mfaTitle: string\n mfaMessage: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaSubmitCta: string\n\n // Errors\n invalidEmailError: string\n networkError: string\n invalidCredentialsError: string\n mfaInvalidCodeError: string\n}\n\nexport interface LoginFormProps {\n // Endpoint URLs (all required)\n passwordLoginUrl: string\n mfaVerifyUrl: string\n magicLinkRequestUrl: string\n resetPasswordUrl: string\n\n // UX\n copy?: Partial<LoginFormCopy>\n className?: string\n defaultMethod?: 'magic-link' | 'password'\n\n // Hooks\n onPasswordSubmitted?: (email: string) => void\n onMagicLinkSubmitted?: (email: string) => void\n onMfaSubmitted?: () => void\n}\n\nconst DEFAULT_COPY: LoginFormCopy = {\n magicLinkTabLabel: 'Enlace mágico',\n passwordTabLabel: 'Contraseña',\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n passwordLabel: 'Contraseña',\n passwordPlaceholder: '••••••••',\n passwordSubmitCta: 'Iniciar sesión',\n forgotPasswordCta: '¿Olvidaste tu contraseña?',\n magicLinkSubmitCta: 'Enviar enlace de acceso',\n magicLinkSuccessTitle: '¡Enlace enviado!',\n magicLinkSuccessMessage: 'Revisa tu correo para acceder.',\n mfaTitle: 'Verificación en dos pasos',\n mfaMessage: 'Introduce el código de 6 dígitos de tu aplicación autenticadora.',\n mfaCodeLabel: 'Código de verificación',\n mfaCodePlaceholder: '123456',\n mfaSubmitCta: 'Verificar código',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'No se pudo enviar el enlace. Inténtalo de nuevo.',\n invalidCredentialsError: 'Email o contraseña incorrectos',\n mfaInvalidCodeError: 'Código incorrecto. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value)\n}\n\ntype FormState = 'idle' | 'submitting' | 'magic-link-success' | 'mfa-required' | 'error'\n\ninterface MfaState {\n factorId: string\n accessToken: string\n refreshToken: string\n}\n\ninterface PasswordLoginResponse {\n data?: {\n mfaRequired: boolean\n sessionExchangeUrl?: string\n factorId?: string\n accessToken?: string\n refreshToken?: string\n }\n error?: string\n}\n\ninterface MfaVerifyResponse {\n data?: { sessionExchangeUrl: string }\n error?: string\n}\n\nexport function LoginForm(props: LoginFormProps) {\n const {\n passwordLoginUrl,\n mfaVerifyUrl,\n magicLinkRequestUrl,\n resetPasswordUrl,\n copy,\n className,\n defaultMethod = 'password',\n onPasswordSubmitted,\n onMagicLinkSubmitted,\n onMfaSubmitted,\n } = props\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [method, setMethod] = React.useState<'magic-link' | 'password'>(defaultMethod)\n const [state, setState] = React.useState<FormState>('idle')\n const [error, setError] = React.useState<string | null>(null)\n\n // Password & magic-link inputs\n const [email, setEmail] = React.useState('')\n const [password, setPassword] = React.useState('')\n\n // MFA state\n const [mfa, setMfa] = React.useState<MfaState | null>(null)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const resetTransientState = React.useCallback(() => {\n setState('idle')\n setError(null)\n setMfa(null)\n setMfaCode('')\n }, [])\n\n const handleTabChange = (next: string) => {\n if (next !== 'magic-link' && next !== 'password') return\n setMethod(next)\n resetTransientState()\n }\n\n const handleMagicLinkSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(magicLinkRequestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (res.ok) {\n setState('magic-link-success')\n onMagicLinkSubmitted?.(trimmed)\n return\n }\n\n setState('error')\n setError(c.networkError)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handlePasswordSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n if (!password) {\n setError(c.invalidCredentialsError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(passwordLoginUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed, password }),\n })\n\n const json = (await res.json().catch(() => ({}))) as PasswordLoginResponse\n\n if (!res.ok || !json.data) {\n setState('error')\n setError(json.error ?? c.invalidCredentialsError)\n return\n }\n\n onPasswordSubmitted?.(trimmed)\n\n if (json.data.mfaRequired) {\n setMfa({\n factorId: json.data.factorId ?? '',\n accessToken: json.data.accessToken ?? '',\n refreshToken: json.data.refreshToken ?? '',\n })\n setState('mfa-required')\n return\n }\n\n const exchangeUrl = json.data.sessionExchangeUrl\n if (!exchangeUrl || !isSafeRedirectUrl(exchangeUrl)) {\n setState('error')\n setError(c.networkError)\n return\n }\n window.location.assign(exchangeUrl)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handleMfaSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n if (!mfa) return\n setError(null)\n setState('submitting')\n\n try {\n const res = await fetch(mfaVerifyUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n factorId: mfa.factorId,\n code: mfaCode,\n accessToken: mfa.accessToken,\n refreshToken: mfa.refreshToken,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as MfaVerifyResponse\n\n if (\n !res.ok ||\n !json.data?.sessionExchangeUrl ||\n !isSafeRedirectUrl(json.data.sessionExchangeUrl)\n ) {\n // Stay in mfa-required state so user can retry the code\n setState('mfa-required')\n setError(json.error ?? c.mfaInvalidCodeError)\n setMfaCode('')\n return\n }\n\n onMfaSubmitted?.()\n window.location.assign(json.data.sessionExchangeUrl)\n } catch {\n setState('mfa-required')\n setError(c.networkError)\n }\n }\n\n // MFA prompt replaces the form entirely\n if (state === 'mfa-required' || (mfa && state === 'submitting')) {\n return (\n <div className={cn('space-y-6', className)}>\n <div className='space-y-1'>\n <h2 className='text-xl font-semibold'>{c.mfaTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.mfaMessage}</p>\n </div>\n <form onSubmit={(e) => void handleMfaSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-mfa-code'>{c.mfaCodeLabel}</Label>\n <Input\n id='login-mfa-code'\n type='text'\n inputMode='numeric'\n pattern='\\d{6}'\n maxLength={6}\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(ev) => setMfaCode(ev.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-mfa-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='login-mfa-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button\n type='submit'\n className='w-full'\n disabled={state === 'submitting' || mfaCode.length !== 6}\n >\n {c.mfaSubmitCta}\n </Button>\n <button\n type='button'\n onClick={resetTransientState}\n className='text-muted-foreground hover:text-foreground w-full text-sm transition-colors'\n disabled={state === 'submitting'}\n >\n ← Volver\n </button>\n </form>\n </div>\n )\n }\n\n // Magic-link success replaces the form\n if (state === 'magic-link-success') {\n return (\n <div className={cn('space-y-2 text-center', className)}>\n <h2 className='text-xl font-semibold'>{c.magicLinkSuccessTitle}</h2>\n <p className='text-muted-foreground'>{c.magicLinkSuccessMessage}</p>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-6', className)}>\n <Tabs value={method} onValueChange={handleTabChange}>\n <TabsList className='grid w-full grid-cols-2'>\n <TabsTrigger value='password'>{c.passwordTabLabel}</TabsTrigger>\n <TabsTrigger value='magic-link'>{c.magicLinkTabLabel}</TabsTrigger>\n </TabsList>\n\n <TabsContent value='password' className='pt-4'>\n <form onSubmit={(e) => void handlePasswordSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-pwd'>{c.emailLabel}</Label>\n <Input\n id='login-email-pwd'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n />\n </div>\n <div className='space-y-2'>\n <div className='flex items-center justify-between'>\n <Label htmlFor='login-password'>{c.passwordLabel}</Label>\n <a\n href={resetPasswordUrl}\n className='text-muted-foreground hover:text-foreground text-xs transition-colors'\n >\n {c.forgotPasswordCta}\n </a>\n </div>\n <Input\n id='login-password'\n type='password'\n autoComplete='current-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(ev) => setPassword(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-pwd-error' : undefined}\n />\n {error && (\n <p id='login-pwd-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.passwordSubmitCta}\n </Button>\n </form>\n </TabsContent>\n\n <TabsContent value='magic-link' className='pt-4'>\n <form onSubmit={(e) => void handleMagicLinkSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-magic'>{c.emailLabel}</Label>\n <Input\n id='login-email-magic'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-magic-error' : undefined}\n />\n {error && (\n <p id='login-magic-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.magicLinkSubmitCta}\n </Button>\n </form>\n </TabsContent>\n </Tabs>\n </div>\n )\n}\n","/**\n * Scheme + URL-trick allowlist for a server-returned redirect target\n * (`sessionExchangeUrl`, `redirectTo`, etc.). Shared by `AuthCallback`,\n * `AuthConfirm`, and `SetPassword` — see `CLAUDE.md` Regla A: a predicate\n * applied more than 3 times gets one home, not N inline copies that drift.\n *\n * IMPORTANT — this is NOT full open-redirect confinement: it does NOT\n * restrict absolute `http`/`https` URLs to a specific origin.\n * `isSafeRedirectUrl('https://evil.com')` returns `true`. What it DOES\n * reject:\n * - dangerous schemes (`javascript:`, `data:`, `vbscript:`, ...) — the\n * classic XSS-via-redirect vector\n * - protocol-relative URLs (`//evil.com`) — these start with `/` but\n * browsers resolve them as an absolute, CROSS-ORIGIN URL (the scheme is\n * inherited from the current page), so a naive \"starts with /\" check\n * would wrongly treat them as same-origin\n * - backslash-authority URLs (`/\\evil.com`, `\\\\evil.com`) — browsers\n * normalize `\\` to `/` for special schemes, so these resolve the same way\n * - the same tricks with an ASCII tab/LF/CR spliced between the slashes\n * (`/\\t/evil.com`) — the WHATWG URL parser strips ALL tab/LF/CR from the\n * input BEFORE parsing (anywhere in the string, not just leading/trailing),\n * so `/\\t/evil.com` is indistinguishable from `//evil.com` to the browser\n * even though a naive regex over the raw string wouldn't see two adjacent\n * slashes\n *\n * Full origin-pinning (rejecting any absolute URL whose origin isn't the\n * actual expected one) is a SEPARATE, not-yet-implemented layer. It can't\n * simply pin to `REBLU_API_BASE_URL`: that's a server-only env var\n * unavailable in these `'use client'` components, and per REB-218's\n * subdomain redirect architecture it would be the WRONG origin anyway\n * (production session-exchange lives at `nexus.<tenantDomain>`, not the\n * SITE's own `REBLU_API_BASE_URL`). It would need a dedicated prop\n * conveying the actual expected origin per deploy/routing mode.\n */\nexport function isSafeRedirectUrl(url: string): boolean {\n if (!url) return false\n\n // Match the WHATWG URL parser's own first step: strip ALL ASCII\n // tab/LF/CR from the input, anywhere in the string — not just\n // leading/trailing — before evaluating what the browser will actually\n // resolve. Without this, '/\\t/evil.com' looks like a single-'/' relative\n // path to a naive check but the browser sees '//evil.com'.\n const clean = url.replace(/[\\t\\n\\r]/g, '')\n if (!clean) return false\n\n // A relative path must start with a SINGLE '/' — '//' or '/\\' resolve as\n // an absolute, cross-origin URL in the browser despite \"starting with /\".\n if (/^[/\\\\]{2}/.test(clean)) return false\n\n const scheme = clean.split(':')[0].toLowerCase()\n return ['http', 'https'].includes(scheme) || clean.startsWith('/')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthCallbackCopy {\n loadingTitle: string\n loadingMessage: string\n successMessage: string\n expiredTitle: string\n expiredMessage: string\n expiredCta: string\n networkErrorTitle: string\n networkErrorMessage: string\n networkErrorRetryCta: string\n networkErrorBackCta: string\n malformedTitle: string\n malformedMessage: string\n malformedCta: string\n}\n\nexport interface AuthCallbackProps {\n exchangeUrl: string\n loginUrl: string\n copy?: Partial<AuthCallbackCopy>\n onSuccess?: (redirectTo: string) => void\n onError?: (state: 'expired' | 'network' | 'malformed', error?: Error) => void\n className?: string\n}\n\ntype State = 'mounting' | 'parsing' | 'exchanging' | 'success' | 'expired' | 'network' | 'malformed'\n\nconst DEFAULT_COPY: AuthCallbackCopy = {\n loadingTitle: 'Autenticando...',\n loadingMessage: 'Estamos verificando tu identidad.',\n successMessage: 'Iniciando sesión...',\n expiredTitle: 'Enlace caducado',\n expiredMessage: 'El enlace ha expirado o ya fue utilizado. Solicita uno nuevo.',\n expiredCta: 'Volver al login',\n networkErrorTitle: 'Error de conexión',\n networkErrorMessage: 'No pudimos completar el inicio de sesión. Inténtalo de nuevo.',\n networkErrorRetryCta: 'Reintentar',\n networkErrorBackCta: 'Volver al login',\n malformedTitle: 'Enlace inválido',\n malformedMessage: 'Enlace inválido. Solicita uno nuevo.',\n malformedCta: 'Volver al login',\n}\n\nfunction parseHash(hash: string): { access_token: string; refresh_token: string } | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const access_token = params.get('access_token') ?? ''\n const refresh_token = params.get('refresh_token') ?? ''\n const type = params.get('type') ?? ''\n if (!access_token || !refresh_token || type !== 'magiclink') return null\n return { access_token, refresh_token }\n}\n\n/**\n * On failure Supabase redirects to the callback with the reason in the hash\n * fragment (e.g. `#error=access_denied&error_code=otp_expired`), never in the\n * query — the server cannot see it (REB-177). Detect it client-side and tell an\n * expired/used link (recoverable → 'expired') apart from any other failure\n * (→ 'malformed').\n */\nfunction parseHashError(hash: string): 'expired' | 'malformed' | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const error = params.get('error')\n const errorCode = params.get('error_code')\n if (!error && !errorCode) return null\n // Expired or already-used magic links come back as access_denied / otp_expired.\n return errorCode === 'otp_expired' || error === 'access_denied' ? 'expired' : 'malformed'\n}\n\nexport function AuthCallback({\n exchangeUrl,\n loginUrl,\n copy,\n onSuccess,\n onError,\n className,\n}: AuthCallbackProps) {\n const [state, setState] = React.useState<State>('mounting')\n const [tokens, setTokens] = React.useState<{\n access_token: string\n refresh_token: string\n } | null>(null)\n const retryRef = React.useRef<HTMLButtonElement>(null)\n const linkRef = React.useRef<HTMLAnchorElement>(null)\n const c = { ...DEFAULT_COPY, ...copy }\n\n const exchange = React.useCallback(\n async (t: { access_token: string; refresh_token: string }) => {\n setState('exchanging')\n try {\n const res = await fetch(exchangeUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(t),\n })\n\n if (res.ok) {\n const raw = (await res.json()) as unknown\n const redirectTo =\n raw !== null &&\n typeof raw === 'object' &&\n 'redirectTo' in raw &&\n typeof (raw as { redirectTo: unknown }).redirectTo === 'string'\n ? (raw as { redirectTo: string }).redirectTo\n : null\n\n if (!redirectTo) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n // Guard against dangerous schemes (javascript:, data:, etc.) AND\n // protocol-relative/backslash-authority URLs (//evil.com, /\\evil.com)\n // — see safe-redirect.ts.\n if (!isSafeRedirectUrl(redirectTo)) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n history.replaceState({}, '', window.location.pathname)\n window.location.replace(redirectTo)\n setState('success')\n onSuccess?.(redirectTo)\n return\n }\n\n if (res.status === 401) {\n setState('expired')\n onError?.('expired')\n return\n }\n\n setState('network')\n onError?.('network', undefined)\n } catch (err) {\n setState('network')\n onError?.('network', err instanceof Error ? err : undefined)\n }\n },\n [exchangeUrl, onError, onSuccess],\n )\n\n React.useEffect(() => {\n setState('parsing')\n // A failure reported in the hash (expired/used/invalid link) is not a token\n // to exchange — surface it directly without hitting the network (REB-177).\n const hashError = parseHashError(window.location.hash)\n if (hashError) {\n setState(hashError)\n onError?.(hashError)\n return\n }\n const parsed = parseHash(window.location.hash)\n if (!parsed) {\n setState('malformed')\n onError?.('malformed')\n return\n }\n setTokens(parsed)\n void exchange(parsed)\n // `exchange`/`onError` are intentionally excluded: this effect must run\n // EXACTLY ONCE on mount to parse `window.location.hash` — re-running it\n // on every `exchange` identity change (which itself changes whenever\n // `onSuccess`/`onError` props change identity) would re-parse an\n // already-cleared hash and misfire.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n React.useEffect(() => {\n if (state === 'network') retryRef.current?.focus()\n if (state === 'expired' || state === 'malformed') linkRef.current?.focus()\n }, [state])\n\n const isLoading = state === 'mounting' || state === 'parsing' || state === 'exchanging'\n\n return (\n <main className={cn('flex min-h-screen items-center justify-center', className)}>\n <div aria-live='polite' className='w-full max-w-md space-y-4 p-6 text-center'>\n {isLoading && (\n <section>\n <h2 className='text-xl font-semibold'>{c.loadingTitle}</h2>\n <p className='text-muted-foreground mt-2'>{c.loadingMessage}</p>\n </section>\n )}\n\n {state === 'success' && (\n <section>\n <p>{c.successMessage}</p>\n </section>\n )}\n\n {state === 'expired' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.expiredTitle}</h2>\n <p className='text-muted-foreground'>{c.expiredMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.expiredCta}\n </a>\n </Button>\n </section>\n )}\n\n {state === 'network' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.networkErrorTitle}</h2>\n <p className='text-muted-foreground'>{c.networkErrorMessage}</p>\n <div className='flex flex-col gap-2'>\n <Button type='button' ref={retryRef} onClick={() => tokens && void exchange(tokens)}>\n {c.networkErrorRetryCta}\n </Button>\n <Button asChild variant='outline'>\n <a href={loginUrl}>{c.networkErrorBackCta}</a>\n </Button>\n </div>\n </section>\n )}\n\n {state === 'malformed' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.malformedTitle}</h2>\n <p className='text-muted-foreground'>{c.malformedMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.malformedCta}\n </a>\n </Button>\n </section>\n )}\n </div>\n </main>\n )\n}\n","/**\n * Maps the raw `?error=` query param that Supabase appends when a magic link is\n * already consumed or expired to a user-facing Spanish message.\n *\n * Supabase redirects to /login?error=... with a raw English message (e.g.\n * \"Email link is invalid or has expired\"). Showing that verbatim is confusing,\n * so we translate the common cases and signal whether the \"Solicitar otro\n * enlace\" CTA should be offered.\n *\n * Matching is case-insensitive and whitespace-tolerant so it survives small\n * variations in how the error text arrives.\n *\n * Relocated from apps/demo-site/src/lib/utils/login-errors.ts (REB-510 slice\n * 2a) as a copy-injectable helper — Spanish defaults are unchanged, and a SITE\n * MAY override individual messages via `Partial<LoginErrorCopy>`.\n */\n\nexport interface LoginErrorInfo {\n /** Message shown in the error card. */\n message: string\n /** Whether to show the \"Solicitar otro enlace\" CTA. */\n showRequestLink: boolean\n}\n\nexport interface LoginErrorCopy {\n expiredMessage: string\n missingTokenMessage: string\n genericMessage: string\n}\n\nconst DEFAULT_COPY: LoginErrorCopy = {\n expiredMessage: 'El enlace ha expirado o ya fue utilizado.',\n missingTokenMessage: 'El enlace no es válido. Solicita uno nuevo.',\n genericMessage: 'No se pudo completar el inicio de sesión. Intenta de nuevo.',\n}\n\nexport function mapLoginError(\n error: string | null | undefined,\n copy?: Partial<LoginErrorCopy>,\n): LoginErrorInfo | null {\n if (!error) return null\n\n const normalized = error.trim().toLowerCase()\n if (normalized === '') return null\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n if (\n normalized.includes('email link is invalid or has expired') ||\n normalized.includes('token has expired or is invalid')\n ) {\n return { message: c.expiredMessage, showRequestLink: true }\n }\n\n if (normalized.includes('missing_token')) {\n return { message: c.missingTokenMessage, showRequestLink: true }\n }\n\n return { message: c.genericMessage, showRequestLink: true }\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useTheme } from 'next-themes'\nimport { cn } from '@reblu/site-ui'\n\nexport interface AuthLogoCopy {\n /**\n * Overrides the rendered `<img>` alt text. Pass `''` explicitly to mark the\n * logo as decorative (e.g. when an adjacent heading already announces\n * `siteName`, so screen readers shouldn't read it twice) — `undefined`\n * (the key simply omitted from `copy`) is what falls back to `siteName`.\n */\n altText?: string\n /** Shown when neither a logo URL nor `siteName` is available. */\n fallbackLabel: string\n}\n\nexport interface AuthLogoProps {\n logoLightExpandedUrl: string | null\n logoDarkExpandedUrl: string | null\n logoLightSmallUrl: string | null\n logoDarkSmallUrl: string | null\n siteName: string\n /** @default 'expanded' */\n variant?: 'expanded' | 'compact'\n className?: string\n copy?: Partial<AuthLogoCopy>\n /**\n * When `true`, renders `null` instead of the `siteName`/fallback-label text\n * span whenever no logo URL resolves for the active variant+theme, rather\n * than showing the text. Use this when the consumer already renders\n * `siteName` as a heading elsewhere on the page — without it, a visitor\n * with no logo configured (or only a light-mode logo, viewed in dark mode)\n * would see/hear `siteName` twice: once from this fallback, once from the\n * consumer's own heading.\n * @default false\n */\n hideTextFallback?: boolean\n}\n\nconst DEFAULT_COPY: Required<Pick<AuthLogoCopy, 'fallbackLabel'>> = {\n fallbackLabel: 'Inicio de sesión',\n}\n\nconst VARIANT_CLASS: Record<'expanded' | 'compact', string> = {\n expanded: 'h-8 w-auto',\n compact: 'h-8 w-8',\n}\n\n// Intrinsic `width`/`height` attributes so the browser reserves layout space\n// via its built-in aspect-ratio-from-attributes heuristic before the real\n// image loads (avoids vertical CLS at minimum; horizontal width still\n// depends on the tenant's actual logo aspect ratio, which `/api/site/config`\n// doesn't expose — an arbitrary-but-reasonable box, not a promise of zero\n// shift). `VARIANT_CLASS`'s `h-8` always wins for height regardless.\nconst VARIANT_INTRINSIC_SIZE: Record<'expanded' | 'compact', { width: number; height: number }> = {\n expanded: { width: 160, height: 32 },\n compact: { width: 32, height: 32 },\n}\n\n/**\n * Dynamic tenant logo for auth pages (REB-510 R1). Resolves expanded/compact ×\n * light/dark from the 4 `/api/site/config` logo URLs, switching light/dark\n * CLIENT-SIDE via `next-themes`. Falls back to `siteName` text, then a generic\n * `copy.fallbackLabel`, whenever the resolved URL for the active\n * variant+theme combination is `null` — it never silently swaps in the OTHER\n * theme's logo.\n *\n * Hydration-safe: `next-themes`' `resolvedTheme` is `undefined` until it\n * mounts client-side, so pre-mount `isDark` is always `false` — the same\n * light-first markup the server rendered, avoiding a hydration mismatch.\n *\n * Renders a plain `<img>`, not `next/image`: this is a distributable SDK\n * consumed by external SITE repos, so it cannot assume the consuming app's\n * `next.config` `images.remotePatterns` allowlists the tenant's logo CDN.\n */\nexport function AuthLogo(props: AuthLogoProps) {\n const {\n logoLightExpandedUrl,\n logoDarkExpandedUrl,\n logoLightSmallUrl,\n logoDarkSmallUrl,\n siteName,\n variant = 'expanded',\n className,\n copy,\n hideTextFallback = false,\n } = props\n const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY.fallbackLabel\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n\n const lightUrl = variant === 'compact' ? logoLightSmallUrl : logoLightExpandedUrl\n const darkUrl = variant === 'compact' ? logoDarkSmallUrl : logoDarkExpandedUrl\n const logoUrl = isDark ? darkUrl : lightUrl\n\n // Tracks a broken/stale logo URL (404, CDN outage, etc.) — degrades to the\n // text fallback instead of a broken-image glyph. Reset whenever `logoUrl`\n // itself changes (e.g. a theme toggle resolves a DIFFERENT URL) so a past\n // failure doesn't permanently block a future, valid attempt.\n const [imgError, setImgError] = React.useState(false)\n React.useEffect(() => {\n setImgError(false)\n }, [logoUrl])\n\n const fallbackText = siteName || fallbackLabel\n // `copy.altText` — a VALUE-defined key (including `''`) wins outright,\n // that's how a consumer marks the logo decorative. A value-undefined key\n // (whether omitted from `copy` entirely, or present with an explicit\n // `undefined` value) falls back to siteName/fallbackLabel — checking value\n // definedness, not `'altText' in copy` key presence, is what makes\n // `copy={{ altText: undefined }}` behave the same as omitting the key.\n const altText = copy?.altText !== undefined ? copy.altText : siteName || fallbackLabel\n\n if (logoUrl && !imgError) {\n const { width, height } = VARIANT_INTRINSIC_SIZE[variant]\n return (\n <img\n src={logoUrl}\n alt={altText}\n width={width}\n height={height}\n className={cn(VARIANT_CLASS[variant], className)}\n onError={() => setImgError(true)}\n />\n )\n }\n\n if (hideTextFallback) return null\n\n return (\n <span className={cn('text-lg font-semibold text-foreground', className)}>{fallbackText}</span>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\nexport interface ResetPasswordRequestCopy {\n emailLabel: string\n emailPlaceholder: string\n submitCta: string\n submittingCta: string\n successTitle: string\n successMessage: string\n backToLoginCta: string\n invalidEmailError: string\n networkError: string\n}\n\nexport interface ResetPasswordRequestProps {\n requestUrl: string\n loginUrl: string\n copy?: Partial<ResetPasswordRequestCopy>\n className?: string\n onSubmitted?: (email: string) => void\n}\n\nconst DEFAULT_COPY: ResetPasswordRequestCopy = {\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n submitCta: 'Enviar enlace de recuperación',\n submittingCta: 'Enviando...',\n successTitle: 'Revisa tu email',\n successMessage:\n 'Si el email existe en nuestra plataforma, recibirás un enlace para restablecer tu contraseña en los próximos minutos.',\n backToLoginCta: '← Volver al inicio de sesión',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'Error de conexión. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Step 1 of the password-reset flow — anti-enumeration: always shows the same\n * generic success message on a 2xx response, regardless of whether the email\n * exists (the backend never leaks that).\n */\nexport function ResetPasswordRequest(props: ResetPasswordRequestProps) {\n const { requestUrl, loginUrl, copy, className, onSubmitted } = props\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [email, setEmail] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [submitted, setSubmitted] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!EMAIL_REGEX.test(trimmed)) {\n setError(c.invalidEmailError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSubmitted` callback from this\n // try/catch: if we called it INSIDE the try and it threw, that throw\n // would land in the network `catch` below and misreport a successful\n // request as a connection error.\n let succeeded = false\n try {\n const res = await fetch(requestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as { error?: string }\n setError(json.error ?? c.networkError)\n setLoading(false)\n return\n }\n\n succeeded = true\n setSubmitted(true)\n setLoading(false)\n } catch {\n setError(c.networkError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n try {\n onSubmitted?.(trimmed)\n } catch (err) {\n console.error('[ResetPasswordRequest] onSubmitted callback threw:', err)\n }\n }\n }\n\n if (submitted) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-request-email'>{c.emailLabel}</Label>\n <Input\n id='reset-request-email'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={loading}\n aria-describedby={error ? 'reset-request-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='reset-request-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={loading}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n <div className='text-center'>\n <a\n href={loginUrl}\n className='text-muted-foreground hover:text-foreground text-sm transition-colors'\n >\n {c.backToLoginCta}\n </a>\n </div>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\n/**\n * Appends `key=value` to `url` without producing a malformed double `?` when\n * `url` (consumer-supplied `loginUrl`, e.g. `/login?tenant=acme`) already\n * carries a query string. Deliberately NOT `new URL(url)` — `loginUrl` is\n * typically a relative path and the WHATWG `URL` constructor requires a base\n * for those.\n */\nfunction withQueryParam(url: string, key: string, value: string): string {\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}${key}=${encodeURIComponent(value)}`\n}\n\nexport interface ResetPasswordConfirmCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaHelpMessage: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n mfaInvalidCodeError: string\n genericError: string\n missingTokensTitle: string\n missingTokensMessage: string\n requestNewLinkCta: string\n}\n\nexport interface ResetPasswordConfirmProps {\n confirmUrl: string\n loginUrl: string\n resetRequestUrl: string\n copy?: Partial<ResetPasswordConfirmCopy>\n className?: string\n onSuccess?: () => void\n}\n\nconst DEFAULT_COPY: ResetPasswordConfirmCopy = {\n passwordLabel: 'Nueva contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n mfaCodeLabel: 'Código de verificación (2FA)',\n mfaCodePlaceholder: '6 dígitos',\n mfaHelpMessage:\n 'Tu cuenta tiene verificación en dos pasos. Introduce el código de tu app de autenticación para completar el cambio.',\n submitCta: 'Establecer nueva contraseña',\n submittingCta: 'Actualizando contraseña...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n mfaInvalidCodeError: 'Introduce el código de 6 dígitos de tu app de autenticación',\n genericError: 'Error al restablecer la contraseña',\n missingTokensTitle: 'Enlace inválido',\n missingTokensMessage: 'El enlace ha expirado o no es válido. Solicita uno nuevo.',\n requestNewLinkCta: 'Solicitar nuevo enlace',\n}\n\n/**\n * Step 2 of the password-reset flow. Reads `access_token`/`refresh_token`\n * straight off `useSearchParams` — no `window.location.search` + effect-timing\n * workaround needed here because, unlike a page-level default export, this is\n * a component the SITE mounts inside its own `<Suspense>` boundary.\n *\n * REB-378: when the account has a verified MFA factor, the recovery session\n * (AAL1) cannot change the password on its own. The backend responds\n * `{ data: { mfaRequired: true } }` and this reveals a 6-digit TOTP field so\n * the user can resubmit with the code (the recovery tokens stay valid).\n */\nexport function ResetPasswordConfirm(props: ResetPasswordConfirmProps) {\n const { confirmUrl, loginUrl, resetRequestUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const accessToken = searchParams.get('access_token')\n const refreshToken = searchParams.get('refresh_token')\n const hasTokens = Boolean(accessToken && refreshToken)\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mfaRequired, setMfaRequired] = React.useState(false)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!hasTokens) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n if (mfaRequired && !/^\\d{6}$/.test(mfaCode)) {\n setError(c.mfaInvalidCodeError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSuccess` callback (and the\n // navigation) from this try/catch: calling either INSIDE the try means a\n // throwing callback lands in the network `catch` below and misreports an\n // already-successful password change as a failure — the user retries\n // with recovery tokens the backend has already consumed.\n let succeeded = false\n try {\n const res = await fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: accessToken,\n refresh_token: refreshToken,\n new_password: password,\n ...(mfaRequired ? { mfa_code: mfaCode } : {}),\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n error?: string\n data?: { success?: boolean; mfaRequired?: boolean }\n }\n\n if (!res.ok) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (json.data?.mfaRequired) {\n setMfaRequired(true)\n setLoading(false)\n return\n }\n\n succeeded = true\n setLoading(false)\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch) — same reasoning as set-password.tsx: a throwing\n // `onSuccess` can no longer block the redirect or escape as an\n // unhandled rejection out of this fire-and-forget async handler.\n router.replace(withQueryParam(loginUrl, 'reset', 'success'))\n try {\n onSuccess?.()\n } catch (err) {\n console.error('[ResetPasswordConfirm] onSuccess callback threw:', err)\n }\n }\n }\n\n if (!hasTokens) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokensTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokensMessage}</p>\n <a href={resetRequestUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.requestNewLinkCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password'>{c.passwordLabel}</Label>\n <Input\n id='reset-confirm-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password-2'>{c.confirmPasswordLabel}</Label>\n <Input\n id='reset-confirm-password-2'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n />\n </div>\n {mfaRequired && (\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-mfa'>{c.mfaCodeLabel}</Label>\n <Input\n id='reset-confirm-mfa'\n type='text'\n inputMode='numeric'\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(e) => setMfaCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={loading}\n maxLength={6}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n <p className='text-muted-foreground text-xs'>{c.mfaHelpMessage}</p>\n </div>\n )}\n {error && (\n <p id='reset-confirm-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button\n type='submit'\n className='w-full'\n disabled={\n loading || !password || !confirmPassword || (mfaRequired && mfaCode.length !== 6)\n }\n >\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface SetPasswordCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n genericError: string\n missingTokenTitle: string\n missingTokenMessage: string\n backToLoginCta: string\n}\n\nexport interface SetPasswordProps {\n submitUrl: string\n loginUrl: string\n copy?: Partial<SetPasswordCopy>\n className?: string\n onSuccess?: (sessionExchangeUrl: string) => void\n}\n\nconst DEFAULT_COPY: SetPasswordCopy = {\n passwordLabel: 'Contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n submitCta: 'Activar cuenta',\n submittingCta: 'Activando cuenta...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n genericError: 'Error al establecer la contraseña',\n missingTokenTitle: 'Enlace inválido',\n missingTokenMessage: 'El enlace de invitación ha expirado o no es válido.',\n backToLoginCta: '← Volver al inicio de sesión',\n}\n\n/**\n * Invite flow — for a user setting their initial password. Reads\n * `session_exchange_token` off `useSearchParams`; on success, redirects via\n * `window.location.assign` (NOT the Next router) because the returned\n * `sessionExchangeUrl` points at base-api's own domain to set cookies, not a\n * same-origin SITE route.\n */\nexport function SetPassword(props: SetPasswordProps) {\n const { submitUrl, loginUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n const sessionExchangeToken = searchParams.get('session_exchange_token')\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!sessionExchangeToken) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n\n setLoading(true)\n // `sessionExchangeUrl` isolates the consumer's `onSuccess` callback (and\n // the actual navigation) from this try/catch: calling either INSIDE the\n // try means a throwing callback lands in the network `catch` below and\n // misreports an already-successful password-set as a connection error —\n // stranding an activated user on a form that tells them it failed.\n let sessionExchangeUrl: string | null = null\n try {\n const res = await fetch(submitUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_exchange_token: sessionExchangeToken,\n new_password: password,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string }\n error?: string\n }\n\n if (!res.ok || !json.data?.sessionExchangeUrl) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n sessionExchangeUrl = json.data.sessionExchangeUrl\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch): if `onSuccess` throws (ad blocker on an analytics call,\n // etc.) the navigation has already been triggered and is unaffected, and\n // the throw doesn't escape as an unhandled rejection out of this\n // fire-and-forget async handler — the user is never stranded on this\n // form by code that isn't even this component's own.\n window.location.assign(sessionExchangeUrl)\n try {\n onSuccess?.(sessionExchangeUrl)\n } catch (err) {\n console.error('[SetPassword] onSuccess callback threw:', err)\n }\n }\n\n if (!sessionExchangeToken) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokenTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokenMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-password'>{c.passwordLabel}</Label>\n <Input\n id='set-password-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n // Single-purpose activation page (this IS the whole page's task,\n // there's no earlier content competing for attention) — same\n // pattern as reset-password-confirm.tsx/reset-password-request.tsx.\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-confirm'>{c.confirmPasswordLabel}</Label>\n <Input\n id='set-password-confirm'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n />\n </div>\n {error && (\n <p id='set-password-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button type='submit' className='w-full' disabled={loading || !password || !confirmPassword}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthConfirmCopy {\n loadingMessage: string\n successTitle: string\n successMessage: string\n successCta: string\n errorTitle: string\n errorMessage: string\n errorCta: string\n}\n\nexport interface AuthConfirmProps {\n confirmUrl: string\n loginUrl: string\n copy?: Partial<AuthConfirmCopy>\n className?: string\n}\n\nconst DEFAULT_COPY: AuthConfirmCopy = {\n loadingMessage: 'Confirmando tu email...',\n successTitle: 'Email confirmado',\n successMessage: 'Tu dirección de email ha sido verificada correctamente.',\n successCta: 'Iniciar sesión',\n errorTitle: 'Error al confirmar',\n errorMessage: 'El enlace de confirmación no es válido o ha expirado.',\n errorCta: 'Volver al inicio de sesión',\n}\n\ntype Status = 'loading' | 'success' | 'error'\n\n/**\n * Dual-mode email confirmation (signup / email-change):\n *\n * 1. `?success=true` — base-api already ran `verifyOtp` server-side and\n * redirected here. Shows the confirmed message with no API call.\n * 2. `?token_hash=&type=` — client-initiated verify: POSTs to `confirmUrl`\n * and follows the returned `sessionExchangeUrl`, or shows success/error\n * copy based on the response.\n *\n * Neither param present → shown as an error state with a link back to login,\n * rather than a silent client-side redirect.\n */\nexport function AuthConfirm(props: AuthConfirmProps) {\n const { confirmUrl, loginUrl, copy, className } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n\n const success = searchParams.get('success')\n const tokenHash = searchParams.get('token_hash')\n const type = searchParams.get('type')\n\n const [status, setStatus] = React.useState<Status>(success === 'true' ? 'success' : 'loading')\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (success === 'true') {\n setStatus('success')\n return\n }\n\n if (tokenHash && type) {\n // `token_hash` is single-use server-side — verifying it twice makes\n // the SECOND call fail even though the first already succeeded. React\n // Strict Mode's dev-only mount→cleanup→mount double-invoke would\n // otherwise fire this fetch twice for the same token. Aborting the\n // in-flight request on cleanup stops the CLIENT from processing that\n // first response and swallows its `AbortError` below instead of\n // surfacing a false \"confirmation failed\" — but `abort()` cannot\n // retroactively un-send a request that already reached base-api\n // before cleanup ran, so this reduces rather than deterministically\n // eliminates the double-POST race (in practice React's dev\n // double-invoke runs both phases synchronously in the same commit, so\n // the abort typically wins, but that is not a spec guarantee). A\n // fully deterministic fix needs server-side idempotency on\n // `verify-otp`, which is out of this package's control. The same\n // abort also guards a genuine unmount mid-request.\n const controller = new AbortController()\n setStatus('loading')\n fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token_hash: tokenHash, type }),\n signal: controller.signal,\n })\n .then(async (res) => {\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string; success?: boolean }\n error?: string\n }\n\n if (!res.ok) {\n setStatus('error')\n setErrorMessage(json.error ?? c.errorMessage)\n return\n }\n\n if (json.data?.sessionExchangeUrl) {\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setStatus('error')\n setErrorMessage(c.errorMessage)\n return\n }\n window.location.assign(json.data.sessionExchangeUrl)\n return\n }\n\n setStatus('success')\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') return\n setStatus('error')\n setErrorMessage(c.errorMessage)\n })\n\n return () => controller.abort()\n }\n\n // Neither success=true nor token_hash+type — nothing to confirm.\n setStatus('error')\n // `c.errorMessage` (derived from the `copy` prop) is intentionally\n // excluded: re-running this effect on every `copy` object identity\n // change would re-fire the fetch. The URL params are the only real\n // trigger for this effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [success, tokenHash, type, confirmUrl])\n\n if (status === 'loading') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <p className='text-muted-foreground text-sm'>{c.loadingMessage}</p>\n </div>\n )\n }\n\n if (status === 'error') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.errorTitle}</h2>\n <p className='text-muted-foreground text-sm'>{errorMessage ?? c.errorMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.errorCta}\n </a>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.successCta}\n </a>\n </div>\n )\n}\n"],"mappings":"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","names":["React","import_site_ui","import_jsx_runtime","DEFAULT_COPY","DEFAULT_COPY","React","import_site_ui","import_jsx_runtime","DEFAULT_COPY","React","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","EMAIL_REGEX","React","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","React","import_navigation","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","React","import_navigation","import_site_ui","import_jsx_runtime","DEFAULT_COPY"]}
1
+ {"version":3,"sources":["../src/auth/index.ts","../src/auth/login-form.tsx","../src/auth/safe-redirect.ts","../src/auth/auth-callback.tsx","../src/auth/login-errors.ts","../src/auth/auth-logo.tsx","../src/auth/reset-password-request.tsx","../src/auth/reset-password-confirm.tsx","../src/auth/set-password.tsx","../src/auth/auth-confirm.tsx"],"sourcesContent":["'use client'\n\n/**\n * `@reblu/site-next/auth` — client auth components (REB-510 / design doc\n * \"site-auth-package\").\n *\n * This barrel MUST begin with its OWN leading `'use client'` directive: tsup\n * only preserves a directive prologue declared by the entry file itself, not\n * one merely re-exported from a child module (see the `cta-buttons` R4\n * precedent in `tsup.config.ts`). Without it, `auth.js`/`auth.cjs` would ship\n * as a directive-less bundle and every consuming SITE would silently lose the\n * client boundary.\n *\n * `LoginForm` + `AuthCallback` were MOVED here verbatim from `@reblu/ui`\n * (private, non-distributable) — this is their new public home.\n * `mapLoginError` was MOVED here from demo-site's SITE-local util, now\n * copy-injectable so any SITE can reuse it.\n *\n * `AuthLogo` + the 4 net-new components (`ResetPasswordRequest`,\n * `ResetPasswordConfirm`, `SetPassword`, `AuthConfirm`) were added directly\n * here (REB-510 slice 2b) — copy-injectable with Spanish defaults, same\n * pattern as `LoginForm`/`AuthCallback`.\n */\nexport { LoginForm, type LoginFormProps, type LoginFormCopy } from './login-form'\nexport { AuthCallback, type AuthCallbackProps, type AuthCallbackCopy } from './auth-callback'\nexport {\n mapLoginError,\n type LoginErrorInfo,\n type LoginErrorCopy,\n} from './login-errors'\nexport { AuthLogo, type AuthLogoProps, type AuthLogoCopy } from './auth-logo'\nexport {\n ResetPasswordRequest,\n type ResetPasswordRequestProps,\n type ResetPasswordRequestCopy,\n} from './reset-password-request'\nexport {\n ResetPasswordConfirm,\n type ResetPasswordConfirmProps,\n type ResetPasswordConfirmCopy,\n} from './reset-password-confirm'\nexport { SetPassword, type SetPasswordProps, type SetPasswordCopy } from './set-password'\nexport { AuthConfirm, type AuthConfirmProps, type AuthConfirmCopy } from './auth-confirm'\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@reblu/site-ui/tabs'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface LoginFormCopy {\n // Tab labels\n magicLinkTabLabel: string\n passwordTabLabel: string\n\n // Shared\n emailLabel: string\n emailPlaceholder: string\n\n // Password tab\n passwordLabel: string\n passwordPlaceholder: string\n passwordSubmitCta: string\n forgotPasswordCta: string\n\n // Magic-link tab\n magicLinkSubmitCta: string\n\n // Magic-link success\n magicLinkSuccessTitle: string\n magicLinkSuccessMessage: string\n\n // MFA prompt\n mfaTitle: string\n mfaMessage: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaSubmitCta: string\n\n // Errors\n invalidEmailError: string\n networkError: string\n invalidCredentialsError: string\n mfaInvalidCodeError: string\n}\n\nexport interface LoginFormProps {\n // Endpoint URLs (all required)\n passwordLoginUrl: string\n mfaVerifyUrl: string\n magicLinkRequestUrl: string\n resetPasswordUrl: string\n\n // UX\n copy?: Partial<LoginFormCopy>\n className?: string\n defaultMethod?: 'magic-link' | 'password'\n\n // Hooks\n onPasswordSubmitted?: (email: string) => void\n onMagicLinkSubmitted?: (email: string) => void\n onMfaSubmitted?: () => void\n}\n\nconst DEFAULT_COPY: LoginFormCopy = {\n magicLinkTabLabel: 'Enlace mágico',\n passwordTabLabel: 'Contraseña',\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n passwordLabel: 'Contraseña',\n passwordPlaceholder: '••••••••',\n passwordSubmitCta: 'Iniciar sesión',\n forgotPasswordCta: '¿Olvidaste tu contraseña?',\n magicLinkSubmitCta: 'Enviar enlace de acceso',\n magicLinkSuccessTitle: '¡Enlace enviado!',\n magicLinkSuccessMessage: 'Revisa tu correo para acceder.',\n mfaTitle: 'Verificación en dos pasos',\n mfaMessage: 'Introduce el código de 6 dígitos de tu aplicación autenticadora.',\n mfaCodeLabel: 'Código de verificación',\n mfaCodePlaceholder: '123456',\n mfaSubmitCta: 'Verificar código',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'No se pudo enviar el enlace. Inténtalo de nuevo.',\n invalidCredentialsError: 'Email o contraseña incorrectos',\n mfaInvalidCodeError: 'Código incorrecto. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value)\n}\n\ntype FormState = 'idle' | 'submitting' | 'magic-link-success' | 'mfa-required' | 'error'\n\ninterface MfaState {\n factorId: string\n accessToken: string\n refreshToken: string\n}\n\ninterface PasswordLoginResponse {\n data?: {\n mfaRequired: boolean\n sessionExchangeUrl?: string\n factorId?: string\n accessToken?: string\n refreshToken?: string\n }\n error?: string\n}\n\ninterface MfaVerifyResponse {\n data?: { sessionExchangeUrl: string }\n error?: string\n}\n\nexport function LoginForm(props: LoginFormProps) {\n const {\n passwordLoginUrl,\n mfaVerifyUrl,\n magicLinkRequestUrl,\n resetPasswordUrl,\n copy,\n className,\n defaultMethod = 'password',\n onPasswordSubmitted,\n onMagicLinkSubmitted,\n onMfaSubmitted,\n } = props\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [method, setMethod] = React.useState<'magic-link' | 'password'>(defaultMethod)\n const [state, setState] = React.useState<FormState>('idle')\n const [error, setError] = React.useState<string | null>(null)\n\n // Password & magic-link inputs\n const [email, setEmail] = React.useState('')\n const [password, setPassword] = React.useState('')\n\n // MFA state\n const [mfa, setMfa] = React.useState<MfaState | null>(null)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const resetTransientState = React.useCallback(() => {\n setState('idle')\n setError(null)\n setMfa(null)\n setMfaCode('')\n }, [])\n\n const handleTabChange = (next: string) => {\n if (next !== 'magic-link' && next !== 'password') return\n setMethod(next)\n resetTransientState()\n }\n\n const handleMagicLinkSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(magicLinkRequestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (res.ok) {\n setState('magic-link-success')\n onMagicLinkSubmitted?.(trimmed)\n return\n }\n\n setState('error')\n setError(c.networkError)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handlePasswordSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n if (!password) {\n setError(c.invalidCredentialsError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(passwordLoginUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed, password }),\n })\n\n const json = (await res.json().catch(() => ({}))) as PasswordLoginResponse\n\n if (!res.ok || !json.data) {\n setState('error')\n setError(json.error ?? c.invalidCredentialsError)\n return\n }\n\n onPasswordSubmitted?.(trimmed)\n\n if (json.data.mfaRequired) {\n setMfa({\n factorId: json.data.factorId ?? '',\n accessToken: json.data.accessToken ?? '',\n refreshToken: json.data.refreshToken ?? '',\n })\n setState('mfa-required')\n return\n }\n\n const exchangeUrl = json.data.sessionExchangeUrl\n if (!exchangeUrl || !isSafeRedirectUrl(exchangeUrl)) {\n setState('error')\n setError(c.networkError)\n return\n }\n window.location.assign(exchangeUrl)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handleMfaSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n if (!mfa) return\n setError(null)\n setState('submitting')\n\n try {\n const res = await fetch(mfaVerifyUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n factorId: mfa.factorId,\n code: mfaCode,\n accessToken: mfa.accessToken,\n refreshToken: mfa.refreshToken,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as MfaVerifyResponse\n\n if (\n !res.ok ||\n !json.data?.sessionExchangeUrl ||\n !isSafeRedirectUrl(json.data.sessionExchangeUrl)\n ) {\n // Stay in mfa-required state so user can retry the code\n setState('mfa-required')\n setError(json.error ?? c.mfaInvalidCodeError)\n setMfaCode('')\n return\n }\n\n onMfaSubmitted?.()\n window.location.assign(json.data.sessionExchangeUrl)\n } catch {\n setState('mfa-required')\n setError(c.networkError)\n }\n }\n\n // MFA prompt replaces the form entirely\n if (state === 'mfa-required' || (mfa && state === 'submitting')) {\n return (\n <div className={cn('space-y-6', className)}>\n <div className='space-y-1'>\n <h2 className='text-xl font-semibold'>{c.mfaTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.mfaMessage}</p>\n </div>\n <form onSubmit={(e) => void handleMfaSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-mfa-code'>{c.mfaCodeLabel}</Label>\n <Input\n id='login-mfa-code'\n type='text'\n inputMode='numeric'\n pattern='\\d{6}'\n maxLength={6}\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(ev) => setMfaCode(ev.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-mfa-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='login-mfa-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button\n type='submit'\n className='w-full'\n disabled={state === 'submitting' || mfaCode.length !== 6}\n >\n {c.mfaSubmitCta}\n </Button>\n <button\n type='button'\n onClick={resetTransientState}\n className='text-muted-foreground hover:text-foreground w-full text-sm transition-colors'\n disabled={state === 'submitting'}\n >\n ← Volver\n </button>\n </form>\n </div>\n )\n }\n\n // Magic-link success replaces the form\n if (state === 'magic-link-success') {\n return (\n <div className={cn('space-y-2 text-center', className)}>\n <h2 className='text-xl font-semibold'>{c.magicLinkSuccessTitle}</h2>\n <p className='text-muted-foreground'>{c.magicLinkSuccessMessage}</p>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-6', className)}>\n <Tabs value={method} onValueChange={handleTabChange}>\n <TabsList className='grid w-full grid-cols-2'>\n <TabsTrigger value='password'>{c.passwordTabLabel}</TabsTrigger>\n <TabsTrigger value='magic-link'>{c.magicLinkTabLabel}</TabsTrigger>\n </TabsList>\n\n <TabsContent value='password' className='pt-4'>\n <form onSubmit={(e) => void handlePasswordSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-pwd'>{c.emailLabel}</Label>\n <Input\n id='login-email-pwd'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n />\n </div>\n <div className='space-y-2'>\n <div className='flex items-center justify-between'>\n <Label htmlFor='login-password'>{c.passwordLabel}</Label>\n <a\n href={resetPasswordUrl}\n className='text-muted-foreground hover:text-foreground text-xs transition-colors'\n >\n {c.forgotPasswordCta}\n </a>\n </div>\n <Input\n id='login-password'\n type='password'\n autoComplete='current-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(ev) => setPassword(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-pwd-error' : undefined}\n />\n {error && (\n <p id='login-pwd-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.passwordSubmitCta}\n </Button>\n </form>\n </TabsContent>\n\n <TabsContent value='magic-link' className='pt-4'>\n <form onSubmit={(e) => void handleMagicLinkSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-magic'>{c.emailLabel}</Label>\n <Input\n id='login-email-magic'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-magic-error' : undefined}\n />\n {error && (\n <p id='login-magic-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.magicLinkSubmitCta}\n </Button>\n </form>\n </TabsContent>\n </Tabs>\n </div>\n )\n}\n","/**\n * Scheme + URL-trick allowlist for a server-returned redirect target\n * (`sessionExchangeUrl`, `redirectTo`, etc.). Shared by `AuthCallback`,\n * `AuthConfirm`, and `SetPassword` — see `CLAUDE.md` Regla A: a predicate\n * applied more than 3 times gets one home, not N inline copies that drift.\n *\n * IMPORTANT — this is NOT full open-redirect confinement: it does NOT\n * restrict absolute `http`/`https` URLs to a specific origin.\n * `isSafeRedirectUrl('https://evil.com')` returns `true`. What it DOES\n * reject:\n * - dangerous schemes (`javascript:`, `data:`, `vbscript:`, ...) — the\n * classic XSS-via-redirect vector\n * - protocol-relative URLs (`//evil.com`) — these start with `/` but\n * browsers resolve them as an absolute, CROSS-ORIGIN URL (the scheme is\n * inherited from the current page), so a naive \"starts with /\" check\n * would wrongly treat them as same-origin\n * - backslash-authority URLs (`/\\evil.com`, `\\\\evil.com`) — browsers\n * normalize `\\` to `/` for special schemes, so these resolve the same way\n * - the same tricks with an ASCII tab/LF/CR spliced between the slashes\n * (`/\\t/evil.com`) — the WHATWG URL parser strips ALL tab/LF/CR from the\n * input BEFORE parsing (anywhere in the string, not just leading/trailing),\n * so `/\\t/evil.com` is indistinguishable from `//evil.com` to the browser\n * even though a naive regex over the raw string wouldn't see two adjacent\n * slashes\n *\n * Full origin-pinning (rejecting any absolute URL whose origin isn't the\n * actual expected one) is a SEPARATE, not-yet-implemented layer. It can't\n * simply pin to `REBLU_API_BASE_URL`: that's a server-only env var\n * unavailable in these `'use client'` components, and per REB-218's\n * subdomain redirect architecture it would be the WRONG origin anyway\n * (production session-exchange lives at `nexus.<tenantDomain>`, not the\n * SITE's own `REBLU_API_BASE_URL`). It would need a dedicated prop\n * conveying the actual expected origin per deploy/routing mode.\n */\nexport function isSafeRedirectUrl(url: string): boolean {\n if (!url) return false\n\n // Match the WHATWG URL parser's own first step: strip ALL ASCII\n // tab/LF/CR from the input, anywhere in the string — not just\n // leading/trailing — before evaluating what the browser will actually\n // resolve. Without this, '/\\t/evil.com' looks like a single-'/' relative\n // path to a naive check but the browser sees '//evil.com'.\n const clean = url.replace(/[\\t\\n\\r]/g, '')\n if (!clean) return false\n\n // A relative path must start with a SINGLE '/' — '//' or '/\\' resolve as\n // an absolute, cross-origin URL in the browser despite \"starting with /\".\n if (/^[/\\\\]{2}/.test(clean)) return false\n\n const scheme = clean.split(':')[0].toLowerCase()\n return ['http', 'https'].includes(scheme) || clean.startsWith('/')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthCallbackCopy {\n loadingTitle: string\n loadingMessage: string\n successMessage: string\n expiredTitle: string\n expiredMessage: string\n expiredCta: string\n networkErrorTitle: string\n networkErrorMessage: string\n networkErrorRetryCta: string\n networkErrorBackCta: string\n malformedTitle: string\n malformedMessage: string\n malformedCta: string\n}\n\nexport interface AuthCallbackProps {\n exchangeUrl: string\n loginUrl: string\n copy?: Partial<AuthCallbackCopy>\n onSuccess?: (redirectTo: string) => void\n onError?: (state: 'expired' | 'network' | 'malformed', error?: Error) => void\n className?: string\n}\n\ntype State = 'mounting' | 'parsing' | 'exchanging' | 'success' | 'expired' | 'network' | 'malformed'\n\nconst DEFAULT_COPY: AuthCallbackCopy = {\n loadingTitle: 'Autenticando...',\n loadingMessage: 'Estamos verificando tu identidad.',\n successMessage: 'Iniciando sesión...',\n expiredTitle: 'Enlace caducado',\n expiredMessage: 'El enlace ha expirado o ya fue utilizado. Solicita uno nuevo.',\n expiredCta: 'Volver al login',\n networkErrorTitle: 'Error de conexión',\n networkErrorMessage: 'No pudimos completar el inicio de sesión. Inténtalo de nuevo.',\n networkErrorRetryCta: 'Reintentar',\n networkErrorBackCta: 'Volver al login',\n malformedTitle: 'Enlace inválido',\n malformedMessage: 'Enlace inválido. Solicita uno nuevo.',\n malformedCta: 'Volver al login',\n}\n\nfunction parseHash(hash: string): { access_token: string; refresh_token: string } | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const access_token = params.get('access_token') ?? ''\n const refresh_token = params.get('refresh_token') ?? ''\n const type = params.get('type') ?? ''\n if (!access_token || !refresh_token || type !== 'magiclink') return null\n return { access_token, refresh_token }\n}\n\n/**\n * On failure Supabase redirects to the callback with the reason in the hash\n * fragment (e.g. `#error=access_denied&error_code=otp_expired`), never in the\n * query — the server cannot see it (REB-177). Detect it client-side and tell an\n * expired/used link (recoverable → 'expired') apart from any other failure\n * (→ 'malformed').\n */\nfunction parseHashError(hash: string): 'expired' | 'malformed' | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const error = params.get('error')\n const errorCode = params.get('error_code')\n if (!error && !errorCode) return null\n // Expired or already-used magic links come back as access_denied / otp_expired.\n return errorCode === 'otp_expired' || error === 'access_denied' ? 'expired' : 'malformed'\n}\n\nexport function AuthCallback({\n exchangeUrl,\n loginUrl,\n copy,\n onSuccess,\n onError,\n className,\n}: AuthCallbackProps) {\n const [state, setState] = React.useState<State>('mounting')\n const [tokens, setTokens] = React.useState<{\n access_token: string\n refresh_token: string\n } | null>(null)\n const retryRef = React.useRef<HTMLButtonElement>(null)\n const linkRef = React.useRef<HTMLAnchorElement>(null)\n const c = { ...DEFAULT_COPY, ...copy }\n\n const exchange = React.useCallback(\n async (t: { access_token: string; refresh_token: string }) => {\n setState('exchanging')\n try {\n const res = await fetch(exchangeUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(t),\n })\n\n if (res.ok) {\n const raw = (await res.json()) as unknown\n const redirectTo =\n raw !== null &&\n typeof raw === 'object' &&\n 'redirectTo' in raw &&\n typeof (raw as { redirectTo: unknown }).redirectTo === 'string'\n ? (raw as { redirectTo: string }).redirectTo\n : null\n\n if (!redirectTo) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n // Guard against dangerous schemes (javascript:, data:, etc.) AND\n // protocol-relative/backslash-authority URLs (//evil.com, /\\evil.com)\n // — see safe-redirect.ts.\n if (!isSafeRedirectUrl(redirectTo)) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n history.replaceState({}, '', window.location.pathname)\n window.location.replace(redirectTo)\n setState('success')\n onSuccess?.(redirectTo)\n return\n }\n\n if (res.status === 401) {\n setState('expired')\n onError?.('expired')\n return\n }\n\n setState('network')\n onError?.('network', undefined)\n } catch (err) {\n setState('network')\n onError?.('network', err instanceof Error ? err : undefined)\n }\n },\n [exchangeUrl, onError, onSuccess],\n )\n\n React.useEffect(() => {\n setState('parsing')\n // A failure reported in the hash (expired/used/invalid link) is not a token\n // to exchange — surface it directly without hitting the network (REB-177).\n const hashError = parseHashError(window.location.hash)\n if (hashError) {\n setState(hashError)\n onError?.(hashError)\n return\n }\n const parsed = parseHash(window.location.hash)\n if (!parsed) {\n setState('malformed')\n onError?.('malformed')\n return\n }\n setTokens(parsed)\n void exchange(parsed)\n // `exchange`/`onError` are intentionally excluded: this effect must run\n // EXACTLY ONCE on mount to parse `window.location.hash` — re-running it\n // on every `exchange` identity change (which itself changes whenever\n // `onSuccess`/`onError` props change identity) would re-parse an\n // already-cleared hash and misfire.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n React.useEffect(() => {\n if (state === 'network') retryRef.current?.focus()\n if (state === 'expired' || state === 'malformed') linkRef.current?.focus()\n }, [state])\n\n const isLoading = state === 'mounting' || state === 'parsing' || state === 'exchanging'\n\n return (\n <main className={cn('flex min-h-screen items-center justify-center', className)}>\n <div aria-live='polite' className='w-full max-w-md space-y-4 p-6 text-center'>\n {isLoading && (\n <section>\n <h2 className='text-xl font-semibold'>{c.loadingTitle}</h2>\n <p className='text-muted-foreground mt-2'>{c.loadingMessage}</p>\n </section>\n )}\n\n {state === 'success' && (\n <section>\n <p>{c.successMessage}</p>\n </section>\n )}\n\n {state === 'expired' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.expiredTitle}</h2>\n <p className='text-muted-foreground'>{c.expiredMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.expiredCta}\n </a>\n </Button>\n </section>\n )}\n\n {state === 'network' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.networkErrorTitle}</h2>\n <p className='text-muted-foreground'>{c.networkErrorMessage}</p>\n <div className='flex flex-col gap-2'>\n <Button type='button' ref={retryRef} onClick={() => tokens && void exchange(tokens)}>\n {c.networkErrorRetryCta}\n </Button>\n <Button asChild variant='outline'>\n <a href={loginUrl}>{c.networkErrorBackCta}</a>\n </Button>\n </div>\n </section>\n )}\n\n {state === 'malformed' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.malformedTitle}</h2>\n <p className='text-muted-foreground'>{c.malformedMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.malformedCta}\n </a>\n </Button>\n </section>\n )}\n </div>\n </main>\n )\n}\n","/**\n * Maps the raw `?error=` query param that Supabase appends when a magic link is\n * already consumed or expired to a user-facing Spanish message.\n *\n * Supabase redirects to /login?error=... with a raw English message (e.g.\n * \"Email link is invalid or has expired\"). Showing that verbatim is confusing,\n * so we translate the common cases and signal whether the \"Solicitar otro\n * enlace\" CTA should be offered.\n *\n * Matching is case-insensitive and whitespace-tolerant so it survives small\n * variations in how the error text arrives.\n *\n * Relocated from apps/demo-site/src/lib/utils/login-errors.ts (REB-510 slice\n * 2a) as a copy-injectable helper — Spanish defaults are unchanged, and a SITE\n * MAY override individual messages via `Partial<LoginErrorCopy>`.\n */\n\nexport interface LoginErrorInfo {\n /** Message shown in the error card. */\n message: string\n /** Whether to show the \"Solicitar otro enlace\" CTA. */\n showRequestLink: boolean\n}\n\nexport interface LoginErrorCopy {\n expiredMessage: string\n missingTokenMessage: string\n genericMessage: string\n}\n\nconst DEFAULT_COPY: LoginErrorCopy = {\n expiredMessage: 'El enlace ha expirado o ya fue utilizado.',\n missingTokenMessage: 'El enlace no es válido. Solicita uno nuevo.',\n genericMessage: 'No se pudo completar el inicio de sesión. Intenta de nuevo.',\n}\n\nexport function mapLoginError(\n error: string | null | undefined,\n copy?: Partial<LoginErrorCopy>,\n): LoginErrorInfo | null {\n if (!error) return null\n\n const normalized = error.trim().toLowerCase()\n if (normalized === '') return null\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n if (\n normalized.includes('email link is invalid or has expired') ||\n normalized.includes('token has expired or is invalid')\n ) {\n return { message: c.expiredMessage, showRequestLink: true }\n }\n\n if (normalized.includes('missing_token')) {\n return { message: c.missingTokenMessage, showRequestLink: true }\n }\n\n return { message: c.genericMessage, showRequestLink: true }\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useTheme } from 'next-themes'\nimport { cn } from '@reblu/site-ui'\n\nexport interface AuthLogoCopy {\n /**\n * Overrides the rendered `<img>` alt text. Pass `''` explicitly to mark the\n * logo as decorative (e.g. when an adjacent heading already announces\n * `siteName`, so screen readers shouldn't read it twice) — `undefined`\n * (the key simply omitted from `copy`) is what falls back to `siteName`.\n */\n altText?: string\n /** Shown when neither a logo URL nor `siteName` is available. */\n fallbackLabel: string\n}\n\nexport interface AuthLogoProps {\n logoLightExpandedUrl: string | null\n logoDarkExpandedUrl: string | null\n logoLightSmallUrl: string | null\n logoDarkSmallUrl: string | null\n siteName: string\n /** @default 'expanded' */\n variant?: 'expanded' | 'compact'\n className?: string\n copy?: Partial<AuthLogoCopy>\n /**\n * Heading level for the `siteName`/fallback-label text rendered when no logo\n * resolves. AuthLogo owns the auth page's heading, so this defaults to the\n * top-level `1`; pass a deeper level only if the page already has an `<h1>`\n * elsewhere that AuthLogo must sit beneath.\n * @default 1\n */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6\n /**\n * When `true`, renders `null` instead of the `siteName`/fallback-label\n * heading whenever no logo URL resolves for the active variant+theme, rather\n * than showing the text. Use this ONLY when the consumer intentionally\n * renders `siteName` elsewhere itself — by default AuthLogo IS the heading\n * (\"logo XOR name\"), so the consumer should NOT add its own\n * `<h1>{siteName}</h1>`.\n * @default false\n */\n hideTextFallback?: boolean\n}\n\nconst DEFAULT_COPY: Required<Pick<AuthLogoCopy, 'fallbackLabel'>> = {\n fallbackLabel: 'Inicio de sesión',\n}\n\nconst VARIANT_CLASS: Record<'expanded' | 'compact', string> = {\n expanded: 'h-8 w-auto',\n compact: 'h-8 w-8',\n}\n\n// Intrinsic `width`/`height` attributes so the browser reserves layout space\n// via its built-in aspect-ratio-from-attributes heuristic before the real\n// image loads (avoids vertical CLS at minimum; horizontal width still\n// depends on the tenant's actual logo aspect ratio, which `/api/site/config`\n// doesn't expose — an arbitrary-but-reasonable box, not a promise of zero\n// shift). `VARIANT_CLASS`'s `h-8` always wins for height regardless.\nconst VARIANT_INTRINSIC_SIZE: Record<'expanded' | 'compact', { width: number; height: number }> = {\n expanded: { width: 160, height: 32 },\n compact: { width: 32, height: 32 },\n}\n\n/**\n * Dynamic tenant logo for auth pages (REB-510 R1). Resolves expanded/compact ×\n * light/dark from the 4 `/api/site/config` logo URLs, switching light/dark\n * CLIENT-SIDE via `next-themes`. Falls back to `siteName` text, then a generic\n * `copy.fallbackLabel`, whenever the resolved URL for the active\n * variant+theme combination is `null` — it never silently swaps in the OTHER\n * theme's logo.\n *\n * Hydration-safe: `next-themes`' `resolvedTheme` is `undefined` until it\n * mounts client-side, so pre-mount `isDark` is always `false` — the same\n * light-first markup the server rendered, avoiding a hydration mismatch.\n *\n * Renders a plain `<img>`, not `next/image`: this is a distributable SDK\n * consumed by external SITE repos, so it cannot assume the consuming app's\n * `next.config` `images.remotePatterns` allowlists the tenant's logo CDN.\n */\nexport function AuthLogo(props: AuthLogoProps) {\n const {\n logoLightExpandedUrl,\n logoDarkExpandedUrl,\n logoLightSmallUrl,\n logoDarkSmallUrl,\n siteName,\n variant = 'expanded',\n className,\n copy,\n headingLevel = 1,\n hideTextFallback = false,\n } = props\n const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY.fallbackLabel\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n\n const lightUrl = variant === 'compact' ? logoLightSmallUrl : logoLightExpandedUrl\n const darkUrl = variant === 'compact' ? logoDarkSmallUrl : logoDarkExpandedUrl\n const logoUrl = isDark ? darkUrl : lightUrl\n\n // Tracks a broken/stale logo URL (404, CDN outage, etc.) — degrades to the\n // text fallback instead of a broken-image glyph. Reset whenever `logoUrl`\n // itself changes (e.g. a theme toggle resolves a DIFFERENT URL) so a past\n // failure doesn't permanently block a future, valid attempt.\n const [imgError, setImgError] = React.useState(false)\n React.useEffect(() => {\n setImgError(false)\n }, [logoUrl])\n\n const fallbackText = siteName || fallbackLabel\n // `copy.altText` — a VALUE-defined key (including `''`) wins outright,\n // that's how a consumer marks the logo decorative. A value-undefined key\n // (whether omitted from `copy` entirely, or present with an explicit\n // `undefined` value) falls back to siteName/fallbackLabel — checking value\n // definedness, not `'altText' in copy` key presence, is what makes\n // `copy={{ altText: undefined }}` behave the same as omitting the key.\n const altText = copy?.altText !== undefined ? copy.altText : siteName || fallbackLabel\n\n // AuthLogo is \"logo XOR name\": exactly ONE of the brand mark (logo image) or\n // the `siteName` text is shown, never both — and by default AuthLogo IS the\n // auth page's heading, so the consuming page must NOT add its own separate\n // `<h1>{siteName}</h1>` (which is what double-rendered the name before).\n const Heading = `h${headingLevel}` as const\n\n if (logoUrl && !imgError) {\n const { width, height } = VARIANT_INTRINSIC_SIZE[variant]\n // Decorative escape hatch: when the consumer hides the text fallback OR\n // marks the logo decorative (`alt=''`), it owns the page heading itself, so\n // render a bare image — wrapping a nameless image in a heading would emit\n // an empty, accessible-name-less heading.\n const bare = hideTextFallback || altText === ''\n const image = (\n <img\n src={logoUrl}\n alt={altText}\n width={width}\n height={height}\n className={cn(VARIANT_CLASS[variant], 'max-w-full', bare && className)}\n onError={() => setImgError(true)}\n />\n )\n if (bare) return image\n // Default: wrap the logo in the heading so the auth page ALWAYS has a\n // heading — even when only the brand image is visible — whose accessible\n // name is the logo's `alt` (= `siteName`). No visible, duplicated name text.\n return <Heading className={cn('inline-flex items-center max-w-full', className)}>{image}</Heading>\n }\n\n if (hideTextFallback) return null\n\n // No logo resolved → render `siteName` (or the generic fallback label) as the\n // page's heading text.\n return (\n <Heading className={cn('text-2xl font-bold tracking-tight text-foreground', className)}>\n {fallbackText}\n </Heading>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\nexport interface ResetPasswordRequestCopy {\n emailLabel: string\n emailPlaceholder: string\n submitCta: string\n submittingCta: string\n successTitle: string\n successMessage: string\n backToLoginCta: string\n invalidEmailError: string\n networkError: string\n}\n\nexport interface ResetPasswordRequestProps {\n requestUrl: string\n loginUrl: string\n copy?: Partial<ResetPasswordRequestCopy>\n className?: string\n onSubmitted?: (email: string) => void\n}\n\nconst DEFAULT_COPY: ResetPasswordRequestCopy = {\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n submitCta: 'Enviar enlace de recuperación',\n submittingCta: 'Enviando...',\n successTitle: 'Revisa tu email',\n successMessage:\n 'Si el email existe en nuestra plataforma, recibirás un enlace para restablecer tu contraseña en los próximos minutos.',\n backToLoginCta: '← Volver al inicio de sesión',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'Error de conexión. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Step 1 of the password-reset flow — anti-enumeration: always shows the same\n * generic success message on a 2xx response, regardless of whether the email\n * exists (the backend never leaks that).\n */\nexport function ResetPasswordRequest(props: ResetPasswordRequestProps) {\n const { requestUrl, loginUrl, copy, className, onSubmitted } = props\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [email, setEmail] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [submitted, setSubmitted] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!EMAIL_REGEX.test(trimmed)) {\n setError(c.invalidEmailError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSubmitted` callback from this\n // try/catch: if we called it INSIDE the try and it threw, that throw\n // would land in the network `catch` below and misreport a successful\n // request as a connection error.\n let succeeded = false\n try {\n const res = await fetch(requestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as { error?: string }\n setError(json.error ?? c.networkError)\n setLoading(false)\n return\n }\n\n succeeded = true\n setSubmitted(true)\n setLoading(false)\n } catch {\n setError(c.networkError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n try {\n onSubmitted?.(trimmed)\n } catch (err) {\n console.error('[ResetPasswordRequest] onSubmitted callback threw:', err)\n }\n }\n }\n\n if (submitted) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-request-email'>{c.emailLabel}</Label>\n <Input\n id='reset-request-email'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={loading}\n aria-describedby={error ? 'reset-request-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='reset-request-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={loading}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n <div className='text-center'>\n <a\n href={loginUrl}\n className='text-muted-foreground hover:text-foreground text-sm transition-colors'\n >\n {c.backToLoginCta}\n </a>\n </div>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\n/**\n * Appends `key=value` to `url` without producing a malformed double `?` when\n * `url` (consumer-supplied `loginUrl`, e.g. `/login?tenant=acme`) already\n * carries a query string. Deliberately NOT `new URL(url)` — `loginUrl` is\n * typically a relative path and the WHATWG `URL` constructor requires a base\n * for those.\n */\nfunction withQueryParam(url: string, key: string, value: string): string {\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}${key}=${encodeURIComponent(value)}`\n}\n\nexport interface ResetPasswordConfirmCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaHelpMessage: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n mfaInvalidCodeError: string\n genericError: string\n missingTokensTitle: string\n missingTokensMessage: string\n requestNewLinkCta: string\n}\n\nexport interface ResetPasswordConfirmProps {\n confirmUrl: string\n loginUrl: string\n resetRequestUrl: string\n copy?: Partial<ResetPasswordConfirmCopy>\n className?: string\n onSuccess?: () => void\n}\n\nconst DEFAULT_COPY: ResetPasswordConfirmCopy = {\n passwordLabel: 'Nueva contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n mfaCodeLabel: 'Código de verificación (2FA)',\n mfaCodePlaceholder: '6 dígitos',\n mfaHelpMessage:\n 'Tu cuenta tiene verificación en dos pasos. Introduce el código de tu app de autenticación para completar el cambio.',\n submitCta: 'Establecer nueva contraseña',\n submittingCta: 'Actualizando contraseña...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n mfaInvalidCodeError: 'Introduce el código de 6 dígitos de tu app de autenticación',\n genericError: 'Error al restablecer la contraseña',\n missingTokensTitle: 'Enlace inválido',\n missingTokensMessage: 'El enlace ha expirado o no es válido. Solicita uno nuevo.',\n requestNewLinkCta: 'Solicitar nuevo enlace',\n}\n\n/**\n * Step 2 of the password-reset flow. Reads `access_token`/`refresh_token`\n * straight off `useSearchParams` — no `window.location.search` + effect-timing\n * workaround needed here because, unlike a page-level default export, this is\n * a component the SITE mounts inside its own `<Suspense>` boundary.\n *\n * REB-378: when the account has a verified MFA factor, the recovery session\n * (AAL1) cannot change the password on its own. The backend responds\n * `{ data: { mfaRequired: true } }` and this reveals a 6-digit TOTP field so\n * the user can resubmit with the code (the recovery tokens stay valid).\n */\nexport function ResetPasswordConfirm(props: ResetPasswordConfirmProps) {\n const { confirmUrl, loginUrl, resetRequestUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const accessToken = searchParams.get('access_token')\n const refreshToken = searchParams.get('refresh_token')\n const hasTokens = Boolean(accessToken && refreshToken)\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mfaRequired, setMfaRequired] = React.useState(false)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!hasTokens) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n if (mfaRequired && !/^\\d{6}$/.test(mfaCode)) {\n setError(c.mfaInvalidCodeError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSuccess` callback (and the\n // navigation) from this try/catch: calling either INSIDE the try means a\n // throwing callback lands in the network `catch` below and misreports an\n // already-successful password change as a failure — the user retries\n // with recovery tokens the backend has already consumed.\n let succeeded = false\n try {\n const res = await fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: accessToken,\n refresh_token: refreshToken,\n new_password: password,\n ...(mfaRequired ? { mfa_code: mfaCode } : {}),\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n error?: string\n data?: { success?: boolean; mfaRequired?: boolean }\n }\n\n if (!res.ok) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (json.data?.mfaRequired) {\n setMfaRequired(true)\n setLoading(false)\n return\n }\n\n succeeded = true\n setLoading(false)\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch) — same reasoning as set-password.tsx: a throwing\n // `onSuccess` can no longer block the redirect or escape as an\n // unhandled rejection out of this fire-and-forget async handler.\n router.replace(withQueryParam(loginUrl, 'reset', 'success'))\n try {\n onSuccess?.()\n } catch (err) {\n console.error('[ResetPasswordConfirm] onSuccess callback threw:', err)\n }\n }\n }\n\n if (!hasTokens) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokensTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokensMessage}</p>\n <a href={resetRequestUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.requestNewLinkCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password'>{c.passwordLabel}</Label>\n <Input\n id='reset-confirm-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password-2'>{c.confirmPasswordLabel}</Label>\n <Input\n id='reset-confirm-password-2'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n />\n </div>\n {mfaRequired && (\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-mfa'>{c.mfaCodeLabel}</Label>\n <Input\n id='reset-confirm-mfa'\n type='text'\n inputMode='numeric'\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(e) => setMfaCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={loading}\n maxLength={6}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n <p className='text-muted-foreground text-xs'>{c.mfaHelpMessage}</p>\n </div>\n )}\n {error && (\n <p id='reset-confirm-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button\n type='submit'\n className='w-full'\n disabled={\n loading || !password || !confirmPassword || (mfaRequired && mfaCode.length !== 6)\n }\n >\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface SetPasswordCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n genericError: string\n missingTokenTitle: string\n missingTokenMessage: string\n backToLoginCta: string\n}\n\nexport interface SetPasswordProps {\n submitUrl: string\n loginUrl: string\n copy?: Partial<SetPasswordCopy>\n className?: string\n onSuccess?: (sessionExchangeUrl: string) => void\n}\n\nconst DEFAULT_COPY: SetPasswordCopy = {\n passwordLabel: 'Contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n submitCta: 'Activar cuenta',\n submittingCta: 'Activando cuenta...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n genericError: 'Error al establecer la contraseña',\n missingTokenTitle: 'Enlace inválido',\n missingTokenMessage: 'El enlace de invitación ha expirado o no es válido.',\n backToLoginCta: '← Volver al inicio de sesión',\n}\n\n/**\n * Invite flow — for a user setting their initial password. Reads\n * `session_exchange_token` off `useSearchParams`; on success, redirects via\n * `window.location.assign` (NOT the Next router) because the returned\n * `sessionExchangeUrl` points at base-api's own domain to set cookies, not a\n * same-origin SITE route.\n */\nexport function SetPassword(props: SetPasswordProps) {\n const { submitUrl, loginUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n const sessionExchangeToken = searchParams.get('session_exchange_token')\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!sessionExchangeToken) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n\n setLoading(true)\n // `sessionExchangeUrl` isolates the consumer's `onSuccess` callback (and\n // the actual navigation) from this try/catch: calling either INSIDE the\n // try means a throwing callback lands in the network `catch` below and\n // misreports an already-successful password-set as a connection error —\n // stranding an activated user on a form that tells them it failed.\n let sessionExchangeUrl: string | null = null\n try {\n const res = await fetch(submitUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_exchange_token: sessionExchangeToken,\n new_password: password,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string }\n error?: string\n }\n\n if (!res.ok || !json.data?.sessionExchangeUrl) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n sessionExchangeUrl = json.data.sessionExchangeUrl\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch): if `onSuccess` throws (ad blocker on an analytics call,\n // etc.) the navigation has already been triggered and is unaffected, and\n // the throw doesn't escape as an unhandled rejection out of this\n // fire-and-forget async handler — the user is never stranded on this\n // form by code that isn't even this component's own.\n window.location.assign(sessionExchangeUrl)\n try {\n onSuccess?.(sessionExchangeUrl)\n } catch (err) {\n console.error('[SetPassword] onSuccess callback threw:', err)\n }\n }\n\n if (!sessionExchangeToken) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokenTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokenMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-password'>{c.passwordLabel}</Label>\n <Input\n id='set-password-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n // Single-purpose activation page (this IS the whole page's task,\n // there's no earlier content competing for attention) — same\n // pattern as reset-password-confirm.tsx/reset-password-request.tsx.\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-confirm'>{c.confirmPasswordLabel}</Label>\n <Input\n id='set-password-confirm'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n />\n </div>\n {error && (\n <p id='set-password-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button type='submit' className='w-full' disabled={loading || !password || !confirmPassword}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthConfirmCopy {\n loadingMessage: string\n successTitle: string\n successMessage: string\n successCta: string\n errorTitle: string\n errorMessage: string\n errorCta: string\n}\n\nexport interface AuthConfirmProps {\n confirmUrl: string\n loginUrl: string\n copy?: Partial<AuthConfirmCopy>\n className?: string\n}\n\nconst DEFAULT_COPY: AuthConfirmCopy = {\n loadingMessage: 'Confirmando tu email...',\n successTitle: 'Email confirmado',\n successMessage: 'Tu dirección de email ha sido verificada correctamente.',\n successCta: 'Iniciar sesión',\n errorTitle: 'Error al confirmar',\n errorMessage: 'El enlace de confirmación no es válido o ha expirado.',\n errorCta: 'Volver al inicio de sesión',\n}\n\ntype Status = 'loading' | 'success' | 'error'\n\n/**\n * Dual-mode email confirmation (signup / email-change):\n *\n * 1. `?success=true` — base-api already ran `verifyOtp` server-side and\n * redirected here. Shows the confirmed message with no API call.\n * 2. `?token_hash=&type=` — client-initiated verify: POSTs to `confirmUrl`\n * and follows the returned `sessionExchangeUrl`, or shows success/error\n * copy based on the response.\n *\n * Neither param present → shown as an error state with a link back to login,\n * rather than a silent client-side redirect.\n */\nexport function AuthConfirm(props: AuthConfirmProps) {\n const { confirmUrl, loginUrl, copy, className } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n\n const success = searchParams.get('success')\n const tokenHash = searchParams.get('token_hash')\n const type = searchParams.get('type')\n\n const [status, setStatus] = React.useState<Status>(success === 'true' ? 'success' : 'loading')\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (success === 'true') {\n setStatus('success')\n return\n }\n\n if (tokenHash && type) {\n // `token_hash` is single-use server-side — verifying it twice makes\n // the SECOND call fail even though the first already succeeded. React\n // Strict Mode's dev-only mount→cleanup→mount double-invoke would\n // otherwise fire this fetch twice for the same token. Aborting the\n // in-flight request on cleanup stops the CLIENT from processing that\n // first response and swallows its `AbortError` below instead of\n // surfacing a false \"confirmation failed\" — but `abort()` cannot\n // retroactively un-send a request that already reached base-api\n // before cleanup ran, so this reduces rather than deterministically\n // eliminates the double-POST race (in practice React's dev\n // double-invoke runs both phases synchronously in the same commit, so\n // the abort typically wins, but that is not a spec guarantee). A\n // fully deterministic fix needs server-side idempotency on\n // `verify-otp`, which is out of this package's control. The same\n // abort also guards a genuine unmount mid-request.\n const controller = new AbortController()\n setStatus('loading')\n fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token_hash: tokenHash, type }),\n signal: controller.signal,\n })\n .then(async (res) => {\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string; success?: boolean }\n error?: string\n }\n\n if (!res.ok) {\n setStatus('error')\n setErrorMessage(json.error ?? c.errorMessage)\n return\n }\n\n if (json.data?.sessionExchangeUrl) {\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setStatus('error')\n setErrorMessage(c.errorMessage)\n return\n }\n window.location.assign(json.data.sessionExchangeUrl)\n return\n }\n\n setStatus('success')\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') return\n setStatus('error')\n setErrorMessage(c.errorMessage)\n })\n\n return () => controller.abort()\n }\n\n // Neither success=true nor token_hash+type — nothing to confirm.\n setStatus('error')\n // `c.errorMessage` (derived from the `copy` prop) is intentionally\n // excluded: re-running this effect on every `copy` object identity\n // change would re-fire the fetch. The URL params are the only real\n // trigger for this effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [success, tokenHash, type, confirmUrl])\n\n if (status === 'loading') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <p className='text-muted-foreground text-sm'>{c.loadingMessage}</p>\n </div>\n )\n }\n\n if (status === 'error') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.errorTitle}</h2>\n <p className='text-muted-foreground text-sm'>{errorMessage ?? c.errorMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.errorCta}\n </a>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.successCta}\n </a>\n </div>\n )\n}\n"],"mappings":"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","names":["React","import_site_ui","import_jsx_runtime","DEFAULT_COPY","DEFAULT_COPY","React","import_site_ui","import_jsx_runtime","DEFAULT_COPY","React","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","EMAIL_REGEX","React","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","React","import_navigation","import_site_ui","import_label","import_jsx_runtime","DEFAULT_COPY","React","import_navigation","import_site_ui","import_jsx_runtime","DEFAULT_COPY"]}
package/dist/auth.d.cts CHANGED
@@ -112,13 +112,20 @@ interface AuthLogoProps {
112
112
  className?: string;
113
113
  copy?: Partial<AuthLogoCopy>;
114
114
  /**
115
- * When `true`, renders `null` instead of the `siteName`/fallback-label text
116
- * span whenever no logo URL resolves for the active variant+theme, rather
117
- * than showing the text. Use this when the consumer already renders
118
- * `siteName` as a heading elsewhere on the page — without it, a visitor
119
- * with no logo configured (or only a light-mode logo, viewed in dark mode)
120
- * would see/hear `siteName` twice: once from this fallback, once from the
121
- * consumer's own heading.
115
+ * Heading level for the `siteName`/fallback-label text rendered when no logo
116
+ * resolves. AuthLogo owns the auth page's heading, so this defaults to the
117
+ * top-level `1`; pass a deeper level only if the page already has an `<h1>`
118
+ * elsewhere that AuthLogo must sit beneath.
119
+ * @default 1
120
+ */
121
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
122
+ /**
123
+ * When `true`, renders `null` instead of the `siteName`/fallback-label
124
+ * heading whenever no logo URL resolves for the active variant+theme, rather
125
+ * than showing the text. Use this ONLY when the consumer intentionally
126
+ * renders `siteName` elsewhere itself — by default AuthLogo IS the heading
127
+ * ("logo XOR name"), so the consumer should NOT add its own
128
+ * `<h1>{siteName}</h1>`.
122
129
  * @default false
123
130
  */
124
131
  hideTextFallback?: boolean;
package/dist/auth.d.ts CHANGED
@@ -112,13 +112,20 @@ interface AuthLogoProps {
112
112
  className?: string;
113
113
  copy?: Partial<AuthLogoCopy>;
114
114
  /**
115
- * When `true`, renders `null` instead of the `siteName`/fallback-label text
116
- * span whenever no logo URL resolves for the active variant+theme, rather
117
- * than showing the text. Use this when the consumer already renders
118
- * `siteName` as a heading elsewhere on the page — without it, a visitor
119
- * with no logo configured (or only a light-mode logo, viewed in dark mode)
120
- * would see/hear `siteName` twice: once from this fallback, once from the
121
- * consumer's own heading.
115
+ * Heading level for the `siteName`/fallback-label text rendered when no logo
116
+ * resolves. AuthLogo owns the auth page's heading, so this defaults to the
117
+ * top-level `1`; pass a deeper level only if the page already has an `<h1>`
118
+ * elsewhere that AuthLogo must sit beneath.
119
+ * @default 1
120
+ */
121
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
122
+ /**
123
+ * When `true`, renders `null` instead of the `siteName`/fallback-label
124
+ * heading whenever no logo URL resolves for the active variant+theme, rather
125
+ * than showing the text. Use this ONLY when the consumer intentionally
126
+ * renders `siteName` elsewhere itself — by default AuthLogo IS the heading
127
+ * ("logo XOR name"), so the consumer should NOT add its own
128
+ * `<h1>{siteName}</h1>`.
122
129
  * @default false
123
130
  */
124
131
  hideTextFallback?: boolean;
package/dist/auth.js CHANGED
@@ -504,6 +504,7 @@ function AuthLogo(props) {
504
504
  variant = "expanded",
505
505
  className,
506
506
  copy,
507
+ headingLevel = 1,
507
508
  hideTextFallback = false
508
509
  } = props;
509
510
  const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY4.fallbackLabel;
@@ -518,22 +519,26 @@ function AuthLogo(props) {
518
519
  }, [logoUrl]);
519
520
  const fallbackText = siteName || fallbackLabel;
520
521
  const altText = copy?.altText !== void 0 ? copy.altText : siteName || fallbackLabel;
522
+ const Heading = `h${headingLevel}`;
521
523
  if (logoUrl && !imgError) {
522
524
  const { width, height } = VARIANT_INTRINSIC_SIZE[variant];
523
- return /* @__PURE__ */ jsx3(
525
+ const bare = hideTextFallback || altText === "";
526
+ const image = /* @__PURE__ */ jsx3(
524
527
  "img",
525
528
  {
526
529
  src: logoUrl,
527
530
  alt: altText,
528
531
  width,
529
532
  height,
530
- className: cn3(VARIANT_CLASS[variant], className),
533
+ className: cn3(VARIANT_CLASS[variant], "max-w-full", bare && className),
531
534
  onError: () => setImgError(true)
532
535
  }
533
536
  );
537
+ if (bare) return image;
538
+ return /* @__PURE__ */ jsx3(Heading, { className: cn3("inline-flex items-center max-w-full", className), children: image });
534
539
  }
535
540
  if (hideTextFallback) return null;
536
- return /* @__PURE__ */ jsx3("span", { className: cn3("text-lg font-semibold text-foreground", className), children: fallbackText });
541
+ return /* @__PURE__ */ jsx3(Heading, { className: cn3("text-2xl font-bold tracking-tight text-foreground", className), children: fallbackText });
537
542
  }
538
543
 
539
544
  // src/auth/reset-password-request.tsx
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/login-form.tsx","../src/auth/safe-redirect.ts","../src/auth/auth-callback.tsx","../src/auth/login-errors.ts","../src/auth/auth-logo.tsx","../src/auth/reset-password-request.tsx","../src/auth/reset-password-confirm.tsx","../src/auth/set-password.tsx","../src/auth/auth-confirm.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@reblu/site-ui/tabs'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface LoginFormCopy {\n // Tab labels\n magicLinkTabLabel: string\n passwordTabLabel: string\n\n // Shared\n emailLabel: string\n emailPlaceholder: string\n\n // Password tab\n passwordLabel: string\n passwordPlaceholder: string\n passwordSubmitCta: string\n forgotPasswordCta: string\n\n // Magic-link tab\n magicLinkSubmitCta: string\n\n // Magic-link success\n magicLinkSuccessTitle: string\n magicLinkSuccessMessage: string\n\n // MFA prompt\n mfaTitle: string\n mfaMessage: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaSubmitCta: string\n\n // Errors\n invalidEmailError: string\n networkError: string\n invalidCredentialsError: string\n mfaInvalidCodeError: string\n}\n\nexport interface LoginFormProps {\n // Endpoint URLs (all required)\n passwordLoginUrl: string\n mfaVerifyUrl: string\n magicLinkRequestUrl: string\n resetPasswordUrl: string\n\n // UX\n copy?: Partial<LoginFormCopy>\n className?: string\n defaultMethod?: 'magic-link' | 'password'\n\n // Hooks\n onPasswordSubmitted?: (email: string) => void\n onMagicLinkSubmitted?: (email: string) => void\n onMfaSubmitted?: () => void\n}\n\nconst DEFAULT_COPY: LoginFormCopy = {\n magicLinkTabLabel: 'Enlace mágico',\n passwordTabLabel: 'Contraseña',\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n passwordLabel: 'Contraseña',\n passwordPlaceholder: '••••••••',\n passwordSubmitCta: 'Iniciar sesión',\n forgotPasswordCta: '¿Olvidaste tu contraseña?',\n magicLinkSubmitCta: 'Enviar enlace de acceso',\n magicLinkSuccessTitle: '¡Enlace enviado!',\n magicLinkSuccessMessage: 'Revisa tu correo para acceder.',\n mfaTitle: 'Verificación en dos pasos',\n mfaMessage: 'Introduce el código de 6 dígitos de tu aplicación autenticadora.',\n mfaCodeLabel: 'Código de verificación',\n mfaCodePlaceholder: '123456',\n mfaSubmitCta: 'Verificar código',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'No se pudo enviar el enlace. Inténtalo de nuevo.',\n invalidCredentialsError: 'Email o contraseña incorrectos',\n mfaInvalidCodeError: 'Código incorrecto. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value)\n}\n\ntype FormState = 'idle' | 'submitting' | 'magic-link-success' | 'mfa-required' | 'error'\n\ninterface MfaState {\n factorId: string\n accessToken: string\n refreshToken: string\n}\n\ninterface PasswordLoginResponse {\n data?: {\n mfaRequired: boolean\n sessionExchangeUrl?: string\n factorId?: string\n accessToken?: string\n refreshToken?: string\n }\n error?: string\n}\n\ninterface MfaVerifyResponse {\n data?: { sessionExchangeUrl: string }\n error?: string\n}\n\nexport function LoginForm(props: LoginFormProps) {\n const {\n passwordLoginUrl,\n mfaVerifyUrl,\n magicLinkRequestUrl,\n resetPasswordUrl,\n copy,\n className,\n defaultMethod = 'password',\n onPasswordSubmitted,\n onMagicLinkSubmitted,\n onMfaSubmitted,\n } = props\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [method, setMethod] = React.useState<'magic-link' | 'password'>(defaultMethod)\n const [state, setState] = React.useState<FormState>('idle')\n const [error, setError] = React.useState<string | null>(null)\n\n // Password & magic-link inputs\n const [email, setEmail] = React.useState('')\n const [password, setPassword] = React.useState('')\n\n // MFA state\n const [mfa, setMfa] = React.useState<MfaState | null>(null)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const resetTransientState = React.useCallback(() => {\n setState('idle')\n setError(null)\n setMfa(null)\n setMfaCode('')\n }, [])\n\n const handleTabChange = (next: string) => {\n if (next !== 'magic-link' && next !== 'password') return\n setMethod(next)\n resetTransientState()\n }\n\n const handleMagicLinkSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(magicLinkRequestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (res.ok) {\n setState('magic-link-success')\n onMagicLinkSubmitted?.(trimmed)\n return\n }\n\n setState('error')\n setError(c.networkError)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handlePasswordSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n if (!password) {\n setError(c.invalidCredentialsError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(passwordLoginUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed, password }),\n })\n\n const json = (await res.json().catch(() => ({}))) as PasswordLoginResponse\n\n if (!res.ok || !json.data) {\n setState('error')\n setError(json.error ?? c.invalidCredentialsError)\n return\n }\n\n onPasswordSubmitted?.(trimmed)\n\n if (json.data.mfaRequired) {\n setMfa({\n factorId: json.data.factorId ?? '',\n accessToken: json.data.accessToken ?? '',\n refreshToken: json.data.refreshToken ?? '',\n })\n setState('mfa-required')\n return\n }\n\n const exchangeUrl = json.data.sessionExchangeUrl\n if (!exchangeUrl || !isSafeRedirectUrl(exchangeUrl)) {\n setState('error')\n setError(c.networkError)\n return\n }\n window.location.assign(exchangeUrl)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handleMfaSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n if (!mfa) return\n setError(null)\n setState('submitting')\n\n try {\n const res = await fetch(mfaVerifyUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n factorId: mfa.factorId,\n code: mfaCode,\n accessToken: mfa.accessToken,\n refreshToken: mfa.refreshToken,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as MfaVerifyResponse\n\n if (\n !res.ok ||\n !json.data?.sessionExchangeUrl ||\n !isSafeRedirectUrl(json.data.sessionExchangeUrl)\n ) {\n // Stay in mfa-required state so user can retry the code\n setState('mfa-required')\n setError(json.error ?? c.mfaInvalidCodeError)\n setMfaCode('')\n return\n }\n\n onMfaSubmitted?.()\n window.location.assign(json.data.sessionExchangeUrl)\n } catch {\n setState('mfa-required')\n setError(c.networkError)\n }\n }\n\n // MFA prompt replaces the form entirely\n if (state === 'mfa-required' || (mfa && state === 'submitting')) {\n return (\n <div className={cn('space-y-6', className)}>\n <div className='space-y-1'>\n <h2 className='text-xl font-semibold'>{c.mfaTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.mfaMessage}</p>\n </div>\n <form onSubmit={(e) => void handleMfaSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-mfa-code'>{c.mfaCodeLabel}</Label>\n <Input\n id='login-mfa-code'\n type='text'\n inputMode='numeric'\n pattern='\\d{6}'\n maxLength={6}\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(ev) => setMfaCode(ev.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-mfa-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='login-mfa-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button\n type='submit'\n className='w-full'\n disabled={state === 'submitting' || mfaCode.length !== 6}\n >\n {c.mfaSubmitCta}\n </Button>\n <button\n type='button'\n onClick={resetTransientState}\n className='text-muted-foreground hover:text-foreground w-full text-sm transition-colors'\n disabled={state === 'submitting'}\n >\n ← Volver\n </button>\n </form>\n </div>\n )\n }\n\n // Magic-link success replaces the form\n if (state === 'magic-link-success') {\n return (\n <div className={cn('space-y-2 text-center', className)}>\n <h2 className='text-xl font-semibold'>{c.magicLinkSuccessTitle}</h2>\n <p className='text-muted-foreground'>{c.magicLinkSuccessMessage}</p>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-6', className)}>\n <Tabs value={method} onValueChange={handleTabChange}>\n <TabsList className='grid w-full grid-cols-2'>\n <TabsTrigger value='password'>{c.passwordTabLabel}</TabsTrigger>\n <TabsTrigger value='magic-link'>{c.magicLinkTabLabel}</TabsTrigger>\n </TabsList>\n\n <TabsContent value='password' className='pt-4'>\n <form onSubmit={(e) => void handlePasswordSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-pwd'>{c.emailLabel}</Label>\n <Input\n id='login-email-pwd'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n />\n </div>\n <div className='space-y-2'>\n <div className='flex items-center justify-between'>\n <Label htmlFor='login-password'>{c.passwordLabel}</Label>\n <a\n href={resetPasswordUrl}\n className='text-muted-foreground hover:text-foreground text-xs transition-colors'\n >\n {c.forgotPasswordCta}\n </a>\n </div>\n <Input\n id='login-password'\n type='password'\n autoComplete='current-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(ev) => setPassword(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-pwd-error' : undefined}\n />\n {error && (\n <p id='login-pwd-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.passwordSubmitCta}\n </Button>\n </form>\n </TabsContent>\n\n <TabsContent value='magic-link' className='pt-4'>\n <form onSubmit={(e) => void handleMagicLinkSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-magic'>{c.emailLabel}</Label>\n <Input\n id='login-email-magic'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-magic-error' : undefined}\n />\n {error && (\n <p id='login-magic-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.magicLinkSubmitCta}\n </Button>\n </form>\n </TabsContent>\n </Tabs>\n </div>\n )\n}\n","/**\n * Scheme + URL-trick allowlist for a server-returned redirect target\n * (`sessionExchangeUrl`, `redirectTo`, etc.). Shared by `AuthCallback`,\n * `AuthConfirm`, and `SetPassword` — see `CLAUDE.md` Regla A: a predicate\n * applied more than 3 times gets one home, not N inline copies that drift.\n *\n * IMPORTANT — this is NOT full open-redirect confinement: it does NOT\n * restrict absolute `http`/`https` URLs to a specific origin.\n * `isSafeRedirectUrl('https://evil.com')` returns `true`. What it DOES\n * reject:\n * - dangerous schemes (`javascript:`, `data:`, `vbscript:`, ...) — the\n * classic XSS-via-redirect vector\n * - protocol-relative URLs (`//evil.com`) — these start with `/` but\n * browsers resolve them as an absolute, CROSS-ORIGIN URL (the scheme is\n * inherited from the current page), so a naive \"starts with /\" check\n * would wrongly treat them as same-origin\n * - backslash-authority URLs (`/\\evil.com`, `\\\\evil.com`) — browsers\n * normalize `\\` to `/` for special schemes, so these resolve the same way\n * - the same tricks with an ASCII tab/LF/CR spliced between the slashes\n * (`/\\t/evil.com`) — the WHATWG URL parser strips ALL tab/LF/CR from the\n * input BEFORE parsing (anywhere in the string, not just leading/trailing),\n * so `/\\t/evil.com` is indistinguishable from `//evil.com` to the browser\n * even though a naive regex over the raw string wouldn't see two adjacent\n * slashes\n *\n * Full origin-pinning (rejecting any absolute URL whose origin isn't the\n * actual expected one) is a SEPARATE, not-yet-implemented layer. It can't\n * simply pin to `REBLU_API_BASE_URL`: that's a server-only env var\n * unavailable in these `'use client'` components, and per REB-218's\n * subdomain redirect architecture it would be the WRONG origin anyway\n * (production session-exchange lives at `nexus.<tenantDomain>`, not the\n * SITE's own `REBLU_API_BASE_URL`). It would need a dedicated prop\n * conveying the actual expected origin per deploy/routing mode.\n */\nexport function isSafeRedirectUrl(url: string): boolean {\n if (!url) return false\n\n // Match the WHATWG URL parser's own first step: strip ALL ASCII\n // tab/LF/CR from the input, anywhere in the string — not just\n // leading/trailing — before evaluating what the browser will actually\n // resolve. Without this, '/\\t/evil.com' looks like a single-'/' relative\n // path to a naive check but the browser sees '//evil.com'.\n const clean = url.replace(/[\\t\\n\\r]/g, '')\n if (!clean) return false\n\n // A relative path must start with a SINGLE '/' — '//' or '/\\' resolve as\n // an absolute, cross-origin URL in the browser despite \"starting with /\".\n if (/^[/\\\\]{2}/.test(clean)) return false\n\n const scheme = clean.split(':')[0].toLowerCase()\n return ['http', 'https'].includes(scheme) || clean.startsWith('/')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthCallbackCopy {\n loadingTitle: string\n loadingMessage: string\n successMessage: string\n expiredTitle: string\n expiredMessage: string\n expiredCta: string\n networkErrorTitle: string\n networkErrorMessage: string\n networkErrorRetryCta: string\n networkErrorBackCta: string\n malformedTitle: string\n malformedMessage: string\n malformedCta: string\n}\n\nexport interface AuthCallbackProps {\n exchangeUrl: string\n loginUrl: string\n copy?: Partial<AuthCallbackCopy>\n onSuccess?: (redirectTo: string) => void\n onError?: (state: 'expired' | 'network' | 'malformed', error?: Error) => void\n className?: string\n}\n\ntype State = 'mounting' | 'parsing' | 'exchanging' | 'success' | 'expired' | 'network' | 'malformed'\n\nconst DEFAULT_COPY: AuthCallbackCopy = {\n loadingTitle: 'Autenticando...',\n loadingMessage: 'Estamos verificando tu identidad.',\n successMessage: 'Iniciando sesión...',\n expiredTitle: 'Enlace caducado',\n expiredMessage: 'El enlace ha expirado o ya fue utilizado. Solicita uno nuevo.',\n expiredCta: 'Volver al login',\n networkErrorTitle: 'Error de conexión',\n networkErrorMessage: 'No pudimos completar el inicio de sesión. Inténtalo de nuevo.',\n networkErrorRetryCta: 'Reintentar',\n networkErrorBackCta: 'Volver al login',\n malformedTitle: 'Enlace inválido',\n malformedMessage: 'Enlace inválido. Solicita uno nuevo.',\n malformedCta: 'Volver al login',\n}\n\nfunction parseHash(hash: string): { access_token: string; refresh_token: string } | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const access_token = params.get('access_token') ?? ''\n const refresh_token = params.get('refresh_token') ?? ''\n const type = params.get('type') ?? ''\n if (!access_token || !refresh_token || type !== 'magiclink') return null\n return { access_token, refresh_token }\n}\n\n/**\n * On failure Supabase redirects to the callback with the reason in the hash\n * fragment (e.g. `#error=access_denied&error_code=otp_expired`), never in the\n * query — the server cannot see it (REB-177). Detect it client-side and tell an\n * expired/used link (recoverable → 'expired') apart from any other failure\n * (→ 'malformed').\n */\nfunction parseHashError(hash: string): 'expired' | 'malformed' | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const error = params.get('error')\n const errorCode = params.get('error_code')\n if (!error && !errorCode) return null\n // Expired or already-used magic links come back as access_denied / otp_expired.\n return errorCode === 'otp_expired' || error === 'access_denied' ? 'expired' : 'malformed'\n}\n\nexport function AuthCallback({\n exchangeUrl,\n loginUrl,\n copy,\n onSuccess,\n onError,\n className,\n}: AuthCallbackProps) {\n const [state, setState] = React.useState<State>('mounting')\n const [tokens, setTokens] = React.useState<{\n access_token: string\n refresh_token: string\n } | null>(null)\n const retryRef = React.useRef<HTMLButtonElement>(null)\n const linkRef = React.useRef<HTMLAnchorElement>(null)\n const c = { ...DEFAULT_COPY, ...copy }\n\n const exchange = React.useCallback(\n async (t: { access_token: string; refresh_token: string }) => {\n setState('exchanging')\n try {\n const res = await fetch(exchangeUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(t),\n })\n\n if (res.ok) {\n const raw = (await res.json()) as unknown\n const redirectTo =\n raw !== null &&\n typeof raw === 'object' &&\n 'redirectTo' in raw &&\n typeof (raw as { redirectTo: unknown }).redirectTo === 'string'\n ? (raw as { redirectTo: string }).redirectTo\n : null\n\n if (!redirectTo) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n // Guard against dangerous schemes (javascript:, data:, etc.) AND\n // protocol-relative/backslash-authority URLs (//evil.com, /\\evil.com)\n // — see safe-redirect.ts.\n if (!isSafeRedirectUrl(redirectTo)) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n history.replaceState({}, '', window.location.pathname)\n window.location.replace(redirectTo)\n setState('success')\n onSuccess?.(redirectTo)\n return\n }\n\n if (res.status === 401) {\n setState('expired')\n onError?.('expired')\n return\n }\n\n setState('network')\n onError?.('network', undefined)\n } catch (err) {\n setState('network')\n onError?.('network', err instanceof Error ? err : undefined)\n }\n },\n [exchangeUrl, onError, onSuccess],\n )\n\n React.useEffect(() => {\n setState('parsing')\n // A failure reported in the hash (expired/used/invalid link) is not a token\n // to exchange — surface it directly without hitting the network (REB-177).\n const hashError = parseHashError(window.location.hash)\n if (hashError) {\n setState(hashError)\n onError?.(hashError)\n return\n }\n const parsed = parseHash(window.location.hash)\n if (!parsed) {\n setState('malformed')\n onError?.('malformed')\n return\n }\n setTokens(parsed)\n void exchange(parsed)\n // `exchange`/`onError` are intentionally excluded: this effect must run\n // EXACTLY ONCE on mount to parse `window.location.hash` — re-running it\n // on every `exchange` identity change (which itself changes whenever\n // `onSuccess`/`onError` props change identity) would re-parse an\n // already-cleared hash and misfire.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n React.useEffect(() => {\n if (state === 'network') retryRef.current?.focus()\n if (state === 'expired' || state === 'malformed') linkRef.current?.focus()\n }, [state])\n\n const isLoading = state === 'mounting' || state === 'parsing' || state === 'exchanging'\n\n return (\n <main className={cn('flex min-h-screen items-center justify-center', className)}>\n <div aria-live='polite' className='w-full max-w-md space-y-4 p-6 text-center'>\n {isLoading && (\n <section>\n <h2 className='text-xl font-semibold'>{c.loadingTitle}</h2>\n <p className='text-muted-foreground mt-2'>{c.loadingMessage}</p>\n </section>\n )}\n\n {state === 'success' && (\n <section>\n <p>{c.successMessage}</p>\n </section>\n )}\n\n {state === 'expired' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.expiredTitle}</h2>\n <p className='text-muted-foreground'>{c.expiredMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.expiredCta}\n </a>\n </Button>\n </section>\n )}\n\n {state === 'network' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.networkErrorTitle}</h2>\n <p className='text-muted-foreground'>{c.networkErrorMessage}</p>\n <div className='flex flex-col gap-2'>\n <Button type='button' ref={retryRef} onClick={() => tokens && void exchange(tokens)}>\n {c.networkErrorRetryCta}\n </Button>\n <Button asChild variant='outline'>\n <a href={loginUrl}>{c.networkErrorBackCta}</a>\n </Button>\n </div>\n </section>\n )}\n\n {state === 'malformed' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.malformedTitle}</h2>\n <p className='text-muted-foreground'>{c.malformedMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.malformedCta}\n </a>\n </Button>\n </section>\n )}\n </div>\n </main>\n )\n}\n","/**\n * Maps the raw `?error=` query param that Supabase appends when a magic link is\n * already consumed or expired to a user-facing Spanish message.\n *\n * Supabase redirects to /login?error=... with a raw English message (e.g.\n * \"Email link is invalid or has expired\"). Showing that verbatim is confusing,\n * so we translate the common cases and signal whether the \"Solicitar otro\n * enlace\" CTA should be offered.\n *\n * Matching is case-insensitive and whitespace-tolerant so it survives small\n * variations in how the error text arrives.\n *\n * Relocated from apps/demo-site/src/lib/utils/login-errors.ts (REB-510 slice\n * 2a) as a copy-injectable helper — Spanish defaults are unchanged, and a SITE\n * MAY override individual messages via `Partial<LoginErrorCopy>`.\n */\n\nexport interface LoginErrorInfo {\n /** Message shown in the error card. */\n message: string\n /** Whether to show the \"Solicitar otro enlace\" CTA. */\n showRequestLink: boolean\n}\n\nexport interface LoginErrorCopy {\n expiredMessage: string\n missingTokenMessage: string\n genericMessage: string\n}\n\nconst DEFAULT_COPY: LoginErrorCopy = {\n expiredMessage: 'El enlace ha expirado o ya fue utilizado.',\n missingTokenMessage: 'El enlace no es válido. Solicita uno nuevo.',\n genericMessage: 'No se pudo completar el inicio de sesión. Intenta de nuevo.',\n}\n\nexport function mapLoginError(\n error: string | null | undefined,\n copy?: Partial<LoginErrorCopy>,\n): LoginErrorInfo | null {\n if (!error) return null\n\n const normalized = error.trim().toLowerCase()\n if (normalized === '') return null\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n if (\n normalized.includes('email link is invalid or has expired') ||\n normalized.includes('token has expired or is invalid')\n ) {\n return { message: c.expiredMessage, showRequestLink: true }\n }\n\n if (normalized.includes('missing_token')) {\n return { message: c.missingTokenMessage, showRequestLink: true }\n }\n\n return { message: c.genericMessage, showRequestLink: true }\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useTheme } from 'next-themes'\nimport { cn } from '@reblu/site-ui'\n\nexport interface AuthLogoCopy {\n /**\n * Overrides the rendered `<img>` alt text. Pass `''` explicitly to mark the\n * logo as decorative (e.g. when an adjacent heading already announces\n * `siteName`, so screen readers shouldn't read it twice) — `undefined`\n * (the key simply omitted from `copy`) is what falls back to `siteName`.\n */\n altText?: string\n /** Shown when neither a logo URL nor `siteName` is available. */\n fallbackLabel: string\n}\n\nexport interface AuthLogoProps {\n logoLightExpandedUrl: string | null\n logoDarkExpandedUrl: string | null\n logoLightSmallUrl: string | null\n logoDarkSmallUrl: string | null\n siteName: string\n /** @default 'expanded' */\n variant?: 'expanded' | 'compact'\n className?: string\n copy?: Partial<AuthLogoCopy>\n /**\n * When `true`, renders `null` instead of the `siteName`/fallback-label text\n * span whenever no logo URL resolves for the active variant+theme, rather\n * than showing the text. Use this when the consumer already renders\n * `siteName` as a heading elsewhere on the page — without it, a visitor\n * with no logo configured (or only a light-mode logo, viewed in dark mode)\n * would see/hear `siteName` twice: once from this fallback, once from the\n * consumer's own heading.\n * @default false\n */\n hideTextFallback?: boolean\n}\n\nconst DEFAULT_COPY: Required<Pick<AuthLogoCopy, 'fallbackLabel'>> = {\n fallbackLabel: 'Inicio de sesión',\n}\n\nconst VARIANT_CLASS: Record<'expanded' | 'compact', string> = {\n expanded: 'h-8 w-auto',\n compact: 'h-8 w-8',\n}\n\n// Intrinsic `width`/`height` attributes so the browser reserves layout space\n// via its built-in aspect-ratio-from-attributes heuristic before the real\n// image loads (avoids vertical CLS at minimum; horizontal width still\n// depends on the tenant's actual logo aspect ratio, which `/api/site/config`\n// doesn't expose — an arbitrary-but-reasonable box, not a promise of zero\n// shift). `VARIANT_CLASS`'s `h-8` always wins for height regardless.\nconst VARIANT_INTRINSIC_SIZE: Record<'expanded' | 'compact', { width: number; height: number }> = {\n expanded: { width: 160, height: 32 },\n compact: { width: 32, height: 32 },\n}\n\n/**\n * Dynamic tenant logo for auth pages (REB-510 R1). Resolves expanded/compact ×\n * light/dark from the 4 `/api/site/config` logo URLs, switching light/dark\n * CLIENT-SIDE via `next-themes`. Falls back to `siteName` text, then a generic\n * `copy.fallbackLabel`, whenever the resolved URL for the active\n * variant+theme combination is `null` — it never silently swaps in the OTHER\n * theme's logo.\n *\n * Hydration-safe: `next-themes`' `resolvedTheme` is `undefined` until it\n * mounts client-side, so pre-mount `isDark` is always `false` — the same\n * light-first markup the server rendered, avoiding a hydration mismatch.\n *\n * Renders a plain `<img>`, not `next/image`: this is a distributable SDK\n * consumed by external SITE repos, so it cannot assume the consuming app's\n * `next.config` `images.remotePatterns` allowlists the tenant's logo CDN.\n */\nexport function AuthLogo(props: AuthLogoProps) {\n const {\n logoLightExpandedUrl,\n logoDarkExpandedUrl,\n logoLightSmallUrl,\n logoDarkSmallUrl,\n siteName,\n variant = 'expanded',\n className,\n copy,\n hideTextFallback = false,\n } = props\n const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY.fallbackLabel\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n\n const lightUrl = variant === 'compact' ? logoLightSmallUrl : logoLightExpandedUrl\n const darkUrl = variant === 'compact' ? logoDarkSmallUrl : logoDarkExpandedUrl\n const logoUrl = isDark ? darkUrl : lightUrl\n\n // Tracks a broken/stale logo URL (404, CDN outage, etc.) — degrades to the\n // text fallback instead of a broken-image glyph. Reset whenever `logoUrl`\n // itself changes (e.g. a theme toggle resolves a DIFFERENT URL) so a past\n // failure doesn't permanently block a future, valid attempt.\n const [imgError, setImgError] = React.useState(false)\n React.useEffect(() => {\n setImgError(false)\n }, [logoUrl])\n\n const fallbackText = siteName || fallbackLabel\n // `copy.altText` — a VALUE-defined key (including `''`) wins outright,\n // that's how a consumer marks the logo decorative. A value-undefined key\n // (whether omitted from `copy` entirely, or present with an explicit\n // `undefined` value) falls back to siteName/fallbackLabel — checking value\n // definedness, not `'altText' in copy` key presence, is what makes\n // `copy={{ altText: undefined }}` behave the same as omitting the key.\n const altText = copy?.altText !== undefined ? copy.altText : siteName || fallbackLabel\n\n if (logoUrl && !imgError) {\n const { width, height } = VARIANT_INTRINSIC_SIZE[variant]\n return (\n <img\n src={logoUrl}\n alt={altText}\n width={width}\n height={height}\n className={cn(VARIANT_CLASS[variant], className)}\n onError={() => setImgError(true)}\n />\n )\n }\n\n if (hideTextFallback) return null\n\n return (\n <span className={cn('text-lg font-semibold text-foreground', className)}>{fallbackText}</span>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\nexport interface ResetPasswordRequestCopy {\n emailLabel: string\n emailPlaceholder: string\n submitCta: string\n submittingCta: string\n successTitle: string\n successMessage: string\n backToLoginCta: string\n invalidEmailError: string\n networkError: string\n}\n\nexport interface ResetPasswordRequestProps {\n requestUrl: string\n loginUrl: string\n copy?: Partial<ResetPasswordRequestCopy>\n className?: string\n onSubmitted?: (email: string) => void\n}\n\nconst DEFAULT_COPY: ResetPasswordRequestCopy = {\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n submitCta: 'Enviar enlace de recuperación',\n submittingCta: 'Enviando...',\n successTitle: 'Revisa tu email',\n successMessage:\n 'Si el email existe en nuestra plataforma, recibirás un enlace para restablecer tu contraseña en los próximos minutos.',\n backToLoginCta: '← Volver al inicio de sesión',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'Error de conexión. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Step 1 of the password-reset flow — anti-enumeration: always shows the same\n * generic success message on a 2xx response, regardless of whether the email\n * exists (the backend never leaks that).\n */\nexport function ResetPasswordRequest(props: ResetPasswordRequestProps) {\n const { requestUrl, loginUrl, copy, className, onSubmitted } = props\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [email, setEmail] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [submitted, setSubmitted] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!EMAIL_REGEX.test(trimmed)) {\n setError(c.invalidEmailError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSubmitted` callback from this\n // try/catch: if we called it INSIDE the try and it threw, that throw\n // would land in the network `catch` below and misreport a successful\n // request as a connection error.\n let succeeded = false\n try {\n const res = await fetch(requestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as { error?: string }\n setError(json.error ?? c.networkError)\n setLoading(false)\n return\n }\n\n succeeded = true\n setSubmitted(true)\n setLoading(false)\n } catch {\n setError(c.networkError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n try {\n onSubmitted?.(trimmed)\n } catch (err) {\n console.error('[ResetPasswordRequest] onSubmitted callback threw:', err)\n }\n }\n }\n\n if (submitted) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-request-email'>{c.emailLabel}</Label>\n <Input\n id='reset-request-email'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={loading}\n aria-describedby={error ? 'reset-request-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='reset-request-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={loading}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n <div className='text-center'>\n <a\n href={loginUrl}\n className='text-muted-foreground hover:text-foreground text-sm transition-colors'\n >\n {c.backToLoginCta}\n </a>\n </div>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\n/**\n * Appends `key=value` to `url` without producing a malformed double `?` when\n * `url` (consumer-supplied `loginUrl`, e.g. `/login?tenant=acme`) already\n * carries a query string. Deliberately NOT `new URL(url)` — `loginUrl` is\n * typically a relative path and the WHATWG `URL` constructor requires a base\n * for those.\n */\nfunction withQueryParam(url: string, key: string, value: string): string {\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}${key}=${encodeURIComponent(value)}`\n}\n\nexport interface ResetPasswordConfirmCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaHelpMessage: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n mfaInvalidCodeError: string\n genericError: string\n missingTokensTitle: string\n missingTokensMessage: string\n requestNewLinkCta: string\n}\n\nexport interface ResetPasswordConfirmProps {\n confirmUrl: string\n loginUrl: string\n resetRequestUrl: string\n copy?: Partial<ResetPasswordConfirmCopy>\n className?: string\n onSuccess?: () => void\n}\n\nconst DEFAULT_COPY: ResetPasswordConfirmCopy = {\n passwordLabel: 'Nueva contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n mfaCodeLabel: 'Código de verificación (2FA)',\n mfaCodePlaceholder: '6 dígitos',\n mfaHelpMessage:\n 'Tu cuenta tiene verificación en dos pasos. Introduce el código de tu app de autenticación para completar el cambio.',\n submitCta: 'Establecer nueva contraseña',\n submittingCta: 'Actualizando contraseña...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n mfaInvalidCodeError: 'Introduce el código de 6 dígitos de tu app de autenticación',\n genericError: 'Error al restablecer la contraseña',\n missingTokensTitle: 'Enlace inválido',\n missingTokensMessage: 'El enlace ha expirado o no es válido. Solicita uno nuevo.',\n requestNewLinkCta: 'Solicitar nuevo enlace',\n}\n\n/**\n * Step 2 of the password-reset flow. Reads `access_token`/`refresh_token`\n * straight off `useSearchParams` — no `window.location.search` + effect-timing\n * workaround needed here because, unlike a page-level default export, this is\n * a component the SITE mounts inside its own `<Suspense>` boundary.\n *\n * REB-378: when the account has a verified MFA factor, the recovery session\n * (AAL1) cannot change the password on its own. The backend responds\n * `{ data: { mfaRequired: true } }` and this reveals a 6-digit TOTP field so\n * the user can resubmit with the code (the recovery tokens stay valid).\n */\nexport function ResetPasswordConfirm(props: ResetPasswordConfirmProps) {\n const { confirmUrl, loginUrl, resetRequestUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const accessToken = searchParams.get('access_token')\n const refreshToken = searchParams.get('refresh_token')\n const hasTokens = Boolean(accessToken && refreshToken)\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mfaRequired, setMfaRequired] = React.useState(false)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!hasTokens) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n if (mfaRequired && !/^\\d{6}$/.test(mfaCode)) {\n setError(c.mfaInvalidCodeError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSuccess` callback (and the\n // navigation) from this try/catch: calling either INSIDE the try means a\n // throwing callback lands in the network `catch` below and misreports an\n // already-successful password change as a failure — the user retries\n // with recovery tokens the backend has already consumed.\n let succeeded = false\n try {\n const res = await fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: accessToken,\n refresh_token: refreshToken,\n new_password: password,\n ...(mfaRequired ? { mfa_code: mfaCode } : {}),\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n error?: string\n data?: { success?: boolean; mfaRequired?: boolean }\n }\n\n if (!res.ok) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (json.data?.mfaRequired) {\n setMfaRequired(true)\n setLoading(false)\n return\n }\n\n succeeded = true\n setLoading(false)\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch) — same reasoning as set-password.tsx: a throwing\n // `onSuccess` can no longer block the redirect or escape as an\n // unhandled rejection out of this fire-and-forget async handler.\n router.replace(withQueryParam(loginUrl, 'reset', 'success'))\n try {\n onSuccess?.()\n } catch (err) {\n console.error('[ResetPasswordConfirm] onSuccess callback threw:', err)\n }\n }\n }\n\n if (!hasTokens) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokensTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokensMessage}</p>\n <a href={resetRequestUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.requestNewLinkCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password'>{c.passwordLabel}</Label>\n <Input\n id='reset-confirm-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password-2'>{c.confirmPasswordLabel}</Label>\n <Input\n id='reset-confirm-password-2'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n />\n </div>\n {mfaRequired && (\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-mfa'>{c.mfaCodeLabel}</Label>\n <Input\n id='reset-confirm-mfa'\n type='text'\n inputMode='numeric'\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(e) => setMfaCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={loading}\n maxLength={6}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n <p className='text-muted-foreground text-xs'>{c.mfaHelpMessage}</p>\n </div>\n )}\n {error && (\n <p id='reset-confirm-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button\n type='submit'\n className='w-full'\n disabled={\n loading || !password || !confirmPassword || (mfaRequired && mfaCode.length !== 6)\n }\n >\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface SetPasswordCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n genericError: string\n missingTokenTitle: string\n missingTokenMessage: string\n backToLoginCta: string\n}\n\nexport interface SetPasswordProps {\n submitUrl: string\n loginUrl: string\n copy?: Partial<SetPasswordCopy>\n className?: string\n onSuccess?: (sessionExchangeUrl: string) => void\n}\n\nconst DEFAULT_COPY: SetPasswordCopy = {\n passwordLabel: 'Contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n submitCta: 'Activar cuenta',\n submittingCta: 'Activando cuenta...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n genericError: 'Error al establecer la contraseña',\n missingTokenTitle: 'Enlace inválido',\n missingTokenMessage: 'El enlace de invitación ha expirado o no es válido.',\n backToLoginCta: '← Volver al inicio de sesión',\n}\n\n/**\n * Invite flow — for a user setting their initial password. Reads\n * `session_exchange_token` off `useSearchParams`; on success, redirects via\n * `window.location.assign` (NOT the Next router) because the returned\n * `sessionExchangeUrl` points at base-api's own domain to set cookies, not a\n * same-origin SITE route.\n */\nexport function SetPassword(props: SetPasswordProps) {\n const { submitUrl, loginUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n const sessionExchangeToken = searchParams.get('session_exchange_token')\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!sessionExchangeToken) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n\n setLoading(true)\n // `sessionExchangeUrl` isolates the consumer's `onSuccess` callback (and\n // the actual navigation) from this try/catch: calling either INSIDE the\n // try means a throwing callback lands in the network `catch` below and\n // misreports an already-successful password-set as a connection error —\n // stranding an activated user on a form that tells them it failed.\n let sessionExchangeUrl: string | null = null\n try {\n const res = await fetch(submitUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_exchange_token: sessionExchangeToken,\n new_password: password,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string }\n error?: string\n }\n\n if (!res.ok || !json.data?.sessionExchangeUrl) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n sessionExchangeUrl = json.data.sessionExchangeUrl\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch): if `onSuccess` throws (ad blocker on an analytics call,\n // etc.) the navigation has already been triggered and is unaffected, and\n // the throw doesn't escape as an unhandled rejection out of this\n // fire-and-forget async handler — the user is never stranded on this\n // form by code that isn't even this component's own.\n window.location.assign(sessionExchangeUrl)\n try {\n onSuccess?.(sessionExchangeUrl)\n } catch (err) {\n console.error('[SetPassword] onSuccess callback threw:', err)\n }\n }\n\n if (!sessionExchangeToken) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokenTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokenMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-password'>{c.passwordLabel}</Label>\n <Input\n id='set-password-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n // Single-purpose activation page (this IS the whole page's task,\n // there's no earlier content competing for attention) — same\n // pattern as reset-password-confirm.tsx/reset-password-request.tsx.\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-confirm'>{c.confirmPasswordLabel}</Label>\n <Input\n id='set-password-confirm'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n />\n </div>\n {error && (\n <p id='set-password-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button type='submit' className='w-full' disabled={loading || !password || !confirmPassword}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthConfirmCopy {\n loadingMessage: string\n successTitle: string\n successMessage: string\n successCta: string\n errorTitle: string\n errorMessage: string\n errorCta: string\n}\n\nexport interface AuthConfirmProps {\n confirmUrl: string\n loginUrl: string\n copy?: Partial<AuthConfirmCopy>\n className?: string\n}\n\nconst DEFAULT_COPY: AuthConfirmCopy = {\n loadingMessage: 'Confirmando tu email...',\n successTitle: 'Email confirmado',\n successMessage: 'Tu dirección de email ha sido verificada correctamente.',\n successCta: 'Iniciar sesión',\n errorTitle: 'Error al confirmar',\n errorMessage: 'El enlace de confirmación no es válido o ha expirado.',\n errorCta: 'Volver al inicio de sesión',\n}\n\ntype Status = 'loading' | 'success' | 'error'\n\n/**\n * Dual-mode email confirmation (signup / email-change):\n *\n * 1. `?success=true` — base-api already ran `verifyOtp` server-side and\n * redirected here. Shows the confirmed message with no API call.\n * 2. `?token_hash=&type=` — client-initiated verify: POSTs to `confirmUrl`\n * and follows the returned `sessionExchangeUrl`, or shows success/error\n * copy based on the response.\n *\n * Neither param present → shown as an error state with a link back to login,\n * rather than a silent client-side redirect.\n */\nexport function AuthConfirm(props: AuthConfirmProps) {\n const { confirmUrl, loginUrl, copy, className } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n\n const success = searchParams.get('success')\n const tokenHash = searchParams.get('token_hash')\n const type = searchParams.get('type')\n\n const [status, setStatus] = React.useState<Status>(success === 'true' ? 'success' : 'loading')\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (success === 'true') {\n setStatus('success')\n return\n }\n\n if (tokenHash && type) {\n // `token_hash` is single-use server-side — verifying it twice makes\n // the SECOND call fail even though the first already succeeded. React\n // Strict Mode's dev-only mount→cleanup→mount double-invoke would\n // otherwise fire this fetch twice for the same token. Aborting the\n // in-flight request on cleanup stops the CLIENT from processing that\n // first response and swallows its `AbortError` below instead of\n // surfacing a false \"confirmation failed\" — but `abort()` cannot\n // retroactively un-send a request that already reached base-api\n // before cleanup ran, so this reduces rather than deterministically\n // eliminates the double-POST race (in practice React's dev\n // double-invoke runs both phases synchronously in the same commit, so\n // the abort typically wins, but that is not a spec guarantee). A\n // fully deterministic fix needs server-side idempotency on\n // `verify-otp`, which is out of this package's control. The same\n // abort also guards a genuine unmount mid-request.\n const controller = new AbortController()\n setStatus('loading')\n fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token_hash: tokenHash, type }),\n signal: controller.signal,\n })\n .then(async (res) => {\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string; success?: boolean }\n error?: string\n }\n\n if (!res.ok) {\n setStatus('error')\n setErrorMessage(json.error ?? c.errorMessage)\n return\n }\n\n if (json.data?.sessionExchangeUrl) {\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setStatus('error')\n setErrorMessage(c.errorMessage)\n return\n }\n window.location.assign(json.data.sessionExchangeUrl)\n return\n }\n\n setStatus('success')\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') return\n setStatus('error')\n setErrorMessage(c.errorMessage)\n })\n\n return () => controller.abort()\n }\n\n // Neither success=true nor token_hash+type — nothing to confirm.\n setStatus('error')\n // `c.errorMessage` (derived from the `copy` prop) is intentionally\n // excluded: re-running this effect on every `copy` object identity\n // change would re-fire the fetch. The URL params are the only real\n // trigger for this effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [success, tokenHash, type, confirmUrl])\n\n if (status === 'loading') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <p className='text-muted-foreground text-sm'>{c.loadingMessage}</p>\n </div>\n )\n }\n\n if (status === 'error') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.errorTitle}</h2>\n <p className='text-muted-foreground text-sm'>{errorMessage ?? c.errorMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.errorCta}\n </a>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.successCta}\n </a>\n </div>\n )\n}\n"],"mappings":";;;AAEA,YAAY,WAAW;AACvB,SAAS,QAAQ,OAAO,UAAU;AAClC,SAAS,aAAa;AACtB,SAAS,MAAM,UAAU,aAAa,mBAAmB;;;AC6BlD,SAAS,kBAAkB,KAAsB;AACtD,MAAI,CAAC,IAAK,QAAO;AAOjB,QAAM,QAAQ,IAAI,QAAQ,aAAa,EAAE;AACzC,MAAI,CAAC,MAAO,QAAO;AAInB,MAAI,YAAY,KAAK,KAAK,EAAG,QAAO;AAEpC,QAAM,SAAS,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,YAAY;AAC/C,SAAO,CAAC,QAAQ,OAAO,EAAE,SAAS,MAAM,KAAK,MAAM,WAAW,GAAG;AACnE;;;ADkPQ,SACE,KADF;AAvOR,IAAM,eAA8B;AAAA,EAClC,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,uBAAuB;AAAA,EACvB,yBAAyB;AAAA,EACzB,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,yBAAyB;AAAA,EACzB,qBAAqB;AACvB;AAEA,IAAM,cAAc;AAEpB,SAAS,aAAa,OAAwB;AAC5C,SAAO,YAAY,KAAK,KAAK;AAC/B;AA0BO,SAAS,UAAU,OAAuB;AAC/C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,IAAI,EAAE,GAAG,cAAc,GAAG,KAAK;AAErC,QAAM,CAAC,QAAQ,SAAS,IAAU,eAAoC,aAAa;AACnF,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAoB,MAAM;AAC1D,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAwB,IAAI;AAG5D,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,EAAE;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAU,eAAS,EAAE;AAGjD,QAAM,CAAC,KAAK,MAAM,IAAU,eAA0B,IAAI;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAU,eAAS,EAAE;AAE/C,QAAM,sBAA4B,kBAAY,MAAM;AAClD,aAAS,MAAM;AACf,aAAS,IAAI;AACb,WAAO,IAAI;AACX,eAAW,EAAE;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,CAAC,SAAiB;AACxC,QAAI,SAAS,gBAAgB,SAAS,WAAY;AAClD,cAAU,IAAI;AACd,wBAAoB;AAAA,EACtB;AAEA,QAAM,wBAAwB,OAAO,MAAwC;AAC3E,MAAE,eAAe;AACjB,aAAS,IAAI;AAEb,UAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAI,CAAC,aAAa,OAAO,GAAG;AAC1B,eAAS,EAAE,iBAAiB;AAC5B,eAAS,OAAO;AAChB;AAAA,IACF;AAEA,aAAS,YAAY;AACrB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,qBAAqB;AAAA,QAC3C,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAAA,MACzC,CAAC;AAED,UAAI,IAAI,IAAI;AACV,iBAAS,oBAAoB;AAC7B,+BAAuB,OAAO;AAC9B;AAAA,MACF;AAEA,eAAS,OAAO;AAChB,eAAS,EAAE,YAAY;AAAA,IACzB,QAAQ;AACN,eAAS,OAAO;AAChB,eAAS,EAAE,YAAY;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,uBAAuB,OAAO,MAAwC;AAC1E,MAAE,eAAe;AACjB,aAAS,IAAI;AAEb,UAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAI,CAAC,aAAa,OAAO,GAAG;AAC1B,eAAS,EAAE,iBAAiB;AAC5B,eAAS,OAAO;AAChB;AAAA,IACF;AACA,QAAI,CAAC,UAAU;AACb,eAAS,EAAE,uBAAuB;AAClC,eAAS,OAAO;AAChB;AAAA,IACF;AAEA,aAAS,YAAY;AACrB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,kBAAkB;AAAA,QACxC,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,SAAS,CAAC;AAAA,MACnD,CAAC;AAED,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAE/C,UAAI,CAAC,IAAI,MAAM,CAAC,KAAK,MAAM;AACzB,iBAAS,OAAO;AAChB,iBAAS,KAAK,SAAS,EAAE,uBAAuB;AAChD;AAAA,MACF;AAEA,4BAAsB,OAAO;AAE7B,UAAI,KAAK,KAAK,aAAa;AACzB,eAAO;AAAA,UACL,UAAU,KAAK,KAAK,YAAY;AAAA,UAChC,aAAa,KAAK,KAAK,eAAe;AAAA,UACtC,cAAc,KAAK,KAAK,gBAAgB;AAAA,QAC1C,CAAC;AACD,iBAAS,cAAc;AACvB;AAAA,MACF;AAEA,YAAM,cAAc,KAAK,KAAK;AAC9B,UAAI,CAAC,eAAe,CAAC,kBAAkB,WAAW,GAAG;AACnD,iBAAS,OAAO;AAChB,iBAAS,EAAE,YAAY;AACvB;AAAA,MACF;AACA,aAAO,SAAS,OAAO,WAAW;AAAA,IACpC,QAAQ;AACN,eAAS,OAAO;AAChB,eAAS,EAAE,YAAY;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,kBAAkB,OAAO,MAAwC;AACrE,MAAE,eAAe;AACjB,QAAI,CAAC,IAAK;AACV,aAAS,IAAI;AACb,aAAS,YAAY;AAErB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,cAAc;AAAA,QACpC,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,UACN,aAAa,IAAI;AAAA,UACjB,cAAc,IAAI;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AAED,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAE/C,UACE,CAAC,IAAI,MACL,CAAC,KAAK,MAAM,sBACZ,CAAC,kBAAkB,KAAK,KAAK,kBAAkB,GAC/C;AAEA,iBAAS,cAAc;AACvB,iBAAS,KAAK,SAAS,EAAE,mBAAmB;AAC5C,mBAAW,EAAE;AACb;AAAA,MACF;AAEA,uBAAiB;AACjB,aAAO,SAAS,OAAO,KAAK,KAAK,kBAAkB;AAAA,IACrD,QAAQ;AACN,eAAS,cAAc;AACvB,eAAS,EAAE,YAAY;AAAA,IACzB;AAAA,EACF;AAGA,MAAI,UAAU,kBAAmB,OAAO,UAAU,cAAe;AAC/D,WACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACvC;AAAA,2BAAC,SAAI,WAAU,aACb;AAAA,4BAAC,QAAG,WAAU,yBAAyB,YAAE,UAAS;AAAA,QAClD,oBAAC,OAAE,WAAU,iCAAiC,YAAE,YAAW;AAAA,SAC7D;AAAA,MACA,qBAAC,UAAK,UAAU,CAAC,MAAM,KAAK,gBAAgB,CAAC,GAAG,WAAU,aAAY,YAAU,MAC9E;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,kBAAkB,YAAE,cAAa;AAAA,UAChD;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAQ;AAAA,cACR,WAAW;AAAA,cACX,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,OAAO,WAAW,GAAG,OAAO,MAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,cAC3E,UAAU,UAAU;AAAA,cACpB,oBAAkB,QAAQ,oBAAoB;AAAA,cAC9C,WAAS;AAAA;AAAA,UACX;AAAA,UACC,SACC,oBAAC,OAAE,IAAG,mBAAkB,MAAK,SAAQ,WAAU,4BAC5C,iBACH;AAAA,WAEJ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,UAAU,UAAU,gBAAgB,QAAQ,WAAW;AAAA,YAEtD,YAAE;AAAA;AAAA,QACL;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,UAAU,UAAU;AAAA,YACrB;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OACF;AAAA,EAEJ;AAGA,MAAI,UAAU,sBAAsB;AAClC,WACE,qBAAC,SAAI,WAAW,GAAG,yBAAyB,SAAS,GACnD;AAAA,0BAAC,QAAG,WAAU,yBAAyB,YAAE,uBAAsB;AAAA,MAC/D,oBAAC,OAAE,WAAU,yBAAyB,YAAE,yBAAwB;AAAA,OAClE;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACvC,+BAAC,QAAK,OAAO,QAAQ,eAAe,iBAClC;AAAA,yBAAC,YAAS,WAAU,2BAClB;AAAA,0BAAC,eAAY,OAAM,YAAY,YAAE,kBAAiB;AAAA,MAClD,oBAAC,eAAY,OAAM,cAAc,YAAE,mBAAkB;AAAA,OACvD;AAAA,IAEA,oBAAC,eAAY,OAAM,YAAW,WAAU,QACtC,+BAAC,UAAK,UAAU,CAAC,MAAM,KAAK,qBAAqB,CAAC,GAAG,WAAU,aAAY,YAAU,MACnF;AAAA,2BAAC,SAAI,WAAU,aACb;AAAA,4BAAC,SAAM,SAAQ,mBAAmB,YAAE,YAAW;AAAA,QAC/C;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,cAAa;AAAA,YACb,aAAa,EAAE;AAAA,YACf,OAAO;AAAA,YACP,UAAU,CAAC,OAAO,SAAS,GAAG,OAAO,KAAK;AAAA,YAC1C,UAAU,UAAU;AAAA;AAAA,QACtB;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,aACb;AAAA,6BAAC,SAAI,WAAU,qCACb;AAAA,8BAAC,SAAM,SAAQ,kBAAkB,YAAE,eAAc;AAAA,UACjD;AAAA,YAAC;AAAA;AAAA,cACC,MAAM;AAAA,cACN,WAAU;AAAA,cAET,YAAE;AAAA;AAAA,UACL;AAAA,WACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,cAAa;AAAA,YACb,aAAa,EAAE;AAAA,YACf,OAAO;AAAA,YACP,UAAU,CAAC,OAAO,YAAY,GAAG,OAAO,KAAK;AAAA,YAC7C,UAAU,UAAU;AAAA,YACpB,oBAAkB,QAAQ,oBAAoB;AAAA;AAAA,QAChD;AAAA,QACC,SACC,oBAAC,OAAE,IAAG,mBAAkB,MAAK,SAAQ,WAAU,4BAC5C,iBACH;AAAA,SAEJ;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,UAAS,UAAU,UAAU,cAC1D,YAAE,mBACL;AAAA,OACF,GACF;AAAA,IAEA,oBAAC,eAAY,OAAM,cAAa,WAAU,QACxC,+BAAC,UAAK,UAAU,CAAC,MAAM,KAAK,sBAAsB,CAAC,GAAG,WAAU,aAAY,YAAU,MACpF;AAAA,2BAAC,SAAI,WAAU,aACb;AAAA,4BAAC,SAAM,SAAQ,qBAAqB,YAAE,YAAW;AAAA,QACjD;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,cAAa;AAAA,YACb,aAAa,EAAE;AAAA,YACf,OAAO;AAAA,YACP,UAAU,CAAC,OAAO,SAAS,GAAG,OAAO,KAAK;AAAA,YAC1C,UAAU,UAAU;AAAA,YACpB,oBAAkB,QAAQ,sBAAsB;AAAA;AAAA,QAClD;AAAA,QACC,SACC,oBAAC,OAAE,IAAG,qBAAoB,MAAK,SAAQ,WAAU,4BAC9C,iBACH;AAAA,SAEJ;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,UAAS,UAAU,UAAU,cAC1D,YAAE,oBACL;AAAA,OACF,GACF;AAAA,KACF,GACF;AAEJ;;;AE9aA,YAAYA,YAAW;AACvB,SAAS,UAAAC,SAAQ,MAAAC,WAAU;AA4LjB,SACE,OAAAC,MADF,QAAAC,aAAA;AA9JV,IAAMC,gBAAiC;AAAA,EACrC,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,cAAc;AAChB;AAEA,SAAS,UAAU,MAAsE;AACvF,QAAM,MAAM,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;AACnD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,IAAI,gBAAgB,GAAG;AACtC,QAAM,eAAe,OAAO,IAAI,cAAc,KAAK;AACnD,QAAM,gBAAgB,OAAO,IAAI,eAAe,KAAK;AACrD,QAAM,OAAO,OAAO,IAAI,MAAM,KAAK;AACnC,MAAI,CAAC,gBAAgB,CAAC,iBAAiB,SAAS,YAAa,QAAO;AACpE,SAAO,EAAE,cAAc,cAAc;AACvC;AASA,SAAS,eAAe,MAA8C;AACpE,QAAM,MAAM,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;AACnD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,IAAI,gBAAgB,GAAG;AACtC,QAAM,QAAQ,OAAO,IAAI,OAAO;AAChC,QAAM,YAAY,OAAO,IAAI,YAAY;AACzC,MAAI,CAAC,SAAS,CAAC,UAAW,QAAO;AAEjC,SAAO,cAAc,iBAAiB,UAAU,kBAAkB,YAAY;AAChF;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,OAAO,QAAQ,IAAU,gBAAgB,UAAU;AAC1D,QAAM,CAAC,QAAQ,SAAS,IAAU,gBAGxB,IAAI;AACd,QAAM,WAAiB,cAA0B,IAAI;AACrD,QAAM,UAAgB,cAA0B,IAAI;AACpD,QAAM,IAAI,EAAE,GAAGA,eAAc,GAAG,KAAK;AAErC,QAAM,WAAiB;AAAA,IACrB,OAAO,MAAuD;AAC5D,eAAS,YAAY;AACrB,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,aAAa;AAAA,UACnC,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,CAAC;AAAA,QACxB,CAAC;AAED,YAAI,IAAI,IAAI;AACV,gBAAM,MAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,aACJ,QAAQ,QACR,OAAO,QAAQ,YACf,gBAAgB,OAChB,OAAQ,IAAgC,eAAe,WAClD,IAA+B,aAChC;AAEN,cAAI,CAAC,YAAY;AACf,qBAAS,SAAS;AAClB,sBAAU,WAAW,MAAS;AAC9B;AAAA,UACF;AAKA,cAAI,CAAC,kBAAkB,UAAU,GAAG;AAClC,qBAAS,SAAS;AAClB,sBAAU,WAAW,MAAS;AAC9B;AAAA,UACF;AAEA,kBAAQ,aAAa,CAAC,GAAG,IAAI,OAAO,SAAS,QAAQ;AACrD,iBAAO,SAAS,QAAQ,UAAU;AAClC,mBAAS,SAAS;AAClB,sBAAY,UAAU;AACtB;AAAA,QACF;AAEA,YAAI,IAAI,WAAW,KAAK;AACtB,mBAAS,SAAS;AAClB,oBAAU,SAAS;AACnB;AAAA,QACF;AAEA,iBAAS,SAAS;AAClB,kBAAU,WAAW,MAAS;AAAA,MAChC,SAAS,KAAK;AACZ,iBAAS,SAAS;AAClB,kBAAU,WAAW,eAAe,QAAQ,MAAM,MAAS;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,CAAC,aAAa,SAAS,SAAS;AAAA,EAClC;AAEA,EAAM,iBAAU,MAAM;AACpB,aAAS,SAAS;AAGlB,UAAM,YAAY,eAAe,OAAO,SAAS,IAAI;AACrD,QAAI,WAAW;AACb,eAAS,SAAS;AAClB,gBAAU,SAAS;AACnB;AAAA,IACF;AACA,UAAM,SAAS,UAAU,OAAO,SAAS,IAAI;AAC7C,QAAI,CAAC,QAAQ;AACX,eAAS,WAAW;AACpB,gBAAU,WAAW;AACrB;AAAA,IACF;AACA,cAAU,MAAM;AAChB,SAAK,SAAS,MAAM;AAAA,EAOtB,GAAG,CAAC,CAAC;AAEL,EAAM,iBAAU,MAAM;AACpB,QAAI,UAAU,UAAW,UAAS,SAAS,MAAM;AACjD,QAAI,UAAU,aAAa,UAAU,YAAa,SAAQ,SAAS,MAAM;AAAA,EAC3E,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,YAAY,UAAU,cAAc,UAAU,aAAa,UAAU;AAE3E,SACE,gBAAAF,KAAC,UAAK,WAAWG,IAAG,iDAAiD,SAAS,GAC5E,0BAAAF,MAAC,SAAI,aAAU,UAAS,WAAU,6CAC/B;AAAA,iBACC,gBAAAA,MAAC,aACC;AAAA,sBAAAD,KAAC,QAAG,WAAU,yBAAyB,YAAE,cAAa;AAAA,MACtD,gBAAAA,KAAC,OAAE,WAAU,8BAA8B,YAAE,gBAAe;AAAA,OAC9D;AAAA,IAGD,UAAU,aACT,gBAAAA,KAAC,aACC,0BAAAA,KAAC,OAAG,YAAE,gBAAe,GACvB;AAAA,IAGD,UAAU,aACT,gBAAAC,MAAC,aAAQ,WAAU,aACjB;AAAA,sBAAAD,KAAC,QAAG,WAAU,yBAAyB,YAAE,cAAa;AAAA,MACtD,gBAAAA,KAAC,OAAE,WAAU,yBAAyB,YAAE,gBAAe;AAAA,MACvD,gBAAAA,KAACI,SAAA,EAAO,SAAO,MAAC,SAAQ,WACtB,0BAAAJ,KAAC,OAAE,MAAM,UAAU,KAAK,SACrB,YAAE,YACL,GACF;AAAA,OACF;AAAA,IAGD,UAAU,aACT,gBAAAC,MAAC,aAAQ,WAAU,aACjB;AAAA,sBAAAD,KAAC,QAAG,WAAU,yBAAyB,YAAE,mBAAkB;AAAA,MAC3D,gBAAAA,KAAC,OAAE,WAAU,yBAAyB,YAAE,qBAAoB;AAAA,MAC5D,gBAAAC,MAAC,SAAI,WAAU,uBACb;AAAA,wBAAAD,KAACI,SAAA,EAAO,MAAK,UAAS,KAAK,UAAU,SAAS,MAAM,UAAU,KAAK,SAAS,MAAM,GAC/E,YAAE,sBACL;AAAA,QACA,gBAAAJ,KAACI,SAAA,EAAO,SAAO,MAAC,SAAQ,WACtB,0BAAAJ,KAAC,OAAE,MAAM,UAAW,YAAE,qBAAoB,GAC5C;AAAA,SACF;AAAA,OACF;AAAA,IAGD,UAAU,eACT,gBAAAC,MAAC,aAAQ,WAAU,aACjB;AAAA,sBAAAD,KAAC,QAAG,WAAU,yBAAyB,YAAE,gBAAe;AAAA,MACxD,gBAAAA,KAAC,OAAE,WAAU,yBAAyB,YAAE,kBAAiB;AAAA,MACzD,gBAAAA,KAACI,SAAA,EAAO,SAAO,MAAC,SAAQ,WACtB,0BAAAJ,KAAC,OAAE,MAAM,UAAU,KAAK,SACrB,YAAE,cACL,GACF;AAAA,OACF;AAAA,KAEJ,GACF;AAEJ;;;ACtNA,IAAMK,gBAA+B;AAAA,EACnC,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,gBAAgB;AAClB;AAEO,SAAS,cACd,OACA,MACuB;AACvB,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAC5C,MAAI,eAAe,GAAI,QAAO;AAE9B,QAAM,IAAI,EAAE,GAAGA,eAAc,GAAG,KAAK;AAErC,MACE,WAAW,SAAS,sCAAsC,KAC1D,WAAW,SAAS,iCAAiC,GACrD;AACA,WAAO,EAAE,SAAS,EAAE,gBAAgB,iBAAiB,KAAK;AAAA,EAC5D;AAEA,MAAI,WAAW,SAAS,eAAe,GAAG;AACxC,WAAO,EAAE,SAAS,EAAE,qBAAqB,iBAAiB,KAAK;AAAA,EACjE;AAEA,SAAO,EAAE,SAAS,EAAE,gBAAgB,iBAAiB,KAAK;AAC5D;;;ACzDA,YAAYC,YAAW;AACvB,SAAS,gBAAgB;AACzB,SAAS,MAAAC,WAAU;AAkHb,gBAAAC,YAAA;AA7EN,IAAMC,gBAA8D;AAAA,EAClE,eAAe;AACjB;AAEA,IAAM,gBAAwD;AAAA,EAC5D,UAAU;AAAA,EACV,SAAS;AACX;AAQA,IAAM,yBAA4F;AAAA,EAChG,UAAU,EAAE,OAAO,KAAK,QAAQ,GAAG;AAAA,EACnC,SAAS,EAAE,OAAO,IAAI,QAAQ,GAAG;AACnC;AAkBO,SAAS,SAAS,OAAsB;AAC7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,EACrB,IAAI;AACJ,QAAM,gBAAgB,MAAM,iBAAiBA,cAAa;AAC1D,QAAM,EAAE,cAAc,IAAI,SAAS;AACnC,QAAM,SAAS,kBAAkB;AAEjC,QAAM,WAAW,YAAY,YAAY,oBAAoB;AAC7D,QAAM,UAAU,YAAY,YAAY,mBAAmB;AAC3D,QAAM,UAAU,SAAS,UAAU;AAMnC,QAAM,CAAC,UAAU,WAAW,IAAU,gBAAS,KAAK;AACpD,EAAM,iBAAU,MAAM;AACpB,gBAAY,KAAK;AAAA,EACnB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,eAAe,YAAY;AAOjC,QAAM,UAAU,MAAM,YAAY,SAAY,KAAK,UAAU,YAAY;AAEzE,MAAI,WAAW,CAAC,UAAU;AACxB,UAAM,EAAE,OAAO,OAAO,IAAI,uBAAuB,OAAO;AACxD,WACE,gBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,WAAWD,IAAG,cAAc,OAAO,GAAG,SAAS;AAAA,QAC/C,SAAS,MAAM,YAAY,IAAI;AAAA;AAAA,IACjC;AAAA,EAEJ;AAEA,MAAI,iBAAkB,QAAO;AAE7B,SACE,gBAAAC,KAAC,UAAK,WAAWD,IAAG,yCAAyC,SAAS,GAAI,wBAAa;AAE3F;;;ACpIA,YAAYG,YAAW;AACvB,SAAS,UAAAC,SAAQ,SAAAC,QAAO,MAAAC,WAAU;AAClC,SAAS,SAAAC,cAAa;AAsGhB,SACE,OAAAC,MADF,QAAAC,aAAA;AAhFN,IAAMC,gBAAyC;AAAA,EAC7C,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAAA,EACd,gBACE;AAAA,EACF,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,cAAc;AAChB;AAEA,IAAMC,eAAc;AAOb,SAAS,qBAAqB,OAAkC;AACrE,QAAM,EAAE,YAAY,UAAU,MAAM,WAAW,YAAY,IAAI;AAC/D,QAAM,IAAI,EAAE,GAAGD,eAAc,GAAG,KAAK;AAErC,QAAM,CAAC,OAAO,QAAQ,IAAU,gBAAS,EAAE;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAS,KAAK;AAClD,QAAM,CAAC,WAAW,YAAY,IAAU,gBAAS,KAAK;AACtD,QAAM,CAAC,OAAO,QAAQ,IAAU,gBAAwB,IAAI;AAE5D,QAAM,eAAe,OAAO,MAAwC;AAClE,MAAE,eAAe;AACjB,aAAS,IAAI;AAEb,UAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,QAAI,CAACC,aAAY,KAAK,OAAO,GAAG;AAC9B,eAAS,EAAE,iBAAiB;AAC5B;AAAA,IACF;AAEA,eAAW,IAAI;AAKf,QAAI,YAAY;AAChB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,YAAY;AAAA,QAClC,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC;AAAA,MACzC,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC/C,iBAAS,KAAK,SAAS,EAAE,YAAY;AACrC,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,kBAAY;AACZ,mBAAa,IAAI;AACjB,iBAAW,KAAK;AAAA,IAClB,QAAQ;AACN,eAAS,EAAE,YAAY;AACvB,iBAAW,KAAK;AAChB;AAAA,IACF;AAEA,QAAI,WAAW;AACb,UAAI;AACF,sBAAc,OAAO;AAAA,MACvB,SAAS,KAAK;AACZ,gBAAQ,MAAM,sDAAsD,GAAG;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW;AACb,WACE,gBAAAF,MAAC,SAAI,WAAWH,IAAG,yBAAyB,SAAS,GACnD;AAAA,sBAAAE,KAAC,QAAG,WAAU,yBAAyB,YAAE,cAAa;AAAA,MACtD,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,YAAE,gBAAe;AAAA,MAC/D,gBAAAA,KAAC,OAAE,MAAM,UAAU,WAAU,qDAC1B,YAAE,gBACL;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,CAAC,MAAM,KAAK,aAAa,CAAC;AAAA,MACpC,WAAWH,IAAG,aAAa,SAAS;AAAA,MACpC,YAAU;AAAA,MAEV;AAAA,wBAAAG,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACD,QAAA,EAAM,SAAQ,uBAAuB,YAAE,YAAW;AAAA,UACnD,gBAAAC;AAAA,YAACH;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,cACxC,UAAU;AAAA,cACV,oBAAkB,QAAQ,wBAAwB;AAAA,cAClD,WAAS;AAAA;AAAA,UACX;AAAA,UACC,SACC,gBAAAG,KAAC,OAAE,IAAG,uBAAsB,MAAK,SAAQ,WAAU,4BAChD,iBACH;AAAA,WAEJ;AAAA,QACA,gBAAAA,KAACJ,SAAA,EAAO,MAAK,UAAS,WAAU,UAAS,UAAU,SAChD,oBAAU,EAAE,gBAAgB,EAAE,WACjC;AAAA,QACA,gBAAAI,KAAC,SAAI,WAAU,eACb,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,WAAU;AAAA,YAET,YAAE;AAAA;AAAA,QACL,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;ACxJA,YAAYI,YAAW;AACvB,SAAS,WAAW,uBAAuB;AAC3C,SAAS,UAAAC,SAAQ,SAAAC,QAAO,MAAAC,WAAU;AAClC,SAAS,SAAAC,cAAa;AAyKhB,SACE,OAAAC,MADF,QAAAC,aAAA;AAhKN,SAAS,eAAe,KAAa,KAAa,OAAuB;AACvE,QAAM,YAAY,IAAI,SAAS,GAAG,IAAI,MAAM;AAC5C,SAAO,GAAG,GAAG,GAAG,SAAS,GAAG,GAAG,IAAI,mBAAmB,KAAK,CAAC;AAC9D;AA8BA,IAAMC,gBAAyC;AAAA,EAC7C,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,4BAA4B;AAAA,EAC5B,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,gBACE;AAAA,EACF,WAAW;AAAA,EACX,eAAe;AAAA,EACf,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,qBAAqB;AAAA,EACrB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,mBAAmB;AACrB;AAaO,SAAS,qBAAqB,OAAkC;AACrE,QAAM,EAAE,YAAY,UAAU,iBAAiB,MAAM,WAAW,UAAU,IAAI;AAC9E,QAAM,IAAI,EAAE,GAAGA,eAAc,GAAG,KAAK;AACrC,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AAErC,QAAM,cAAc,aAAa,IAAI,cAAc;AACnD,QAAM,eAAe,aAAa,IAAI,eAAe;AACrD,QAAM,YAAY,QAAQ,eAAe,YAAY;AAErD,QAAM,CAAC,UAAU,WAAW,IAAU,gBAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAU,gBAAS,EAAE;AAC/D,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAU,gBAAwB,IAAI;AAC5D,QAAM,CAAC,aAAa,cAAc,IAAU,gBAAS,KAAK;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAS,EAAE;AAE/C,QAAM,eAAe,OAAO,MAAwC;AAClE,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,QAAI,CAAC,UAAW;AAEhB,QAAI,SAAS,SAAS,GAAG;AACvB,eAAS,EAAE,qBAAqB;AAChC;AAAA,IACF;AACA,QAAI,aAAa,iBAAiB;AAChC,eAAS,EAAE,qBAAqB;AAChC;AAAA,IACF;AACA,QAAI,eAAe,CAAC,UAAU,KAAK,OAAO,GAAG;AAC3C,eAAS,EAAE,mBAAmB;AAC9B;AAAA,IACF;AAEA,eAAW,IAAI;AAMf,QAAI,YAAY;AAChB,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,YAAY;AAAA,QAClC,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,cAAc;AAAA,UACd,eAAe;AAAA,UACf,cAAc;AAAA,UACd,GAAI,cAAc,EAAE,UAAU,QAAQ,IAAI,CAAC;AAAA,QAC7C,CAAC;AAAA,MACH,CAAC;AAED,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAK/C,UAAI,CAAC,IAAI,IAAI;AACX,iBAAS,KAAK,SAAS,EAAE,YAAY;AACrC,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,UAAI,KAAK,MAAM,aAAa;AAC1B,uBAAe,IAAI;AACnB,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,kBAAY;AACZ,iBAAW,KAAK;AAAA,IAClB,QAAQ;AACN,eAAS,EAAE,YAAY;AACvB,iBAAW,KAAK;AAChB;AAAA,IACF;AAEA,QAAI,WAAW;AAKb,aAAO,QAAQ,eAAe,UAAU,SAAS,SAAS,CAAC;AAC3D,UAAI;AACF,oBAAY;AAAA,MACd,SAAS,KAAK;AACZ,gBAAQ,MAAM,oDAAoD,GAAG;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAW;AACd,WACE,gBAAAD,MAAC,SAAI,WAAWH,IAAG,yBAAyB,SAAS,GACnD;AAAA,sBAAAE,KAAC,QAAG,WAAU,yBAAyB,YAAE,oBAAmB;AAAA,MAC5D,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,YAAE,sBAAqB;AAAA,MACrE,gBAAAA,KAAC,OAAE,MAAM,iBAAiB,WAAU,qDACjC,YAAE,mBACL;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,CAAC,MAAM,KAAK,aAAa,CAAC;AAAA,MACpC,WAAWH,IAAG,aAAa,SAAS;AAAA,MACpC,YAAU;AAAA,MAEV;AAAA,wBAAAG,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACD,QAAA,EAAM,SAAQ,0BAA0B,YAAE,eAAc;AAAA,UACzD,gBAAAC;AAAA,YAACH;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA,cAC3C,UAAU;AAAA,cACV,WAAW;AAAA,cACX,oBAAkB,QAAQ,wBAAwB;AAAA,cAClD,WAAS;AAAA;AAAA,UACX;AAAA,WACF;AAAA,QACA,gBAAAI,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACD,QAAA,EAAM,SAAQ,4BAA4B,YAAE,sBAAqB;AAAA,UAClE,gBAAAC;AAAA,YAACH;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,cAClD,UAAU;AAAA,cACV,WAAW;AAAA,cACX,oBAAkB,QAAQ,wBAAwB;AAAA;AAAA,UACpD;AAAA,WACF;AAAA,QACC,eACC,gBAAAI,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACD,QAAA,EAAM,SAAQ,qBAAqB,YAAE,cAAa;AAAA,UACnD,gBAAAC;AAAA,YAACH;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,cACzE,UAAU;AAAA,cACV,WAAW;AAAA,cACX,oBAAkB,QAAQ,wBAAwB;AAAA,cAClD,WAAS;AAAA;AAAA,UACX;AAAA,UACA,gBAAAG,KAAC,OAAE,WAAU,iCAAiC,YAAE,gBAAe;AAAA,WACjE;AAAA,QAED,SACC,gBAAAA,KAAC,OAAE,IAAG,uBAAsB,MAAK,SAAQ,WAAU,4BAChD,iBACH;AAAA,QAEF,gBAAAA;AAAA,UAACJ;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,UACE,WAAW,CAAC,YAAY,CAAC,mBAAoB,eAAe,QAAQ,WAAW;AAAA,YAGhF,oBAAU,EAAE,gBAAgB,EAAE;AAAA;AAAA,QACjC;AAAA;AAAA;AAAA,EACF;AAEJ;;;AC5PA,YAAYO,YAAW;AACvB,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,UAAAC,SAAQ,SAAAC,QAAO,MAAAC,WAAU;AAClC,SAAS,SAAAC,cAAa;AAmIhB,SACE,OAAAC,MADF,QAAAC,aAAA;AAzGN,IAAMC,gBAAgC;AAAA,EACpC,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,4BAA4B;AAAA,EAC5B,WAAW;AAAA,EACX,eAAe;AAAA,EACf,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,gBAAgB;AAClB;AASO,SAAS,YAAY,OAAyB;AACnD,QAAM,EAAE,WAAW,UAAU,MAAM,WAAW,UAAU,IAAI;AAC5D,QAAM,IAAI,EAAE,GAAGA,eAAc,GAAG,KAAK;AACrC,QAAM,eAAeC,iBAAgB;AACrC,QAAM,uBAAuB,aAAa,IAAI,wBAAwB;AAEtE,QAAM,CAAC,UAAU,WAAW,IAAU,gBAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAU,gBAAS,EAAE;AAC/D,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAU,gBAAwB,IAAI;AAE5D,QAAM,eAAe,OAAO,MAAwC;AAClE,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,QAAI,CAAC,qBAAsB;AAE3B,QAAI,SAAS,SAAS,GAAG;AACvB,eAAS,EAAE,qBAAqB;AAChC;AAAA,IACF;AACA,QAAI,aAAa,iBAAiB;AAChC,eAAS,EAAE,qBAAqB;AAChC;AAAA,IACF;AAEA,eAAW,IAAI;AAMf,QAAI,qBAAoC;AACxC,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,WAAW;AAAA,QACjC,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,wBAAwB;AAAA,UACxB,cAAc;AAAA,QAChB,CAAC;AAAA,MACH,CAAC;AAED,YAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAK/C,UAAI,CAAC,IAAI,MAAM,CAAC,KAAK,MAAM,oBAAoB;AAC7C,iBAAS,KAAK,SAAS,EAAE,YAAY;AACrC,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,UAAI,CAAC,kBAAkB,KAAK,KAAK,kBAAkB,GAAG;AACpD,iBAAS,EAAE,YAAY;AACvB,mBAAW,KAAK;AAChB;AAAA,MACF;AAEA,2BAAqB,KAAK,KAAK;AAAA,IACjC,QAAQ;AACN,eAAS,EAAE,YAAY;AACvB,iBAAW,KAAK;AAChB;AAAA,IACF;AAQA,WAAO,SAAS,OAAO,kBAAkB;AACzC,QAAI;AACF,kBAAY,kBAAkB;AAAA,IAChC,SAAS,KAAK;AACZ,cAAQ,MAAM,2CAA2C,GAAG;AAAA,IAC9D;AAAA,EACF;AAEA,MAAI,CAAC,sBAAsB;AACzB,WACE,gBAAAF,MAAC,SAAI,WAAWG,IAAG,yBAAyB,SAAS,GACnD;AAAA,sBAAAJ,KAAC,QAAG,WAAU,yBAAyB,YAAE,mBAAkB;AAAA,MAC3D,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,YAAE,qBAAoB;AAAA,MACpE,gBAAAA,KAAC,OAAE,MAAM,UAAU,WAAU,qDAC1B,YAAE,gBACL;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,CAAC,MAAM,KAAK,aAAa,CAAC;AAAA,MACpC,WAAWG,IAAG,aAAa,SAAS;AAAA,MACpC,YAAU;AAAA,MAEV;AAAA,wBAAAH,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACK,QAAA,EAAM,SAAQ,yBAAyB,YAAE,eAAc;AAAA,UACxD,gBAAAL;AAAA,YAACM;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA,cAC3C,UAAU;AAAA,cACV,WAAW;AAAA,cACX,oBAAkB,QAAQ,uBAAuB;AAAA,cAIjD,WAAS;AAAA;AAAA,UACX;AAAA,WACF;AAAA,QACA,gBAAAL,MAAC,SAAI,WAAU,eACb;AAAA,0BAAAD,KAACK,QAAA,EAAM,SAAQ,wBAAwB,YAAE,sBAAqB;AAAA,UAC9D,gBAAAL;AAAA,YAACM;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,cAAa;AAAA,cACb,aAAa,EAAE;AAAA,cACf,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,cAClD,UAAU;AAAA,cACV,WAAW;AAAA,cACX,oBAAkB,QAAQ,uBAAuB;AAAA;AAAA,UACnD;AAAA,WACF;AAAA,QACC,SACC,gBAAAN,KAAC,OAAE,IAAG,sBAAqB,MAAK,SAAQ,WAAU,4BAC/C,iBACH;AAAA,QAEF,gBAAAA,KAACO,SAAA,EAAO,MAAK,UAAS,WAAU,UAAS,UAAU,WAAW,CAAC,YAAY,CAAC,iBACzE,oBAAU,EAAE,gBAAgB,EAAE,WACjC;AAAA;AAAA;AAAA,EACF;AAEJ;;;AChMA,YAAYC,YAAW;AACvB,SAAS,mBAAAC,wBAAuB;AAChC,SAAS,MAAAC,WAAU;AAoIX,gBAAAC,MAOF,QAAAC,aAPE;AAhHR,IAAMC,gBAAgC;AAAA,EACpC,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,UAAU;AACZ;AAgBO,SAAS,YAAY,OAAyB;AACnD,QAAM,EAAE,YAAY,UAAU,MAAM,UAAU,IAAI;AAClD,QAAM,IAAI,EAAE,GAAGA,eAAc,GAAG,KAAK;AACrC,QAAM,eAAeC,iBAAgB;AAErC,QAAM,UAAU,aAAa,IAAI,SAAS;AAC1C,QAAM,YAAY,aAAa,IAAI,YAAY;AAC/C,QAAM,OAAO,aAAa,IAAI,MAAM;AAEpC,QAAM,CAAC,QAAQ,SAAS,IAAU,gBAAiB,YAAY,SAAS,YAAY,SAAS;AAC7F,QAAM,CAAC,cAAc,eAAe,IAAU,gBAAwB,IAAI;AAE1E,EAAM,iBAAU,MAAM;AACpB,QAAI,YAAY,QAAQ;AACtB,gBAAU,SAAS;AACnB;AAAA,IACF;AAEA,QAAI,aAAa,MAAM;AAgBrB,YAAM,aAAa,IAAI,gBAAgB;AACvC,gBAAU,SAAS;AACnB,YAAM,YAAY;AAAA,QAChB,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,WAAW,KAAK,CAAC;AAAA,QACpD,QAAQ,WAAW;AAAA,MACrB,CAAC,EACE,KAAK,OAAO,QAAQ;AACnB,cAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAK/C,YAAI,CAAC,IAAI,IAAI;AACX,oBAAU,OAAO;AACjB,0BAAgB,KAAK,SAAS,EAAE,YAAY;AAC5C;AAAA,QACF;AAEA,YAAI,KAAK,MAAM,oBAAoB;AACjC,cAAI,CAAC,kBAAkB,KAAK,KAAK,kBAAkB,GAAG;AACpD,sBAAU,OAAO;AACjB,4BAAgB,EAAE,YAAY;AAC9B;AAAA,UACF;AACA,iBAAO,SAAS,OAAO,KAAK,KAAK,kBAAkB;AACnD;AAAA,QACF;AAEA,kBAAU,SAAS;AAAA,MACrB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAI,eAAe,SAAS,IAAI,SAAS,aAAc;AACvD,kBAAU,OAAO;AACjB,wBAAgB,EAAE,YAAY;AAAA,MAChC,CAAC;AAEH,aAAO,MAAM,WAAW,MAAM;AAAA,IAChC;AAGA,cAAU,OAAO;AAAA,EAMnB,GAAG,CAAC,SAAS,WAAW,MAAM,UAAU,CAAC;AAEzC,MAAI,WAAW,WAAW;AACxB,WACE,gBAAAH,KAAC,SAAI,WAAWI,IAAG,yBAAyB,SAAS,GACnD,0BAAAJ,KAAC,OAAE,WAAU,iCAAiC,YAAE,gBAAe,GACjE;AAAA,EAEJ;AAEA,MAAI,WAAW,SAAS;AACtB,WACE,gBAAAC,MAAC,SAAI,WAAWG,IAAG,yBAAyB,SAAS,GACnD;AAAA,sBAAAJ,KAAC,QAAG,WAAU,yBAAyB,YAAE,YAAW;AAAA,MACpD,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,0BAAgB,EAAE,cAAa;AAAA,MAC7E,gBAAAA,KAAC,OAAE,MAAM,UAAU,WAAU,qDAC1B,YAAE,UACL;AAAA,OACF;AAAA,EAEJ;AAEA,SACE,gBAAAC,MAAC,SAAI,WAAWG,IAAG,yBAAyB,SAAS,GACnD;AAAA,oBAAAJ,KAAC,QAAG,WAAU,yBAAyB,YAAE,cAAa;AAAA,IACtD,gBAAAA,KAAC,OAAE,WAAU,iCAAiC,YAAE,gBAAe;AAAA,IAC/D,gBAAAA,KAAC,OAAE,MAAM,UAAU,WAAU,qDAC1B,YAAE,YACL;AAAA,KACF;AAEJ;","names":["React","Button","cn","jsx","jsxs","DEFAULT_COPY","cn","Button","DEFAULT_COPY","React","cn","jsx","DEFAULT_COPY","React","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","EMAIL_REGEX","React","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","React","useSearchParams","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","useSearchParams","cn","Label","Input","Button","React","useSearchParams","cn","jsx","jsxs","DEFAULT_COPY","useSearchParams","cn"]}
1
+ {"version":3,"sources":["../src/auth/login-form.tsx","../src/auth/safe-redirect.ts","../src/auth/auth-callback.tsx","../src/auth/login-errors.ts","../src/auth/auth-logo.tsx","../src/auth/reset-password-request.tsx","../src/auth/reset-password-confirm.tsx","../src/auth/set-password.tsx","../src/auth/auth-confirm.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@reblu/site-ui/tabs'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface LoginFormCopy {\n // Tab labels\n magicLinkTabLabel: string\n passwordTabLabel: string\n\n // Shared\n emailLabel: string\n emailPlaceholder: string\n\n // Password tab\n passwordLabel: string\n passwordPlaceholder: string\n passwordSubmitCta: string\n forgotPasswordCta: string\n\n // Magic-link tab\n magicLinkSubmitCta: string\n\n // Magic-link success\n magicLinkSuccessTitle: string\n magicLinkSuccessMessage: string\n\n // MFA prompt\n mfaTitle: string\n mfaMessage: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaSubmitCta: string\n\n // Errors\n invalidEmailError: string\n networkError: string\n invalidCredentialsError: string\n mfaInvalidCodeError: string\n}\n\nexport interface LoginFormProps {\n // Endpoint URLs (all required)\n passwordLoginUrl: string\n mfaVerifyUrl: string\n magicLinkRequestUrl: string\n resetPasswordUrl: string\n\n // UX\n copy?: Partial<LoginFormCopy>\n className?: string\n defaultMethod?: 'magic-link' | 'password'\n\n // Hooks\n onPasswordSubmitted?: (email: string) => void\n onMagicLinkSubmitted?: (email: string) => void\n onMfaSubmitted?: () => void\n}\n\nconst DEFAULT_COPY: LoginFormCopy = {\n magicLinkTabLabel: 'Enlace mágico',\n passwordTabLabel: 'Contraseña',\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n passwordLabel: 'Contraseña',\n passwordPlaceholder: '••••••••',\n passwordSubmitCta: 'Iniciar sesión',\n forgotPasswordCta: '¿Olvidaste tu contraseña?',\n magicLinkSubmitCta: 'Enviar enlace de acceso',\n magicLinkSuccessTitle: '¡Enlace enviado!',\n magicLinkSuccessMessage: 'Revisa tu correo para acceder.',\n mfaTitle: 'Verificación en dos pasos',\n mfaMessage: 'Introduce el código de 6 dígitos de tu aplicación autenticadora.',\n mfaCodeLabel: 'Código de verificación',\n mfaCodePlaceholder: '123456',\n mfaSubmitCta: 'Verificar código',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'No se pudo enviar el enlace. Inténtalo de nuevo.',\n invalidCredentialsError: 'Email o contraseña incorrectos',\n mfaInvalidCodeError: 'Código incorrecto. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isValidEmail(value: string): boolean {\n return EMAIL_REGEX.test(value)\n}\n\ntype FormState = 'idle' | 'submitting' | 'magic-link-success' | 'mfa-required' | 'error'\n\ninterface MfaState {\n factorId: string\n accessToken: string\n refreshToken: string\n}\n\ninterface PasswordLoginResponse {\n data?: {\n mfaRequired: boolean\n sessionExchangeUrl?: string\n factorId?: string\n accessToken?: string\n refreshToken?: string\n }\n error?: string\n}\n\ninterface MfaVerifyResponse {\n data?: { sessionExchangeUrl: string }\n error?: string\n}\n\nexport function LoginForm(props: LoginFormProps) {\n const {\n passwordLoginUrl,\n mfaVerifyUrl,\n magicLinkRequestUrl,\n resetPasswordUrl,\n copy,\n className,\n defaultMethod = 'password',\n onPasswordSubmitted,\n onMagicLinkSubmitted,\n onMfaSubmitted,\n } = props\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [method, setMethod] = React.useState<'magic-link' | 'password'>(defaultMethod)\n const [state, setState] = React.useState<FormState>('idle')\n const [error, setError] = React.useState<string | null>(null)\n\n // Password & magic-link inputs\n const [email, setEmail] = React.useState('')\n const [password, setPassword] = React.useState('')\n\n // MFA state\n const [mfa, setMfa] = React.useState<MfaState | null>(null)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const resetTransientState = React.useCallback(() => {\n setState('idle')\n setError(null)\n setMfa(null)\n setMfaCode('')\n }, [])\n\n const handleTabChange = (next: string) => {\n if (next !== 'magic-link' && next !== 'password') return\n setMethod(next)\n resetTransientState()\n }\n\n const handleMagicLinkSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(magicLinkRequestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (res.ok) {\n setState('magic-link-success')\n onMagicLinkSubmitted?.(trimmed)\n return\n }\n\n setState('error')\n setError(c.networkError)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handlePasswordSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!isValidEmail(trimmed)) {\n setError(c.invalidEmailError)\n setState('error')\n return\n }\n if (!password) {\n setError(c.invalidCredentialsError)\n setState('error')\n return\n }\n\n setState('submitting')\n try {\n const res = await fetch(passwordLoginUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed, password }),\n })\n\n const json = (await res.json().catch(() => ({}))) as PasswordLoginResponse\n\n if (!res.ok || !json.data) {\n setState('error')\n setError(json.error ?? c.invalidCredentialsError)\n return\n }\n\n onPasswordSubmitted?.(trimmed)\n\n if (json.data.mfaRequired) {\n setMfa({\n factorId: json.data.factorId ?? '',\n accessToken: json.data.accessToken ?? '',\n refreshToken: json.data.refreshToken ?? '',\n })\n setState('mfa-required')\n return\n }\n\n const exchangeUrl = json.data.sessionExchangeUrl\n if (!exchangeUrl || !isSafeRedirectUrl(exchangeUrl)) {\n setState('error')\n setError(c.networkError)\n return\n }\n window.location.assign(exchangeUrl)\n } catch {\n setState('error')\n setError(c.networkError)\n }\n }\n\n const handleMfaSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n if (!mfa) return\n setError(null)\n setState('submitting')\n\n try {\n const res = await fetch(mfaVerifyUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n factorId: mfa.factorId,\n code: mfaCode,\n accessToken: mfa.accessToken,\n refreshToken: mfa.refreshToken,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as MfaVerifyResponse\n\n if (\n !res.ok ||\n !json.data?.sessionExchangeUrl ||\n !isSafeRedirectUrl(json.data.sessionExchangeUrl)\n ) {\n // Stay in mfa-required state so user can retry the code\n setState('mfa-required')\n setError(json.error ?? c.mfaInvalidCodeError)\n setMfaCode('')\n return\n }\n\n onMfaSubmitted?.()\n window.location.assign(json.data.sessionExchangeUrl)\n } catch {\n setState('mfa-required')\n setError(c.networkError)\n }\n }\n\n // MFA prompt replaces the form entirely\n if (state === 'mfa-required' || (mfa && state === 'submitting')) {\n return (\n <div className={cn('space-y-6', className)}>\n <div className='space-y-1'>\n <h2 className='text-xl font-semibold'>{c.mfaTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.mfaMessage}</p>\n </div>\n <form onSubmit={(e) => void handleMfaSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-mfa-code'>{c.mfaCodeLabel}</Label>\n <Input\n id='login-mfa-code'\n type='text'\n inputMode='numeric'\n pattern='\\d{6}'\n maxLength={6}\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(ev) => setMfaCode(ev.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-mfa-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='login-mfa-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button\n type='submit'\n className='w-full'\n disabled={state === 'submitting' || mfaCode.length !== 6}\n >\n {c.mfaSubmitCta}\n </Button>\n <button\n type='button'\n onClick={resetTransientState}\n className='text-muted-foreground hover:text-foreground w-full text-sm transition-colors'\n disabled={state === 'submitting'}\n >\n ← Volver\n </button>\n </form>\n </div>\n )\n }\n\n // Magic-link success replaces the form\n if (state === 'magic-link-success') {\n return (\n <div className={cn('space-y-2 text-center', className)}>\n <h2 className='text-xl font-semibold'>{c.magicLinkSuccessTitle}</h2>\n <p className='text-muted-foreground'>{c.magicLinkSuccessMessage}</p>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-6', className)}>\n <Tabs value={method} onValueChange={handleTabChange}>\n <TabsList className='grid w-full grid-cols-2'>\n <TabsTrigger value='password'>{c.passwordTabLabel}</TabsTrigger>\n <TabsTrigger value='magic-link'>{c.magicLinkTabLabel}</TabsTrigger>\n </TabsList>\n\n <TabsContent value='password' className='pt-4'>\n <form onSubmit={(e) => void handlePasswordSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-pwd'>{c.emailLabel}</Label>\n <Input\n id='login-email-pwd'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n />\n </div>\n <div className='space-y-2'>\n <div className='flex items-center justify-between'>\n <Label htmlFor='login-password'>{c.passwordLabel}</Label>\n <a\n href={resetPasswordUrl}\n className='text-muted-foreground hover:text-foreground text-xs transition-colors'\n >\n {c.forgotPasswordCta}\n </a>\n </div>\n <Input\n id='login-password'\n type='password'\n autoComplete='current-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(ev) => setPassword(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-pwd-error' : undefined}\n />\n {error && (\n <p id='login-pwd-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.passwordSubmitCta}\n </Button>\n </form>\n </TabsContent>\n\n <TabsContent value='magic-link' className='pt-4'>\n <form onSubmit={(e) => void handleMagicLinkSubmit(e)} className='space-y-4' noValidate>\n <div className='space-y-2'>\n <Label htmlFor='login-email-magic'>{c.emailLabel}</Label>\n <Input\n id='login-email-magic'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(ev) => setEmail(ev.target.value)}\n disabled={state === 'submitting'}\n aria-describedby={error ? 'login-magic-error' : undefined}\n />\n {error && (\n <p id='login-magic-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={state === 'submitting'}>\n {c.magicLinkSubmitCta}\n </Button>\n </form>\n </TabsContent>\n </Tabs>\n </div>\n )\n}\n","/**\n * Scheme + URL-trick allowlist for a server-returned redirect target\n * (`sessionExchangeUrl`, `redirectTo`, etc.). Shared by `AuthCallback`,\n * `AuthConfirm`, and `SetPassword` — see `CLAUDE.md` Regla A: a predicate\n * applied more than 3 times gets one home, not N inline copies that drift.\n *\n * IMPORTANT — this is NOT full open-redirect confinement: it does NOT\n * restrict absolute `http`/`https` URLs to a specific origin.\n * `isSafeRedirectUrl('https://evil.com')` returns `true`. What it DOES\n * reject:\n * - dangerous schemes (`javascript:`, `data:`, `vbscript:`, ...) — the\n * classic XSS-via-redirect vector\n * - protocol-relative URLs (`//evil.com`) — these start with `/` but\n * browsers resolve them as an absolute, CROSS-ORIGIN URL (the scheme is\n * inherited from the current page), so a naive \"starts with /\" check\n * would wrongly treat them as same-origin\n * - backslash-authority URLs (`/\\evil.com`, `\\\\evil.com`) — browsers\n * normalize `\\` to `/` for special schemes, so these resolve the same way\n * - the same tricks with an ASCII tab/LF/CR spliced between the slashes\n * (`/\\t/evil.com`) — the WHATWG URL parser strips ALL tab/LF/CR from the\n * input BEFORE parsing (anywhere in the string, not just leading/trailing),\n * so `/\\t/evil.com` is indistinguishable from `//evil.com` to the browser\n * even though a naive regex over the raw string wouldn't see two adjacent\n * slashes\n *\n * Full origin-pinning (rejecting any absolute URL whose origin isn't the\n * actual expected one) is a SEPARATE, not-yet-implemented layer. It can't\n * simply pin to `REBLU_API_BASE_URL`: that's a server-only env var\n * unavailable in these `'use client'` components, and per REB-218's\n * subdomain redirect architecture it would be the WRONG origin anyway\n * (production session-exchange lives at `nexus.<tenantDomain>`, not the\n * SITE's own `REBLU_API_BASE_URL`). It would need a dedicated prop\n * conveying the actual expected origin per deploy/routing mode.\n */\nexport function isSafeRedirectUrl(url: string): boolean {\n if (!url) return false\n\n // Match the WHATWG URL parser's own first step: strip ALL ASCII\n // tab/LF/CR from the input, anywhere in the string — not just\n // leading/trailing — before evaluating what the browser will actually\n // resolve. Without this, '/\\t/evil.com' looks like a single-'/' relative\n // path to a naive check but the browser sees '//evil.com'.\n const clean = url.replace(/[\\t\\n\\r]/g, '')\n if (!clean) return false\n\n // A relative path must start with a SINGLE '/' — '//' or '/\\' resolve as\n // an absolute, cross-origin URL in the browser despite \"starting with /\".\n if (/^[/\\\\]{2}/.test(clean)) return false\n\n const scheme = clean.split(':')[0].toLowerCase()\n return ['http', 'https'].includes(scheme) || clean.startsWith('/')\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthCallbackCopy {\n loadingTitle: string\n loadingMessage: string\n successMessage: string\n expiredTitle: string\n expiredMessage: string\n expiredCta: string\n networkErrorTitle: string\n networkErrorMessage: string\n networkErrorRetryCta: string\n networkErrorBackCta: string\n malformedTitle: string\n malformedMessage: string\n malformedCta: string\n}\n\nexport interface AuthCallbackProps {\n exchangeUrl: string\n loginUrl: string\n copy?: Partial<AuthCallbackCopy>\n onSuccess?: (redirectTo: string) => void\n onError?: (state: 'expired' | 'network' | 'malformed', error?: Error) => void\n className?: string\n}\n\ntype State = 'mounting' | 'parsing' | 'exchanging' | 'success' | 'expired' | 'network' | 'malformed'\n\nconst DEFAULT_COPY: AuthCallbackCopy = {\n loadingTitle: 'Autenticando...',\n loadingMessage: 'Estamos verificando tu identidad.',\n successMessage: 'Iniciando sesión...',\n expiredTitle: 'Enlace caducado',\n expiredMessage: 'El enlace ha expirado o ya fue utilizado. Solicita uno nuevo.',\n expiredCta: 'Volver al login',\n networkErrorTitle: 'Error de conexión',\n networkErrorMessage: 'No pudimos completar el inicio de sesión. Inténtalo de nuevo.',\n networkErrorRetryCta: 'Reintentar',\n networkErrorBackCta: 'Volver al login',\n malformedTitle: 'Enlace inválido',\n malformedMessage: 'Enlace inválido. Solicita uno nuevo.',\n malformedCta: 'Volver al login',\n}\n\nfunction parseHash(hash: string): { access_token: string; refresh_token: string } | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const access_token = params.get('access_token') ?? ''\n const refresh_token = params.get('refresh_token') ?? ''\n const type = params.get('type') ?? ''\n if (!access_token || !refresh_token || type !== 'magiclink') return null\n return { access_token, refresh_token }\n}\n\n/**\n * On failure Supabase redirects to the callback with the reason in the hash\n * fragment (e.g. `#error=access_denied&error_code=otp_expired`), never in the\n * query — the server cannot see it (REB-177). Detect it client-side and tell an\n * expired/used link (recoverable → 'expired') apart from any other failure\n * (→ 'malformed').\n */\nfunction parseHashError(hash: string): 'expired' | 'malformed' | null {\n const raw = hash.startsWith('#') ? hash.slice(1) : hash\n if (!raw) return null\n const params = new URLSearchParams(raw)\n const error = params.get('error')\n const errorCode = params.get('error_code')\n if (!error && !errorCode) return null\n // Expired or already-used magic links come back as access_denied / otp_expired.\n return errorCode === 'otp_expired' || error === 'access_denied' ? 'expired' : 'malformed'\n}\n\nexport function AuthCallback({\n exchangeUrl,\n loginUrl,\n copy,\n onSuccess,\n onError,\n className,\n}: AuthCallbackProps) {\n const [state, setState] = React.useState<State>('mounting')\n const [tokens, setTokens] = React.useState<{\n access_token: string\n refresh_token: string\n } | null>(null)\n const retryRef = React.useRef<HTMLButtonElement>(null)\n const linkRef = React.useRef<HTMLAnchorElement>(null)\n const c = { ...DEFAULT_COPY, ...copy }\n\n const exchange = React.useCallback(\n async (t: { access_token: string; refresh_token: string }) => {\n setState('exchanging')\n try {\n const res = await fetch(exchangeUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(t),\n })\n\n if (res.ok) {\n const raw = (await res.json()) as unknown\n const redirectTo =\n raw !== null &&\n typeof raw === 'object' &&\n 'redirectTo' in raw &&\n typeof (raw as { redirectTo: unknown }).redirectTo === 'string'\n ? (raw as { redirectTo: string }).redirectTo\n : null\n\n if (!redirectTo) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n // Guard against dangerous schemes (javascript:, data:, etc.) AND\n // protocol-relative/backslash-authority URLs (//evil.com, /\\evil.com)\n // — see safe-redirect.ts.\n if (!isSafeRedirectUrl(redirectTo)) {\n setState('network')\n onError?.('network', undefined)\n return\n }\n\n history.replaceState({}, '', window.location.pathname)\n window.location.replace(redirectTo)\n setState('success')\n onSuccess?.(redirectTo)\n return\n }\n\n if (res.status === 401) {\n setState('expired')\n onError?.('expired')\n return\n }\n\n setState('network')\n onError?.('network', undefined)\n } catch (err) {\n setState('network')\n onError?.('network', err instanceof Error ? err : undefined)\n }\n },\n [exchangeUrl, onError, onSuccess],\n )\n\n React.useEffect(() => {\n setState('parsing')\n // A failure reported in the hash (expired/used/invalid link) is not a token\n // to exchange — surface it directly without hitting the network (REB-177).\n const hashError = parseHashError(window.location.hash)\n if (hashError) {\n setState(hashError)\n onError?.(hashError)\n return\n }\n const parsed = parseHash(window.location.hash)\n if (!parsed) {\n setState('malformed')\n onError?.('malformed')\n return\n }\n setTokens(parsed)\n void exchange(parsed)\n // `exchange`/`onError` are intentionally excluded: this effect must run\n // EXACTLY ONCE on mount to parse `window.location.hash` — re-running it\n // on every `exchange` identity change (which itself changes whenever\n // `onSuccess`/`onError` props change identity) would re-parse an\n // already-cleared hash and misfire.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n React.useEffect(() => {\n if (state === 'network') retryRef.current?.focus()\n if (state === 'expired' || state === 'malformed') linkRef.current?.focus()\n }, [state])\n\n const isLoading = state === 'mounting' || state === 'parsing' || state === 'exchanging'\n\n return (\n <main className={cn('flex min-h-screen items-center justify-center', className)}>\n <div aria-live='polite' className='w-full max-w-md space-y-4 p-6 text-center'>\n {isLoading && (\n <section>\n <h2 className='text-xl font-semibold'>{c.loadingTitle}</h2>\n <p className='text-muted-foreground mt-2'>{c.loadingMessage}</p>\n </section>\n )}\n\n {state === 'success' && (\n <section>\n <p>{c.successMessage}</p>\n </section>\n )}\n\n {state === 'expired' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.expiredTitle}</h2>\n <p className='text-muted-foreground'>{c.expiredMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.expiredCta}\n </a>\n </Button>\n </section>\n )}\n\n {state === 'network' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.networkErrorTitle}</h2>\n <p className='text-muted-foreground'>{c.networkErrorMessage}</p>\n <div className='flex flex-col gap-2'>\n <Button type='button' ref={retryRef} onClick={() => tokens && void exchange(tokens)}>\n {c.networkErrorRetryCta}\n </Button>\n <Button asChild variant='outline'>\n <a href={loginUrl}>{c.networkErrorBackCta}</a>\n </Button>\n </div>\n </section>\n )}\n\n {state === 'malformed' && (\n <section className='space-y-4'>\n <h2 className='text-xl font-semibold'>{c.malformedTitle}</h2>\n <p className='text-muted-foreground'>{c.malformedMessage}</p>\n <Button asChild variant='outline'>\n <a href={loginUrl} ref={linkRef}>\n {c.malformedCta}\n </a>\n </Button>\n </section>\n )}\n </div>\n </main>\n )\n}\n","/**\n * Maps the raw `?error=` query param that Supabase appends when a magic link is\n * already consumed or expired to a user-facing Spanish message.\n *\n * Supabase redirects to /login?error=... with a raw English message (e.g.\n * \"Email link is invalid or has expired\"). Showing that verbatim is confusing,\n * so we translate the common cases and signal whether the \"Solicitar otro\n * enlace\" CTA should be offered.\n *\n * Matching is case-insensitive and whitespace-tolerant so it survives small\n * variations in how the error text arrives.\n *\n * Relocated from apps/demo-site/src/lib/utils/login-errors.ts (REB-510 slice\n * 2a) as a copy-injectable helper — Spanish defaults are unchanged, and a SITE\n * MAY override individual messages via `Partial<LoginErrorCopy>`.\n */\n\nexport interface LoginErrorInfo {\n /** Message shown in the error card. */\n message: string\n /** Whether to show the \"Solicitar otro enlace\" CTA. */\n showRequestLink: boolean\n}\n\nexport interface LoginErrorCopy {\n expiredMessage: string\n missingTokenMessage: string\n genericMessage: string\n}\n\nconst DEFAULT_COPY: LoginErrorCopy = {\n expiredMessage: 'El enlace ha expirado o ya fue utilizado.',\n missingTokenMessage: 'El enlace no es válido. Solicita uno nuevo.',\n genericMessage: 'No se pudo completar el inicio de sesión. Intenta de nuevo.',\n}\n\nexport function mapLoginError(\n error: string | null | undefined,\n copy?: Partial<LoginErrorCopy>,\n): LoginErrorInfo | null {\n if (!error) return null\n\n const normalized = error.trim().toLowerCase()\n if (normalized === '') return null\n\n const c = { ...DEFAULT_COPY, ...copy }\n\n if (\n normalized.includes('email link is invalid or has expired') ||\n normalized.includes('token has expired or is invalid')\n ) {\n return { message: c.expiredMessage, showRequestLink: true }\n }\n\n if (normalized.includes('missing_token')) {\n return { message: c.missingTokenMessage, showRequestLink: true }\n }\n\n return { message: c.genericMessage, showRequestLink: true }\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useTheme } from 'next-themes'\nimport { cn } from '@reblu/site-ui'\n\nexport interface AuthLogoCopy {\n /**\n * Overrides the rendered `<img>` alt text. Pass `''` explicitly to mark the\n * logo as decorative (e.g. when an adjacent heading already announces\n * `siteName`, so screen readers shouldn't read it twice) — `undefined`\n * (the key simply omitted from `copy`) is what falls back to `siteName`.\n */\n altText?: string\n /** Shown when neither a logo URL nor `siteName` is available. */\n fallbackLabel: string\n}\n\nexport interface AuthLogoProps {\n logoLightExpandedUrl: string | null\n logoDarkExpandedUrl: string | null\n logoLightSmallUrl: string | null\n logoDarkSmallUrl: string | null\n siteName: string\n /** @default 'expanded' */\n variant?: 'expanded' | 'compact'\n className?: string\n copy?: Partial<AuthLogoCopy>\n /**\n * Heading level for the `siteName`/fallback-label text rendered when no logo\n * resolves. AuthLogo owns the auth page's heading, so this defaults to the\n * top-level `1`; pass a deeper level only if the page already has an `<h1>`\n * elsewhere that AuthLogo must sit beneath.\n * @default 1\n */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6\n /**\n * When `true`, renders `null` instead of the `siteName`/fallback-label\n * heading whenever no logo URL resolves for the active variant+theme, rather\n * than showing the text. Use this ONLY when the consumer intentionally\n * renders `siteName` elsewhere itself — by default AuthLogo IS the heading\n * (\"logo XOR name\"), so the consumer should NOT add its own\n * `<h1>{siteName}</h1>`.\n * @default false\n */\n hideTextFallback?: boolean\n}\n\nconst DEFAULT_COPY: Required<Pick<AuthLogoCopy, 'fallbackLabel'>> = {\n fallbackLabel: 'Inicio de sesión',\n}\n\nconst VARIANT_CLASS: Record<'expanded' | 'compact', string> = {\n expanded: 'h-8 w-auto',\n compact: 'h-8 w-8',\n}\n\n// Intrinsic `width`/`height` attributes so the browser reserves layout space\n// via its built-in aspect-ratio-from-attributes heuristic before the real\n// image loads (avoids vertical CLS at minimum; horizontal width still\n// depends on the tenant's actual logo aspect ratio, which `/api/site/config`\n// doesn't expose — an arbitrary-but-reasonable box, not a promise of zero\n// shift). `VARIANT_CLASS`'s `h-8` always wins for height regardless.\nconst VARIANT_INTRINSIC_SIZE: Record<'expanded' | 'compact', { width: number; height: number }> = {\n expanded: { width: 160, height: 32 },\n compact: { width: 32, height: 32 },\n}\n\n/**\n * Dynamic tenant logo for auth pages (REB-510 R1). Resolves expanded/compact ×\n * light/dark from the 4 `/api/site/config` logo URLs, switching light/dark\n * CLIENT-SIDE via `next-themes`. Falls back to `siteName` text, then a generic\n * `copy.fallbackLabel`, whenever the resolved URL for the active\n * variant+theme combination is `null` — it never silently swaps in the OTHER\n * theme's logo.\n *\n * Hydration-safe: `next-themes`' `resolvedTheme` is `undefined` until it\n * mounts client-side, so pre-mount `isDark` is always `false` — the same\n * light-first markup the server rendered, avoiding a hydration mismatch.\n *\n * Renders a plain `<img>`, not `next/image`: this is a distributable SDK\n * consumed by external SITE repos, so it cannot assume the consuming app's\n * `next.config` `images.remotePatterns` allowlists the tenant's logo CDN.\n */\nexport function AuthLogo(props: AuthLogoProps) {\n const {\n logoLightExpandedUrl,\n logoDarkExpandedUrl,\n logoLightSmallUrl,\n logoDarkSmallUrl,\n siteName,\n variant = 'expanded',\n className,\n copy,\n headingLevel = 1,\n hideTextFallback = false,\n } = props\n const fallbackLabel = copy?.fallbackLabel ?? DEFAULT_COPY.fallbackLabel\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n\n const lightUrl = variant === 'compact' ? logoLightSmallUrl : logoLightExpandedUrl\n const darkUrl = variant === 'compact' ? logoDarkSmallUrl : logoDarkExpandedUrl\n const logoUrl = isDark ? darkUrl : lightUrl\n\n // Tracks a broken/stale logo URL (404, CDN outage, etc.) — degrades to the\n // text fallback instead of a broken-image glyph. Reset whenever `logoUrl`\n // itself changes (e.g. a theme toggle resolves a DIFFERENT URL) so a past\n // failure doesn't permanently block a future, valid attempt.\n const [imgError, setImgError] = React.useState(false)\n React.useEffect(() => {\n setImgError(false)\n }, [logoUrl])\n\n const fallbackText = siteName || fallbackLabel\n // `copy.altText` — a VALUE-defined key (including `''`) wins outright,\n // that's how a consumer marks the logo decorative. A value-undefined key\n // (whether omitted from `copy` entirely, or present with an explicit\n // `undefined` value) falls back to siteName/fallbackLabel — checking value\n // definedness, not `'altText' in copy` key presence, is what makes\n // `copy={{ altText: undefined }}` behave the same as omitting the key.\n const altText = copy?.altText !== undefined ? copy.altText : siteName || fallbackLabel\n\n // AuthLogo is \"logo XOR name\": exactly ONE of the brand mark (logo image) or\n // the `siteName` text is shown, never both — and by default AuthLogo IS the\n // auth page's heading, so the consuming page must NOT add its own separate\n // `<h1>{siteName}</h1>` (which is what double-rendered the name before).\n const Heading = `h${headingLevel}` as const\n\n if (logoUrl && !imgError) {\n const { width, height } = VARIANT_INTRINSIC_SIZE[variant]\n // Decorative escape hatch: when the consumer hides the text fallback OR\n // marks the logo decorative (`alt=''`), it owns the page heading itself, so\n // render a bare image — wrapping a nameless image in a heading would emit\n // an empty, accessible-name-less heading.\n const bare = hideTextFallback || altText === ''\n const image = (\n <img\n src={logoUrl}\n alt={altText}\n width={width}\n height={height}\n className={cn(VARIANT_CLASS[variant], 'max-w-full', bare && className)}\n onError={() => setImgError(true)}\n />\n )\n if (bare) return image\n // Default: wrap the logo in the heading so the auth page ALWAYS has a\n // heading — even when only the brand image is visible — whose accessible\n // name is the logo's `alt` (= `siteName`). No visible, duplicated name text.\n return <Heading className={cn('inline-flex items-center max-w-full', className)}>{image}</Heading>\n }\n\n if (hideTextFallback) return null\n\n // No logo resolved → render `siteName` (or the generic fallback label) as the\n // page's heading text.\n return (\n <Heading className={cn('text-2xl font-bold tracking-tight text-foreground', className)}>\n {fallbackText}\n </Heading>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\nexport interface ResetPasswordRequestCopy {\n emailLabel: string\n emailPlaceholder: string\n submitCta: string\n submittingCta: string\n successTitle: string\n successMessage: string\n backToLoginCta: string\n invalidEmailError: string\n networkError: string\n}\n\nexport interface ResetPasswordRequestProps {\n requestUrl: string\n loginUrl: string\n copy?: Partial<ResetPasswordRequestCopy>\n className?: string\n onSubmitted?: (email: string) => void\n}\n\nconst DEFAULT_COPY: ResetPasswordRequestCopy = {\n emailLabel: 'Correo electrónico',\n emailPlaceholder: 'tu@email.com',\n submitCta: 'Enviar enlace de recuperación',\n submittingCta: 'Enviando...',\n successTitle: 'Revisa tu email',\n successMessage:\n 'Si el email existe en nuestra plataforma, recibirás un enlace para restablecer tu contraseña en los próximos minutos.',\n backToLoginCta: '← Volver al inicio de sesión',\n invalidEmailError: 'Introduce un correo electrónico válido.',\n networkError: 'Error de conexión. Por favor, inténtalo de nuevo.',\n}\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Step 1 of the password-reset flow — anti-enumeration: always shows the same\n * generic success message on a 2xx response, regardless of whether the email\n * exists (the backend never leaks that).\n */\nexport function ResetPasswordRequest(props: ResetPasswordRequestProps) {\n const { requestUrl, loginUrl, copy, className, onSubmitted } = props\n const c = { ...DEFAULT_COPY, ...copy }\n\n const [email, setEmail] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [submitted, setSubmitted] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n\n const trimmed = email.trim().toLowerCase()\n if (!EMAIL_REGEX.test(trimmed)) {\n setError(c.invalidEmailError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSubmitted` callback from this\n // try/catch: if we called it INSIDE the try and it threw, that throw\n // would land in the network `catch` below and misreport a successful\n // request as a connection error.\n let succeeded = false\n try {\n const res = await fetch(requestUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email: trimmed }),\n })\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as { error?: string }\n setError(json.error ?? c.networkError)\n setLoading(false)\n return\n }\n\n succeeded = true\n setSubmitted(true)\n setLoading(false)\n } catch {\n setError(c.networkError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n try {\n onSubmitted?.(trimmed)\n } catch (err) {\n console.error('[ResetPasswordRequest] onSubmitted callback threw:', err)\n }\n }\n }\n\n if (submitted) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-request-email'>{c.emailLabel}</Label>\n <Input\n id='reset-request-email'\n type='email'\n autoComplete='email'\n placeholder={c.emailPlaceholder}\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n disabled={loading}\n aria-describedby={error ? 'reset-request-error' : undefined}\n autoFocus\n />\n {error && (\n <p id='reset-request-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n </div>\n <Button type='submit' className='w-full' disabled={loading}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n <div className='text-center'>\n <a\n href={loginUrl}\n className='text-muted-foreground hover:text-foreground text-sm transition-colors'\n >\n {c.backToLoginCta}\n </a>\n </div>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\n\n/**\n * Appends `key=value` to `url` without producing a malformed double `?` when\n * `url` (consumer-supplied `loginUrl`, e.g. `/login?tenant=acme`) already\n * carries a query string. Deliberately NOT `new URL(url)` — `loginUrl` is\n * typically a relative path and the WHATWG `URL` constructor requires a base\n * for those.\n */\nfunction withQueryParam(url: string, key: string, value: string): string {\n const separator = url.includes('?') ? '&' : '?'\n return `${url}${separator}${key}=${encodeURIComponent(value)}`\n}\n\nexport interface ResetPasswordConfirmCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n mfaCodeLabel: string\n mfaCodePlaceholder: string\n mfaHelpMessage: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n mfaInvalidCodeError: string\n genericError: string\n missingTokensTitle: string\n missingTokensMessage: string\n requestNewLinkCta: string\n}\n\nexport interface ResetPasswordConfirmProps {\n confirmUrl: string\n loginUrl: string\n resetRequestUrl: string\n copy?: Partial<ResetPasswordConfirmCopy>\n className?: string\n onSuccess?: () => void\n}\n\nconst DEFAULT_COPY: ResetPasswordConfirmCopy = {\n passwordLabel: 'Nueva contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n mfaCodeLabel: 'Código de verificación (2FA)',\n mfaCodePlaceholder: '6 dígitos',\n mfaHelpMessage:\n 'Tu cuenta tiene verificación en dos pasos. Introduce el código de tu app de autenticación para completar el cambio.',\n submitCta: 'Establecer nueva contraseña',\n submittingCta: 'Actualizando contraseña...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n mfaInvalidCodeError: 'Introduce el código de 6 dígitos de tu app de autenticación',\n genericError: 'Error al restablecer la contraseña',\n missingTokensTitle: 'Enlace inválido',\n missingTokensMessage: 'El enlace ha expirado o no es válido. Solicita uno nuevo.',\n requestNewLinkCta: 'Solicitar nuevo enlace',\n}\n\n/**\n * Step 2 of the password-reset flow. Reads `access_token`/`refresh_token`\n * straight off `useSearchParams` — no `window.location.search` + effect-timing\n * workaround needed here because, unlike a page-level default export, this is\n * a component the SITE mounts inside its own `<Suspense>` boundary.\n *\n * REB-378: when the account has a verified MFA factor, the recovery session\n * (AAL1) cannot change the password on its own. The backend responds\n * `{ data: { mfaRequired: true } }` and this reveals a 6-digit TOTP field so\n * the user can resubmit with the code (the recovery tokens stay valid).\n */\nexport function ResetPasswordConfirm(props: ResetPasswordConfirmProps) {\n const { confirmUrl, loginUrl, resetRequestUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const accessToken = searchParams.get('access_token')\n const refreshToken = searchParams.get('refresh_token')\n const hasTokens = Boolean(accessToken && refreshToken)\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mfaRequired, setMfaRequired] = React.useState(false)\n const [mfaCode, setMfaCode] = React.useState('')\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!hasTokens) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n if (mfaRequired && !/^\\d{6}$/.test(mfaCode)) {\n setError(c.mfaInvalidCodeError)\n return\n }\n\n setLoading(true)\n // `succeeded` isolates the consumer's `onSuccess` callback (and the\n // navigation) from this try/catch: calling either INSIDE the try means a\n // throwing callback lands in the network `catch` below and misreports an\n // already-successful password change as a failure — the user retries\n // with recovery tokens the backend has already consumed.\n let succeeded = false\n try {\n const res = await fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n access_token: accessToken,\n refresh_token: refreshToken,\n new_password: password,\n ...(mfaRequired ? { mfa_code: mfaCode } : {}),\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n error?: string\n data?: { success?: boolean; mfaRequired?: boolean }\n }\n\n if (!res.ok) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (json.data?.mfaRequired) {\n setMfaRequired(true)\n setLoading(false)\n return\n }\n\n succeeded = true\n setLoading(false)\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n if (succeeded) {\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch) — same reasoning as set-password.tsx: a throwing\n // `onSuccess` can no longer block the redirect or escape as an\n // unhandled rejection out of this fire-and-forget async handler.\n router.replace(withQueryParam(loginUrl, 'reset', 'success'))\n try {\n onSuccess?.()\n } catch (err) {\n console.error('[ResetPasswordConfirm] onSuccess callback threw:', err)\n }\n }\n }\n\n if (!hasTokens) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokensTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokensMessage}</p>\n <a href={resetRequestUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.requestNewLinkCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password'>{c.passwordLabel}</Label>\n <Input\n id='reset-confirm-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-password-2'>{c.confirmPasswordLabel}</Label>\n <Input\n id='reset-confirm-password-2'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n />\n </div>\n {mfaRequired && (\n <div className='space-y-1.5'>\n <Label htmlFor='reset-confirm-mfa'>{c.mfaCodeLabel}</Label>\n <Input\n id='reset-confirm-mfa'\n type='text'\n inputMode='numeric'\n autoComplete='one-time-code'\n placeholder={c.mfaCodePlaceholder}\n value={mfaCode}\n onChange={(e) => setMfaCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n disabled={loading}\n maxLength={6}\n aria-describedby={error ? 'reset-confirm-error' : undefined}\n autoFocus\n />\n <p className='text-muted-foreground text-xs'>{c.mfaHelpMessage}</p>\n </div>\n )}\n {error && (\n <p id='reset-confirm-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button\n type='submit'\n className='w-full'\n disabled={\n loading || !password || !confirmPassword || (mfaRequired && mfaCode.length !== 6)\n }\n >\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Button, Input, cn } from '@reblu/site-ui'\nimport { Label } from '@reblu/site-ui/label'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface SetPasswordCopy {\n passwordLabel: string\n passwordPlaceholder: string\n confirmPasswordLabel: string\n confirmPasswordPlaceholder: string\n submitCta: string\n submittingCta: string\n passwordTooShortError: string\n passwordMismatchError: string\n genericError: string\n missingTokenTitle: string\n missingTokenMessage: string\n backToLoginCta: string\n}\n\nexport interface SetPasswordProps {\n submitUrl: string\n loginUrl: string\n copy?: Partial<SetPasswordCopy>\n className?: string\n onSuccess?: (sessionExchangeUrl: string) => void\n}\n\nconst DEFAULT_COPY: SetPasswordCopy = {\n passwordLabel: 'Contraseña',\n passwordPlaceholder: 'Mínimo 8 caracteres',\n confirmPasswordLabel: 'Confirmar contraseña',\n confirmPasswordPlaceholder: 'Repite la contraseña',\n submitCta: 'Activar cuenta',\n submittingCta: 'Activando cuenta...',\n passwordTooShortError: 'La contraseña debe tener al menos 8 caracteres',\n passwordMismatchError: 'Las contraseñas no coinciden',\n genericError: 'Error al establecer la contraseña',\n missingTokenTitle: 'Enlace inválido',\n missingTokenMessage: 'El enlace de invitación ha expirado o no es válido.',\n backToLoginCta: '← Volver al inicio de sesión',\n}\n\n/**\n * Invite flow — for a user setting their initial password. Reads\n * `session_exchange_token` off `useSearchParams`; on success, redirects via\n * `window.location.assign` (NOT the Next router) because the returned\n * `sessionExchangeUrl` points at base-api's own domain to set cookies, not a\n * same-origin SITE route.\n */\nexport function SetPassword(props: SetPasswordProps) {\n const { submitUrl, loginUrl, copy, className, onSuccess } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n const sessionExchangeToken = searchParams.get('session_exchange_token')\n\n const [password, setPassword] = React.useState('')\n const [confirmPassword, setConfirmPassword] = React.useState('')\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n e.preventDefault()\n setError(null)\n if (!sessionExchangeToken) return\n\n if (password.length < 8) {\n setError(c.passwordTooShortError)\n return\n }\n if (password !== confirmPassword) {\n setError(c.passwordMismatchError)\n return\n }\n\n setLoading(true)\n // `sessionExchangeUrl` isolates the consumer's `onSuccess` callback (and\n // the actual navigation) from this try/catch: calling either INSIDE the\n // try means a throwing callback lands in the network `catch` below and\n // misreports an already-successful password-set as a connection error —\n // stranding an activated user on a form that tells them it failed.\n let sessionExchangeUrl: string | null = null\n try {\n const res = await fetch(submitUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_exchange_token: sessionExchangeToken,\n new_password: password,\n }),\n })\n\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string }\n error?: string\n }\n\n if (!res.ok || !json.data?.sessionExchangeUrl) {\n setError(json.error ?? c.genericError)\n setLoading(false)\n return\n }\n\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n sessionExchangeUrl = json.data.sessionExchangeUrl\n } catch {\n setError(c.genericError)\n setLoading(false)\n return\n }\n\n // Navigate FIRST, consumer callback SECOND (isolated in its own\n // try/catch): if `onSuccess` throws (ad blocker on an analytics call,\n // etc.) the navigation has already been triggered and is unaffected, and\n // the throw doesn't escape as an unhandled rejection out of this\n // fire-and-forget async handler — the user is never stranded on this\n // form by code that isn't even this component's own.\n window.location.assign(sessionExchangeUrl)\n try {\n onSuccess?.(sessionExchangeUrl)\n } catch (err) {\n console.error('[SetPassword] onSuccess callback threw:', err)\n }\n }\n\n if (!sessionExchangeToken) {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.missingTokenTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.missingTokenMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.backToLoginCta}\n </a>\n </div>\n )\n }\n\n return (\n <form\n onSubmit={(e) => void handleSubmit(e)}\n className={cn('space-y-5', className)}\n noValidate\n >\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-password'>{c.passwordLabel}</Label>\n <Input\n id='set-password-password'\n type='password'\n autoComplete='new-password'\n placeholder={c.passwordPlaceholder}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n // Single-purpose activation page (this IS the whole page's task,\n // there's no earlier content competing for attention) — same\n // pattern as reset-password-confirm.tsx/reset-password-request.tsx.\n autoFocus\n />\n </div>\n <div className='space-y-1.5'>\n <Label htmlFor='set-password-confirm'>{c.confirmPasswordLabel}</Label>\n <Input\n id='set-password-confirm'\n type='password'\n autoComplete='new-password'\n placeholder={c.confirmPasswordPlaceholder}\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n disabled={loading}\n minLength={8}\n aria-describedby={error ? 'set-password-error' : undefined}\n />\n </div>\n {error && (\n <p id='set-password-error' role='alert' className='text-destructive text-sm'>\n {error}\n </p>\n )}\n <Button type='submit' className='w-full' disabled={loading || !password || !confirmPassword}>\n {loading ? c.submittingCta : c.submitCta}\n </Button>\n </form>\n )\n}\n","'use client'\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { cn } from '@reblu/site-ui'\nimport { isSafeRedirectUrl } from './safe-redirect'\n\nexport interface AuthConfirmCopy {\n loadingMessage: string\n successTitle: string\n successMessage: string\n successCta: string\n errorTitle: string\n errorMessage: string\n errorCta: string\n}\n\nexport interface AuthConfirmProps {\n confirmUrl: string\n loginUrl: string\n copy?: Partial<AuthConfirmCopy>\n className?: string\n}\n\nconst DEFAULT_COPY: AuthConfirmCopy = {\n loadingMessage: 'Confirmando tu email...',\n successTitle: 'Email confirmado',\n successMessage: 'Tu dirección de email ha sido verificada correctamente.',\n successCta: 'Iniciar sesión',\n errorTitle: 'Error al confirmar',\n errorMessage: 'El enlace de confirmación no es válido o ha expirado.',\n errorCta: 'Volver al inicio de sesión',\n}\n\ntype Status = 'loading' | 'success' | 'error'\n\n/**\n * Dual-mode email confirmation (signup / email-change):\n *\n * 1. `?success=true` — base-api already ran `verifyOtp` server-side and\n * redirected here. Shows the confirmed message with no API call.\n * 2. `?token_hash=&type=` — client-initiated verify: POSTs to `confirmUrl`\n * and follows the returned `sessionExchangeUrl`, or shows success/error\n * copy based on the response.\n *\n * Neither param present → shown as an error state with a link back to login,\n * rather than a silent client-side redirect.\n */\nexport function AuthConfirm(props: AuthConfirmProps) {\n const { confirmUrl, loginUrl, copy, className } = props\n const c = { ...DEFAULT_COPY, ...copy }\n const searchParams = useSearchParams()\n\n const success = searchParams.get('success')\n const tokenHash = searchParams.get('token_hash')\n const type = searchParams.get('type')\n\n const [status, setStatus] = React.useState<Status>(success === 'true' ? 'success' : 'loading')\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (success === 'true') {\n setStatus('success')\n return\n }\n\n if (tokenHash && type) {\n // `token_hash` is single-use server-side — verifying it twice makes\n // the SECOND call fail even though the first already succeeded. React\n // Strict Mode's dev-only mount→cleanup→mount double-invoke would\n // otherwise fire this fetch twice for the same token. Aborting the\n // in-flight request on cleanup stops the CLIENT from processing that\n // first response and swallows its `AbortError` below instead of\n // surfacing a false \"confirmation failed\" — but `abort()` cannot\n // retroactively un-send a request that already reached base-api\n // before cleanup ran, so this reduces rather than deterministically\n // eliminates the double-POST race (in practice React's dev\n // double-invoke runs both phases synchronously in the same commit, so\n // the abort typically wins, but that is not a spec guarantee). A\n // fully deterministic fix needs server-side idempotency on\n // `verify-otp`, which is out of this package's control. The same\n // abort also guards a genuine unmount mid-request.\n const controller = new AbortController()\n setStatus('loading')\n fetch(confirmUrl, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token_hash: tokenHash, type }),\n signal: controller.signal,\n })\n .then(async (res) => {\n const json = (await res.json().catch(() => ({}))) as {\n data?: { sessionExchangeUrl?: string; success?: boolean }\n error?: string\n }\n\n if (!res.ok) {\n setStatus('error')\n setErrorMessage(json.error ?? c.errorMessage)\n return\n }\n\n if (json.data?.sessionExchangeUrl) {\n if (!isSafeRedirectUrl(json.data.sessionExchangeUrl)) {\n setStatus('error')\n setErrorMessage(c.errorMessage)\n return\n }\n window.location.assign(json.data.sessionExchangeUrl)\n return\n }\n\n setStatus('success')\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') return\n setStatus('error')\n setErrorMessage(c.errorMessage)\n })\n\n return () => controller.abort()\n }\n\n // Neither success=true nor token_hash+type — nothing to confirm.\n setStatus('error')\n // `c.errorMessage` (derived from the `copy` prop) is intentionally\n // excluded: re-running this effect on every `copy` object identity\n // change would re-fire the fetch. The URL params are the only real\n // trigger for this effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [success, tokenHash, type, confirmUrl])\n\n if (status === 'loading') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <p className='text-muted-foreground text-sm'>{c.loadingMessage}</p>\n </div>\n )\n }\n\n if (status === 'error') {\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.errorTitle}</h2>\n <p className='text-muted-foreground text-sm'>{errorMessage ?? c.errorMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.errorCta}\n </a>\n </div>\n )\n }\n\n return (\n <div className={cn('space-y-4 text-center', className)}>\n <h2 className='text-lg font-semibold'>{c.successTitle}</h2>\n <p className='text-muted-foreground text-sm'>{c.successMessage}</p>\n <a href={loginUrl} className='text-primary inline-block text-sm hover:underline'>\n {c.successCta}\n </a>\n </div>\n )\n}\n"],"mappings":"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","names":["React","Button","cn","jsx","jsxs","DEFAULT_COPY","cn","Button","DEFAULT_COPY","React","cn","jsx","DEFAULT_COPY","React","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","EMAIL_REGEX","React","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","React","useSearchParams","Button","Input","cn","Label","jsx","jsxs","DEFAULT_COPY","useSearchParams","cn","Label","Input","Button","React","useSearchParams","cn","jsx","jsxs","DEFAULT_COPY","useSearchParams","cn"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reblu/site-next",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Next.js RSC glue for Reblu SITEs — cache + CTA + auth server/client components.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Reblu — Adolfo Unturbe",
@@ -91,9 +91,9 @@
91
91
  "react": ">=19"
92
92
  },
93
93
  "dependencies": {
94
- "@reblu/site-client": "^0.1.2",
95
- "@reblu/site-contracts": "^0.1.2",
96
- "@reblu/site-ui": "^0.3.0"
94
+ "@reblu/site-client": "^1.0.0",
95
+ "@reblu/site-ui": "^0.3.1",
96
+ "@reblu/site-contracts": "^0.2.0"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@testing-library/jest-dom": "^6.9.1",