otp request + otp verify + component toast

This commit is contained in:
hamid zarghami
2025-11-11 12:35:44 +03:30
parent 04f3b3b9b5
commit bd2f5b33c1
16 changed files with 488 additions and 654 deletions
+14
View File
@@ -0,0 +1,14 @@
import { useMutation } from "@tanstack/react-query";
import * as api from "../service/LoginService";
export const useOtpRequest = () => {
return useMutation({
mutationFn: api.loginOTP,
});
};
export const useOtpVerify = () => {
return useMutation({
mutationFn: api.loginVerifyOTP,
});
};
+12 -158
View File
@@ -1,169 +1,23 @@
'use client'
import React, {
useState,
type FormEvent,
type ChangeEvent,
useEffect,
useTransition
} from 'react'
import { useAuthStore } from '@/zustand/authStore'
import { redirect, useParams, useRouter } from 'next/navigation'
import StepEnterPassword from '@/features/auth/components/StepEnterPassword'
import React from 'react'
import StepEnterNumber from '@/features/auth/components/StepEnterNumber'
import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums'
import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper'
import { useLogin } from '@/hooks/auth/useLogin'
import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists'
import { useAuthTempStore } from '@/zustand/authTempStroe'
type Props = object
function AuthIndex ({}: Props) {
const update_temp_store = useAuthTempStore(state => state.update)
const user_temp_store = useAuthTempStore(state => state.user)
const [number, setNumber] = useState(user_temp_store?.number ?? '')
const [password, setPassword] = useState('')
const [showPassword, setShowPassword] = useState(false)
const [rememberMe, setRememberMe] = useState(false)
const [step, setStep] = useState(
number == '' ? AUTH_STEP.ENTER_NUMBER : AUTH_STEP.ENTER_CURRENT_PASSWORD
)
const [isPending, startTransition] = useTransition()
const isAuthenticated = useAuthStore(state => state.isAuthenticated)
const loginMutation = useLogin()
const { run: runUserExistCheck } = useCheckUserExistsLazy()
const router = useRouter()
const { name } = useParams()
const basePath = `/${name ?? ''}`
useEffect(() => {
if (isAuthenticated) {
redirect(basePath)
}
}, [isAuthenticated, basePath])
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_PHONE) {
setNumber(() => e.target.value)
} else if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_PASSWORD) {
setPassword(() => e.target.value)
} else if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_REMEMBER_ME) {
setRememberMe(state => !state)
}
}
const onClick = async (
e:
| React.MouseEvent<HTMLButtonElement, MouseEvent>
| React.MouseEvent<HTMLInputElement, MouseEvent>
) => {
e.stopPropagation()
const target = e.target as HTMLButtonElement
if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) {
setShowPassword(state => !state)
}
}
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
startTransition(async () => {
try {
/* DEV SECTION */
/* REMOVE THIS CODE FOR PRODUCTION */
throw false
/* END OF DEV SECTION */
/* SECTION DETAILS */
/* if number is entered in the /auth page this page directly shows the password page */
/* Otherwise it shows the number page */
if (step == AUTH_STEP.ENTER_NUMBER) {
const userExists = await runUserExistCheck(number)
update_temp_store({
number: number,
mustLogin: userExists
})
if (userExists) {
setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD)
} else {
router.replace(`${basePath}/auth/signup`)
}
} else if (
step == AUTH_STEP.ENTER_OTP ||
step == AUTH_STEP.ENTER_CREATE_PASSWORD ||
step == AUTH_STEP.ENTER_RESET_PASSWORD
) {
setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD)
} else if (step == AUTH_STEP.ENTER_CURRENT_PASSWORD) {
update_temp_store({
password,
rememberMe
})
try {
await loginMutation.mutateAsync({ phone: number, password })
console.log('Signed in')
router.replace(basePath)
} catch (e) {
console.error('Could not Login: ', e)
}
}
} catch (ex) {
console.error(ex)
// TODO: Add toast errors
/* DEV SECTION */
/* REMOVE THIS CODE FOR PRODUCTION */
if (step == AUTH_STEP.ENTER_NUMBER) {
update_temp_store({
number,
mustLogin: !number.endsWith('0')
})
setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD)
} else if (
step == AUTH_STEP.ENTER_OTP ||
step == AUTH_STEP.ENTER_CREATE_PASSWORD ||
step == AUTH_STEP.ENTER_RESET_PASSWORD
) {
setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD)
} else if (step == AUTH_STEP.ENTER_CURRENT_PASSWORD) {
update_temp_store({
password,
rememberMe
})
try {
console.log('Signed in')
router.replace(basePath)
} catch (e) {
console.error('Could not Login: ', e)
}
}
/* END OF DEV SECTION */
}
})
}
function AuthIndex({ }: Props) {
return (
<AuthFormWrapper isPending={isPending} onSubmit={onSubmit}>
{step === AUTH_STEP.ENTER_NUMBER && (
<StepEnterNumber
pending={isPending}
onChange={onChange}
value={number}
/>
)}
{step === AUTH_STEP.ENTER_CURRENT_PASSWORD && (
<StepEnterPassword
pending={isPending}
onChange={onChange}
onClick={onClick}
value={password}
passwordVisible={showPassword}
rememberMe={rememberMe}
/>
)}
<AuthFormWrapper isPending={false} onSubmit={() => undefined}>
<StepEnterNumber />
{/* <StepEnterPassword
pending={false}
onChange={() => undefined}
onClick={() => undefined}
value=''
passwordVisible={false}
rememberMe={false}
/> */}
</AuthFormWrapper>
)
}
@@ -0,0 +1,12 @@
import { api } from "@/lib/api/axiosInstance";
import { LoginOTPRequestType, LoginVerifyOTPType } from "../types/Types";
export const loginOTP = async (params: LoginOTPRequestType) => {
const { data } = await api.post("/auth/otp/request", params);
return data;
};
export const loginVerifyOTP = async (params: LoginVerifyOTPType) => {
const { data } = await api.post("/auth/otp/verify", params);
return data;
};
+9
View File
@@ -0,0 +1,9 @@
export type LoginOTPRequestType = {
phone: string;
slug: string;
};
export type LoginVerifyOTPType = {
phone: string;
otp: string;
};