feat: add sports detail route and update homepage meta description
This commit is contained in:
parent
c162258c99
commit
6814a82b76
3 changed files with 176 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ export default [
|
|||
index("routes/admin._index.tsx"),
|
||||
route("sports", "routes/admin.sports.tsx"),
|
||||
route("sports/new", "routes/admin.sports.new.tsx"),
|
||||
route("sports/:id", "routes/admin.sports.$id.tsx"),
|
||||
route("sports-seasons", "routes/admin.sports-seasons.tsx"),
|
||||
route("sports-seasons/new", "routes/admin.sports-seasons.new.tsx"),
|
||||
route("sports-seasons/:id", "routes/admin.sports-seasons.$id.tsx"),
|
||||
|
|
|
|||
170
app/routes/admin.sports.$id.tsx
Normal file
170
app/routes/admin.sports.$id.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import { Form, Link, redirect } from "react-router";
|
||||
import type { Route } from "./+types/admin.sports.$id";
|
||||
import { findSportById, updateSport } from "~/models/sport";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { Textarea } from "~/components/ui/textarea";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "~/components/ui/select";
|
||||
|
||||
export async function loader({ params }: Route.LoaderArgs) {
|
||||
const sport = await findSportById(params.id);
|
||||
|
||||
if (!sport) {
|
||||
throw new Response("Sport not found", { status: 404 });
|
||||
}
|
||||
|
||||
return { sport };
|
||||
}
|
||||
|
||||
export async function action({ request, params }: Route.ActionArgs) {
|
||||
const formData = await request.formData();
|
||||
const name = formData.get("name");
|
||||
const type = formData.get("type");
|
||||
const slug = formData.get("slug");
|
||||
const description = formData.get("description");
|
||||
|
||||
// Validation
|
||||
if (typeof name !== "string" || !name.trim()) {
|
||||
return { error: "Sport name is required" };
|
||||
}
|
||||
|
||||
if (type !== "team" && type !== "individual") {
|
||||
return { error: "Sport type must be either 'team' or 'individual'" };
|
||||
}
|
||||
|
||||
if (typeof slug !== "string" || !slug.trim()) {
|
||||
return { error: "Slug is required" };
|
||||
}
|
||||
|
||||
// Validate slug format (lowercase, hyphens only)
|
||||
if (!/^[a-z0-9-]+$/.test(slug)) {
|
||||
return { error: "Slug must contain only lowercase letters, numbers, and hyphens" };
|
||||
}
|
||||
|
||||
try {
|
||||
await updateSport(params.id, {
|
||||
name: name.trim(),
|
||||
type,
|
||||
slug: slug.trim(),
|
||||
description: typeof description === "string" ? description.trim() : null,
|
||||
});
|
||||
|
||||
return redirect("/admin/sports");
|
||||
} catch (error) {
|
||||
console.error("Error updating sport:", error);
|
||||
return { error: "Failed to update sport. The slug might already exist." };
|
||||
}
|
||||
}
|
||||
|
||||
export default function EditSport({ loaderData, actionData }: Route.ComponentProps) {
|
||||
const { sport } = loaderData;
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="max-w-2xl">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold">Edit Sport</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Update the sport information
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Sport Details</CardTitle>
|
||||
<CardDescription>
|
||||
Modify the information for this sport
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form method="post" className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Sport Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="e.g., NFL, NBA, Golf - Men's"
|
||||
defaultValue={sport.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="type">Type</Label>
|
||||
<Select name="type" defaultValue={sport.type} required>
|
||||
<SelectTrigger id="type">
|
||||
<SelectValue placeholder="Select sport type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="team">Team Sport</SelectItem>
|
||||
<SelectItem value="individual">Individual Sport</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Team sports have teams, individual sports have players
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="slug">Slug</Label>
|
||||
<Input
|
||||
id="slug"
|
||||
name="slug"
|
||||
type="text"
|
||||
placeholder="e.g., nfl, nba, golf-mens"
|
||||
pattern="[a-z0-9-]+"
|
||||
defaultValue={sport.slug}
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
URL-friendly identifier (lowercase, hyphens only)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description (Optional)</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
placeholder="Brief description of the sport"
|
||||
rows={3}
|
||||
defaultValue={sport.description || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{actionData?.error && (
|
||||
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||
{actionData.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button type="submit" className="flex-1">
|
||||
Update Sport
|
||||
</Button>
|
||||
<Button type="button" variant="outline" asChild>
|
||||
<Link to="/admin/sports">Cancel</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,10 +17,10 @@ import {
|
|||
|
||||
export function meta() {
|
||||
return [
|
||||
{ title: "Brackt - Fantasy Sports Brackets" },
|
||||
{ title: "Brackt - Fantasy All of the Sports!" },
|
||||
{
|
||||
name: "description",
|
||||
content: "Create and manage your fantasy sports leagues",
|
||||
content: "Play multi-sport fantasy leagues with your friends!",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -117,7 +117,9 @@ export default function Home({ loaderData }: Route.ComponentProps) {
|
|||
<p className="text-sm text-muted-foreground">
|
||||
Current Season
|
||||
</p>
|
||||
<p className="font-medium">{league.currentSeason.year}</p>
|
||||
<p className="font-medium">
|
||||
{league.currentSeason.year}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Status</p>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue