| | @@ -0,0 +1,135 @@ |
| + | import Link from "next/link"; |
| + | import { notFound } from "next/navigation"; |
| + | import { revalidatePath } from "next/cache"; |
| + | import { EVENT_TYPE } from "@stepping/types"; |
| + | import { serverCaller } from "@/lib/trpc/server"; |
| + | |
| + | export const dynamic = "force-dynamic"; |
| + | |
| + | const TYPE_LABEL: Record<string, string> = { |
| + | step_show: "Step show", |
| + | stroll: "Stroll", |
| + | competition: "Competition", |
| + | class: "Class", |
| + | social: "Social", |
| + | special: "Special", |
| + | }; |
| + | |
| + | function fmtDate(d: Date | null) { |
| + | if (!d) return "Date TBD"; |
| + | return new Date(d).toLocaleString("en-US", { |
| + | weekday: "short", |
| + | month: "short", |
| + | day: "numeric", |
| + | hour: "numeric", |
| + | minute: "2-digit", |
| + | }); |
| + | } |
| + | |
| + | export default async function CityPage({ |
| + | params, |
| + | }: { |
| + | params: Promise<{ slug: string }>; |
| + | }) { |
| + | const { slug } = await params; |
| + | const caller = serverCaller(); |
| + | const city = await caller.city.bySlug({ slug }); |
| + | if (!city) notFound(); |
| + | |
| + | const events = await caller.event.list({ cityId: city.id }); |
| + | |
| + | async function addEvent(formData: FormData) { |
| + | "use server"; |
| + | const title = String(formData.get("title") ?? "").trim(); |
| + | const type = String(formData.get("type") ?? "step_show"); |
| + | const startsAtRaw = String(formData.get("startsAt") ?? ""); |
| + | if (!title) return; |
| + | await serverCaller().event.create({ |
| + | cityId: city!.id, |
| + | title, |
| + | type: type as (typeof EVENT_TYPE)[number], |
| + | startsAt: startsAtRaw ? new Date(startsAtRaw) : undefined, |
| + | }); |
| + | revalidatePath(`/city/${slug}`); |
| + | } |
| + | |
| + | return ( |
| + | <main className="mx-auto w-full max-w-2xl flex-1 px-6 py-12"> |
| + | <Link |
| + | href="/" |
| + | className="text-sm text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200" |
| + | > |
| + | ← All cities |
| + | </Link> |
| + | |
| + | <h1 className="mt-3 text-3xl font-semibold tracking-tight text-black dark:text-zinc-50"> |
| + | {city.name} |
| + | </h1> |
| + | <p className="text-zinc-500 dark:text-zinc-400">{city.metroLabel}</p> |
| + | |
| + | <section className="mt-8"> |
| + | <h2 className="text-sm font-medium uppercase tracking-widest text-amber-600"> |
| + | Add an event |
| + | </h2> |
| + | <form action={addEvent} className="mt-3 flex flex-col gap-3"> |
| + | <input |
| + | name="title" |
| + | required |
| + | placeholder="Event name (e.g. Homecoming Step Show)" |
| + | className="rounded-lg border border-zinc-300 bg-white px-3 py-2 text-black dark:border-zinc-700 dark:bg-zinc-950 dark:text-white" |
| + | /> |
| + | <div className="flex gap-3"> |
| + | <select |
| + | name="type" |
| + | className="rounded-lg border border-zinc-300 bg-white px-3 py-2 text-black dark:border-zinc-700 dark:bg-zinc-950 dark:text-white" |
| + | > |
| + | {EVENT_TYPE.map((t) => ( |
| + | <option key={t} value={t}> |
| + | {TYPE_LABEL[t] ?? t} |
| + | </option> |
| + | ))} |
| + | </select> |
| + | <input |
| + | type="datetime-local" |
| + | name="startsAt" |
| + | className="rounded-lg border border-zinc-300 bg-white px-3 py-2 text-black dark:border-zinc-700 dark:bg-zinc-950 dark:text-white" |
| + | /> |
| + | </div> |
| + | <button |
| + | type="submit" |
| + | className="self-start rounded-full bg-amber-600 px-5 py-2 font-medium text-white hover:bg-amber-700" |
| + | > |
| + | Post event |
| + | </button> |
| + | </form> |
| + | </section> |
| + | |
| + | <section className="mt-10"> |
| + | <h2 className="text-sm font-medium uppercase tracking-widest text-zinc-400"> |
| + | Events |
| + | </h2> |
| + | {events.length === 0 ? ( |
| + | <p className="mt-3 text-zinc-500"> |
| + | No events yet. Add the first one above. |
| + | </p> |
| + | ) : ( |
| + | <ul className="mt-3 divide-y divide-zinc-200 dark:divide-zinc-800"> |
| + | {events.map((e) => ( |
| + | <li key={e.id} className="flex items-center justify-between py-4"> |
| + | <div> |
| + | <p className="font-medium text-black dark:text-zinc-50"> |
| + | {e.title} |
| + | </p> |
| + | <p className="text-sm text-zinc-500">{fmtDate(e.startsAt)}</p> |
| + | </div> |
| + | <span className="rounded-full bg-zinc-100 px-3 py-1 text-xs font-medium text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300"> |
| + | {TYPE_LABEL[e.type] ?? e.type} |
| + | </span> |
| + | </li> |
| + | ))} |
| + | </ul> |
| + | )} |
| + | </section> |
| + | </main> |
| + | ); |
| + | } |