* Show "Syncing Draft State..." overlay during draft reconnection When a user reconnects to a draft room (socket reconnect, tab visibility return, etc.), there was a gap between when the socket connected and when the full draft state sync arrived. The ConnectionOverlay vanished instantly on reconnect, showing potentially stale data. Add an isSyncing flag that keeps the overlay visible with "Syncing Draft State..." messaging until the actual data arrives via socket or HTTP revalidation. A 5-second safety timeout prevents the overlay from getting stuck. Fixes #78 * Add team names utility tests
183 lines
7.1 KiB
TypeScript
183 lines
7.1 KiB
TypeScript
import { useState } from "react";
|
|
import { useMediaQuery } from "~/hooks/useMediaQuery";
|
|
import type * as schema from "~/database/schema";
|
|
import type { getDraftPicksForSeason } from "~/models/draft-pick";
|
|
import type { findSeasonWithTeamsAndLeague } from "~/models/season";
|
|
import type { findDraftSlotsBySeasonId } from "~/models/draft-slot";
|
|
import type { getSeasonAutodraftSettings } from "~/models/autodraft-settings";
|
|
import type { getSeasonTimers } from "~/models/draft-timer";
|
|
|
|
export type QueueItem = typeof schema.draftQueue.$inferSelect;
|
|
|
|
export type AutodraftStatusEntry = {
|
|
isEnabled: boolean;
|
|
mode: "next_pick" | "while_on";
|
|
queueOnly: boolean;
|
|
};
|
|
|
|
type DraftPicks = Awaited<ReturnType<typeof getDraftPicksForSeason>>;
|
|
type Season = NonNullable<Awaited<ReturnType<typeof findSeasonWithTeamsAndLeague>>>;
|
|
type DraftSlots = Awaited<ReturnType<typeof findDraftSlotsBySeasonId>>;
|
|
type AutodraftSettings = Awaited<ReturnType<typeof getSeasonAutodraftSettings>>;
|
|
type Timers = Awaited<ReturnType<typeof getSeasonTimers>>;
|
|
|
|
interface UseDraftRoomStateParams {
|
|
draftPicks: DraftPicks;
|
|
season: Season;
|
|
userTeam: { id: string } | undefined;
|
|
isCommissioner: boolean;
|
|
autodraftSettings: AutodraftSettings;
|
|
timers: Timers;
|
|
draftSlots: DraftSlots;
|
|
userQueue: QueueItem[];
|
|
initialWatchedParticipantIds: string[];
|
|
}
|
|
|
|
export function useDraftRoomState({
|
|
draftPicks,
|
|
season,
|
|
userTeam,
|
|
isCommissioner,
|
|
autodraftSettings,
|
|
timers,
|
|
draftSlots,
|
|
userQueue,
|
|
initialWatchedParticipantIds,
|
|
}: UseDraftRoomStateParams) {
|
|
const [picks, setPicks] = useState(draftPicks);
|
|
const [currentPick, setCurrentPick] = useState(season.currentPickNumber || 1);
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
const [hideDrafted, setHideDrafted] = useState(true);
|
|
const [hideIneligible, setHideIneligible] = useState(true);
|
|
const [hideCompletedSports, setHideCompletedSports] = useState(false);
|
|
const [sportFilters, setSportFilters] = useState<string[]>([]);
|
|
const [queue, setQueue] = useState<QueueItem[]>(userQueue);
|
|
const [isPaused, setIsPaused] = useState(season.draftPaused || false);
|
|
const [isDraftComplete, setIsDraftComplete] = useState(
|
|
season.status === "active" || season.status === "completed"
|
|
);
|
|
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
|
if (typeof window === "undefined") return false;
|
|
const stored = localStorage.getItem("draftSidebarCollapsed");
|
|
return stored ? JSON.parse(stored) : false;
|
|
});
|
|
const [activeTab, setActiveTab] = useState<"participants" | "board" | "rosters" | "summary">("participants");
|
|
const [mobileTab, setMobileTab] = useState<"available" | "queue" | "board" | "teams" | "controls">(
|
|
!userTeam && isCommissioner ? "board" : "available"
|
|
);
|
|
const [teamsSubTab, setTeamsSubTab] = useState<"rosters" | "summary">("rosters");
|
|
const [boardSubTab, setBoardSubTab] = useState<"board" | "pick-list">("board");
|
|
const isMobile = useMediaQuery("(max-width: 767px)");
|
|
|
|
const [autodraftStatus, setAutodraftStatus] = useState<Record<string, AutodraftStatusEntry>>(() => {
|
|
const status: Record<string, AutodraftStatusEntry> = {};
|
|
autodraftSettings.forEach((setting) => {
|
|
status[setting.teamId] = {
|
|
isEnabled: setting.isEnabled,
|
|
mode: setting.mode,
|
|
queueOnly: setting.queueOnly,
|
|
};
|
|
});
|
|
return status;
|
|
});
|
|
|
|
const [connectedTeams, setConnectedTeams] = useState<Set<string>>(new Set());
|
|
|
|
const [watchedParticipantIds, setWatchedParticipantIds] = useState<Set<string>>(
|
|
() => new Set(initialWatchedParticipantIds)
|
|
);
|
|
|
|
const [showOnlyWatched, setShowOnlyWatched] = useState(false);
|
|
|
|
const [teamTimers, setTeamTimers] = useState<Record<string, number>>(() => {
|
|
const timersMap: Record<string, number> = {};
|
|
const initialTime = season.draftInitialTime || 120;
|
|
|
|
if (timers.length > 0) {
|
|
timers.forEach((timer) => {
|
|
timersMap[timer.teamId] = timer.timeRemaining;
|
|
});
|
|
} else {
|
|
draftSlots.forEach((slot) => {
|
|
timersMap[slot.team.id] = initialTime;
|
|
});
|
|
}
|
|
|
|
return timersMap;
|
|
});
|
|
|
|
const [forcePickDialogOpen, setForcePickDialogOpen] = useState(false);
|
|
const [selectedPickSlot, setSelectedPickSlot] = useState<{
|
|
pickNumber: number;
|
|
teamId: string;
|
|
} | null>(null);
|
|
|
|
const [replacePickDialogOpen, setReplacePickDialogOpen] = useState(false);
|
|
const [replacePickSlot, setReplacePickSlot] = useState<{
|
|
pickNumber: number;
|
|
teamId: string;
|
|
oldParticipantId: string;
|
|
} | null>(null);
|
|
|
|
const [rollbackConfirmOpen, setRollbackConfirmOpen] = useState(false);
|
|
const [rollbackPickNumber, setRollbackPickNumber] = useState<number | null>(null);
|
|
const [isRollingBack, setIsRollingBack] = useState(false);
|
|
|
|
const [timeBankDialogOpen, setTimeBankDialogOpen] = useState(false);
|
|
const [timeBankTeamId, setTimeBankTeamId] = useState<string | null>(null);
|
|
const [commissionerAutodraftDialogOpen, setCommissionerAutodraftDialogOpen] = useState(false);
|
|
const [commissionerAutodraftTeamId, setCommissionerAutodraftTeamId] = useState<string | null>(null);
|
|
const [timeBankAmount, setTimeBankAmount] = useState("1");
|
|
const [timeBankUnit, setTimeBankUnit] = useState<"seconds" | "minutes" | "hours">("minutes");
|
|
const [timeBankDirection, setTimeBankDirection] = useState<"add" | "remove">("add");
|
|
const [isAdjustingTimeBank, setIsAdjustingTimeBank] = useState(false);
|
|
|
|
const [isOvernightPause, setIsOvernightPause] = useState(false);
|
|
const [overnightResumesAt, setOvernightResumesAt] = useState<Date | null>(null);
|
|
const [roomClosed, setRoomClosed] = useState(false);
|
|
const [isSyncing, setIsSyncing] = useState(false);
|
|
|
|
return {
|
|
picks, setPicks,
|
|
currentPick, setCurrentPick,
|
|
searchQuery, setSearchQuery,
|
|
hideDrafted, setHideDrafted,
|
|
hideIneligible, setHideIneligible,
|
|
hideCompletedSports, setHideCompletedSports,
|
|
sportFilters, setSportFilters,
|
|
queue, setQueue,
|
|
isPaused, setIsPaused,
|
|
isDraftComplete, setIsDraftComplete,
|
|
sidebarCollapsed, setSidebarCollapsed,
|
|
activeTab, setActiveTab,
|
|
mobileTab, setMobileTab,
|
|
teamsSubTab, setTeamsSubTab,
|
|
boardSubTab, setBoardSubTab,
|
|
isMobile,
|
|
autodraftStatus, setAutodraftStatus,
|
|
connectedTeams, setConnectedTeams,
|
|
watchedParticipantIds, setWatchedParticipantIds,
|
|
showOnlyWatched, setShowOnlyWatched,
|
|
teamTimers, setTeamTimers,
|
|
forcePickDialogOpen, setForcePickDialogOpen,
|
|
selectedPickSlot, setSelectedPickSlot,
|
|
replacePickDialogOpen, setReplacePickDialogOpen,
|
|
replacePickSlot, setReplacePickSlot,
|
|
rollbackConfirmOpen, setRollbackConfirmOpen,
|
|
rollbackPickNumber, setRollbackPickNumber,
|
|
isRollingBack, setIsRollingBack,
|
|
timeBankDialogOpen, setTimeBankDialogOpen,
|
|
timeBankTeamId, setTimeBankTeamId,
|
|
commissionerAutodraftDialogOpen, setCommissionerAutodraftDialogOpen,
|
|
commissionerAutodraftTeamId, setCommissionerAutodraftTeamId,
|
|
timeBankAmount, setTimeBankAmount,
|
|
timeBankUnit, setTimeBankUnit,
|
|
timeBankDirection, setTimeBankDirection,
|
|
isAdjustingTimeBank, setIsAdjustingTimeBank,
|
|
isOvernightPause, setIsOvernightPause,
|
|
overnightResumesAt, setOvernightResumesAt,
|
|
roomClosed, setRoomClosed,
|
|
isSyncing, setIsSyncing,
|
|
};
|
|
}
|