refactor: remove guest book feature and update league settings success flow

This commit is contained in:
Chris Parsons 2025-10-11 01:03:31 -07:00
parent 9749fc8b77
commit 48bca99f37
8 changed files with 375 additions and 127 deletions

View file

@ -1,7 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useSearchParams } from "react-router"; import { useSearchParams } from "react-router";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { toast } from "sonner"; import { toast } from "sonner";
import type { Route } from "./+types/home"; import type { Route } from "./+types/home";
@ -9,50 +7,18 @@ import { Welcome } from "../welcome/welcome";
export function meta({}: Route.MetaArgs) { export function meta({}: Route.MetaArgs) {
return [ return [
{ title: "New React Router App" }, { title: "Brackt - Fantasy Sports Brackets" },
{ name: "description", content: "Welcome to React Router!" }, { name: "description", content: "Create and manage your fantasy sports leagues" },
]; ];
} }
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
let name = formData.get("name");
let email = formData.get("email");
if (typeof name !== "string" || typeof email !== "string") {
return { guestBookError: "Name and email are required" };
}
name = name.trim();
email = email.trim();
if (!name || !email) {
return { guestBookError: "Name and email are required" };
}
const db = database();
try {
await db.insert(schema.guestBook).values({ name, email });
} catch (error) {
return { guestBookError: "Error adding to guest book" };
}
}
export async function loader({ context }: Route.LoaderArgs) { export async function loader({ context }: Route.LoaderArgs) {
const db = database();
const guestBook = await db.query.guestBook.findMany({
columns: {
id: true,
name: true,
},
});
return { return {
guestBook,
message: context.VALUE_FROM_EXPRESS, message: context.VALUE_FROM_EXPRESS,
}; };
} }
export default function Home({ actionData, loaderData }: Route.ComponentProps) { export default function Home({ loaderData }: Route.ComponentProps) {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
useEffect(() => { useEffect(() => {
@ -63,11 +29,5 @@ export default function Home({ actionData, loaderData }: Route.ComponentProps) {
} }
}, [searchParams, setSearchParams]); }, [searchParams, setSearchParams]);
return ( return <Welcome message={loaderData.message} />;
<Welcome
guestBook={loaderData.guestBook}
guestBookError={actionData?.guestBookError}
message={loaderData.message}
/>
);
} }

View file

@ -121,7 +121,7 @@ export async function action(args: Route.ActionArgs) {
name: name.trim(), name: name.trim(),
}); });
return { success: "League updated successfully" }; return redirect(`/leagues/${leagueId}?updated=true`);
} catch (error) { } catch (error) {
console.error("Error updating league:", error); console.error("Error updating league:", error);
return { error: "Failed to update league. Please try again." }; return { error: "Failed to update league. Please try again." };
@ -198,12 +198,6 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
</p> </p>
</div> </div>
{actionData?.success && (
<div className="bg-green-50 dark:bg-green-950 text-green-700 dark:text-green-300 px-4 py-3 rounded-md text-sm">
{actionData.success}
</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}

View file

@ -1,10 +1,15 @@
import { Link } from "react-router"; import { useEffect } from "react";
import { Link, useSearchParams } from "react-router";
import { getAuth } from "@clerk/react-router/server"; import { getAuth } from "@clerk/react-router/server";
import { findLeagueById } from "~/models/league"; import { toast } from "sonner";
import { findCurrentSeason } from "~/models/season"; import {
import { findTeamsBySeasonId } from "~/models/team"; findLeagueById,
import { findCommissionersByLeagueId } from "~/models/commissioner"; findCurrentSeason,
findTeamsBySeasonId,
findCommissionersByLeagueId,
} from "~/models";
import type { Route } from "./+types/$leagueId"; import type { Route } from "./+types/$leagueId";
import { Button } from "~/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
@ -12,7 +17,6 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
export async function loader(args: Route.LoaderArgs) { export async function loader(args: Route.LoaderArgs) {
const { userId } = await getAuth(args); const { userId } = await getAuth(args);
@ -50,6 +54,15 @@ export async function loader(args: Route.LoaderArgs) {
export default function LeagueHome({ loaderData }: Route.ComponentProps) { export default function LeagueHome({ loaderData }: Route.ComponentProps) {
const { league, season, teams, commissioners, currentUserId, isUserCommissioner } = loaderData; const { league, season, teams, commissioners, currentUserId, isUserCommissioner } = loaderData;
const [searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
if (searchParams.get("updated") === "true") {
toast.success("League updated successfully");
// Remove the query parameter
setSearchParams({});
}
}, [searchParams, setSearchParams]);
return ( return (
<div className="container mx-auto py-8 px-4"> <div className="container mx-auto py-8 px-4">

View file

@ -1,23 +1,10 @@
import { Form, Link, useNavigation } from "react-router"; import { Link } from "react-router";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import logoDark from "./logo-dark.svg"; import logoDark from "./logo-dark.svg";
import logoLight from "./logo-light.svg"; import logoLight from "./logo-light.svg";
export function Welcome({ export function Welcome({ message }: { message: string }) {
guestBook,
guestBookError,
message,
}: {
guestBook: {
name: string;
id: number;
}[];
guestBookError?: string;
message: string;
}) {
const navigation = useNavigation();
return ( return (
<main className="flex items-center justify-center pt-16 pb-4"> <main className="flex items-center justify-center pt-16 pb-4">
<div className="flex-1 flex flex-col items-center gap-16 min-h-0"> <div className="flex-1 flex flex-col items-center gap-16 min-h-0">
@ -62,55 +49,6 @@ export function Welcome({
))} ))}
</ul> </ul>
</nav> </nav>
<section className="rounded-3xl border border-gray-200 p-6 dark:border-gray-700 space-y-4">
<Form
method="post"
className="space-y-4 w-full max-w-lg"
onSubmit={(event) => {
if (navigation.state === "submitting") {
event.preventDefault();
}
const form = event.currentTarget;
requestAnimationFrame(() => {
form.reset();
});
}}
>
<input
name="name"
placeholder="Name"
required
className="w-full dark:bg-gray-800 dark:text-gray-200 dark:border-gray-700 dark:focus:ring-blue-500 h-10 px-3 rounded-lg border border-gray-200 focus:ring-1 focus:ring-blue-500"
/>
<input
name="email"
type="email"
placeholder="your@email.com"
required
className="w-full dark:bg-gray-800 dark:text-gray-200 dark:border-gray-700 dark:focus:ring-blue-500 h-10 px-3 rounded-lg border border-gray-200 focus:ring-1 focus:ring-blue-500"
/>
<button
type="submit"
disabled={navigation.state === "submitting"}
className="w-full h-10 px-3 text-white bg-blue-500 rounded-lg hover:bg-blue-600"
>
Sign Guest Book
</button>
{guestBookError && (
<p className="text-red-500 dark:text-red-400">
{guestBookError}
</p>
)}
</Form>
<ul className="text-center">
{<li className="p-3">{message}</li>}
{guestBook.map(({ id, name }) => (
<li key={id} className="p-3">
{name}
</li>
))}
</ul>
</section>
</div> </div>
</div> </div>
</main> </main>

View file

@ -1,11 +1,5 @@
import { integer, pgTable, varchar, uuid, timestamp, pgEnum } from "drizzle-orm/pg-core"; import { integer, pgTable, varchar, uuid, timestamp, pgEnum } from "drizzle-orm/pg-core";
export const guestBook = pgTable("guestBook", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: varchar({ length: 255 }).notNull(),
email: varchar({ length: 255 }).notNull().unique(),
});
// Users table - synced from Clerk // Users table - synced from Clerk
export const users = pgTable("users", { export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(), id: uuid("id").primaryKey().defaultRandom(),

View file

@ -0,0 +1 @@
DROP TABLE "guestBook" CASCADE;

View file

@ -0,0 +1,341 @@
{
"id": "6839ce43-d04c-4246-9b00-7820935e1166",
"prevId": "5e858241-1709-4099-90d2-58c7e3bf0d23",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.commissioners": {
"name": "commissioners",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"league_id": {
"name": "league_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"commissioners_league_id_leagues_id_fk": {
"name": "commissioners_league_id_leagues_id_fk",
"tableFrom": "commissioners",
"tableTo": "leagues",
"columnsFrom": [
"league_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.leagues": {
"name": "leagues",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_by": {
"name": "created_by",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.seasons": {
"name": "seasons",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"league_id": {
"name": "league_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"year": {
"name": "year",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "season_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'pre_draft'"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"seasons_league_id_leagues_id_fk": {
"name": "seasons_league_id_leagues_id_fk",
"tableFrom": "seasons",
"tableTo": "leagues",
"columnsFrom": [
"league_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.teams": {
"name": "teams",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"season_id": {
"name": "season_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"owner_id": {
"name": "owner_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"teams_season_id_seasons_id_fk": {
"name": "teams_season_id_seasons_id_fk",
"tableFrom": "teams",
"tableTo": "seasons",
"columnsFrom": [
"season_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"clerk_id": {
"name": "clerk_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"first_name": {
"name": "first_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"last_name": {
"name": "last_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"image_url": {
"name": "image_url",
"type": "varchar(512)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_clerk_id_unique": {
"name": "users_clerk_id_unique",
"nullsNotDistinct": false,
"columns": [
"clerk_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.season_status": {
"name": "season_status",
"schema": "public",
"values": [
"pre_draft",
"draft",
"active",
"completed"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View file

@ -36,6 +36,13 @@
"when": 1760168383812, "when": 1760168383812,
"tag": "0004_spicy_zarda", "tag": "0004_spicy_zarda",
"breakpoints": true "breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1760169537260,
"tag": "0005_abnormal_thunderbird",
"breakpoints": true
} }
] ]
} }