account-page-password-reset #14
6 changed files with 126 additions and 12 deletions
|
|
@ -4,14 +4,14 @@ const props = defineProps<{
|
|||
email: string
|
||||
}>();
|
||||
|
||||
const {resetPassword} = useAuth();
|
||||
const {resetPasswordWithToken} = useAuth();
|
||||
const password = ref("");
|
||||
const passwordConfirmation = ref("");
|
||||
const tokenExpired = ref(false);
|
||||
|
||||
const handlePasswordReset = () => {
|
||||
try {
|
||||
resetPassword(password.value, passwordConfirmation.value, props.token, props.email);
|
||||
resetPasswordWithToken(password.value, passwordConfirmation.value, props.token, props.email);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && error.message === "TOKEN_EXPIRED")
|
||||
tokenExpired.value = true;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import ButtonAction from "~/components/common/button-action.vue";
|
||||
|
||||
const {resetPassword} = useAuth();
|
||||
|
||||
const currentPassword = ref("");
|
||||
const newPassword = ref("");
|
||||
const confirmNewPassword = ref("");
|
||||
const errors = ref<string[]>([]);
|
||||
const successMessage = ref("");
|
||||
|
||||
const handlePasswordReset = async () => {
|
||||
errors.value = []
|
||||
try {
|
||||
await resetPassword(newPassword.value, confirmNewPassword.value, currentPassword.value);
|
||||
successMessage.value = "Password reset successful!";
|
||||
} catch (error) {
|
||||
const fieldErrors = Object.values((error as any)?.errors ?? {}).flat() as string[]
|
||||
errors.value = fieldErrors.length ? fieldErrors : [error?.message ?? "An error occurred. Please try again."]
|
||||
successMessage.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="password-form">
|
||||
<form class="password-form" @submit.prevent>
|
||||
<div class="form-group">
|
||||
<label for="current-password">Current Password</label>
|
||||
<input id="current-password" type="password"/>
|
||||
<label for="new-password">Current Password</label>
|
||||
<input id="new-password" v-model="currentPassword" type="password"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="current-password">New Password</label>
|
||||
<input id="current-password" v-model="newPassword" type="password"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="current-password">New Password (again)</label>
|
||||
<input id="current-password" v-model="confirmNewPassword" type="password"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new-password">New Password</label>
|
||||
<input id="new-password" type="password"/>
|
||||
</div>
|
||||
|
||||
<ButtonAction button-text="Reset Password" @action="handlePasswordReset"/>
|
||||
<ul v-if="errors.length" class="error-message">
|
||||
<li v-for="msg in errors" :key="msg">{{ msg }}</li>
|
||||
</ul>
|
||||
<p v-if="successMessage" class="success-message">{{ successMessage }}</p>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
|
@ -22,4 +53,12 @@
|
|||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: var(--color-button-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -37,12 +37,32 @@ export const useAuth = () => {
|
|||
await navigateTo('/auth/login')
|
||||
}
|
||||
|
||||
const resetPassword = async (password: string, passwordConfirmation: string, token: string, email: string) => {
|
||||
const resetPassword = async (password: string, passwordConfirmation: string, currentPassword: string) => {
|
||||
await $fetch('/sanctum/csrf-cookie', {
|
||||
baseURL: config.public.apiBase,
|
||||
credentials: 'include',
|
||||
})
|
||||
await $api('/api/reset-password', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
password,
|
||||
password_confirmation: passwordConfirmation,
|
||||
current_password: currentPassword
|
||||
},
|
||||
onResponseError: ({response}) => {
|
||||
const err = new Error(response._data?.message ?? 'Failed to reset password');
|
||||
(err as any).errors = response._data?.errors ?? {}
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetPasswordWithToken = async (password: string, passwordConfirmation: string, token: string, email: string) => {
|
||||
await $fetch('/sanctum/csrf-cookie', {
|
||||
baseURL: config.public.apiBase,
|
||||
credentials: 'include',
|
||||
})
|
||||
await $api('/api/reset-password-token', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
password,
|
||||
|
|
@ -59,6 +79,20 @@ export const useAuth = () => {
|
|||
await navigateTo('/lists')
|
||||
}
|
||||
|
||||
const forgotPassword = async (email: string) => {
|
||||
await $fetch('/sanctum/csrf-cookie', {
|
||||
baseURL: config.public.apiBase,
|
||||
credentials: 'include',
|
||||
})
|
||||
await $api('/api/forgot-password', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
email
|
||||
},
|
||||
})
|
||||
await navigateTo('/login')
|
||||
}
|
||||
|
||||
const xsrfToken = useCookie('XSRF-TOKEN')
|
||||
|
||||
const logout = async () => {
|
||||
|
|
@ -77,5 +111,5 @@ export const useAuth = () => {
|
|||
navigateTo('/auth/login')
|
||||
}
|
||||
|
||||
return {login, register, resetPassword, logout}
|
||||
return {login, register, forgotPassword, resetPassword, resetPasswordWithToken, logout}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ export default defineNuxtRouteMiddleware((to) => {
|
|||
const publicRoutes = [
|
||||
'auth-login',
|
||||
'auth-register',
|
||||
'auth-reset-password',
|
||||
'auth-reset-password-token',
|
||||
'auth-forgot-password',
|
||||
'invitations-token-accept',
|
||||
'invitations-token-decline',
|
||||
]
|
||||
|
|
|
|||
35
app/pages/auth/forgot-password.vue
Normal file
35
app/pages/auth/forgot-password.vue
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<script lang="ts" setup>
|
||||
|
||||
import ButtonAction from "~/components/common/button-action.vue";
|
||||
|
||||
definePageMeta({
|
||||
layout: 'auth'
|
||||
})
|
||||
|
||||
const {forgotPassword} = useAuth();
|
||||
const email = ref("");
|
||||
const handlePasswordReset = () => {
|
||||
forgotPassword(email.value);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<form class="forgot-password-form">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" v-model="email" type="email"/>
|
||||
<ButtonAction button-text="Send Reset Link" @action="handlePasswordReset"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -10,11 +10,16 @@ definePageMeta({
|
|||
<div class="content">
|
||||
<h1>Log in</h1>
|
||||
<login-form/>
|
||||
<NuxtLink class="link" to="/auth/forgot-password">Forgot Your Password?</NuxtLink>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.link {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue