chore: seperated auth page files
This commit is contained in:
+60
-156
@@ -1,22 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, type FormEvent, type ChangeEvent, useEffect, useRef } from 'react'
|
import React, { useState, type FormEvent, type ChangeEvent, useEffect } from 'react'
|
||||||
import PrimaryButton from '@/components/button/PrimaryButton'
|
import { useAuthStore } from '@/zustand/userStore';
|
||||||
import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon';
|
import { loginUser } from '@/features/auth/actions/loginUser';
|
||||||
import InputField from '@/components/input/InputField';
|
import { redirect } from 'next/navigation';
|
||||||
import OTPInputField from '@/components/input/MultiInputField';
|
import StepEnterPassword from '@/features/auth/components/StepEnterPassword';
|
||||||
|
import StepEnterNumber from '@/features/auth/components/StepEnterNumber';
|
||||||
enum LOGIN_STEPS {
|
import StepEnterOtp from '@/features/auth/components/StepEnterOtp';
|
||||||
ENTER_NUMBER = 0,
|
import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums';
|
||||||
ENTER_PASSWORD = 1,
|
import { useCountdown } from '@/hooks/useCountdown';
|
||||||
ENTER_OTP = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CLICK_ID {
|
|
||||||
TOGGLE_PASSWORD = "togglePassword",
|
|
||||||
RESET_PASSWORD = "resetPassword",
|
|
||||||
RESEND_OTP = "resendOtp"
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = object
|
type Props = object
|
||||||
|
|
||||||
@@ -26,45 +18,16 @@ function AuthIndex({ }: Props) {
|
|||||||
const [otp, setOtp] = useState("");
|
const [otp, setOtp] = useState("");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [rememberMe, setRememberMe] = useState(false);
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
const [step, setStep] = useState(LOGIN_STEPS.ENTER_NUMBER);
|
const [step, setStep] = useState(AUTH_STEP.ENTER_NUMBER);
|
||||||
const [timerRunning, setTimerRunning] = useState(false);
|
const { timerRunning, secondsLeft, restart } = useCountdown(step === AUTH_STEP.ENTER_OTP);
|
||||||
const [secondLeft, setSecondsLeft] = useState(0);
|
|
||||||
const intervalRef = useRef<NodeJS.Timeout | null>(null); // ← useRef for interval
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timerRunning && intervalRef.current === null) {
|
if (isAuthenticated) {
|
||||||
intervalRef.current = setInterval(() => {
|
redirect("/");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}, [isAuthenticated])
|
||||||
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])
|
|
||||||
|
|
||||||
const updateInput = (e: ChangeEvent<HTMLInputElement>) => {
|
const updateInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.id == "phone") {
|
if (e.target.id == "phone") {
|
||||||
@@ -89,133 +52,74 @@ function AuthIndex({ }: Props) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const target = e.target as HTMLButtonElement;
|
const target = e.target as HTMLButtonElement;
|
||||||
|
|
||||||
if (target.id === CLICK_ID.TOGGLE_PASSWORD) {
|
if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) {
|
||||||
setShowPassword((state) => !state);
|
setShowPassword((state) => !state);
|
||||||
}
|
}
|
||||||
else if (target.id === CLICK_ID.RESET_PASSWORD) {
|
else if (target.id === AUTH_PAGE_ELEMENT.RESET_PASSWORD) {
|
||||||
setShowPassword((state) => !state);
|
setShowPassword((state) => !state);
|
||||||
}
|
}
|
||||||
else if (target.id === CLICK_ID.RESEND_OTP) {
|
else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) {
|
||||||
setSecondsLeft(30);
|
if(!timerRunning) {
|
||||||
setTimerRunning(true);
|
restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = (e: FormEvent<HTMLFormElement>) => {
|
const submit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log("STEP: ", step)
|
if (step == AUTH_STEP.ENTER_NUMBER) {
|
||||||
if (step == LOGIN_STEPS.ENTER_NUMBER) {
|
setStep(AUTH_STEP.ENTER_PASSWORD)
|
||||||
setStep(LOGIN_STEPS.ENTER_PASSWORD)
|
|
||||||
}
|
}
|
||||||
if (step == LOGIN_STEPS.ENTER_PASSWORD) {
|
if (step == AUTH_STEP.ENTER_PASSWORD) {
|
||||||
setStep(LOGIN_STEPS.ENTER_OTP)
|
if (await loginUser(number, password)) {
|
||||||
|
console.log("Logged in")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Wrong credentials")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (step == LOGIN_STEPS.ENTER_OTP) {
|
if (step == AUTH_STEP.ENTER_OTP) {
|
||||||
setStep(LOGIN_STEPS.ENTER_NUMBER)
|
setStep(AUTH_STEP.ENTER_NUMBER)
|
||||||
setTimerRunning(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step == LOGIN_STEPS.ENTER_NUMBER) {
|
if (step == AUTH_STEP.ENTER_NUMBER) {
|
||||||
// First Step -> Enter number
|
// First Step -> Enter number
|
||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className='p-6 h-full flex flex-col justify-between'>
|
<StepEnterNumber
|
||||||
<div className='block'>
|
onChange={updateInput}
|
||||||
<img src='/assets/images/login-banner.png' />
|
onSubmit={submit}
|
||||||
<div className='pt-4' dir='rtl'>
|
value={number}
|
||||||
<h6 className='text-lg font-bold'>ورود به سیستم</h6>
|
/>
|
||||||
<p className='mt-3 text-[13px]'>برای ورود شماره همراه خود را وارد نمایید.</p>
|
|
||||||
</div>
|
|
||||||
<InputField
|
|
||||||
className='mt-10'
|
|
||||||
htmlFor='phone'
|
|
||||||
labelText='شماره همراه'
|
|
||||||
value={number}
|
|
||||||
placeholder='09120000000'
|
|
||||||
onChange={updateInput}
|
|
||||||
type='number' />
|
|
||||||
</div>
|
|
||||||
<PrimaryButton type='submit'>بعدی</PrimaryButton>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step == LOGIN_STEPS.ENTER_PASSWORD) {
|
if (step == AUTH_STEP.ENTER_PASSWORD) {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className='p-6 h-full flex flex-col justify-between'>
|
<StepEnterPassword
|
||||||
<div className='block'>
|
onChange={updateInput}
|
||||||
<img src='/assets/images/login-banner.png' />
|
onClick={onClickEvent}
|
||||||
<div className='pt-4' dir='rtl'>
|
onSubmit={submit}
|
||||||
<h6 className='text-lg font-bold'>ورود به سیستم</h6>
|
value={password}
|
||||||
<p className='mt-3 text-[13px]'>کلمه عبور خود را وارد نمایید.</p>
|
passwordVisible={showPassword}
|
||||||
</div>
|
rememberMe={rememberMe}
|
||||||
<InputField
|
/>
|
||||||
className='mt-10'
|
|
||||||
htmlFor='password'
|
|
||||||
labelText='کلمه عبور'
|
|
||||||
value={password}
|
|
||||||
placeholder=''
|
|
||||||
onChange={updateInput}
|
|
||||||
type={showPassword ? 'text' : 'password'}>
|
|
||||||
<button className='ps-3' id={CLICK_ID.TOGGLE_PASSWORD} type='button' onClick={onClickEvent}>
|
|
||||||
<EyeToggleIcon slash={showPassword} className="pointer-events-none" />
|
|
||||||
</button>
|
|
||||||
</InputField>
|
|
||||||
<div className='inline-flex justify-between items-center w-full pt-3'>
|
|
||||||
<button id={CLICK_ID.RESET_PASSWORD} type='button' onClick={onClickEvent} className='text-sm2 cursor-pointer'>بازیابی کلمه عبور</button>
|
|
||||||
|
|
||||||
<div className='inline-flex justify-between items-center'>
|
|
||||||
{/* TODO: customize checkbox */}
|
|
||||||
<label htmlFor='rememberMe' className='text-sm2 px-2 py-0.5'>مرا به خاطر بسپار</label>
|
|
||||||
<input id='rememberMe' className='h-4.5 w-4.5 checked:accent-primary' onChange={updateInput} type='checkbox' checked={rememberMe} /> </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PrimaryButton type='submit'>ورود به منو</PrimaryButton>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step == LOGIN_STEPS.ENTER_OTP) {
|
if (step == AUTH_STEP.ENTER_OTP) {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className='p-6 h-full flex flex-col justify-between'>
|
<StepEnterOtp
|
||||||
<div className='block'>
|
onChange={updateInput}
|
||||||
<img src='/assets/images/login-banner.png' />
|
onClick={onClickEvent}
|
||||||
<div className='pt-4' dir='rtl'>
|
phoneNumber={number}
|
||||||
{/* // TODO: add persian digits font */}
|
onSubmit={submit}
|
||||||
<h6 className='text-lg font-bold'>کد فعالسازی را وارد کنید</h6>
|
value={otp}
|
||||||
<p className='mt-3 text-[13px]'>کد ۶ رقمی ارسال شده به شماره {number} را وارد نمایید.</p>
|
timerRunning={timerRunning}
|
||||||
</div>
|
secondsLeft={secondsLeft}
|
||||||
<OTPInputField
|
/>
|
||||||
className='mt-10'
|
|
||||||
maxLength={6}
|
|
||||||
htmlFor='otp'
|
|
||||||
labelText=''
|
|
||||||
value={otp}
|
|
||||||
placeholder=''
|
|
||||||
onChange={updateInput}
|
|
||||||
type='text'>
|
|
||||||
|
|
||||||
</OTPInputField>
|
|
||||||
<div className='inline-flex justify-center items-center w-full pt-6 text-xs'>
|
|
||||||
<div dir='rtl'>
|
|
||||||
{timerRunning ?
|
|
||||||
<div>{secondLeft} ثانیه دیگر تا دریافت کد</div> :
|
|
||||||
<div>
|
|
||||||
کد را دریافت نکردید؟
|
|
||||||
<span className='text-primary px-1'>
|
|
||||||
<button id={CLICK_ID.RESEND_OTP} className='cursor-pointer' onClick={onClickEvent}>
|
|
||||||
دوباره امتحان کنید
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PrimaryButton type='submit'>بعدی</PrimaryButton>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AuthIndex
|
export default AuthIndex
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum AUTH_STEP {
|
||||||
|
ENTER_NUMBER = 0,
|
||||||
|
ENTER_PASSWORD = 1,
|
||||||
|
ENTER_OTP = 2,
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum AUTH_PAGE_ELEMENT {
|
||||||
|
TOGGLE_PASSWORD = "togglePassword",
|
||||||
|
RESET_PASSWORD = "resetPassword",
|
||||||
|
RESEND_OTP = "resendOtp"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { AUTH_STEP } from "./authStep";
|
||||||
|
import { AUTH_PAGE_ELEMENT } from "./elementIds";
|
||||||
|
|
||||||
|
export { AUTH_STEP };
|
||||||
|
export { AUTH_PAGE_ELEMENT };
|
||||||
@@ -3,22 +3,25 @@ import { useAuthStore } from '@/zustand/userStore'
|
|||||||
|
|
||||||
// Simulated API login
|
// Simulated API login
|
||||||
export async function loginUser(number: string, password: string) {
|
export async function loginUser(number: string, password: string) {
|
||||||
// Simulate API call - replace with real one
|
if (number == "123" && password == "123") {
|
||||||
const response = await new Promise<{ user: any }>((resolve) =>
|
// Simulate API call - replace with real one
|
||||||
setTimeout(() => {
|
const response = await new Promise<{ user: any }>((resolve) =>
|
||||||
resolve({
|
setTimeout(() => {
|
||||||
user: {
|
resolve({
|
||||||
id: '123',
|
user: {
|
||||||
name: 'John Doe',
|
id: '123',
|
||||||
number,
|
name: 'John Doe',
|
||||||
token: 'mock-jwt-token',
|
number,
|
||||||
},
|
token: 'mock-jwt-token',
|
||||||
})
|
},
|
||||||
}, 1000)
|
})
|
||||||
)
|
}, 1000)
|
||||||
|
)
|
||||||
|
|
||||||
const { login } = useAuthStore.getState()
|
const { login } = useAuthStore.getState()
|
||||||
login(response.user)
|
login(response.user)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<HTMLFormElement> | undefined;
|
||||||
|
onChange: ((e: React.ChangeEvent<HTMLInputElement>) => void) & React.ChangeEventHandler<HTMLInputElement>;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StepEnterNumber({ onSubmit, onChange, value }: Props) {
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit} className='p-6 h-full flex flex-col justify-between'>
|
||||||
|
<div className='block'>
|
||||||
|
<img src='/assets/images/login-banner.png' />
|
||||||
|
<div className='pt-4' dir='rtl'>
|
||||||
|
<h6 className='text-lg font-bold'>ورود به سیستم</h6>
|
||||||
|
<p className='mt-3 text-[13px]'>برای ورود شماره همراه خود را وارد نمایید.</p>
|
||||||
|
</div>
|
||||||
|
<InputField
|
||||||
|
className='mt-10'
|
||||||
|
htmlFor='phone'
|
||||||
|
labelText='شماره همراه'
|
||||||
|
value={value}
|
||||||
|
placeholder='09120000000'
|
||||||
|
onChange={onChange}
|
||||||
|
type='number' />
|
||||||
|
</div>
|
||||||
|
<PrimaryButton type='submit'>بعدی</PrimaryButton>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StepEnterNumber
|
||||||
@@ -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<HTMLFormElement> | undefined;
|
||||||
|
onChange: ((e: React.ChangeEvent<HTMLInputElement>) => void) & React.ChangeEventHandler<HTMLInputElement>;
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement> | undefined;
|
||||||
|
value: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
timerRunning: boolean;
|
||||||
|
secondsLeft: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StepEnterOtp({ onSubmit, onChange, onClick, value, phoneNumber, timerRunning, secondsLeft }: Props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit} className='p-6 h-full flex flex-col justify-between'>
|
||||||
|
<div className='block'>
|
||||||
|
<img src='/assets/images/login-banner.png' />
|
||||||
|
<div className='pt-4' dir='rtl'>
|
||||||
|
{/* // TODO: add persian digits font */}
|
||||||
|
<h6 className='text-lg font-bold'>کد فعالسازی را وارد کنید</h6>
|
||||||
|
<p className='mt-3 text-[13px]'>کد ۶ رقمی ارسال شده به شماره {phoneNumber} را وارد نمایید.</p>
|
||||||
|
</div>
|
||||||
|
<OTPInputField
|
||||||
|
className='mt-10'
|
||||||
|
maxLength={6}
|
||||||
|
htmlFor='otp'
|
||||||
|
labelText=''
|
||||||
|
value={value}
|
||||||
|
placeholder=''
|
||||||
|
onChange={onChange}
|
||||||
|
type='text'>
|
||||||
|
|
||||||
|
</OTPInputField>
|
||||||
|
<div className='inline-flex justify-center items-center w-full pt-6 text-xs'>
|
||||||
|
<div dir='rtl'>
|
||||||
|
{timerRunning ?
|
||||||
|
<div>{secondsLeft} ثانیه دیگر تا دریافت کد</div> :
|
||||||
|
<div>
|
||||||
|
کد را دریافت نکردید؟
|
||||||
|
<span className='text-primary px-1'>
|
||||||
|
<button id={AUTH_PAGE_ELEMENTS.RESEND_OTP} className='cursor-pointer' onClick={onClick}>
|
||||||
|
دوباره امتحان کنید
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PrimaryButton type='submit'>بعدی</PrimaryButton>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StepEnterOtp
|
||||||
@@ -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<HTMLFormElement> | undefined;
|
||||||
|
onChange: ((e: React.ChangeEvent<HTMLInputElement>) => void) & React.ChangeEventHandler<HTMLInputElement>;
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement> | undefined;
|
||||||
|
value: string;
|
||||||
|
passwordVisible: boolean;
|
||||||
|
rememberMe: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StepEnterPassword({ onSubmit, onChange, onClick, value, passwordVisible, rememberMe }: Props) {
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit} className='p-6 h-full flex flex-col justify-between'>
|
||||||
|
<div className='block'>
|
||||||
|
<img src='/assets/images/login-banner.png' />
|
||||||
|
<div className='pt-4' dir='rtl'>
|
||||||
|
<h6 className='text-lg font-bold'>ورود به سیستم</h6>
|
||||||
|
<p className='mt-3 text-[13px]'>کلمه عبور خود را وارد نمایید.</p>
|
||||||
|
</div>
|
||||||
|
<InputField
|
||||||
|
className='mt-10'
|
||||||
|
htmlFor='password'
|
||||||
|
labelText='کلمه عبور'
|
||||||
|
value={value}
|
||||||
|
placeholder=''
|
||||||
|
onChange={onChange}
|
||||||
|
type={passwordVisible ? 'text' : 'password'}>
|
||||||
|
<button className='ps-3' id={AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD} type='button' onClick={onClick}>
|
||||||
|
<EyeToggleIcon slash={passwordVisible} className="pointer-events-none" />
|
||||||
|
</button>
|
||||||
|
</InputField>
|
||||||
|
<div className='inline-flex justify-between items-center w-full pt-3'>
|
||||||
|
<button id={AUTH_PAGE_ELEMENT.RESET_PASSWORD} type='button' onClick={onClick} className='text-sm2 cursor-pointer'>بازیابی کلمه عبور</button>
|
||||||
|
|
||||||
|
<div className='inline-flex justify-between items-center'>
|
||||||
|
{/* TODO: customize checkbox */}
|
||||||
|
<label htmlFor='rememberMe' className='text-sm2 px-2 py-0.5'>مرا به خاطر بسپار</label>
|
||||||
|
<input id='rememberMe' className='h-4.5 w-4.5 checked:accent-primary' onChange={onChange} type='checkbox' checked={rememberMe} /> </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PrimaryButton type='submit'>ورود به منو</PrimaryButton>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StepEnterPassword
|
||||||
@@ -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<NodeJS.Timeout | null>(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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user