refactor: 重构登陆模块
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import type { InputCustomEvent } from "@ionic/vue";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { logoGoogle, phonePortraitOutline } from "ionicons/icons";
|
||||
import { authClient } from "@/auth";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "success", value: string): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const model = defineModel({ type: String, required: true });
|
||||
const inputInstance = useTemplateRef<InputInstance>("inputInstance");
|
||||
|
||||
function markTouched() {
|
||||
inputInstance.value?.$el.classList.add("ion-touched");
|
||||
}
|
||||
function validate(value: string) {
|
||||
inputInstance.value?.$el.classList.remove("ion-valid");
|
||||
inputInstance.value?.$el.classList.remove("ion-invalid");
|
||||
|
||||
if (value === "") {
|
||||
return false;
|
||||
}
|
||||
const isEmailValid = emailPattern.test(model.value);
|
||||
|
||||
isEmailValid ? inputInstance.value?.$el.classList.add("ion-valid") : inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
|
||||
return isEmailValid;
|
||||
}
|
||||
|
||||
async function submitSendVerification() {
|
||||
const isEmailValid = validate(model.value);
|
||||
if (!isEmailValid) {
|
||||
inputInstance.value?.$el.classList.remove("ion-invalid");
|
||||
inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
inputInstance.value?.$el.classList.add("ion-touched");
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
|
||||
email: model.value, // required
|
||||
type: "sign-in", // required
|
||||
});
|
||||
if (data?.success) {
|
||||
emit("success", model.value);
|
||||
}
|
||||
else {
|
||||
const toast = await toastController.create({
|
||||
message: error?.message || t("auth.common.failedSendCode"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="title">
|
||||
<strong>{{ t('auth.login.title') }}</strong>
|
||||
</h1>
|
||||
|
||||
<ui-input-label
|
||||
ref="inputInstance"
|
||||
v-model="model"
|
||||
:label="t('auth.common.email')"
|
||||
:placeholder="t('auth.common.enterEmail')"
|
||||
type="email"
|
||||
:error-text="t('auth.common.validEmailError')"
|
||||
@ion-input="validate($event.target.value as string)"
|
||||
@ion-blur="markTouched"
|
||||
/>
|
||||
|
||||
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSendVerification">
|
||||
{{ t('auth.common.next') }}
|
||||
</ion-button>
|
||||
|
||||
<ui-divider :text="t('auth.common.orContinueWith')" />
|
||||
|
||||
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
|
||||
<IonIcon slot="start" aria-hidden="true" :icon="logoGoogle" />
|
||||
{{ t('auth.common.google') }}
|
||||
</ion-button>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,45 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import { toastController } from "@ionic/vue";
|
||||
|
||||
const props = defineProps<{
|
||||
email: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "success", value: string): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const model = defineModel({ type: String, required: true });
|
||||
|
||||
async function submitSignup() {
|
||||
if (model.value.length !== 6) {
|
||||
const toast = await toastController.create({
|
||||
message: t("auth.common.validVerificationCodeError"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
return;
|
||||
}
|
||||
|
||||
emit("success", model.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1><strong>{{ t('auth.verification.title') }}</strong></h1>
|
||||
<p>{{ t('auth.verification.description', { email: props.email }) }}</p>
|
||||
|
||||
<div>
|
||||
<ion-input-otp v-model="model" :length="6" />
|
||||
|
||||
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSignup">
|
||||
{{ t('auth.login.loginButton') }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,55 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import { modalController } from "@ionic/vue";
|
||||
import { authClient } from "@/auth";
|
||||
import Step1 from "./email/step1.vue";
|
||||
import Step2 from "./email/step2.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const form = ref({
|
||||
email: "",
|
||||
verificationCode: "",
|
||||
});
|
||||
const step = ref(1);
|
||||
|
||||
async function closeModal() {
|
||||
await modalController.dismiss();
|
||||
}
|
||||
|
||||
async function submitSignup() {
|
||||
const { data, error } = await authClient.signIn.emailOtp({
|
||||
email: form.value.email,
|
||||
otp: form.value.verificationCode,
|
||||
});
|
||||
if (data?.user) {
|
||||
await modalController.dismiss(data.user);
|
||||
}
|
||||
else {
|
||||
console.error("Login failed", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IonPage>
|
||||
<IonHeader class="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton @click="closeModal">
|
||||
{{ t('auth.common.close') }}
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent :fullscreen="true" class="ion-padding">
|
||||
<Step1 v-if="step === 1" v-model="form.email" @success="step = 2" />
|
||||
<Step2 v-else-if="step === 2" v-model="form.verificationCode" :email="form.email" @success="submitSignup" />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,93 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import type { InputCustomEvent } from "@ionic/vue";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { logoGoogle, phonePortraitOutline } from "ionicons/icons";
|
||||
import { authClient } from "@/auth";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "success", value: string): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const model = defineModel({ type: String, required: true });
|
||||
const inputInstance = useTemplateRef<InputInstance>("inputInstance");
|
||||
|
||||
function markTouched() {
|
||||
inputInstance.value?.$el.classList.add("ion-touched");
|
||||
}
|
||||
function validate(value: string) {
|
||||
inputInstance.value?.$el.classList.remove("ion-valid");
|
||||
inputInstance.value?.$el.classList.remove("ion-invalid");
|
||||
|
||||
if (value === "") {
|
||||
return false;
|
||||
}
|
||||
const isEmailValid = emailPattern.test(model.value);
|
||||
|
||||
isEmailValid ? inputInstance.value?.$el.classList.add("ion-valid") : inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
|
||||
return isEmailValid;
|
||||
}
|
||||
|
||||
async function submitSendVerification() {
|
||||
const isEmailValid = validate(model.value);
|
||||
if (!isEmailValid) {
|
||||
inputInstance.value?.$el.classList.remove("ion-invalid");
|
||||
inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
inputInstance.value?.$el.classList.add("ion-touched");
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
|
||||
email: model.value, // required
|
||||
type: "sign-in", // required
|
||||
});
|
||||
if (data?.success) {
|
||||
emit("success", model.value);
|
||||
}
|
||||
else {
|
||||
const toast = await toastController.create({
|
||||
message: error?.message || t("auth.common.failedSendCode"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1><strong>{{ t('auth.signup.title') }}</strong></h1>
|
||||
<p>{{ t('auth.signup.description') }}</p>
|
||||
|
||||
<div>
|
||||
<ui-input
|
||||
ref="inputInstance"
|
||||
v-model="model"
|
||||
type="email"
|
||||
:placeholder="t('auth.signup.emailPlaceholder')"
|
||||
:error-text="t('auth.common.validEmailError')"
|
||||
@ion-input="validate($event.target.value as string)"
|
||||
@ion-blur="markTouched"
|
||||
/>
|
||||
|
||||
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSendVerification">
|
||||
{{ t('auth.signup.signupButton') }}
|
||||
</ion-button>
|
||||
|
||||
<ui-divider :text="t('auth.common.orContinueWith')" />
|
||||
|
||||
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
|
||||
<IonIcon slot="start" aria-hidden="true" :icon="phonePortraitOutline" />
|
||||
{{ t('auth.common.phoneNumber') }}
|
||||
</ion-button>
|
||||
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
|
||||
<IonIcon slot="start" aria-hidden="true" :icon="logoGoogle" />
|
||||
{{ t('auth.common.google') }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,49 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import type { PropType } from "vue";
|
||||
import type { AuthUserSignup } from "@/auth/type";
|
||||
import { toastController } from "@ionic/vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "success", value: AuthUserSignup): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const model = defineModel({ type: Object as PropType<AuthUserSignup>, required: true });
|
||||
|
||||
async function submitSignup() {
|
||||
if (model.value.verificationCode.length !== 6) {
|
||||
const toast = await toastController.create({
|
||||
message: t("auth.common.validVerificationCodeError"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
return;
|
||||
}
|
||||
|
||||
emit("success", model.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1><strong>{{ t('auth.verification.title') }}</strong></h1>
|
||||
<p>{{ t('auth.verification.description', { email: model.email }) }}</p>
|
||||
|
||||
<div>
|
||||
<ion-input-otp v-model="model.verificationCode" :length="6" />
|
||||
<!--
|
||||
<ui-input v-model="model.name" placeholder="Name" />
|
||||
|
||||
<ui-input v-model="model.password" placeholder="Password" />
|
||||
|
||||
<ui-input v-model="model.confirmPassword" placeholder="Confirm Password" /> -->
|
||||
|
||||
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSignup">
|
||||
{{ t('auth.common.submit') }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import { modalController } from "@ionic/vue";
|
||||
import VerificationCode from "./verification-code.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
async function closeModal() {
|
||||
await modalController.dismiss();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IonPage>
|
||||
<IonHeader class="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton @click="closeModal">
|
||||
{{ t('auth.common.close') }}
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent :fullscreen="true" class="ion-padding">
|
||||
<VerificationCode />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
</template>
|
||||
@@ -1,63 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import type { AuthUserSignup } from "@/auth/type";
|
||||
import { modalController, toastController } from "@ionic/vue";
|
||||
import MaterialIconThemeGoogle from "~icons/material-icon-theme/google";
|
||||
import { authClient } from "@/auth";
|
||||
import Step1 from "./email/step1.vue";
|
||||
import Step2 from "./email/step2.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const form = ref<AuthUserSignup>({
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
verificationCode: "",
|
||||
});
|
||||
const step = ref(1);
|
||||
|
||||
function reset() {
|
||||
step.value = 1;
|
||||
form.value.name = "";
|
||||
form.value.email = "";
|
||||
form.value.password = "";
|
||||
form.value.confirmPassword = "";
|
||||
form.value.verificationCode = "";
|
||||
}
|
||||
|
||||
async function submitSignup() {
|
||||
const { data, error } = await authClient.signIn.emailOtp({
|
||||
email: form.value.email,
|
||||
otp: form.value.verificationCode,
|
||||
});
|
||||
if (data?.user) {
|
||||
reset();
|
||||
step.value = 1;
|
||||
await modalController.dismiss(data.user);
|
||||
const toast = await toastController.create({
|
||||
message: t("auth.signup.emailVerified"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
}
|
||||
else {
|
||||
const toast = await toastController.create({
|
||||
message: error?.message || t("auth.common.failedVerifyCode"),
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Step1 v-if="step === 1" v-model="form.email" @success="step = 2" />
|
||||
<Step2 v-else-if="step === 2" v-model="form" @success="submitSignup" />
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
@@ -1,34 +1,53 @@
|
||||
import { modalController } from "@ionic/vue";
|
||||
import { emailOTPClient } from "better-auth/client/plugins";
|
||||
import type { PhoneCountry } from "./type";
|
||||
import { emailOTPClient, phoneNumberClient, usernameClient } from "better-auth/client/plugins";
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
import CircleFlagsCnHk from "~icons/circle-flags/cn-hk";
|
||||
import CircleFlagsEnUs from "~icons/circle-flags/en-us";
|
||||
import CircleFlagsTw from "~icons/circle-flags/tw";
|
||||
import CircleFlagsZh from "~icons/circle-flags/zh";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
fetchOptions: {
|
||||
credentials: "include",
|
||||
},
|
||||
plugins: [emailOTPClient()],
|
||||
plugins: [emailOTPClient(), phoneNumberClient(), usernameClient()],
|
||||
});
|
||||
|
||||
export async function modelControllerSignup(presentingElement?: HTMLElement) {
|
||||
const SignupContent = await import("./components/signup/index.vue");
|
||||
|
||||
const modal = await modalController.create({
|
||||
component: SignupContent.default,
|
||||
presentingElement,
|
||||
canDismiss: async (data, role) => role !== "gesture",
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
export async function modelControllerLogin(presentingElement?: HTMLElement) {
|
||||
const LoginContent = await import("./components/login/index.vue");
|
||||
|
||||
const modal = await modalController.create({
|
||||
component: LoginContent.default,
|
||||
presentingElement,
|
||||
canDismiss: async (data, role) => role !== "gesture",
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
export const countries: PhoneCountry[] = [
|
||||
{
|
||||
code: "CN",
|
||||
dialCode: "+86",
|
||||
name: "中国大陆",
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
maxLength: 11,
|
||||
placeholder: "13800138000",
|
||||
icon: CircleFlagsZh,
|
||||
},
|
||||
{
|
||||
code: "HK",
|
||||
dialCode: "+852",
|
||||
name: "中国香港",
|
||||
pattern: /^[5-9]\d{7}$/,
|
||||
maxLength: 8,
|
||||
placeholder: "51234567",
|
||||
icon: CircleFlagsCnHk,
|
||||
},
|
||||
{
|
||||
code: "TW",
|
||||
dialCode: "+886",
|
||||
name: "中国台湾",
|
||||
pattern: /^9\d{8}$/,
|
||||
maxLength: 9,
|
||||
placeholder: "912345678",
|
||||
icon: CircleFlagsTw,
|
||||
},
|
||||
{
|
||||
code: "US",
|
||||
dialCode: "+1",
|
||||
name: "美国",
|
||||
pattern: /^\d{10}$/,
|
||||
maxLength: 10,
|
||||
placeholder: "2025550123",
|
||||
icon: CircleFlagsEnUs,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { FunctionalComponent, SVGAttributes } from "vue";
|
||||
|
||||
export interface AuthUserSignup {
|
||||
name: string;
|
||||
email: string;
|
||||
@@ -5,3 +7,13 @@ export interface AuthUserSignup {
|
||||
confirmPassword: string;
|
||||
verificationCode: string;
|
||||
}
|
||||
|
||||
export interface PhoneCountry {
|
||||
code: string;
|
||||
dialCode: string;
|
||||
name: string;
|
||||
pattern: RegExp;
|
||||
maxLength: number;
|
||||
placeholder: string;
|
||||
icon: FunctionalComponent<SVGAttributes>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user