create-aron-app 0.1.0 → 0.1.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/package.json +5 -2
- package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
- package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
- package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
- package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
- package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
- package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
- package/templates/_base/.cursor/commands/builder.md +0 -0
- package/templates/_base/.cursor/commands/pr.md +7 -0
- package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
- package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
- package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
- package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
- package/templates/_base/.env.convex.example +3 -0
- package/templates/_base/.github/workflows/ci.yml +29 -0
- package/templates/_base/.nvmrc +1 -0
- package/templates/_base/.vscode/settings.json +9 -0
- package/templates/_base/apps/api/auth.config.ts +18 -0
- package/templates/_base/apps/api/functions.ts +99 -0
- package/templates/_base/apps/api/project.json +22 -0
- package/templates/_base/apps/api/schema.ts +11 -0
- package/templates/_base/apps/api/todos/crud.ts +81 -0
- package/templates/_base/apps/api/todos/schema.ts +11 -0
- package/templates/_base/apps/api/todos/types.ts +22 -0
- package/templates/_base/apps/api/tsconfig.json +23 -0
- package/templates/_base/apps/api/types.ts +16 -0
- package/templates/_base/biome.json +114 -0
- package/templates/_base/convex.json +4 -0
- package/templates/_base/emails/project.json +16 -0
- package/templates/_base/emails/tsconfig.json +5 -0
- package/templates/_base/emails/welcome_email.tsx +53 -0
- package/templates/_base/nx.json +29 -0
- package/templates/_base/package.json +73 -0
- package/templates/_base/scripts/sync_convex_env.ts +63 -0
- package/templates/_base/shared/assets/image.d.ts +4 -0
- package/templates/_base/shared/assets/src/styles/global.css +73 -0
- package/templates/_base/shared/assets/tsconfig.json +5 -0
- package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
- package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
- package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
- package/templates/_base/shared/ui/src/base/button.tsx +69 -0
- package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
- package/templates/_base/shared/ui/src/base/card.tsx +79 -0
- package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
- package/templates/_base/shared/ui/src/base/command.tsx +165 -0
- package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
- package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
- package/templates/_base/shared/ui/src/base/form.tsx +161 -0
- package/templates/_base/shared/ui/src/base/input.tsx +129 -0
- package/templates/_base/shared/ui/src/base/label.tsx +19 -0
- package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
- package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
- package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
- package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
- package/templates/_base/shared/ui/src/base/select.tsx +151 -0
- package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
- package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
- package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
- package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
- package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
- package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
- package/templates/_base/shared/ui/src/base/table.tsx +91 -0
- package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
- package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
- package/templates/_base/shared/ui/src/base/utils.ts +17 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
- package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
- package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
- package/templates/_base/shared/ui/tsconfig.json +8 -0
- package/templates/_base/shared/utils/src/convex.ts +3 -0
- package/templates/_base/shared/utils/src/time.ts +12 -0
- package/templates/_base/shared/utils/tsconfig.json +5 -0
- package/templates/_base/skills-lock.json +35 -0
- package/templates/_base/tsconfig.base.json +34 -0
- package/templates/nextjs/.env.example +8 -0
- package/templates/nextjs/index.d.ts +6 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.js +22 -0
- package/templates/nextjs/postcss.config.js +17 -0
- package/templates/nextjs/project.json +22 -0
- package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
- package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
- package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
- package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
- package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
- package/templates/nextjs/src/app/app.css +3 -0
- package/templates/nextjs/src/app/layout.tsx +26 -0
- package/templates/nextjs/src/convex.ts +11 -0
- package/templates/nextjs/src/middleware.ts +18 -0
- package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
- package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
- package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
- package/templates/nextjs/src/utils/font.ts +9 -0
- package/templates/nextjs/tsconfig.json +42 -0
- package/templates/react-router/.env.example +8 -0
- package/templates/react-router/postcss.config.js +15 -0
- package/templates/react-router/project.json +23 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +9 -0
- package/templates/react-router/src/app.css +3 -0
- package/templates/react-router/src/components/error_boundary.tsx +33 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
- package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
- package/templates/react-router/src/root.tsx +37 -0
- package/templates/react-router/src/routes/auth/layout.tsx +13 -0
- package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
- package/templates/react-router/src/routes/index.tsx +9 -0
- package/templates/react-router/src/routes/layout.tsx +26 -0
- package/templates/react-router/src/routes/todos/[id].tsx +22 -0
- package/templates/react-router/src/routes/todos/index.tsx +13 -0
- package/templates/react-router/src/routes.ts +12 -0
- package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
- package/templates/react-router/tsconfig.json +20 -0
- package/templates/react-router/vite.config.ts +40 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Custom Sign-Up Flow (Core 2)
|
|
2
|
+
|
|
3
|
+
> This document covers the **older SDK** (`@clerk/nextjs` v5–v6, `@clerk/clerk-react` v5–v6, `@clerk/clerk-expo` v1–v2). For the current SDK, see `core-3/custom-sign-up.md`.
|
|
4
|
+
|
|
5
|
+
Build a custom sign-up experience using the `useSignUp()` hook.
|
|
6
|
+
|
|
7
|
+
## Hook API
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { useSignUp } from '@clerk/nextjs' // or @clerk/clerk-react, @clerk/clerk-expo
|
|
11
|
+
|
|
12
|
+
const { signUp, isLoaded, setActive } = useSignUp()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Property | Type | Description |
|
|
16
|
+
|----------|------|-------------|
|
|
17
|
+
| `signUp` | `SignUp` | Sign-up object with methods |
|
|
18
|
+
| `isLoaded` | `boolean` | Whether the hook has loaded |
|
|
19
|
+
| `setActive` | `(params) => Promise` | Sets the active session |
|
|
20
|
+
|
|
21
|
+
## Sign-Up Flow
|
|
22
|
+
|
|
23
|
+
### 1. Create Sign-Up
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const result = await signUp.create({
|
|
27
|
+
emailAddress: 'user@example.com',
|
|
28
|
+
password: 'securePassword123',
|
|
29
|
+
firstName: 'Jane', // optional
|
|
30
|
+
lastName: 'Doe', // optional
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Prepare Verification
|
|
35
|
+
|
|
36
|
+
Send a verification code to the user's email or phone:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
await signUp.prepareVerification({
|
|
40
|
+
strategy: 'email_code', // or 'phone_code', 'email_link'
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Attempt Verification
|
|
45
|
+
|
|
46
|
+
Verify the code the user received:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const result = await signUp.attemptVerification({
|
|
50
|
+
strategy: 'email_code',
|
|
51
|
+
code: '123456',
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 4. Finalize
|
|
56
|
+
|
|
57
|
+
Set the active session after successful sign-up:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
await setActive({ session: signUp.createdSessionId })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### SSO (OAuth)
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
await signUp.authenticateWithRedirect({
|
|
67
|
+
strategy: 'oauth_google',
|
|
68
|
+
redirectUrl: '/sso-callback',
|
|
69
|
+
redirectUrlComplete: '/',
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Error Handling
|
|
74
|
+
|
|
75
|
+
Use try/catch with `isClerkAPIResponseError()`:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { isClerkAPIResponseError } from '@clerk/nextjs/errors'
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await signUp.create({ emailAddress, password })
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (isClerkAPIResponseError(err)) {
|
|
84
|
+
err.errors.forEach((e) => {
|
|
85
|
+
console.log(e.code) // e.g. 'form_password_pwned'
|
|
86
|
+
console.log(e.message) // Human-readable message
|
|
87
|
+
console.log(e.longMessage) // Detailed message
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Complete Example: Email/Password with Email Verification
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
'use client'
|
|
97
|
+
import { useState } from 'react'
|
|
98
|
+
import { useSignUp } from '@clerk/nextjs'
|
|
99
|
+
import { isClerkAPIResponseError } from '@clerk/nextjs/errors'
|
|
100
|
+
import { useRouter } from 'next/navigation'
|
|
101
|
+
|
|
102
|
+
export default function SignUpPage() {
|
|
103
|
+
const { signUp, isLoaded, setActive } = useSignUp()
|
|
104
|
+
const router = useRouter()
|
|
105
|
+
|
|
106
|
+
const [email, setEmail] = useState('')
|
|
107
|
+
const [password, setPassword] = useState('')
|
|
108
|
+
const [code, setCode] = useState('')
|
|
109
|
+
const [step, setStep] = useState<'register' | 'verify'>('register')
|
|
110
|
+
const [error, setError] = useState('')
|
|
111
|
+
|
|
112
|
+
if (!isLoaded) return <div>Loading...</div>
|
|
113
|
+
|
|
114
|
+
async function handleRegister(e: React.FormEvent) {
|
|
115
|
+
e.preventDefault()
|
|
116
|
+
setError('')
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await signUp.create({ emailAddress: email, password })
|
|
120
|
+
await signUp.prepareVerification({ strategy: 'email_code' })
|
|
121
|
+
setStep('verify')
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (isClerkAPIResponseError(err)) {
|
|
124
|
+
setError(err.errors[0]?.message || 'Sign up failed')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function handleVerify(e: React.FormEvent) {
|
|
130
|
+
e.preventDefault()
|
|
131
|
+
setError('')
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const result = await signUp.attemptVerification({
|
|
135
|
+
strategy: 'email_code',
|
|
136
|
+
code,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
if (result.status === 'complete') {
|
|
140
|
+
await setActive({ session: result.createdSessionId })
|
|
141
|
+
router.push('/')
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (isClerkAPIResponseError(err)) {
|
|
145
|
+
setError(err.errors[0]?.message || 'Verification failed')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (step === 'verify') {
|
|
151
|
+
return (
|
|
152
|
+
<form onSubmit={handleVerify}>
|
|
153
|
+
<p>Check your email for a verification code.</p>
|
|
154
|
+
<input
|
|
155
|
+
type="text"
|
|
156
|
+
value={code}
|
|
157
|
+
onChange={(e) => setCode(e.target.value)}
|
|
158
|
+
placeholder="Verification code"
|
|
159
|
+
/>
|
|
160
|
+
{error && <p>{error}</p>}
|
|
161
|
+
<button type="submit">Verify Email</button>
|
|
162
|
+
</form>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<form onSubmit={handleRegister}>
|
|
168
|
+
<input
|
|
169
|
+
type="email"
|
|
170
|
+
value={email}
|
|
171
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
172
|
+
placeholder="Email"
|
|
173
|
+
/>
|
|
174
|
+
<input
|
|
175
|
+
type="password"
|
|
176
|
+
value={password}
|
|
177
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
178
|
+
placeholder="Password"
|
|
179
|
+
/>
|
|
180
|
+
{error && <p>{error}</p>}
|
|
181
|
+
<button type="submit">Sign Up</button>
|
|
182
|
+
</form>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Docs
|
|
188
|
+
|
|
189
|
+
- [Custom sign-up flow](https://clerk.com/docs/custom-flows/overview)
|
|
190
|
+
- [useSignUp() reference](https://clerk.com/docs/references/react/use-sign-up)
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Custom Sign-In Flow
|
|
2
|
+
|
|
3
|
+
Build a custom sign-in experience using the `useSignIn()` hook.
|
|
4
|
+
|
|
5
|
+
## Hook API
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useSignIn } from '@clerk/nextjs' // or @clerk/react, @clerk/expo
|
|
9
|
+
|
|
10
|
+
const { signIn, errors, fetchStatus } = useSignIn()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Property | Type | Description |
|
|
14
|
+
|----------|------|-------------|
|
|
15
|
+
| `signIn` | `SignInFuture` | Sign-in object with namespaced methods |
|
|
16
|
+
| `errors` | `Errors<SignInFields>` | Structured error object |
|
|
17
|
+
| `fetchStatus` | `'idle' \| 'fetching'` | Network request status |
|
|
18
|
+
|
|
19
|
+
## Sign-In Methods
|
|
20
|
+
|
|
21
|
+
### Password
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const { error } = await signIn.password({
|
|
25
|
+
identifier: 'user@example.com',
|
|
26
|
+
password: 'securePassword123',
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SSO (OAuth / Enterprise)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const { error } = await signIn.sso({
|
|
34
|
+
strategy: 'oauth_google', // or 'oauth_github', 'enterprise_sso', etc.
|
|
35
|
+
redirectUrl: '/dashboard', // where to go after SSO completes
|
|
36
|
+
redirectCallbackUrl: '/sso-callback', // intermediate callback route
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Passkey
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const { error } = await signIn.passkey({ flow: 'discoverable' })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Web3
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const { error } = await signIn.web3({ strategy: 'web3_solana_signature' })
|
|
50
|
+
// or
|
|
51
|
+
const { error } = await signIn.web3({ strategy: 'web3_base_signature' })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Ticket (Invitation link)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const { error } = await signIn.ticket({ ticket: 'ticket_abc123' })
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Email Code
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Send code (emailAddress is optional if a signIn already exists from a prior method call)
|
|
64
|
+
const { error } = await signIn.emailCode.sendCode({ emailAddress: 'user@example.com' })
|
|
65
|
+
|
|
66
|
+
// Verify code
|
|
67
|
+
const { error } = await signIn.emailCode.verifyCode({ code: '123456' })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Phone Code
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Send code (phoneNumber is optional if a signIn already exists from a prior method call)
|
|
74
|
+
const { error } = await signIn.phoneCode.sendCode({ phoneNumber: '+12015551234' })
|
|
75
|
+
|
|
76
|
+
// Verify code
|
|
77
|
+
const { error } = await signIn.phoneCode.verifyCode({ code: '123456' })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## MFA (Second Factor)
|
|
81
|
+
|
|
82
|
+
A second factor is required when `signIn.status` is one of:
|
|
83
|
+
- `'needs_second_factor'` — user has MFA enabled (TOTP, backup codes, etc.)
|
|
84
|
+
- `'needs_client_trust'` — new device sign-in without MFA; requires email or phone code verification
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// TOTP (Authenticator app)
|
|
88
|
+
const { error } = await signIn.mfa.verifyTOTP({ code: '123456' })
|
|
89
|
+
|
|
90
|
+
// Backup code
|
|
91
|
+
const { error } = await signIn.mfa.verifyBackupCode({ code: 'backup-code-here' })
|
|
92
|
+
|
|
93
|
+
// Email code
|
|
94
|
+
const { error: sendErr } = await signIn.mfa.sendEmailCode()
|
|
95
|
+
const { error: verifyErr } = await signIn.mfa.verifyEmailCode({ code: '123456' })
|
|
96
|
+
|
|
97
|
+
// Phone code
|
|
98
|
+
const { error: sendErr } = await signIn.mfa.sendPhoneCode()
|
|
99
|
+
const { error: verifyErr } = await signIn.mfa.verifyPhoneCode({ code: '123456' })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Password Reset
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// 1. Send reset code
|
|
106
|
+
const { error } = await signIn.resetPasswordEmailCode.sendCode()
|
|
107
|
+
|
|
108
|
+
// 2. Verify the code
|
|
109
|
+
const { error } = await signIn.resetPasswordEmailCode.verifyCode({ code: '123456' })
|
|
110
|
+
|
|
111
|
+
// 3. Submit new password
|
|
112
|
+
const { error } = await signIn.resetPasswordEmailCode.submitPassword({
|
|
113
|
+
password: 'newSecurePassword123',
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Client Trust
|
|
118
|
+
|
|
119
|
+
When a user signs in with a valid password from a new device without MFA enabled, the sign-in status becomes `needs_client_trust`. This requires an additional verification step:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
if (signIn.status === 'needs_client_trust') {
|
|
123
|
+
// Check supportedSecondFactors for available methods (email_code or phone_code)
|
|
124
|
+
const factors = signIn.supportedSecondFactors
|
|
125
|
+
// Use the appropriate mfa method to verify
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Finalizing Sign-In
|
|
130
|
+
|
|
131
|
+
After successful authentication, call `finalize()` to activate the session:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
await signIn.finalize({
|
|
135
|
+
navigate: async ({ session, decorateUrl }) => {
|
|
136
|
+
const destination = session.currentTask
|
|
137
|
+
? `/sign-in/tasks/${session.currentTask.key}`
|
|
138
|
+
: '/'
|
|
139
|
+
const url = decorateUrl(destination)
|
|
140
|
+
// decorateUrl may return an absolute URL for Safari ITP
|
|
141
|
+
if (url.startsWith('http')) {
|
|
142
|
+
window.location.href = url
|
|
143
|
+
} else {
|
|
144
|
+
router.push(url)
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- `decorateUrl(path)` — decorates the URL with session info (required to support Safari's Intelligent Tracking Prevention). May return an absolute URL.
|
|
151
|
+
- `session.currentTask` — check for pending session tasks before redirecting
|
|
152
|
+
|
|
153
|
+
### Reset State
|
|
154
|
+
|
|
155
|
+
Clear local sign-in state and start over:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
signIn.reset()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Error Handling
|
|
162
|
+
|
|
163
|
+
All methods return `Promise<{ error: ClerkError | null }>`. Errors are also available reactively on the hook:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const { signIn, errors } = useSignIn()
|
|
167
|
+
|
|
168
|
+
// Field-level errors
|
|
169
|
+
errors?.fields?.identifier // { code, message, longMessage? }
|
|
170
|
+
errors?.fields?.password // { code, message, longMessage? }
|
|
171
|
+
errors?.fields?.code // { code, message, longMessage? }
|
|
172
|
+
|
|
173
|
+
// Global errors (not tied to a field)
|
|
174
|
+
errors?.global // ClerkGlobalHookError[] | null
|
|
175
|
+
|
|
176
|
+
// Raw error array
|
|
177
|
+
errors?.raw // ClerkError[] | null
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Complete Example: Email/Password with MFA
|
|
181
|
+
|
|
182
|
+
From [the docs](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication). Supports SMS verification codes, authenticator app (TOTP), and backup codes.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
'use client'
|
|
186
|
+
|
|
187
|
+
import { useSignIn } from '@clerk/nextjs'
|
|
188
|
+
import { useRouter } from 'next/navigation'
|
|
189
|
+
|
|
190
|
+
export default function Page() {
|
|
191
|
+
const { signIn, errors, fetchStatus } = useSignIn()
|
|
192
|
+
const router = useRouter()
|
|
193
|
+
|
|
194
|
+
const handleSubmit = async (formData: FormData) => {
|
|
195
|
+
const emailAddress = formData.get('email') as string
|
|
196
|
+
const password = formData.get('password') as string
|
|
197
|
+
|
|
198
|
+
await signIn.password({
|
|
199
|
+
emailAddress,
|
|
200
|
+
password,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// If you're using the authenticator app strategy, remove this check.
|
|
204
|
+
if (signIn.status === 'needs_second_factor') {
|
|
205
|
+
await signIn.mfa.sendPhoneCode()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (signIn.status === 'complete') {
|
|
209
|
+
await signIn.finalize({
|
|
210
|
+
navigate: ({ session, decorateUrl }) => {
|
|
211
|
+
if (session?.currentTask) {
|
|
212
|
+
// Handle pending session tasks
|
|
213
|
+
// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
|
|
214
|
+
console.log(session?.currentTask)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const url = decorateUrl('/')
|
|
219
|
+
if (url.startsWith('http')) {
|
|
220
|
+
window.location.href = url
|
|
221
|
+
} else {
|
|
222
|
+
router.push(url)
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const handleMFAVerification = async (formData: FormData) => {
|
|
230
|
+
const code = formData.get('code') as string
|
|
231
|
+
const useBackupCode = formData.get('useBackupCode') === 'on'
|
|
232
|
+
|
|
233
|
+
if (useBackupCode) {
|
|
234
|
+
await signIn.mfa.verifyBackupCode({ code })
|
|
235
|
+
} else {
|
|
236
|
+
await signIn.mfa.verifyPhoneCode({ code })
|
|
237
|
+
// If you're using the authenticator app strategy, use the following method instead:
|
|
238
|
+
// await signIn.mfa.verifyTOTP({ code })
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (signIn.status === 'complete') {
|
|
242
|
+
await signIn.finalize({
|
|
243
|
+
navigate: ({ session, decorateUrl }) => {
|
|
244
|
+
if (session?.currentTask) {
|
|
245
|
+
// Handle pending session tasks
|
|
246
|
+
// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
|
|
247
|
+
console.log(session?.currentTask)
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const url = decorateUrl('/')
|
|
252
|
+
if (url.startsWith('http')) {
|
|
253
|
+
window.location.href = url
|
|
254
|
+
} else {
|
|
255
|
+
router.push(url)
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (signIn.status === 'needs_second_factor') {
|
|
263
|
+
return (
|
|
264
|
+
<div>
|
|
265
|
+
<h1>Verify your account</h1>
|
|
266
|
+
<form action={handleMFAVerification}>
|
|
267
|
+
<div>
|
|
268
|
+
<label htmlFor="code">Code</label>
|
|
269
|
+
<input id="code" name="code" type="text" />
|
|
270
|
+
{errors.fields.code && <p>{errors.fields.code.message}</p>}
|
|
271
|
+
</div>
|
|
272
|
+
<div>
|
|
273
|
+
<label>
|
|
274
|
+
Use backup code
|
|
275
|
+
<input type="checkbox" name="useBackupCode" />
|
|
276
|
+
</label>
|
|
277
|
+
</div>
|
|
278
|
+
<button type="submit" disabled={fetchStatus === 'fetching'}>
|
|
279
|
+
Verify
|
|
280
|
+
</button>
|
|
281
|
+
</form>
|
|
282
|
+
</div>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<>
|
|
288
|
+
<h1>Sign in</h1>
|
|
289
|
+
<form action={handleSubmit}>
|
|
290
|
+
<div>
|
|
291
|
+
<label htmlFor="email">Enter email address</label>
|
|
292
|
+
<input id="email" name="email" type="email" />
|
|
293
|
+
{errors.fields.identifier && <p>{errors.fields.identifier.message}</p>}
|
|
294
|
+
</div>
|
|
295
|
+
<div>
|
|
296
|
+
<label htmlFor="password">Enter password</label>
|
|
297
|
+
<input id="password" name="password" type="password" />
|
|
298
|
+
{errors.fields.password && <p>{errors.fields.password.message}</p>}
|
|
299
|
+
</div>
|
|
300
|
+
<button type="submit" disabled={fetchStatus === 'fetching'}>
|
|
301
|
+
Continue
|
|
302
|
+
</button>
|
|
303
|
+
</form>
|
|
304
|
+
{errors && <p>{JSON.stringify(errors, null, 2)}</p>}
|
|
305
|
+
</>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Docs
|
|
311
|
+
|
|
312
|
+
- [Custom sign-in flow](https://clerk.com/docs/custom-flows/overview)
|
|
313
|
+
- [MFA custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication)
|
|
314
|
+
- [useSignIn() reference](https://clerk.com/docs/references/react/use-sign-in)
|