Skip to content
Merged
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
66 changes: 40 additions & 26 deletions app/api/workspace/create/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
);
}
}

Expand Down
37 changes: 21 additions & 16 deletions components/CreateWorkspaceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 = () => {
Expand All @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion components/VercelChat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,8 +28,11 @@ interface ChatProps {
}

export function Chat({ id, reportId, initialMessages }: ChatProps) {
const { selectedOrgId } = useOrganization();
const providerKey = `${id}-${selectedOrgId ?? "personal"}`;

return (
<VercelChatProvider chatId={id} initialMessages={initialMessages}>
<VercelChatProvider key={providerKey} chatId={id} initialMessages={initialMessages}>
<ChatContent reportId={reportId} id={id} />
</VercelChatProvider>
);
Expand Down
46 changes: 27 additions & 19 deletions hooks/useInitialArtists.tsx
Original file line number Diff line number Diff line change
@@ -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<string, ArtistRecord>;
Expand All @@ -10,45 +10,53 @@ const useInitialArtists = (
setSelectedArtist: Dispatch<SetStateAction<ArtistRecord | null>>,
selectedOrgId: string | null,
) => {
// Store all org selections in a single object
const [selections, setSelections] = useLocalStorage<ArtistSelections>(
"RECOUP_ARTIST_SELECTIONS",
{}
);

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 }));
},
[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]);

Expand Down
Loading