feat: add draft pause/resume functionality with UI controls and socket events
This commit is contained in:
parent
a7f5df923f
commit
06db7fde7a
4 changed files with 211 additions and 0 deletions
|
|
@ -18,6 +18,8 @@ export default [
|
|||
route("api/queue/reorder", "routes/api/queue.reorder.ts"),
|
||||
route("api/draft/make-pick", "routes/api/draft.make-pick.ts"),
|
||||
route("api/draft/start", "routes/api/draft.start.ts"),
|
||||
route("api/draft/pause", "routes/api/draft.pause.ts"),
|
||||
route("api/draft/resume", "routes/api/draft.resume.ts"),
|
||||
route("api/draft/force-autopick", "routes/api/draft.force-autopick.ts"),
|
||||
route("api/draft/force-manual-pick", "routes/api/draft.force-manual-pick.ts"),
|
||||
route("user-profile", "routes/user-profile.tsx"),
|
||||
|
|
|
|||
77
app/routes/api/draft.pause.ts
Normal file
77
app/routes/api/draft.pause.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { getAuth } from "@clerk/react-router/ssr.server";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { database } from "~/database/context";
|
||||
import * as schema from "~/database/schema";
|
||||
|
||||
export async function action(args: any) {
|
||||
const { request } = args;
|
||||
const auth = await getAuth(args);
|
||||
const userId = (auth as any).userId as string | null;
|
||||
|
||||
if (!userId) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const seasonId = formData.get("seasonId") as string;
|
||||
|
||||
if (!seasonId) {
|
||||
return Response.json({ error: "Season ID is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const db = database();
|
||||
|
||||
// Get season
|
||||
const season = await db.query.seasons.findFirst({
|
||||
where: eq(schema.seasons.id, seasonId),
|
||||
with: {
|
||||
league: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!season) {
|
||||
return Response.json({ error: "Season not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Verify user is commissioner
|
||||
const isCommissioner = await db.query.commissioners.findFirst({
|
||||
where: and(
|
||||
eq(schema.commissioners.leagueId, season.leagueId),
|
||||
eq(schema.commissioners.userId, userId)
|
||||
),
|
||||
});
|
||||
|
||||
if (!isCommissioner) {
|
||||
return Response.json(
|
||||
{ error: "Only commissioners can pause the draft" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if draft is active
|
||||
if (season.status !== "draft") {
|
||||
return Response.json(
|
||||
{ error: "Draft is not active" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Pause the draft
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({ draftPaused: true })
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Emit socket event
|
||||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
io.to(`draft-${seasonId}`).emit("draft-paused", {
|
||||
seasonId,
|
||||
paused: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Socket.IO error:", error);
|
||||
}
|
||||
|
||||
return Response.json({ success: true, paused: true });
|
||||
}
|
||||
77
app/routes/api/draft.resume.ts
Normal file
77
app/routes/api/draft.resume.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { getAuth } from "@clerk/react-router/ssr.server";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { database } from "~/database/context";
|
||||
import * as schema from "~/database/schema";
|
||||
|
||||
export async function action(args: any) {
|
||||
const { request } = args;
|
||||
const auth = await getAuth(args);
|
||||
const userId = (auth as any).userId as string | null;
|
||||
|
||||
if (!userId) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const seasonId = formData.get("seasonId") as string;
|
||||
|
||||
if (!seasonId) {
|
||||
return Response.json({ error: "Season ID is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const db = database();
|
||||
|
||||
// Get season
|
||||
const season = await db.query.seasons.findFirst({
|
||||
where: eq(schema.seasons.id, seasonId),
|
||||
with: {
|
||||
league: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!season) {
|
||||
return Response.json({ error: "Season not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Verify user is commissioner
|
||||
const isCommissioner = await db.query.commissioners.findFirst({
|
||||
where: and(
|
||||
eq(schema.commissioners.leagueId, season.leagueId),
|
||||
eq(schema.commissioners.userId, userId)
|
||||
),
|
||||
});
|
||||
|
||||
if (!isCommissioner) {
|
||||
return Response.json(
|
||||
{ error: "Only commissioners can resume the draft" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if draft is active
|
||||
if (season.status !== "draft") {
|
||||
return Response.json(
|
||||
{ error: "Draft is not active" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Resume the draft
|
||||
await db
|
||||
.update(schema.seasons)
|
||||
.set({ draftPaused: false })
|
||||
.where(eq(schema.seasons.id, seasonId));
|
||||
|
||||
// Emit socket event
|
||||
try {
|
||||
const io = (global as any).__socketIO;
|
||||
io.to(`draft-${seasonId}`).emit("draft-resumed", {
|
||||
seasonId,
|
||||
paused: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Socket.IO error:", error);
|
||||
}
|
||||
|
||||
return Response.json({ success: true, paused: false });
|
||||
}
|
||||
|
|
@ -183,6 +183,7 @@ export default function DraftRoom() {
|
|||
const [sportFilter, setSportFilter] = useState<string>("all");
|
||||
const [queue, setQueue] = useState(userQueue);
|
||||
const [timeRemaining, setTimeRemaining] = useState<number | null>(null);
|
||||
const [isPaused, setIsPaused] = useState(season.draftPaused || false);
|
||||
const [teamTimers, setTeamTimers] = useState<Record<string, number>>(() => {
|
||||
// Initialize with timers from loader data
|
||||
const timersMap: Record<string, number> = {};
|
||||
|
|
@ -218,12 +219,24 @@ export default function DraftRoom() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleDraftPaused = () => {
|
||||
setIsPaused(true);
|
||||
};
|
||||
|
||||
const handleDraftResumed = () => {
|
||||
setIsPaused(false);
|
||||
};
|
||||
|
||||
on("pick-made", handlePickMade);
|
||||
on("timer-update", handleTimerUpdate);
|
||||
on("draft-paused", handleDraftPaused);
|
||||
on("draft-resumed", handleDraftResumed);
|
||||
|
||||
return () => {
|
||||
off("pick-made", handlePickMade);
|
||||
off("timer-update", handleTimerUpdate);
|
||||
off("draft-paused", handleDraftPaused);
|
||||
off("draft-resumed", handleDraftResumed);
|
||||
};
|
||||
}, [on, off, currentPick]);
|
||||
|
||||
|
|
@ -295,6 +308,34 @@ export default function DraftRoom() {
|
|||
}
|
||||
};
|
||||
|
||||
const handlePauseDraft = async () => {
|
||||
const formData = new FormData();
|
||||
formData.append("seasonId", season.id);
|
||||
|
||||
const response = await fetch("/api/draft/pause", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResumeDraft = async () => {
|
||||
const formData = new FormData();
|
||||
formData.append("seasonId", season.id);
|
||||
|
||||
const response = await fetch("/api/draft/resume", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsPaused(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMakePick = async (participantId: string) => {
|
||||
const formData = new FormData();
|
||||
formData.append("seasonId", season.id);
|
||||
|
|
@ -494,6 +535,20 @@ export default function DraftRoom() {
|
|||
{isCommissioner && season.status === "pre_draft" && (
|
||||
<Button onClick={handleStartDraft}>Start Draft</Button>
|
||||
)}
|
||||
|
||||
{isCommissioner && season.status === "draft" && (
|
||||
<>
|
||||
{isPaused ? (
|
||||
<Button onClick={handleResumeDraft} variant="default">
|
||||
Resume Draft
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handlePauseDraft} variant="outline">
|
||||
Pause Draft
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue