Files
pocket-chords/app/app/routes/songs.$id.tsx

86 lines
2.7 KiB
TypeScript

import { useState } from "react";
import { data, Link } from "react-router";
import type { Route } from "./+types/songs.$id";
import { TransposeBar } from "~/components/transpose-bar";
import { ChordChart } from "~/components/chord-chart";
import { EditSongSheet } from "~/components/edit-song-sheet";
import { DeleteSongDialog } from "~/components/delete-song-dialog";
import { transposeSong } from "~/lib/transpose";
import { getSong } from "~/lib/api";
import type { Song, SongSummary } from "~/lib/types";
export function meta({ data }: Route.MetaArgs) {
if (!data?.song) return [{ title: "PocketChords" }];
return [
{ title: `${data.song.meta.title} — PocketChords` },
{ name: "description", content: data.song.meta.artist },
];
}
export async function loader({ params }: Route.LoaderArgs) {
const id = params.id ?? "";
try {
const song = await getSong(id);
if (!song) throw data("Song not found", { status: 404 });
return { song, id };
} catch (err: unknown) {
if (err && typeof err === "object" && "status" in err && (err as { status: number }).status === 404) {
throw err;
}
return { song: null as unknown as Song, id };
}
}
export default function SongDetail({ loaderData }: Route.ComponentProps) {
const { song: initialSong, id } = loaderData;
const [song, setSong] = useState<Song | null>(initialSong ?? null);
const [offset, setOffset] = useState(0);
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
if (!song) {
return (
<div className="flex flex-col items-center justify-center h-full gap-4">
<p className="text-muted-foreground text-sm">Song not found or unavailable.</p>
<Link to="/" className="text-sm text-primary underline-offset-4 hover:underline">
Back to library
</Link>
</div>
);
}
const displayed = transposeSong(song, offset);
function handleUpdated(summary: SongSummary) {
setSong((prev) => prev ? { ...prev, meta: summary.meta } : prev);
}
return (
<div className="flex flex-col h-full max-w-lg mx-auto">
<TransposeBar
meta={song.meta}
offset={offset}
onOffsetChange={setOffset}
onEdit={() => setEditOpen(true)}
onDelete={() => setDeleteOpen(true)}
/>
<div className="flex-1 overflow-y-auto">
<ChordChart sections={displayed.sections} />
</div>
<EditSongSheet
id={id}
meta={song.meta}
open={editOpen}
onOpenChange={setEditOpen}
onUpdated={handleUpdated}
/>
<DeleteSongDialog
id={id}
title={song.meta.title}
open={deleteOpen}
onOpenChange={setDeleteOpen}
/>
</div>
);
}