@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.
- package/README.md +180 -4
- package/dist/components/AuthLayout.d.ts +9 -0
- package/dist/components/AuthPages.d.ts +15 -1
- package/dist/components/AuthPages.js +7 -1
- package/dist/components/AuthPagesServer.d.ts +6 -1
- package/dist/components/AuthPagesServer.js +11 -2
- package/dist/components/pages/ForgotPasswordPageHero.d.ts +1 -1
- package/dist/components/pages/ForgotPasswordPageHero.js +8 -6
- package/dist/components/pages/ForgotPasswordPageTailwind.d.ts +1 -1
- package/dist/components/pages/ForgotPasswordPageTailwind.js +12 -7
- package/dist/components/pages/LoginPageHero.js +18 -16
- package/dist/components/pages/LoginPageTailwind.js +18 -16
- package/dist/components/pages/SetPasswordPageHero.d.ts +1 -1
- package/dist/components/pages/SetPasswordPageHero.js +8 -6
- package/dist/components/pages/SetPasswordPageTailwind.d.ts +1 -1
- package/dist/components/pages/SetPasswordPageTailwind.js +9 -7
- package/dist/components/pages/SignupPageHero.d.ts +1 -1
- package/dist/components/pages/SignupPageHero.js +20 -14
- package/dist/components/pages/SignupPageTailwind.d.ts +1 -1
- package/dist/components/pages/SignupPageTailwind.js +17 -14
- package/dist/components/pages/VerifyOtpPageHero.js +9 -7
- package/dist/components/pages/VerifyOtpPageTailwind.js +13 -8
- package/dist/components/ui/locale.d.ts +29 -0
- package/dist/components/ui/locale.js +53 -0
- package/dist/components/ui/translations.d.ts +91 -0
- package/dist/components/ui/translations.js +179 -0
- package/dist/exports/client.d.ts +3 -0
- package/dist/exports/client.js +3 -0
- 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` |
|
|
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
|
-
|
|
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:
|
|
14
|
-
subtitle:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
12
|
-
subtitle:
|
|
13
|
+
title: t.title,
|
|
14
|
+
subtitle: t.subtitle,
|
|
13
15
|
poweredBy: poweredBy,
|
|
14
16
|
cardClassName: cardClassName,
|
|
15
17
|
backgroundClass: backgroundClass,
|
|
16
|
-
footer: /*#__PURE__*/
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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' ?
|
|
18
|
-
const stepSubtitle = step === 'otp-prompt' ?
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 ?
|
|
309
|
+
isPending ? t.sendingCode : t.sendCode
|
|
308
310
|
]
|
|
309
311
|
})
|
|
310
312
|
})
|