Skip to content
Closed
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
25 changes: 0 additions & 25 deletions app/api/segments/route.ts

This file was deleted.

2 changes: 1 addition & 1 deletion components/Segments/SegmentButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Card } from "@/components/ui/card";
import { Users, ArrowUpRight } from "lucide-react";
import { Segment } from "@/lib/supabase/getArtistSegments";
import type { Segment } from "@/types/Segment";
import SegmentFanCircles from "./SegmentFanCircles";

interface SegmentButtonProps {
Expand Down
2 changes: 1 addition & 1 deletion components/Segments/Segments.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useGenerateSegmentReport from "@/hooks/useGenerateSegmentReport";
import { type Segment } from "@/lib/supabase/getArtistSegments";
import type { Segment } from "@/types/Segment";
import SegmentButton from "./SegmentButton";

interface SegmentsProps {
Expand Down
22 changes: 9 additions & 13 deletions hooks/useArtistSegments.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { type Segment } from "@/lib/supabase/getArtistSegments";
import { useQuery } from "@tanstack/react-query";

async function fetchSegments(artistId: string): Promise<Segment[]> {
const response = await fetch(`/api/segments?artistId=${artistId}`);
if (!response.ok) {
throw new Error("Failed to fetch segments");
}
const segments: Segment[] = await response.json();
return segments.filter((s) => s.size > 0);
}
import { usePrivy } from "@privy-io/react-auth";
import { getArtistSegments } from "@/lib/artists/getArtistSegments";

export function useArtistSegments(artistId?: string) {
const { getAccessToken, authenticated } = usePrivy();
return useQuery({
queryKey: ["segments", artistId],
queryFn: () => fetchSegments(artistId!),
enabled: !!artistId,
staleTime: 1000 * 60 * 5, // 5 minutes
queryFn: async () => {
const accessToken = await getAccessToken();
return getArtistSegments(accessToken!, artistId!);
},
enabled: !!artistId && authenticated,
Comment on lines +9 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In @privy-io/react-auth, can usePrivy().getAccessToken() return null/undefined even when authenticated is true? Please cite the official docs/type signature.

💡 Result:

No, usePrivy.getAccessToken does not return null/undefined when authenticated is true. The official Privy documentation states that when a user is authenticated, Privy issues an app access token, and getAccessToken retrieves this token as a string, automatically refreshing it if expired. The docs show it used without checking authentication first and describe it returning a string. The authenticated boolean from usePrivy indicates the user has a valid session with an access token. If not authenticated, calling getAccessToken would fail (likely throw), but when authenticated is true, it succeeds and returns the token string. No type signature explicitly showing | null was found in docs or npm pages, and examples treat it as Promise<string>.

Citations:


Remove non-null assertions and add explicit error handling for better clarity.

The enabled guard and Privy's contract ensure getAccessToken() returns a string when authenticated is true, making the assertions on accessToken redundant. However, for production auth code, explicit checks with clear error messages are better practice than relying on assertions.

Suggested fix
     queryFn: async () => {
+      if (!artistId) {
+        throw new Error("Artist ID is required for fetching segments");
+      }
       const accessToken = await getAccessToken();
-      return getArtistSegments(accessToken!, artistId!);
+      return getArtistSegments(accessToken, artistId);
     },

Per Privy documentation, getAccessToken() returns Promise<string> when authenticated is true, and throws on error rather than returning null. The enabled guard prevents query execution when either condition is false, so the assertions are safe but unnecessary. Removing them improves readability and aligns with the guideline to handle edge cases explicitly in auth code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useArtistSegments.ts` around lines 9 - 13, Remove the non-null
assertions in the queryFn and add explicit checks: inside the queryFn of
useArtistSegments.ts, call getAccessToken() and verify the returned accessToken
is a non-empty string (and that artistId is defined) before calling
getArtistSegments; if either is missing throw or return a clear Error with a
descriptive message (e.g., "Missing access token" or "Missing artistId") so you
don't rely on the ! assertions and the enabled guard alone.

staleTime: 1000 * 60 * 5,
refetchOnWindowFocus: false,
});
}
52 changes: 52 additions & 0 deletions lib/artists/getArtistSegments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";
import type { Segment } from "@/types/Segment";

interface MappedArtistSegment {
id: string;
name: string;
size: number;
icon?: string;
}

interface GetArtistSegmentsResponse {
status: "success" | "error";
segments?: MappedArtistSegment[];
pagination?: {
total_count: number;
page: number;
limit: number;
total_pages: number;
};
error?: string;
}

/**
* Fetches aggregated segments for an artist from the dedicated API.
*
* @param accessToken - Privy access token for Bearer auth
* @param artistId - Artist account ID (path-encoded)
* @returns Consumer `Segment[]` shape, filtered to `size > 0`
*/
export async function getArtistSegments(
accessToken: string,
artistId: string,
): Promise<Segment[]> {
const response = await fetch(
`${getClientApiBaseUrl()}/api/artists/${artistId}/segments`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

URL-encode artistId before path interpolation.

Direct interpolation can produce malformed paths for unexpected IDs. Encode it before composing the URL.

Suggested fix
-    `${getClientApiBaseUrl()}/api/artists/${artistId}/segments`,
+    `${getClientApiBaseUrl()}/api/artists/${encodeURIComponent(artistId)}/segments`,

As per coding guidelines, "Implement built-in security practices for authentication and data handling."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`${getClientApiBaseUrl()}/api/artists/${artistId}/segments`,
`${getClientApiBaseUrl()}/api/artists/${encodeURIComponent(artistId)}/segments`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/artists/getArtistSegments.ts` at line 35, The URL is built by directly
interpolating artistId into the path in getArtistSegments (the
`${getClientApiBaseUrl()}/api/artists/${artistId}/segments` expression);
URL-encode artistId with encodeURIComponent before composing the URL (either
inline `${encodeURIComponent(artistId)}` or assign `const encodedArtistId =
encodeURIComponent(artistId)` and use that) so the request path is safe for any
characters in artistId.

{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);

const data: GetArtistSegmentsResponse = await response.json();

if (!response.ok || data.status === "error") {
throw new Error(data.error || "Failed to fetch segments");
}

return (data.segments || [])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fetch all segment pages before returning UI data

This helper returns only data.segments from a single response even though the API contract is explicitly paginated (pagination.total_pages is present), so any artist whose segments span multiple pages will silently lose segments in the sidebar and segments screen after this cutover. The previous local Supabase route returned the full set; this migration should either request a sufficiently large limit or iterate through all pages before mapping/filtering.

Useful? React with 👍 / 👎.

.map((s) => ({ id: s.id, name: s.name, size: s.size, icon: s.icon }))
.filter((s) => s.size > 0);
}
36 changes: 0 additions & 36 deletions lib/supabase/fan_segments/selectFanSegments.ts

This file was deleted.

35 changes: 0 additions & 35 deletions lib/supabase/getArtistSegmentNames.ts

This file was deleted.

70 changes: 0 additions & 70 deletions lib/supabase/getArtistSegments.ts

This file was deleted.

32 changes: 0 additions & 32 deletions lib/supabase/getSegmentCounts.ts

This file was deleted.

9 changes: 6 additions & 3 deletions lib/tools/getArtistSegments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ const getArtistSegments = tool({
inputSchema: schema,
execute: async ({ artist_account_id, page, limit }) => {
try {
// Construct URL with query parameters
const url = new URL(`${getClientApiBaseUrl()}/api/artist/segments`);
url.searchParams.append("artist_account_id", artist_account_id);
// Construct URL with query parameters. The artist account ID is now
// encoded in the path under the nested `/api/artists/{id}/segments`
// resource; only `page` and `limit` remain as query params.
const url = new URL(
`${getClientApiBaseUrl()}/api/artists/${artist_account_id}/segments`,
);
if (page) url.searchParams.append("page", page.toString());
if (limit) url.searchParams.append("limit", limit.toString());

Expand Down
19 changes: 19 additions & 0 deletions types/Segment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Tables } from "@/types/database.types";

type Social = Tables<"socials">;

/**
* Aggregated segment shape consumed by segment list UI
* (`SegmentsWrapper`, `FanGroupNavItem`, `MiniMenu`, `Segments`, `SegmentButton`).
*
* `fans` is optional because the dedicated `GET /api/artists/{id}/segments`
* endpoint does not return a fan roster; components that render avatars
* fall back to an empty array.
*/
export interface Segment {
id: string;
name: string;
size: number;
icon?: string;
fans?: Social[];
}
Loading