Gitea Actions / Deploy (push) Successful in 29s
sessions fix password change sign-in fix
145 lines
3.9 KiB
Vue
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>
|