From 9072a5e9cd223b97e874d3e2dc724345bd50f5e4 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 18 Jun 2026 08:37:14 -0500 Subject: [PATCH] feat: catalog landing header + onboarding empty state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns /catalogs/[id] into a real landing for a warm lead: a header with the catalog name, its track count, and a single next action ('Ask the agent about this catalog', which opens a chat pre-seeded via /?q=). Replaces the bare 'No catalogs found.' with an onboarding empty state that routes to the free valuation or the agent. Part of recoupable/chat#1801 Phase 3. Play-count-per-track and the valuation band are deferred — they need a new measurements-by-catalog API endpoint (no by-ISRC/by-catalog playcount endpoint exists today; only per-album and per-track). Noted on the issue. Co-Authored-By: Claude Opus 4.8 (1M context) --- components/Catalog/CatalogLandingHeader.tsx | 57 +++++++++++++++++++++ components/Catalog/CatalogSongsPage.tsx | 3 +- components/Catalog/CatalogsPageContent.tsx | 3 +- components/Catalog/EmptyCatalogsState.tsx | 43 ++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 components/Catalog/CatalogLandingHeader.tsx create mode 100644 components/Catalog/EmptyCatalogsState.tsx diff --git a/components/Catalog/CatalogLandingHeader.tsx b/components/Catalog/CatalogLandingHeader.tsx new file mode 100644 index 000000000..9f8dc2093 --- /dev/null +++ b/components/Catalog/CatalogLandingHeader.tsx @@ -0,0 +1,57 @@ +"use client"; + +import Link from "next/link"; +import { Sparkles } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import useCatalogs from "@/hooks/useCatalogs"; +import useArtistCatalogSongs from "@/hooks/useArtistCatalogSongs"; + +interface CatalogLandingHeaderProps { + catalogId: string; +} + +/** + * Landing header for a catalog deep-link (e.g. a warm lead arriving from a + * marketing valuation). Shows the catalog name, how many tracks it holds, and + * a single clear next action — open the agent pre-asked about this catalog — + * so the work the lead just did has an obvious continuation. + */ +const CatalogLandingHeader = ({ catalogId }: CatalogLandingHeaderProps) => { + const { data: catalogsData } = useCatalogs(); + const catalog = catalogsData?.catalogs?.find((c) => c.id === catalogId); + + const { data, isLoading } = useArtistCatalogSongs({ + catalogId, + pageSize: 1, + }); + const trackCount = data?.pages?.[0]?.pagination?.total_count ?? 0; + + const name = catalog?.name ?? "Your catalog"; + const prompt = `Tell me about my catalog "${name}" — what stands out across these tracks, and what should I do next?`; + + return ( +
+
+

{name}

+

+ {isLoading ? ( + + ) : ( + <> + {trackCount} {trackCount === 1 ? "track" : "tracks"} in this catalog + + )} +

+
+ +
+ ); +}; + +export default CatalogLandingHeader; diff --git a/components/Catalog/CatalogSongsPage.tsx b/components/Catalog/CatalogSongsPage.tsx index 228d29e65..eec78ab23 100644 --- a/components/Catalog/CatalogSongsPage.tsx +++ b/components/Catalog/CatalogSongsPage.tsx @@ -1,6 +1,7 @@ "use client"; import CatalogSongsPageContent from "./CatalogSongsPageContent"; +import CatalogLandingHeader from "./CatalogLandingHeader"; import { useRouter } from "next/navigation"; import { ArrowLeft } from "lucide-react"; @@ -27,7 +28,7 @@ const CatalogSongsPage = ({ catalogId }: CatalogSongsPageProps) => { Back to Catalogs -

Catalog Songs

+ ); diff --git a/components/Catalog/CatalogsPageContent.tsx b/components/Catalog/CatalogsPageContent.tsx index dff747ae6..b84466806 100644 --- a/components/Catalog/CatalogsPageContent.tsx +++ b/components/Catalog/CatalogsPageContent.tsx @@ -2,6 +2,7 @@ import useCatalogs from "@/hooks/useCatalogs"; import CatalogCard from "./CatalogCard"; +import EmptyCatalogsState from "./EmptyCatalogsState"; import { Skeleton } from "@/components/ui/skeleton"; import { useUserProvider } from "@/providers/UserProvder"; @@ -35,7 +36,7 @@ const CatalogsPageContent = () => { const catalogs = data?.catalogs || []; if (!catalogs.length) { - return

No catalogs found.

; + return ; } return ( diff --git a/components/Catalog/EmptyCatalogsState.tsx b/components/Catalog/EmptyCatalogsState.tsx new file mode 100644 index 000000000..2744fa7bb --- /dev/null +++ b/components/Catalog/EmptyCatalogsState.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Link from "next/link"; +import { Sparkles, BarChart3 } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +const VALUATION_URL = "https://recoupable.com/valuation"; +const AGENT_PROMPT = + "Help me set up my first catalog and understand what my music is worth."; + +/** + * Onboarding empty state for an account with no catalogs yet. Replaces the bare + * "No catalogs found." text with a guided next step: run a free valuation (the + * same flow that materializes a catalog) or ask the agent to help build one. + */ +const EmptyCatalogsState = () => { + return ( +
+

No catalogs yet

+

+ A catalog is your collection of tracks with their play counts and + estimated value. Run a free valuation to measure your catalog, or ask the + agent to help you build one. +

+
+ + +
+
+ ); +}; + +export default EmptyCatalogsState;