@main12/auth-login 0.3.8 → 0.4.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.
Files changed (29) hide show
  1. package/README.md +180 -4
  2. package/dist/components/AuthLayout.d.ts +9 -0
  3. package/dist/components/AuthPages.d.ts +15 -1
  4. package/dist/components/AuthPages.js +7 -1
  5. package/dist/components/AuthPagesServer.d.ts +6 -1
  6. package/dist/components/AuthPagesServer.js +11 -2
  7. package/dist/components/pages/ForgotPasswordPageHero.d.ts +1 -1
  8. package/dist/components/pages/ForgotPasswordPageHero.js +8 -6
  9. package/dist/components/pages/ForgotPasswordPageTailwind.d.ts +1 -1
  10. package/dist/components/pages/ForgotPasswordPageTailwind.js +12 -7
  11. package/dist/components/pages/LoginPageHero.js +18 -16
  12. package/dist/components/pages/LoginPageTailwind.js +18 -16
  13. package/dist/components/pages/SetPasswordPageHero.d.ts +1 -1
  14. package/dist/components/pages/SetPasswordPageHero.js +8 -6
  15. package/dist/components/pages/SetPasswordPageTailwind.d.ts +1 -1
  16. package/dist/components/pages/SetPasswordPageTailwind.js +9 -7
  17. package/dist/components/pages/SignupPageHero.d.ts +1 -1
  18. package/dist/components/pages/SignupPageHero.js +20 -14
  19. package/dist/components/pages/SignupPageTailwind.d.ts +1 -1
  20. package/dist/components/pages/SignupPageTailwind.js +17 -14
  21. package/dist/components/pages/VerifyOtpPageHero.js +9 -7
  22. package/dist/components/pages/VerifyOtpPageTailwind.js +13 -8
  23. package/dist/components/ui/locale.d.ts +29 -0
  24. package/dist/components/ui/locale.js +53 -0
  25. package/dist/components/ui/translations.d.ts +91 -0
  26. package/dist/components/ui/translations.js +179 -0
  27. package/dist/exports/client.d.ts +3 -0
  28. package/dist/exports/client.js +3 -0
  29. package/package.json +1 -1
package/README.md CHANGED
@@ -36,6 +36,7 @@ plugins: [
36
36
  - **Smart redirects** — logged-in users are redirected away from auth pages
37
37
  - **Google account picker** — always shows account selection by default (`prompt: 'select_account'`)
38
38
  - **Zero runtime deps** (Tailwind mode) — Next.js + React are peer dependencies
39
+ - **Multi-language** — built-in English/Spanish, auto-detected per-request, fully overridable via `messages` prop, works with or without next-intl/next-i18next
39
40
 
40
41
  ---
41
42
 
@@ -146,6 +147,41 @@ This redirects:
146
147
 
147
148
  ---
148
149
 
150
+ ## Testing All Pages
151
+
152
+ Once the catch-all route is set up, every auth page is reachable directly by URL. Some pages require query params to have meaningful content (they're normally reached by clicking through the flow, e.g. signup → verify-otp), so use the URLs below to test each one in isolation:
153
+
154
+ | Page | URL to test | Notes |
155
+ |------|-------------|-------|
156
+ | **Login** | `/auth/login` | 3 sub-states, driven by user interaction: email step (default) → password step or OTP-prompt step, depending on whether the account has a password and `passwordLogin`/`otpLogin` config |
157
+ | **Signup** | `/auth/signup` | Hidden entirely if `allowSignup: false` — falls back to rendering Login instead |
158
+ | **Forgot Password** | `/auth/forgot-password` | No query params required |
159
+ | **Verify OTP** | `/auth/verify-otp?email=test@example.com&purpose=signup` | ⚠️ **Renders a blank page if `email` is missing** — always include `?email=...`. `purpose` can be `login`, `signup`, or `password-reset` (changes the title/subtitle) |
160
+ | **Set Password** | `/auth/set-password` | No query params required |
161
+
162
+ > **Why does `/auth/verify-otp` show a blank page?** The page intentionally renders `null` when there's no `email` in the URL, since in real usage it's always reached via a redirect from signup/login/forgot-password that appends `?email=...&purpose=...`. This is expected — not a bug. Always test it with the full query string above.
163
+
164
+ ### Testing the Login page's 3 sub-states
165
+
166
+ The Login page's step is internal UI state, not driven by the URL — to see each one:
167
+ 1. **Email step** (default) — just load `/auth/login`.
168
+ 2. **Password step** — enter an email that belongs to a user *with* a password set, then submit. Requires `passwordLogin: true` (default).
169
+ 3. **OTP-prompt step** — enter an email that belongs to a user *without* a password set (e.g. a Google OAuth-only user), with `otpLogin: true` and `passwordLogin: true`.
170
+
171
+ ### Testing Google OAuth
172
+
173
+ Google OAuth buttons only render when `providers.google` is configured with valid credentials (see [Google OAuth](#google-oauth-providers) below). With no credentials, the button is hidden — this is expected in a fresh setup.
174
+
175
+ ### Testing different locales
176
+
177
+ Every page also respects the `locale` prop / auto-detection (see [Multi-Language Support](#multi-language-support)). To manually verify a specific language without changing your browser or OS settings, append the plugin's `locale` prop explicitly in your route file, or set the `NEXT_LOCALE` cookie / send an `Accept-Language` header:
178
+
179
+ ```bash
180
+ curl -H "Accept-Language: es-MX,es;q=0.9" http://localhost:3000/auth/login
181
+ ```
182
+
183
+ ---
184
+
149
185
  ## Configuration
150
186
 
151
187
  ```ts
@@ -260,9 +296,91 @@ The `AuthPages` component accepts these props for customization:
260
296
  showGoogleOAuth={true} // Override Google OAuth visibility
261
297
  onPasswordLogin={customLogin} // Custom login handler
262
298
  onSignup={customSignup} // Custom signup handler
299
+ locale="es" // Override auto-detected locale (see Multi-Language Support)
300
+ messages={{ es: { login: { title: 'Bienvenido' } } }} // Partial translation overrides
301
+ />
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Multi-Language Support
307
+
308
+ Built-in support for **English (`en`)** and **Spanish (`es`)** — works out of the box, no configuration required. The plugin never depends on next-intl, next-i18next, or any i18n library; it reads the same conventional signals those libraries already write, so it plugs into whatever your app already does automatically.
309
+
310
+ ### Automatic locale detection
311
+
312
+ If you don't pass a `locale` prop, it's resolved per-request in this order:
313
+
314
+ 1. `NEXT_LOCALE` cookie (written by next-intl, next-i18next, and most i18n routing middlewares by convention)
315
+ 2. `Accept-Language` request header
316
+ 3. `<html lang="...">` attribute (client-side fallback)
317
+ 4. `'en'` (default)
318
+
319
+ This means if your app already uses next-intl or next-i18next, the auth pages automatically match your site's current language — **zero extra code needed**.
320
+
321
+ ### Explicit locale override
322
+
323
+ Pass `locale` directly on `<AuthPages />` (or any individual page component) to force a specific language for that render, regardless of auto-detection — useful if you resolve the locale yourself server-side:
324
+
325
+ ```tsx
326
+ // app/(auth)/auth/[...slug]/page.tsx
327
+ import { AuthPages } from '@main12/auth-login/rsc'
328
+ import { getLocale } from 'next-intl/server' // or however your app resolves it
329
+
330
+ export default async function Page({ params }: { params: Promise<{ slug: string[] }> }) {
331
+ const { slug } = await params
332
+ const locale = await getLocale()
333
+ return <AuthPages slug={slug} locale={locale} />
334
+ }
335
+ ```
336
+
337
+ ### Overriding copy / adding new languages
338
+
339
+ Pass a `messages` object — a partial override, keyed by locale. Any key you don't specify falls back to the built-in English/Spanish copy automatically. You can also introduce entirely new locales this way (e.g. French, Portuguese):
340
+
341
+ ```tsx
342
+ <AuthPages
343
+ slug={slug}
344
+ messages={{
345
+ en: {
346
+ login: { title: 'Welcome Back!' }, // override just this one key
347
+ },
348
+ es: {
349
+ login: { title: '¡Bienvenido de nuevo!' },
350
+ },
351
+ fr: { // brand new locale, not built-in — falls back to English for any key not provided
352
+ login: { title: 'Content de vous revoir', subtitle: 'Connectez-vous pour continuer.' },
353
+ signup: { title: 'Créer un compte' },
354
+ },
355
+ }}
263
356
  />
264
357
  ```
265
358
 
359
+ Resolution order per key: `messages[locale]` → `messages.en` → built-in `[locale]` dictionary → built-in `en` dictionary.
360
+
361
+ ### Available translation keys
362
+
363
+ Each page has its own section — see `UiTranslations` (exported from `@main12/auth-login/client`) for the full shape:
364
+
365
+ | Section | Covers |
366
+ |---------|--------|
367
+ | `login` | Title/subtitle for all 3 sub-states (email, password, OTP-prompt), Google button, form labels, links |
368
+ | `signup` | Title/subtitle, form labels, Google button, terms/privacy links, login link |
369
+ | `forgotPassword` | Title/subtitle, form labels, back-to-login link |
370
+ | `verifyOtp` | Title/subtitle for both variants (code verification vs password-reset), resend button (supports `{seconds}` interpolation) |
371
+ | `setPassword` | Title/subtitle, form labels, password requirements text |
372
+
373
+ ### Using translations in custom pages
374
+
375
+ If you're building fully custom pages with the hooks (see [Building Custom Pages](#building-custom-pages)), import `getUiTranslations` directly:
376
+
377
+ ```tsx
378
+ import { getUiTranslations } from '@main12/auth-login/client'
379
+
380
+ const t = getUiTranslations(locale, messages).login
381
+ // t.title, t.continueWithGoogle, t.emailLabel, etc.
382
+ ```
383
+
266
384
  ---
267
385
 
268
386
  ## Individual Page Setup (Advanced)
@@ -455,15 +573,73 @@ await payload.sendEmail({
455
573
 
456
574
  ---
457
575
 
458
- ## Exports
576
+ ## Exports & Components Reference
459
577
 
460
578
  | Import path | Contents |
461
579
  |-------------|----------|
462
- | `@main12/auth-login` | Plugin factory, types, server config |
463
- | `@main12/auth-login/client` | Page components, `AuthPages`, hooks, services, UI utilities |
464
- | `@main12/auth-login/rsc` | Email template generators, translations |
580
+ | `@main12/auth-login` | Plugin factory (`authLoginPlugin`), types, server config |
581
+ | `@main12/auth-login/client` | Page components, `AuthPages`, hooks, services, UI/locale utilities |
582
+ | `@main12/auth-login/rsc` | Server component `AuthPages` wrapper, email template generators, email translations |
465
583
  | `@main12/auth-login/proxy` | Next.js 16 proxy for route redirects |
466
584
 
585
+ ### Components (`@main12/auth-login/client` unless noted)
586
+
587
+ | Component | Description | Typical usage |
588
+ |-----------|-------------|----------------|
589
+ | `AuthPages` (also `@main12/auth-login/rsc`) | Catch-all component — renders the correct page based on `slug`. The `/rsc` version is a server component that auto-resolves plugin config + locale from headers; the `/client` version needs `'use client'` and manual config props | `<AuthPages slug={slug} />` in your `[...slug]/page.tsx` |
590
+ | `LoginPage` | Standalone login page (email → password/OTP flow) | Individual route setup, or full customization via props |
591
+ | `SignupPage` | Standalone signup page | Individual route setup |
592
+ | `ForgotPasswordPage` | Standalone forgot-password page | Individual route setup |
593
+ | `VerifyOtpPage` | Standalone OTP verification page — **requires `?email=...` in the URL** | Reached via redirect from signup/login/forgot-password |
594
+ | `SetPasswordPage` | Standalone set/reset password page | Individual route setup, or post-OTP password creation |
595
+ | `AuthLayout` | Shared card/background/logo/footer chrome used by every page — use it directly when building fully custom pages | `<AuthLayout title="..." subtitle="...">{children}</AuthLayout>` |
596
+ | `AuthClientInit` | Drop into your root layout to sync server plugin config (`style`, Google OAuth flag) to client bundles. Optional — only needed if you hit issues with plugin config not reaching client components in certain bundler setups | `<AuthClientInit />` inside `<body>` |
597
+ | `PoweredBy` | The "Powered by Main 12" footer badge, rendered automatically on every page (configurable via `poweredBy` prop) | Rarely used standalone — mostly internal |
598
+
599
+ ### Hooks (`@main12/auth-login/client`)
600
+
601
+ | Hook | Returns | Key inputs |
602
+ |------|---------|------------|
603
+ | `useLoginFlow({ redirectTo, onPasswordLogin })` | `step, email, password, error, isLoading, handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail` | `redirectTo: string`, `onPasswordLogin: (creds) => Promise<void>` |
604
+ | `useForgotPasswordFlow()` | `email, error, isLoading, setEmail, handleSubmit` | none |
605
+ | `useVerifyOtpFlow({ email, purpose, redirectTo })` | `otp, error, isLoading, isResending, resendCooldown, setOtp, handleSubmit, handleResendCode` | `email: string`, `purpose: 'login'\|'signup'\|'password-reset'` |
606
+ | `useSetPasswordFlow({ redirectTo })` | `password, confirmPassword, error, isLoading, strength, setPassword, setConfirmPassword, handleSubmit` | `redirectTo: string` |
607
+
608
+ ### Service functions (`@main12/auth-login/client`)
609
+
610
+ Low-level `fetch` wrappers used internally by the hooks — call directly for fully custom flows:
611
+
612
+ | Function | Description |
613
+ |----------|--------------|
614
+ | `checkEmail(email)` | Checks whether an email is already registered |
615
+ | `sendOtp(email, purpose)` | Requests a new OTP code |
616
+ | `verifyOtp(email, otp, purpose)` | Verifies an OTP code, logs the user in on success |
617
+ | `setUserPassword(password)` | Sets/updates the current user's password |
618
+ | `signup(name, email)` | Creates a new account, triggers welcome email + OTP verification |
619
+ | `initiateGoogleLogin()` | Redirects to the Google OAuth flow |
620
+
621
+ ### Utilities & config (`@main12/auth-login/client`)
622
+
623
+ | Export | Description |
624
+ |--------|--------------|
625
+ | `initClientConfig(opts)` | Manually sync plugin config into the client bundle (used internally by `AuthPages`/`AuthClientInit`) |
626
+ | `evaluatePasswordStrength(password)` | Returns `{ score, isValid, ... }` — used by the password strength meter |
627
+ | `isPasswordValid(password)` / `MIN_PASSWORD_LENGTH` | Password validation helpers |
628
+ | `getUiTranslations(locale, messages)` | Resolve translated UI copy — see [Multi-Language Support](#multi-language-support) |
629
+ | `uiTranslations` | Raw built-in `{ en, es }` dictionaries, if you need to read them directly |
630
+ | `detectClientLocale()` | Client-side locale auto-detection (cookie → `<html lang>` → `'en'`) |
631
+
632
+ ### Email templates (`@main12/auth-login/rsc`)
633
+
634
+ | Export | Description |
635
+ |--------|--------------|
636
+ | `generateWelcomeEmail(params)` | Welcome email after signup |
637
+ | `generateOtpEmail(params)` | OTP code email (login/signup/password-reset) |
638
+ | `generatePasswordResetEmail(params)` | Password reset code email |
639
+ | `generatePasswordChangedEmail(params)` | Confirmation after password change |
640
+ | `getEmailTranslations(locale)` | English/Spanish email copy (separate dictionary from the UI translations above) |
641
+ | `wrapInBaseTemplate(options)` | Wraps any HTML body in the plugin's branded email shell |
642
+
467
643
  ---
468
644
 
469
645
  ## ShadCN Compatibility
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import type { DeepPartial, UiTranslations } from './ui/translations';
2
3
  export interface AuthLayoutConfig {
3
4
  /** Component or URL. Falls back to pluginConfig when omitted. */
4
5
  logo?: React.ReactNode;
@@ -13,6 +14,14 @@ export interface AuthLayoutConfig {
13
14
  };
14
15
  cardClassName?: string;
15
16
  backgroundClass?: string;
17
+ /**
18
+ * Locale for this page's copy. Built-in support for 'en' and 'es'.
19
+ * Auto-detected by `<AuthPages />` when omitted; pass explicitly when using
20
+ * individual page components directly. Falls back to 'en'.
21
+ */
22
+ locale?: string;
23
+ /** Partial translation overrides, keyed by locale. See `getUiTranslations`. */
24
+ messages?: Record<string, DeepPartial<UiTranslations>>;
16
25
  }
17
26
  export interface AuthLayoutProps extends AuthLayoutConfig {
18
27
  children: React.ReactNode;
@@ -1,4 +1,5 @@
1
1
  import type React from 'react';
2
+ import type { DeepPartial, UiTranslations } from './ui/translations';
2
3
  export interface AuthPagesProps {
3
4
  /** The slug segments from the catch-all route, e.g. ['login'] or ['forgot-password'] */
4
5
  slug?: string[];
@@ -30,5 +31,18 @@ export interface AuthPagesProps {
30
31
  passwordLogin?: boolean;
31
32
  /** Allow OTP-based login. @default true */
32
33
  otpLogin?: boolean;
34
+ /**
35
+ * Locale for the auth pages' copy. Built-in support for 'en' and 'es'.
36
+ * If omitted, auto-detected from the `NEXT_LOCALE` cookie, `Accept-Language`
37
+ * header, or `<html lang>` — works automatically with next-intl,
38
+ * next-i18next, or no i18n library at all. Falls back to 'en'.
39
+ */
40
+ locale?: string;
41
+ /**
42
+ * Partial translation overrides, keyed by locale. Any key you omit falls
43
+ * back to the built-in English/Spanish copy. You can also add entirely new
44
+ * locales this way (e.g. `messages={{ fr: { login: { title: 'Bienvenue' } } }}`).
45
+ */
46
+ messages?: Record<string, DeepPartial<UiTranslations>>;
33
47
  }
34
- export default function AuthPages({ slug, redirectTo, logo, onPasswordLogin, onSignup, basePath, showGoogleOAuth, backgroundClass, style, allowSignup, passwordLogin, otpLogin, }: AuthPagesProps): import("react/jsx-runtime").JSX.Element;
48
+ export default function AuthPages({ slug, redirectTo, logo, onPasswordLogin, onSignup, basePath, showGoogleOAuth, backgroundClass, style, allowSignup, passwordLogin, otpLogin, locale, messages, }: AuthPagesProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { pluginConfig, initClientConfig } from '../config.js';
4
+ import { detectClientLocale } from './ui/locale.js';
4
5
  import LoginPage from './pages/LoginPage.js';
5
6
  import SignupPage from './pages/SignupPage.js';
6
7
  import ForgotPasswordPage from './pages/ForgotPasswordPage.js';
@@ -38,7 +39,7 @@ const defaultSignup = async ({ name, email })=>{
38
39
  throw new Error(err.message || 'Signup failed');
39
40
  }
40
41
  };
41
- export default function AuthPages({ slug, redirectTo = '/admin', logo, onPasswordLogin = defaultPasswordLogin, onSignup = defaultSignup, basePath = '/auth', showGoogleOAuth, backgroundClass, style, allowSignup = true, passwordLogin = true, otpLogin = true }) {
42
+ export default function AuthPages({ slug, redirectTo = '/admin', logo, onPasswordLogin = defaultPasswordLogin, onSignup = defaultSignup, basePath = '/auth', showGoogleOAuth, backgroundClass, style, allowSignup = true, passwordLogin = true, otpLogin = true, locale, messages }) {
42
43
  // Sync server plugin config to client using props (which come from the server via AuthPagesServer)
43
44
  initClientConfig({
44
45
  style: style ?? pluginConfig.style,
@@ -46,10 +47,15 @@ export default function AuthPages({ slug, redirectTo = '/admin', logo, onPasswor
46
47
  passwordLogin,
47
48
  otpLogin
48
49
  });
50
+ // Resolve locale client-side only if the server didn't already resolve one
51
+ // (AuthPagesServer always resolves and passes an explicit locale down).
52
+ const resolvedLocale = locale ?? detectClientLocale();
49
53
  const page = slug?.[0] ?? 'login';
50
54
  const base = basePath.replace(/\/$/, '');
51
55
  const shared = {
52
56
  logo,
57
+ locale: resolvedLocale,
58
+ messages,
53
59
  ...showGoogleOAuth !== undefined ? {
54
60
  showGoogleOAuth
55
61
  } : {},
@@ -7,6 +7,11 @@ export type { AuthPagesProps };
7
7
  * down to the client component. No env vars involved — the plugin's
8
8
  * `style`, `googleOAuthEnabled`, etc. options are the single source of truth.
9
9
  *
10
+ * Locale is resolved per-request: an explicit `locale` prop always wins,
11
+ * otherwise it's auto-detected from the `NEXT_LOCALE` cookie or
12
+ * `Accept-Language` header (works automatically with next-intl,
13
+ * next-i18next, or no i18n library at all — no dependency required).
14
+ *
10
15
  * ```tsx
11
16
  * // app/(auth)/auth/[...slug]/page.tsx (NO 'use client' needed!)
12
17
  * import { AuthPages } from '@main12/auth-login/rsc'
@@ -16,4 +21,4 @@ export type { AuthPagesProps };
16
21
  * }
17
22
  * ```
18
23
  */
19
- export default function AuthPages(props: AuthPagesProps): import("react/jsx-runtime").JSX.Element;
24
+ export default function AuthPages(props: AuthPagesProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { headers } from 'next/headers';
2
3
  import AuthPagesClient from './AuthPages.js';
3
4
  import { pluginConfig } from '../config.js';
5
+ import { detectServerLocale } from './ui/locale.js';
4
6
  /**
5
7
  * Server component wrapper for AuthPages.
6
8
  * Reads plugin config directly from the shared `pluginConfig` singleton
@@ -8,6 +10,11 @@ import { pluginConfig } from '../config.js';
8
10
  * down to the client component. No env vars involved — the plugin's
9
11
  * `style`, `googleOAuthEnabled`, etc. options are the single source of truth.
10
12
  *
13
+ * Locale is resolved per-request: an explicit `locale` prop always wins,
14
+ * otherwise it's auto-detected from the `NEXT_LOCALE` cookie or
15
+ * `Accept-Language` header (works automatically with next-intl,
16
+ * next-i18next, or no i18n library at all — no dependency required).
17
+ *
11
18
  * ```tsx
12
19
  * // app/(auth)/auth/[...slug]/page.tsx (NO 'use client' needed!)
13
20
  * import { AuthPages } from '@main12/auth-login/rsc'
@@ -16,13 +23,15 @@ import { pluginConfig } from '../config.js';
16
23
  * return <AuthPages slug={slug} />
17
24
  * }
18
25
  * ```
19
- */ export default function AuthPages(props) {
26
+ */ export default async function AuthPages(props) {
27
+ const resolvedLocale = props.locale ?? detectServerLocale(await headers());
20
28
  return /*#__PURE__*/ _jsx(AuthPagesClient, {
21
29
  ...props,
22
30
  showGoogleOAuth: props.showGoogleOAuth ?? pluginConfig.googleOAuthEnabled,
23
31
  style: props.style ?? pluginConfig.style,
24
32
  allowSignup: props.allowSignup ?? pluginConfig.allowSignup,
25
33
  passwordLogin: pluginConfig.passwordLogin,
26
- otpLogin: pluginConfig.otpLogin
34
+ otpLogin: pluginConfig.otpLogin,
35
+ locale: resolvedLocale
27
36
  });
28
37
  }
@@ -2,4 +2,4 @@ import type { AuthLayoutConfig } from '../AuthLayout';
2
2
  export interface ForgotPasswordPageHeroProps extends AuthLayoutConfig {
3
3
  loginUrl?: string;
4
4
  }
5
- export default function ForgotPasswordPageHero({ loginUrl, logo, poweredBy, cardClassName, backgroundClass }: ForgotPasswordPageHeroProps): import("react/jsx-runtime").JSX.Element;
5
+ export default function ForgotPasswordPageHero({ loginUrl, logo, poweredBy, cardClassName, backgroundClass, locale, messages }: ForgotPasswordPageHeroProps): import("react/jsx-runtime").JSX.Element;
@@ -6,12 +6,14 @@ import { Icon } from '@iconify/react';
6
6
  import { motion } from 'framer-motion';
7
7
  import { useForgotPasswordFlow } from '../../auth/application/hooks/useForgotPasswordFlow.js';
8
8
  import { AuthLayout } from '../AuthLayout.js';
9
- export default function ForgotPasswordPageHero({ loginUrl = '/login', logo, poweredBy, cardClassName, backgroundClass }) {
9
+ import { getUiTranslations } from '../ui/translations.js';
10
+ export default function ForgotPasswordPageHero({ loginUrl = '/login', logo, poweredBy, cardClassName, backgroundClass, locale, messages }) {
10
11
  const { email, error, isLoading, setEmail, handleSubmit } = useForgotPasswordFlow();
12
+ const t = getUiTranslations(locale, messages).forgotPassword;
11
13
  return /*#__PURE__*/ _jsx(AuthLayout, {
12
14
  logo: logo,
13
- title: "Forgot Password",
14
- subtitle: "Enter your email and we'll send you a reset code",
15
+ title: t.title,
16
+ subtitle: t.subtitle,
15
17
  poweredBy: poweredBy,
16
18
  cardClassName: cardClassName,
17
19
  backgroundClass: backgroundClass,
@@ -23,7 +25,7 @@ export default function ForgotPasswordPageHero({ loginUrl = '/login', logo, powe
23
25
  icon: "lucide:arrow-left",
24
26
  width: 16
25
27
  }),
26
- "Back to Login"
28
+ t.backToLogin
27
29
  ]
28
30
  }),
29
31
  children: /*#__PURE__*/ _jsxs("form", {
@@ -49,7 +51,7 @@ export default function ForgotPasswordPageHero({ loginUrl = '/login', logo, powe
49
51
  children: [
50
52
  /*#__PURE__*/ _jsx(Label, {
51
53
  className: "text-gray-600",
52
- children: "Email"
54
+ children: t.emailLabel
53
55
  }),
54
56
  /*#__PURE__*/ _jsx(Input, {
55
57
  variant: "secondary"
@@ -68,7 +70,7 @@ export default function ForgotPasswordPageHero({ loginUrl = '/login', logo, powe
68
70
  color: "current",
69
71
  size: "sm"
70
72
  }),
71
- "Send Reset Code"
73
+ t.sendResetCode
72
74
  ]
73
75
  })
74
76
  })
@@ -2,4 +2,4 @@ import type { AuthLayoutConfig } from '../AuthLayout';
2
2
  export interface ForgotPasswordPageProps extends AuthLayoutConfig {
3
3
  loginUrl?: string;
4
4
  }
5
- export default function ForgotPasswordPage({ loginUrl, logo, poweredBy, cardClassName, backgroundClass, }: ForgotPasswordPageProps): import("react/jsx-runtime").JSX.Element;
5
+ export default function ForgotPasswordPage({ loginUrl, logo, poweredBy, cardClassName, backgroundClass, locale, messages, }: ForgotPasswordPageProps): import("react/jsx-runtime").JSX.Element;
@@ -4,19 +4,24 @@ import React from 'react';
4
4
  import { Button, Input } from '../ui/index.js';
5
5
  import { useForgotPasswordFlow } from '../../auth/application/hooks/useForgotPasswordFlow.js';
6
6
  import { AuthLayout } from '../AuthLayout.js';
7
- export default function ForgotPasswordPage({ loginUrl = '/login', logo, poweredBy, cardClassName, backgroundClass }) {
7
+ import { getUiTranslations } from '../ui/translations.js';
8
+ export default function ForgotPasswordPage({ loginUrl = '/login', logo, poweredBy, cardClassName, backgroundClass, locale, messages }) {
8
9
  const { email, error, isLoading, setEmail, handleSubmit } = useForgotPasswordFlow();
10
+ const t = getUiTranslations(locale, messages).forgotPassword;
9
11
  return /*#__PURE__*/ _jsx(AuthLayout, {
10
12
  logo: logo,
11
- title: "Forgot Password",
12
- subtitle: "Enter your email and we'll send you a reset code",
13
+ title: t.title,
14
+ subtitle: t.subtitle,
13
15
  poweredBy: poweredBy,
14
16
  cardClassName: cardClassName,
15
17
  backgroundClass: backgroundClass,
16
- footer: /*#__PURE__*/ _jsx("a", {
18
+ footer: /*#__PURE__*/ _jsxs("a", {
17
19
  href: loginUrl,
18
20
  className: "text-sm text-gray-600 hover:text-gray-900 inline-flex items-center gap-1",
19
- children: "← Back to Login"
21
+ children: [
22
+ "← ",
23
+ t.backToLogin
24
+ ]
20
25
  }),
21
26
  children: /*#__PURE__*/ _jsxs("form", {
22
27
  onSubmit: handleSubmit,
@@ -28,7 +33,7 @@ export default function ForgotPasswordPage({ loginUrl = '/login', logo, poweredB
28
33
  }),
29
34
  /*#__PURE__*/ _jsx(Input, {
30
35
  type: "email",
31
- label: "Email",
36
+ label: t.emailLabel,
32
37
  value: email,
33
38
  onChange: (e)=>setEmail(e.target.value),
34
39
  isRequired: true
@@ -37,7 +42,7 @@ export default function ForgotPasswordPage({ loginUrl = '/login', logo, poweredB
37
42
  type: "submit",
38
43
  variant: "primary",
39
44
  isLoading: isLoading,
40
- children: "Send Reset Code"
45
+ children: t.sendResetCode
41
46
  })
42
47
  ]
43
48
  })
@@ -7,15 +7,17 @@ import { motion, AnimatePresence } from 'framer-motion';
7
7
  import { Icon } from '@iconify/react';
8
8
  import { useLoginFlow } from '../../auth/application/hooks/useLoginFlow.js';
9
9
  import { AuthLayout } from '../AuthLayout.js';
10
- function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth = true, signupUrl = '/signup', logo, poweredBy, cardClassName, backgroundClass }) {
10
+ import { getUiTranslations } from '../ui/translations.js';
11
+ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth = true, signupUrl = '/signup', logo, poweredBy, cardClassName, backgroundClass, locale, messages }) {
11
12
  const searchParams = useSearchParams();
12
13
  const resolvedRedirect = searchParams.get('redirect') || redirectTo;
14
+ const t = getUiTranslations(locale, messages).login;
13
15
  const { step, email, password, error, isLoading, isSendingOtp, showPassword, setEmail, setPassword, setShowPassword, handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail, handleGoogleLogin } = useLoginFlow({
14
16
  redirectTo: resolvedRedirect,
15
17
  onPasswordLogin
16
18
  });
17
- const stepTitle = step === 'otp-prompt' ? 'Verify Identity' : step === 'email' ? 'Welcome Back' : 'Enter Password';
18
- const stepSubtitle = step === 'otp-prompt' ? "We need to verify it's you." : step === 'email' ? 'Sign in with your email to continue.' : 'Enter your password to sign in.';
19
+ const stepTitle = step === 'otp-prompt' ? t.otpPromptTitle : step === 'email' ? t.title : t.passwordStepTitle;
20
+ const stepSubtitle = step === 'otp-prompt' ? t.otpPromptSubtitle : step === 'email' ? t.subtitle : t.passwordStepSubtitle;
19
21
  return /*#__PURE__*/ _jsx(AuthLayout, {
20
22
  logo: logo,
21
23
  title: stepTitle,
@@ -26,12 +28,12 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
26
28
  footer: signupUrl ? /*#__PURE__*/ _jsxs("p", {
27
29
  className: "text-center text-gray-600 text-sm",
28
30
  children: [
29
- "Don't have an account?",
31
+ t.noAccount,
30
32
  ' ',
31
33
  /*#__PURE__*/ _jsx("a", {
32
34
  href: signupUrl,
33
35
  className: "text-gray-900 font-medium hover:underline",
34
- children: "Sign up"
36
+ children: t.signUpLink
35
37
  })
36
38
  ]
37
39
  }) : undefined,
@@ -67,7 +69,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
67
69
  icon: "flat-color-icons:google",
68
70
  width: 20
69
71
  }),
70
- "Continue with Google"
72
+ t.continueWithGoogle
71
73
  ]
72
74
  }),
73
75
  /*#__PURE__*/ _jsxs("div", {
@@ -78,7 +80,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
78
80
  }),
79
81
  /*#__PURE__*/ _jsx("span", {
80
82
  className: "text-gray-500 text-sm",
81
- children: "or"
83
+ children: t.or
82
84
  }),
83
85
  /*#__PURE__*/ _jsx(Separator, {
84
86
  className: "flex-1"
@@ -110,7 +112,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
110
112
  children: [
111
113
  /*#__PURE__*/ _jsx(Label, {
112
114
  className: "text-gray-600",
113
- children: "Email"
115
+ children: t.emailLabel
114
116
  }),
115
117
  /*#__PURE__*/ _jsx(Input, {
116
118
  variant: "secondary"
@@ -128,7 +130,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
128
130
  color: "current",
129
131
  size: "sm"
130
132
  }),
131
- "Continue"
133
+ t.continue
132
134
  ]
133
135
  })
134
136
  })
@@ -164,7 +166,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
164
166
  type: "button",
165
167
  onClick: handleEditEmail,
166
168
  className: "text-gray-600 text-sm font-medium hover:text-gray-900",
167
- children: "Edit"
169
+ children: t.edit
168
170
  })
169
171
  ]
170
172
  }),
@@ -192,7 +194,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
192
194
  children: [
193
195
  /*#__PURE__*/ _jsx(Label, {
194
196
  className: "text-gray-600",
195
- children: "Password"
197
+ children: t.passwordLabel
196
198
  }),
197
199
  /*#__PURE__*/ _jsxs("div", {
198
200
  className: "relative",
@@ -219,7 +221,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
219
221
  children: /*#__PURE__*/ _jsx("a", {
220
222
  href: "/forgot-password",
221
223
  className: "text-sm text-gray-700 hover:underline",
222
- children: "Forgot password?"
224
+ children: t.forgotPassword
223
225
  })
224
226
  }),
225
227
  /*#__PURE__*/ _jsx(Button, {
@@ -234,7 +236,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
234
236
  color: "current",
235
237
  size: "sm"
236
238
  }),
237
- "Continue"
239
+ t.continue
238
240
  ]
239
241
  })
240
242
  })
@@ -270,7 +272,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
270
272
  type: "button",
271
273
  onClick: handleEditEmail,
272
274
  className: "text-gray-600 text-sm font-medium hover:text-gray-900",
273
- children: "Edit"
275
+ children: t.edit
274
276
  })
275
277
  ]
276
278
  }),
@@ -288,7 +290,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
288
290
  }),
289
291
  /*#__PURE__*/ _jsx("p", {
290
292
  className: "text-sm text-blue-700",
291
- children: "We'll send a verification code to this email."
293
+ children: t.verificationNotice
292
294
  })
293
295
  ]
294
296
  }),
@@ -304,7 +306,7 @@ function LoginHeroContent({ onPasswordLogin, redirectTo = '/', showGoogleOAuth =
304
306
  color: "current",
305
307
  size: "sm"
306
308
  }),
307
- isPending ? 'Sending...' : 'Send Code'
309
+ isPending ? t.sendingCode : t.sendCode
308
310
  ]
309
311
  })
310
312
  })