diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index 97de1c4..a062e99 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -13,6 +13,7 @@ import { findAllSportsSeasons } from "~/models/sports-season"; import { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot"; import { deleteAllDraftPicks } from "~/models/draft-pick"; import { clearAllQueuesForSeason } from "~/models/draft-queue"; +import { deleteSeasonTimers } from "~/models/draft-timer"; import type { Route } from "./+types/$leagueId.settings"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; @@ -284,21 +285,24 @@ export async function action(args: Route.ActionArgs) { return { error: "Only admins can reset the draft" }; } - // Check if season status is draft or completed - if (season.status !== "draft" && season.status !== "completed" && season.status !== "active") { - return { error: "Draft can only be reset after it has started" }; - } - try { // Delete all draft picks await deleteAllDraftPicks(season.id); - + // Clear all draft queues await clearAllQueuesForSeason(season.id); - - // Set season status back to pre_draft (keeps draft order intact) - await updateSeason(season.id, { status: "pre_draft" }); - + + // Delete all draft timers + await deleteSeasonTimers(season.id); + + // Set season status back to pre_draft and reset draft state (keeps draft order intact) + await updateSeason(season.id, { + status: "pre_draft", + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); + return { success: true, message: "Draft has been reset successfully. Draft order preserved." }; } catch (error) { console.error("Error resetting draft:", error); @@ -676,7 +680,6 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone mode="single" selected={draftDate} onSelect={setDraftDate} - initialFocus disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))} /> @@ -1017,11 +1020,11 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone {/* Reset Draft - Admin Only */} - {isAdmin && season && (season.status === "draft" || season.status === "completed" || season.status === "active") && ( + {isAdmin && season && (

Reset Draft

- Delete all draft picks and reset the season to pre-draft status. The draft order will be preserved. + Delete all draft picks, queues, and timers, and reset the season to pre-draft status. The draft order will be preserved.

@@ -1033,7 +1036,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone Reset the draft? - This will delete all draft picks and draft queues, and set the season back to pre-draft status. + This will delete all draft picks, draft queues, and draft timers, and set the season back to pre-draft status. The draft order will remain intact. This action cannot be undone. diff --git a/app/routes/leagues/__tests__/draft-reset.test.ts b/app/routes/leagues/__tests__/draft-reset.test.ts index a3e0a65..685c103 100644 --- a/app/routes/leagues/__tests__/draft-reset.test.ts +++ b/app/routes/leagues/__tests__/draft-reset.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { deleteAllDraftPicks } from '~/models/draft-pick'; import { clearAllQueuesForSeason } from '~/models/draft-queue'; +import { deleteSeasonTimers } from '~/models/draft-timer'; import { updateSeason } from '~/models/season'; import { isUserAdminByClerkId } from '~/models/user'; @@ -38,6 +39,10 @@ vi.mock('~/models/draft-queue', () => ({ clearAllQueuesForSeason: vi.fn(), })); +vi.mock('~/models/draft-timer', () => ({ + deleteSeasonTimers: vi.fn(), +})); + vi.mock('~/models/season', () => ({ updateSeason: vi.fn(), findCurrentSeasonWithSports: vi.fn(), @@ -119,6 +124,17 @@ describe('Draft Reset - Delete Operations', () => { expect(clearAllQueuesForSeason).toHaveBeenCalledTimes(1); }); + it('should delete all draft timers for a season', async () => { + const seasonId = 'season-1'; + + vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined); + + await deleteSeasonTimers(seasonId); + + expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId); + expect(deleteSeasonTimers).toHaveBeenCalledTimes(1); + }); + it('should handle errors when deleting draft picks fails', async () => { const seasonId = 'season-1'; const error = new Error('Database error'); @@ -136,6 +152,15 @@ describe('Draft Reset - Delete Operations', () => { await expect(clearAllQueuesForSeason(seasonId)).rejects.toThrow('Database error'); }); + + it('should handle errors when deleting timers fails', async () => { + const seasonId = 'season-1'; + const error = new Error('Database error'); + + vi.mocked(deleteSeasonTimers).mockRejectedValue(error); + + await expect(deleteSeasonTimers(seasonId)).rejects.toThrow('Database error'); + }); }); describe('Draft Reset - Season Status Update', () => { @@ -172,30 +197,30 @@ describe('Draft Reset - Status Validation', () => { it('should allow reset when season status is draft', () => { const seasonStatus = 'draft'; - const allowedStatuses = ['draft', 'completed', 'active']; + const validStatuses = ['pre_draft', 'draft', 'completed', 'active']; - expect(allowedStatuses.includes(seasonStatus)).toBe(true); + expect(validStatuses.includes(seasonStatus)).toBe(true); }); it('should allow reset when season status is completed', () => { const seasonStatus = 'completed'; - const allowedStatuses = ['draft', 'completed', 'active']; + const validStatuses = ['pre_draft', 'draft', 'completed', 'active']; - expect(allowedStatuses.includes(seasonStatus)).toBe(true); + expect(validStatuses.includes(seasonStatus)).toBe(true); }); it('should allow reset when season status is active', () => { const seasonStatus = 'active'; - const allowedStatuses = ['draft', 'completed', 'active']; + const validStatuses = ['pre_draft', 'draft', 'completed', 'active']; - expect(allowedStatuses.includes(seasonStatus)).toBe(true); + expect(validStatuses.includes(seasonStatus)).toBe(true); }); - it('should not allow reset when season status is pre_draft', () => { + it('should allow reset when season status is pre_draft', () => { const seasonStatus = 'pre_draft'; - const allowedStatuses = ['draft', 'completed', 'active']; + const validStatuses = ['pre_draft', 'draft', 'completed', 'active']; - expect(allowedStatuses.includes(seasonStatus)).toBe(false); + expect(validStatuses.includes(seasonStatus)).toBe(true); }); }); @@ -223,14 +248,29 @@ describe('Draft Reset - Complete Workflow', () => { await clearAllQueuesForSeason(seasonId); expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId); - // Step 4: Update season status + // Step 4: Delete draft timers + vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined); + await deleteSeasonTimers(seasonId); + expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId); + + // Step 5: Update season status and reset draft state const mockUpdatedSeason = createMockSeason({ id: seasonId, status: 'pre_draft' }); vi.mocked(updateSeason).mockResolvedValue(mockUpdatedSeason); - const result = await updateSeason(seasonId, { status: 'pre_draft' }); + const result = await updateSeason(seasonId, { + status: 'pre_draft', + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); expect(result.status).toBe('pre_draft'); - expect(updateSeason).toHaveBeenCalledWith(seasonId, { status: 'pre_draft' }); + expect(updateSeason).toHaveBeenCalledWith(seasonId, { + status: 'pre_draft', + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); }); it('should not proceed with reset if admin check fails', async () => { @@ -244,6 +284,7 @@ describe('Draft Reset - Complete Workflow', () => { // Should not call any reset functions expect(deleteAllDraftPicks).not.toHaveBeenCalled(); expect(clearAllQueuesForSeason).not.toHaveBeenCalled(); + expect(deleteSeasonTimers).not.toHaveBeenCalled(); expect(updateSeason).not.toHaveBeenCalled(); }); @@ -385,6 +426,10 @@ describe('Draft Reset - Data Integrity', () => { callOrder.push('clearAllQueuesForSeason'); }); + vi.mocked(deleteSeasonTimers).mockImplementation(async () => { + callOrder.push('deleteSeasonTimers'); + }); + vi.mocked(updateSeason).mockImplementation(async () => { callOrder.push('updateSeason'); return createMockSeason({ id: seasonId, status: 'pre_draft' }); @@ -393,12 +438,19 @@ describe('Draft Reset - Data Integrity', () => { // Execute in correct order await deleteAllDraftPicks(seasonId); await clearAllQueuesForSeason(seasonId); - await updateSeason(seasonId, { status: 'pre_draft' }); + await deleteSeasonTimers(seasonId); + await updateSeason(seasonId, { + status: 'pre_draft', + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); // Verify order expect(callOrder).toEqual([ 'deleteAllDraftPicks', 'clearAllQueuesForSeason', + 'deleteSeasonTimers', 'updateSeason', ]); }); @@ -409,17 +461,30 @@ describe('Draft Reset - Data Integrity', () => { // All operations reference the same season vi.mocked(deleteAllDraftPicks).mockResolvedValue(undefined); vi.mocked(clearAllQueuesForSeason).mockResolvedValue(undefined); + vi.mocked(deleteSeasonTimers).mockResolvedValue(undefined); vi.mocked(updateSeason).mockResolvedValue( createMockSeason({ id: seasonId, status: 'pre_draft' }) ); await deleteAllDraftPicks(seasonId); await clearAllQueuesForSeason(seasonId); - await updateSeason(seasonId, { status: 'pre_draft' }); + await deleteSeasonTimers(seasonId); + await updateSeason(seasonId, { + status: 'pre_draft', + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); // All operations used the same season ID expect(deleteAllDraftPicks).toHaveBeenCalledWith(seasonId); expect(clearAllQueuesForSeason).toHaveBeenCalledWith(seasonId); - expect(updateSeason).toHaveBeenCalledWith(seasonId, { status: 'pre_draft' }); + expect(deleteSeasonTimers).toHaveBeenCalledWith(seasonId); + expect(updateSeason).toHaveBeenCalledWith(seasonId, { + status: 'pre_draft', + currentPickNumber: null, + draftStartedAt: null, + draftPaused: false, + }); }); });