@main12/auth-login 0.4.0 → 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 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
@@ -7,6 +7,9 @@ export { useForgotPasswordFlow } from '../auth/application/hooks/useForgotPasswo
7
7
  export { useSetPasswordFlow } from '../auth/application/hooks/useSetPasswordFlow';
8
8
  export { checkEmail, sendOtp, verifyOtp, setUserPassword, signup, initiateGoogleLogin, } from '../auth/application/services/authService';
9
9
  export { evaluatePasswordStrength, isPasswordValid, MIN_PASSWORD_LENGTH } from '../auth/domain/passwordRules';
10
+ export { getUiTranslations, uiTranslations } from '../components/ui/translations';
11
+ export type { UiTranslations, DeepPartial } from '../components/ui/translations';
12
+ export { detectClientLocale } from '../components/ui/locale';
10
13
  export { default as LoginPage } from '../components/pages/LoginPage';
11
14
  export { default as SignupPage } from '../components/pages/SignupPage';
12
15
  export { default as ForgotPasswordPage } from '../components/pages/ForgotPasswordPage';
@@ -14,6 +14,9 @@ export { useSetPasswordFlow } from '../auth/application/hooks/useSetPasswordFlow
14
14
  export { checkEmail, sendOtp, verifyOtp, setUserPassword, signup, initiateGoogleLogin } from '../auth/application/services/authService.js';
15
15
  // Domain utilities
16
16
  export { evaluatePasswordStrength, isPasswordValid, MIN_PASSWORD_LENGTH } from '../auth/domain/passwordRules.js';
17
+ // UI translations / locale utilities
18
+ export { getUiTranslations, uiTranslations } from '../components/ui/translations.js';
19
+ export { detectClientLocale } from '../components/ui/locale.js';
17
20
  // Page components
18
21
  export { default as LoginPage } from '../components/pages/LoginPage.js';
19
22
  export { default as SignupPage } from '../components/pages/SignupPage.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@main12/auth-login",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Reusable Payload CMS auth plugin — login, signup, OTP, forgot password, branded emails, Powered by Main12",
5
5
  "license": "MIT",
6
6
  "type": "module",