@xenterprises/nuxt-x-auth-better 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/LICENSE +60 -0
- package/README.md +199 -0
- package/app/app.config.ts +66 -0
- package/app/assets/css/main.css +118 -0
- package/app/components/XAuth/ForgotPassword.vue +91 -0
- package/app/components/XAuth/Handler.vue +186 -0
- package/app/components/XAuth/Login.vue +121 -0
- package/app/components/XAuth/MagicLink.vue +111 -0
- package/app/components/XAuth/OAuthButton.vue +130 -0
- package/app/components/XAuth/OAuthButtonGroup.vue +58 -0
- package/app/components/XAuth/Signup.vue +68 -0
- package/app/composables/types.ts +69 -0
- package/app/composables/useXAuth.ts +317 -0
- package/app/layouts/auth.vue +223 -0
- package/app/middleware/auth.global.ts +45 -0
- package/app/pages/auth/forgot-password.vue +9 -0
- package/app/pages/auth/handler/[...slug].vue +33 -0
- package/app/pages/auth/login.vue +9 -0
- package/app/pages/auth/logout.vue +18 -0
- package/app/pages/auth/magic-link.vue +9 -0
- package/app/pages/auth/signup.vue +9 -0
- package/app/plugins/auth-token.ts +9 -0
- package/app/utils/cookieStorage.ts +64 -0
- package/app/utils/fieldMapper.ts +102 -0
- package/nuxt.config.ts +25 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 X Enterprises LLC. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
exclusive property of X Enterprises LLC, a Washington limited liability
|
|
7
|
+
company.
|
|
8
|
+
|
|
9
|
+
TERMS AND CONDITIONS
|
|
10
|
+
|
|
11
|
+
1. OWNERSHIP
|
|
12
|
+
All rights, title, and interest in and to the Software, including all
|
|
13
|
+
intellectual property rights, are and shall remain the exclusive property
|
|
14
|
+
of X Enterprises LLC.
|
|
15
|
+
|
|
16
|
+
2. RESTRICTIONS
|
|
17
|
+
Without the prior written consent of X Enterprises LLC, you may not:
|
|
18
|
+
- Copy, modify, or distribute the Software
|
|
19
|
+
- Reverse engineer, decompile, or disassemble the Software
|
|
20
|
+
- Sublicense, sell, lease, or otherwise transfer the Software
|
|
21
|
+
- Remove or alter any proprietary notices or labels
|
|
22
|
+
|
|
23
|
+
3. AUTHORIZED USE
|
|
24
|
+
Use of this Software is limited to authorized employees, contractors, and
|
|
25
|
+
agents of X Enterprises LLC, solely for purposes approved by X Enterprises
|
|
26
|
+
LLC.
|
|
27
|
+
|
|
28
|
+
4. NO WARRANTY
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
32
|
+
X ENTERPRISES LLC BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
33
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF,
|
|
34
|
+
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
|
36
|
+
|
|
37
|
+
5. LIMITATION OF LIABILITY
|
|
38
|
+
IN NO EVENT SHALL X ENTERPRISES LLC BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
|
39
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
40
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
41
|
+
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
42
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
43
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
44
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
45
|
+
|
|
46
|
+
6. GOVERNING LAW
|
|
47
|
+
This license shall be governed by and construed in accordance with the laws
|
|
48
|
+
of the State of Washington, United States, without regard to its conflict
|
|
49
|
+
of law provisions.
|
|
50
|
+
|
|
51
|
+
7. TERMINATION
|
|
52
|
+
This license is effective until terminated. X Enterprises LLC may terminate
|
|
53
|
+
this license at any time without notice. Upon termination, you must destroy
|
|
54
|
+
all copies of the Software in your possession.
|
|
55
|
+
|
|
56
|
+
For licensing inquiries, contact: legal@x.enterprises
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
X Enterprises LLC
|
|
60
|
+
Bothell, Washington, United States
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @xenterprises/nuxt-x-auth-better
|
|
2
|
+
|
|
3
|
+
BetterAuth authentication layer for Nuxt 4.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Better Auth integration** - Industry-standard authentication with Better Auth
|
|
8
|
+
- **OAuth providers** - Google, GitHub, and more
|
|
9
|
+
- **Magic link authentication** - Passwordless login via email
|
|
10
|
+
- **Secure token storage** - HttpOnly cookies with SameSite protection
|
|
11
|
+
- **Field normalization** - Unified user object across providers
|
|
12
|
+
- **Pre-built UI components** - Login, Signup, Forgot Password, Magic Link, OAuth
|
|
13
|
+
- **Route protection** - Global middleware for authenticated routes
|
|
14
|
+
- **Responsive design** - Built with Nuxt UI v4
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @xenterprises/nuxt-x-auth-better better-auth
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### 1. Extend the layer
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// nuxt.config.ts
|
|
28
|
+
export default defineNuxtConfig({
|
|
29
|
+
extends: ['@xenterprises/nuxt-x-auth-better']
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Configure your provider
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// app.config.ts
|
|
37
|
+
export default defineAppConfig({
|
|
38
|
+
xAuth: {
|
|
39
|
+
tokens: {
|
|
40
|
+
accessCookie: 'x_auth_access',
|
|
41
|
+
refreshCookie: 'x_auth_refresh',
|
|
42
|
+
hasRefresh: true,
|
|
43
|
+
},
|
|
44
|
+
redirects: {
|
|
45
|
+
login: '/auth/login',
|
|
46
|
+
signup: '/auth/signup',
|
|
47
|
+
afterLogin: '/dashboard',
|
|
48
|
+
afterSignup: '/dashboard',
|
|
49
|
+
afterLogout: '/auth/login',
|
|
50
|
+
forgotPassword: '/auth/forgot-password',
|
|
51
|
+
},
|
|
52
|
+
features: {
|
|
53
|
+
oauth: true,
|
|
54
|
+
magicLink: true,
|
|
55
|
+
forgotPassword: true,
|
|
56
|
+
signup: true,
|
|
57
|
+
},
|
|
58
|
+
oauthProviders: ['google', 'github'],
|
|
59
|
+
ui: {
|
|
60
|
+
showLogo: true,
|
|
61
|
+
logoUrl: '/logo.svg',
|
|
62
|
+
brandName: 'My App',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Set environment variables
|
|
69
|
+
|
|
70
|
+
```env
|
|
71
|
+
NUXT_PUBLIC_BETTER_AUTH_BASE_URL=https://api.example.com
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Composable
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<script setup>
|
|
80
|
+
const {
|
|
81
|
+
user,
|
|
82
|
+
login,
|
|
83
|
+
logout,
|
|
84
|
+
signup,
|
|
85
|
+
loginWithOAuth,
|
|
86
|
+
sendMagicLink,
|
|
87
|
+
forgotPassword,
|
|
88
|
+
isLoading,
|
|
89
|
+
isAuthenticated
|
|
90
|
+
} = useXAuth()
|
|
91
|
+
|
|
92
|
+
// Email/password login
|
|
93
|
+
await login('email@example.com', 'password')
|
|
94
|
+
|
|
95
|
+
// OAuth login
|
|
96
|
+
await loginWithOAuth('google')
|
|
97
|
+
|
|
98
|
+
// Magic link
|
|
99
|
+
await sendMagicLink('email@example.com')
|
|
100
|
+
|
|
101
|
+
// Signup
|
|
102
|
+
await signup('email@example.com', 'password', { name: 'John' })
|
|
103
|
+
|
|
104
|
+
// Password reset
|
|
105
|
+
await forgotPassword('email@example.com')
|
|
106
|
+
|
|
107
|
+
// Logout
|
|
108
|
+
await logout()
|
|
109
|
+
</script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Normalized User Object
|
|
113
|
+
|
|
114
|
+
Better Auth returns a normalized user object:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
{
|
|
118
|
+
id: 'user-123',
|
|
119
|
+
email: 'user@example.com',
|
|
120
|
+
name: 'John Doe',
|
|
121
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
122
|
+
emailVerified: true,
|
|
123
|
+
metadata: { /* provider-specific data */ }
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Protected Routes
|
|
128
|
+
|
|
129
|
+
Routes are automatically protected by the global middleware.
|
|
130
|
+
|
|
131
|
+
**Route types:**
|
|
132
|
+
- **Guest-only routes** - `/auth/login`, `/auth/signup`, `/auth/forgot-password`, `/auth/magic-link` (redirects logged-in users)
|
|
133
|
+
- **Public routes** - `/auth/handler/*`, `/auth/logout` (accessible by anyone)
|
|
134
|
+
- **Protected routes** - Everything else (requires authentication)
|
|
135
|
+
|
|
136
|
+
## Components
|
|
137
|
+
|
|
138
|
+
| Component | Description |
|
|
139
|
+
|-----------|-------------|
|
|
140
|
+
| `XAuthLogin` | Email/password login form |
|
|
141
|
+
| `XAuthSignup` | Registration form |
|
|
142
|
+
| `XAuthForgotPassword` | Password reset request |
|
|
143
|
+
| `XAuthMagicLink` | Magic link authentication |
|
|
144
|
+
| `XAuthOAuthButton` | Single OAuth provider button |
|
|
145
|
+
| `XAuthOAuthButtonGroup` | All configured OAuth buttons |
|
|
146
|
+
| `XAuthHandler` | OAuth callback handler |
|
|
147
|
+
|
|
148
|
+
## Pages (Auto-registered)
|
|
149
|
+
|
|
150
|
+
- `/auth/login` - Login page
|
|
151
|
+
- `/auth/signup` - Registration page
|
|
152
|
+
- `/auth/forgot-password` - Password reset
|
|
153
|
+
- `/auth/magic-link` - Magic link authentication
|
|
154
|
+
- `/auth/logout` - Logout handler
|
|
155
|
+
- `/auth/handler/*` - OAuth callback handlers
|
|
156
|
+
|
|
157
|
+
## OAuth Providers
|
|
158
|
+
|
|
159
|
+
Configure which OAuth providers to show:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// app.config.ts
|
|
163
|
+
export default defineAppConfig({
|
|
164
|
+
xAuth: {
|
|
165
|
+
oauthProviders: ['google', 'github', 'microsoft', 'apple'],
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Available providers depend on your Better Auth backend configuration.
|
|
171
|
+
|
|
172
|
+
## Token Storage
|
|
173
|
+
|
|
174
|
+
Tokens are stored in secure cookies:
|
|
175
|
+
- `HttpOnly` - Not accessible via JavaScript
|
|
176
|
+
- `Secure` - Only sent over HTTPS in production
|
|
177
|
+
- `SameSite=Lax` - CSRF protection
|
|
178
|
+
|
|
179
|
+
Cookie names are configurable via `tokens.accessCookie` and `tokens.refreshCookie`.
|
|
180
|
+
|
|
181
|
+
## Testing
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Unit tests
|
|
185
|
+
npm run test
|
|
186
|
+
|
|
187
|
+
# E2E tests
|
|
188
|
+
npx playwright test
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Requirements
|
|
192
|
+
|
|
193
|
+
- Nuxt 4+
|
|
194
|
+
- Better Auth 1.0+
|
|
195
|
+
- @nuxt/ui v4+ (included)
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export default defineAppConfig({
|
|
2
|
+
xAuth: {
|
|
3
|
+
tokens: {
|
|
4
|
+
accessCookie: "x_auth_access",
|
|
5
|
+
refreshCookie: "x_auth_refresh",
|
|
6
|
+
hasRefresh: true,
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
redirects: {
|
|
10
|
+
login: "/auth/login",
|
|
11
|
+
signup: "/auth/signup",
|
|
12
|
+
afterLogin: "/",
|
|
13
|
+
afterSignup: "/",
|
|
14
|
+
afterLogout: "/auth/login",
|
|
15
|
+
forgotPassword: "/auth/forgot-password",
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
features: {
|
|
19
|
+
oauth: false,
|
|
20
|
+
magicLink: false,
|
|
21
|
+
otp: false,
|
|
22
|
+
forgotPassword: true,
|
|
23
|
+
signup: true,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
oauthProviders: [] as Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
icon: string;
|
|
30
|
+
}>,
|
|
31
|
+
|
|
32
|
+
ui: {
|
|
33
|
+
showLogo: true,
|
|
34
|
+
showBrandName: true,
|
|
35
|
+
logoUrl: "",
|
|
36
|
+
brandName: "",
|
|
37
|
+
tagline: "",
|
|
38
|
+
layout: "centered" as "centered" | "split",
|
|
39
|
+
background: {
|
|
40
|
+
type: "gradient" as "gradient" | "image" | "solid",
|
|
41
|
+
imageUrl: "",
|
|
42
|
+
overlayOpacity: 50,
|
|
43
|
+
},
|
|
44
|
+
card: {
|
|
45
|
+
glass: false,
|
|
46
|
+
glassIntensity: "medium" as "subtle" | "medium" | "strong",
|
|
47
|
+
logoUrl: "",
|
|
48
|
+
},
|
|
49
|
+
legal: {
|
|
50
|
+
copyright: "",
|
|
51
|
+
links: [] as Array<{ label: string; to: string }>,
|
|
52
|
+
},
|
|
53
|
+
split: {
|
|
54
|
+
heroPosition: "left" as "left" | "right",
|
|
55
|
+
heroImageUrl: "",
|
|
56
|
+
headline: "",
|
|
57
|
+
subheadline: "",
|
|
58
|
+
features: [] as string[],
|
|
59
|
+
},
|
|
60
|
+
form: {
|
|
61
|
+
icon: "",
|
|
62
|
+
showSeparator: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "@nuxt/ui";
|
|
3
|
+
|
|
4
|
+
:focus-visible {
|
|
5
|
+
outline: 2px solid rgb(var(--color-primary-500));
|
|
6
|
+
outline-offset: 2px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.password-strength {
|
|
10
|
+
display: flex;
|
|
11
|
+
gap: 0.25rem;
|
|
12
|
+
margin-top: 0.5rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.password-strength__bar {
|
|
16
|
+
flex: 1;
|
|
17
|
+
height: 3px;
|
|
18
|
+
border-radius: 9999px;
|
|
19
|
+
background: rgb(var(--color-neutral-200));
|
|
20
|
+
transition: background 0.2s ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dark .password-strength__bar {
|
|
24
|
+
background: rgb(var(--color-neutral-800));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.password-strength[data-strength="weak"] .password-strength__bar:nth-child(1) {
|
|
28
|
+
background: rgb(var(--color-error-500));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.password-strength[data-strength="medium"] .password-strength__bar:nth-child(1),
|
|
32
|
+
.password-strength[data-strength="medium"] .password-strength__bar:nth-child(2) {
|
|
33
|
+
background: rgb(var(--color-warning-500));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.password-strength[data-strength="strong"] .password-strength__bar {
|
|
37
|
+
background: rgb(var(--color-success-500));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@keyframes skeleton-pulse {
|
|
41
|
+
0%, 100% {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
50% {
|
|
45
|
+
opacity: 0.5;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.skeleton {
|
|
50
|
+
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
51
|
+
background: rgb(var(--color-neutral-200));
|
|
52
|
+
border-radius: 0.375rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dark .skeleton {
|
|
56
|
+
background: rgb(var(--color-neutral-800));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@keyframes shake {
|
|
60
|
+
0%, 100% { transform: translateX(0); }
|
|
61
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
62
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.auth-error {
|
|
66
|
+
animation: shake 0.5s ease-in-out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@keyframes checkmark {
|
|
70
|
+
0% {
|
|
71
|
+
stroke-dashoffset: 100;
|
|
72
|
+
}
|
|
73
|
+
100% {
|
|
74
|
+
stroke-dashoffset: 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.auth-success-check {
|
|
79
|
+
stroke-dasharray: 100;
|
|
80
|
+
stroke-dashoffset: 100;
|
|
81
|
+
animation: checkmark 0.5s ease-out forwards;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.page-enter-active,
|
|
85
|
+
.page-leave-active {
|
|
86
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.page-enter-from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: translateY(10px);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.page-leave-to {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
transform: translateY(-10px);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@media (prefers-reduced-motion: reduce) {
|
|
100
|
+
*,
|
|
101
|
+
*::before,
|
|
102
|
+
*::after {
|
|
103
|
+
animation-duration: 0.01ms !important;
|
|
104
|
+
animation-iteration-count: 1 !important;
|
|
105
|
+
transition-duration: 0.01ms !important;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@media print {
|
|
110
|
+
.auth-layout {
|
|
111
|
+
background: white !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.auth-bg,
|
|
115
|
+
.auth-hero {
|
|
116
|
+
display: none !important;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<Transition
|
|
4
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
5
|
+
enter-from-class="opacity-0 translate-y-2"
|
|
6
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
7
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
8
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
9
|
+
leave-to-class="opacity-0 -translate-y-2"
|
|
10
|
+
mode="out-in"
|
|
11
|
+
>
|
|
12
|
+
<UAuthForm
|
|
13
|
+
v-if="!emailSent"
|
|
14
|
+
key="form"
|
|
15
|
+
title="Reset your password"
|
|
16
|
+
description="We'll send you a link to reset it"
|
|
17
|
+
:fields="fields"
|
|
18
|
+
:schema="schema"
|
|
19
|
+
:loading="isLoading"
|
|
20
|
+
:submit="{ label: 'Send reset link' }"
|
|
21
|
+
@submit="sendResetEmail"
|
|
22
|
+
>
|
|
23
|
+
<template #footer>
|
|
24
|
+
<ULink
|
|
25
|
+
:to="config.xAuth?.redirects?.login || '/auth/login'"
|
|
26
|
+
class="inline-flex items-center gap-1.5 text-sm text-muted hover:text-highlighted transition-colors"
|
|
27
|
+
>
|
|
28
|
+
<UIcon name="i-lucide-arrow-left" class="size-4" />
|
|
29
|
+
Back to sign in
|
|
30
|
+
</ULink>
|
|
31
|
+
</template>
|
|
32
|
+
</UAuthForm>
|
|
33
|
+
|
|
34
|
+
<article v-else key="success" class="text-center space-y-6">
|
|
35
|
+
<figure class="mx-auto flex size-16 items-center justify-center rounded-full bg-success/10 ring-1 ring-success/20">
|
|
36
|
+
<UIcon name="i-lucide-check" class="size-8 text-success" />
|
|
37
|
+
</figure>
|
|
38
|
+
|
|
39
|
+
<header class="space-y-2">
|
|
40
|
+
<h3 class="text-xl font-semibold tracking-tight text-highlighted">
|
|
41
|
+
Check your email
|
|
42
|
+
</h3>
|
|
43
|
+
<p class="text-sm text-muted">
|
|
44
|
+
If an account exists for <strong class="text-highlighted">{{ sentEmail }}</strong>,
|
|
45
|
+
we've sent password reset instructions.
|
|
46
|
+
</p>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
<UButton
|
|
50
|
+
label="Return to sign in"
|
|
51
|
+
block
|
|
52
|
+
size="lg"
|
|
53
|
+
@click="goToLogin"
|
|
54
|
+
/>
|
|
55
|
+
</article>
|
|
56
|
+
</Transition>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script setup>
|
|
61
|
+
import { z } from "zod"
|
|
62
|
+
|
|
63
|
+
const config = useAppConfig()
|
|
64
|
+
const router = useRouter()
|
|
65
|
+
const { forgotPassword, isLoading, emailSent } = useXAuth()
|
|
66
|
+
|
|
67
|
+
const sentEmail = ref("")
|
|
68
|
+
|
|
69
|
+
const fields = [
|
|
70
|
+
{
|
|
71
|
+
name: "email",
|
|
72
|
+
type: "email",
|
|
73
|
+
label: "Email",
|
|
74
|
+
placeholder: "you@example.com",
|
|
75
|
+
required: true,
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
const schema = z.object({
|
|
80
|
+
email: z.string().email("Please enter a valid email address"),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
async function sendResetEmail(payload) {
|
|
84
|
+
sentEmail.value = payload.data.email
|
|
85
|
+
await forgotPassword(payload.data.email)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function goToLogin() {
|
|
89
|
+
router.push(config.xAuth?.redirects?.login || "/auth/login")
|
|
90
|
+
}
|
|
91
|
+
</script>
|