feat: add email sign up

This commit is contained in:
2025-12-11 22:32:23 +07:00
parent 8e88f6a44c
commit 7afdf84ef1
8 changed files with 192 additions and 27 deletions

View File

@@ -1,31 +1,25 @@
<script lang='ts' setup>
import { modalController } from "@ionic/vue";
import VerifyCode from "./verify-code.vue";
const name = ref();
const cancel = () => modalController.dismiss(null, "cancel");
const confirm = () => modalController.dismiss(name.value, "confirm");
async function closeModal() {
await modalController.dismiss();
}
</script>
<template>
<IonHeader>
<IonPage>
<IonHeader class="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonButton color="medium" @click="cancel">
Cancel
</IonButton>
</IonButtons>
<IonTitle>Modal</IonTitle>
<IonButtons slot="end">
<IonButton :strong="true" @click="confirm">
Confirm
<IonButton @click="closeModal">
Close
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<IonItem>
<IonInput v-model="name" label-placement="stacked" label="Enter your name" placeholder="Your name" />
</IonItem>
<IonContent :fullscreen="true" class="ion-padding">
<VerifyCode />
</IonContent>
</IonPage>
</template>

View File

@@ -0,0 +1,139 @@
<script lang='ts' setup>
import type { AuthUserSignup } from "../type";
import { toastController } from "@ionic/vue";
import { logoGoogle, phonePortraitOutline } from "ionicons/icons";
import { emailPattern } from "@/utils";
import { authClient } from "..";
const form = ref<AuthUserSignup>({
email: "",
password: "",
confirmPassword: "",
verificationCode: "",
});
const step = ref(1);
function onEmailBlur() {
const isEmailValid = emailPattern.test(form.value.email);
if (!isEmailValid) {
toastController
.create({
message: "Please enter a valid email address.",
duration: 1500,
position: "bottom",
})
.then((toast) => {
toast.present();
});
}
}
async function submitSendVerification() {
const isEmailValid = emailPattern.test(form.value.email);
if (!isEmailValid) {
const toast = await toastController.create({
message: "Please enter a valid email address.",
duration: 1500,
position: "bottom",
});
await toast.present();
return;
}
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
email: form.value.email, // required
type: "sign-in", // required
});
if (data?.success) {
step.value = 2;
}
else {
const toast = await toastController.create({
message: error?.message || "Failed to send verification code.",
duration: 1500,
position: "bottom",
});
await toast.present();
}
}
function reset() {
step.value = 1;
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?.token) {
reset();
step.value = 1;
const toast = await toastController.create({
message: "Email verified successfully!",
duration: 1500,
position: "bottom",
});
await toast.present();
}
else {
const toast = await toastController.create({
message: error?.message || "Failed to verify the code.",
duration: 1500,
position: "bottom",
});
await toast.present();
}
}
</script>
<template>
<div v-if="step === 1">
<h1><strong>What's your email?</strong></h1>
<p>You'll use this email to login and access everything we have to offer.</p>
<div>
<ion-item>
<ion-input v-model="form.email" type="email" placeholder="Enter your email address" @ion-blur="onEmailBlur" />
</ion-item>
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSendVerification">
Sign up
</ion-button>
<divider text="Or continue with" />
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
<IonIcon slot="start" aria-hidden="true" :icon="phonePortraitOutline" />
Phone Number
</ion-button>
<ion-button color="medium" expand="block" class="ion-margin-top" shape="round">
<IonIcon slot="start" aria-hidden="true" :icon="logoGoogle" />
Google
</ion-button>
</div>
</div>
<div v-else-if="step === 2">
<h1><strong>Verify your email</strong></h1>
<p>We have sent a verification code to {{ form.email }}. Please enter the code below to verify your email address.</p>
<div>
<ion-item>
<ion-input-otp v-model="form.verificationCode" :length="6" />
</ion-item>
<ion-button expand="block" class="ion-margin-top" shape="round" @click="submitSignup">
Submit
</ion-button>
</div>
</div>
</template>
<style lang='css' scoped></style>

View File

@@ -1,12 +1,19 @@
import { modalController } from "@ionic/vue";
import { emailOTPClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/vue";
import SignupContent from "./components/signup.vue";
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_BASE_API_URL, // The base URL of your auth server
plugins: [emailOTPClient()],
});
export function signupController() {
return modalController.create({
component: () => import("./components/signup.vue"),
export async function modelControllerSignup(presentingElement?: HTMLElement) {
const modal = await modalController.create({
component: SignupContent,
presentingElement,
canDismiss: async (data, role) => role !== "gesture",
});
return modal;
}

6
src/auth/type.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface AuthUserSignup {
email: string;
password: string;
confirmPassword: string;
verificationCode: string;
}

View File

@@ -0,0 +1,15 @@
import { authClient } from "@/auth";
export function useAuth() {
// Better Auth 提供的 session
const session = authClient.useSession();
const user = computed(() => session.value.data?.user);
const isAuthenticated = computed(() => !!session.value.data);
return {
user,
session,
isAuthenticated,
};
}

2
src/utils/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./ionic-helper";
export * from "./pattern";

View File

@@ -0,0 +1 @@
export type PageInstance = InstanceType<typeof import("@ionic/vue").IonPage>;

1
src/utils/pattern.ts Normal file
View File

@@ -0,0 +1 @@
export const emailPattern = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;