diff --git a/app/api/workspace/create/route.ts b/app/api/workspace/create/route.ts index d511a7b3c..ea7f599fb 100644 --- a/app/api/workspace/create/route.ts +++ b/app/api/workspace/create/route.ts @@ -1,41 +1,55 @@ import { NextRequest } from "next/server"; -import { createArtistInDb } from "@/lib/supabase/createArtistInDb"; +import { NEW_API_BASE_URL } from "@/lib/consts"; /** - * Create a blank workspace for an account - * Uses the same underlying structure as artists + * @deprecated This endpoint is deprecated. Use recoup-api directly at POST /api/workspaces + * + * Create a blank workspace for an account. + * This route now proxies to recoup-api for workspace creation. */ export async function POST(req: NextRequest) { - try { - const body = await req.json(); - const { account_id, name } = body; - - if (!account_id) { - return Response.json( - { message: "Missing required parameter: account_id" }, - { status: 400 } - ); - } + const sunsetDate = new Date("2026-03-01"); + const deprecationHeaders = { + Deprecation: "true", + Sunset: sunsetDate.toUTCString(), + Link: `<${NEW_API_BASE_URL}/api/workspaces>; rel="deprecation"`, + }; - // Create workspace with provided name or default to "Untitled" - const workspaceName = name?.trim() || "Untitled"; + try { + const body = await req.text(); - // Create workspace account - // This creates: account record + account_info + account_workspace_ids link - const workspace = await createArtistInDb(workspaceName, account_id, true); + // Forward auth headers to recoup-api + const headers = new Headers(); + const authHeader = req.headers.get("authorization"); + const apiKeyHeader = req.headers.get("x-api-key"); - if (!workspace) { - return Response.json( - { message: "Failed to create workspace" }, - { status: 500 } - ); + if (authHeader) { + headers.set("authorization", authHeader); } + if (apiKeyHeader) { + headers.set("x-api-key", apiKeyHeader); + } + headers.set("Content-Type", "application/json"); + + const response = await fetch(`${NEW_API_BASE_URL}/api/workspaces`, { + method: "POST", + headers, + body, + }); + + const responseData = await response.json(); - return Response.json({ workspace }, { status: 200 }); + return Response.json(responseData, { + status: response.status, + headers: deprecationHeaders, + }); } catch (error) { - console.error("Error creating workspace:", error); + console.error("Error proxying workspace creation:", error); const message = error instanceof Error ? error.message : "Failed to create workspace"; - return Response.json({ message }, { status: 400 }); + return Response.json( + { status: "error", error: message }, + { status: 500, headers: deprecationHeaders } + ); } } diff --git a/components/CreateWorkspaceModal.tsx b/components/CreateWorkspaceModal.tsx index 0b918f857..9bc501f02 100644 --- a/components/CreateWorkspaceModal.tsx +++ b/components/CreateWorkspaceModal.tsx @@ -3,10 +3,12 @@ import { useState } from "react"; import { Music, FolderPlus, Loader } from "lucide-react"; import { toast } from "sonner"; +import { usePrivy } from "@privy-io/react-auth"; import Modal from "./Modal"; import { useArtistProvider } from "@/providers/ArtistProvider"; -import { useUserProvider } from "@/providers/UserProvder"; +import { useOrganization } from "@/providers/OrganizationProvider"; import { cn } from "@/lib/utils"; +import { NEW_API_BASE_URL } from "@/lib/consts"; interface CreateWorkspaceModalProps { isOpen: boolean; @@ -22,7 +24,8 @@ const CreateWorkspaceModal = ({ isOpen, onClose }: CreateWorkspaceModalProps) => setEditableArtist, setIsOpenSettingModal, } = useArtistProvider(); - const { userData } = useUserProvider(); + const { getAccessToken } = usePrivy(); + const { selectedOrgId } = useOrganization(); const [isCreating, setIsCreating] = useState(false); const handleCreateArtist = () => { @@ -32,39 +35,41 @@ const CreateWorkspaceModal = ({ isOpen, onClose }: CreateWorkspaceModalProps) => }; const handleCreateWorkspace = async () => { - if (!userData?.id) return; - setIsCreating(true); try { - const response = await fetch("/api/workspace/create", { + const accessToken = await getAccessToken(); + if (!accessToken) { + toast.error("Please sign in to create a workspace"); + return; + } + + const response = await fetch(`${NEW_API_BASE_URL}/api/workspaces`, { method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ account_id: userData.id }), + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + organization_id: selectedOrgId, + }), }); const data = await response.json(); if (response.ok && data.workspace) { - // Format workspace to match ArtistRecord structure const newWorkspace = { ...data.workspace, account_id: data.workspace.id, }; - // Auto-select the new workspace setSelectedArtist(newWorkspace); - - // Set it as editable and open settings modal setEditableArtist(newWorkspace); setIsOpenSettingModal(true); - - // Refresh the list to include the new workspace await getArtists(); } else { - toast.error("Failed to create workspace"); + toast.error(data.error || "Failed to create workspace"); } - } catch (error) { - console.error("Error creating workspace:", error); + } catch { toast.error("Failed to create workspace"); } finally { setIsCreating(false); diff --git a/components/VercelChat/chat.tsx b/components/VercelChat/chat.tsx index ae1bc2295..64059d856 100644 --- a/components/VercelChat/chat.tsx +++ b/components/VercelChat/chat.tsx @@ -19,6 +19,7 @@ import { useDropzone } from "@/hooks/useDropzone"; import FileDragOverlay from "./FileDragOverlay"; import { Loader } from "lucide-react"; import { memo } from "react"; +import { useOrganization } from "@/providers/OrganizationProvider"; interface ChatProps { id: string; @@ -27,8 +28,11 @@ interface ChatProps { } export function Chat({ id, reportId, initialMessages }: ChatProps) { + const { selectedOrgId } = useOrganization(); + const providerKey = `${id}-${selectedOrgId ?? "personal"}`; + return ( - + ); diff --git a/hooks/useInitialArtists.tsx b/hooks/useInitialArtists.tsx index d894bbf35..495cd4866 100644 --- a/hooks/useInitialArtists.tsx +++ b/hooks/useInitialArtists.tsx @@ -1,5 +1,5 @@ import { ArtistRecord } from "@/types/Artist"; -import { Dispatch, SetStateAction, useEffect, useCallback } from "react"; +import { Dispatch, SetStateAction, useEffect, useCallback, useMemo } from "react"; import { useLocalStorage } from "usehooks-ts"; type ArtistSelections = Record; @@ -10,7 +10,6 @@ const useInitialArtists = ( setSelectedArtist: Dispatch>, selectedOrgId: string | null, ) => { - // Store all org selections in a single object const [selections, setSelections] = useLocalStorage( "RECOUP_ARTIST_SELECTIONS", {} @@ -18,7 +17,11 @@ const useInitialArtists = ( const orgKey = selectedOrgId || "personal"; - // Save selected artist for current org + const validArtistIds = useMemo( + () => new Set(artists.map((a) => a.account_id)), + [artists] + ); + const saveSelection = useCallback( (artist: ArtistRecord) => { setSelections((prev) => ({ ...prev, [orgKey]: artist })); @@ -26,29 +29,34 @@ const useInitialArtists = ( [orgKey, setSelections] ); - // Restore selected artist when org changes + // Restore saved artist only if it belongs to current org useEffect(() => { + if (artists.length === 0) return; + const savedArtist = selections[orgKey]; if (savedArtist && Object.keys(savedArtist).length > 0) { - // Only update if the saved artist differs from current selection - if (savedArtist.account_id !== selectedArtist?.account_id) { - setSelectedArtist(savedArtist); + if (validArtistIds.has(savedArtist.account_id)) { + if (savedArtist.account_id !== selectedArtist?.account_id) { + setSelectedArtist(savedArtist); + } } } - }, [orgKey, selections, selectedArtist?.account_id, setSelectedArtist]); + }, [orgKey, selections, selectedArtist?.account_id, setSelectedArtist, artists.length, validArtistIds]); - // Update selected artist with fresh data from artists list + // Sync selection with fresh artist data, clear if artist left current org or was deleted useEffect(() => { - if (selectedArtist && artists.length > 0) { - const currentArtist = artists.find( - (artist: ArtistRecord) => - artist.account_id === selectedArtist.account_id, - ); - if (currentArtist && !selectedArtist?.isWrapped) { - setSelectedArtist(currentArtist); - // Persist fresh data to localStorage so page reload shows updated image - saveSelection(currentArtist); - } + if (!selectedArtist) return; + + const currentArtist = artists.find( + (artist: ArtistRecord) => + artist.account_id === selectedArtist.account_id, + ); + + if (!currentArtist) { + setSelectedArtist(null); + } else if (!selectedArtist?.isWrapped) { + setSelectedArtist(currentArtist); + saveSelection(currentArtist); } }, [artists, selectedArtist, setSelectedArtist, saveSelection]);