From 01d05c05006d16097b3f94b500baf2dd14ed3fc9 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 9 May 2026 23:03:40 +0530 Subject: [PATCH] feat(chat): migrate agent templates calls to dedicated api Cuts over the 6 agent-templates / agent-creator callers to the new dedicated api endpoints with Bearer auth and snake_case bodies. The GET list now embeds the creator block per row, so AgentCreator no longer fires a per-card fetch. Deletes the orphaned local routes (app/api/agent-templates/, app/api/agent-creator/) and their backing supabase helpers (lib/supabase/agent_templates/), plus admin.ts/getAccountById.ts/ getAccountDetailsByEmails.ts where no other callers remain. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/agent-creator/route.ts | 47 ------ app/api/agent-templates/favorites/route.ts | 32 ---- app/api/agent-templates/route.ts | 153 ------------------ components/Agents/AgentCard.tsx | 35 ++-- components/Agents/AgentCreator.tsx | 35 +--- components/Agents/AgentDeleteButton.tsx | 50 ++++-- components/Agents/AgentEditDialog.tsx | 75 +++++---- components/Agents/AgentHeart.tsx | 6 +- components/Agents/AgentPreviewDialog.tsx | 38 +++-- components/Agents/AgentTags.tsx | 2 +- components/Agents/Agents.tsx | 9 +- components/Agents/AgentsSkeleton.tsx | 7 +- components/Agents/CreateAgentDialog.tsx | 35 ++-- components/Agents/CreateAgentForm.tsx | 15 +- components/Agents/EmailShareInput.tsx | 23 ++- .../Agents/ExistingSharedEmailsList.tsx | 5 +- components/Agents/PrivacySection.tsx | 11 +- components/Agents/SubmitButton.tsx | 2 +- components/Agents/TagSelector.tsx | 52 +++--- components/Agents/useAgentCard.ts | 12 +- components/Agents/useAgentData.ts | 26 ++- components/Agents/useAgentToggleFavorite.ts | 40 +++-- lib/admin.ts | 6 - lib/agent-templates/fetchAgentTemplates.ts | 27 +++- .../getAccountDetailsByEmails.ts | 25 --- .../addAgentTemplateFavorite.ts | 18 --- .../agent_templates/createAgentTemplate.ts | 33 ---- .../createAgentTemplateShares.ts | 34 ---- .../agent_templates/deleteAgentTemplate.ts | 9 -- .../deleteAgentTemplateShares.ts | 30 ---- .../getAgentTemplateSharesByTemplateIds.ts | 30 ---- .../agent_templates/getAgentTemplates.ts | 14 -- .../getSharedEmailsForTemplates.ts | 48 ------ .../getSharedTemplatesForUser.ts | 40 ----- .../getUserAccessibleTemplates.ts | 43 ----- .../getUserTemplateFavorites.ts | 18 --- .../insertAgentTemplateShares.ts | 41 ----- .../listAgentTemplatesForUser.ts | 23 --- .../removeAgentTemplateFavorite.ts | 18 --- .../agent_templates/updateAgentTemplate.ts | 36 ----- .../updateAgentTemplateShares.ts | 37 ----- .../verifyAgentTemplateOwner.ts | 13 -- types/AgentTemplates.ts | 22 ++- 43 files changed, 333 insertions(+), 942 deletions(-) delete mode 100644 app/api/agent-creator/route.ts delete mode 100644 app/api/agent-templates/favorites/route.ts delete mode 100644 app/api/agent-templates/route.ts delete mode 100644 lib/admin.ts delete mode 100644 lib/supabase/account_emails/getAccountDetailsByEmails.ts delete mode 100644 lib/supabase/agent_templates/addAgentTemplateFavorite.ts delete mode 100644 lib/supabase/agent_templates/createAgentTemplate.ts delete mode 100644 lib/supabase/agent_templates/createAgentTemplateShares.ts delete mode 100644 lib/supabase/agent_templates/deleteAgentTemplate.ts delete mode 100644 lib/supabase/agent_templates/deleteAgentTemplateShares.ts delete mode 100644 lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts delete mode 100644 lib/supabase/agent_templates/getAgentTemplates.ts delete mode 100644 lib/supabase/agent_templates/getSharedEmailsForTemplates.ts delete mode 100644 lib/supabase/agent_templates/getSharedTemplatesForUser.ts delete mode 100644 lib/supabase/agent_templates/getUserAccessibleTemplates.ts delete mode 100644 lib/supabase/agent_templates/getUserTemplateFavorites.ts delete mode 100644 lib/supabase/agent_templates/insertAgentTemplateShares.ts delete mode 100644 lib/supabase/agent_templates/listAgentTemplatesForUser.ts delete mode 100644 lib/supabase/agent_templates/removeAgentTemplateFavorite.ts delete mode 100644 lib/supabase/agent_templates/updateAgentTemplate.ts delete mode 100644 lib/supabase/agent_templates/updateAgentTemplateShares.ts delete mode 100644 lib/supabase/agent_templates/verifyAgentTemplateOwner.ts diff --git a/app/api/agent-creator/route.ts b/app/api/agent-creator/route.ts deleted file mode 100644 index 8380a329c..000000000 --- a/app/api/agent-creator/route.ts +++ /dev/null @@ -1,47 +0,0 @@ -import getAccountById from "@/lib/supabase/accounts/getAccountById"; -import { ADMIN_EMAILS } from "@/lib/admin"; -import { NextRequest } from "next/server"; - -export const runtime = "edge"; - -export async function GET(req: NextRequest) { - const creatorId = req.nextUrl.searchParams.get("creatorId"); - - if (!creatorId) { - return Response.json({ message: "Missing creatorId" }, { status: 400 }); - } - - try { - const account = await getAccountById(creatorId); - - if (!account) { - return Response.json({ message: "Creator not found" }, { status: 404 }); - } - - const info = Array.isArray(account.account_info) - ? account.account_info[0] || null - : null; - const email = Array.isArray(account.account_emails) - ? account.account_emails[0]?.email || null - : null; - const isAdmin = !!email && ADMIN_EMAILS.includes(email); - - return Response.json( - { - creator: { - name: account.name || null, - image: info?.image || null, - is_admin: isAdmin, - }, - }, - { status: 200 }, - ); - } catch (e) { - const message = e instanceof Error ? e.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/app/api/agent-templates/favorites/route.ts b/app/api/agent-templates/favorites/route.ts deleted file mode 100644 index d1fb81729..000000000 --- a/app/api/agent-templates/favorites/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextResponse } from "next/server"; -import { addAgentTemplateFavorite } from "@/lib/supabase/agent_templates/addAgentTemplateFavorite"; -import { removeAgentTemplateFavorite } from "@/lib/supabase/agent_templates/removeAgentTemplateFavorite"; -import type { ToggleFavoriteRequest, ToggleFavoriteResponse } from "@/types/AgentTemplates"; - -export const runtime = "edge"; - -export async function POST(request: Request) { - try { - const { templateId, userId, isFavourite }: ToggleFavoriteRequest = await request.json(); - - if (!templateId || !userId) { - return NextResponse.json({ error: "Missing templateId or userId" }, { status: 400 }); - } - - if (isFavourite) { - await addAgentTemplateFavorite(templateId, userId); - } else { - await removeAgentTemplateFavorite(templateId, userId); - } - - return NextResponse.json({ success: true } as ToggleFavoriteResponse); - } catch (error) { - console.error("Error toggling favourite:", error); - return NextResponse.json({ error: "Failed to toggle favourite" } as ToggleFavoriteResponse, { status: 500 }); - } -} - -export const dynamic = "force-dynamic"; -export const revalidate = 0; - - diff --git a/app/api/agent-templates/route.ts b/app/api/agent-templates/route.ts deleted file mode 100644 index c594cd80f..000000000 --- a/app/api/agent-templates/route.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { NextResponse } from "next/server"; -// Supabase client is used in utilities imported below; no direct use here -import { getUserAccessibleTemplates } from "@/lib/supabase/agent_templates/getUserAccessibleTemplates"; -import { createAgentTemplate } from "@/lib/supabase/agent_templates/createAgentTemplate"; -import { updateAgentTemplate } from "@/lib/supabase/agent_templates/updateAgentTemplate"; -import { deleteAgentTemplate } from "@/lib/supabase/agent_templates/deleteAgentTemplate"; -import { verifyAgentTemplateOwner } from "@/lib/supabase/agent_templates/verifyAgentTemplateOwner"; -import { getSharedEmailsForTemplates } from "@/lib/supabase/agent_templates/getSharedEmailsForTemplates"; - -export const runtime = "edge"; - -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const userId = searchParams.get("userId"); - - const templates = await getUserAccessibleTemplates(userId ?? undefined); - - // Get shared emails for private templates - const privateTemplateIds = templates - .filter(template => template.is_private) - .map(template => template.id); - - let sharedEmails: Record = {}; - if (privateTemplateIds.length > 0) { - sharedEmails = await getSharedEmailsForTemplates(privateTemplateIds); - } - - // Add shared emails to templates - const templatesWithEmails = templates.map(template => ({ - ...template, - shared_emails: template.is_private ? (sharedEmails[template.id] || []) : [] - })); - - return NextResponse.json(templatesWithEmails); - } catch (error) { - console.error('Error fetching agent templates:', error); - return NextResponse.json({ error: 'Failed to fetch templates' }, { status: 500 }); - } -} - -export async function POST(request: Request) { - try { - const body = await request.json(); - const { - title, - description, - prompt, - tags, - isPrivate, - shareEmails, - userId, - }: { - title: string; - description: string; - prompt: string; - tags: string[]; - isPrivate: boolean; - shareEmails?: string[]; - userId?: string | null; - } = body; - - const data = await createAgentTemplate({ - title, - description, - prompt, - tags, - isPrivate, - shareEmails, - userId, - }); - return NextResponse.json(data, { status: 201 }); - } catch (error) { - console.error("Error creating agent template:", error); - return NextResponse.json({ error: "Failed to create template" }, { status: 500 }); - } -} - -export async function DELETE(request: Request) { - try { - const { id, userId } = (await request.json()) as { id: string; userId: string }; - if (!id || !userId) { - return NextResponse.json({ error: "Missing id or userId" }, { status: 400 }); - } - - const isOwner = await verifyAgentTemplateOwner(id, userId); - if (!isOwner) { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - - await deleteAgentTemplate(id); - return NextResponse.json({ success: true }); - } catch (error) { - console.error("Error deleting template:", error); - return NextResponse.json({ error: "Failed to delete template" }, { status: 500 }); - } -} - -export async function PATCH(request: Request) { - try { - const body = await request.json(); - const { - id, - userId, - title, - description, - prompt, - tags, - isPrivate, - shareEmails, - }: { - id: string; - userId: string; - title?: string; - description?: string; - prompt?: string; - tags?: string[]; - isPrivate?: boolean; - shareEmails?: string[]; - } = body; - - if (!id || !userId) { - return NextResponse.json({ error: "Missing id or userId" }, { status: 400 }); - } - - const isOwner = await verifyAgentTemplateOwner(id, userId); - if (!isOwner) { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - - const updateFields: { - title?: string; - description?: string; - prompt?: string; - tags?: string[]; - is_private?: boolean; - } = {}; - if (typeof title !== "undefined") updateFields.title = title; - if (typeof description !== "undefined") updateFields.description = description; - if (typeof prompt !== "undefined") updateFields.prompt = prompt; - if (typeof tags !== "undefined") updateFields.tags = tags; - if (typeof isPrivate !== "undefined") updateFields.is_private = isPrivate; - - const data = await updateAgentTemplate(id, updateFields, shareEmails); - return NextResponse.json(data); - } catch (error) { - console.error("Error updating agent template:", error); - return NextResponse.json({ error: "Failed to update template" }, { status: 500 }); - } -} - -export const dynamic = "force-dynamic"; -export const revalidate = 0; \ No newline at end of file diff --git a/components/Agents/AgentCard.tsx b/components/Agents/AgentCard.tsx index 5ca0dc271..e7b103fe4 100644 --- a/components/Agents/AgentCard.tsx +++ b/components/Agents/AgentCard.tsx @@ -1,5 +1,5 @@ import type React from "react"; -import { ExternalLink, Users} from "lucide-react"; +import { ExternalLink, Users } from "lucide-react"; import { Card, CardHeader, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import AgentCreator from "./AgentCreator"; @@ -22,7 +22,7 @@ interface AgentCardProps { const AgentCard: React.FC = ({ agent, onClick, - onToggleFavorite + onToggleFavorite, }) => { const { userData } = useUserProvider(); @@ -31,11 +31,11 @@ const AgentCard: React.FC = ({ pillTag, handleCardKeyDown, handleCardClick, - handleToggleFavorite + handleToggleFavorite, } = useAgentCard({ agent, onClick, - onToggleFavorite + onToggleFavorite, }); return ( @@ -62,14 +62,19 @@ const AgentCard: React.FC = ({ {pillTag || "General"} {isSharedWithUser && ( - + Shared )}
-

{agent.title}

+

+ {agent.title} +

{agent.description}

@@ -83,22 +88,30 @@ const AgentCard: React.FC = ({
e.stopPropagation()}> handleToggleFavorite(agent.id ?? "", !(agent.is_favourite ?? false))} + onToggle={() => + handleToggleFavorite( + agent.id ?? "", + !(agent.is_favourite ?? false), + ) + } /> - {userData?.id && userData.id === agent.creator ? ( + {userData?.id && userData.id === agent.creator?.id ? ( ) : ( )} - +
{/* Creator avatar or brand */} - +
); }; -export default AgentCard; \ No newline at end of file +export default AgentCard; diff --git a/components/Agents/AgentCreator.tsx b/components/Agents/AgentCreator.tsx index 77a6c566b..12dbc288b 100644 --- a/components/Agents/AgentCreator.tsx +++ b/components/Agents/AgentCreator.tsx @@ -1,39 +1,20 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; import Image from "next/image"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; +import type { AgentTemplateCreator } from "@/types/AgentTemplates"; interface AgentCreatorProps { - creatorId: string | null; + creator: AgentTemplateCreator | null; className?: string; } -type CreatorResponse = { - creator?: { - name?: string | null; - image?: string | null; - is_admin?: boolean | null; - } | null; -}; - -const AgentCreator = ({ creatorId, className }: AgentCreatorProps) => { - const { data } = useQuery({ - queryKey: ["agent-creator", creatorId], - queryFn: async () => { - const res = await fetch(`/api/agent-creator?creatorId=${creatorId}`, { cache: "no-store" }); - if (!res.ok) throw new Error("failed"); - return (await res.json()) as CreatorResponse; - }, - enabled: !!creatorId, - staleTime: 60_000, - }); - - const isAdmin = !!data?.creator?.is_admin; - const imageUrl = data?.creator?.image || ""; - const name = data?.creator?.name || ""; +const AgentCreator = ({ creator, className }: AgentCreatorProps) => { + const isAdmin = !!creator?.is_admin; + const imageUrl = creator?.image || ""; + const name = creator?.name || ""; - if (!creatorId || isAdmin) { + if (!creator || isAdmin) { return (
{ }; export default AgentCreator; - - diff --git a/components/Agents/AgentDeleteButton.tsx b/components/Agents/AgentDeleteButton.tsx index 51ef0a022..e76f55519 100644 --- a/components/Agents/AgentDeleteButton.tsx +++ b/components/Agents/AgentDeleteButton.tsx @@ -13,27 +13,43 @@ import { import { Button } from "@/components/ui/button"; import { Trash2 } from "lucide-react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { usePrivy } from "@privy-io/react-auth"; import { useUserProvider } from "@/providers/UserProvder"; +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; interface AgentDeleteButtonProps { id: string; creatorId?: string | null; } -const AgentDeleteButton: React.FC = ({ id, creatorId }) => { +const AgentDeleteButton: React.FC = ({ + id, + creatorId, +}) => { const { userData } = useUserProvider(); + const { getAccessToken } = usePrivy(); const queryClient = useQueryClient(); const isOwner = Boolean(userData?.id && userData.id === creatorId); const del = useMutation({ mutationFn: async () => { - const res = await fetch("/api/agent-templates", { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id, userId: userData?.id }), - }); - if (!res.ok) throw new Error("Failed to delete template"); - return res.json(); + const accessToken = await getAccessToken(); + if (!accessToken) throw new Error("Not authenticated"); + + const res = await fetch( + `${getClientApiBaseUrl()}/api/agent-templates/${id}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + const data = await res.json(); + if (!res.ok || data.status === "error") { + throw new Error(data.error || "Failed to delete template"); + } + return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); @@ -45,7 +61,12 @@ const AgentDeleteButton: React.FC = ({ id, creatorId }) return ( - @@ -53,12 +74,17 @@ const AgentDeleteButton: React.FC = ({ id, creatorId }) Delete this template? - This action cannot be undone. This will permanently delete the agent template. + This action cannot be undone. This will permanently delete the agent + template. Cancel - del.mutate()} disabled={del.isPending} className="bg-red-500 hover:bg-red-600 rounded-xl"> + del.mutate()} + disabled={del.isPending} + className="bg-red-500 hover:bg-red-600 rounded-xl" + > Delete @@ -68,5 +94,3 @@ const AgentDeleteButton: React.FC = ({ id, creatorId }) }; export default AgentDeleteButton; - - diff --git a/components/Agents/AgentEditDialog.tsx b/components/Agents/AgentEditDialog.tsx index 703151aaa..99ff2ccc7 100644 --- a/components/Agents/AgentEditDialog.tsx +++ b/components/Agents/AgentEditDialog.tsx @@ -11,7 +11,8 @@ import { Button } from "@/components/ui/button"; import { Pencil } from "lucide-react"; import CreateAgentForm from "./CreateAgentForm"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useUserProvider } from "@/providers/UserProvder"; +import { usePrivy } from "@privy-io/react-auth"; +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; import type { AgentTemplateRow } from "@/types/AgentTemplates"; import { useState, useEffect } from "react"; @@ -21,9 +22,11 @@ interface AgentEditDialogProps { const AgentEditDialog: React.FC = ({ agent }) => { const [open, setOpen] = useState(false); - const { userData } = useUserProvider(); + const { getAccessToken } = usePrivy(); const queryClient = useQueryClient(); - const [currentSharedEmails, setCurrentSharedEmails] = useState(agent.shared_emails || []); + const [currentSharedEmails, setCurrentSharedEmails] = useState( + agent.shared_emails || [], + ); const editTemplate = useMutation({ mutationFn: async (values: { @@ -35,26 +38,37 @@ const AgentEditDialog: React.FC = ({ agent }) => { shareEmails?: string[]; }) => { // Combine existing emails (after removals) with new emails - const finalShareEmails = values.shareEmails && values.shareEmails.length > 0 - ? [...currentSharedEmails, ...values.shareEmails] - : currentSharedEmails; + const finalShareEmails = + values.shareEmails && values.shareEmails.length > 0 + ? [...currentSharedEmails, ...values.shareEmails] + : currentSharedEmails; + + const accessToken = await getAccessToken(); + if (!accessToken) throw new Error("Not authenticated"); - const res = await fetch("/api/agent-templates", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - id: agent.id, - userId: userData?.id, - title: values.title, - description: values.description, - prompt: values.prompt, - tags: values.tags, - isPrivate: values.isPrivate, - shareEmails: finalShareEmails, - }), - }); - if (!res.ok) throw new Error("Failed to update template"); - return res.json(); + const res = await fetch( + `${getClientApiBaseUrl()}/api/agent-templates/${agent.id}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + title: values.title, + description: values.description, + prompt: values.prompt, + tags: values.tags, + is_private: values.isPrivate, + share_emails: finalShareEmails, + }), + }, + ); + const data = await res.json(); + if (!res.ok || data.status === "error") { + throw new Error(data.error || "Failed to update template"); + } + return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); @@ -87,14 +101,23 @@ const AgentEditDialog: React.FC = ({ agent }) => { return ( - - Edit Agent - Update the agent template details. + + Edit Agent + + + Update the agent template details. + = ({ agent }) => { }; export default AgentEditDialog; - - diff --git a/components/Agents/AgentHeart.tsx b/components/Agents/AgentHeart.tsx index 0e3c42ae0..98fcd3336 100644 --- a/components/Agents/AgentHeart.tsx +++ b/components/Agents/AgentHeart.tsx @@ -12,7 +12,7 @@ interface AgentHeartProps { const AgentHeart: React.FC = ({ isFavorited, onToggle, - className + className, }) => { return ( diff --git a/components/Agents/AgentPreviewDialog.tsx b/components/Agents/AgentPreviewDialog.tsx index 5d59e28e1..448a7ba27 100644 --- a/components/Agents/AgentPreviewDialog.tsx +++ b/components/Agents/AgentPreviewDialog.tsx @@ -22,7 +22,11 @@ interface AgentPreviewDialogProps { const AgentPreviewDialog: React.FC = ({ agent }) => ( - @@ -43,25 +47,39 @@ const AgentPreviewDialog: React.FC = ({ agent }) => (
-
Prompt
+
+ Prompt +
-{agent.prompt}
+            {agent.prompt}
           
-
Tags
+
+ Tags +
{(agent.tags || []).map((t) => ( - {t} + + {t} + ))}
-
Updated
-
{agent.updated_at ? format(new Date(agent.updated_at), "PPpp") : "—"}
-
Creator
-
{agent.creator ?? "Recoup"}
+
+ Updated +
+
+ {agent.updated_at + ? format(new Date(agent.updated_at), "PPpp") + : "—"} +
+
+ Creator +
+
{agent.creator?.name ?? "Recoup"}
@@ -70,5 +88,3 @@ const AgentPreviewDialog: React.FC = ({ agent }) => ( ); export default AgentPreviewDialog; - - diff --git a/components/Agents/AgentTags.tsx b/components/Agents/AgentTags.tsx index b3bb48532..fcadaec94 100644 --- a/components/Agents/AgentTags.tsx +++ b/components/Agents/AgentTags.tsx @@ -31,4 +31,4 @@ const AgentTags: React.FC = ({
); -export default AgentTags; \ No newline at end of file +export default AgentTags; diff --git a/components/Agents/Agents.tsx b/components/Agents/Agents.tsx index f9f12c672..f0a4318ec 100644 --- a/components/Agents/Agents.tsx +++ b/components/Agents/Agents.tsx @@ -39,7 +39,10 @@ const Agents = () => { {isPrivate ? "Private" : "Public"} - togglePrivate()} /> + togglePrivate()} + /> @@ -78,7 +81,9 @@ const Agents = () => { handleAgentClick(agent)} - onToggleFavorite={(id, next) => handleToggleFavorite(id, next)} + onToggleFavorite={(id, next) => + handleToggleFavorite(id, next) + } /> ))} diff --git a/components/Agents/AgentsSkeleton.tsx b/components/Agents/AgentsSkeleton.tsx index c04846a5d..1400b0717 100644 --- a/components/Agents/AgentsSkeleton.tsx +++ b/components/Agents/AgentsSkeleton.tsx @@ -4,7 +4,10 @@ const AgentsSkeleton = () => { return (
{Array.from({ length: 6 }).map((_, i) => ( -
+
{/* Header area matches CardHeader p-4 pb-2 */}
@@ -34,5 +37,3 @@ const AgentsSkeleton = () => { }; export default AgentsSkeleton; - - diff --git a/components/Agents/CreateAgentDialog.tsx b/components/Agents/CreateAgentDialog.tsx index bbcac6a10..508521ad1 100644 --- a/components/Agents/CreateAgentDialog.tsx +++ b/components/Agents/CreateAgentDialog.tsx @@ -10,7 +10,8 @@ import CreateAgentForm from "./CreateAgentForm"; import { useState } from "react"; import { type CreateAgentFormData } from "./schemas"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useUserProvider } from "@/providers/UserProvder"; +import { usePrivy } from "@privy-io/react-auth"; +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; interface CreateAgentDialogProps { children: React.ReactNode; @@ -18,21 +19,34 @@ interface CreateAgentDialogProps { const CreateAgentDialog = ({ children }: CreateAgentDialogProps) => { const [open, setOpen] = useState(false); - const { userData } = useUserProvider(); + const { getAccessToken } = usePrivy(); const queryClient = useQueryClient(); const createTemplate = useMutation({ mutationFn: async (values: CreateAgentFormData) => { - const res = await fetch("/api/agent-templates", { + const accessToken = await getAccessToken(); + if (!accessToken) throw new Error("Not authenticated"); + + const res = await fetch(`${getClientApiBaseUrl()}/api/agent-templates`, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, body: JSON.stringify({ - ...values, - userId: userData?.id ?? null, + title: values.title, + description: values.description, + prompt: values.prompt, + tags: values.tags, + is_private: values.isPrivate, + share_emails: values.shareEmails, }), }); - if (!res.ok) throw new Error("Failed to create template"); - return res.json(); + const data = await res.json(); + if (!res.ok || data.status === "error") { + throw new Error(data.error || "Failed to create template"); + } + return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); @@ -56,7 +70,10 @@ const CreateAgentDialog = ({ children }: CreateAgentDialogProps) => { Create a new intelligent agent to help manage your roster tasks. - + ); diff --git a/components/Agents/CreateAgentForm.tsx b/components/Agents/CreateAgentForm.tsx index 03ceb38b4..420d4ab9d 100644 --- a/components/Agents/CreateAgentForm.tsx +++ b/components/Agents/CreateAgentForm.tsx @@ -16,7 +16,14 @@ interface CreateAgentFormProps { onExistingEmailsChange?: (emails: string[]) => void; } -const CreateAgentForm = ({ onSubmit, isSubmitting, initialValues, submitLabel, existingSharedEmails, onExistingEmailsChange }: CreateAgentFormProps) => { +const CreateAgentForm = ({ + onSubmit, + isSubmitting, + initialValues, + submitLabel, + existingSharedEmails, + onExistingEmailsChange, +}: CreateAgentFormProps) => { const form = useForm({ resolver: zodResolver(createAgentSchema), defaultValues: { @@ -44,7 +51,11 @@ const CreateAgentForm = ({ onSubmit, isSubmitting, initialValues, submitLabel, e
- + ); diff --git a/components/Agents/EmailShareInput.tsx b/components/Agents/EmailShareInput.tsx index 90196d4f1..a4c74fcbf 100644 --- a/components/Agents/EmailShareInput.tsx +++ b/components/Agents/EmailShareInput.tsx @@ -12,7 +12,12 @@ interface EmailShareInputProps { onExistingEmailsChange?: (emails: string[]) => void; } -const EmailShareInput = ({ emails, existingSharedEmails = [], onEmailsChange, onExistingEmailsChange }: EmailShareInputProps) => { +const EmailShareInput = ({ + emails, + existingSharedEmails = [], + onEmailsChange, + onExistingEmailsChange, +}: EmailShareInputProps) => { const [inputValue, setInputValue] = useState(""); const handleKeyDown = (e: React.KeyboardEvent) => { @@ -33,7 +38,10 @@ const EmailShareInput = ({ emails, existingSharedEmails = [], onEmailsChange, on } // Check if email already exists in either existing or new emails - if (emails.includes(trimmedEmail) || existingSharedEmails.includes(trimmedEmail)) { + if ( + emails.includes(trimmedEmail) || + existingSharedEmails.includes(trimmedEmail) + ) { setInputValue(""); return; } @@ -43,12 +51,14 @@ const EmailShareInput = ({ emails, existingSharedEmails = [], onEmailsChange, on }; const removeNewEmail = (emailToRemove: string) => { - onEmailsChange(emails.filter(email => email !== emailToRemove)); + onEmailsChange(emails.filter((email) => email !== emailToRemove)); }; const removeExistingEmail = (emailToRemove: string) => { if (onExistingEmailsChange) { - onExistingEmailsChange(existingSharedEmails.filter(email => email !== emailToRemove)); + onExistingEmailsChange( + existingSharedEmails.filter((email) => email !== emailToRemove), + ); } }; @@ -72,10 +82,7 @@ const EmailShareInput = ({ emails, existingSharedEmails = [], onEmailsChange, on emails={existingSharedEmails} onRemoveEmail={removeExistingEmail} /> - +
)}
diff --git a/components/Agents/ExistingSharedEmailsList.tsx b/components/Agents/ExistingSharedEmailsList.tsx index 2dd376026..aecea6c56 100644 --- a/components/Agents/ExistingSharedEmailsList.tsx +++ b/components/Agents/ExistingSharedEmailsList.tsx @@ -6,7 +6,10 @@ interface ExistingSharedEmailsListProps { onRemoveEmail?: (email: string) => void; } -const ExistingSharedEmailsList = ({ emails, onRemoveEmail }: ExistingSharedEmailsListProps) => { +const ExistingSharedEmailsList = ({ + emails, + onRemoveEmail, +}: ExistingSharedEmailsListProps) => { if (emails.length === 0) return null; return ( diff --git a/components/Agents/PrivacySection.tsx b/components/Agents/PrivacySection.tsx index 8b6a64141..846eeebd6 100644 --- a/components/Agents/PrivacySection.tsx +++ b/components/Agents/PrivacySection.tsx @@ -10,7 +10,11 @@ interface PrivacySectionProps { onExistingEmailsChange?: (emails: string[]) => void; } -const PrivacySection = ({ form, existingSharedEmails = [], onExistingEmailsChange }: PrivacySectionProps) => { +const PrivacySection = ({ + form, + existingSharedEmails = [], + onExistingEmailsChange, +}: PrivacySectionProps) => { const isPrivate = form.watch("isPrivate"); return ( @@ -29,7 +33,10 @@ const PrivacySection = ({ form, existingSharedEmails = [], onExistingEmailsChang emails={form.watch("shareEmails") ?? []} existingSharedEmails={existingSharedEmails} onEmailsChange={(emails) => { - form.setValue("shareEmails", emails, { shouldDirty: true, shouldValidate: true }); + form.setValue("shareEmails", emails, { + shouldDirty: true, + shouldValidate: true, + }); }} onExistingEmailsChange={onExistingEmailsChange} /> diff --git a/components/Agents/SubmitButton.tsx b/components/Agents/SubmitButton.tsx index fa197a45b..5bce53e82 100644 --- a/components/Agents/SubmitButton.tsx +++ b/components/Agents/SubmitButton.tsx @@ -22,7 +22,7 @@ const SubmitButton = ({ isSubmitting, submitLabel }: SubmitButtonProps) => { {submitLabel ? `${submitLabel}...` : "Saving..."} ) : ( - submitLabel ?? "Save" + (submitLabel ?? "Save") )}
diff --git a/components/Agents/TagSelector.tsx b/components/Agents/TagSelector.tsx index 70190e640..086158b6f 100644 --- a/components/Agents/TagSelector.tsx +++ b/components/Agents/TagSelector.tsx @@ -24,32 +24,34 @@ const TagSelector = ({ form }: TagSelectorProps) => {
- {tags.filter((t) => t !== "Recommended").map((tag) => { - const isSelected = selectedTags.includes(tag); - return ( - toggleTag(tag)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - toggleTag(tag); + {tags + .filter((t) => t !== "Recommended") + .map((tag) => { + const isSelected = selectedTags.includes(tag); + return ( + toggleTag(tag)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggleTag(tag); + } + }} + className={ + isSelected + ? "cursor-pointer select-none rounded-full focus:ring-0" + : "cursor-pointer select-none rounded-full bg-transparent border-border text-muted-foreground hover:bg-muted focus:ring-0" } - }} - className={ - isSelected - ? "cursor-pointer select-none rounded-full focus:ring-0" - : "cursor-pointer select-none rounded-full bg-transparent border-border text-muted-foreground hover:bg-muted focus:ring-0" - } - variant={isSelected ? "default" : "outline"} - > - {tag} - - ); - })} + variant={isSelected ? "default" : "outline"} + > + {tag} + + ); + })}
{form.formState.errors.tags && (

diff --git a/components/Agents/useAgentCard.ts b/components/Agents/useAgentCard.ts index 8ff280fc3..8f0e55c79 100644 --- a/components/Agents/useAgentCard.ts +++ b/components/Agents/useAgentCard.ts @@ -50,9 +50,9 @@ export function useAgentCard({ const isSharedWithUser = Boolean( !!agent.is_private && !!userData?.id && - userData.id !== agent.creator && + userData.id !== agent.creator?.id && !!email && - agent.shared_emails?.includes(email) + agent.shared_emails?.includes(email), ); const handleCardKeyDown = useCallback( @@ -73,14 +73,14 @@ export function useAgentCard({ onClick(agent); } }, - [onClick, agent] + [onClick, agent], ); const handleCardClick = useCallback( (event: React.MouseEvent) => { const target = event.target as HTMLElement; const interactiveAncestor = target.closest( - "button, [role='button'], input, textarea, select, a" + "button, [role='button'], input, textarea, select, a", ); // Ignore clicks on interactive elements, but allow the card itself (role=button) @@ -93,14 +93,14 @@ export function useAgentCard({ onClick(agent); }, - [onClick, agent] + [onClick, agent], ); const handleToggleFavorite = useCallback( (agentId: string, nextFavourite: boolean) => { onToggleFavorite?.(agentId, nextFavourite); }, - [onToggleFavorite] + [onToggleFavorite], ); return { diff --git a/components/Agents/useAgentData.ts b/components/Agents/useAgentData.ts index b148ab2a2..ea208e1e8 100644 --- a/components/Agents/useAgentData.ts +++ b/components/Agents/useAgentData.ts @@ -1,6 +1,7 @@ import { useUserProvider } from "@/providers/UserProvder"; import { useEffect, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { usePrivy } from "@privy-io/react-auth"; import type { AgentTemplateRow } from "@/types/AgentTemplates"; import fetchAgentTemplates from "@/lib/agent-templates/fetchAgentTemplates"; @@ -8,6 +9,7 @@ export type Agent = AgentTemplateRow; export function useAgentData() { const { userData } = useUserProvider(); + const { getAccessToken, authenticated } = usePrivy(); const queryClient = useQueryClient(); const [agents, setAgents] = useState([]); const [selectedTag, setSelectedTag] = useState("Recommended"); @@ -17,9 +19,15 @@ export function useAgentData() { const { data, isPending } = useQuery({ queryKey: ["agent-templates"], - queryFn: () => fetchAgentTemplates(userData!), + queryFn: async () => { + const accessToken = await getAccessToken(); + if (!accessToken) { + throw new Error("Please sign in to view agent templates"); + } + return fetchAgentTemplates(accessToken); + }, retry: 1, - enabled: !!userData?.id, + enabled: authenticated && !!userData?.id, }); useEffect(() => { @@ -38,8 +46,8 @@ export function useAgentData() { new Set( data .flatMap((agent: Agent) => agent.tags || []) - .filter((tag: string) => !!tag && !actionTags.includes(tag)) - ) + .filter((tag: string) => !!tag && !actionTags.includes(tag)), + ), ); const allTags = ["Recommended", ...uniqueTags]; setTags(Array.from(new Set(allTags))); @@ -57,7 +65,7 @@ export function useAgentData() { (selectedTag === "Recommended" ? true : agent.tags?.includes(selectedTag)) && - (isPrivate ? agent.is_private === true : agent.is_private !== true) + (isPrivate ? agent.is_private === true : agent.is_private !== true), ); // Hide the "Audience Segmentation" card from UI - keep all other logic intact const gridAgents = filteredAgents; @@ -67,7 +75,13 @@ export function useAgentData() { if (userData?.id) { queryClient.prefetchQuery({ queryKey: ["agent-templates"], - queryFn: () => fetchAgentTemplates(userData), + queryFn: async () => { + const accessToken = await getAccessToken(); + if (!accessToken) { + throw new Error("Please sign in to view agent templates"); + } + return fetchAgentTemplates(accessToken); + }, staleTime: 5 * 60 * 1000, // 5 minutes }); } diff --git a/components/Agents/useAgentToggleFavorite.ts b/components/Agents/useAgentToggleFavorite.ts index 0d87717b1..7dcae11d0 100644 --- a/components/Agents/useAgentToggleFavorite.ts +++ b/components/Agents/useAgentToggleFavorite.ts @@ -1,38 +1,44 @@ import { useUserProvider } from "@/providers/UserProvder"; +import { usePrivy } from "@privy-io/react-auth"; import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; -import type { ToggleFavoriteRequest } from "@/types/AgentTemplates"; +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; export function useAgentToggleFavorite() { const { userData } = useUserProvider(); + const { getAccessToken } = usePrivy(); const queryClient = useQueryClient(); const handleToggleFavorite = async ( templateId: string, - nextFavourite: boolean + nextFavourite: boolean, ) => { if (!userData?.id || !templateId) return; - + try { - const body: ToggleFavoriteRequest = { - templateId, - userId: userData.id, - isFavourite: nextFavourite, - }; - const res = await fetch("/api/agent-templates/favorites", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - }); - + const accessToken = await getAccessToken(); + if (!accessToken) throw new Error("Not authenticated"); + + const res = await fetch( + `${getClientApiBaseUrl()}/api/agent-templates/${templateId}/favorite`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ is_favourite: nextFavourite }), + }, + ); + if (!res.ok) { throw new Error("Failed to toggle favorite"); } - + toast.success( - nextFavourite ? "Added to favorites" : "Removed from favorites" + nextFavourite ? "Added to favorites" : "Removed from favorites", ); - + // Invalidate templates list so is_favourite and favorites_count refresh queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); } catch { diff --git a/lib/admin.ts b/lib/admin.ts deleted file mode 100644 index 0b5207890..000000000 --- a/lib/admin.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Make sure this information remain private on server side -import "server-only"; - -export const ADMIN_EMAILS: string[] = [ - "sidney+1@recoupable.com", -]; diff --git a/lib/agent-templates/fetchAgentTemplates.ts b/lib/agent-templates/fetchAgentTemplates.ts index b43e9f0b2..963da3949 100644 --- a/lib/agent-templates/fetchAgentTemplates.ts +++ b/lib/agent-templates/fetchAgentTemplates.ts @@ -1,12 +1,29 @@ +import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; import type { AgentTemplateRow } from "@/types/AgentTemplates"; -import type { AccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDetails"; + +interface AgentTemplatesListResponse { + status: "success" | "error"; + templates?: AgentTemplateRow[]; + error?: string; +} const fetchAgentTemplates = async ( - userData: AccountWithDetails + accessToken: string, ): Promise => { - const res = await fetch(`/api/agent-templates?userId=${userData?.id}`); - if (!res.ok) throw new Error("Failed to fetch agent templates"); - return (await res.json()) as AgentTemplateRow[]; + const res = await fetch(`${getClientApiBaseUrl()}/api/agent-templates`, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + const data: AgentTemplatesListResponse = await res.json(); + + if (!res.ok || data.status === "error") { + throw new Error(data.error || "Failed to fetch agent templates"); + } + + return data.templates || []; }; export default fetchAgentTemplates; diff --git a/lib/supabase/account_emails/getAccountDetailsByEmails.ts b/lib/supabase/account_emails/getAccountDetailsByEmails.ts deleted file mode 100644 index 4a38f7f51..000000000 --- a/lib/supabase/account_emails/getAccountDetailsByEmails.ts +++ /dev/null @@ -1,25 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; -import type { Tables } from "@/types/database.types"; - -/** - * Get account_emails by email addresses - * @param emails Array of email addresses to query - * @returns Array of account_emails rows - */ -export default async function getAccountDetailsByEmails( - emails: string[] -): Promise[]> { - if (!Array.isArray(emails) || emails.length === 0) return []; - - const { data, error } = await supabase - .from("account_emails") - .select("*") - .in("email", emails); - - if (error) { - console.error("Error fetching account_emails by emails:", error); - return []; - } - - return data || []; -} diff --git a/lib/supabase/agent_templates/addAgentTemplateFavorite.ts b/lib/supabase/agent_templates/addAgentTemplateFavorite.ts deleted file mode 100644 index 261909c15..000000000 --- a/lib/supabase/agent_templates/addAgentTemplateFavorite.ts +++ /dev/null @@ -1,18 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function addAgentTemplateFavorite( - templateId: string, - userId: string -) { - const { error } = await supabase - .from("agent_template_favorites") - .insert({ template_id: templateId, user_id: userId }) - .select("template_id") - .maybeSingle(); - - if (error && error.code !== "23505") { - throw error; // ignore unique violation - } - - return { success: true } as const; -} diff --git a/lib/supabase/agent_templates/createAgentTemplate.ts b/lib/supabase/agent_templates/createAgentTemplate.ts deleted file mode 100644 index 276f6e31c..000000000 --- a/lib/supabase/agent_templates/createAgentTemplate.ts +++ /dev/null @@ -1,33 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; -import { createAgentTemplateShares } from "./createAgentTemplateShares"; - -export async function createAgentTemplate(params: { - title: string; - description: string; - prompt: string; - tags: string[]; - isPrivate: boolean; - shareEmails?: string[]; - userId?: string | null; -}) { - const { data, error } = await supabase - .from("agent_templates") - .insert({ - title: params.title, - description: params.description, - prompt: params.prompt, - tags: params.tags, - is_private: params.isPrivate, - creator: params.userId ?? null, - }) - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count") - .single(); - if (error) throw error; - - // Handle email sharing if agent is private and emails are provided - if (params.isPrivate && params.shareEmails && params.shareEmails.length > 0) { - await createAgentTemplateShares(data.id, params.shareEmails); - } - - return data; -} diff --git a/lib/supabase/agent_templates/createAgentTemplateShares.ts b/lib/supabase/agent_templates/createAgentTemplateShares.ts deleted file mode 100644 index c2714b57e..000000000 --- a/lib/supabase/agent_templates/createAgentTemplateShares.ts +++ /dev/null @@ -1,34 +0,0 @@ -import getAccountDetailsByEmails from "@/lib/supabase/account_emails/getAccountDetailsByEmails"; -import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; - -/** - * Create agent template shares for multiple email addresses - * @param templateId - The template ID to share - * @param emails - Array of email addresses to share with - */ -export async function createAgentTemplateShares( - templateId: string, - emails: string[] -): Promise { - if (!emails || emails.length === 0) { - return; - } - - // Get user accounts by email using utility function - const userEmails = await getAccountDetailsByEmails(emails); - - if (userEmails.length === 0) { - return; - } - - // Create share records for found users (filter out null account_ids) - const sharesData = userEmails - .filter(userEmail => userEmail.account_id !== null) - .map(userEmail => ({ - template_id: templateId, - user_id: userEmail.account_id!, - })); - - // Insert shares using utility function - await insertAgentTemplateShares(sharesData); -} diff --git a/lib/supabase/agent_templates/deleteAgentTemplate.ts b/lib/supabase/agent_templates/deleteAgentTemplate.ts deleted file mode 100644 index e1212334c..000000000 --- a/lib/supabase/agent_templates/deleteAgentTemplate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function deleteAgentTemplate(id: string) { - const { error } = await supabase.from("agent_templates").delete().eq("id", id); - if (error) throw error; - return { success: true } as const; -} - - diff --git a/lib/supabase/agent_templates/deleteAgentTemplateShares.ts b/lib/supabase/agent_templates/deleteAgentTemplateShares.ts deleted file mode 100644 index cd3e74c2a..000000000 --- a/lib/supabase/agent_templates/deleteAgentTemplateShares.ts +++ /dev/null @@ -1,30 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -interface AgentTemplateShare { - id: string; - template_id: string; - user_id: string; - created_at: string; -} - -/** - * Delete all agent template shares for a specific template - * @param templateId The template ID to delete shares for - * @returns Array of deleted share records - */ -export async function deleteAgentTemplateSharesByTemplateId( - templateId: string -): Promise { - const { data, error } = await supabase - .from("agent_template_shares") - .delete() - .eq("template_id", templateId) - .select(); - - if (error) { - console.error("Error deleting agent template shares:", error); - throw error; - } - - return (data as AgentTemplateShare[]) || []; -} diff --git a/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts b/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts deleted file mode 100644 index 27326f4b6..000000000 --- a/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts +++ /dev/null @@ -1,30 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -interface AgentTemplateShare { - template_id: string; - user_id: string; - created_at: string; -} - -/** - * Get all agent template shares for specific template IDs - * @param templateIds Array of template IDs to get shares for - * @returns Array of share records - */ -export async function getAgentTemplateSharesByTemplateIds( - templateIds: string[] -): Promise { - if (!Array.isArray(templateIds) || templateIds.length === 0) return []; - - const { data, error } = await supabase - .from("agent_template_shares") - .select("template_id, user_id, created_at") - .in("template_id", templateIds); - - if (error) { - console.error("Error fetching agent template shares:", error); - throw error; - } - - return (data as AgentTemplateShare[]) || []; -} diff --git a/lib/supabase/agent_templates/getAgentTemplates.ts b/lib/supabase/agent_templates/getAgentTemplates.ts deleted file mode 100644 index 70cfaf61b..000000000 --- a/lib/supabase/agent_templates/getAgentTemplates.ts +++ /dev/null @@ -1,14 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function getAgentTemplates() { - const { data, error } = await supabase - .from('agent_templates') - .select('id, title, description, prompt, tags, creator, is_private') - .order('title'); - - if (error) { - throw error; - } - - return data || []; -} \ No newline at end of file diff --git a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts b/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts deleted file mode 100644 index 0a2fa8874..000000000 --- a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts +++ /dev/null @@ -1,48 +0,0 @@ -import getAccountEmails from "@/lib/supabase/account_emails/getAccountEmails"; -import { getAgentTemplateSharesByTemplateIds } from "./getAgentTemplateSharesByTemplateIds"; - -export async function getSharedEmailsForTemplates(templateIds: string[]): Promise> { - if (!templateIds || templateIds.length === 0) return {}; - - // Get all shares for these templates using existing utility - const shares = await getAgentTemplateSharesByTemplateIds(templateIds); - - if (shares.length === 0) return {}; - - // Get all user IDs who have access to these templates - const userIds = [...new Set(shares.map(share => share.user_id))]; - - // Get emails for these users using existing utility - const emails = await getAccountEmails(userIds); - - // Create a map of user_id to email - const userEmailMap: Record = {}; - emails.forEach(emailRecord => { - if (emailRecord.account_id && emailRecord.email) { - if (!userEmailMap[emailRecord.account_id]) { - userEmailMap[emailRecord.account_id] = []; - } - userEmailMap[emailRecord.account_id].push(emailRecord.email); - } - }); - - // Create the final map of template_id to emails - const emailMap: Record = {}; - - shares.forEach(share => { - if (!emailMap[share.template_id]) { - emailMap[share.template_id] = []; - } - - // Add all emails for this user - const userEmails = userEmailMap[share.user_id] || []; - emailMap[share.template_id].push(...userEmails); - }); - - // Remove duplicates for each template - Object.keys(emailMap).forEach(templateId => { - emailMap[templateId] = [...new Set(emailMap[templateId])]; - }); - - return emailMap; -} diff --git a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts b/lib/supabase/agent_templates/getSharedTemplatesForUser.ts deleted file mode 100644 index b9d0a23e8..000000000 --- a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts +++ /dev/null @@ -1,40 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; -import type { AgentTemplateRow } from "@/types/AgentTemplates"; - -interface SharedTemplateData { - templates: AgentTemplateRow | AgentTemplateRow[]; -} - -export async function getSharedTemplatesForUser(userId: string): Promise { - const { data, error } = await supabase - .from("agent_template_shares") - .select(` - templates:agent_templates( - id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at - ) - `) - .eq("user_id", userId); - - if (error) throw error; - - const templates: AgentTemplateRow[] = []; - const processedIds = new Set(); - - data?.forEach((share: SharedTemplateData) => { - if (!share || !share.templates) return; - - // Handle both single template and array of templates - const templateList = Array.isArray(share.templates) - ? share.templates - : [share.templates]; - - templateList?.forEach((template: AgentTemplateRow) => { - if (template && template.id && !processedIds.has(template.id)) { - templates.push(template); - processedIds.add(template.id); - } - }); - }); - - return templates; -} diff --git a/lib/supabase/agent_templates/getUserAccessibleTemplates.ts b/lib/supabase/agent_templates/getUserAccessibleTemplates.ts deleted file mode 100644 index 0762f54e6..000000000 --- a/lib/supabase/agent_templates/getUserAccessibleTemplates.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { AgentTemplateRow } from "@/types/AgentTemplates"; -import { listAgentTemplatesForUser } from "./listAgentTemplatesForUser"; -import { getSharedTemplatesForUser } from "./getSharedTemplatesForUser"; -import { getUserTemplateFavorites } from "./getUserTemplateFavorites"; - -export async function getUserAccessibleTemplates(userId?: string | null) { - if (userId && userId !== "undefined") { - // Get owned and public templates - const ownedAndPublic = await listAgentTemplatesForUser(userId); - - // Get shared templates using dedicated utility - const sharedTemplates = await getSharedTemplatesForUser(userId); - - // Combine templates and avoid duplicates - const allTemplates = [...ownedAndPublic]; - const templateIds = new Set(ownedAndPublic.map(t => t.id)); - - sharedTemplates.forEach((template) => { - if (!templateIds.has(template.id)) { - allTemplates.push(template); - templateIds.add(template.id); - } - }); - - // Get user's favorite templates - const favouriteIds = await getUserTemplateFavorites(userId); - - // Mark favorites - return allTemplates.map((template: AgentTemplateRow) => ({ - ...template, - is_favourite: favouriteIds.has(template.id), - })); - } - - // For anonymous users, return public templates only - const publicTemplates = await listAgentTemplatesForUser(null); - return publicTemplates.map((template: AgentTemplateRow) => ({ - ...template, - is_favourite: false - })); -} - - diff --git a/lib/supabase/agent_templates/getUserTemplateFavorites.ts b/lib/supabase/agent_templates/getUserTemplateFavorites.ts deleted file mode 100644 index 04a9bc6a3..000000000 --- a/lib/supabase/agent_templates/getUserTemplateFavorites.ts +++ /dev/null @@ -1,18 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -interface TemplateFavorite { - template_id: string; -} - -export async function getUserTemplateFavorites(userId: string): Promise> { - const { data, error } = await supabase - .from("agent_template_favorites") - .select("template_id") - .eq("user_id", userId); - - if (error) throw error; - - return new Set( - (data || []).map((f: TemplateFavorite) => f.template_id) - ); -} diff --git a/lib/supabase/agent_templates/insertAgentTemplateShares.ts b/lib/supabase/agent_templates/insertAgentTemplateShares.ts deleted file mode 100644 index 23677c60a..000000000 --- a/lib/supabase/agent_templates/insertAgentTemplateShares.ts +++ /dev/null @@ -1,41 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -interface AgentTemplateShare { - id: string; - template_id: string; - user_id: string; - created_at: string; -} - -interface AgentTemplateShareInsert { - template_id: string; - user_id: string; -} - -/** - * Insert multiple agent template shares - * @param shares Array of share records to insert - * @returns Array of inserted share records - */ -export async function insertAgentTemplateShares( - shares: AgentTemplateShareInsert[] -): Promise { - if (!Array.isArray(shares) || shares.length === 0) { - return []; - } - - const { data, error } = await supabase - .from("agent_template_shares") - .upsert(shares, { - onConflict: "template_id,user_id", - ignoreDuplicates: true - }) - .select(); - - if (error) { - console.error("Error inserting agent template shares:", error); - throw error; - } - - return (data as AgentTemplateShare[]) || []; -} diff --git a/lib/supabase/agent_templates/listAgentTemplatesForUser.ts b/lib/supabase/agent_templates/listAgentTemplatesForUser.ts deleted file mode 100644 index f1674d463..000000000 --- a/lib/supabase/agent_templates/listAgentTemplatesForUser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function listAgentTemplatesForUser(userId?: string | null) { - if (userId && userId !== "undefined") { - const { data, error } = await supabase - .from("agent_templates") - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at") - .or(`creator.eq.${userId},is_private.eq.false`) - .order("title"); - if (error) throw error; - return data ?? []; - } - - const { data, error } = await supabase - .from("agent_templates") - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at") - .eq("is_private", false) - .order("title"); - if (error) throw error; - return data ?? []; -} - - diff --git a/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts b/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts deleted file mode 100644 index aff40ec1c..000000000 --- a/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts +++ /dev/null @@ -1,18 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function removeAgentTemplateFavorite( - templateId: string, - userId: string -) { - const { error } = await supabase - .from("agent_template_favorites") - .delete() - .eq("template_id", templateId) - .eq("user_id", userId); - - if (error) { - throw error; - } - - return { success: true } as const; -} diff --git a/lib/supabase/agent_templates/updateAgentTemplate.ts b/lib/supabase/agent_templates/updateAgentTemplate.ts deleted file mode 100644 index 1c489cf60..000000000 --- a/lib/supabase/agent_templates/updateAgentTemplate.ts +++ /dev/null @@ -1,36 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; -import { updateAgentTemplateShares } from "./updateAgentTemplateShares"; - -export type AgentTemplateUpdates = { - title?: string; - description?: string; - prompt?: string; - tags?: string[]; - is_private?: boolean; -}; - -export async function updateAgentTemplate( - id: string, - updates: AgentTemplateUpdates, - shareEmails?: string[] -) { - const { data, error } = await supabase - .from("agent_templates") - .update({ - ...updates, - updated_at: new Date().toISOString(), - }) - .eq("id", id) - .select( - "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at" - ) - .single(); - if (error) throw error; - - // Handle email sharing updates if shareEmails is provided - if (typeof shareEmails !== "undefined") { - await updateAgentTemplateShares(id, shareEmails); - } - - return data; -} \ No newline at end of file diff --git a/lib/supabase/agent_templates/updateAgentTemplateShares.ts b/lib/supabase/agent_templates/updateAgentTemplateShares.ts deleted file mode 100644 index 3ea34298e..000000000 --- a/lib/supabase/agent_templates/updateAgentTemplateShares.ts +++ /dev/null @@ -1,37 +0,0 @@ -import getAccountDetailsByEmails from "@/lib/supabase/account_emails/getAccountDetailsByEmails"; -import { deleteAgentTemplateSharesByTemplateId } from "./deleteAgentTemplateShares"; -import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; - -/** - * Update agent template shares - replaces existing shares with new ones - * @param templateId - The template ID to update shares for - * @param emails - Array of email addresses to share with (replaces existing) - */ -export async function updateAgentTemplateShares( - templateId: string, - emails: string[] -): Promise { - // First, delete existing shares - await deleteAgentTemplateSharesByTemplateId(templateId); - - // Then create new shares if emails provided - if (emails && emails.length > 0) { - // Get user accounts by email using utility function - const userEmails = await getAccountDetailsByEmails(emails); - - if (userEmails.length === 0) { - return; - } - - // Create share records for found users (filter out null account_ids) - const sharesData = userEmails - .filter(userEmail => userEmail.account_id !== null) - .map(userEmail => ({ - template_id: templateId, - user_id: userEmail.account_id!, - })); - - // Insert shares using utility function - await insertAgentTemplateShares(sharesData); - } -} diff --git a/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts b/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts deleted file mode 100644 index 668b090de..000000000 --- a/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts +++ /dev/null @@ -1,13 +0,0 @@ -import supabase from "@/lib/supabase/serverClient"; - -export async function verifyAgentTemplateOwner(id: string, userId: string): Promise { - const { data, error } = await supabase - .from("agent_templates") - .select("id, creator") - .eq("id", id) - .single(); - if (error) throw error; - return Boolean(data && data.creator === userId); -} - - diff --git a/types/AgentTemplates.ts b/types/AgentTemplates.ts index bc082a211..8201f646c 100644 --- a/types/AgentTemplates.ts +++ b/types/AgentTemplates.ts @@ -1,14 +1,12 @@ -export type ToggleFavoriteRequest = { - templateId: string; - userId: string; - isFavourite: boolean; -}; +export type ToggleFavoriteResponse = + | { status: "success" } + | { status: "error"; error: string }; -export type ToggleFavoriteResponse = { - success: true; - favorites_count: number | null; -} | { - error: string; +export type AgentTemplateCreator = { + id: string; + name: string | null; + image: string | null; + is_admin: boolean | null; }; export type AgentTemplateRow = { @@ -17,7 +15,7 @@ export type AgentTemplateRow = { description: string; prompt: string; tags: string[] | null; - creator: string | null; + creator: AgentTemplateCreator | null; is_private: boolean; created_at: string | null; favorites_count: number | null; @@ -27,5 +25,3 @@ export type AgentTemplateRow = { // emails the template is shared with (only for private templates) shared_emails?: string[]; }; - -