Files
ulitka-app/src/components/PasswordChange.vue
T
vadim f0706432d4
Gitea Actions / Deploy (push) Successful in 29s
avatar fix
sessions fix
password change
sign-in fix
2026-10-23 19:25:11 +03:00

145 lines
3.9 KiB
Vue

<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { authClient } from '@/lib/auth-client.js'
import { useSnackbarStore } from '@/stores/snackbar.ts'
const session = authClient.useSession()
const snackbarStore = useSnackbarStore()
const email = computed(() => session.value.data?.user.email ?? '')
const err = ref('')
const loading = ref(false)
const reset = reactive({
newPassword: '',
OTP: '',
showOTP: false,
showPassword: false,
})
const canReset = computed(() => reset.newPassword.length > 0 && reset.OTP.length === 6)
watch(
() => reset.OTP,
(val) => {
if (val.length >= 1) err.value = ''
},
)
const sendOTP = async () => {
if (!email.value) return
loading.value = true
const { data, error } = await authClient.emailOtp.requestPasswordReset({
email: email.value,
})
loading.value = false
if (data?.success) {
reset.showOTP = true
err.value = ''
}
if (error) err.value = error.message ?? 'Failed to send code'
}
const confirmPasswordReset = async () => {
if (!canReset.value) return
loading.value = true
const { data, error } = await authClient.emailOtp.resetPassword({
email: email.value,
otp: reset.OTP,
password: reset.newPassword,
})
loading.value = false
if (data?.success) {
snackbarStore.add({ title: 'Password changed', text: '', color: 'success' })
reset.newPassword = ''
reset.OTP = ''
reset.showOTP = false
err.value = ''
}
if (error) {
err.value = error.message ?? 'Failed to reset password'
reset.OTP = ''
}
}
const cancel = () => {
reset.newPassword = ''
reset.OTP = ''
reset.showOTP = false
err.value = ''
}
</script>
<template>
<v-sheet border class="pa-4 d-flex flex-column ga-2">
<div class="d-flex ga-2 align-center justify-space-between">
<div class="text-uppercase text-title-large">Change password</div>
<v-chip v-if="err" size="small" variant="flat" color="error" density="compact">{{ err }}</v-chip>
</div>
<div v-if="reset.showOTP" class="d-flex flex-column ga-2">
<div class="text-body-medium">Enter the code sent to {{ email }} and a new password</div>
<v-text-field
class="w-50"
clearable
bg-color="background"
density="comfortable"
placeholder="New password"
prepend-inner-icon="mdi-lock"
:type="reset.showPassword ? 'text' : 'password'"
:append-inner-icon="reset.showPassword ? 'mdi-eye-off' : 'mdi-eye'"
@click:append-inner="reset.showPassword = !reset.showPassword"
v-model="reset.newPassword"
/>
<div class="d-flex ga-2 align-center">
<div>CODE from E-mail:</div>
<v-otp-input
class="justify-start pa-0"
color="background"
density="comfortable"
pattern="numeric"
v-model="reset.OTP"
@finish="confirmPasswordReset"
/>
</div>
<div class="d-flex ga-1">
<v-btn density="comfortable" text="Cancel" @click="cancel" />
<v-btn
prepend-icon="mdi-autorenew"
density="comfortable"
text="Resend Code"
:disabled="loading"
@click="sendOTP"
/>
<v-btn
variant="tonal"
density="comfortable"
prepend-icon="mdi-check-circle"
text="Reset password"
:loading="loading"
:disabled="!canReset"
@click="confirmPasswordReset"
/>
</div>
</div>
<div v-else class="d-flex flex-column ga-2">
<div class="text-body-medium">A confirmation code will be sent to {{ email }}</div>
<div class="d-flex ga-1">
<v-btn
prepend-icon="mdi-email-fast-outline"
density="comfortable"
text="Send Code"
:loading="loading"
:disabled="!email"
@click="sendOTP"
/>
</div>
</div>
</v-sheet>
</template>
<style scoped lang="scss"></style>