feat(auth): adds local account authentication
This commit is contained in:
183
frontend/src/views/profile/account/ChangePasswordDialog.vue
Normal file
183
frontend/src/views/profile/account/ChangePasswordDialog.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="card dialog">
|
||||
|
||||
<div class="card-title">
|
||||
{{ t('changePassword') }}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p class="description mb-4">{{ t('passwordDescription') }}</p>
|
||||
|
||||
<v-text-field
|
||||
v-model="newPassword"
|
||||
:label="t('newPassword')"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
required
|
||||
:hint="t('passwordRequirements')"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showNewPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
:label="t('confirmPassword')"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
required
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-icon
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
class="visibility-toggle"
|
||||
size="small"
|
||||
>
|
||||
{{ showConfirmPassword ? 'mdi-eye-off' : 'mdi-eye' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<div v-if="errorMessage" class="error-message mb-4">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="secondary" @click="$emit('closeRequested')">
|
||||
{{ t('cancel') }}
|
||||
</button>
|
||||
<button class="primary" @click="handleChangePassword" :disabled="isLoading">
|
||||
{{ t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from '@/stores/authStore.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const emit = defineEmits(['closeRequested']);
|
||||
|
||||
const newPassword = ref('');
|
||||
const confirmPassword = ref('');
|
||||
const isLoading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
async function handleChangePassword() {
|
||||
// Clear previous error
|
||||
errorMessage.value = '';
|
||||
|
||||
// Validate passwords match
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
errorMessage.value = t('passwordsDoNotMatch');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (newPassword.value.length < 8) {
|
||||
errorMessage.value = t('passwordTooShort');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
// Pass empty string for current password since we're already authenticated
|
||||
// This will use the set-password endpoint for OAuth users
|
||||
await authStore.changePassword(newPassword.value);
|
||||
|
||||
// Success - close dialog
|
||||
emit('closeRequested');
|
||||
|
||||
// You could also emit a success event if needed
|
||||
// emit('success');
|
||||
} catch (error) {
|
||||
console.error('Failed to change password:', error);
|
||||
// Use error message from response if available, or the error message itself, or fallback
|
||||
errorMessage.value = error.response?.data || error.message || t('passwordUpdateFailed');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.dialog {
|
||||
@apply max-w-md mx-auto;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@apply text-red-500 text-sm mt-2;
|
||||
}
|
||||
|
||||
.visibility-toggle {
|
||||
@apply cursor-pointer;
|
||||
@apply transition-opacity duration-300;
|
||||
@apply opacity-60 hover:opacity-100;
|
||||
@apply absolute right-2 top-1/2 transform -translate-y-1/2;
|
||||
@apply z-10;
|
||||
}
|
||||
|
||||
/* Override Vuetify's default padding to accommodate our icon */
|
||||
:deep(.v-field__append-inner) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"changePassword": "Set Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm New Password",
|
||||
"passwordRequirements": "Password must be at least 8 characters",
|
||||
"passwordDescription": "Setting a password allows you to log in directly with your email and password, even if you originally signed up with Google.",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"passwordsDoNotMatch": "New passwords do not match",
|
||||
"passwordTooShort": "Password must be at least 8 characters long",
|
||||
"passwordUpdateFailed": "Failed to update password. Please try again."
|
||||
},
|
||||
"fr": {
|
||||
"changePassword": "Définir le mot de passe",
|
||||
"newPassword": "Nouveau mot de passe",
|
||||
"confirmPassword": "Confirmer le nouveau mot de passe",
|
||||
"passwordRequirements": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordDescription": "La définition d'un mot de passe vous permet de vous connecter directement avec votre email et mot de passe, même si vous vous êtes initialement inscrit avec Google.",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"passwordsDoNotMatch": "Les nouveaux mots de passe ne correspondent pas",
|
||||
"passwordTooShort": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"passwordUpdateFailed": "Échec de la mise à jour du mot de passe. Veuillez réessayer."
|
||||
},
|
||||
"es": {
|
||||
"changePassword": "Establecer contraseña",
|
||||
"newPassword": "Nueva contraseña",
|
||||
"confirmPassword": "Confirmar nueva contraseña",
|
||||
"passwordRequirements": "La contraseña debe tener al menos 8 caracteres",
|
||||
"passwordDescription": "Establecer una contraseña le permite iniciar sesión directamente con su correo electrónico y contraseña, incluso si originalmente se registró con Google.",
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"passwordsDoNotMatch": "Las nuevas contraseñas no coinciden",
|
||||
"passwordTooShort": "La contraseña debe tener al menos 8 caracteres",
|
||||
"passwordUpdateFailed": "Error al actualizar la contraseña. Por favor, inténtelo de nuevo."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
Reference in New Issue
Block a user