summaryrefslogtreecommitdiff
path: root/app/routes/events.$id.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/routes/events.$id.tsx')
-rw-r--r--app/routes/events.$id.tsx124
1 files changed, 124 insertions, 0 deletions
diff --git a/app/routes/events.$id.tsx b/app/routes/events.$id.tsx
new file mode 100644
index 0000000..cecb282
--- /dev/null
+++ b/app/routes/events.$id.tsx
@@ -0,0 +1,124 @@
+import { useLoaderData, Link } from "react-router";
+import type { Route } from "./+types/events.$id";
+import { getEvent } from "~/lib/db.server";
+
+export async function loader({ params }: Route.LoaderArgs) {
+ const id = parseInt(params.id, 10);
+ if (isNaN(id)) throw new Response("Not Found", { status: 404 });
+ const event = getEvent(id);
+ if (!event) throw new Response("Not Found", { status: 404 });
+ return { event };
+}
+
+export default function EventDetail() {
+ const { event } = useLoaderData<typeof loader>();
+
+ return (
+ <div className="min-h-screen bg-gray-950 text-gray-100">
+ <header className="border-b border-gray-800 px-6 py-4 flex items-center justify-between">
+ <Link to="/" className="text-xl font-bold tracking-tight text-white">
+ 🎸 東京ライブハウス
+ </Link>
+ <nav className="flex gap-6 text-sm text-gray-400">
+ <Link to="/events" className="hover:text-white transition-colors">イベント</Link>
+ <Link to="/venues" className="hover:text-white transition-colors">会場一覧</Link>
+ </nav>
+ </header>
+
+ <main className="max-w-3xl mx-auto px-4 py-10">
+ <Link to="/events" className="text-sm text-indigo-400 hover:underline">
+ ← イベント一覧に戻る
+ </Link>
+
+ <div className="mt-6">
+ {event.image_url && (
+ <img
+ src={event.image_url}
+ alt={event.title}
+ className="w-full max-h-72 object-cover rounded-xl mb-6"
+ />
+ )}
+
+ <div className="flex items-start justify-between gap-4 flex-wrap">
+ <div>
+ <h1 className="text-3xl font-bold leading-tight">{event.title}</h1>
+ {event.artist && (
+ <p className="mt-1 text-lg text-gray-300">{event.artist}</p>
+ )}
+ </div>
+ <span className="rounded-full bg-indigo-700/60 px-3 py-1 text-sm font-medium whitespace-nowrap">
+ {event.venue_name}
+ </span>
+ </div>
+
+ <dl className="mt-8 grid grid-cols-2 gap-4 text-sm">
+ <Detail label="日付" value={formatDate(event.date)} />
+ {event.open_time && <Detail label="OPEN" value={event.open_time} />}
+ {event.start_time && <Detail label="START" value={event.start_time} />}
+ {event.price && <Detail label="料金" value={event.price} />}
+ {event.venue_area && <Detail label="エリア" value={event.venue_area} />}
+ </dl>
+
+ {event.description && (
+ <p className="mt-8 text-gray-300 leading-relaxed whitespace-pre-line">
+ {event.description}
+ </p>
+ )}
+
+ <div className="mt-8 flex gap-4 flex-wrap">
+ {event.ticket_url && (
+ <a
+ href={event.ticket_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="rounded-md bg-indigo-600 px-5 py-2 text-sm font-medium hover:bg-indigo-500 transition-colors"
+ >
+ チケット購入
+ </a>
+ )}
+ {event.source_url && (
+ <a
+ href={event.source_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="rounded-md bg-gray-700 px-5 py-2 text-sm font-medium hover:bg-gray-600 transition-colors"
+ >
+ 詳細ページ
+ </a>
+ )}
+ {event.venue_url && (
+ <a
+ href={event.venue_url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="rounded-md bg-gray-700 px-5 py-2 text-sm font-medium hover:bg-gray-600 transition-colors"
+ >
+ 会場サイト
+ </a>
+ )}
+ </div>
+
+ <p className="mt-10 text-xs text-gray-600">
+ 最終取得: {event.fetched_at}
+ </p>
+ </div>
+ </main>
+ </div>
+ );
+}
+
+function Detail({ label, value }: { label: string; value: string }) {
+ return (
+ <div className="rounded-lg bg-gray-800/60 p-3">
+ <dt className="text-xs text-gray-500 mb-1">{label}</dt>
+ <dd className="font-medium">{value}</dd>
+ </div>
+ );
+}
+
+function formatDate(iso: string): string {
+ const [y, m, d] = iso.split("-");
+ const days = ["日", "月", "火", "水", "木", "金", "土"];
+ const day = days[new Date(iso).getDay()];
+ return `${y}年${m}月${d}日(${day})`;
+}