Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions components/Catalog/CatalogLandingHeader.tsx
Original file line number Diff line number Diff line change
@@ -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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Fetches ALL catalogs via useCatalogs() just to extract one catalog name with .find(). For a warm-lead deep-link page where the catalog list cache may be cold, this is an unnecessary full-list fetch. A targeted useCatalog(id) hook or accepting catalogName as a prop would be more efficient.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Catalog/CatalogLandingHeader.tsx, line 21:

<comment>Fetches ALL catalogs via useCatalogs() just to extract one catalog name with `.find()`. For a warm-lead deep-link page where the catalog list cache may be cold, this is an unnecessary full-list fetch. A targeted `useCatalog(id)` hook or accepting catalogName as a prop would be more efficient.</comment>

<file context>
@@ -0,0 +1,57 @@
+ * 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);
+
</file context>

const catalog = catalogsData?.catalogs?.find((c) => c.id === catalogId);

const { data, isLoading } = useArtistCatalogSongs({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: useArtistCatalogSongs may return an artist-filtered subset count, but the header labels it as "{n} tracks in this catalog" — implying the full catalog total. When an active artist is selected and the catalog contains tracks by multiple artists, warm leads will see an understated track count. Either use an unfiltered catalog-songs query for the header count or change the label to indicate the count is artist-scoped.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Catalog/CatalogLandingHeader.tsx, line 24:

<comment>`useArtistCatalogSongs` may return an artist-filtered subset count, but the header labels it as "{n} tracks in this catalog" — implying the full catalog total. When an active artist is selected and the catalog contains tracks by multiple artists, warm leads will see an understated track count. Either use an unfiltered catalog-songs query for the header count or change the label to indicate the count is artist-scoped.</comment>

<file context>
@@ -0,0 +1,57 @@
+  const { data: catalogsData } = useCatalogs();
+  const catalog = catalogsData?.catalogs?.find((c) => c.id === catalogId);
+
+  const { data, isLoading } = useArtistCatalogSongs({
+    catalogId,
+    pageSize: 1,
</file context>

catalogId,
pageSize: 1,
});
const trackCount = data?.pages?.[0]?.pagination?.total_count ?? 0;
Comment on lines +24 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use unfiltered counts for catalog totals

In accounts where an active artist is selected and this catalog includes tracks by multiple artists, useArtistCatalogSongs passes the selected artist name to getCatalogSongs and only removes the filter when that filtered result is zero. That makes trackCount a subset while the header says <n> tracks in this catalog, so warm leads can see an understated catalog total; fetch the header count with an unfiltered catalog-songs query or label it as artist-scoped.

Useful? React with 👍 / 👎.


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 (
<div className="rounded-xl bg-card shadow p-5 mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0">
<h1 className="text-lg md:text-xl font-semibold truncate">{name}</h1>
<p className="text-sm text-muted-foreground mt-1">
{isLoading ? (
<Skeleton className="h-4 w-24" />
) : (
<>
{trackCount} {trackCount === 1 ? "track" : "tracks"} in this catalog
</>
)}
</p>
</div>
<Button asChild className="shrink-0">
<Link href={`/?q=${encodeURIComponent(prompt)}`}>
<Sparkles />
Ask the agent about this catalog
</Link>
</Button>
</div>
);
};

export default CatalogLandingHeader;
3 changes: 2 additions & 1 deletion components/Catalog/CatalogSongsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import CatalogSongsPageContent from "./CatalogSongsPageContent";
import CatalogLandingHeader from "./CatalogLandingHeader";
import { useRouter } from "next/navigation";
import { ArrowLeft } from "lucide-react";

Expand All @@ -27,7 +28,7 @@ const CatalogSongsPage = ({ catalogId }: CatalogSongsPageProps) => {
Back to Catalogs
</button>
</div>
<h1 className="text-lg md:text-xl font-medium pb-4">Catalog Songs</h1>
<CatalogLandingHeader catalogId={catalogId} />
<CatalogSongsPageContent catalogId={catalogId} />
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion components/Catalog/CatalogsPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -35,7 +36,7 @@ const CatalogsPageContent = () => {
const catalogs = data?.catalogs || [];

if (!catalogs.length) {
return <p className="text-sm text-muted-foreground">No catalogs found.</p>;
return <EmptyCatalogsState />;
}

return (
Expand Down
43 changes: 43 additions & 0 deletions components/Catalog/EmptyCatalogsState.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="rounded-xl bg-card shadow p-8 text-center max-w-xl mx-auto">
<h2 className="text-lg font-semibold">No catalogs yet</h2>
<p className="text-sm text-muted-foreground mt-2">
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.
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center mt-6">
<Button asChild>
<a href={VALUATION_URL} target="_blank" rel="noreferrer">
<BarChart3 />
Measure your catalog value
</a>
</Button>
<Button asChild variant="outline">
<Link href={`/?q=${encodeURIComponent(AGENT_PROMPT)}`}>
<Sparkles />
Ask the agent
</Link>
</Button>
</div>
</div>
);
};

export default EmptyCatalogsState;
Loading