Files
hossein-por-shop/frontend/pages/signin.vue
T
2026-05-05 20:22:46 +03:30

269 lines
8.7 KiB
Vue

<script lang="ts" setup>
// import
import { helpers, required } from "@vuelidate/validators";
import { useVuelidate } from "@vuelidate/core";
import useOtp from "~/composables/api/auth/useOtp";
import useSignIn from "~/composables/api/auth/useSignIn";
import { definePageMeta } from "#imports";
import useGetAccount from "~/composables/api/account/useGetAccount";
import { useAuth } from "~/composables/api/auth/useAuth";
import { useToast } from "~/composables/global/useToast";
import { useTimer } from "~/composables/global/useTimer";
// types
type LoginInfo = {
phone: string;
};
// meta
definePageMeta({
layout: "none",
middleware: ["check-is-not-logged-in"],
});
// seo
useSeoMeta({
title: "ورود به فروشگاه",
});
// state
const route = useRoute();
const { addToast } = useToast();
const { updateToken, updateRefreshToken } = useAuth();
const { refetch: refetchAccount } = useGetAccount();
const showOtp = ref(false);
const otpCode = ref([]);
const formRules = computed(() => {
return {
phone: {
required: helpers.withMessage("Phone is required", required),
phoneValidator: helpers.withMessage("شماره تلفن وارد شده معتبر نمی باشد", helpers.regex(/^[1-9][0-9]{9}$/)),
},
};
});
const loginInfo = ref<LoginInfo>({
phone: "",
});
const formValidator$ = useVuelidate(formRules, loginInfo);
const {
timer: otpBlockerTimePassed,
start: startOtpBlocker,
reset: resetOtpBlocker,
isPending: isResendOtpBlocked,
} = useTimer({
duration: 5000,
});
const { mutateAsync: sendOtp, isPending: sendOtpIsPending } = useOtp();
const { mutateAsync: signIn, isPending: signInIsPending, status: signInStatus } = useSignIn();
// computed
const sendOtpHandler = async () => {
if (!sendOtpIsPending.value) {
try {
await sendOtp({
phone: `0${loginInfo.value.phone}`,
});
addToast({
message: "کد برای شما ارسال شد",
options: {
status: "success",
},
});
showOtp.value = true;
} catch (e) {
addToast({
message: "مشکلی پیش آمده",
options: {
status: "error",
},
});
}
}
};
const handleLogin = async () => {
if (!showOtp.value) {
await formValidator$.value.$validate();
if (!formValidator$.value.$errors.length) {
await sendOtpHandler();
}
} else {
try {
const response = await signIn({
otp: otpCode.value.join(""),
phone: `0${loginInfo.value.phone}`,
});
updateToken(response.access);
updateRefreshToken(response.refresh);
await refetchAccount();
addToast({
message: "با موفقیت وارد شدید",
options: {
status: "success",
},
});
if (route.query.hasOwnProperty("cb_url")) {
window.location.href = route.query["cb_url"] as string;
} else {
window.location.href = "/";
}
} catch (e) {
otpCode.value = [];
addToast({ message: "مشکلی پیش آمده" });
}
}
};
const resendOtp = async () => {
try {
await sendOtpHandler();
resetOtpBlocker();
startOtpBlocker();
} catch (e) {
resetOtpBlocker();
}
};
const resetForm = () => {
loginInfo.value.phone = "";
formValidator$.value.$reset();
otpCode.value = [];
showOtp.value = false;
};
</script>
<template>
<div class="w-full flex h-svh items-center relative container">
<div class="pattern -z-10 size-full fixed inset-0" />
<div class="flex items-center justify-center flex-col size-full translate-y-[-100px]">
<img
class="aspect-square w-[250px] sm:w-[325px] translate-y-[90px] sm:translate-y-[120px] animate-fade-in"
src="/img/heymlz/heymlz-signin.gif"
:style="{
filter: 'drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.15))',
}"
/>
<div
class="max-w-[600px] w-full p-6 h-[350px] sm:h-[430px] flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl"
>
<h1 class="typo-h-6 sm:typo-h-5 mt-12">
{{ showOtp ? "کد ارسالی را وارد کنید" : "شماره خود را وارد کنید" }}
</h1>
<form
@submit.prevent
class="max-w-[500px] w-full mt-10"
>
<Input
v-if="!showOtp"
class="w-full tracking-[3px] persian-number"
v-model="loginInfo.phone"
placeholder="۹۳۸۰۱۲۳۴۵۶"
dir="ltr"
:error="formValidator$.phone.$error"
>
<template #startItem>
<div class="flex items-center gap-3">
<Icon
class="translate-y-[-1px] static-icon"
name="twemoji:flag-iran"
size="24"
/>
<span class="text-slate-500 typo-label-sm"> +۹۸ </span>
</div>
</template>
</Input>
<OtpInput
v-else
v-model="otpCode"
:status="signInStatus === 'success' ? 'success' : signInStatus === 'error' ? 'error' : 'idle'"
:disabled="signInIsPending || sendOtpIsPending"
:autofocus="true"
@complete="handleLogin"
/>
<div
class="flex justify-center gap-2 px-4 text-slate-500/80 rounded-lg mt-4"
>
<span class="text-xs lg:text-sm"> لطفا حروف را با کیبورد انگلیسی وارد کنید </span>
<Icon
name="ci:bi-info-circle"
class="lg:text-xl"
/>
</div>
<Button
data-testid="send-otp-code-button"
v-if="!showOtp"
class="rounded-full w-full mt-6 sm:mt-10 max-sm:h-[45px]"
type="submit"
@click="handleLogin"
:loading="sendOtpIsPending"
:disabled="sendOtpIsPending"
>
ارسال کد
</Button>
<div
v-else
class="flex items-center w-full gap-4 mt-2 sm:mt-4"
>
<Button
class="rounded-full w-full mt-4 max-sm:h-[45px]"
type="button"
variant="secondary"
@click="resetForm"
:disabled="signInIsPending || sendOtpIsPending"
>
تغییر شماره
</Button>
<Button
class="rounded-full w-full mt-4 max-sm:h-[45px]"
type="submit"
@click="resendOtp"
:loading="signInIsPending || sendOtpIsPending"
:disabled="signInIsPending || isResendOtpBlocked || sendOtpIsPending"
>
ارسال مجدد کد
{{ isResendOtpBlocked ? otpBlockerTimePassed : "" }}
</Button>
</div>
<NuxtLink
to="/"
class="flex items-center gap-2 justify-center mt-6"
>
<Icon
name="ci:left-rotation"
class="lg:text-xl"
/>
<span class="text-xs lg:text-sm"> بازگشت به فروشگاه </span>
</NuxtLink>
</form>
</div>
</div>
</div>
</template>