diff --git a/components/Agents/AgentEditDialog.tsx b/components/Agents/AgentEditDialog.tsx index 703151aaa..d96ac363b 100644 --- a/components/Agents/AgentEditDialog.tsx +++ b/components/Agents/AgentEditDialog.tsx @@ -10,10 +10,11 @@ import { 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 type { AgentTemplateRow } from "@/types/AgentTemplates"; import { useState, useEffect } from "react"; +import { type CreateAgentFormData } from "./schemas"; +import { useAgentForm } from "@/hooks/useAgentForm"; +import { useEditAgentTemplate } from "@/hooks/useEditAgentTemplate"; interface AgentEditDialogProps { agent: AgentTemplateRow; @@ -21,68 +22,45 @@ interface AgentEditDialogProps { const AgentEditDialog: React.FC = ({ agent }) => { const [open, setOpen] = useState(false); - const { userData } = useUserProvider(); - const queryClient = useQueryClient(); - const [currentSharedEmails, setCurrentSharedEmails] = useState(agent.shared_emails || []); - - const editTemplate = useMutation({ - mutationFn: async (values: { - title?: string; - description?: string; - prompt?: string; - tags?: string[]; - isPrivate?: boolean; - shareEmails?: string[]; - }) => { - // Combine existing emails (after removals) with new emails - const finalShareEmails = values.shareEmails && values.shareEmails.length > 0 - ? [...currentSharedEmails, ...values.shareEmails] - : currentSharedEmails; - - 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(); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); - setOpen(false); - }, + const [currentSharedEmails, setCurrentSharedEmails] = useState( + agent.shared_emails || [] + ); + const form = useAgentForm({ + title: agent.title, + description: agent.description, + prompt: agent.prompt, + tags: agent.tags ?? [], + isPrivate: agent.is_private, + shareEmails: [], + }); + const editTemplate = useEditAgentTemplate({ + agent, + currentSharedEmails, + onSuccess: () => setOpen(false), }); - - const onSubmit = (values: { - title: string; - description: string; - prompt: string; - tags: string[]; - isPrivate: boolean; - shareEmails?: string[]; - }) => { - editTemplate.mutate(values); - }; const handleExistingEmailsChange = (emails: string[]) => { setCurrentSharedEmails(emails); }; - // Reset current shared emails when dialog opens or agent changes + // Reset form state when the dialog opens for a specific agent, but do not + // clobber in-progress edits on background refetches. useEffect(() => { if (open) { setCurrentSharedEmails(agent.shared_emails || []); + form.reset({ + title: agent.title, + description: agent.description, + prompt: agent.prompt, + tags: agent.tags ?? [], + isPrivate: agent.is_private, + shareEmails: [], + }); } - }, [open, agent.shared_emails]); + // Reset only when the dialog opens for a different agent identity. + // Depending on all agent fields would wipe in-progress edits on background refetch. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, agent.id, form]); return ( @@ -97,16 +75,9 @@ const AgentEditDialog: React.FC = ({ agent }) => { Update the agent template details. editTemplate.mutate(values)} isSubmitting={editTemplate.isPending} - initialValues={{ - title: agent.title, - description: agent.description, - prompt: agent.prompt, - tags: agent.tags ?? [], - isPrivate: agent.is_private, - shareEmails: [], - }} existingSharedEmails={currentSharedEmails} onExistingEmailsChange={handleExistingEmailsChange} submitLabel="Save changes" @@ -117,5 +88,3 @@ const AgentEditDialog: React.FC = ({ agent }) => { }; export default AgentEditDialog; - - diff --git a/components/Agents/AgentVisibilityControl.tsx b/components/Agents/AgentVisibilityControl.tsx new file mode 100644 index 000000000..6872dcdb5 --- /dev/null +++ b/components/Agents/AgentVisibilityControl.tsx @@ -0,0 +1,39 @@ +import { UseFormReturn } from "react-hook-form"; +import { Switch } from "@/components/ui/switch"; +import { CreateAgentFormData } from "./schemas"; + +interface AgentVisibilityControlProps { + form: UseFormReturn; +} + +const AgentVisibilityControl = ({ form }: AgentVisibilityControlProps) => { + const isPrivate = form.watch("isPrivate"); + + return ( +
+ + Public + + + form.setValue("isPrivate", checked, { + shouldDirty: true, + shouldValidate: true, + }) + } + /> + + Private + +
+ ); +}; + +export default AgentVisibilityControl; diff --git a/components/Agents/Agents.tsx b/components/Agents/Agents.tsx index f9f12c672..a6e8be113 100644 --- a/components/Agents/Agents.tsx +++ b/components/Agents/Agents.tsx @@ -6,8 +6,8 @@ import { useAgentData } from "./useAgentData"; import { useAgentToggleFavorite } from "./useAgentToggleFavorite"; import type { Agent } from "./useAgentData"; import CreateAgentButton from "./CreateAgentButton"; -import { Switch } from "@/components/ui/switch"; import AgentsSkeleton from "./AgentsSkeleton"; +import AgentsVisibilityFilter from "./AgentsVisibilityFilter"; const Agents = () => { const { push } = useRouter(); @@ -35,12 +35,10 @@ const Agents = () => { Agents
-
- - {isPrivate ? "Private" : "Public"} - - togglePrivate()} /> -
+
diff --git a/components/Agents/AgentsVisibilityFilter.tsx b/components/Agents/AgentsVisibilityFilter.tsx new file mode 100644 index 000000000..f3905ad46 --- /dev/null +++ b/components/Agents/AgentsVisibilityFilter.tsx @@ -0,0 +1,52 @@ +import { cn } from "@/lib/utils"; + +interface AgentsVisibilityFilterProps { + isPrivate: boolean; + togglePrivate: () => void; +} + +const AgentsVisibilityFilter = ({ + isPrivate, + togglePrivate, +}: AgentsVisibilityFilterProps) => { + return ( +
+ + +
+ ); +}; + +export default AgentsVisibilityFilter; diff --git a/components/Agents/CreateAgentDialog.tsx b/components/Agents/CreateAgentDialog.tsx index bbcac6a10..c406ad264 100644 --- a/components/Agents/CreateAgentDialog.tsx +++ b/components/Agents/CreateAgentDialog.tsx @@ -7,10 +7,11 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import CreateAgentForm from "./CreateAgentForm"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { type CreateAgentFormData } from "./schemas"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useUserProvider } from "@/providers/UserProvder"; +import { useAgentForm } from "@/hooks/useAgentForm"; interface CreateAgentDialogProps { children: React.ReactNode; @@ -20,6 +21,7 @@ const CreateAgentDialog = ({ children }: CreateAgentDialogProps) => { const [open, setOpen] = useState(false); const { userData } = useUserProvider(); const queryClient = useQueryClient(); + const form = useAgentForm(); const createTemplate = useMutation({ mutationFn: async (values: CreateAgentFormData) => { @@ -44,6 +46,12 @@ const CreateAgentDialog = ({ children }: CreateAgentDialogProps) => { createTemplate.mutate(values); }; + useEffect(() => { + if (!open) { + form.reset(); + } + }, [form, open]); + return ( {children} @@ -56,7 +64,11 @@ 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..8a48f21f5 100644 --- a/components/Agents/CreateAgentForm.tsx +++ b/components/Agents/CreateAgentForm.tsx @@ -1,45 +1,27 @@ -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; -import { createAgentSchema, type CreateAgentFormData } from "./schemas"; +import { UseFormReturn } from "react-hook-form"; +import { type CreateAgentFormData } from "./schemas"; import FormFields from "./FormFields"; import TagSelector from "./TagSelector"; import PrivacySection from "./PrivacySection"; import SubmitButton from "./SubmitButton"; interface CreateAgentFormProps { + form: UseFormReturn; onSubmit: (values: CreateAgentFormData) => void; isSubmitting?: boolean; - initialValues?: Partial; submitLabel?: string; existingSharedEmails?: string[]; onExistingEmailsChange?: (emails: string[]) => void; } -const CreateAgentForm = ({ onSubmit, isSubmitting, initialValues, submitLabel, existingSharedEmails, onExistingEmailsChange }: CreateAgentFormProps) => { - const form = useForm({ - resolver: zodResolver(createAgentSchema), - defaultValues: { - title: initialValues?.title ?? "", - description: initialValues?.description ?? "", - prompt: initialValues?.prompt ?? "", - tags: initialValues?.tags ?? [], - isPrivate: initialValues?.isPrivate ?? false, - shareEmails: initialValues?.shareEmails ?? [], - }, - }); - - const isPrivate = form.watch("isPrivate"); - - // Ensure shareEmails is initialized when private is toggled - useEffect(() => { - if (!isPrivate) { - form.setValue("shareEmails", []); - } else if (!form.getValues("shareEmails")) { - form.setValue("shareEmails", []); - } - }, [isPrivate, form]); - +const CreateAgentForm = ({ + form, + onSubmit, + isSubmitting, + submitLabel, + existingSharedEmails, + onExistingEmailsChange, +}: CreateAgentFormProps) => { return (
diff --git a/components/Agents/EmailShareInput.tsx b/components/Agents/EmailShareInput.tsx index 90196d4f1..e2e9ca7ac 100644 --- a/components/Agents/EmailShareInput.tsx +++ b/components/Agents/EmailShareInput.tsx @@ -57,6 +57,10 @@ const EmailShareInput = ({ emails, existingSharedEmails = [], onEmailsChange, on return (
+

+ Add email addresses for the people who should be able to view this + private agent. +

; @@ -14,27 +14,25 @@ const PrivacySection = ({ form, existingSharedEmails = [], onExistingEmailsChang const isPrivate = form.watch("isPrivate"); return ( - <> -
- form.setValue("isPrivate", checked)} - /> - +
+
+ +
{isPrivate && ( { - form.setValue("shareEmails", emails, { shouldDirty: true, shouldValidate: true }); - }} - onExistingEmailsChange={onExistingEmailsChange} - /> + emails={form.watch("shareEmails") ?? []} + existingSharedEmails={existingSharedEmails} + onEmailsChange={(emails) => { + form.setValue("shareEmails", emails, { shouldDirty: true, shouldValidate: true }); + }} + onExistingEmailsChange={onExistingEmailsChange} + /> )} - +
); }; diff --git a/components/Agents/TagSelector.tsx b/components/Agents/TagSelector.tsx index 70190e640..18c371899 100644 --- a/components/Agents/TagSelector.tsx +++ b/components/Agents/TagSelector.tsx @@ -10,6 +10,7 @@ interface TagSelectorProps { const TagSelector = ({ form }: TagSelectorProps) => { const { tags } = useAgentData(); + const availableTags = tags.filter((tag) => tag !== "Recommended"); const selectedTags = form.watch("tags") ?? []; const toggleTag = (tag: string) => { @@ -24,7 +25,7 @@ const TagSelector = ({ form }: TagSelectorProps) => {
- {tags.filter((t) => t !== "Recommended").map((tag) => { + {availableTags.map((tag) => { const isSelected = selectedTags.includes(tag); return ( ) { + const form = useForm({ + resolver: zodResolver(createAgentSchema), + defaultValues: { + title: initialValues?.title ?? "", + description: initialValues?.description ?? "", + prompt: initialValues?.prompt ?? "", + tags: initialValues?.tags ?? [], + isPrivate: initialValues?.isPrivate ?? false, + shareEmails: initialValues?.shareEmails ?? [], + }, + }); + + const isPrivate = form.watch("isPrivate"); + + useEffect(() => { + if (!isPrivate) { + form.setValue("shareEmails", []); + } else if (!form.getValues("shareEmails")) { + form.setValue("shareEmails", []); + } + }, [isPrivate, form]); + + return form; +} diff --git a/hooks/useEditAgentTemplate.ts b/hooks/useEditAgentTemplate.ts new file mode 100644 index 000000000..fcc7fcb66 --- /dev/null +++ b/hooks/useEditAgentTemplate.ts @@ -0,0 +1,52 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useUserProvider } from "@/providers/UserProvder"; +import type { AgentTemplateRow } from "@/types/AgentTemplates"; +import type { CreateAgentFormData } from "@/components/Agents/schemas"; + +interface UseEditAgentTemplateOptions { + agent: AgentTemplateRow; + currentSharedEmails: string[]; + onSuccess: () => void; +} + +export function useEditAgentTemplate({ + agent, + currentSharedEmails, + onSuccess, +}: UseEditAgentTemplateOptions) { + const { userData } = useUserProvider(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (values: CreateAgentFormData) => { + const finalShareEmails = values.isPrivate + ? [...currentSharedEmails, ...(values.shareEmails ?? [])] + : []; + + 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(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); + onSuccess(); + }, + }); +} diff --git a/lib/supabase/agent_templates/createAgentTemplateShares.ts b/lib/supabase/agent_templates/createAgentTemplateShares.ts index c2714b57e..62c01eb8f 100644 --- a/lib/supabase/agent_templates/createAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/createAgentTemplateShares.ts @@ -1,5 +1,6 @@ -import getAccountDetailsByEmails from "@/lib/supabase/account_emails/getAccountDetailsByEmails"; +import { insertAgentTemplateEmailShares } from "./insertAgentTemplateEmailShares"; import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; +import { splitShareEmailsByAccount } from "./splitShareEmailsByAccount"; /** * Create agent template shares for multiple email addresses @@ -14,21 +15,23 @@ export async function createAgentTemplateShares( return; } - // Get user accounts by email using utility function - const userEmails = await getAccountDetailsByEmails(emails); + const { accountIds, invitedEmails } = await splitShareEmailsByAccount(emails); - if (userEmails.length === 0) { - return; + if (accountIds.length > 0) { + await insertAgentTemplateShares( + accountIds.map((accountId) => ({ + template_id: templateId, + user_id: accountId, + })) + ); } - // Create share records for found users (filter out null account_ids) - const sharesData = userEmails - .filter(userEmail => userEmail.account_id !== null) - .map(userEmail => ({ + if (invitedEmails.length > 0) { + await insertAgentTemplateEmailShares( + invitedEmails.map((email) => ({ template_id: templateId, - user_id: userEmail.account_id!, - })); - - // Insert shares using utility function - await insertAgentTemplateShares(sharesData); + email, + })) + ); + } } diff --git a/lib/supabase/agent_templates/deleteAgentTemplateEmailShares.ts b/lib/supabase/agent_templates/deleteAgentTemplateEmailShares.ts new file mode 100644 index 000000000..f2c2d81a3 --- /dev/null +++ b/lib/supabase/agent_templates/deleteAgentTemplateEmailShares.ts @@ -0,0 +1,21 @@ +import supabase from "@/lib/supabase/serverClient"; +import type { Tables } from "@/types/database.types"; + +type AgentTemplateEmailShare = Tables<"agent_template_email_shares">; + +export async function deleteAgentTemplateEmailSharesByTemplateId( + templateId: string +): Promise { + const { data, error } = await supabase + .from("agent_template_email_shares") + .delete() + .eq("template_id", templateId) + .select("*"); + + if (error) { + console.error("Error deleting agent template email shares:", error); + throw error; + } + + return data || []; +} diff --git a/lib/supabase/agent_templates/getAgentTemplateEmailSharesByTemplateIds.ts b/lib/supabase/agent_templates/getAgentTemplateEmailSharesByTemplateIds.ts new file mode 100644 index 000000000..31feda931 --- /dev/null +++ b/lib/supabase/agent_templates/getAgentTemplateEmailSharesByTemplateIds.ts @@ -0,0 +1,24 @@ +import supabase from "@/lib/supabase/serverClient"; +import type { Tables } from "@/types/database.types"; + +type AgentTemplateEmailShare = Tables<"agent_template_email_shares">; + +export async function getAgentTemplateEmailSharesByTemplateIds( + templateIds: string[] +): Promise { + if (!Array.isArray(templateIds) || templateIds.length === 0) { + return []; + } + + const { data, error } = await supabase + .from("agent_template_email_shares") + .select("*") + .in("template_id", templateIds); + + if (error) { + console.error("Error fetching agent template email shares:", error); + throw error; + } + + return data || []; +} diff --git a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts b/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts index 0a2fa8874..4967d729e 100644 --- a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts +++ b/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts @@ -1,13 +1,17 @@ import getAccountEmails from "@/lib/supabase/account_emails/getAccountEmails"; +import { getAgentTemplateEmailSharesByTemplateIds } from "./getAgentTemplateEmailSharesByTemplateIds"; 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); + const [shares, invitedEmailShares] = await Promise.all([ + getAgentTemplateSharesByTemplateIds(templateIds), + getAgentTemplateEmailSharesByTemplateIds(templateIds), + ]); - if (shares.length === 0) return {}; + if (shares.length === 0 && invitedEmailShares.length === 0) return {}; // Get all user IDs who have access to these templates const userIds = [...new Set(shares.map(share => share.user_id))]; @@ -39,6 +43,14 @@ export async function getSharedEmailsForTemplates(templateIds: string[]): Promis emailMap[share.template_id].push(...userEmails); }); + invitedEmailShares.forEach((share) => { + if (!emailMap[share.template_id]) { + emailMap[share.template_id] = []; + } + + emailMap[share.template_id].push(share.email); + }); + // Remove duplicates for each template Object.keys(emailMap).forEach(templateId => { emailMap[templateId] = [...new Set(emailMap[templateId])]; diff --git a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts b/lib/supabase/agent_templates/getSharedTemplatesForUser.ts index b9d0a23e8..f281fe3d6 100644 --- a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts +++ b/lib/supabase/agent_templates/getSharedTemplatesForUser.ts @@ -1,5 +1,6 @@ import supabase from "@/lib/supabase/serverClient"; import type { AgentTemplateRow } from "@/types/AgentTemplates"; +import getAccountEmails from "@/lib/supabase/account_emails/getAccountEmails"; interface SharedTemplateData { templates: AgentTemplateRow | AgentTemplateRow[]; @@ -36,5 +37,42 @@ export async function getSharedTemplatesForUser(userId: string): Promise row.email) + .filter((email): email is string => typeof email === "string" && email.length > 0); + + if (emailAddresses.length === 0) { + return templates; + } + + const { data: emailShareData, error: emailShareError } = await supabase + .from("agent_template_email_shares") + .select(` + templates:agent_templates( + id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at + ) + `) + .in("email", emailAddresses); + + if (emailShareError) { + throw emailShareError; + } + + emailShareData?.forEach((share: SharedTemplateData) => { + if (!share || !share.templates) return; + + 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/insertAgentTemplateEmailShares.ts b/lib/supabase/agent_templates/insertAgentTemplateEmailShares.ts new file mode 100644 index 000000000..12e71d4db --- /dev/null +++ b/lib/supabase/agent_templates/insertAgentTemplateEmailShares.ts @@ -0,0 +1,28 @@ +import supabase from "@/lib/supabase/serverClient"; +import type { Tables } from "@/types/database.types"; + +type AgentTemplateEmailShare = Tables<"agent_template_email_shares">; +type AgentTemplateEmailShareInsert = Tables<"agent_template_email_shares">["Insert"]; + +export async function insertAgentTemplateEmailShares( + shares: AgentTemplateEmailShareInsert[] +): Promise { + if (!Array.isArray(shares) || shares.length === 0) { + return []; + } + + const { data, error } = await supabase + .from("agent_template_email_shares") + .upsert(shares, { + onConflict: "template_id,email", + ignoreDuplicates: true, + }) + .select("*"); + + if (error) { + console.error("Error inserting agent template email shares:", error); + throw error; + } + + return data || []; +} diff --git a/lib/supabase/agent_templates/splitShareEmailsByAccount.ts b/lib/supabase/agent_templates/splitShareEmailsByAccount.ts new file mode 100644 index 000000000..1e03c7a10 --- /dev/null +++ b/lib/supabase/agent_templates/splitShareEmailsByAccount.ts @@ -0,0 +1,44 @@ +import getAccountDetailsByEmails from "@/lib/supabase/account_emails/getAccountDetailsByEmails"; + +export interface SplitShareEmailsByAccountResult { + accountIds: string[]; + invitedEmails: string[]; +} + +export async function splitShareEmailsByAccount( + emails: string[] +): Promise { + const normalizedEmails = [...new Set( + emails + .map((email) => email.trim().toLowerCase()) + .filter(Boolean) + )]; + + if (normalizedEmails.length === 0) { + return { + accountIds: [], + invitedEmails: [], + }; + } + + const userEmails = await getAccountDetailsByEmails(normalizedEmails); + const resolvedEmails = new Set( + userEmails + .map((row) => row.email) + .filter((email): email is string => typeof email === "string" && email.length > 0) + .map((email) => email.toLowerCase()) + ); + + const accountIds = [...new Set( + userEmails + .map((row) => row.account_id) + .filter((accountId): accountId is string => typeof accountId === "string" && accountId.length > 0) + )]; + + const invitedEmails = normalizedEmails.filter((email) => !resolvedEmails.has(email)); + + return { + accountIds, + invitedEmails, + }; +} diff --git a/lib/supabase/agent_templates/updateAgentTemplateShares.ts b/lib/supabase/agent_templates/updateAgentTemplateShares.ts index 3ea34298e..56388371b 100644 --- a/lib/supabase/agent_templates/updateAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/updateAgentTemplateShares.ts @@ -1,6 +1,8 @@ -import getAccountDetailsByEmails from "@/lib/supabase/account_emails/getAccountDetailsByEmails"; +import { deleteAgentTemplateEmailSharesByTemplateId } from "./deleteAgentTemplateEmailShares"; import { deleteAgentTemplateSharesByTemplateId } from "./deleteAgentTemplateShares"; +import { insertAgentTemplateEmailShares } from "./insertAgentTemplateEmailShares"; import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; +import { splitShareEmailsByAccount } from "./splitShareEmailsByAccount"; /** * Update agent template shares - replaces existing shares with new ones @@ -11,27 +13,33 @@ export async function updateAgentTemplateShares( templateId: string, emails: string[] ): Promise { - // First, delete existing shares - await deleteAgentTemplateSharesByTemplateId(templateId); + // Replace both account-based shares and raw invited email shares. + await Promise.all([ + deleteAgentTemplateSharesByTemplateId(templateId), + deleteAgentTemplateEmailSharesByTemplateId(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 (!emails || emails.length === 0) { + return; + } - if (userEmails.length === 0) { - return; - } + const { accountIds, invitedEmails } = await splitShareEmailsByAccount(emails); - // Create share records for found users (filter out null account_ids) - const sharesData = userEmails - .filter(userEmail => userEmail.account_id !== null) - .map(userEmail => ({ + if (accountIds.length > 0) { + await insertAgentTemplateShares( + accountIds.map((accountId) => ({ template_id: templateId, - user_id: userEmail.account_id!, - })); + user_id: accountId, + })) + ); + } - // Insert shares using utility function - await insertAgentTemplateShares(sharesData); + if (invitedEmails.length > 0) { + await insertAgentTemplateEmailShares( + invitedEmails.map((email) => ({ + template_id: templateId, + email, + })) + ); } } diff --git a/types/database.types.ts b/types/database.types.ts index cd14166ce..75a0c6d78 100644 --- a/types/database.types.ts +++ b/types/database.types.ts @@ -623,6 +623,32 @@ export type Database = { }, ] } + agent_template_email_shares: { + Row: { + created_at: string | null + email: string + template_id: string + } + Insert: { + created_at?: string | null + email: string + template_id: string + } + Update: { + created_at?: string | null + email?: string + template_id?: string + } + Relationships: [ + { + foreignKeyName: "agent_template_email_shares_template_id_fkey" + columns: ["template_id"] + isOneToOne: false + referencedRelation: "agent_templates" + referencedColumns: ["id"] + }, + ] + } agent_templates: { Row: { created_at: string