feat: add draft date/time selection to league creation and settings
This commit is contained in:
parent
501c5b183f
commit
dd9d385f16
11 changed files with 1416 additions and 26 deletions
68
app/components/ui/calendar.tsx
Normal file
68
app/components/ui/calendar.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
|
import { DayPicker, type DayPickerProps } from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
import { buttonVariants } from "~/components/ui/button"
|
||||||
|
|
||||||
|
export type CalendarProps = DayPickerProps
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
...props
|
||||||
|
}: CalendarProps) {
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn("p-3", className)}
|
||||||
|
classNames={{
|
||||||
|
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||||
|
month: "space-y-4",
|
||||||
|
month_caption: "flex justify-center pt-1 relative items-center",
|
||||||
|
caption_label: "text-sm font-medium",
|
||||||
|
nav: "space-x-1 flex items-center",
|
||||||
|
button_previous: cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1"
|
||||||
|
),
|
||||||
|
button_next: cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1"
|
||||||
|
),
|
||||||
|
month_grid: "w-full border-collapse space-y-1",
|
||||||
|
weekdays: "flex",
|
||||||
|
weekday:
|
||||||
|
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
||||||
|
week: "flex w-full mt-2",
|
||||||
|
day: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||||
|
day_button: cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
||||||
|
),
|
||||||
|
range_end: "day-range-end",
|
||||||
|
selected:
|
||||||
|
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||||
|
today: "bg-accent text-accent-foreground",
|
||||||
|
outside:
|
||||||
|
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||||
|
disabled: "text-muted-foreground opacity-50",
|
||||||
|
range_middle:
|
||||||
|
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||||
|
hidden: "invisible",
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Chevron: ({ orientation }) => {
|
||||||
|
const Icon = orientation === "left" ? ChevronLeft : ChevronRight
|
||||||
|
return <Icon className="h-4 w-4" />
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Calendar.displayName = "Calendar"
|
||||||
|
|
||||||
|
export { Calendar }
|
||||||
29
app/components/ui/popover.tsx
Normal file
29
app/components/ui/popover.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent }
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Form, Link, redirect, useNavigation } from "react-router";
|
import { Form, Link, redirect, useNavigation } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
import { findLeagueById, updateLeague, deleteLeague } from "~/models/league";
|
import { findLeagueById, updateLeague, deleteLeague } from "~/models/league";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { findCurrentSeasonWithSports, updateSeason } from "~/models/season";
|
import { findCurrentSeasonWithSports, updateSeason } from "~/models/season";
|
||||||
|
|
@ -12,6 +14,13 @@ import type { Route } from "./+types/$leagueId.settings";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "~/components/ui/popover";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -117,31 +126,6 @@ export async function action(args: Route.ActionArgs) {
|
||||||
return { error: "No active season found" };
|
return { error: "No active season found" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent === "update-draft-rounds") {
|
|
||||||
if (season.status !== "pre_draft") {
|
|
||||||
return { error: "Cannot modify draft rounds after draft has started" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const draftRounds = formData.get("draftRounds");
|
|
||||||
const draftRoundsNum = typeof draftRounds === "string" ? parseInt(draftRounds, 10) : null;
|
|
||||||
|
|
||||||
if (draftRoundsNum === null || isNaN(draftRoundsNum)) {
|
|
||||||
return { error: "Draft rounds must be a valid number" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const sportsCount = season.seasonSports?.length || 0;
|
|
||||||
if (draftRoundsNum < sportsCount) {
|
|
||||||
return { error: `Draft rounds must be at least ${sportsCount} (number of sports selected)` };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draftRoundsNum < 1 || draftRoundsNum > 50) {
|
|
||||||
return { error: "Draft rounds must be between 1 and 50" };
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateSeason(season.id, { draftRounds: draftRoundsNum });
|
|
||||||
return { success: true, message: "Draft rounds updated successfully" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent === "update-sports") {
|
if (intent === "update-sports") {
|
||||||
if (season.status !== "pre_draft") {
|
if (season.status !== "pre_draft") {
|
||||||
return { error: "Cannot modify sports after draft has started" };
|
return { error: "Cannot modify sports after draft has started" };
|
||||||
|
|
@ -226,6 +210,8 @@ export async function action(args: Route.ActionArgs) {
|
||||||
if (intent === "update") {
|
if (intent === "update") {
|
||||||
const name = formData.get("name");
|
const name = formData.get("name");
|
||||||
const teamCount = formData.get("teamCount");
|
const teamCount = formData.get("teamCount");
|
||||||
|
const draftDateTime = formData.get("draftDateTime");
|
||||||
|
const draftRounds = formData.get("draftRounds");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -241,6 +227,34 @@ export async function action(args: Route.ActionArgs) {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update season settings
|
||||||
|
const seasonUpdates: any = {};
|
||||||
|
|
||||||
|
// Handle draft rounds
|
||||||
|
if (typeof draftRounds === "string") {
|
||||||
|
const draftRoundsNum = parseInt(draftRounds, 10);
|
||||||
|
if (!isNaN(draftRoundsNum)) {
|
||||||
|
const sportsCount = season.seasonSports?.length || 0;
|
||||||
|
if (draftRoundsNum < sportsCount) {
|
||||||
|
return { error: `Draft rounds must be at least ${sportsCount} (number of sports selected)` };
|
||||||
|
}
|
||||||
|
if (draftRoundsNum < 1 || draftRoundsNum > 50) {
|
||||||
|
return { error: "Draft rounds must be between 1 and 50" };
|
||||||
|
}
|
||||||
|
seasonUpdates.draftRounds = draftRoundsNum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle draft date/time
|
||||||
|
if (typeof draftDateTime === "string") {
|
||||||
|
seasonUpdates.draftDateTime = draftDateTime ? new Date(draftDateTime) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update season if there are changes
|
||||||
|
if (Object.keys(seasonUpdates).length > 0) {
|
||||||
|
await updateSeason(season.id, seasonUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle team count changes if provided
|
// Handle team count changes if provided
|
||||||
if (typeof teamCount === "string") {
|
if (typeof teamCount === "string") {
|
||||||
const newTeamCount = parseInt(teamCount, 10);
|
const newTeamCount = parseInt(teamCount, 10);
|
||||||
|
|
@ -307,6 +321,14 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
: teams.map((team: any) => team.id)
|
: teams.map((team: any) => team.id)
|
||||||
);
|
);
|
||||||
const [draftRounds, setDraftRounds] = useState<number>(season?.draftRounds || 20);
|
const [draftRounds, setDraftRounds] = useState<number>(season?.draftRounds || 20);
|
||||||
|
const [draftDate, setDraftDate] = useState<Date | undefined>(
|
||||||
|
season?.draftDateTime ? new Date(season.draftDateTime) : undefined
|
||||||
|
);
|
||||||
|
const [draftTime, setDraftTime] = useState<string>(
|
||||||
|
season?.draftDateTime
|
||||||
|
? format(new Date(season.draftDateTime), "HH:mm")
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
// Update draft order when loader data changes (after randomization)
|
// Update draft order when loader data changes (after randomization)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -478,6 +500,58 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="draftDate">Draft Date & Time</Label>
|
||||||
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
className={cn(
|
||||||
|
"justify-start text-left font-normal",
|
||||||
|
!draftDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
disabled={!canEditDraftRounds}
|
||||||
|
>
|
||||||
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||||
|
{draftDate ? format(draftDate, "PPP") : <span>Pick a date</span>}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={draftDate}
|
||||||
|
onSelect={setDraftDate}
|
||||||
|
initialFocus
|
||||||
|
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Input
|
||||||
|
type="time"
|
||||||
|
value={draftTime}
|
||||||
|
onChange={(e) => setDraftTime(e.target.value)}
|
||||||
|
placeholder="Select time"
|
||||||
|
disabled={!canEditDraftRounds}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{draftDate && draftTime && (
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="draftDateTime"
|
||||||
|
value={new Date(`${format(draftDate, "yyyy-MM-dd")}T${draftTime}`).toISOString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!draftDate && !draftTime && (
|
||||||
|
<input type="hidden" name="draftDateTime" value="" />
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{canEditDraftRounds
|
||||||
|
? "You must set a draft date and time before starting the draft"
|
||||||
|
: "Draft date cannot be changed after draft has started"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||||
import { Link, useSearchParams } from "react-router";
|
import { Link, useSearchParams } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { format } from "date-fns";
|
||||||
import {
|
import {
|
||||||
findLeagueById,
|
findLeagueById,
|
||||||
findCurrentSeason,
|
findCurrentSeason,
|
||||||
|
|
@ -360,6 +361,14 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
||||||
{Math.max(0, season.draftRounds - sportsCount)}
|
{Math.max(0, season.draftRounds - sportsCount)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{season.draftDateTime && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Draft Date</p>
|
||||||
|
<p className="font-medium">
|
||||||
|
{format(new Date(season.draftDateTime), "PPP 'at' p")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import { createManyTeams } from "~/models/team";
|
||||||
import { findActiveSeasonTemplates, findSeasonTemplateWithSportsSeasons } from "~/models/season-template";
|
import { findActiveSeasonTemplates, findSeasonTemplateWithSportsSeasons } from "~/models/season-template";
|
||||||
import { findAllSportsSeasons } from "~/models/sports-season";
|
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||||
import { generateUniqueTeamNames } from "~/utils/team-names";
|
import { generateUniqueTeamNames } from "~/utils/team-names";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon, Check } from "lucide-react";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -29,7 +31,13 @@ import { Badge } from "~/components/ui/badge";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
import { Checkbox } from "~/components/ui/checkbox";
|
||||||
import { Check } from "lucide-react";
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "~/components/ui/popover";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
export async function loader() {
|
export async function loader() {
|
||||||
const templates = await findActiveSeasonTemplates();
|
const templates = await findActiveSeasonTemplates();
|
||||||
|
|
@ -67,6 +75,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
const teamCount = formData.get("teamCount");
|
const teamCount = formData.get("teamCount");
|
||||||
const templateId = formData.get("templateId");
|
const templateId = formData.get("templateId");
|
||||||
const draftRounds = formData.get("draftRounds");
|
const draftRounds = formData.get("draftRounds");
|
||||||
|
const draftDateTime = formData.get("draftDateTime");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -108,6 +117,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
status: "pre_draft",
|
status: "pre_draft",
|
||||||
templateId: typeof templateId === "string" && templateId !== "" ? templateId : null,
|
templateId: typeof templateId === "string" && templateId !== "" ? templateId : null,
|
||||||
draftRounds: draftRoundsNum,
|
draftRounds: draftRoundsNum,
|
||||||
|
draftDateTime: typeof draftDateTime === "string" && draftDateTime ? new Date(draftDateTime) : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set this as the current season for the league
|
// Set this as the current season for the league
|
||||||
|
|
@ -155,6 +165,8 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
|
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
|
||||||
const [selectedSports, setSelectedSports] = useState<Set<string>>(new Set());
|
const [selectedSports, setSelectedSports] = useState<Set<string>>(new Set());
|
||||||
const [draftRounds, setDraftRounds] = useState<number>(20);
|
const [draftRounds, setDraftRounds] = useState<number>(20);
|
||||||
|
const [draftDate, setDraftDate] = useState<Date>();
|
||||||
|
const [draftTime, setDraftTime] = useState<string>("");
|
||||||
|
|
||||||
const handleSportToggle = (sportId: string) => {
|
const handleSportToggle = (sportId: string) => {
|
||||||
const newSelected = new Set(selectedSports);
|
const newSelected = new Set(selectedSports);
|
||||||
|
|
@ -269,6 +281,51 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="draftDate">Draft Date & Time</Label>
|
||||||
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
className={cn(
|
||||||
|
"justify-start text-left font-normal",
|
||||||
|
!draftDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||||
|
{draftDate ? format(draftDate, "PPP") : <span>Pick a date</span>}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={draftDate}
|
||||||
|
onSelect={setDraftDate}
|
||||||
|
initialFocus
|
||||||
|
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Input
|
||||||
|
type="time"
|
||||||
|
value={draftTime}
|
||||||
|
onChange={(e) => setDraftTime(e.target.value)}
|
||||||
|
placeholder="Select time"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{draftDate && draftTime && (
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="draftDateTime"
|
||||||
|
value={new Date(`${format(draftDate, "yyyy-MM-dd")}T${draftTime}`).toISOString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
You must set a draft date and time before starting the draft
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label>Sports Selection</Label>
|
<Label>Sports Selection</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ export const seasons = pgTable("seasons", {
|
||||||
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
||||||
draftRounds: integer("draft_rounds").notNull().default(20),
|
draftRounds: integer("draft_rounds").notNull().default(20),
|
||||||
flexSpots: integer("flex_spots").notNull().default(0),
|
flexSpots: integer("flex_spots").notNull().default(0),
|
||||||
|
draftDateTime: timestamp("draft_date_time"),
|
||||||
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
|
|
|
||||||
1
drizzle/0016_majestic_roulette.sql
Normal file
1
drizzle/0016_majestic_roulette.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE "seasons" ADD COLUMN "draft_date_time" timestamp;
|
||||||
1058
drizzle/meta/0016_snapshot.json
Normal file
1058
drizzle/meta/0016_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -113,6 +113,13 @@
|
||||||
"when": 1760589195551,
|
"when": 1760589195551,
|
||||||
"tag": "0015_exotic_loners",
|
"tag": "0015_exotic_loners",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 16,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1760590495868,
|
||||||
|
"tag": "0016_majestic_roulette",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
83
package-lock.json
generated
83
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||||
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-radio-group": "^1.3.8",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"compression": "^1.8.0",
|
"compression": "^1.8.0",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-kit": "~0.28.1",
|
"drizzle-kit": "~0.28.1",
|
||||||
"drizzle-orm": "~0.36.3",
|
"drizzle-orm": "~0.36.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|
@ -29,6 +31,7 @@
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router": "^7.7.1",
|
"react-router": "^7.7.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
|
|
@ -625,6 +628,12 @@
|
||||||
"node": ">=18.17.0"
|
"node": ">=18.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@date-fns/tz": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@drizzle-team/brocli": {
|
"node_modules/@drizzle-team/brocli": {
|
||||||
"version": "0.10.2",
|
"version": "0.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
|
||||||
|
|
@ -2003,6 +2012,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
|
||||||
|
"integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||||
|
"@radix-ui/react-focus-guards": "1.1.3",
|
||||||
|
"@radix-ui/react-focus-scope": "1.1.7",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-popper": "1.2.8",
|
||||||
|
"@radix-ui/react-portal": "1.1.9",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.6.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||||
|
|
@ -3726,6 +3772,22 @@
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns-jalali": {
|
||||||
|
"version": "4.1.0-0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
|
||||||
|
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
|
@ -5567,6 +5629,27 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-day-picker": {
|
||||||
|
"version": "9.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.1.tgz",
|
||||||
|
"integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@date-fns/tz": "^1.4.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-jalali": "^4.1.0-0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/gpbl"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||||
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-radio-group": "^1.3.8",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
|
@ -26,6 +27,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"compression": "^1.8.0",
|
"compression": "^1.8.0",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-kit": "~0.28.1",
|
"drizzle-kit": "~0.28.1",
|
||||||
"drizzle-orm": "~0.36.3",
|
"drizzle-orm": "~0.36.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
|
@ -35,6 +37,7 @@
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router": "^7.7.1",
|
"react-router": "^7.7.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue