@main12/auth-login 0.1.3 → 0.1.5
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 +158 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ pnpm add @heroui/react framer-motion @iconify/react
|
|
|
51
51
|
|
|
52
52
|
## Quick Start
|
|
53
53
|
|
|
54
|
-
### 1. Add the plugin + Users collection
|
|
54
|
+
### 1. Add the plugin + Users collection
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
import { authLoginPlugin } from '@main12/auth-login'
|
|
@@ -63,7 +63,6 @@ export default buildConfig({
|
|
|
63
63
|
auth: { tokenExpiration: 7200, verify: false, maxLoginAttempts: 5 },
|
|
64
64
|
fields: [
|
|
65
65
|
{ name: 'name', type: 'text' },
|
|
66
|
-
// OTP fields required by the plugin:
|
|
67
66
|
{ name: 'otpHash', type: 'text', admin: { hidden: true } },
|
|
68
67
|
{ name: 'otpAttempts', type: 'number', admin: { hidden: true } },
|
|
69
68
|
{ name: 'otpExpiresAt', type: 'text', admin: { hidden: true } },
|
|
@@ -74,40 +73,36 @@ export default buildConfig({
|
|
|
74
73
|
authLoginPlugin({
|
|
75
74
|
projectName: 'My App',
|
|
76
75
|
domain: 'https://myapp.com',
|
|
77
|
-
logo: '/logo.png',
|
|
78
|
-
style: 'hero-ui',
|
|
76
|
+
logo: '/logo.png',
|
|
77
|
+
style: 'hero-ui',
|
|
79
78
|
}),
|
|
80
79
|
],
|
|
81
80
|
})
|
|
82
81
|
```
|
|
83
82
|
|
|
84
|
-
### 2.
|
|
85
|
-
|
|
86
|
-
Create one-line route files under `src/app/(frontend)/(auth)/`:
|
|
83
|
+
### 2. Create one-line route files
|
|
87
84
|
|
|
88
85
|
```tsx
|
|
89
|
-
// login/page.tsx
|
|
86
|
+
// src/app/(frontend)/(auth)/login/page.tsx
|
|
90
87
|
export { LoginPage as default } from '@main12/auth-login/client'
|
|
91
88
|
|
|
92
|
-
// signup/page.tsx
|
|
89
|
+
// src/app/(frontend)/(auth)/signup/page.tsx
|
|
93
90
|
export { SignupPage as default } from '@main12/auth-login/client'
|
|
94
91
|
|
|
95
|
-
// forgot-password/page.tsx
|
|
92
|
+
// src/app/(frontend)/(auth)/forgot-password/page.tsx
|
|
96
93
|
export { ForgotPasswordPage as default } from '@main12/auth-login/client'
|
|
97
94
|
|
|
98
|
-
// verify-otp/page.tsx
|
|
95
|
+
// src/app/(frontend)/(auth)/verify-otp/page.tsx
|
|
99
96
|
export { VerifyOtpPage as default } from '@main12/auth-login/client'
|
|
100
97
|
|
|
101
|
-
// set-password/page.tsx
|
|
98
|
+
// src/app/(frontend)/(auth)/set-password/page.tsx
|
|
102
99
|
export { SetPasswordPage as default } from '@main12/auth-login/client'
|
|
103
100
|
```
|
|
104
101
|
|
|
105
|
-
The logo is already handled — no need to pass it. The plugin reads it from the global config.
|
|
106
|
-
|
|
107
102
|
### 3. Wire up the login action
|
|
108
103
|
|
|
109
104
|
```tsx
|
|
110
|
-
// login/page.tsx
|
|
105
|
+
// login/page.tsx
|
|
111
106
|
'use client'
|
|
112
107
|
import { LoginPage } from '@main12/auth-login/client'
|
|
113
108
|
import { useAuth } from '@/providers/Auth'
|
|
@@ -118,93 +113,121 @@ export default function Page() {
|
|
|
118
113
|
}
|
|
119
114
|
```
|
|
120
115
|
|
|
121
|
-
### 4.
|
|
116
|
+
### 4. Visit `/login` — done.
|
|
122
117
|
|
|
123
118
|
---
|
|
124
119
|
|
|
125
|
-
## Configuration
|
|
120
|
+
## Configuration
|
|
126
121
|
|
|
127
122
|
```ts
|
|
128
123
|
authLoginPlugin({
|
|
129
|
-
//
|
|
130
|
-
projectName: 'My App', // Used in email subjects and footers
|
|
124
|
+
projectName: 'My App', // Email subjects + footers
|
|
131
125
|
contactEmail: 'hi@myapp.com', // Email footer contact
|
|
132
126
|
domain: 'https://myapp.com', // Links in emails
|
|
133
|
-
logo: '/logo.png', //
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
style: 'tailwind', // 'tailwind' (default) | 'hero-ui'
|
|
137
|
-
|
|
138
|
-
// === Enable/Disable ===
|
|
139
|
-
enabled: true, // Set false to disable the plugin
|
|
127
|
+
logo: '/logo.png', // All auth pages + email headers
|
|
128
|
+
style: 'tailwind', // 'tailwind' | 'hero-ui'
|
|
129
|
+
enabled: true,
|
|
140
130
|
})
|
|
141
131
|
```
|
|
142
132
|
|
|
143
133
|
### Per-page overrides
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```ts
|
|
135
|
+
```tsx
|
|
148
136
|
<LoginPage
|
|
149
137
|
logo={<AppLogo width={180} />} // Override global logo (optional)
|
|
150
|
-
onPasswordLogin={login} // Required
|
|
151
|
-
redirectTo="/dashboard"
|
|
152
|
-
showGoogleOAuth={true}
|
|
153
|
-
signupUrl="/signup"
|
|
154
|
-
poweredBy={{
|
|
155
|
-
enabled: true,
|
|
156
|
-
logoUrl: '/custom.png', // Override default Main12 logo
|
|
157
|
-
linkUrl: 'https://your-site.com',
|
|
158
|
-
}}
|
|
138
|
+
onPasswordLogin={login} // Required
|
|
139
|
+
redirectTo="/dashboard"
|
|
140
|
+
showGoogleOAuth={true}
|
|
141
|
+
signupUrl="/signup"
|
|
142
|
+
poweredBy={{ enabled: true, logoUrl: '/custom.png', linkUrl: 'https://...' }}
|
|
159
143
|
/>
|
|
160
144
|
```
|
|
161
145
|
|
|
162
146
|
---
|
|
163
147
|
|
|
164
|
-
## Building Custom
|
|
148
|
+
## Building Custom Pages
|
|
149
|
+
|
|
150
|
+
Use the plugin's hooks to build your own UI with any component library (HeroUI, shadcn, plain Tailwind).
|
|
151
|
+
|
|
152
|
+
### Hook Quick-Reference
|
|
153
|
+
|
|
154
|
+
| Hook | Returns | Key inputs |
|
|
155
|
+
|------|---------|------------|
|
|
156
|
+
| `useLoginFlow({ redirectTo, onPasswordLogin })` | `step, email, password, error, isLoading, handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail` | `redirectTo: string`, `onPasswordLogin: (creds) => Promise<void>` |
|
|
157
|
+
| `useForgotPasswordFlow()` | `email, error, isLoading, setEmail, handleSubmit` | none |
|
|
158
|
+
| `useVerifyOtpFlow({ email, purpose, redirectTo })` | `otp, error, isLoading, isResending, resendCooldown, setOtp, handleSubmit, handleResendCode` | `email: string`, `purpose: 'login'\|'signup'\|'password-reset'` |
|
|
159
|
+
| `useSetPasswordFlow({ redirectTo })` | `password, confirmPassword, error, isLoading, strength, setPassword, setConfirmPassword, handleSubmit` | `redirectTo: string` |
|
|
165
160
|
|
|
166
|
-
|
|
161
|
+
> **Signup note:** No hook needed — call `signup(name, email)` from `@main12/auth-login/client`, then redirect to `/verify-otp?email=...&purpose=signup`.
|
|
167
162
|
|
|
168
|
-
###
|
|
163
|
+
### Full Example: Custom Login Page
|
|
169
164
|
|
|
170
165
|
```tsx
|
|
171
166
|
'use client'
|
|
172
167
|
import { useLoginFlow } from '@main12/auth-login/client'
|
|
173
|
-
import { Button, Input } from '@heroui/react' // or shadcn, or plain HTML
|
|
174
168
|
import { useAuth } from '@/providers/Auth'
|
|
175
169
|
|
|
176
|
-
export default function
|
|
170
|
+
export default function CustomLogin() {
|
|
177
171
|
const { login } = useAuth()
|
|
178
172
|
const {
|
|
179
173
|
step, email, password, error, isLoading, showPassword,
|
|
180
174
|
setEmail, setPassword, setShowPassword,
|
|
181
175
|
handleEmailSubmit, handlePasswordSubmit, handleSendOtp, handleEditEmail,
|
|
182
|
-
} = useLoginFlow({
|
|
183
|
-
redirectTo: '/dashboard',
|
|
184
|
-
onPasswordLogin: login,
|
|
185
|
-
})
|
|
176
|
+
} = useLoginFlow({ redirectTo: '/dashboard', onPasswordLogin: login })
|
|
186
177
|
|
|
187
178
|
return (
|
|
188
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
179
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
189
180
|
<div className="w-full max-w-md p-8 bg-white rounded-2xl shadow-xl">
|
|
181
|
+
<img src="/logo.png" className="mx-auto mb-6" width={180} alt="" />
|
|
182
|
+
|
|
183
|
+
{/* Step 1: Email */}
|
|
190
184
|
{step === 'email' && (
|
|
191
|
-
<form onSubmit={handleEmailSubmit}>
|
|
192
|
-
<
|
|
193
|
-
<
|
|
185
|
+
<form onSubmit={handleEmailSubmit} className="space-y-4">
|
|
186
|
+
<h1 className="text-xl font-semibold text-center">Welcome Back</h1>
|
|
187
|
+
{error && <p className="text-red-600 text-sm">{error}</p>}
|
|
188
|
+
<input type="email" value={email} onChange={e => setEmail(e.target.value)}
|
|
189
|
+
placeholder="Email" required className="w-full h-12 px-4 border rounded-xl" />
|
|
190
|
+
<button type="submit" disabled={isLoading}
|
|
191
|
+
className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
|
|
192
|
+
{isLoading ? 'Loading...' : 'Continue'}
|
|
193
|
+
</button>
|
|
194
194
|
</form>
|
|
195
195
|
)}
|
|
196
|
+
|
|
197
|
+
{/* Step 2a: Password */}
|
|
196
198
|
{step === 'password' && (
|
|
197
|
-
<form onSubmit={handlePasswordSubmit}>
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
<form onSubmit={handlePasswordSubmit} className="space-y-4">
|
|
200
|
+
<div className="flex justify-between border rounded-xl px-4 py-3">
|
|
201
|
+
<span>{email}</span>
|
|
202
|
+
<button type="button" onClick={handleEditEmail} className="text-sm">Edit</button>
|
|
203
|
+
</div>
|
|
204
|
+
{error && <p className="text-red-600 text-sm">{error}</p>}
|
|
205
|
+
<input type={showPassword ? 'text' : 'password'} value={password}
|
|
206
|
+
onChange={e => setPassword(e.target.value)} placeholder="Password" required
|
|
207
|
+
className="w-full h-12 px-4 border rounded-xl" autoFocus />
|
|
208
|
+
<button type="submit" disabled={isLoading}
|
|
209
|
+
className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
|
|
210
|
+
{isLoading ? 'Loading...' : 'Sign In'}
|
|
211
|
+
</button>
|
|
201
212
|
</form>
|
|
202
213
|
)}
|
|
214
|
+
|
|
215
|
+
{/* Step 2b: OTP Prompt (migrated users without password) */}
|
|
203
216
|
{step === 'otp-prompt' && (
|
|
204
|
-
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
<div className="space-y-4">
|
|
218
|
+
<div className="flex justify-between border rounded-xl px-4 py-3">
|
|
219
|
+
<span>{email}</span>
|
|
220
|
+
<button type="button" onClick={handleEditEmail}>Edit</button>
|
|
221
|
+
</div>
|
|
222
|
+
{error && <p className="text-red-600 text-sm">{error}</p>}
|
|
223
|
+
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-sm text-blue-700">
|
|
224
|
+
We'll send a verification code to this email.
|
|
225
|
+
</div>
|
|
226
|
+
<button onClick={handleSendOtp} disabled={isLoading}
|
|
227
|
+
className="w-full h-12 bg-[#D5E855] rounded-full font-semibold">
|
|
228
|
+
{isLoading ? 'Sending...' : 'Send Code'}
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
208
231
|
)}
|
|
209
232
|
</div>
|
|
210
233
|
</div>
|
|
@@ -212,60 +235,50 @@ export default function MyCustomLogin() {
|
|
|
212
235
|
}
|
|
213
236
|
```
|
|
214
237
|
|
|
215
|
-
### Custom
|
|
238
|
+
### Custom Signup
|
|
216
239
|
|
|
217
|
-
|
|
240
|
+
Same pattern — call `onSignup(name, email)` from your form, redirect to `/verify-otp?email=...&purpose=signup` on success.
|
|
218
241
|
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
### Custom Verify OTP
|
|
243
|
+
|
|
244
|
+
Use `useVerifyOtpFlow({ email, purpose, redirectTo })`. It manages the 6-digit input, verification, resend cooldown (60s), and auto-redirect. All you need is an `<input>` or `<InputOtp>` for the code and a verify button.
|
|
245
|
+
|
|
246
|
+
### Custom Forgot Password
|
|
221
247
|
|
|
222
|
-
|
|
223
|
-
const myTranslations = getEmailTranslations('en')
|
|
224
|
-
myTranslations.welcome.subject = 'Welcome to My SaaS! 🚀'
|
|
225
|
-
myTranslations.otp.purposeLogin = 'Use this code to access your dashboard:'
|
|
248
|
+
Use `useForgotPasswordFlow()`. Collect email, call `handleSubmit(e)`. It checks user exists, sends OTP, and redirects to `/verify-otp?email=...&purpose=password-reset`.
|
|
226
249
|
|
|
227
|
-
|
|
228
|
-
import { generateWelcomeEmail, generateOtpEmail } from '@main12/auth-login/rsc'
|
|
250
|
+
### Custom Set Password
|
|
229
251
|
|
|
230
|
-
|
|
252
|
+
Use `useSetPasswordFlow({ redirectTo })`. Two password fields (new + confirm). The hook validates strength (≥8 chars, 3/4 criteria) and matches. Returns `strength.score` (0-5) for a visual indicator.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Custom Email Templates
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { generateWelcomeEmail, getEmailTranslations } from '@main12/auth-login/rsc'
|
|
260
|
+
|
|
261
|
+
// Override translations
|
|
262
|
+
const t = getEmailTranslations('en')
|
|
263
|
+
t.welcome.subject = 'Welcome to My SaaS! 🚀'
|
|
264
|
+
|
|
265
|
+
// Or wrap template generators
|
|
266
|
+
function myWelcome(params) {
|
|
231
267
|
const base = generateWelcomeEmail(params)
|
|
232
|
-
return {
|
|
233
|
-
...base,
|
|
234
|
-
html: base.html.replace('Get Started', 'Launch Dashboard'),
|
|
235
|
-
}
|
|
268
|
+
return { ...base, html: base.html.replace('Get Started', 'Launch Now') }
|
|
236
269
|
}
|
|
237
270
|
|
|
238
|
-
// Use in
|
|
271
|
+
// Use in hooks/endpoints
|
|
239
272
|
await payload.sendEmail({
|
|
240
273
|
to: user.email,
|
|
241
|
-
|
|
242
|
-
html: myWelcomeEmail({ userName: user.name }).html,
|
|
274
|
+
...myWelcome({ userName: user.name, userEmail: user.email }),
|
|
243
275
|
})
|
|
244
276
|
```
|
|
245
277
|
|
|
246
278
|
---
|
|
247
279
|
|
|
248
|
-
## Exported Hooks & Services
|
|
249
|
-
|
|
250
|
-
| Hook / Service | Type | Purpose |
|
|
251
|
-
|---------------|------|---------|
|
|
252
|
-
| `useLoginFlow` | Hook | Multi-step login state machine (email → password/OTP) |
|
|
253
|
-
| `useVerifyOtpFlow` | Hook | OTP input, verify, resend with cooldown |
|
|
254
|
-
| `useForgotPasswordFlow` | Hook | Email → check → send OTP |
|
|
255
|
-
| `useSetPasswordFlow` | Hook | Set password with strength indicator |
|
|
256
|
-
| `checkEmail(email)` | Service | Check if user exists and has password |
|
|
257
|
-
| `sendOtp(email, purpose)` | Service | Send OTP to email |
|
|
258
|
-
| `verifyOtp(email, otp)` | Service | Verify OTP code |
|
|
259
|
-
| `setUserPassword(pw, confirm)` | Service | Set/update password |
|
|
260
|
-
| `signup(name, email)` | Service | Create new user |
|
|
261
|
-
| `initiateGoogleLogin(redirect)` | Service | Redirect to Google OAuth |
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
280
|
## API Endpoints
|
|
266
281
|
|
|
267
|
-
Registered automatically by the plugin:
|
|
268
|
-
|
|
269
282
|
| Method | Path | Description |
|
|
270
283
|
|--------|------|-------------|
|
|
271
284
|
| POST | `/api/auth/check-email` | Check if email is registered |
|
|
@@ -278,20 +291,62 @@ Registered automatically by the plugin:
|
|
|
278
291
|
|
|
279
292
|
## ShadCN Compatibility
|
|
280
293
|
|
|
281
|
-
The `tailwind` style
|
|
294
|
+
The `tailwind` style works in ShadCN projects out of the box. For ShadCN components, build a [custom page](#building-custom-pages) — import the plugin's hooks and use your `@/components/ui/button`, `@/components/ui/input`, etc.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 🤖 AI Agent Prompts
|
|
299
|
+
|
|
300
|
+
Copy these prompts into Claude, Cursor, Copilot, or any AI agent.
|
|
301
|
+
|
|
302
|
+
### Prompt: Set up the auth plugin in a new Payload project
|
|
282
303
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
```
|
|
305
|
+
Add @main12/auth-login to this Payload project:
|
|
306
|
+
|
|
307
|
+
1. Install: pnpm add @main12/auth-login
|
|
308
|
+
2. In payload.config.ts, add:
|
|
309
|
+
- Users collection with auth enabled + otpHash, otpAttempts, otpExpiresAt fields
|
|
310
|
+
- Plugin: authLoginPlugin({ projectName: "<PROJECT>", domain: "<URL>", logo: "/logo.png", style: "hero-ui" })
|
|
311
|
+
3. Create route files under src/app/(frontend)/(auth)/:
|
|
312
|
+
- login/page.tsx → export { LoginPage as default } from '@main12/auth-login/client'
|
|
313
|
+
- signup/page.tsx → export { SignupPage as default } from '@main12/auth-login/client'
|
|
314
|
+
- forgot-password/page.tsx → export { ForgotPasswordPage as default } from '@main12/auth-login/client'
|
|
315
|
+
- verify-otp/page.tsx → export { VerifyOtpPage as default } from '@main12/auth-login/client'
|
|
316
|
+
- set-password/page.tsx → export { SetPasswordPage as default } from '@main12/auth-login/client'
|
|
317
|
+
4. In login/page.tsx, wrap LoginPage with useAuth() to pass onPasswordLogin={login}.
|
|
318
|
+
5. Verify: visit /login
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Prompt: Build a custom login page with HeroUI components
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
Build a custom login page using @main12/auth-login hooks and @heroui/react:
|
|
325
|
+
|
|
326
|
+
- Import useLoginFlow from '@main12/auth-login/client'
|
|
327
|
+
- Use useAuth() from Payload's Auth provider for the login function
|
|
328
|
+
- Render 3 steps: email input → password input / OTP prompt
|
|
329
|
+
- Use HeroUI <Button>, <Input> with variant="bordered", rounded-full
|
|
330
|
+
- Add "Continue with Google" button using initiateGoogleLogin from the plugin
|
|
331
|
+
- Add "Powered by Main12" footer from the plugin's PoweredBy component
|
|
332
|
+
```
|
|
286
333
|
|
|
287
|
-
|
|
334
|
+
### Prompt: Customize email templates for my project
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
Customize the email templates from @main12/auth-login for my project "<PROJECT_NAME>":
|
|
338
|
+
|
|
339
|
+
- Import generateWelcomeEmail, generateOtpEmail, generatePasswordResetEmail, generatePasswordChangedEmail from '@main12/auth-login/rsc'
|
|
340
|
+
- Override each to use my brand colors (primary: <COLOR>, accent: <COLOR>)
|
|
341
|
+
- Change the welcome email CTA text to "<CUSTOM_TEXT>"
|
|
342
|
+
- Change the OTP email purpose text to "<CUSTOM_TEXT>"
|
|
343
|
+
- Add my project's social media links to the footer
|
|
344
|
+
```
|
|
288
345
|
|
|
289
346
|
---
|
|
290
347
|
|
|
291
348
|
## Dev Testing
|
|
292
349
|
|
|
293
|
-
This repo ships with a dev harness. To test locally:
|
|
294
|
-
|
|
295
350
|
```bash
|
|
296
351
|
git clone https://github.com/MAIN-12/auth-login-plugin.git
|
|
297
352
|
cd auth-login-plugin
|
package/package.json
CHANGED