Files
riwa-ionic/src/views/user-settings/email.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>