182 lines
4.7 KiB
Vue
182 lines
4.7 KiB
Vue
<script lang='ts' setup>
|
|
import type { GenericObject } from "vee-validate";
|
|
import { toastController } from "@ionic/vue";
|
|
import { arrowBackOutline } from "ionicons/icons";
|
|
import { Field, Form } from "vee-validate";
|
|
import * as yup from "yup";
|
|
import { safeClient } from "@/api";
|
|
import { authClient, emailSchema } from "@/auth";
|
|
|
|
const { user } = useAuth();
|
|
const email = ref(user.value?.email || "");
|
|
const { updateProfile } = useUserStore();
|
|
const { t } = useI18n();
|
|
const countdown = ref(0);
|
|
const isSending = ref(false);
|
|
const canResend = computed(() => countdown.value === 0 && !isSending.value);
|
|
const emailError = ref("");
|
|
|
|
let timer: number | null = null;
|
|
|
|
function startCountdown() {
|
|
countdown.value = 60;
|
|
timer = setInterval(() => {
|
|
countdown.value--;
|
|
if (countdown.value <= 0) {
|
|
if (timer) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
async function sendOtp() {
|
|
const emailValue = email.value.trim();
|
|
if (!emailValue) {
|
|
emailError.value = t("auth.login.validation.emailRequired");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await yup.string().email().validate(emailValue);
|
|
}
|
|
catch {
|
|
emailError.value = t("auth.login.validation.emailInvalid");
|
|
return;
|
|
}
|
|
|
|
if (!canResend.value) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
emailError.value = "";
|
|
isSending.value = true;
|
|
await authClient.emailOtp.sendVerificationOtp({
|
|
email: emailValue,
|
|
type: "sign-in",
|
|
});
|
|
|
|
const toast = await toastController.create({
|
|
message: t("auth.login.sendCodeSuccess"),
|
|
duration: 2000,
|
|
position: "top",
|
|
color: "success",
|
|
});
|
|
await toast.present();
|
|
startCountdown();
|
|
}
|
|
catch (error: any) {
|
|
const toast = await toastController.create({
|
|
message: error?.message || t("auth.common.failedSendCode"),
|
|
duration: 2000,
|
|
position: "top",
|
|
color: "danger",
|
|
});
|
|
await toast.present();
|
|
}
|
|
finally {
|
|
isSending.value = false;
|
|
}
|
|
}
|
|
|
|
async function handleSave(values: GenericObject) {
|
|
const { data } = await safeClient(authClient.emailOtp.checkVerificationOtp({
|
|
email: values.email, // required
|
|
type: "email-verification", // required
|
|
otp: values.otp, // required
|
|
}));
|
|
if (data.value?.success) {
|
|
await safeClient(authClient.changeEmail({
|
|
newEmail: values.email,
|
|
}));
|
|
}
|
|
}
|
|
|
|
onUnmounted(() => {
|
|
if (timer) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<ion-page>
|
|
<ion-header>
|
|
<ion-toolbar class="ui-toolbar">
|
|
<ion-buttons slot="start">
|
|
<ion-button @click="$router.back()">
|
|
<ion-icon slot="icon-only" :icon="arrowBackOutline" />
|
|
</ion-button>
|
|
</ion-buttons>
|
|
<ion-title>用户设置</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-content :fullscreen="true" class="ion-padding">
|
|
<div class="space-y-3">
|
|
<Form :validation-schema="emailSchema" class="mt-5" @submit="handleSave">
|
|
<Field v-slot="{ field, errorMessage }" name="email" type="email">
|
|
<div class="mb-4">
|
|
<ui-input
|
|
v-bind="field"
|
|
v-model="email"
|
|
:placeholder="t('auth.login.enterEmail')"
|
|
type="email"
|
|
>
|
|
<ion-button
|
|
slot="end"
|
|
fill="clear"
|
|
size="small"
|
|
:disabled="!canResend"
|
|
@click="sendOtp"
|
|
>
|
|
<span v-if="countdown > 0">
|
|
{{ countdown }}s
|
|
</span>
|
|
<span v-else-if="isSending">
|
|
{{ t('auth.login.sending') }}
|
|
</span>
|
|
<span v-else>
|
|
{{ t('auth.login.getCode') }}
|
|
</span>
|
|
</ion-button>
|
|
</ui-input>
|
|
<div v-if="errorMessage || emailError" class="text-xs text-red-500 mt-1">
|
|
{{ errorMessage || emailError }}
|
|
</div>
|
|
</div>
|
|
</Field>
|
|
|
|
<Field v-slot="{ field, errorMessage }" name="otp" type="text">
|
|
<div class="mb-4">
|
|
<ui-input
|
|
v-bind="field"
|
|
:placeholder="t('auth.login.enterOtp')"
|
|
:maxlength="6"
|
|
type="text"
|
|
/>
|
|
<div v-if="errorMessage" class="text-xs text-red-500 mt-1">
|
|
{{ errorMessage }}
|
|
</div>
|
|
</div>
|
|
</Field>
|
|
|
|
<ion-button
|
|
expand="block"
|
|
class="ion-margin-top"
|
|
shape="round"
|
|
type="submit"
|
|
>
|
|
{{ t('auth.login.loginButton') }}
|
|
</ion-button>
|
|
</Form>
|
|
</div>
|
|
</ion-content>
|
|
</ion-page>
|
|
</template>
|
|
|
|
<style lang='css' scoped></style>
|