From d4f88490ae2aec97fb332d9aef4fadda4dd26657 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Mon, 13 Apr 2026 23:15:33 +0530 Subject: [PATCH 1/3] feat: migrate artist manual save to dedicated api --- app/api/artist/profile/route.ts | 63 --------------------- hooks/useArtists.tsx | 23 ++++---- lib/accounts/updateAccountProfile.ts | 9 +-- lib/files/getKnowledgeBaseText.ts | 2 +- lib/saveArtist.tsx | 38 +++++++++++-- lib/supabase/artist/updateArtistProfile.tsx | 7 +-- lib/supabase/getArtistKnowledge.ts | 9 +-- types/Artist.tsx | 3 +- types/knowledge.ts | 5 ++ 9 files changed, 57 insertions(+), 102 deletions(-) delete mode 100644 app/api/artist/profile/route.ts create mode 100644 types/knowledge.ts diff --git a/app/api/artist/profile/route.ts b/app/api/artist/profile/route.ts deleted file mode 100644 index c30e353a8..000000000 --- a/app/api/artist/profile/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -import getFormattedArtist from "@/lib/getFormattedArtist"; -import supabase from "@/lib/supabase/serverClient"; -import updateArtistProfile from "@/lib/supabase/artist/updateArtistProfile"; -import updateArtistSocials from "@/lib/supabase/updateArtistSocials"; -import { addArtistToOrganization } from "@/lib/supabase/artist_organization_ids/addArtistToOrganization"; -import { NextRequest } from "next/server"; - -export async function POST(req: NextRequest) { - const body = await req.json(); - const image = body.image; - const name = body.name; - const artistId = body.artistId; - const email = body.email; - const profileUrls = body.profileUrls; - const label = body.label; - const instruction = body.instruction; - const knowledges = body.knowledges; - const organizationId = body.organizationId; // Optional: link new artist to org - - try { - // Track if this is a new artist creation (artistId is empty) - const isNewArtist = !artistId; - - const { account_id: artistAccountId } = await updateArtistProfile( - artistId, - email, - image, - name, - instruction, - label, - knowledges - ); - - await updateArtistSocials(artistAccountId as string, profileUrls); - - // Link new artist to organization if organizationId provided - if (isNewArtist && organizationId && artistAccountId) { - await addArtistToOrganization(artistAccountId as string, organizationId); - } - const { data: account } = await supabase - .from("accounts") - .select("*, account_info(*), account_socials(*, social:socials(*))") - .eq("id", artistAccountId) - .single(); - - if (!account) throw new Error("failed"); - - return Response.json( - { - artist: getFormattedArtist(account), - }, - { status: 200 } - ); - } catch (error) { - console.error(error); - const message = error instanceof Error ? error.message : "failed"; - return Response.json({ message }, { status: 400 }); - } -} - -export const dynamic = "force-dynamic"; -export const fetchCache = "force-no-store"; -export const revalidate = 0; diff --git a/hooks/useArtists.tsx b/hooks/useArtists.tsx index 4142299a3..04aa6c94b 100644 --- a/hooks/useArtists.tsx +++ b/hooks/useArtists.tsx @@ -3,7 +3,6 @@ import { useOrganization } from "@/providers/OrganizationProvider"; import { ArtistRecord } from "@/types/Artist"; import { useCallback, useEffect, useState } from "react"; import useArtistSetting from "./useArtistSetting"; -import { SETTING_MODE } from "@/types/Setting"; import useArtistMode from "./useArtistMode"; import saveArtist from "@/lib/saveArtist"; import useInitialArtists from "./useInitialArtists"; @@ -15,7 +14,7 @@ import { sortArtistsWithPinnedFirst } from "@/lib/artists/sortArtistsWithPinnedF const useArtists = () => { const artistSetting = useArtistSetting(); const [isLoading, setIsLoading] = useState(true); - const { email, userData } = useUserProvider(); + const { userData } = useUserProvider(); const { selectedOrgId } = useOrganization(); const { getAccessToken } = usePrivy(); const [artists, setArtists] = useState([]); @@ -117,8 +116,15 @@ const useArtists = () => { overrideKnowledges?: Array<{ name: string; url: string; type: string }>, ) => { setUpdating(true); - const saveMode = artistMode.settingMode; try { + const accessToken = await getAccessToken(); + const artistId = artistSetting.editableArtist?.account_id; + + if (!accessToken || !artistId) { + setUpdating(false); + return null; + } + const profileUrls = { TWITTER: artistSetting.twitter, TIKTOK: artistSetting.tiktok, @@ -129,25 +135,16 @@ const useArtists = () => { FACEBOOK: artistSetting.facebook, THREADS: artistSetting.threads, }; - const data = await saveArtist({ + const data = await saveArtist(accessToken, artistId, { name: artistSetting.name, image: artistSetting.image, profileUrls, instruction: artistSetting.instruction, label: artistSetting.label, knowledges: overrideKnowledges ?? artistSetting.bases, - artistId: - saveMode === SETTING_MODE.CREATE - ? "" - : artistSetting.editableArtist?.account_id, - email, - // Link new artist to selected org (only applies when creating) - organizationId: saveMode === SETTING_MODE.CREATE ? selectedOrgId : null, }); await getArtists(data.artist?.account_id); setUpdating(false); - if (artistMode.settingMode === SETTING_MODE.CREATE) - artistMode.setSettingMode(SETTING_MODE.UPDATE); return data.artist; } catch (error) { console.error(error); diff --git a/lib/accounts/updateAccountProfile.ts b/lib/accounts/updateAccountProfile.ts index 06dacc850..76c28619a 100644 --- a/lib/accounts/updateAccountProfile.ts +++ b/lib/accounts/updateAccountProfile.ts @@ -1,10 +1,5 @@ import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; - -type KnowledgeItem = { - name: string; - url: string; - type: string; -}; +import type { Knowledge } from "@/types/knowledge"; type UpdateAccountProfileArgs = { accountId: string; @@ -16,7 +11,7 @@ type UpdateAccountProfileArgs = { jobTitle?: string; roleType?: string; companyName?: string; - knowledges?: KnowledgeItem[]; + knowledges?: Knowledge[]; }; /** diff --git a/lib/files/getKnowledgeBaseText.ts b/lib/files/getKnowledgeBaseText.ts index 3a73db98c..49a68e13f 100644 --- a/lib/files/getKnowledgeBaseText.ts +++ b/lib/files/getKnowledgeBaseText.ts @@ -1,4 +1,4 @@ -import type { Knowledge } from "@/lib/supabase/artist/updateArtistProfile"; +import type { Knowledge } from "@/types/knowledge"; /** * Processes knowledge base files and extracts text content. diff --git a/lib/saveArtist.tsx b/lib/saveArtist.tsx index 849c8c04d..fd544018d 100644 --- a/lib/saveArtist.tsx +++ b/lib/saveArtist.tsx @@ -1,13 +1,39 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const saveArtist = async (artist_data: any) => { - const response = await fetch("/api/artist/profile", { - method: "POST", - body: JSON.stringify(artist_data), +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; +import type { ArtistRecord } from "@/types/Artist"; +import type { Knowledge } from "@/types/knowledge"; + +interface SaveArtistPayload { + name: string; + image: string; + instruction: string; + label: string; + knowledges: Knowledge[]; + profileUrls: Record; +} + +interface SaveArtistResponse { + artist?: ArtistRecord; + error?: string; +} + +const saveArtist = async ( + accessToken: string, + artistId: string, + artistData: SaveArtistPayload, +): Promise => { + const response = await fetch(`${getClientApiBaseUrl()}/api/artists/${artistId}`, { + method: "PATCH", + body: JSON.stringify(artistData), headers: { "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, }); - const data = await response.json(); + const data: SaveArtistResponse = await response.json(); + + if (!response.ok || !data.artist) { + throw new Error(data.error || "Failed to save artist"); + } return data; }; diff --git a/lib/supabase/artist/updateArtistProfile.tsx b/lib/supabase/artist/updateArtistProfile.tsx index 80b490e5e..9d424ecb0 100644 --- a/lib/supabase/artist/updateArtistProfile.tsx +++ b/lib/supabase/artist/updateArtistProfile.tsx @@ -7,12 +7,9 @@ import getAccountByEmail from "@/lib/supabase/accounts/getAccountByEmail"; import insertAccount from "@/lib/supabase/accounts/insertAccount"; import insertAccountArtistId from "@/lib/supabase/account_artist_ids/insertAccountArtistId"; import type { Tables } from "@/types/database.types"; +import type { Knowledge } from "@/types/knowledge"; -export type Knowledge = { - url: string; - name: string; - type: string; -}; +export type { Knowledge } from "@/types/knowledge"; export type ArtistProfile = Tables<"accounts"> & Tables<"account_info">; diff --git a/lib/supabase/getArtistKnowledge.ts b/lib/supabase/getArtistKnowledge.ts index 18145145c..48b383925 100644 --- a/lib/supabase/getArtistKnowledge.ts +++ b/lib/supabase/getArtistKnowledge.ts @@ -1,12 +1,9 @@ import supabase from "./serverClient"; +import type { Knowledge } from "@/types/knowledge"; -export interface KnowledgeBaseEntry { - url: string; - name: string; - type: string; -} +export type KnowledgeBaseEntry = Knowledge; -export async function getArtistKnowledge(artistId: string): Promise { +export async function getArtistKnowledge(artistId: string): Promise { const { data, error } = await supabase .from("account_info") .select("knowledges") diff --git a/types/Artist.tsx b/types/Artist.tsx index 3d07f88b8..44b9a4788 100644 --- a/types/Artist.tsx +++ b/types/Artist.tsx @@ -1,5 +1,6 @@ import { SOCIAL } from "./Agent"; import { FAN_TYPE } from "./fans"; +import type { Knowledge } from "./knowledge"; export type Artist = { name: string; @@ -17,7 +18,7 @@ export type ArtistRecord = { created_at?: string; id?: string; instruction?: string | null; - knowledges?: any; + knowledges?: Knowledge[] | null; label?: string | null; organization?: string | null; pinned?: boolean; diff --git a/types/knowledge.ts b/types/knowledge.ts new file mode 100644 index 000000000..77fbba747 --- /dev/null +++ b/types/knowledge.ts @@ -0,0 +1,5 @@ +export type Knowledge = { + url: string; + name: string; + type: string; +}; From cfd4154e0a7a4534ddc393c855cd9474237a6f98 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Tue, 14 Apr 2026 22:23:30 +0530 Subject: [PATCH 2/3] feat: route artist pin toggles through PATCH /api/artists/{id} Replace the chat-local /api/artist/pin endpoint with the unified PATCH artist endpoint. useArtistPinToggle now calls saveArtist with a { pinned } payload using a Privy bearer token, matching the profile save flow. Removes the dead chat route and toggleArtistPin supabase helper. Co-Authored-By: Claude Opus 4.6 --- app/api/artist/pin/route.ts | 32 ---------------- hooks/useArtistPinToggle.tsx | 38 ++++++++----------- lib/saveArtist.tsx | 13 ++++--- .../account_artist_ids/toggleArtistPin.ts | 32 ---------------- 4 files changed, 23 insertions(+), 92 deletions(-) delete mode 100644 app/api/artist/pin/route.ts delete mode 100644 lib/supabase/account_artist_ids/toggleArtistPin.ts diff --git a/app/api/artist/pin/route.ts b/app/api/artist/pin/route.ts deleted file mode 100644 index 4ad102d4f..000000000 --- a/app/api/artist/pin/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextRequest } from "next/server"; -import { toggleArtistPin } from "@/lib/supabase/account_artist_ids/toggleArtistPin"; - -export const runtime = "edge"; - -export async function POST(req: NextRequest) { - const body = await req.json(); - const { accountId, artistId, pinned } = body; - - if (!accountId || !artistId || typeof pinned !== "boolean") { - return Response.json( - { message: "Missing required fields: accountId, artistId, pinned" }, - { status: 400 } - ); - } - - try { - const result = await toggleArtistPin({ - accountId, - artistId, - pinned, - }); - - return Response.json(result, { status: 200 }); - } catch (error) { - console.error("Unexpected error:", error); - const message = error instanceof Error ? error.message : "failed"; - return Response.json({ message }, { status: 400 }); - } -} - -export const dynamic = "force-dynamic"; diff --git a/hooks/useArtistPinToggle.tsx b/hooks/useArtistPinToggle.tsx index 2977c2014..dcdff23c0 100644 --- a/hooks/useArtistPinToggle.tsx +++ b/hooks/useArtistPinToggle.tsx @@ -1,11 +1,14 @@ import { useState } from "react"; +import { usePrivy } from "@privy-io/react-auth"; import { useArtistProvider } from "@/providers/ArtistProvider"; import { useUserProvider } from "@/providers/UserProvder"; +import saveArtist from "@/lib/saveArtist"; import { ArtistRecord } from "@/types/Artist"; export const useArtistPinToggle = (artist: ArtistRecord | null) => { const { userData } = useUserProvider(); const { getArtists, setArtists } = useArtistProvider(); + const { getAccessToken } = usePrivy(); const [isPinning, setIsPinning] = useState(false); const handlePinToggle = async (e: React.MouseEvent) => { @@ -13,41 +16,32 @@ export const useArtistPinToggle = (artist: ArtistRecord | null) => { if (!artist || !userData?.id || isPinning) return; const newPinnedStatus = !artist.pinned; - - // Optimistic update - immediately update the UI + const previousPinnedStatus = artist.pinned; + setArtists((prevArtists: ArtistRecord[]) => prevArtists.map((a: ArtistRecord) => - a.account_id === artist.account_id ? { ...a, pinned: newPinnedStatus } : a - ) + a.account_id === artist.account_id ? { ...a, pinned: newPinnedStatus } : a, + ), ); setIsPinning(true); - + try { - const response = await fetch("/api/artist/pin", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - accountId: userData.id, - artistId: artist.account_id, - pinned: newPinnedStatus, - }), - }); - - if (!response.ok) { - throw new Error("Failed to toggle pin status"); + const accessToken = await getAccessToken(); + if (!accessToken) { + throw new Error("Missing access token"); } - // Refetch to ensure consistency with server + await saveArtist(accessToken, artist.account_id, { pinned: newPinnedStatus }); + await getArtists(); } catch (error) { console.error("Error toggling pin:", error); - - // Rollback optimistic update on error + setArtists((prevArtists: ArtistRecord[]) => prevArtists.map((a: ArtistRecord) => - a.account_id === artist.account_id ? { ...a, pinned: artist.pinned } : a - ) + a.account_id === artist.account_id ? { ...a, pinned: previousPinnedStatus } : a, + ), ); } finally { setIsPinning(false); diff --git a/lib/saveArtist.tsx b/lib/saveArtist.tsx index fd544018d..0e1a30dc2 100644 --- a/lib/saveArtist.tsx +++ b/lib/saveArtist.tsx @@ -3,12 +3,13 @@ import type { ArtistRecord } from "@/types/Artist"; import type { Knowledge } from "@/types/knowledge"; interface SaveArtistPayload { - name: string; - image: string; - instruction: string; - label: string; - knowledges: Knowledge[]; - profileUrls: Record; + name?: string; + image?: string; + instruction?: string; + label?: string; + knowledges?: Knowledge[]; + profileUrls?: Record; + pinned?: boolean; } interface SaveArtistResponse { diff --git a/lib/supabase/account_artist_ids/toggleArtistPin.ts b/lib/supabase/account_artist_ids/toggleArtistPin.ts deleted file mode 100644 index dcb76a779..000000000 --- a/lib/supabase/account_artist_ids/toggleArtistPin.ts +++ /dev/null @@ -1,32 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -interface ToggleArtistPinParams { - accountId: string; - artistId: string; - pinned: boolean; -} - -/** - * Toggle the pinned status of an artist for a user. - * Uses upsert to create the row if it doesn't exist (for org artists). - */ -export const toggleArtistPin = async ({ - accountId, - artistId, - pinned, -}: ToggleArtistPinParams) => { - const { error } = await supabase - .from("account_artist_ids") - .upsert( - { account_id: accountId, artist_id: artistId, pinned }, - { onConflict: "account_id,artist_id" } - ); - - if (error) { - throw new Error("Failed to update pinned status"); - } - - return { success: true, pinned }; -}; - -export default toggleArtistPin; From ceb5814c7d14ed1ff4c0ce6b5a1a768604ecbb59 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Fri, 17 Apr 2026 14:52:11 -0500 Subject: [PATCH 3/3] refactor: remove redundant Knowledge re-export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback — the module was both importing Knowledge from @/types/knowledge and re-exporting it from the same path. Drop the re-export and point the two consumers (KnowledgeBaseSection, UpdateArtistInfoSuccess) at the canonical @/types/knowledge source. Co-Authored-By: Claude Opus 4.7 (1M context) --- components/VercelChat/tools/KnowledgeBaseSection.tsx | 2 +- components/VercelChat/tools/UpdateArtistInfoSuccess.tsx | 2 +- lib/supabase/artist/updateArtistProfile.tsx | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/components/VercelChat/tools/KnowledgeBaseSection.tsx b/components/VercelChat/tools/KnowledgeBaseSection.tsx index b04d0f717..97d1f14e3 100644 --- a/components/VercelChat/tools/KnowledgeBaseSection.tsx +++ b/components/VercelChat/tools/KnowledgeBaseSection.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FileText, ExternalLink } from "lucide-react"; import Link from "next/link"; -import { Knowledge } from "@/lib/supabase/artist/updateArtistProfile"; +import { Knowledge } from "@/types/knowledge"; const KnowledgeBaseSection = ({ knowledges }: { knowledges: Knowledge[] }) => { return ( diff --git a/components/VercelChat/tools/UpdateArtistInfoSuccess.tsx b/components/VercelChat/tools/UpdateArtistInfoSuccess.tsx index 3c196177c..ccbfc0044 100644 --- a/components/VercelChat/tools/UpdateArtistInfoSuccess.tsx +++ b/components/VercelChat/tools/UpdateArtistInfoSuccess.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import ArtistHeroSection from "./ArtistHeroSection"; import KnowledgeBaseSection from "./KnowledgeBaseSection"; -import { Knowledge } from "@/lib/supabase/artist/updateArtistProfile"; +import { Knowledge } from "@/types/knowledge"; import { CheckCircle, FileText } from "lucide-react"; import { useArtistProvider } from "@/providers/ArtistProvider"; import { ArtistProfile } from "@/lib/supabase/artist/updateArtistProfile"; diff --git a/lib/supabase/artist/updateArtistProfile.tsx b/lib/supabase/artist/updateArtistProfile.tsx index 9d424ecb0..914381b78 100644 --- a/lib/supabase/artist/updateArtistProfile.tsx +++ b/lib/supabase/artist/updateArtistProfile.tsx @@ -9,8 +9,6 @@ import insertAccountArtistId from "@/lib/supabase/account_artist_ids/insertAccou import type { Tables } from "@/types/database.types"; import type { Knowledge } from "@/types/knowledge"; -export type { Knowledge } from "@/types/knowledge"; - export type ArtistProfile = Tables<"accounts"> & Tables<"account_info">; const updateArtistProfile = async (