Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# useCountdown Hook
Створює таймер зворотного відліку.
Автоматично викликає `onTimeOut` після завершення зворотного відліку.
По замовчуванню `delay` 10s
Output:
```ts
export type CountdownController = {
start: () => void;
dismiss: () => void;
remaining: number | null;
};
```
```ts
export const EmailVerification: React.FC<{
initialValues: FormValues;
onSubmit: (values: FormValues) => void;
}> = ({ initialValues, onSubmit }) => {
const form = useForm<FormValues>({
defaultValues: initialValues,
resolver: zodResolver(verifySchema),
});
const sendCodeMutation = useSendVerificationCode();
const { t } = useTranslation();
const [countdownFinished, setCountdownFinished] = React.useState(false);
const { start, remaining } = useCountdown({
onTimeOut: () => setCountdownFinished(true),
delay: 59 * 1000,
});
React.useEffect(() => {
start();
}, []);
const handleResendCode = () => {
const email = form.getValues().email;
if (!email) return;
sendCodeMutation.mutate(
{ email },
{
onSuccess() {
toast.info(t('Verification code sent to email'));
start();
setCountdownFinished(false);
},
}
);
};
return (
<Form></Form>
);
};
```
+1
View File
@@ -0,0 +1 @@
export * from './useCountdown';
@@ -0,0 +1,67 @@
'use client';
import React from 'react';
export type UseCountdownParams = {
onTimeOut: () => void;
delay?: number;
};
export type CountdownController = {
start: () => void;
dismiss: () => void;
remaining: number | null;
};
export const useCountdown = ({ onTimeOut, delay = 10000 }: UseCountdownParams): CountdownController => {
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const intervalRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const [remaining, setRemaining] = React.useState<number | null>(null);
const start = React.useCallback(() => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (intervalRef.current) clearInterval(intervalRef.current);
const totalSeconds = Math.ceil(delay / 1000);
setRemaining(totalSeconds);
intervalRef.current = setInterval(() => {
setRemaining((prev) => {
if (prev === null || prev <= 1) {
clearInterval(intervalRef.current!);
return null;
}
return prev - 1;
});
}, 1000);
timeoutRef.current = setTimeout(() => {
onTimeOut();
timeoutRef.current = null;
setRemaining(null);
}, delay);
}, [delay, onTimeOut]);
const dismiss = React.useCallback(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setRemaining(null);
}, []);
React.useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);
return { start, dismiss, remaining };
};