202 lines
4.3 KiB
Vue
202 lines
4.3 KiB
Vue
<template>
|
|
<v-dialog v-model="show" max-width="400" persistent>
|
|
<v-card>
|
|
<v-card-title class="text-h5 text-center pa-6" style="color: #a31515;">
|
|
Reset Password
|
|
</v-card-title>
|
|
|
|
<v-card-text class="px-6">
|
|
<p class="text-body-2 mb-4 text-center text-medium-emphasis">
|
|
Enter your email address and we'll send you a link to reset your password.
|
|
</p>
|
|
|
|
<v-form @submit.prevent="handleSubmit" ref="resetForm">
|
|
<v-text-field
|
|
v-model="email"
|
|
label="Email Address"
|
|
type="email"
|
|
prepend-inner-icon="mdi-email"
|
|
variant="outlined"
|
|
:error-messages="errors.email"
|
|
:disabled="loading"
|
|
required
|
|
@input="clearErrors"
|
|
/>
|
|
|
|
<v-alert
|
|
v-if="message"
|
|
:type="messageType"
|
|
class="mb-4"
|
|
variant="tonal"
|
|
>
|
|
{{ message }}
|
|
</v-alert>
|
|
</v-form>
|
|
</v-card-text>
|
|
|
|
<v-card-actions class="px-6 pb-6">
|
|
<v-btn
|
|
variant="text"
|
|
@click="close"
|
|
:disabled="loading"
|
|
>
|
|
Cancel
|
|
</v-btn>
|
|
<v-spacer />
|
|
<v-btn
|
|
color="primary"
|
|
@click="handleSubmit"
|
|
:loading="loading"
|
|
:disabled="!email || !isValidEmail"
|
|
style="background-color: #a31515 !important;"
|
|
>
|
|
Send Reset Link
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
modelValue: boolean;
|
|
}
|
|
|
|
interface Emits {
|
|
(e: 'update:modelValue', value: boolean): void;
|
|
(e: 'success', message: string): void;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
const emit = defineEmits<Emits>();
|
|
|
|
// Reactive data
|
|
const email = ref('');
|
|
const loading = ref(false);
|
|
const message = ref('');
|
|
const messageType = ref<'success' | 'error' | 'warning' | 'info'>('info');
|
|
const errors = ref({
|
|
email: ''
|
|
});
|
|
|
|
const resetForm = ref();
|
|
|
|
// Computed
|
|
const show = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => emit('update:modelValue', value)
|
|
});
|
|
|
|
const isValidEmail = computed(() => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email.value);
|
|
});
|
|
|
|
// Methods
|
|
const clearErrors = () => {
|
|
errors.value.email = '';
|
|
message.value = '';
|
|
};
|
|
|
|
const validateEmail = () => {
|
|
errors.value.email = '';
|
|
|
|
if (!email.value) {
|
|
errors.value.email = 'Email is required';
|
|
return false;
|
|
}
|
|
|
|
if (!isValidEmail.value) {
|
|
errors.value.email = 'Please enter a valid email address';
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!validateEmail()) return;
|
|
|
|
loading.value = true;
|
|
message.value = '';
|
|
|
|
try {
|
|
const response = await $fetch<{
|
|
success: boolean;
|
|
message: string;
|
|
}>('/api/auth/forgot-password', {
|
|
method: 'POST',
|
|
body: {
|
|
email: email.value
|
|
}
|
|
});
|
|
|
|
if (response.success) {
|
|
message.value = response.message;
|
|
messageType.value = 'success';
|
|
|
|
// Emit success event
|
|
emit('success', response.message);
|
|
|
|
// Auto-close after 3 seconds
|
|
setTimeout(() => {
|
|
close();
|
|
}, 3000);
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Password reset error:', error);
|
|
message.value = error.data?.message || 'Failed to send reset email. Please try again.';
|
|
messageType.value = 'error';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const close = () => {
|
|
show.value = false;
|
|
|
|
// Reset form after dialog closes
|
|
setTimeout(() => {
|
|
email.value = '';
|
|
message.value = '';
|
|
errors.value.email = '';
|
|
loading.value = false;
|
|
}, 300);
|
|
};
|
|
|
|
// Auto-focus email field when dialog opens
|
|
watch(show, (newValue) => {
|
|
if (newValue) {
|
|
nextTick(() => {
|
|
const emailField = document.querySelector('input[type="email"]') as HTMLInputElement;
|
|
if (emailField) {
|
|
emailField.focus();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.v-card {
|
|
border-radius: 16px !important;
|
|
}
|
|
|
|
.v-card-title {
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.v-btn {
|
|
text-transform: none !important;
|
|
}
|
|
|
|
/* Form field focus styles */
|
|
.v-field--focused {
|
|
border-color: #a31515 !important;
|
|
}
|
|
|
|
.v-field--focused .v-field__outline {
|
|
border-color: #a31515 !important;
|
|
}
|
|
</style>
|