brackt/app/components/draft/DraftPickCell.tsx
chrisp 841760541d
All checks were successful
🚀 Deploy / 🧪 Test (push) Successful in 1m27s
🚀 Deploy / ʦ TypeScript (push) Successful in 1m16s
🚀 Deploy / 🔍 Lint (push) Successful in 1m55s
🚀 Deploy / 🐳 Build (push) Successful in 14m23s
🚀 Deploy / 🚀 Deploy (push) Successful in 10s
Fix snake draft Discord pick numbering and small UX tweaks (#2)
## Summary

- **Discord snake draft fix**: pick notifications now show the sequential pick-in-round (e.g. "Round 2, Pick 9") rather than the snake-adjusted slot position ("Round 2, Pick 5"). Also removes the now-unused \`pickInRound\` param from \`notifyPickMadeOnDiscord\` and adds a regression test for the 13-team case.
- **League card**: league name in draft-in-progress cards is now a link to the league homepage (the Enter Draft button still goes to the draft room).
- **Overnight pause label**: shortened to "🌙 Pause" — the "Resumes 4:00 AM" line below already provides context, so "Overnight" was just causing wrapping.
- **Queue & Picks backgrounds**: items use \`bg-card\` instead of \`bg-muted\` for better visual separation from the panel background.

## Test plan

- [x] Discord: in a snake draft, verify pick #22 of 13 shows "Round 2, Pick 9" in Discord
- [x] League card: dashboard with a draft-in-progress league — click name → league homepage, click Enter Draft → draft room
- [x] Overnight pause: pause a draft overnight — cell shows "🌙 Pause" on one line, "Resumes X:XX" below
- [x] Queue/Picks: open draft room and confirm queue items and recent picks visually pop from the sidebar background
- [x] Unit tests: `npm run test:run` passes (13 discord tests)

Co-authored-by: Chris Parsons <chrisparsons1127@gmail.com>
Reviewed-on: #2
2026-05-22 04:18:11 +00:00

160 lines
4.9 KiB
TypeScript

import { forwardRef, type CSSProperties, type ComponentPropsWithoutRef } from "react";
import { cn } from "~/lib/utils";
import type { SeasonStatus } from "~/models/season";
import { BRACKT_GRADIENT } from "~/lib/brand";
export type { CoronaState } from "~/lib/corona-states";
import type { CoronaState } from "~/lib/corona-states";
type CoronaVariant =
| "gradient"
| "electric"
| "emerald"
| "amber"
| "coral";
type DraftPickCellProps = ComponentPropsWithoutRef<"div"> & {
pickNumber: number;
round: number;
pickInRound: number;
state: "picked" | "current" | "upcoming";
pick?: {
participant: { name: string };
sport: { name: string };
};
corona?: CoronaVariant;
coronaState?: CoronaState;
seasonStatus?: SeasonStatus;
draftPaused?: boolean;
isOvernightPause?: boolean;
resumesAt?: Date;
};
const coronaClasses: Record<CoronaVariant, string> = {
gradient: "[background:var(--brackt-gradient)]",
electric: "bg-electric",
emerald: "bg-emerald-500",
amber: "bg-amber-accent",
coral: "bg-coral-accent",
};
function getCoronaStyle(cs: CoronaState): CSSProperties {
switch (cs.type) {
case "eliminated":
return { background: "oklch(0.45 0.20 25)" };
case "pending":
return { background: "rgba(255, 255, 255, 0.08)" };
case "scored":
return {
background: BRACKT_GRADIENT,
opacity: cs.brightness,
};
}
}
export const DraftPickCell = forwardRef<HTMLDivElement, DraftPickCellProps>(
function DraftPickCell({
pickNumber,
round,
pickInRound,
state,
pick,
corona = "gradient",
coronaState,
seasonStatus,
draftPaused,
isOvernightPause,
resumesAt,
className,
children,
...rest
}, ref) {
const isCurrent = state === "current";
const showCorona = !isCurrent;
let currentLabel = "On The Clock";
if (seasonStatus === "pre_draft") {
currentLabel = "Up Next";
} else if (seasonStatus === "draft" && isOvernightPause) {
currentLabel = "🌙 Pause";
} else if (seasonStatus === "draft" && draftPaused) {
currentLabel = "Paused Draft";
}
return (
<div
ref={ref}
className={cn(
"relative flex-1 min-w-20 overflow-hidden",
className,
)}
{...rest}
>
{isCurrent ? (
<div className="absolute inset-0 rounded-lg [background:var(--brackt-gradient)] animate-pulse-gradient" />
) : coronaState ? (
<div
className="absolute top-0.5 bottom-0.5 left-0 right-0 rounded-lg"
style={getCoronaStyle(coronaState)}
/>
) : state === "picked" ? (
<div
className={cn(
"absolute top-0.5 bottom-0.5 left-0 right-0 rounded-lg",
coronaClasses[corona],
)}
/>
) : state === "upcoming" ? (
<div
className="absolute top-0.5 bottom-0.5 left-0 right-0 rounded-lg"
style={{ background: "rgba(255, 255, 255, 0.08)" }}
/>
) : null}
<div
className={cn(
"relative z-10 h-14 border-2 rounded-lg p-1.5 transition-all",
(showCorona || isCurrent) && "mr-1",
isCurrent
? "bg-muted/80 border-transparent"
: showCorona
? "bg-muted border-transparent"
: "border-border bg-card",
)}
title={`Overall Pick #${pickNumber}`}
>
<div className={cn("flex flex-col", isCurrent ? "items-center justify-center h-full" : "gap-0.5")}>
{!isCurrent && (
<div className="flex items-center justify-between">
<div className="text-[10px] font-mono text-muted-foreground leading-3">
{round}.{String(pickInRound).padStart(2, "0")}
</div>
{coronaState && coronaState.type !== "pending" && (
<span className={`text-[10px] font-mono font-bold ${coronaState.type === "eliminated" ? "text-coral-accent" : "text-emerald-400"}`}>
{coronaState.type === "scored" && "▲"}{coronaState.points}
</span>
)}
</div>
)}
{state === "picked" && pick ? (
<>
<div className="font-semibold truncate text-xs leading-4">{pick.participant.name}</div>
<div className="text-muted-foreground truncate text-[10px] leading-3">
{pick.sport.name}
</div>
</>
) : isCurrent ? (
<>
<div className="text-sm font-bold text-white">{currentLabel}</div>
{resumesAt && (
<div className="text-[10px] text-muted-foreground mt-0.5">
Resumes {resumesAt.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}
</div>
)}
</>
) : null}
</div>
{children && <div className="absolute inset-0 pointer-events-none [&_*]:pointer-events-auto">{children}</div>}
</div>
</div>
);
});
DraftPickCell.displayName = "DraftPickCell";