monacousa-portal/components/ForgotPasswordDialog.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>