feat: 重构登录和注册流程,添加邮箱验证和验证码输入组件,优化样式
This commit is contained in:
@@ -1,51 +0,0 @@
|
|||||||
<script lang='ts' setup>
|
|
||||||
import { modalController } from "@ionic/vue";
|
|
||||||
import { logoGoogle } from "ionicons/icons";
|
|
||||||
|
|
||||||
async function closeModal() {
|
|
||||||
await modalController.dismiss();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<IonPage>
|
|
||||||
<IonHeader class="ion-no-border">
|
|
||||||
<IonToolbar>
|
|
||||||
<IonButtons slot="start">
|
|
||||||
<IonButton @click="closeModal">
|
|
||||||
Close
|
|
||||||
</IonButton>
|
|
||||||
</IonButtons>
|
|
||||||
</IonToolbar>
|
|
||||||
</IonHeader>
|
|
||||||
<IonContent :fullscreen="true" class="ion-padding">
|
|
||||||
<h1 class="title">
|
|
||||||
<strong>Log in</strong>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<ui-input-label
|
|
||||||
label="Email"
|
|
||||||
type="email"
|
|
||||||
placeholder="Enter your email"
|
|
||||||
clear-input
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ion-button expand="block" class="ion-margin-top" shape="round">
|
|
||||||
Next
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ui-divider text="Or continue with" />
|
|
||||||
|
|
||||||
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
|
|
||||||
<IonIcon slot="start" aria-hidden="true" :icon="logoGoogle" />
|
|
||||||
Google
|
|
||||||
</ion-button>
|
|
||||||
</IonContent>
|
|
||||||
</IonPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.title {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
87
src/auth/components/login/email/step1.vue
Normal file
87
src/auth/components/login/email/step1.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<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 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 || "Failed to send verification code.",
|
||||||
|
duration: 1500,
|
||||||
|
position: "bottom",
|
||||||
|
});
|
||||||
|
|
||||||
|
await toast.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1 class="title">
|
||||||
|
<strong>Log in</strong>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<ui-input-label
|
||||||
|
ref="inputInstance"
|
||||||
|
v-model="model"
|
||||||
|
label="Email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
type="email"
|
||||||
|
error-text="Please enter a valid email address."
|
||||||
|
@ion-input="validate($event.target.value as string)"
|
||||||
|
@ion-blur="markTouched"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSendVerification">
|
||||||
|
Next
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
|
<ui-divider text="Or continue with" />
|
||||||
|
|
||||||
|
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
|
||||||
|
<IonIcon slot="start" aria-hidden="true" :icon="logoGoogle" />
|
||||||
|
Google
|
||||||
|
</ion-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
41
src/auth/components/login/email/step2.vue
Normal file
41
src/auth/components/login/email/step2.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang='ts' setup>
|
||||||
|
import { toastController } from "@ionic/vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
email: string;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "success", value: string): void;
|
||||||
|
}>();
|
||||||
|
const model = defineModel({ type: String, required: true });
|
||||||
|
|
||||||
|
async function submitSignup() {
|
||||||
|
if (model.value.length !== 6) {
|
||||||
|
const toast = await toastController.create({
|
||||||
|
message: "Please enter a valid 6-digit verification code.",
|
||||||
|
duration: 1500,
|
||||||
|
position: "bottom",
|
||||||
|
});
|
||||||
|
|
||||||
|
await toast.present();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("success", model.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1><strong>Verify your email</strong></h1>
|
||||||
|
<p>We have sent a verification code to {{ email }}. Please enter the code below to verify your email address.</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ion-input-otp v-model="model" :length="6" />
|
||||||
|
|
||||||
|
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSignup">
|
||||||
|
Log in
|
||||||
|
</ion-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
53
src/auth/components/login/index.vue
Normal file
53
src/auth/components/login/index.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<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 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">
|
||||||
|
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,8 +1,8 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
import type { AuthUserSignup } from "../type";
|
import type { AuthUserSignup } from "@/auth/type";
|
||||||
import { toastController } from "@ionic/vue";
|
import { modalController, toastController } from "@ionic/vue";
|
||||||
import MaterialIconThemeGoogle from "~icons/material-icon-theme/google";
|
import MaterialIconThemeGoogle from "~icons/material-icon-theme/google";
|
||||||
import { authClient } from "..";
|
import { authClient } from "@/auth";
|
||||||
import Step1 from "./email/step1.vue";
|
import Step1 from "./email/step1.vue";
|
||||||
import Step2 from "./email/step2.vue";
|
import Step2 from "./email/step2.vue";
|
||||||
|
|
||||||
@@ -29,9 +29,10 @@ async function submitSignup() {
|
|||||||
email: form.value.email,
|
email: form.value.email,
|
||||||
otp: form.value.verificationCode,
|
otp: form.value.verificationCode,
|
||||||
});
|
});
|
||||||
if (data?.token) {
|
if (data?.user) {
|
||||||
reset();
|
reset();
|
||||||
step.value = 1;
|
step.value = 1;
|
||||||
|
await modalController.dismiss(data.user);
|
||||||
const toast = await toastController.create({
|
const toast = await toastController.create({
|
||||||
message: "Email verified successfully!",
|
message: "Email verified successfully!",
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
@@ -10,7 +10,7 @@ export const authClient = createAuthClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function modelControllerSignup(presentingElement?: HTMLElement) {
|
export async function modelControllerSignup(presentingElement?: HTMLElement) {
|
||||||
const SignupContent = await import("./components/signup.vue");
|
const SignupContent = await import("./components/signup/index.vue");
|
||||||
|
|
||||||
const modal = await modalController.create({
|
const modal = await modalController.create({
|
||||||
component: SignupContent.default,
|
component: SignupContent.default,
|
||||||
@@ -22,7 +22,7 @@ export async function modelControllerSignup(presentingElement?: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function modelControllerLogin(presentingElement?: HTMLElement) {
|
export async function modelControllerLogin(presentingElement?: HTMLElement) {
|
||||||
const LoginContent = await import("./components/login.vue");
|
const LoginContent = await import("./components/login/index.vue");
|
||||||
|
|
||||||
const modal = await modalController.create({
|
const modal = await modalController.create({
|
||||||
component: LoginContent.default,
|
component: LoginContent.default,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
http://ionicframework.com/docs/theming/ */
|
http://ionicframework.com/docs/theming/ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--ui-input-background: #ffffff;
|
--ui-input-background: #efefef;
|
||||||
--ui-input-color: #222222;
|
--ui-input-color: #222222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user