247 lines
7.9 KiB
Vue
247 lines
7.9 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"],
|
|
});
|
|
|
|
// state
|
|
|
|
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 new Promise((resolve) => setTimeout(resolve, 1000));
|
|
await refetchAccount();
|
|
|
|
addToast({
|
|
message: "با موفقیت وارد شدید",
|
|
options: {
|
|
status: "success",
|
|
},
|
|
});
|
|
|
|
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-[400px] 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-8">شماره خود را وارد کنید</h1>
|
|
|
|
<form
|
|
@submit.prevent
|
|
class="max-w-[500px] w-full mt-12"
|
|
>
|
|
<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"
|
|
/>
|
|
|
|
<Button
|
|
data-testid="send-otp-code-button"
|
|
v-if="!showOtp"
|
|
class="rounded-full w-full mt-4 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-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>
|