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 { Form, Link, redirect, useNavigation } from "react-router";
|
||||
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 { isCommissioner } from "~/models/commissioner";
|
||||
import { findCurrentSeasonWithSports, updateSeason } from "~/models/season";
|
||||
|
|
@ -12,6 +14,13 @@ import type { Route } from "./+types/$leagueId.settings";
|
|||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
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 {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -117,31 +126,6 @@ export async function action(args: Route.ActionArgs) {
|
|||
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 (season.status !== "pre_draft") {
|
||||
return { error: "Cannot modify sports after draft has started" };
|
||||
|
|
@ -226,6 +210,8 @@ export async function action(args: Route.ActionArgs) {
|
|||
if (intent === "update") {
|
||||
const name = formData.get("name");
|
||||
const teamCount = formData.get("teamCount");
|
||||
const draftDateTime = formData.get("draftDateTime");
|
||||
const draftRounds = formData.get("draftRounds");
|
||||
|
||||
// Validation
|
||||
if (typeof name !== "string" || !name.trim()) {
|
||||
|
|
@ -241,6 +227,34 @@ export async function action(args: Route.ActionArgs) {
|
|||
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
|
||||
if (typeof teamCount === "string") {
|
||||
const newTeamCount = parseInt(teamCount, 10);
|
||||
|
|
@ -307,6 +321,14 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
: teams.map((team: any) => team.id)
|
||||
);
|
||||
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)
|
||||
useEffect(() => {
|
||||
|
|
@ -478,6 +500,58 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
)}
|
||||
</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>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
|||
import { Link, useSearchParams } from "react-router";
|
||||
import { getAuth } from "@clerk/react-router/server";
|
||||
import { toast } from "sonner";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
findLeagueById,
|
||||
findCurrentSeason,
|
||||
|
|
@ -360,6 +361,14 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
|||
{Math.max(0, season.draftRounds - sportsCount)}
|
||||
</p>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { createManyTeams } from "~/models/team";
|
|||
import { findActiveSeasonTemplates, findSeasonTemplateWithSportsSeasons } from "~/models/season-template";
|
||||
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||
import { generateUniqueTeamNames } from "~/utils/team-names";
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon, Check } from "lucide-react";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -29,7 +31,13 @@ import { Badge } from "~/components/ui/badge";
|
|||
import { Input } from "~/components/ui/input";
|
||||
import { Label } from "~/components/ui/label";
|
||||
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() {
|
||||
const templates = await findActiveSeasonTemplates();
|
||||
|
|
@ -67,6 +75,7 @@ export async function action(args: Route.ActionArgs) {
|
|||
const teamCount = formData.get("teamCount");
|
||||
const templateId = formData.get("templateId");
|
||||
const draftRounds = formData.get("draftRounds");
|
||||
const draftDateTime = formData.get("draftDateTime");
|
||||
|
||||
// Validation
|
||||
if (typeof name !== "string" || !name.trim()) {
|
||||
|
|
@ -108,6 +117,7 @@ export async function action(args: Route.ActionArgs) {
|
|||
status: "pre_draft",
|
||||
templateId: typeof templateId === "string" && templateId !== "" ? templateId : null,
|
||||
draftRounds: draftRoundsNum,
|
||||
draftDateTime: typeof draftDateTime === "string" && draftDateTime ? new Date(draftDateTime) : null,
|
||||
});
|
||||
|
||||
// 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 [selectedSports, setSelectedSports] = useState<Set<string>>(new Set());
|
||||
const [draftRounds, setDraftRounds] = useState<number>(20);
|
||||
const [draftDate, setDraftDate] = useState<Date>();
|
||||
const [draftTime, setDraftTime] = useState<string>("");
|
||||
|
||||
const handleSportToggle = (sportId: string) => {
|
||||
const newSelected = new Set(selectedSports);
|
||||
|
|
@ -269,6 +281,51 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
|
|||
</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">
|
||||
<Label>Sports Selection</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export const seasons = pgTable("seasons", {
|
|||
.references(() => seasonTemplates.id, { onDelete: "set null" }),
|
||||
draftRounds: integer("draft_rounds").notNull().default(20),
|
||||
flexSpots: integer("flex_spots").notNull().default(0),
|
||||
draftDateTime: timestamp("draft_date_time"),
|
||||
inviteCode: varchar("invite_code", { length: 20 }).notNull().unique(),
|
||||
createdAt: timestamp("created_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,
|
||||
"tag": "0015_exotic_loners",
|
||||
"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-label": "^2.1.7",
|
||||
"@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-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"compression": "^1.8.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"drizzle-kit": "~0.28.1",
|
||||
"drizzle-orm": "~0.36.3",
|
||||
"express": "^5.1.0",
|
||||
|
|
@ -29,6 +31,7 @@
|
|||
"next-themes": "^0.4.6",
|
||||
"postgres": "^3.4.5",
|
||||
"react": "^19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router": "^7.7.1",
|
||||
"sonner": "^2.0.7",
|
||||
|
|
@ -625,6 +628,12 @@
|
|||
"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": {
|
||||
"version": "0.10.2",
|
||||
"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": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||
|
|
@ -3726,6 +3772,22 @@
|
|||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"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": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
|
|
@ -5567,6 +5629,27 @@
|
|||
"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": {
|
||||
"version": "19.2.0",
|
||||
"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-label": "^2.1.7",
|
||||
"@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-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"compression": "^1.8.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"drizzle-kit": "~0.28.1",
|
||||
"drizzle-orm": "~0.36.3",
|
||||
"express": "^5.1.0",
|
||||
|
|
@ -35,6 +37,7 @@
|
|||
"next-themes": "^0.4.6",
|
||||
"postgres": "^3.4.5",
|
||||
"react": "^19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router": "^7.7.1",
|
||||
"sonner": "^2.0.7",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue