11<script setup lang="ts">
2- import { ref , watch } from ' vue'
3- import type { UserWithoutPassword , ModuleOptions , ResetPasswordFormProps } from ' #nuxt-users/types'
2+ /**
3+ * NUsersResetPasswordForm Component
4+ *
5+ * A dual-purpose password form component that handles both:
6+ * 1. Password reset from email links (when token and email are in URL query params)
7+ * 2. Password change for logged-in users (when no token/email in URL)
8+ *
9+ * The component automatically detects which mode to use based on URL parameters.
10+ * For password reset: requires token and email in URL query params
11+ * For password change: requires user to be logged in and provide current password
12+ */
13+ import { ref , watch , computed } from ' vue'
14+ import type { UserWithoutPassword , ModuleOptions , ResetPasswordFormProps } from ' nuxt-users/utils'
415import { usePasswordValidation } from ' ../composables/usePasswordValidation'
5- import { useRuntimeConfig } from ' #imports'
16+ import { useRuntimeConfig , useRoute , useRouter } from ' #imports'
617import NUsersPasswordStrengthIndicator from ' ./NUsersPasswordStrengthIndicator.vue'
718
819interface Emits {
@@ -17,10 +28,22 @@ const moduleOptions = nuxtUsers as ModuleOptions
1728const props = defineProps <ResetPasswordFormProps >()
1829const emit = defineEmits <Emits >()
1930
31+ const route = useRoute ()
32+ const router = useRouter ()
33+
2034const isPasswordLoading = ref (false )
2135const passwordError = ref (' ' )
2236const passwordSuccess = ref (' ' )
2337
38+ // Check if this is a password reset from email link
39+ const isPasswordReset = computed (() => {
40+ return route .query .token && route .query .email
41+ })
42+
43+ // Get token and email from URL for password reset
44+ const resetToken = computed (() => route .query .token as string )
45+ const resetEmail = computed (() => route .query .email as string )
46+
2447// Password form data
2548const passwordForm = ref ({
2649 currentPassword: ' ' ,
@@ -41,7 +64,7 @@ watch(() => passwordForm.value.newPassword, (newPassword) => {
4164 }
4265})
4366
44- // Update password
67+ // Update password (for logged-in users)
4568const updatePassword = async () => {
4669 isPasswordLoading .value = true
4770 passwordError .value = ' '
@@ -76,16 +99,94 @@ const updatePassword = async () => {
7699 isPasswordLoading .value = false
77100 }
78101}
102+
103+ // Reset password (for email reset links)
104+ const resetPassword = async () => {
105+ if (! resetToken .value || ! resetEmail .value ) {
106+ passwordError .value = ' Missing reset token or email. Please use the link from your email.'
107+ return
108+ }
109+
110+ if (passwordForm .value .newPassword !== passwordForm .value .newPasswordConfirmation ) {
111+ passwordError .value = ' Passwords do not match'
112+ return
113+ }
114+
115+ isPasswordLoading .value = true
116+ passwordError .value = ' '
117+ passwordSuccess .value = ' '
118+
119+ try {
120+ await $fetch (props .resetPasswordEndpoint || nuxtUsers .apiBasePath + ' /password/reset' , {
121+ method: ' POST' ,
122+ body: {
123+ token: resetToken .value ,
124+ email: resetEmail .value ,
125+ password: passwordForm .value .newPassword ,
126+ password_confirmation: passwordForm .value .newPasswordConfirmation
127+ }
128+ })
129+
130+ passwordSuccess .value = ' Password reset successfully. You can now log in with your new password.'
131+ emit (' password-updated' )
132+
133+ // Clear form
134+ passwordForm .value = {
135+ currentPassword: ' ' ,
136+ newPassword: ' ' ,
137+ newPasswordConfirmation: ' '
138+ }
139+
140+ // Redirect to login page after a short delay
141+ setTimeout (() => {
142+ router .push (props .redirectTo || ' /login' )
143+ }, 2000 )
144+ }
145+ catch (err : unknown ) {
146+ const errorMessage = err && typeof err === ' object' && ' data' in err && err .data && typeof err .data === ' object' && ' statusMessage' in err .data
147+ ? String (err .data .statusMessage )
148+ : err instanceof Error
149+ ? err .message
150+ : ' Failed to reset password'
151+ passwordError .value = errorMessage
152+ emit (' password-error' , errorMessage )
153+ }
154+ finally {
155+ isPasswordLoading .value = false
156+ }
157+ }
158+
159+ // Handle form submission based on mode
160+ const handleSubmit = () => {
161+ if (isPasswordReset .value ) {
162+ resetPassword ()
163+ }
164+ else {
165+ updatePassword ()
166+ }
167+ }
79168 </script >
80169
81170<template >
82171 <div class =" n-users-section" >
83172 <h2 class =" n-users-section-header" >
84- Change Password
173+ {{ isPasswordReset ? 'Reset Password' : ' Change Password' }}
85174 </h2 >
86175
87- <form @submit.prevent =" updatePassword" >
88- <div class =" n-users-form-group" >
176+ <div
177+ v-if =" isPasswordReset"
178+ class =" n-users-info-message"
179+ >
180+ <p >You are resetting your password using a secure link.</p >
181+ <p ><strong >Email:</strong > {{ resetEmail }}</p >
182+ </div >
183+
184+ <form @submit.prevent =" handleSubmit" >
185+ <!-- Current Password field - only show for logged-in users -->
186+ <div
187+ v-if =" !isPasswordReset"
188+ class =" n-users-form-group"
189+ >
89190 <label
90191 for =" currentPassword"
91192 class =" n-users-form-label"
@@ -104,7 +205,7 @@ const updatePassword = async () => {
104205 <label
105206 for =" newPassword"
106207 class =" n-users-form-label"
107- >New Password</label >
208+ >{{ isPasswordReset ? ' New Password' : 'New Password' }} </label >
108209 <input
109210 id =" newPassword"
110211 v-model =" passwordForm.newPassword"
@@ -134,7 +235,7 @@ const updatePassword = async () => {
134235 <label
135236 for =" newPasswordConfirmation"
136237 class =" n-users-form-label"
137- >Confirm New Password</label >
238+ >Confirm {{ isPasswordReset ? ' New Password' : 'New Password' }} </label >
138239 <input
139240 id =" newPasswordConfirmation"
140241 v-model =" passwordForm.newPasswordConfirmation"
@@ -164,8 +265,12 @@ const updatePassword = async () => {
164265 class =" n-users-btn n-users-btn-primary"
165266 :disabled =" isPasswordLoading"
166267 >
167- <span v-if =" isPasswordLoading" >Updating...</span >
168- <span v-else >Update Password</span >
268+ <span v-if =" isPasswordLoading" >
269+ {{ isPasswordReset ? 'Resetting...' : 'Updating...' }}
270+ </span >
271+ <span v-else >
272+ {{ isPasswordReset ? 'Reset Password' : 'Update Password' }}
273+ </span >
169274 </button >
170275 </form >
171276 </div >
0 commit comments