51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
|
|
export type CountdownFormat = "clock" | "duration";
|
|
|
|
interface CountdownProps {
|
|
targetUnixSeconds: number;
|
|
format?: CountdownFormat;
|
|
className?: string;
|
|
}
|
|
|
|
function formatClock(secondsLeft: number): string {
|
|
const m = Math.floor(secondsLeft / 60);
|
|
const s = secondsLeft % 60;
|
|
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
/**
|
|
* Format seconds into "Xd Xh Xm" for "next eligible" style countdown.
|
|
* Exported for reuse in denial panel and success modal.
|
|
*/
|
|
export function formatDuration(secondsLeft: number): string {
|
|
if (secondsLeft <= 0) return "0d 0h 0m";
|
|
const d = Math.floor(secondsLeft / 86400);
|
|
const h = Math.floor((secondsLeft % 86400) / 3600);
|
|
const m = Math.floor((secondsLeft % 3600) / 60);
|
|
const parts: string[] = [];
|
|
if (d > 0) parts.push(`${d}d`);
|
|
parts.push(`${h}h`);
|
|
parts.push(`${m}m`);
|
|
return parts.join(" ");
|
|
}
|
|
|
|
function getSecondsLeft(targetUnixSeconds: number): number {
|
|
return Math.max(0, targetUnixSeconds - Math.floor(Date.now() / 1000));
|
|
}
|
|
|
|
export function Countdown({ targetUnixSeconds, format = "clock", className }: CountdownProps) {
|
|
const [secondsLeft, setSecondsLeft] = useState(() => getSecondsLeft(targetUnixSeconds));
|
|
|
|
useEffect(() => {
|
|
setSecondsLeft(getSecondsLeft(targetUnixSeconds));
|
|
const t = setInterval(() => {
|
|
setSecondsLeft(getSecondsLeft(targetUnixSeconds));
|
|
}, 1000);
|
|
return () => clearInterval(t);
|
|
}, [targetUnixSeconds]);
|
|
|
|
const text = format === "duration" ? formatDuration(secondsLeft) : formatClock(secondsLeft);
|
|
return <span className={className}>{text}</span>;
|
|
}
|