import { format, formatDistanceToNow } from "date-fns"; import { ChevronRight, ChessPawn, Hourglass, LayoutGrid, Moon } from "lucide-react"; import { Link } from "react-router"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader } from "~/components/ui/card"; import { GradientIcon } from "~/components/ui/GradientIcon"; // ─── Types ──────────────────────────────────────────────────────────────────── export interface DraftInfoCardProps { draftRounds: number; draftTimerMode: "standard" | "chess_clock"; /** Per-pick time for standard; initial bank for chess clock (seconds) */ draftInitialTime: number; /** Bonus seconds added after each pick (chess clock only; ignored in standard) */ draftIncrementTime: number; sportsCount: number; isDraftOrderSet: boolean; /** true when season status is pre_draft or draft */ isDraftOrPreDraft: boolean; draftDateTime?: string | Date | null; draftBoardHref: string; draftRoomHref: string; /** Link to the pre-draft queue builder; shown only when draft order is not yet set and user has a team */ queueBuilderHref?: string; /** 1-based draft position for the current user; omit or undefined to hide the panel */ userDraftPosition?: number; /** IANA timezone used to display the draft date and time. */ draftTimezone?: string | null; overnightPauseMode?: "none" | "league" | "per_user"; overnightPauseStart?: string | null; overnightPauseEnd?: string | null; overnightPauseTimezone?: string | null; } // ─── Sub-components ─────────────────────────────────────────────────────────── function InfoPanel({ label, value, sub }: { label: string; value: React.ReactNode; sub?: React.ReactNode }) { return (
{label} {value} {sub && {sub}}
); } // ─── Card variant (pre_draft / draft) ───────────────────────────────────────── function DraftInfoCardVariant({ draftRounds, draftTimerMode, draftInitialTime, draftIncrementTime, sportsCount, isDraftOrderSet, draftDateTime, draftTimezone, draftRoomHref, queueBuilderHref, userDraftPosition, overnightPauseMode, overnightPauseStart, overnightPauseEnd, overnightPauseTimezone, }: DraftInfoCardProps) { const flexSpots = Math.max(0, draftRounds - sportsCount); const timerLabel = draftTimerMode === "chess_clock" ? "Chess Clock" : "Standard Timer"; const draftDate = draftDateTime ? new Date(draftDateTime) : null; const timerValue = draftTimerMode === "chess_clock" ? `${formatHumanTime(draftInitialTime, { attributive: true })} time bank` : formatHumanTime(draftInitialTime); const timerSub = draftTimerMode === "chess_clock" ? `+${formatHumanTime(draftIncrementTime, { attributive: true })} increment` : "per pick"; const roundsSub = [ `${sportsCount} sport${sportsCount !== 1 ? "s" : ""}`, flexSpots > 0 ? `${flexSpots} flex` : null, ].filter(Boolean).join(" + "); const hasPosition = userDraftPosition !== null && userDraftPosition !== undefined; const hasOvernight = !!(overnightPauseMode && overnightPauseMode !== "none" && overnightPauseStart && overnightPauseEnd); // Mechanics row: Rounds + (Your Pick?) + Timer // When overnight is shown, Draft Date moves to the top row so mechanics has 2–3 items. // When no overnight, Draft Date stays in this row giving 3–4 items. const mechanicsColsClass = hasOvernight ? (hasPosition ? "grid-cols-2 sm:grid-cols-3" : "grid-cols-2") : (hasPosition ? "grid-cols-2 sm:grid-cols-4" : "grid-cols-2 sm:grid-cols-3"); const timerPanel = ( {draftTimerMode === "chess_clock" ? : } {timerValue} } sub={timerSub} /> ); return (

Draft Info

{isDraftOrderSet ? ( ) : queueBuilderHref ? ( ) : null}
{/* Scheduling row: Draft Date + Overnight Pause side-by-side when overnight is configured */} {hasOvernight && (
{formatDraftDateTime(draftDate, draftTimezone)} : "TBD"} sub={draftDate ? {formatRelativeDate(draftDate)} : undefined} /> {formatHHMM(overnightPauseStart ?? "")} – {formatHHMM(overnightPauseEnd ?? "")} } sub={overnightPauseMode === "league" && overnightPauseTimezone ? overnightPauseTimezone : "per-user timezone"} />
)} {/* Mechanics row: Rounds + Your Pick? + Timer (+ Draft Date when no overnight) */}
{!hasOvernight && ( {formatDraftDateTime(draftDate, draftTimezone)} : "TBD"} sub={draftDate ? {formatRelativeDate(draftDate)} : undefined} /> )} {hasPosition && ( )} {timerPanel} {/* placeholder to keep grid balanced on mobile when only 2 items */} {hasOvernight && !hasPosition &&
}
); } // ─── Helpers ────────────────────────────────────────────────────────────────── /** Formats "23:00" → "11 PM", "07:00" → "7 AM". */ function formatHHMM(hhmm: string): string { const [hStr, mStr] = hhmm.split(":"); const h = parseInt(hStr, 10); const m = parseInt(mStr, 10); const period = h < 12 ? "AM" : "PM"; const h12 = h % 12 === 0 ? 12 : h % 12; return m === 0 ? `${h12} ${period}` : `${h12}:${String(m).padStart(2, "0")} ${period}`; } function formatDraftDateTime(date: Date, timezone: string | null | undefined): string { const parts = new Intl.DateTimeFormat("en-US", { timeZone: getDisplayTimezone(timezone), month: "long", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", hour12: true, timeZoneName: "shortGeneric", }).formatToParts(date); const getPart = (type: Intl.DateTimeFormatPartTypes) => ( parts.find((part) => part.type === type)?.value ?? "" ); return `${getPart("month")} ${getPart("day")}, ${getPart("year")} ${getPart("hour")}:${getPart("minute")}${getPart("dayPeriod")} ${getPart("timeZoneName")}`; } function getDisplayTimezone(timezone: string | null | undefined): string { if (!timezone) return "UTC"; try { new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(); return timezone; } catch { return "UTC"; } } function formatRelativeDate(date: Date): string { const value = formatDistanceToNow(date, { addSuffix: true }); return value.charAt(0).toUpperCase() + value.slice(1); } /** Converts a seconds value to a short human-readable string, e.g. "1 minute", "10 seconds", "2 hours". */ function formatHumanTime(seconds: number, options: { attributive?: boolean } = {}): string { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; const parts: string[] = []; const plural = (value: number) => (!options.attributive && value !== 1 ? "s" : ""); if (h > 0) parts.push(`${h} hour${plural(h)}`); if (m > 0) parts.push(`${m} minute${plural(m)}`); if (s > 0) parts.push(`${s} second${plural(s)}`); return parts.join(" ") || "0 seconds"; } // ─── Bare variant (active / completed) ──────────────────────────────────────── function DraftInfoBareVariant({ draftRounds, draftTimerMode, draftInitialTime, draftIncrementTime, sportsCount, draftDateTime, draftBoardHref, }: DraftInfoCardProps) { const flexSpots = Math.max(0, draftRounds - sportsCount); return (
Draft
View Draft Board
{/* Mode icon */}
{draftTimerMode === "chess_clock" ? : }
{draftDateTime && (
{format(new Date(draftDateTime), "MMM d, yyyy")} | {format(new Date(draftDateTime), "p")}
)}
{draftRounds} rounds ({sportsCount} sport{sportsCount !== 1 ? "s" : ""} {flexSpots > 0 && ` + ${flexSpots} flex`})
{draftTimerMode === "standard" ? `Standard — ${formatHumanTime(draftInitialTime)}/pick` : `Chess Clock — ${formatHumanTime(draftInitialTime)} + ${formatHumanTime(draftIncrementTime)} increment`}
); } // ─── Public API ─────────────────────────────────────────────────────────────── export function DraftInfoCard(props: DraftInfoCardProps) { if (props.isDraftOrPreDraft) { return ; } return ; }