+ );
+}
+
+// ── Bet Card ──────────────────────────────────────────────────────────────────
+
+function BetCard({
+ bet,
+ huddleId,
+ myClerkId,
+ proposerName,
+ opponentName,
+ isCommissioner,
+}: {
+ bet: SideBet;
+ huddleId: string;
+ myClerkId: string;
+ proposerName: string;
+ opponentName: string;
+ isCommissioner: boolean;
+}) {
+ const [showSettle, setShowSettle] = useState(false);
+ const respondToBet = useRespondToBet();
+ const cancelBet = useCancelBet();
+
+ const isProposer = bet.proposerId === myClerkId;
+ const isOpponent = bet.opponentId === myClerkId;
+ const isParty = isProposer || isOpponent;
+
+ const winnerName =
+ bet.winnerId === bet.proposerId
+ ? proposerName
+ : bet.winnerId === bet.opponentId
+ ? opponentName
+ : null;
+
+ return (
+
+ {/* Header row */}
+
+
+
{bet.description}
+
+ Week {bet.week} · {formatAmount(bet.amount)}
+
+
+
+
+
+ {/* Parties */}
+
+ {proposerName}
+ challenged
+ {opponentName}
+
+
+ {/* Settlement note or winner */}
+ {bet.status === "settled" && (
+
+ {winnerName} won
+ {bet.settlementNote && (
+ — {bet.settlementNote}
+ )}
+
+ )}
+
+ {/* Actions */}
+
+ {/* Opponent responds to pending bet */}
+ {bet.status === "pending" && isOpponent && (
+ <>
+
+ respondToBet.mutate({ huddleId, betId: bet.id, response: "accepted" })
+ }
+ >
+ Accept
+
+
+ respondToBet.mutate({ huddleId, betId: bet.id, response: "rejected" })
+ }
+ >
+ Reject
+
+ >
+ )}
+
+ {/* Proposer waits on pending, can cancel */}
+ {bet.status === "pending" && isProposer && (
+
+ Waiting for {opponentName}…
+
+ )}
+
+ {/* Either party or commissioner can cancel pending/accepted */}
+ {(bet.status === "pending" || bet.status === "accepted") &&
+ (isParty || isCommissioner) && (
+ cancelBet.mutate({ huddleId, betId: bet.id })}
+ >
+ Cancel bet
+
+ )}
+
+ {/* Either party or commissioner can settle an accepted bet */}
+ {bet.status === "accepted" && (isParty || isCommissioner) && !showSettle && (
+ setShowSettle(true)}>Settle
+ )}
+
+
+ {showSettle && (
+
setShowSettle(false)}
+ />
+ )}
+
+ );
+}
+
+// ── Filters ───────────────────────────────────────────────────────────────────
+
+type Filter = "all" | "mine" | "pending" | "active" | "history";
+
+const FILTER_LABELS: { key: Filter; label: string }[] = [
+ { key: "all", label: "All" },
+ { key: "mine", label: "Mine" },
+ { key: "pending", label: "Pending" },
+ { key: "active", label: "Active" },
+ { key: "history", label: "History" },
+];
+
+// ── Main Page ─────────────────────────────────────────────────────────────────
+
+export function SideBetsPage() {
+ const selectedLeagueId = useAppSelector((state) => state.auth.selectedLeagueId);
+ const { user } = useUser();
+ const myClerkId = user?.id ?? "";
+
+ const huddle = useSelectedLeagueHuddle();
+ const huddleId = huddle?.id ?? null;
+
+ const { data: huddleDetail } = useHuddleDetail(huddleId);
+ const { data: nflState } = useNFLState();
+ const { data: rosters } = useLeagueRosters(selectedLeagueId);
+ const { data: leagueUsers } = useLeagueUsers(selectedLeagueId);
+ const { data: bets = [], isLoading } = useSideBets(huddleId);
+
+ const [filter, setFilter] = useState