Skip to content

Commit 9df6a76

Browse files
committed
feat: add email confirmation component
1 parent df1b1a5 commit 9df6a76

File tree

11 files changed

+398
-31
lines changed

11 files changed

+398
-31
lines changed

docs/user-guide/components.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1485,9 +1485,106 @@ If authentication components are not working:
14851485
3. **Review server logs** - Look for any server-side errors
14861486
4. **Test with the playground** - Try the components in the module's playground
14871487

1488+
### NUsersEmailConfirmation
1489+
1490+
A user-friendly email confirmation page that displays success or error states based on URL parameters. This component is designed to be used on a dedicated confirmation page that users are redirected to after clicking email confirmation links.
1491+
1492+
#### Basic Usage
1493+
1494+
```vue
1495+
<!-- pages/email-confirmation.vue -->
1496+
<template>
1497+
<div>
1498+
<NUsersEmailConfirmation />
1499+
</div>
1500+
</template>
1501+
1502+
<script setup>
1503+
// Set page title and meta tags
1504+
useHead({
1505+
title: 'Email Confirmation',
1506+
meta: [
1507+
{ name: 'description', content: 'Confirm your email address' }
1508+
]
1509+
})
1510+
</script>
1511+
```
1512+
1513+
#### Configuration
1514+
1515+
To enable email confirmation redirects, configure your `nuxt.config.ts`:
1516+
1517+
```ts
1518+
export default defineNuxtConfig({
1519+
modules: ['nuxt-users'],
1520+
nuxtUsers: {
1521+
// URL to redirect to after email confirmation
1522+
emailConfirmationUrl: '/email-confirmation',
1523+
auth: {
1524+
// Whitelist the confirmation page
1525+
whitelist: ['/register', '/email-confirmation']
1526+
}
1527+
}
1528+
})
1529+
```
1530+
1531+
#### Props
1532+
1533+
| Prop | Type | Default | Description |
1534+
|------|------|---------|-------------|
1535+
| `successTitle` | `string` | `'Email Confirmed!'` | Title for successful confirmation |
1536+
| `errorTitle` | `string` | `'Confirmation Failed'` | Title for failed confirmation |
1537+
| `loginButtonText` | `string` | `'Continue to Login'` | Text for the login button |
1538+
| `loginUrl` | `string` | `'/login'` | URL to redirect to for login |
1539+
| `showLoginButton` | `boolean` | `true` | Whether to show the login button |
1540+
1541+
#### URL Parameters
1542+
1543+
The component automatically reads these query parameters:
1544+
- `status` - Either `'success'` or `'error'`
1545+
- `message` - The message to display to the user
1546+
1547+
#### Customization Slots
1548+
1549+
```vue
1550+
<NUsersEmailConfirmation>
1551+
<!-- Custom success state -->
1552+
<template #success-icon>
1553+
<div class="custom-success-icon">
1554+
🎉
1555+
</div>
1556+
</template>
1557+
1558+
<template #success-content>
1559+
<h1>Welcome!</h1>
1560+
<p>Your account is ready to use.</p>
1561+
</template>
1562+
1563+
<template #success-actions>
1564+
<NuxtLink to="/dashboard" class="n-users-btn n-users-btn-primary">
1565+
Go to Dashboard
1566+
</NuxtLink>
1567+
</template>
1568+
1569+
<!-- Custom error state -->
1570+
<template #error-content>
1571+
<h1>Oops!</h1>
1572+
<p>Something went wrong with your confirmation.</p>
1573+
</template>
1574+
</NUsersEmailConfirmation>
1575+
```
1576+
1577+
#### User Experience Flow
1578+
1579+
1. **User registers** using `NUsersRegisterForm`
1580+
2. **Confirmation email sent** with link to `/api/nuxt-users/confirm-email?token=...&email=...`
1581+
3. **User clicks link** - API processes the confirmation
1582+
4. **API redirects** to `/email-confirmation?status=success&message=...`
1583+
5. **User sees friendly page** instead of raw JSON
1584+
14881585
## Next Steps
14891586

14901587
- **[Getting Started](/user-guide/getting-started)** - Learn how to set up authentication in your app
14911588
- **[Configuration](/user-guide/configuration)** - Explore configuration options for components
14921589
- **[Authentication](/user-guide/authentication)** - Understand the authentication system
1493-
- **[API Reference](/api/)** - Explore available API endpoints
1590+
- **[API Reference](/api/)** - Explore available API endpoints

playground/nuxt.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ export default defineNuxtConfig({
66

77
...BASE_CONFIG,
88
nuxtUsers: {
9+
emailConfirmationUrl: '/email-confirmation',
910
auth: {
10-
whitelist: ['/noauth', '/register'],
11+
whitelist: ['/noauth', '/register', '/email-confirmation'],
1112
tokenExpiration: 10,
1213
// TODO: if it is uncommented it makes `yarn test:types` fails - for some unknown reason
1314
// INFO: if it is commented out you can not login to the playground
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<div>
3+
<NUsersEmailConfirmation />
4+
</div>
5+
</template>
6+
7+
<script setup>
8+
// Set page title and meta tags
9+
useHead({
10+
title: 'Email Confirmation - Nuxt Users',
11+
meta: [
12+
{ name: 'description', content: 'Confirm your email address to activate your account' }
13+
]
14+
})
15+
16+
// This is an example page that consumer apps can create
17+
// Users just need to create pages/email-confirmation.vue (or whatever path they configure)
18+
// and use the NUsersEmailConfirmation component
19+
</script>

src/module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const defaultOptions: ModuleOptions = {
2929
},
3030
},
3131
passwordResetUrl: '/reset-password',
32+
emailConfirmationUrl: '/email-confirmation',
3233
auth: {
3334
whitelist: [],
3435
tokenExpiration: 24 * 60, // 24 hours
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useRoute } from '#app'
4+
5+
// Get status and message from URL query parameters
6+
const route = useRoute()
7+
const status = computed(() => route.query.status as string)
8+
const message = computed(() => route.query.message as string)
9+
10+
const isSuccess = computed(() => status.value === 'success')
11+
const isError = computed(() => status.value === 'error')
12+
13+
// Props for customization
14+
interface Props {
15+
successTitle?: string
16+
errorTitle?: string
17+
loginButtonText?: string
18+
loginUrl?: string
19+
showLoginButton?: boolean
20+
}
21+
22+
const _props = withDefaults(defineProps<Props>(), {
23+
successTitle: 'Email Confirmed!',
24+
errorTitle: 'Confirmation Failed',
25+
loginButtonText: 'Continue to Login',
26+
loginUrl: '/login',
27+
showLoginButton: true
28+
})
29+
</script>
30+
31+
<template>
32+
<div class="n-users-section">
33+
<div class="n-users-card">
34+
<!-- Success state -->
35+
<div
36+
v-if="isSuccess"
37+
class="n-users-confirmation-content"
38+
>
39+
<slot name="success-icon">
40+
<div class="n-users-confirmation-icon n-users-confirmation-success">
41+
<svg
42+
viewBox="0 0 24 24"
43+
fill="none"
44+
stroke="currentColor"
45+
stroke-width="2"
46+
>
47+
<path d="m9 12 2 2 4-4" />
48+
<circle
49+
cx="12"
50+
cy="12"
51+
r="10"
52+
/>
53+
</svg>
54+
</div>
55+
</slot>
56+
57+
<div class="n-users-section-header">
58+
<slot name="success-content">
59+
<h1 class="n-users-login-title">
60+
{{ successTitle }}
61+
</h1>
62+
<div class="n-users-success-message">
63+
{{ message || 'Your email has been confirmed and your account is now active.' }}
64+
</div>
65+
</slot>
66+
</div>
67+
68+
<slot name="success-actions">
69+
<div
70+
v-if="showLoginButton"
71+
class="n-users-form-actions"
72+
>
73+
<a
74+
:href="loginUrl"
75+
class="n-users-btn n-users-btn-primary"
76+
>
77+
{{ loginButtonText }}
78+
</a>
79+
</div>
80+
</slot>
81+
</div>
82+
83+
<!-- Error state -->
84+
<div
85+
v-else-if="isError"
86+
class="n-users-confirmation-content"
87+
>
88+
<slot name="error-icon">
89+
<div class="n-users-confirmation-icon n-users-confirmation-error">
90+
<svg
91+
viewBox="0 0 24 24"
92+
fill="none"
93+
stroke="currentColor"
94+
stroke-width="2"
95+
>
96+
<circle
97+
cx="12"
98+
cy="12"
99+
r="10"
100+
/>
101+
<line
102+
x1="15"
103+
y1="9"
104+
x2="9"
105+
y2="15"
106+
/>
107+
<line
108+
x1="9"
109+
y1="9"
110+
x2="15"
111+
y2="15"
112+
/>
113+
</svg>
114+
</div>
115+
</slot>
116+
117+
<div class="n-users-section-header">
118+
<slot name="error-content">
119+
<h1 class="n-users-login-title">
120+
{{ errorTitle }}
121+
</h1>
122+
<div class="n-users-error-message">
123+
{{ message || 'The confirmation link is invalid or has expired. Please try registering again or contact support.' }}
124+
</div>
125+
</slot>
126+
</div>
127+
128+
<slot name="error-actions">
129+
<div class="n-users-form-actions">
130+
<a
131+
:href="loginUrl"
132+
class="n-users-btn n-users-btn-secondary"
133+
>
134+
Back to Login
135+
</a>
136+
</div>
137+
</slot>
138+
</div>
139+
140+
<!-- Loading/unknown state -->
141+
<div
142+
v-else
143+
class="n-users-confirmation-content"
144+
>
145+
<slot name="loading-content">
146+
<div class="n-users-confirmation-icon">
147+
<div class="n-users-loading-spinner n-users-confirmation-spinner" />
148+
</div>
149+
<div class="n-users-section-header">
150+
<h1 class="n-users-login-title">
151+
Processing...
152+
</h1>
153+
<div class="n-users-form-help">
154+
Please wait while we process your email confirmation.
155+
</div>
156+
</div>
157+
</slot>
158+
</div>
159+
</div>
160+
</div>
161+
</template>
162+
163+
<style scoped>
164+
/* Only add minimal styles needed for this specific component */
165+
.n-users-confirmation-content {
166+
text-align: center;
167+
padding: 2rem;
168+
}
169+
170+
.n-users-confirmation-icon {
171+
width: 80px;
172+
height: 80px;
173+
margin: 0 auto 2rem;
174+
border-radius: 50%;
175+
display: flex;
176+
align-items: center;
177+
justify-content: center;
178+
}
179+
180+
.n-users-confirmation-icon svg {
181+
width: 40px;
182+
height: 40px;
183+
}
184+
185+
.n-users-confirmation-success {
186+
background-color: var(--nu-color-success-light);
187+
color: var(--nu-color-success);
188+
}
189+
190+
.n-users-confirmation-error {
191+
background-color: var(--nu-color-error-light);
192+
color: var(--nu-color-error);
193+
}
194+
195+
.n-users-confirmation-spinner {
196+
width: 40px;
197+
height: 40px;
198+
border-width: 4px;
199+
border-color: var(--nu-color-primary);
200+
border-top-color: transparent;
201+
}
202+
203+
/* Mobile responsiveness */
204+
@media (max-width: 640px) {
205+
.n-users-confirmation-content {
206+
padding: 1.5rem;
207+
}
208+
209+
.n-users-confirmation-icon {
210+
width: 60px;
211+
height: 60px;
212+
margin-bottom: 1.5rem;
213+
}
214+
215+
.n-users-confirmation-icon svg {
216+
width: 30px;
217+
height: 30px;
218+
}
219+
}
220+
</style>

0 commit comments

Comments
 (0)