From 90833c5b939e5a19ee3d7af4a65a3ca710ca79f1 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 8 Apr 2026 02:36:09 +0200 Subject: [PATCH] fix(app): meta null guard, deduplicate previewChords, env API URL, always show add card --- app/.env.example | 1 + app/app/components/add-song-sheet.tsx | 21 +++-------------- app/app/lib/mock.ts | 2 +- app/app/routes/home.tsx | 33 +++++++++++++-------------- app/app/routes/songs.$id.tsx | 6 ++--- 5 files changed, 24 insertions(+), 39 deletions(-) create mode 100644 app/.env.example diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 0000000..5934e2e --- /dev/null +++ b/app/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:8000 diff --git a/app/app/components/add-song-sheet.tsx b/app/app/components/add-song-sheet.tsx index 25d8f9c..12306cc 100644 --- a/app/app/components/add-song-sheet.tsx +++ b/app/app/components/add-song-sheet.tsx @@ -9,6 +9,7 @@ import { import { Input } from "~/components/ui/input"; import { Button } from "~/components/ui/button"; import type { Song, SongSummary } from "~/lib/types"; +import { previewChords } from "~/lib/mock"; interface Props { open: boolean; @@ -16,23 +17,6 @@ interface Props { onSongAdded: (summary: SongSummary) => void; } -function previewChords(song: Song): string[] { - const seen = new Set(); - const result: string[] = []; - for (const section of song.sections) { - for (const line of section.lines) { - for (const cp of line.chords) { - if (!seen.has(cp.chord)) { - seen.add(cp.chord); - result.push(cp.chord); - } - } - } - if (result.length >= 5) break; - } - return result.slice(0, 5); -} - export function AddSongSheet({ open, onOpenChange, onSongAdded }: Props) { const navigate = useNavigate(); const [url, setUrl] = useState(""); @@ -58,7 +42,8 @@ export function AddSongSheet({ open, onOpenChange, onSongAdded }: Props) { throw new Error("File upload requires running the app locally with direct file paths. Use a URL instead."); } - const resp = await fetch("http://localhost:8000/tabs/parse", { + const apiBase = import.meta.env.VITE_API_URL ?? "http://localhost:8000"; + const resp = await fetch(`${apiBase}/tabs/parse`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source: url.trim() }), diff --git a/app/app/lib/mock.ts b/app/app/lib/mock.ts index 544bb04..a2cffda 100644 --- a/app/app/lib/mock.ts +++ b/app/app/lib/mock.ts @@ -114,7 +114,7 @@ const SONGS_MAP: Record = { "song-naked": NAKED, }; -function previewChords(song: Song): string[] { +export function previewChords(song: Song): string[] { const seen = new Set(); const result: string[] = []; for (const section of song.sections) { diff --git a/app/app/routes/home.tsx b/app/app/routes/home.tsx index 28cbad8..0fbb43e 100644 --- a/app/app/routes/home.tsx +++ b/app/app/routes/home.tsx @@ -58,26 +58,25 @@ export default function Home({ loaderData }: Route.ComponentProps) { {/* Grid */}
- {filtered.length === 0 ? ( -

+ {filtered.length === 0 && ( +

{query ? "No songs match your search." : "No songs yet. Tap Add to get started."}

- ) : ( -
- {filtered.map((song) => ( - - ))} - {/* Add card */} - setSheetOpen(true)} - > - - - - -
)} +
+ {filtered.map((song) => ( + + ))} + {/* Add card */} + setSheetOpen(true)} + > + + + + +
(() => { + const [song] = useState(() => { if (loaderData.song) return loaderData.song; if (tempId && typeof window !== "undefined") { const stored = sessionStorage.getItem(tempId);