diff --git a/app/components/draft/CommissionerControls.tsx b/app/components/draft/CommissionerControls.tsx
new file mode 100644
index 0000000..d00f7c9
--- /dev/null
+++ b/app/components/draft/CommissionerControls.tsx
@@ -0,0 +1,107 @@
+import { Button } from "~/components/ui/button";
+import { Play, Pause, FastForward } from "lucide-react";
+
+interface CommissionerControlsProps {
+ isDraftStarted: boolean;
+ isDraftPaused: boolean;
+ isDraftComplete: boolean;
+ currentTeamName: string;
+ onStartDraft: () => void;
+ onPauseDraft: () => void;
+ onResumeDraft: () => void;
+ onForcePick: () => void;
+}
+
+export function CommissionerControls({
+ isDraftStarted,
+ isDraftPaused,
+ isDraftComplete,
+ currentTeamName,
+ onStartDraft,
+ onPauseDraft,
+ onResumeDraft,
+ onForcePick,
+}: CommissionerControlsProps) {
+ if (isDraftComplete) {
+ return (
+
+ );
+ }
+
+ if (!isDraftStarted) {
+ return (
+
+
+ The draft has not started yet. Click below to begin the draft.
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Draft Status */}
+
+
+ Draft Status:
+
+ {isDraftPaused ? "Paused" : "Active"}
+
+
+
+ Current Pick:
+ {currentTeamName}
+
+
+
+ {/* Control Buttons */}
+
+ {isDraftPaused ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Help Text */}
+
+
+ Force Pick: Automatically selects the top player from the
+ current team's queue, or the highest EV available player if queue is empty.
+
+
+
+ );
+}
diff --git a/app/routes.ts b/app/routes.ts
index 2426d2e..6b7a80b 100644
--- a/app/routes.ts
+++ b/app/routes.ts
@@ -9,6 +9,7 @@ export default [
route("leagues/:leagueId/draft", "routes/leagues/$leagueId.draft.tsx"),
route("teams/:teamId/settings", "routes/teams/$teamId.settings.tsx"),
route("api/webhooks/clerk", "routes/api/webhooks/clerk.ts"),
+ route("api/draft-actions", "routes/api/draft-actions.ts"),
route("user-profile", "routes/user-profile.tsx"),
route("how-to-play", "routes/how-to-play.tsx"),
diff --git a/app/routes/api/draft-actions.ts b/app/routes/api/draft-actions.ts
new file mode 100644
index 0000000..269dacb
--- /dev/null
+++ b/app/routes/api/draft-actions.ts
@@ -0,0 +1,95 @@
+import { data, type ActionFunctionArgs } from "react-router";
+import { getAuth } from "@clerk/react-router/server";
+import {
+ addToQueue,
+ removeFromQueue,
+ reorderQueueWithSeason,
+ findTeamById,
+ getTeamQueue,
+} from "~/models";
+
+export async function action(args: ActionFunctionArgs) {
+ const { userId } = await getAuth(args);
+ if (!userId) {
+ return data({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const { request } = args;
+
+ const formData = await request.formData();
+ const actionType = formData.get("action");
+ const teamId = formData.get("teamId");
+
+ if (!teamId || typeof teamId !== "string") {
+ return data({ error: "Team ID is required" }, { status: 400 });
+ }
+
+ // Verify user owns this team
+ const team = await findTeamById(teamId);
+ if (!team || team.ownerId !== userId) {
+ return data({ error: "You do not own this team" }, { status: 403 });
+ }
+
+ try {
+ switch (actionType) {
+ case "add-to-queue": {
+ const participantId = formData.get("participantId");
+ const seasonId = formData.get("seasonId");
+
+ if (!participantId || typeof participantId !== "string") {
+ return data({ error: "Participant ID is required" }, { status: 400 });
+ }
+ if (!seasonId || typeof seasonId !== "string") {
+ return data({ error: "Season ID is required" }, { status: 400 });
+ }
+
+ // Get current queue to determine position
+ const currentQueue = await getTeamQueue(teamId);
+ const queuePosition = currentQueue.length + 1;
+
+ await addToQueue({
+ seasonId,
+ teamId,
+ participantId,
+ queuePosition,
+ });
+
+ return data({ success: true });
+ }
+
+ case "remove-from-queue": {
+ const queueItemId = formData.get("queueItemId");
+
+ if (!queueItemId || typeof queueItemId !== "string") {
+ return data({ error: "Queue item ID is required" }, { status: 400 });
+ }
+
+ await removeFromQueue(queueItemId);
+ return data({ success: true });
+ }
+
+ case "reorder-queue": {
+ const participantIds = formData.get("participantIds");
+ const seasonId = formData.get("seasonId");
+
+ if (!participantIds || typeof participantIds !== "string") {
+ return data({ error: "Participant IDs are required" }, { status: 400 });
+ }
+ if (!seasonId || typeof seasonId !== "string") {
+ return data({ error: "Season ID is required" }, { status: 400 });
+ }
+
+ const idsArray = JSON.parse(participantIds);
+ await reorderQueueWithSeason(seasonId, teamId, idsArray);
+
+ return data({ success: true });
+ }
+
+ default:
+ return data({ error: "Invalid action" }, { status: 400 });
+ }
+ } catch (error) {
+ console.error("Draft action error:", error);
+ return data({ error: "Failed to perform action" }, { status: 500 });
+ }
+}
diff --git a/app/routes/api/draft-commissioner.ts b/app/routes/api/draft-commissioner.ts
new file mode 100644
index 0000000..ec311a1
--- /dev/null
+++ b/app/routes/api/draft-commissioner.ts
@@ -0,0 +1,203 @@
+import { data, type ActionFunctionArgs } from "react-router";
+import { getAuth } from "@clerk/react-router/server";
+import {
+ findSeasonById,
+ updateSeason,
+ updateSeasonStatus,
+ findTeamsBySeasonId,
+ isCommissioner,
+ createDraftPick,
+ initializeDraftTimers,
+ updateTeamTimer,
+ getSeasonTimers,
+ autoPickForTeam,
+ calculatePickInfo,
+ removeFromQueue,
+ getTeamQueue,
+} from "~/models";
+import { getSocketIO } from "../../../server/socket.js";
+
+export async function action(args: ActionFunctionArgs) {
+ const { userId } = await getAuth(args);
+ if (!userId) {
+ return data({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const { request } = args;
+ const formData = await request.formData();
+ const actionType = formData.get("action");
+ const seasonId = formData.get("seasonId");
+
+ if (!seasonId || typeof seasonId !== "string") {
+ return data({ error: "Season ID is required" }, { status: 400 });
+ }
+
+ // Get season
+ const season = await findSeasonById(seasonId);
+ if (!season) {
+ return data({ error: "Season not found" }, { status: 404 });
+ }
+
+ // Verify user is commissioner
+ const isUserCommissioner = await isCommissioner(season.leagueId, userId);
+ if (!isUserCommissioner) {
+ return data({ error: "Only commissioners can perform this action" }, { status: 403 });
+ }
+
+ try {
+ const io = getSocketIO();
+
+ switch (actionType) {
+ case "start-draft": {
+ // Check if draft already started
+ if (season.draftStartedAt) {
+ return data({ error: "Draft has already started" }, { status: 400 });
+ }
+
+ // Get teams
+ const teams = await findTeamsBySeasonId(seasonId);
+
+ // Initialize timers
+ await initializeDraftTimers(seasonId, teams, season.draftInitialTime || 120);
+
+ // Update season
+ await updateSeason(seasonId, {
+ draftStartedAt: new Date(),
+ currentPickNumber: 1,
+ draftPaused: false,
+ });
+ await updateSeasonStatus(seasonId, "draft");
+
+ // Get updated timers
+ const timers = await getSeasonTimers(seasonId);
+
+ // Broadcast to all clients
+ io.to(`draft-${seasonId}`).emit("draft-started", {
+ seasonId,
+ startedAt: new Date(),
+ timers,
+ });
+
+ return data({ success: true });
+ }
+
+ case "pause-draft": {
+ const pause = formData.get("pause") === "true";
+
+ // Check if draft has started
+ if (!season.draftStartedAt) {
+ return data({ error: "Draft has not started yet" }, { status: 400 });
+ }
+
+ // Update season
+ await updateSeason(seasonId, { draftPaused: pause });
+
+ // Broadcast to all clients
+ io.to(`draft-${seasonId}`).emit("draft-paused", {
+ seasonId,
+ isPaused: pause,
+ });
+
+ return data({ success: true });
+ }
+
+ case "force-pick": {
+ const teamId = formData.get("teamId");
+
+ if (!teamId || typeof teamId !== "string") {
+ return data({ error: "Team ID is required" }, { status: 400 });
+ }
+
+ // Check if draft has started
+ if (!season.draftStartedAt) {
+ return data({ error: "Draft has not started" }, { status: 400 });
+ }
+
+ // Check if draft is complete
+ const teams = await findTeamsBySeasonId(seasonId);
+ const totalPicks = season.draftRounds * teams.length;
+ const currentPickNumber = season.currentPickNumber || 1;
+
+ if (currentPickNumber > totalPicks) {
+ return data({ error: "Draft is already complete" }, { status: 400 });
+ }
+
+ // Auto-pick for the team
+ const participantId = await autoPickForTeam(seasonId, teamId);
+
+ if (!participantId) {
+ return data({ error: "No available participants to pick" }, { status: 400 });
+ }
+
+ // Calculate pick info
+ const { round, pickInRound } = calculatePickInfo(currentPickNumber, teams.length);
+
+ // Create the draft pick
+ const pick = await createDraftPick({
+ seasonId,
+ teamId,
+ participantId,
+ pickNumber: currentPickNumber,
+ round,
+ pickInRound,
+ pickedByUserId: userId,
+ pickedByType: "commissioner",
+ timeUsed: 0,
+ });
+
+ // Remove from queue if it was queued
+ const queue = await getTeamQueue(teamId);
+ const queueItem = queue.find((item: any) => item.participantId === participantId);
+ if (queueItem) {
+ await removeFromQueue(queueItem.id);
+ }
+
+ // Update season to next pick
+ const nextPickNumber = currentPickNumber + 1;
+ await updateSeason(seasonId, { currentPickNumber: nextPickNumber });
+
+ // Add increment time to current team's timer
+ if (season.draftIncrementTime) {
+ const timers = await getSeasonTimers(seasonId);
+ const currentTimer = timers.find((t: any) => t.teamId === teamId);
+ if (currentTimer) {
+ const newTime = currentTimer.timeRemaining + season.draftIncrementTime;
+ await updateTeamTimer(teamId, newTime);
+ }
+ }
+
+ // Check if draft is complete
+ if (nextPickNumber > totalPicks) {
+ await updateSeasonStatus(seasonId, "active");
+ io.to(`draft-${seasonId}`).emit("draft-completed", {
+ seasonId,
+ });
+ }
+
+ // Broadcast pick to all clients in the room
+ io.to(`draft-${seasonId}`).emit("pick-made", {
+ pick,
+ nextPickNumber,
+ isDraftComplete: nextPickNumber > totalPicks,
+ });
+
+ // Emit queue update to team owner
+ const updatedQueue = await getTeamQueue(teamId);
+ io.to(`draft-${seasonId}`).emit("queue-updated", {
+ teamId,
+ queue: updatedQueue,
+ });
+
+ return data({ success: true, pick });
+ }
+
+ default:
+ return data({ error: "Invalid action" }, { status: 400 });
+ }
+ } catch (error) {
+ console.error("Commissioner action error:", error);
+ return data({
+ error: error instanceof Error ? error.message : "Failed to perform action"
+ }, { status: 500 });
+ }
+}
diff --git a/app/routes/api/draft-pick.ts b/app/routes/api/draft-pick.ts
new file mode 100644
index 0000000..0293154
--- /dev/null
+++ b/app/routes/api/draft-pick.ts
@@ -0,0 +1,175 @@
+import { data, type ActionFunctionArgs } from "react-router";
+import { getAuth } from "@clerk/react-router/server";
+import {
+ findSeasonById,
+ updateSeason,
+ updateSeasonStatus,
+ findTeamsBySeasonId,
+ isCommissioner,
+ createDraftPick,
+ isParticipantDrafted,
+ updateTeamTimer,
+ getSeasonTimers,
+ calculatePickInfo,
+ getTeamForPick,
+ findDraftSlotsBySeasonId,
+ removeFromQueue,
+ getTeamQueue,
+} from "~/models";
+import { getSocketIO } from "../../../server/socket.js";
+
+export async function action(args: ActionFunctionArgs) {
+ const { userId } = await getAuth(args);
+ if (!userId) {
+ return data({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const { request } = args;
+ const formData = await request.formData();
+
+ const seasonId = formData.get("seasonId");
+ const teamId = formData.get("teamId");
+ const participantId = formData.get("participantId");
+
+ if (!seasonId || typeof seasonId !== "string") {
+ return data({ error: "Season ID is required" }, { status: 400 });
+ }
+
+ if (!teamId || typeof teamId !== "string") {
+ return data({ error: "Team ID is required" }, { status: 400 });
+ }
+
+ if (!participantId || typeof participantId !== "string") {
+ return data({ error: "Participant ID is required" }, { status: 400 });
+ }
+
+ try {
+ // Get season
+ const season = await findSeasonById(seasonId);
+ if (!season) {
+ return data({ error: "Season not found" }, { status: 404 });
+ }
+
+ // Check if draft has started
+ if (!season.draftStartedAt) {
+ return data({ error: "Draft has not started" }, { status: 400 });
+ }
+
+ // Check if draft is paused
+ if (season.draftPaused) {
+ return data({ error: "Draft is currently paused" }, { status: 400 });
+ }
+
+ // Check if draft is complete
+ const teams = await findTeamsBySeasonId(seasonId);
+ const totalPicks = season.draftRounds * teams.length;
+ const currentPickNumber = season.currentPickNumber || 1;
+
+ if (currentPickNumber > totalPicks) {
+ return data({ error: "Draft is already complete" }, { status: 400 });
+ }
+
+ // Get draft slots to determine current team
+ const draftSlots = await findDraftSlotsBySeasonId(seasonId);
+ const draftOrder = draftSlots.map((slot: any) => ({
+ teamId: slot.teamId,
+ draftOrder: slot.draftOrder,
+ }));
+
+ const currentTeamId = getTeamForPick(currentPickNumber, draftOrder);
+
+ if (!currentTeamId) {
+ return data({ error: "Could not determine current team" }, { status: 400 });
+ }
+
+ // Verify it's the correct team's turn
+ if (teamId !== currentTeamId) {
+ return data({ error: "It is not this team's turn" }, { status: 400 });
+ }
+
+ // Verify user has permission (team owner or commissioner)
+ const team = teams.find((t: any) => t.id === teamId);
+ const isTeamOwner = team?.ownerId === userId;
+ const isUserCommissioner = await isCommissioner(season.leagueId, userId);
+
+ if (!isTeamOwner && !isUserCommissioner) {
+ return data({ error: "You do not have permission to make this pick" }, { status: 403 });
+ }
+
+ // Check if participant is already drafted
+ const isDrafted = await isParticipantDrafted(seasonId, participantId);
+ if (isDrafted) {
+ return data({ error: "This participant has already been drafted" }, { status: 400 });
+ }
+
+ // Calculate pick info
+ const { round, pickInRound } = calculatePickInfo(currentPickNumber, teams.length);
+
+ // Determine picked by type
+ const pickedByType = isUserCommissioner && !isTeamOwner ? "commissioner" : "owner";
+
+ // Create the draft pick
+ const pick = await createDraftPick({
+ seasonId,
+ teamId,
+ participantId,
+ pickNumber: currentPickNumber,
+ round,
+ pickInRound,
+ pickedByUserId: userId,
+ pickedByType,
+ timeUsed: 0,
+ });
+
+ // Remove from queue if it was queued
+ const queue = await getTeamQueue(teamId);
+ const queueItem = queue.find((item: any) => item.participantId === participantId);
+ if (queueItem) {
+ await removeFromQueue(queueItem.id);
+ }
+
+ // Update season to next pick
+ const nextPickNumber = currentPickNumber + 1;
+ await updateSeason(seasonId, { currentPickNumber: nextPickNumber });
+
+ // Add increment time to current team's timer
+ if (season.draftIncrementTime) {
+ const timers = await getSeasonTimers(seasonId);
+ const currentTimer = timers.find((t: any) => t.teamId === teamId);
+ if (currentTimer) {
+ const newTime = currentTimer.timeRemaining + season.draftIncrementTime;
+ await updateTeamTimer(teamId, newTime);
+ }
+ }
+
+ // Check if draft is complete
+ const io = getSocketIO();
+ if (nextPickNumber > totalPicks) {
+ await updateSeasonStatus(seasonId, "active");
+ io.to(`draft-${seasonId}`).emit("draft-completed", {
+ seasonId,
+ });
+ }
+
+ // Broadcast pick to all clients in the room
+ io.to(`draft-${seasonId}`).emit("pick-made", {
+ pick,
+ nextPickNumber,
+ isDraftComplete: nextPickNumber > totalPicks,
+ });
+
+ // Emit queue update to team owner
+ const updatedQueue = await getTeamQueue(teamId);
+ io.to(`draft-${seasonId}`).emit("queue-updated", {
+ teamId,
+ queue: updatedQueue,
+ });
+
+ return data({ success: true, pick });
+ } catch (error) {
+ console.error("Error making pick:", error);
+ return data({
+ error: error instanceof Error ? error.message : "Failed to make pick",
+ }, { status: 500 });
+ }
+}
diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx
index 9aba38e..9860b28 100644
--- a/app/routes/leagues/$leagueId.draft.tsx
+++ b/app/routes/leagues/$leagueId.draft.tsx
@@ -1,6 +1,7 @@
import { data, redirect } from "react-router";
import type { LoaderFunctionArgs } from "react-router";
import { getAuth } from "@clerk/react-router/server";
+import { useEffect, useState } from "react";
import {
findLeagueById,
findCurrentSeason,
@@ -11,10 +12,13 @@ import {
getSeasonTimers,
findSeasonWithSportsSeasons,
getTeamQueue,
+ getTeamForPick,
} from "~/models";
import { DraftGrid } from "~/components/draft/DraftGrid";
import { PlayerList } from "~/components/draft/PlayerList";
import { DraftQueue } from "~/components/draft/DraftQueue";
+import { CommissionerControls } from "~/components/draft/CommissionerControls";
+import { useDraftSocket } from "~/hooks/useDraftSocket";
export async function loader(args: LoaderFunctionArgs) {
const { userId } = await getAuth(args);
@@ -114,17 +118,173 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited ({
+ teamId: slot.teamId,
+ draftOrder: slot.draftOrder,
+ }));
+ const currentTeamId = getTeamForPick(currentPickNumber, draftOrder);
+ const currentTeam = teams.find((t: any) => t.id === currentTeamId);
+ const currentTeamName = currentTeam?.name || "Unknown";
+
+ // Socket event handlers
+ useEffect(() => {
+ // Listen for pick made
+ const handlePickMade = (data: any) => {
+ setDraftPicks((prev: any) => [...prev, data.pick]);
+ setCurrentPickNumber(data.nextPickNumber);
+ setIsDraftComplete(data.isDraftComplete);
+ };
+
+ // Listen for draft started
+ const handleDraftStarted = (data: any) => {
+ setIsDraftStarted(true);
+ setIsDraftPaused(false);
+ setTimers(data.timers);
+ };
+
+ // Listen for draft paused
+ const handleDraftPaused = (data: any) => {
+ setIsDraftPaused(data.isPaused);
+ };
+
+ // Listen for draft completed
+ const handleDraftCompleted = () => {
+ setIsDraftComplete(true);
+ };
+
+ // Listen for queue updates
+ const handleQueueUpdated = (data: any) => {
+ if (userTeam && data.teamId === userTeam.id) {
+ setQueueItems(data.queue);
+ }
+ };
+
+ on("pick-made", handlePickMade);
+ on("draft-started", handleDraftStarted);
+ on("draft-paused", handleDraftPaused);
+ on("draft-completed", handleDraftCompleted);
+ on("queue-updated", handleQueueUpdated);
+
+ return () => {
+ off("pick-made", handlePickMade);
+ off("draft-started", handleDraftStarted);
+ off("draft-paused", handleDraftPaused);
+ off("draft-completed", handleDraftCompleted);
+ off("queue-updated", handleQueueUpdated);
+ };
+ }, [on, off, userTeam]);
+
+ // Commissioner action handlers
+ const handleStartDraft = async () => {
+ try {
+ const formData = new FormData();
+ formData.append("action", "start-draft");
+ formData.append("seasonId", season.id);
+
+ const response = await fetch("/api/draft-commissioner", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ console.error("Failed to start draft:", error);
+ }
+ } catch (error) {
+ console.error("Error starting draft:", error);
+ }
+ };
+
+ const handlePauseDraft = async () => {
+ try {
+ const formData = new FormData();
+ formData.append("action", "pause-draft");
+ formData.append("seasonId", season.id);
+ formData.append("pause", "true");
+
+ const response = await fetch("/api/draft-commissioner", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ console.error("Failed to pause draft:", error);
+ }
+ } catch (error) {
+ console.error("Error pausing draft:", error);
+ }
+ };
+
+ const handleResumeDraft = async () => {
+ try {
+ const formData = new FormData();
+ formData.append("action", "pause-draft");
+ formData.append("seasonId", season.id);
+ formData.append("pause", "false");
+
+ const response = await fetch("/api/draft-commissioner", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ console.error("Failed to resume draft:", error);
+ }
+ } catch (error) {
+ console.error("Error resuming draft:", error);
+ }
+ };
+
+ const handleForcePick = async () => {
+ if (!currentTeamId) return;
+
+ try {
+ const formData = new FormData();
+ formData.append("action", "force-pick");
+ formData.append("seasonId", season.id);
+ formData.append("teamId", currentTeamId);
+
+ const response = await fetch("/api/draft-commissioner", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ console.error("Failed to force pick:", error);
+ }
+ } catch (error) {
+ console.error("Error forcing pick:", error);
+ }
+ };
+
return (
{/* Header */}
@@ -213,9 +373,16 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited
Commissioner Controls
-
- Controls will be implemented in Phase 9
-
+
) : (
@@ -229,6 +396,10 @@ export default function DraftRoom({ loaderData }: { loaderData: AwaitedDraft Rounds:
{season.draftRounds}
+
+ Current Team:
+ {currentTeamName}
+
)}
diff --git a/server/socket.ts b/server/socket.ts
index 8f45ee9..daa3cbb 100644
--- a/server/socket.ts
+++ b/server/socket.ts
@@ -32,13 +32,29 @@ export function setupSocketIO(httpServer: HTTPServer): SocketServer {
console.log(`Socket ${socket.id} left draft room: ${roomName}`);
});
+ // Draft Actions - Phase 9
+ // These events will be handled by API routes and then broadcast back via socket
+ // The client will call API routes which will then use getSocketIO() to broadcast
+ socket.on("make-pick", (_data) => {
+ console.log("make-pick event received, should use API route instead");
+ });
+
+ socket.on("commissioner-start-draft", (_data) => {
+ console.log("commissioner-start-draft event received, should use API route instead");
+ });
+
+ socket.on("commissioner-pause-draft", (_data) => {
+ console.log("commissioner-pause-draft event received, should use API route instead");
+ });
+
+ socket.on("commissioner-force-pick", (_data) => {
+ console.log("commissioner-force-pick event received, should use API route instead");
+ });
+
// Handle disconnection
socket.on("disconnect", () => {
console.log(`Client disconnected: ${socket.id}`);
});
-
- // Draft-specific events will be added in Phase 9
- // For now, we're just setting up the infrastructure
});
return io;
diff --git a/tsconfig.node.json b/tsconfig.node.json
index afaea84..a717121 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -4,7 +4,8 @@
"server/**/*.ts",
"vite.config.ts",
"database/**/*.ts",
- "app/contexts/**/*.ts"
+ "app/contexts/**/*.ts",
+ "app/models/**/*.ts"
],
"compilerOptions": {
"composite": true,
diff --git a/vite.config.ts b/vite.config.ts
index 36dea4b..8f02a76 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -15,4 +15,9 @@ export default defineConfig(({ isSsrBuild }) => ({
server: {
allowedHosts: true,
},
+ resolve: {
+ alias: {
+ "~/database": "/database",
+ },
+ },
}));