52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { type Lang, dict } from "@/lib/i18n";
|
|
|
|
// "투표 종료까지" 카운트다운 (D7). 마감 = 킥오프 정각(lockAt).
|
|
// 서버/클라 hydration mismatch 방지: 시간 계산은 마운트 후 useEffect에서만.
|
|
export default function Countdown({ to, lang = "ko" }: { to: string; lang?: Lang }) {
|
|
const t = dict(lang);
|
|
const label = t.cdUntil;
|
|
const [left, setLeft] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
const target = new Date(to).getTime();
|
|
const tick = () => setLeft(Math.max(0, target - Date.now()));
|
|
tick();
|
|
const id = setInterval(tick, 1000);
|
|
return () => clearInterval(id);
|
|
}, [to]);
|
|
|
|
const ended = left !== null && left <= 0;
|
|
|
|
return (
|
|
<div className="flex items-center justify-center gap-2 rounded-xl border border-[var(--line-d)] bg-[var(--bg2)] px-3 py-2.5">
|
|
<span className="text-[12px] font-semibold text-[var(--ink-muted)]">
|
|
{ended ? t.cdClosed : label}
|
|
</span>
|
|
{!ended && (
|
|
<span
|
|
className="font-mono text-[18px] font-extrabold tabular-nums text-[var(--green)]"
|
|
style={{ textShadow: "0 0 12px rgba(74,255,160,0.45)" }}
|
|
suppressHydrationWarning
|
|
>
|
|
{left === null ? "--:--:--" : fmt(left, lang)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function fmt(ms: number, lang: Lang): string {
|
|
const s = Math.floor(ms / 1000);
|
|
const d = Math.floor(s / 86400);
|
|
const h = Math.floor((s % 86400) / 3600);
|
|
const m = Math.floor((s % 3600) / 60);
|
|
const sec = s % 60;
|
|
const pad = (n: number) => String(n).padStart(2, "0");
|
|
const dayUnit = lang === "en" ? "d" : "일";
|
|
if (d > 0) return `${d}${dayUnit} ${pad(h)}:${pad(m)}:${pad(sec)}`;
|
|
return `${pad(h)}:${pad(m)}:${pad(sec)}`;
|
|
}
|