From ef91469766234635ee74b08891eed04d0ec507d9 Mon Sep 17 00:00:00 2001 From: Mahyar Khanbolooki Date: Thu, 21 Aug 2025 20:24:31 +0330 Subject: [PATCH] fix: seperated auth pages --- src/app/auth/forgot-password/page.tsx | 251 ++++++++++++++++ src/app/auth/login/page.tsx | 169 +++++++++++ src/app/auth/page.tsx | 268 +++++------------- src/app/auth/signup/page.tsx | 241 ++++++++++++++++ src/enums/authStep.ts | 4 +- .../auth/components/StepEnterPassword.tsx | 7 +- src/zustand/authTempStroe.ts | 42 +++ 7 files changed, 787 insertions(+), 195 deletions(-) create mode 100644 src/app/auth/forgot-password/page.tsx create mode 100644 src/app/auth/login/page.tsx create mode 100644 src/app/auth/signup/page.tsx create mode 100644 src/zustand/authTempStroe.ts diff --git a/src/app/auth/forgot-password/page.tsx b/src/app/auth/forgot-password/page.tsx new file mode 100644 index 0000000..47b1443 --- /dev/null +++ b/src/app/auth/forgot-password/page.tsx @@ -0,0 +1,251 @@ +'use client' + +import React, { + useState, + type FormEvent, + type ChangeEvent, + useEffect, + useTransition +} from 'react' +import { useAuthStore } from '@/zustand/authStore' +import { redirect, useRouter } from 'next/navigation' +import StepEnterNumber from '@/features/auth/components/StepEnterNumber' +import StepEnterOtp from '@/features/auth/components/StepEnterOtp' +import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums' +import { useCountdown } from '@/hooks/useCountdown' +import StepNewPassword from '@/features/auth/components/StepNewPassword' +import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper' +import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists' +import { useOtpRequest } from '@/hooks/auth/useOtpRequest' +import { useOtpValidation } from '@/hooks/auth/useOtpValidation' +import { useResetPassword } from '@/hooks/auth/useResetPassword' +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 user_auth_store = useAuthStore(state => state.user) + const isAuthenticated = useAuthStore(state => state.isAuthenticated) + const [number, setNumber] = useState( + user_temp_store?.number ?? user_auth_store?.number ?? '' + ) + const [password, setPassword] = useState('') + const [passwordRepeat, setPasswordRepeat] = useState('') + const [otp, setOtp] = useState('') + const [showPassword, setShowPassword] = useState(false) + const [showPasswordRepeat, setShowPasswordRepeat] = useState(false) + const [step, setStep] = useState( + number == '' ? AUTH_STEP.ENTER_NUMBER : AUTH_STEP.ENTER_OTP + ) + const { timerRunning, secondsLeft, restart, stop } = useCountdown( + step === AUTH_STEP.ENTER_OTP + ) + const [isPending, startTransition] = useTransition() + + const { run: runUserExistCheck } = useCheckUserExistsLazy() + const { run: runOtpRequest } = useOtpRequest() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { run: runOtpValidation } = useOtpValidation() + const { run: runResetPassword } = useResetPassword() + const router = useRouter() + + useEffect(() => { + if (isAuthenticated) { + redirect('/') + } + }, [isAuthenticated]) + + useEffect(() => { + if (step === AUTH_STEP.ENTER_OTP) { + startTransition(() => { + setOtp('') + console.log('REQUEST OTP') + runOtpRequest(number) + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [step]) + + const onChange = (e: ChangeEvent) => { + 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_PASSWORD_REPEAT) { + setPasswordRepeat(() => e.target.value) + } else if (e.target.name.startsWith(AUTH_PAGE_ELEMENT.INPUT_OTP)) { + const index = +e.target.name.split('-')[2] + const prev = otp.padEnd(6).split('') + prev[index] = e.target.value + const next = prev.join('') + if (otp.length >= 6 && next.trim().length >= otp.length) return + setOtp(next.trimEnd()) + } + } + + const onClick = async ( + e: + | React.MouseEvent + | React.MouseEvent + ) => { + e.stopPropagation() + const target = e.target as HTMLButtonElement + + if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) { + setShowPassword(state => !state) + } + if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_REPEAT_PASSWORD) { + setShowPasswordRepeat(state => !state) + } else if (target.id === AUTH_PAGE_ELEMENT.RESET_PASSWORD) { + setShowPassword(false) + setShowPasswordRepeat(false) + setPassword('') + setPasswordRepeat('') + setStep(() => AUTH_STEP.ENTER_OTP) + } else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) { + if (!timerRunning) { + startTransition(async () => { + try { + await runOtpRequest(number) + restart() + } catch { + console.error('Could not ask for otp') + } + }) + } + } + } + + const onSubmit = async (e: FormEvent) => { + 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 or is available in main auth store this page directly shows the OTP 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_RESET_PASSWORD) + } else { + // TODO: error user not found + } + } else if (step == AUTH_STEP.ENTER_OTP) { + // if (!await runOtpValidation({ phone: number, otp })) { + // setOtp(''); + // console.error('Wrong otp'); + // } + stop() + console.log(await runUserExistCheck(number)) + if (await runUserExistCheck(number)) { + setStep(AUTH_STEP.ENTER_RESET_PASSWORD) + } else { + // TODO: error user not found + } + } else if ( + step == AUTH_STEP.ENTER_CREATE_PASSWORD || + step == AUTH_STEP.ENTER_CURRENT_PASSWORD + ) { + setStep(AUTH_STEP.ENTER_RESET_PASSWORD) + } else if (step == AUTH_STEP.ENTER_RESET_PASSWORD) { + update_temp_store({ + password, + repeatPassword: passwordRepeat + }) + try { + await runResetPassword({ + phone: number, + newPassword: password, + otp: otp + }) + console.log('Password changed') + redirect('/') + } catch (e) { + console.error('Could not reset password: ', 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 + }) + setStep(AUTH_STEP.ENTER_OTP) + } else if (step === AUTH_STEP.ENTER_OTP) { + setStep(AUTH_STEP.ENTER_RESET_PASSWORD) + } else if ( + step == AUTH_STEP.ENTER_CREATE_PASSWORD || + step == AUTH_STEP.ENTER_CURRENT_PASSWORD + ) { + setStep(AUTH_STEP.ENTER_RESET_PASSWORD) + } else if (step == AUTH_STEP.ENTER_RESET_PASSWORD) { + update_temp_store({ + password, + repeatPassword: passwordRepeat + }) + try { + console.log('Password changed') + router.replace('/auth') + } catch (e) { + console.error('Could not reset password: ', e) + } + } + /* END OF DEV SECTION */ + } + }) + } + + return ( + + {step === AUTH_STEP.ENTER_NUMBER && ( + + )} + + {step === AUTH_STEP.ENTER_OTP && ( + + )} + + {step === AUTH_STEP.ENTER_RESET_PASSWORD && ( + + )} + + ) +} + +export default AuthIndex diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx new file mode 100644 index 0000000..4dde800 --- /dev/null +++ b/src/app/auth/login/page.tsx @@ -0,0 +1,169 @@ +'use client' + +import React, { + useState, + type FormEvent, + type ChangeEvent, + useEffect, + useTransition +} from 'react' +import { useAuthStore } from '@/zustand/authStore' +import { redirect, useRouter } from 'next/navigation' +import StepEnterPassword from '@/features/auth/components/StepEnterPassword' +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() + + useEffect(() => { + if (isAuthenticated) { + redirect('/') + } + }, [isAuthenticated]) + + const onChange = (e: ChangeEvent) => { + 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 + | React.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) => { + 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('/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') + redirect('/') + } 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') + redirect('/') + } catch (e) { + console.error('Could not Login: ', e) + } + } + /* END OF DEV SECTION */ + } + }) + } + + return ( + + {step === AUTH_STEP.ENTER_NUMBER && ( + + )} + + {step === AUTH_STEP.ENTER_CURRENT_PASSWORD && ( + + )} + + ) +} + +export default AuthIndex diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx index 1a90788..61b2e1f 100644 --- a/src/app/auth/page.tsx +++ b/src/app/auth/page.tsx @@ -1,207 +1,95 @@ -'use client'; +'use client' -import React, { useState, type FormEvent, type ChangeEvent, useEffect, useTransition } from 'react' -import { useAuthStore } from '@/zustand/authStore'; -import { redirect } from 'next/navigation'; -import StepEnterPassword from '@/features/auth/components/StepEnterPassword'; -import StepEnterNumber from '@/features/auth/components/StepEnterNumber'; -import StepEnterOtp from '@/features/auth/components/StepEnterOtp'; -import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums'; -import { useCountdown } from '@/hooks/useCountdown'; -import StepNewPassword from '@/features/auth/components/StepNewPassword'; -import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper'; -import { useLogin } from '@/hooks/auth/useLogin'; -import { useSignup } from '@/hooks/auth/useSignup'; -import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists'; -import { useOtpRequest } from '@/hooks/auth/useOtpRequest'; -import { useOtpValidation } from '@/hooks/auth/useOtpValidation'; -import { useResetPassword } from '@/hooks/auth/useResetPassword'; +import React, { + useState, + type FormEvent, + type ChangeEvent, + useEffect, + useTransition +} from 'react' +import { useAuthStore } from '@/zustand/authStore' +import { redirect, useRouter } from 'next/navigation' +import StepEnterNumber from '@/features/auth/components/StepEnterNumber' +import { AUTH_PAGE_ELEMENT } from '@/enums' +import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper' +import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists' +import { useAuthTempStore } from '@/zustand/authTempStroe' type Props = object -function AuthIndex({ }: Props) { - const [number, setNumber] = useState(""); - const [password, setPassword] = useState(""); - const [passwordRepeat, setPasswordRepeat] = useState(""); - const [otp, setOtp] = useState(""); - const [showPassword, setShowPassword] = useState(false); - const [showPasswordRepeat, setShowPasswordRepeat] = useState(false); - const [rememberMe, setRememberMe] = useState(false); - const [step, setStep] = useState(AUTH_STEP.ENTER_NUMBER); - const { timerRunning, secondsLeft, restart, stop } = useCountdown(step === AUTH_STEP.ENTER_OTP); - const [isPending, startTransition] = useTransition(); +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 [isPending, startTransition] = useTransition() - const isAuthenticated = useAuthStore((state) => state.isAuthenticated) - const loginMutation = useLogin() - const signupMutation = useSignup() - const { run: runUserExistCheck } = useCheckUserExistsLazy() - const { run: runOtpRequest } = useOtpRequest(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { run: runOtpValidation } = useOtpValidation(); - const { run: runResetPassword } = useResetPassword(); + const isAuthenticated = useAuthStore(state => state.isAuthenticated) + const { run: runUserExistCheck } = useCheckUserExistsLazy() - const resetStates = () => { - setNumber(''); - setPassword(''); - setPasswordRepeat(''); - setOtp(''); - setShowPassword(false); - setShowPasswordRepeat(false); - setRememberMe(false); - stop(); + const router = useRouter() + + useEffect(() => { + if (isAuthenticated) { + redirect('/') } + }, [isAuthenticated]) - useEffect(() => { - if (isAuthenticated) { - redirect("/"); - } - }, [isAuthenticated]) - - useEffect(() => { - if (step === AUTH_STEP.ENTER_OTP) { - startTransition(() => { - setOtp(''); - console.log("REQUEST OTP") - runOtpRequest(number); - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [step]); - - const onChange = (e: ChangeEvent) => { - 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_PASSWORD_REPEAT) { - setPasswordRepeat(() => e.target.value) - } - else if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_REMEMBER_ME) { - setRememberMe((state) => !state) - } - else if (e.target.name.startsWith(AUTH_PAGE_ELEMENT.INPUT_OTP)) { - const index = +e.target.name.split('-')[2]; - const prev = otp.padEnd(6).split(''); - prev[index] = e.target.value; - const next = prev.join(''); - if (otp.length >= 6 && next.trim().length >= otp.length) return; - setOtp(next.trimEnd()); - } + const onChange = (e: ChangeEvent) => { + if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_PHONE) { + setNumber(() => e.target.value) } + } - const onClick = async (e: React.MouseEvent | React.MouseEvent) => { - e.stopPropagation(); - const target = e.target as HTMLButtonElement; + const onSubmit = async (e: FormEvent) => { + e.preventDefault() + startTransition(async () => { + try { + /* DEV SECTION */ + /* REMOVE THIS CODE FOR PRODUCTION */ + throw false + /* EMD OF DEV SECTION */ - if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) { - setShowPassword((state) => !state); + /* SECTION DETAILS */ + /* If user is already registered, go to login page */ + /* Otherwise go to signup page */ + /* We store auth data in a temp auth store so the next page must */ + /* access the phone number entered in this page */ + /* and if it doesnt, it will ask for the number again */ + const userExists = await runUserExistCheck(number) + update_temp_store({ + number: number, + mustLogin: userExists + }) + if (userExists) { + router.push('/auth/login') + } else { + router.push('/auth/signup') } - if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_REPEAT_PASSWORD) { - setShowPasswordRepeat((state) => !state); + } catch (ex) { + console.error(ex) + // TODO: Add toast errors + + /* DEV SECTION */ + /* REMOVE THIS CODE FOR PRODUCTION */ + update_temp_store({ + number: number, + mustLogin: !number.endsWith('0') + }) + if (number.endsWith('0')) { + router.push('/auth/signup') + } else { + router.push('/auth/login') } - else if (target.id === AUTH_PAGE_ELEMENT.RESET_PASSWORD) { - setShowPassword(false); - setShowPasswordRepeat(false); - setPassword(''); - setPasswordRepeat(''); - setStep(() => AUTH_STEP.ENTER_OTP) - } - else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) { - if (!timerRunning) { - startTransition(async () => { - try { - await runOtpRequest(number); - restart(); - } catch { - console.error("Could not ask for otp") - } - }); - } - } - }; + /* END OF DEV SECTION */ + } + }) + } - const onSubmit = async (e: FormEvent) => { - e.preventDefault(); - startTransition(async () => { - try { - if (step == AUTH_STEP.ENTER_NUMBER) { - - if (await runUserExistCheck(number)) { - setStep(AUTH_STEP.ENTER_PASSWORD) - } else { - setStep(AUTH_STEP.ENTER_OTP); - } - } - else if (step == AUTH_STEP.ENTER_PASSWORD) { - try { - await loginMutation.mutateAsync({ phone: number, password }) - console.log("Logged in") - redirect("/") - } - catch (e) { - console.error("Wrong credentials: ", e) - } - } - else if (step == AUTH_STEP.ENTER_OTP) { - // if (!await runOtpValidation({ phone: number, otp })) { - // setOtp(''); - // console.error('Wrong otp'); - // } - stop(); - console.log(await runUserExistCheck(number)); - if (await runUserExistCheck(number)) { - console.log("1") - setStep(AUTH_STEP.ENTER_RESET_PASSWORD) - } else { - console.log("2") - setStep(AUTH_STEP.ENTER_NEW_PASSWORD) - } - } - else if (step == AUTH_STEP.ENTER_RESET_PASSWORD) { - console.log("Password changed") - try { - await runResetPassword({ - phone: number, - newPassword: password, - otp - }) - resetStates(); - setStep(AUTH_STEP.ENTER_NUMBER) - } catch (ex) { - console.error('Password reset failed: ', ex) - } - } - else if (step == AUTH_STEP.ENTER_NEW_PASSWORD) { - try { - await signupMutation.mutateAsync({ phone: number, password }) - console.log("Signed up") - redirect("/") - } - catch (e) { - console.error("Could not signup: ", e) - } - } - } catch (ex) { - console.error(ex) - } - }); - } - - const stepMap = { - [AUTH_STEP.ENTER_NUMBER]: , - [AUTH_STEP.ENTER_PASSWORD]: , - [AUTH_STEP.ENTER_OTP]: , - [AUTH_STEP.ENTER_NEW_PASSWORD]: , - [AUTH_STEP.ENTER_RESET_PASSWORD]: , - } - - return ( - - {stepMap[step] || 'say what now?'} - - ) + return ( + + + + ) } export default AuthIndex diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx new file mode 100644 index 0000000..3aa73f9 --- /dev/null +++ b/src/app/auth/signup/page.tsx @@ -0,0 +1,241 @@ +'use client' + +import React, { + useState, + type FormEvent, + type ChangeEvent, + useEffect, + useTransition +} from 'react' +import { useAuthStore } from '@/zustand/authStore' +import { redirect, useRouter } from 'next/navigation' +import StepEnterNumber from '@/features/auth/components/StepEnterNumber' +import StepEnterOtp from '@/features/auth/components/StepEnterOtp' +import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums' +import { useCountdown } from '@/hooks/useCountdown' +import StepNewPassword from '@/features/auth/components/StepNewPassword' +import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper' +import { useSignup } from '@/hooks/auth/useSignup' +import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists' +import { useOtpRequest } from '@/hooks/auth/useOtpRequest' +import { useOtpValidation } from '@/hooks/auth/useOtpValidation' +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 [passwordRepeat, setPasswordRepeat] = useState('') + const [otp, setOtp] = useState('') + const [showPassword, setShowPassword] = useState(false) + const [showPasswordRepeat, setShowPasswordRepeat] = useState(false) + const [step, setStep] = useState( + number == '' ? AUTH_STEP.ENTER_NUMBER : AUTH_STEP.ENTER_OTP + ) + const { timerRunning, secondsLeft, restart, stop } = useCountdown( + step === AUTH_STEP.ENTER_OTP + ) + const [isPending, startTransition] = useTransition() + + const isAuthenticated = useAuthStore(state => state.isAuthenticated) + const signupMutation = useSignup() + const { run: runUserExistCheck } = useCheckUserExistsLazy() + const { run: runOtpRequest } = useOtpRequest() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { run: runOtpValidation } = useOtpValidation() + const router = useRouter() + + useEffect(() => { + if (isAuthenticated) { + redirect('/') + } + }, [isAuthenticated]) + + useEffect(() => { + if (step === AUTH_STEP.ENTER_OTP) { + startTransition(() => { + setOtp('') + console.log('REQUEST OTP') + runOtpRequest(number) + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [step]) + + const onChange = (e: ChangeEvent) => { + 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_PASSWORD_REPEAT) { + setPasswordRepeat(() => e.target.value) + } else if (e.target.name.startsWith(AUTH_PAGE_ELEMENT.INPUT_OTP)) { + const index = +e.target.name.split('-')[2] + const prev = otp.padEnd(6).split('') + prev[index] = e.target.value + const next = prev.join('') + if (otp.length >= 6 && next.trim().length >= otp.length) return + setOtp(next.trimEnd()) + } + } + + const onClick = async ( + e: + | React.MouseEvent + | React.MouseEvent + ) => { + e.stopPropagation() + const target = e.target as HTMLButtonElement + + if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) { + setShowPassword(state => !state) + } + if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_REPEAT_PASSWORD) { + setShowPasswordRepeat(state => !state) + } else if (target.id === AUTH_PAGE_ELEMENT.RESET_PASSWORD) { + setShowPassword(false) + setShowPasswordRepeat(false) + setPassword('') + setPasswordRepeat('') + setStep(() => AUTH_STEP.ENTER_OTP) + } else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) { + if (!timerRunning) { + startTransition(async () => { + try { + await runOtpRequest(number) + restart() + } catch { + console.error('Could not ask for otp') + } + }) + } + } + } + + const onSubmit = async (e: FormEvent) => { + 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 OTP 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 { + setStep(AUTH_STEP.ENTER_OTP) + } + } else if (step == AUTH_STEP.ENTER_OTP) { + // if (!await runOtpValidation({ phone: number, otp })) { + // setOtp(''); + // console.error('Wrong otp'); + // } + update_temp_store({ + otp: otp + }) + stop() + setStep(AUTH_STEP.ENTER_CREATE_PASSWORD) + } else if ( + step == AUTH_STEP.ENTER_CREATE_PASSWORD || + step == AUTH_STEP.ENTER_CURRENT_PASSWORD + ) { + update_temp_store({ + password: password, + repeatPassword: passwordRepeat + }) + try { + await signupMutation.mutateAsync({ phone: number, password }) + console.log('Signed up') + redirect('/') + } catch (e) { + console.error('Could not signup: ', 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: number, + mustLogin: !number.endsWith('0') + }) + setStep(AUTH_STEP.ENTER_OTP) + } else if (step == AUTH_STEP.ENTER_OTP) { + stop() + update_temp_store({ + otp: otp + }) + setStep(AUTH_STEP.ENTER_CREATE_PASSWORD) + } else if (step == AUTH_STEP.ENTER_CREATE_PASSWORD) { + update_temp_store({ + password: password, + repeatPassword: passwordRepeat + }) + try { + console.log('Signed up') + // redirect('/') + router.replace('/') // TODO: redirect to target menu page + } catch (e) { + console.error('Could not signup: ', e) + } + } + /* END OF DEV SECTION */ + } + }) + } + + return ( + + {step === AUTH_STEP.ENTER_NUMBER && ( + + )} + + {step === AUTH_STEP.ENTER_OTP && ( + + )} + + {step === AUTH_STEP.ENTER_CREATE_PASSWORD && ( + + )} + + ) +} + +export default AuthIndex diff --git a/src/enums/authStep.ts b/src/enums/authStep.ts index 3133c1c..1eb0c27 100644 --- a/src/enums/authStep.ts +++ b/src/enums/authStep.ts @@ -1,7 +1,7 @@ export enum AUTH_STEP { ENTER_NUMBER = 0, - ENTER_PASSWORD = 1, + ENTER_CURRENT_PASSWORD = 1, ENTER_OTP = 2, - ENTER_NEW_PASSWORD = 3, + ENTER_CREATE_PASSWORD = 3, ENTER_RESET_PASSWORD = 4, } \ No newline at end of file diff --git a/src/features/auth/components/StepEnterPassword.tsx b/src/features/auth/components/StepEnterPassword.tsx index 6f81c57..b8ebea5 100644 --- a/src/features/auth/components/StepEnterPassword.tsx +++ b/src/features/auth/components/StepEnterPassword.tsx @@ -5,6 +5,7 @@ import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon' import InputField from '@/components/input/InputField' import { AUTH_PAGE_ELEMENT } from '@/enums' import Image from 'next/image' +import Link from 'next/link' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -88,14 +89,14 @@ function StepEnterPassword ({
- +
{/* TODO: customize checkbox */} diff --git a/src/zustand/authTempStroe.ts b/src/zustand/authTempStroe.ts new file mode 100644 index 0000000..1f008ce --- /dev/null +++ b/src/zustand/authTempStroe.ts @@ -0,0 +1,42 @@ +// zustand/userStore.ts +import { create } from 'zustand' +import { createJSONStorage, persist } from 'zustand/middleware' + +type TempUser = { + number?: string + otp?: string + password?: string + repeatPassword?: string + previousPassword?: string + mustLogin?: boolean + rememberMe?: boolean +} + +type AuthTempStore = { + user: TempUser | null + update: (userData: TempUser) => void +} + +export const useAuthTempStore = create()( + persist( + set => ({ + user: null, + mustLogin: false, + update: userData => { + set(state => { + if (!userData) return state // no user to update + return { + user: { + ...state.user, + ...userData + } + } + }) + } + }), + { + name: 'auth-temp-storage', // Key in localStorage + storage: createJSONStorage(() => sessionStorage) + } + ) +)