diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx index 905c525..1cbfc9f 100644 --- a/src/app/auth/page.tsx +++ b/src/app/auth/page.tsx @@ -1,22 +1,14 @@ 'use client'; -import React, { useState, type FormEvent, type ChangeEvent, useEffect, useRef } from 'react' -import PrimaryButton from '@/components/button/PrimaryButton' -import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon'; -import InputField from '@/components/input/InputField'; -import OTPInputField from '@/components/input/MultiInputField'; - -enum LOGIN_STEPS { - ENTER_NUMBER = 0, - ENTER_PASSWORD = 1, - ENTER_OTP = 2, -} - -enum CLICK_ID { - TOGGLE_PASSWORD = "togglePassword", - RESET_PASSWORD = "resetPassword", - RESEND_OTP = "resendOtp" -} +import React, { useState, type FormEvent, type ChangeEvent, useEffect } from 'react' +import { useAuthStore } from '@/zustand/userStore'; +import { loginUser } from '@/features/auth/actions/loginUser'; +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'; type Props = object @@ -26,45 +18,16 @@ function AuthIndex({ }: Props) { const [otp, setOtp] = useState(""); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(false); - const [step, setStep] = useState(LOGIN_STEPS.ENTER_NUMBER); - const [timerRunning, setTimerRunning] = useState(false); - const [secondLeft, setSecondsLeft] = useState(0); - const intervalRef = useRef(null); // ← useRef for interval + const [step, setStep] = useState(AUTH_STEP.ENTER_NUMBER); + const { timerRunning, secondsLeft, restart } = useCountdown(step === AUTH_STEP.ENTER_OTP); + + const isAuthenticated = useAuthStore((state) => state.isAuthenticated) useEffect(() => { - if (timerRunning && intervalRef.current === null) { - intervalRef.current = setInterval(() => { - setSecondsLeft((state) => { - if (state <= 1) { // changed to <= 1 to prevent negative values - setTimerRunning(false); - clearInterval(intervalRef.current!); - intervalRef.current = null; - return 0; - } - return state - 1; - }); - }, 1000); + if (isAuthenticated) { + redirect("/"); } - - return () => { - // Cleanup if component unmounts or timerRunning changes - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }; - }, [timerRunning]); - - - useEffect(() => { - if (step == LOGIN_STEPS.ENTER_OTP) { - setSecondsLeft(30); - setTimerRunning(true); - } - return () => { - - } - }, [step]) + }, [isAuthenticated]) const updateInput = (e: ChangeEvent) => { if (e.target.id == "phone") { @@ -89,133 +52,74 @@ function AuthIndex({ }: Props) { e.stopPropagation(); const target = e.target as HTMLButtonElement; - if (target.id === CLICK_ID.TOGGLE_PASSWORD) { + if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) { setShowPassword((state) => !state); } - else if (target.id === CLICK_ID.RESET_PASSWORD) { + else if (target.id === AUTH_PAGE_ELEMENT.RESET_PASSWORD) { setShowPassword((state) => !state); } - else if (target.id === CLICK_ID.RESEND_OTP) { - setSecondsLeft(30); - setTimerRunning(true); + else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) { + if(!timerRunning) { + restart(); + } } }; - const submit = (e: FormEvent) => { + const submit = async (e: FormEvent) => { e.preventDefault(); - console.log("STEP: ", step) - if (step == LOGIN_STEPS.ENTER_NUMBER) { - setStep(LOGIN_STEPS.ENTER_PASSWORD) + if (step == AUTH_STEP.ENTER_NUMBER) { + setStep(AUTH_STEP.ENTER_PASSWORD) } - if (step == LOGIN_STEPS.ENTER_PASSWORD) { - setStep(LOGIN_STEPS.ENTER_OTP) + if (step == AUTH_STEP.ENTER_PASSWORD) { + if (await loginUser(number, password)) { + console.log("Logged in") + } + else { + console.log("Wrong credentials") + } } - if (step == LOGIN_STEPS.ENTER_OTP) { - setStep(LOGIN_STEPS.ENTER_NUMBER) - setTimerRunning(false); + if (step == AUTH_STEP.ENTER_OTP) { + setStep(AUTH_STEP.ENTER_NUMBER) } } - if (step == LOGIN_STEPS.ENTER_NUMBER) { + if (step == AUTH_STEP.ENTER_NUMBER) { // First Step -> Enter number return ( -
-
- -
-
ورود به سیستم
-

برای ورود شماره همراه خود را وارد نمایید.

-
- -
- بعدی -
+ ) } - if (step == LOGIN_STEPS.ENTER_PASSWORD) { + if (step == AUTH_STEP.ENTER_PASSWORD) { return ( -
-
- -
-
ورود به سیستم
-

کلمه عبور خود را وارد نمایید.

-
- - - -
- - -
- {/* TODO: customize checkbox */} - -
-
-
- ورود به منو -
+ ) } - if (step == LOGIN_STEPS.ENTER_OTP) { + if (step == AUTH_STEP.ENTER_OTP) { return ( -
-
- -
- {/* // TODO: add persian digits font */} -
کد فعالسازی را وارد کنید
-

کد ۶ رقمی ارسال شده به شماره {number} را وارد نمایید.

-
- - - -
-
- {timerRunning ? -
{secondLeft} ثانیه دیگر تا دریافت کد
: -
- کد را دریافت نکردید؟ - - - -
- } -
-
-
- بعدی -
+ ) } } -export default AuthIndex \ No newline at end of file +export default AuthIndex diff --git a/src/enums/authStep.ts b/src/enums/authStep.ts new file mode 100644 index 0000000..f8f8b67 --- /dev/null +++ b/src/enums/authStep.ts @@ -0,0 +1,5 @@ +export enum AUTH_STEP { + ENTER_NUMBER = 0, + ENTER_PASSWORD = 1, + ENTER_OTP = 2, +} \ No newline at end of file diff --git a/src/enums/elementIds.ts b/src/enums/elementIds.ts new file mode 100644 index 0000000..88f7203 --- /dev/null +++ b/src/enums/elementIds.ts @@ -0,0 +1,5 @@ +export enum AUTH_PAGE_ELEMENT { + TOGGLE_PASSWORD = "togglePassword", + RESET_PASSWORD = "resetPassword", + RESEND_OTP = "resendOtp" +} \ No newline at end of file diff --git a/src/enums/index.ts b/src/enums/index.ts new file mode 100644 index 0000000..e0825aa --- /dev/null +++ b/src/enums/index.ts @@ -0,0 +1,5 @@ +import { AUTH_STEP } from "./authStep"; +import { AUTH_PAGE_ELEMENT } from "./elementIds"; + +export { AUTH_STEP }; +export { AUTH_PAGE_ELEMENT }; diff --git a/src/features/auth/actions/loginUser.ts b/src/features/auth/actions/loginUser.ts index 76d192f..18c4621 100644 --- a/src/features/auth/actions/loginUser.ts +++ b/src/features/auth/actions/loginUser.ts @@ -3,22 +3,25 @@ import { useAuthStore } from '@/zustand/userStore' // Simulated API login export async function loginUser(number: string, password: string) { - // Simulate API call - replace with real one - const response = await new Promise<{ user: any }>((resolve) => - setTimeout(() => { - resolve({ - user: { - id: '123', - name: 'John Doe', - number, - token: 'mock-jwt-token', - }, - }) - }, 1000) - ) + if (number == "123" && password == "123") { + // Simulate API call - replace with real one + const response = await new Promise<{ user: any }>((resolve) => + setTimeout(() => { + resolve({ + user: { + id: '123', + name: 'John Doe', + number, + token: 'mock-jwt-token', + }, + }) + }, 1000) + ) - const { login } = useAuthStore.getState() - login(response.user) + const { login } = useAuthStore.getState() + login(response.user) + return true; + } - return true; + return false; } diff --git a/src/features/auth/components/StepEnterNumber.tsx b/src/features/auth/components/StepEnterNumber.tsx new file mode 100644 index 0000000..5147b6b --- /dev/null +++ b/src/features/auth/components/StepEnterNumber.tsx @@ -0,0 +1,34 @@ +import PrimaryButton from '@/components/button/PrimaryButton'; +import InputField from '@/components/input/InputField'; +import React from 'react' + +type Props = { + onSubmit: React.FormEventHandler | undefined; + onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; + value: string; +} + +function StepEnterNumber({ onSubmit, onChange, value }: Props) { + return ( +
+
+ +
+
ورود به سیستم
+

برای ورود شماره همراه خود را وارد نمایید.

+
+ +
+ بعدی +
+ ) +} + +export default StepEnterNumber \ No newline at end of file diff --git a/src/features/auth/components/StepEnterOtp.tsx b/src/features/auth/components/StepEnterOtp.tsx new file mode 100644 index 0000000..8f1ce66 --- /dev/null +++ b/src/features/auth/components/StepEnterOtp.tsx @@ -0,0 +1,59 @@ +import PrimaryButton from '@/components/button/PrimaryButton'; +import OTPInputField from '@/components/input/MultiInputField'; +import { AUTH_PAGE_ELEMENT as AUTH_PAGE_ELEMENTS } from '@/enums'; +import React from 'react' + +type Props = { + onSubmit: React.FormEventHandler | undefined; + onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; + onClick: React.MouseEventHandler | undefined; + value: string; + phoneNumber: string; + timerRunning: boolean; + secondsLeft: number; +} + +function StepEnterOtp({ onSubmit, onChange, onClick, value, phoneNumber, timerRunning, secondsLeft }: Props) { + + return ( +
+
+ +
+ {/* // TODO: add persian digits font */} +
کد فعالسازی را وارد کنید
+

کد ۶ رقمی ارسال شده به شماره {phoneNumber} را وارد نمایید.

+
+ + + +
+
+ {timerRunning ? +
{secondsLeft} ثانیه دیگر تا دریافت کد
: +
+ کد را دریافت نکردید؟ + + + +
+ } +
+
+
+ بعدی +
+ ) +} + +export default StepEnterOtp \ No newline at end of file diff --git a/src/features/auth/components/StepEnterPassword.tsx b/src/features/auth/components/StepEnterPassword.tsx new file mode 100644 index 0000000..e7a473c --- /dev/null +++ b/src/features/auth/components/StepEnterPassword.tsx @@ -0,0 +1,51 @@ +import PrimaryButton from '@/components/button/PrimaryButton'; +import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon'; +import InputField from '@/components/input/InputField'; +import { AUTH_PAGE_ELEMENT } from '@/enums'; +import React from 'react' + +type Props = { + onSubmit: React.FormEventHandler | undefined; + onChange: ((e: React.ChangeEvent) => void) & React.ChangeEventHandler; + onClick: React.MouseEventHandler | undefined; + value: string; + passwordVisible: boolean; + rememberMe: boolean; +} + +function StepEnterPassword({ onSubmit, onChange, onClick, value, passwordVisible, rememberMe }: Props) { + return ( +
+
+ +
+
ورود به سیستم
+

کلمه عبور خود را وارد نمایید.

+
+ + + +
+ + +
+ {/* TODO: customize checkbox */} + +
+
+
+ ورود به منو +
+ ) +} + +export default StepEnterPassword \ No newline at end of file diff --git a/src/hooks/useCountdown.ts b/src/hooks/useCountdown.ts new file mode 100644 index 0000000..bb91e5d --- /dev/null +++ b/src/hooks/useCountdown.ts @@ -0,0 +1,51 @@ +import { useEffect, useRef, useState } from 'react'; + +export const useCountdown = (shouldStart: boolean, duration: number = 30, delay: number = 1000) => { + const [timerRunning, setTimerRunning] = useState(false); + const [secondsLeft, setSecondsLeft] = useState(duration); + const intervalRef = useRef(null); + + useEffect(() => { + if (shouldStart) { + setSecondsLeft(duration); + setTimerRunning(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shouldStart]); + + const stop = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + } + + const restart = () => { + stop(); + setSecondsLeft(duration); + setTimerRunning(true); + } + + useEffect(() => { + if (timerRunning && intervalRef.current === null) { + intervalRef.current = setInterval(() => { + setSecondsLeft((prev) => { + if (prev <= 1) { + setTimerRunning(false); + clearInterval(intervalRef.current!); + intervalRef.current = null; + return 0; + } + return prev - 1; + }); + }, delay); + } + + return () => { + stop(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timerRunning]); + + return { timerRunning, secondsLeft, setTimerRunning, setSecondsLeft, stop, restart }; +};