feat: add sports selection and team management to league settings
This commit is contained in:
parent
94586564e2
commit
0999e1c980
6 changed files with 490 additions and 67 deletions
42
app/components/ui/radio-group.tsx
Normal file
42
app/components/ui/radio-group.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||||
|
import { Circle } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
|
const RadioGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
className={cn("grid gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const RadioGroupItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||||
|
<Circle className="h-2.5 w-2.5 fill-current text-current" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
|
|
@ -59,6 +59,27 @@ export async function findCurrentSeason(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function findCurrentSeasonWithSports(
|
||||||
|
leagueId: string
|
||||||
|
): Promise<SeasonWithSportsSeasons | undefined> {
|
||||||
|
const db = database();
|
||||||
|
return await db.query.seasons.findFirst({
|
||||||
|
where: eq(schema.seasons.leagueId, leagueId),
|
||||||
|
orderBy: (seasons, { desc }) => [desc(seasons.year)],
|
||||||
|
with: {
|
||||||
|
seasonSports: {
|
||||||
|
with: {
|
||||||
|
sportsSeason: {
|
||||||
|
with: {
|
||||||
|
sport: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as SeasonWithSportsSeasons | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function findSeasonByLeagueAndYear(
|
export async function findSeasonByLeagueAndYear(
|
||||||
leagueId: string,
|
leagueId: string,
|
||||||
year: number
|
year: number
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ import { Form, Link, redirect } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
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 { findCurrentSeason } from "~/models/season";
|
import { findCurrentSeasonWithSports } from "~/models/season";
|
||||||
import { findTeamsBySeasonId } from "~/models/team";
|
import { findTeamsBySeasonId, createManyTeams, deleteTeam } from "~/models/team";
|
||||||
|
import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport";
|
||||||
|
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||||
import type { Route } from "./+types/$leagueId.settings";
|
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";
|
||||||
|
|
@ -23,6 +25,8 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "~/components/ui/select";
|
} from "~/components/ui/select";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { Checkbox } from "~/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
|
|
@ -61,13 +65,21 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current season to count teams
|
// Get current season with sports
|
||||||
const season = await findCurrentSeason(leagueId);
|
const season = await findCurrentSeasonWithSports(leagueId);
|
||||||
const teams = season ? await findTeamsBySeasonId(season.id) : [];
|
const teams = season ? await findTeamsBySeasonId(season.id) : [];
|
||||||
|
const allSportsSeasons = await findAllSportsSeasons();
|
||||||
|
|
||||||
|
// Count teams with owners
|
||||||
|
const teamsWithOwners = teams.filter(team => team.ownerId !== null).length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
league,
|
league,
|
||||||
|
season,
|
||||||
|
teams,
|
||||||
teamCount: teams.length,
|
teamCount: teams.length,
|
||||||
|
teamsWithOwners,
|
||||||
|
allSportsSeasons: allSportsSeasons as Array<typeof allSportsSeasons[0] & { sport: { id: string; name: string; type: string; slug: string } }>,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,6 +108,48 @@ export async function action(args: Route.ActionArgs) {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const intent = formData.get("intent");
|
const intent = formData.get("intent");
|
||||||
|
|
||||||
|
// Get current season to check status
|
||||||
|
const season = await findCurrentSeasonWithSports(leagueId);
|
||||||
|
if (!season) {
|
||||||
|
return { error: "No active season found" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent === "update-sports") {
|
||||||
|
if (season.status !== "pre_draft") {
|
||||||
|
return { error: "Cannot modify sports after draft has started" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSports = formData.getAll("sportsSeasons");
|
||||||
|
const currentSportIds = new Set(
|
||||||
|
season.seasonSports?.map((s: any) => s.sportsSeason.id) || []
|
||||||
|
);
|
||||||
|
const newSportIds = new Set(selectedSports as string[]);
|
||||||
|
|
||||||
|
// Remove sports that are no longer selected
|
||||||
|
for (const sportId of currentSportIds) {
|
||||||
|
if (!newSportIds.has(sportId)) {
|
||||||
|
await unlinkSportFromSeason(season.id, sportId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add newly selected sports
|
||||||
|
const sportsToAdd = [];
|
||||||
|
for (const sportId of newSportIds) {
|
||||||
|
if (!currentSportIds.has(sportId)) {
|
||||||
|
sportsToAdd.push({
|
||||||
|
seasonId: season.id,
|
||||||
|
sportsSeasonId: sportId as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sportsToAdd.length > 0) {
|
||||||
|
await linkMultipleSportsToSeason(sportsToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
if (intent === "delete") {
|
if (intent === "delete") {
|
||||||
// Delete the league
|
// Delete the league
|
||||||
await deleteLeague(leagueId);
|
await deleteLeague(leagueId);
|
||||||
|
|
@ -106,6 +160,7 @@ 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");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -121,6 +176,49 @@ export async function action(args: Route.ActionArgs) {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle team count changes if provided
|
||||||
|
if (typeof teamCount === "string") {
|
||||||
|
const newTeamCount = parseInt(teamCount, 10);
|
||||||
|
|
||||||
|
if (!isNaN(newTeamCount)) {
|
||||||
|
const teams = await findTeamsBySeasonId(season.id);
|
||||||
|
const currentTeamCount = teams.length;
|
||||||
|
const teamsWithOwners = teams.filter(t => t.ownerId !== null).length;
|
||||||
|
|
||||||
|
// Validate team count
|
||||||
|
if (newTeamCount < 6 || newTeamCount > 16) {
|
||||||
|
return { error: "Number of teams must be between 6 and 16" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTeamCount < teamsWithOwners) {
|
||||||
|
return { error: `Cannot reduce team count below ${teamsWithOwners} (number of teams with owners)` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add or remove teams as needed
|
||||||
|
if (newTeamCount > currentTeamCount) {
|
||||||
|
// Add new teams
|
||||||
|
const teamsToAdd = Array.from(
|
||||||
|
{ length: newTeamCount - currentTeamCount },
|
||||||
|
(_, i) => ({
|
||||||
|
seasonId: season.id,
|
||||||
|
name: `Team ${currentTeamCount + i + 1}`,
|
||||||
|
ownerId: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await createManyTeams(teamsToAdd);
|
||||||
|
} else if (newTeamCount < currentTeamCount) {
|
||||||
|
// Remove teams without owners (from the end)
|
||||||
|
const teamsToRemove = teams
|
||||||
|
.filter(t => t.ownerId === null)
|
||||||
|
.slice(-(currentTeamCount - newTeamCount));
|
||||||
|
|
||||||
|
for (const team of teamsToRemove) {
|
||||||
|
await deleteTeam(team.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return redirect(`/leagues/${leagueId}?updated=true`);
|
return redirect(`/leagues/${leagueId}?updated=true`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating league:", error);
|
console.error("Error updating league:", error);
|
||||||
|
|
@ -132,8 +230,25 @@ export async function action(args: Route.ActionArgs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LeagueSettings({ loaderData, actionData }: Route.ComponentProps) {
|
export default function LeagueSettings({ loaderData, actionData }: Route.ComponentProps) {
|
||||||
const { league, teamCount } = loaderData;
|
const { league, season, teamCount, teamsWithOwners, allSportsSeasons } = loaderData;
|
||||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
|
const [selectedSports, setSelectedSports] = useState<Set<string>>(
|
||||||
|
new Set(season?.seasonSports?.map((s: any) => s.sportsSeason.id) || [])
|
||||||
|
);
|
||||||
|
|
||||||
|
const canEditSports = season && season.status === "pre_draft";
|
||||||
|
const canEditTeamCount = season && season.status === "pre_draft";
|
||||||
|
const minTeamCount = Math.max(6, teamsWithOwners);
|
||||||
|
|
||||||
|
const handleSportToggle = (sportId: string) => {
|
||||||
|
const newSelected = new Set(selectedSports);
|
||||||
|
if (newSelected.has(sportId)) {
|
||||||
|
newSelected.delete(sportId);
|
||||||
|
} else {
|
||||||
|
newSelected.add(sportId);
|
||||||
|
}
|
||||||
|
setSelectedSports(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
setIsDeleteDialogOpen(false);
|
setIsDeleteDialogOpen(false);
|
||||||
|
|
@ -181,20 +296,31 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="teamCount">Number of Teams</Label>
|
<Label htmlFor="teamCount">Number of Teams</Label>
|
||||||
<Select name="teamCount" defaultValue={teamCount.toString()} disabled>
|
<Select
|
||||||
|
name="teamCount"
|
||||||
|
defaultValue={teamCount.toString()}
|
||||||
|
disabled={!canEditTeamCount}
|
||||||
|
>
|
||||||
<SelectTrigger id="teamCount">
|
<SelectTrigger id="teamCount">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{Array.from({ length: 11 }, (_, i) => i + 6).map((num) => (
|
{Array.from({ length: 11 }, (_, i) => i + 6).map((num) => (
|
||||||
<SelectItem key={num} value={num.toString()}>
|
<SelectItem
|
||||||
|
key={num}
|
||||||
|
value={num.toString()}
|
||||||
|
disabled={num < minTeamCount}
|
||||||
|
>
|
||||||
{num} Teams
|
{num} Teams
|
||||||
|
{num < minTeamCount && ` (${teamsWithOwners} teams have owners)`}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Team count cannot be changed after league creation
|
{canEditTeamCount
|
||||||
|
? `Can be changed before draft starts (minimum ${minTeamCount} based on registered teams)`
|
||||||
|
: "Team count cannot be changed after draft has started"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -211,6 +337,84 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Sports Seasons Management */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Sports Seasons</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{canEditSports
|
||||||
|
? "Select the sports seasons that teams can draft from"
|
||||||
|
: "Sports cannot be modified after the draft has started"}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Form method="post" className="space-y-4">
|
||||||
|
<input type="hidden" name="intent" value="update-sports" />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="bg-muted/50 border border-muted-foreground/20 rounded-md p-3">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
<strong>Recommendation:</strong> Select 16-20 sports seasons for optimal league balance and variety.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-sm font-medium">
|
||||||
|
Sports Seasons ({selectedSports.size} selected)
|
||||||
|
</Label>
|
||||||
|
<div className="max-h-96 overflow-y-auto border rounded-md p-3 space-y-2">
|
||||||
|
{allSportsSeasons.length > 0 ? (
|
||||||
|
allSportsSeasons.map((season) => (
|
||||||
|
<div key={season.id} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={season.id}
|
||||||
|
name="sportsSeasons"
|
||||||
|
value={season.id}
|
||||||
|
checked={selectedSports.has(season.id)}
|
||||||
|
onCheckedChange={() => handleSportToggle(season.id)}
|
||||||
|
disabled={!canEditSports}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={season.id}
|
||||||
|
className={`font-normal flex-1 ${canEditSports ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'}`}
|
||||||
|
>
|
||||||
|
<span className="font-medium">{season.sport.name}</span> - {season.name} ({season.year})
|
||||||
|
<Badge variant="outline" className="ml-2 text-xs">
|
||||||
|
{season.scoringType.replace("_", " ")}
|
||||||
|
</Badge>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No sports seasons available
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{canEditSports && (
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Save Sports Selection
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{actionData?.success && (
|
||||||
|
<div className="bg-green-500/15 text-green-700 dark:text-green-400 px-4 py-3 rounded-md text-sm">
|
||||||
|
Sports updated successfully!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{actionData?.error && (
|
||||||
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||||
|
{actionData.error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Danger Zone */}
|
{/* Danger Zone */}
|
||||||
<Card className="border-destructive">
|
<Card className="border-destructive">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,16 @@ import { createLeague, setCurrentSeason } from "~/models/league";
|
||||||
import { createSeason } from "~/models/season";
|
import { createSeason } from "~/models/season";
|
||||||
import { createManyTeams } from "~/models/team";
|
import { createManyTeams } from "~/models/team";
|
||||||
import { createCommissioner } from "~/models/commissioner";
|
import { createCommissioner } from "~/models/commissioner";
|
||||||
|
import { linkMultipleSportsToSeason } from "~/models/season-sport";
|
||||||
|
import { findActiveSeasonTemplates, findSeasonTemplateWithSportsSeasons } from "~/models/season-template";
|
||||||
|
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||||
import type { Route } from "./+types/new";
|
import type { Route } from "./+types/new";
|
||||||
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 { Checkbox } from "~/components/ui/checkbox";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -22,6 +28,30 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "~/components/ui/select";
|
} from "~/components/ui/select";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
||||||
|
export async function loader() {
|
||||||
|
const templates = await findActiveSeasonTemplates();
|
||||||
|
const allSportsSeasons = await findAllSportsSeasons();
|
||||||
|
|
||||||
|
// Fetch templates with their sports seasons for mapping
|
||||||
|
const templatesWithSports = await Promise.all(
|
||||||
|
templates.map(async (template) => {
|
||||||
|
const templateWithSports = await findSeasonTemplateWithSportsSeasons(template.id);
|
||||||
|
return {
|
||||||
|
...template,
|
||||||
|
sportsSeasonIds: templateWithSports?.seasonTemplateSports?.map(
|
||||||
|
(ts: any) => ts.sportsSeason.id
|
||||||
|
) || []
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
templates: templatesWithSports,
|
||||||
|
allSportsSeasons: allSportsSeasons as Array<typeof allSportsSeasons[0] & { sport: { id: string; name: string; type: string; slug: string } }>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function action(args: Route.ActionArgs) {
|
export async function action(args: Route.ActionArgs) {
|
||||||
const { userId } = await getAuth(args);
|
const { userId } = await getAuth(args);
|
||||||
|
|
@ -34,6 +64,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const name = formData.get("name");
|
const name = formData.get("name");
|
||||||
const teamCount = formData.get("teamCount");
|
const teamCount = formData.get("teamCount");
|
||||||
|
const templateId = formData.get("templateId");
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
|
|
@ -68,11 +99,23 @@ export async function action(args: Route.ActionArgs) {
|
||||||
leagueId: league.id,
|
leagueId: league.id,
|
||||||
year: currentYear,
|
year: currentYear,
|
||||||
status: "pre_draft",
|
status: "pre_draft",
|
||||||
|
templateId: typeof templateId === "string" && templateId !== "" ? templateId : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set this as the current season for the league
|
// Set this as the current season for the league
|
||||||
await setCurrentSeason(league.id, season.id);
|
await setCurrentSeason(league.id, season.id);
|
||||||
|
|
||||||
|
// Add selected sports to the season
|
||||||
|
const selectedSports = formData.getAll("sportsSeasons");
|
||||||
|
if (selectedSports.length > 0) {
|
||||||
|
await linkMultipleSportsToSeason(
|
||||||
|
selectedSports.map((id) => ({
|
||||||
|
seasonId: season.id,
|
||||||
|
sportsSeasonId: id as string,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create teams with placeholder names
|
// Create teams with placeholder names
|
||||||
const teams = Array.from({ length: teamCountNum }, (_, i) => ({
|
const teams = Array.from({ length: teamCountNum }, (_, i) => ({
|
||||||
seasonId: season.id,
|
seasonId: season.id,
|
||||||
|
|
@ -90,7 +133,25 @@ export async function action(args: Route.ActionArgs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NewLeague({ actionData }: Route.ComponentProps) {
|
export default function NewLeague({ loaderData, actionData }: Route.ComponentProps) {
|
||||||
|
const { templates, allSportsSeasons } = loaderData;
|
||||||
|
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
|
||||||
|
const [selectedSports, setSelectedSports] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const handleSportToggle = (sportId: string) => {
|
||||||
|
const newSelected = new Set(selectedSports);
|
||||||
|
if (newSelected.has(sportId)) {
|
||||||
|
newSelected.delete(sportId);
|
||||||
|
} else {
|
||||||
|
newSelected.add(sportId);
|
||||||
|
}
|
||||||
|
setSelectedSports(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTemplateClick = (templateId: string, sportsSeasonIds: string[]) => {
|
||||||
|
setSelectedTemplate(templateId);
|
||||||
|
setSelectedSports(new Set(sportsSeasonIds));
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="container max-w-2xl mx-auto py-8 px-4">
|
<div className="container max-w-2xl mx-auto py-8 px-4">
|
||||||
<Card>
|
<Card>
|
||||||
|
|
@ -140,6 +201,93 @@ export default function NewLeague({ actionData }: Route.ComponentProps) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Label>Sports Selection</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Choose a template to auto-select sports, or manually select your own
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input type="hidden" name="templateId" value={selectedTemplate} />
|
||||||
|
|
||||||
|
{/* Template Cards */}
|
||||||
|
{templates.length > 0 && (
|
||||||
|
<div className="grid gap-3">
|
||||||
|
{templates.map((template) => (
|
||||||
|
<Card
|
||||||
|
key={template.id}
|
||||||
|
className={`cursor-pointer transition-all ${
|
||||||
|
selectedTemplate === template.id
|
||||||
|
? "ring-2 ring-primary"
|
||||||
|
: "hover:border-primary/50"
|
||||||
|
}`}
|
||||||
|
onClick={() => handleTemplateClick(template.id, template.sportsSeasonIds)}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<CardTitle className="text-lg">{template.name}</CardTitle>
|
||||||
|
<CardDescription>{template.year}</CardDescription>
|
||||||
|
</div>
|
||||||
|
{selectedTemplate === template.id && (
|
||||||
|
<div className="rounded-full bg-primary text-primary-foreground p-1">
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
{template.description && (
|
||||||
|
<CardContent className="pt-0">
|
||||||
|
<p className="text-sm text-muted-foreground">{template.description}</p>
|
||||||
|
</CardContent>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sports Checkboxes */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="bg-muted/50 border border-muted-foreground/20 rounded-md p-3">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
<strong>Recommendation:</strong> Select 16-20 sports seasons for optimal league balance and variety.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-sm font-medium">
|
||||||
|
Sports Seasons ({selectedSports.size} selected)
|
||||||
|
</Label>
|
||||||
|
<div className="max-h-64 overflow-y-auto border rounded-md p-3 space-y-2">
|
||||||
|
{allSportsSeasons.length > 0 ? (
|
||||||
|
allSportsSeasons.map((season) => (
|
||||||
|
<div key={season.id} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={season.id}
|
||||||
|
name="sportsSeasons"
|
||||||
|
value={season.id}
|
||||||
|
checked={selectedSports.has(season.id)}
|
||||||
|
onCheckedChange={() => handleSportToggle(season.id)}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={season.id}
|
||||||
|
className="font-normal cursor-pointer flex-1"
|
||||||
|
>
|
||||||
|
<span className="font-medium">{season.sport.name}</span> - {season.name} ({season.year})
|
||||||
|
<Badge variant="outline" className="ml-2 text-xs">
|
||||||
|
{season.scoringType.replace("_", " ")}
|
||||||
|
</Badge>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No sports seasons available
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{actionData?.error && (
|
{actionData?.error && (
|
||||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||||
{actionData.error}
|
{actionData.error}
|
||||||
|
|
|
||||||
123
package-lock.json
generated
123
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-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",
|
||||||
"@react-router/express": "^7.7.1",
|
"@react-router/express": "^7.7.1",
|
||||||
|
|
@ -19,6 +20,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",
|
||||||
|
"drizzle-kit": "~0.28.1",
|
||||||
"drizzle-orm": "~0.36.3",
|
"drizzle-orm": "~0.36.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"isbot": "^5.1.27",
|
"isbot": "^5.1.27",
|
||||||
|
|
@ -45,7 +47,6 @@
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
"drizzle-kit": "~0.28.1",
|
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
|
@ -628,7 +629,6 @@
|
||||||
"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",
|
||||||
"integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
|
"integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild-kit/core-utils": {
|
"node_modules/@esbuild-kit/core-utils": {
|
||||||
|
|
@ -636,7 +636,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
|
||||||
"integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
|
"integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
|
||||||
"deprecated": "Merged into tsx: https://tsx.is",
|
"deprecated": "Merged into tsx: https://tsx.is",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.18.20",
|
"esbuild": "~0.18.20",
|
||||||
|
|
@ -650,7 +649,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -667,7 +665,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -684,7 +681,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -701,7 +697,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -718,7 +713,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -735,7 +729,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -752,7 +745,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -769,7 +761,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -786,7 +777,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -803,7 +793,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -820,7 +809,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -837,7 +825,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -854,7 +841,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -871,7 +857,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -888,7 +873,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -905,7 +889,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -922,7 +905,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -939,7 +921,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -956,7 +937,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -973,7 +953,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -990,7 +969,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1007,7 +985,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1021,7 +998,6 @@
|
||||||
"version": "0.18.20",
|
"version": "0.18.20",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -1060,7 +1036,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
|
||||||
"integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
|
"integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
|
||||||
"deprecated": "Merged into tsx: https://tsx.is",
|
"deprecated": "Merged into tsx: https://tsx.is",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esbuild-kit/core-utils": "^3.3.2",
|
"@esbuild-kit/core-utils": "^3.3.2",
|
||||||
|
|
@ -1074,7 +1049,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1091,7 +1065,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1108,7 +1081,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1125,7 +1097,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1142,7 +1113,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1159,7 +1129,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1176,7 +1145,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1193,7 +1161,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1210,7 +1177,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1227,7 +1193,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1244,7 +1209,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1261,7 +1225,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1278,7 +1241,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1295,7 +1257,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1312,7 +1273,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1329,7 +1289,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1346,7 +1305,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1380,7 +1338,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1414,7 +1371,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1448,7 +1404,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1465,7 +1420,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1482,7 +1436,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1499,7 +1452,6 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -2154,6 +2106,69 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-radio-group": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
|
||||||
|
"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-direction": "1.1.1",
|
||||||
|
"@radix-ui/react-presence": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-roving-focus": "1.1.11",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||||
|
"@radix-ui/react-use-previous": "1.1.1",
|
||||||
|
"@radix-ui/react-use-size": "1.1.1"
|
||||||
|
},
|
||||||
|
"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-roving-focus": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.3",
|
||||||
|
"@radix-ui/react-collection": "1.1.7",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-direction": "1.1.1",
|
||||||
|
"@radix-ui/react-id": "1.1.1",
|
||||||
|
"@radix-ui/react-primitive": "2.1.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||||
|
},
|
||||||
|
"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-select": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
|
||||||
|
|
@ -3445,7 +3460,6 @@
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
|
|
@ -3821,7 +3835,6 @@
|
||||||
"version": "0.28.1",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.28.1.tgz",
|
||||||
"integrity": "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==",
|
"integrity": "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@drizzle-team/brocli": "^0.10.2",
|
"@drizzle-team/brocli": "^0.10.2",
|
||||||
|
|
@ -4070,7 +4083,6 @@
|
||||||
"version": "0.19.12",
|
"version": "0.19.12",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
||||||
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
|
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -4109,7 +4121,6 @@
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
||||||
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
|
|
@ -4358,7 +4369,6 @@
|
||||||
"version": "4.12.0",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
|
||||||
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
|
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"resolve-pkg-maps": "^1.0.0"
|
"resolve-pkg-maps": "^1.0.0"
|
||||||
|
|
@ -5697,7 +5707,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
|
|
@ -5987,7 +5996,6 @@
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -6007,7 +6015,6 @@
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -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-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",
|
||||||
"@react-router/express": "^7.7.1",
|
"@react-router/express": "^7.7.1",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue