From 8c1025ece9ae43536c6be2f556a8dd9059178f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?= Date: Fri, 19 Jun 2026 10:31:21 +0200 Subject: [PATCH] refactor(onboarding): extract useCreateAgent and useDeleteAgent hooks Co-Authored-By: Claude Sonnet 4.6 --- .../onboarding/hooks/use-create-agent.ts | 41 +++++ .../onboarding/hooks/use-delete-agent.ts | 25 +++ .../onboarding/steps/step-agent-create.tsx | 167 +++++++----------- 3 files changed, 129 insertions(+), 104 deletions(-) create mode 100644 src/features/onboarding/hooks/use-create-agent.ts create mode 100644 src/features/onboarding/hooks/use-delete-agent.ts diff --git a/src/features/onboarding/hooks/use-create-agent.ts b/src/features/onboarding/hooks/use-create-agent.ts new file mode 100644 index 00000000..5c34d94a --- /dev/null +++ b/src/features/onboarding/hooks/use-create-agent.ts @@ -0,0 +1,41 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { useOnboarding } from "@onboardjs/react"; +import { toast } from "sonner"; +import { createAgentAction } from "@/features/agents/agents.action"; +import type { OnboardingAgent, OnboardingDefaultsData } from "@/features/onboarding/types"; + +export const useCreateAgent = () => { + const { state, updateContext } = useOnboarding(); + + return useMutation({ + mutationFn: async (name: string) => { + const orgId = (state?.context.flowData.org as any)?.id as string | undefined; + if (!orgId) throw new Error("Missing org ID — cannot create agent"); + + const defaults = (state?.context.flowData.defaults ?? {}) as OnboardingDefaultsData; + const result = await createAgentAction({ + organizationId: orgId, + data: { name, description: "" }, + }); + if (!result?.data?.data) { + throw new Error(result?.serverError ?? `Failed to create agent "${name}"`); + } + + const newAgent: OnboardingAgent = { + id: result.data.data.id, + name: result.data.data.name, + notifierId: defaults.notifierId, + storageId: defaults.storageId, + }; + const agents = [ + ...((state?.context.flowData.agents ?? []) as OnboardingAgent[]), + newAgent, + ]; + await updateContext({ flowData: { ...state?.context.flowData, agents } }); + return newAgent; + }, + onError: (err: Error) => toast.error(err.message), + }); +}; diff --git a/src/features/onboarding/hooks/use-delete-agent.ts b/src/features/onboarding/hooks/use-delete-agent.ts new file mode 100644 index 00000000..3206be25 --- /dev/null +++ b/src/features/onboarding/hooks/use-delete-agent.ts @@ -0,0 +1,25 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { useOnboarding } from "@onboardjs/react"; +import { toast } from "sonner"; +import { deleteAgentAction } from "@/features/agents/agent-delete.action"; +import type { OnboardingAgent } from "@/features/onboarding/types"; + +export const useDeleteAgent = () => { + const { state, updateContext } = useOnboarding(); + + return useMutation({ + mutationFn: async (agentId: string) => { + const orgId = (state?.context.flowData.org as any)?.id as string | undefined; + const result = await deleteAgentAction({ agentId, organizationId: orgId }); + if (result?.data?.success === false) throw new Error("Failed to delete agent"); + + const agents = ( + (state?.context.flowData.agents ?? []) as OnboardingAgent[] + ).filter((a) => a.id !== agentId); + await updateContext({ flowData: { ...state?.context.flowData, agents } }); + }, + onError: (err: Error) => toast.error(err.message), + }); +}; diff --git a/src/features/onboarding/steps/step-agent-create.tsx b/src/features/onboarding/steps/step-agent-create.tsx index d6e1b5ba..aa404c2f 100644 --- a/src/features/onboarding/steps/step-agent-create.tsx +++ b/src/features/onboarding/steps/step-agent-create.tsx @@ -2,118 +2,77 @@ import { useState } from "react"; import { useOnboarding } from "@onboardjs/react"; -import { toast } from "sonner"; +import { X } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { X } from "lucide-react"; -import { createAgentAction } from "@/features/agents/agents.action"; -import { deleteAgentAction } from "@/features/agents/agent-delete.action"; -import type { OnboardingAgent, OnboardingDefaultsData } from "@/features/onboarding/types"; +import { useCreateAgent } from "@/features/onboarding/hooks/use-create-agent"; +import { useDeleteAgent } from "@/features/onboarding/hooks/use-delete-agent"; +import type { OnboardingAgent } from "@/features/onboarding/types"; export const StepAgentCreate = () => { - const { next, updateContext, state } = useOnboarding(); - const defaults = (state?.context.flowData.defaults ?? {}) as OnboardingDefaultsData; - const agents = (state?.context.flowData.agents ?? []) as OnboardingAgent[]; - const orgId = (state?.context.flowData.org as any)?.id as string | undefined; + const { next, state } = useOnboarding(); + const agents = (state?.context.flowData.agents ?? []) as OnboardingAgent[]; + const [name, setName] = useState(""); + const createAgent = useCreateAgent(); + const deleteAgent = useDeleteAgent(); - const [name, setName] = useState(""); - const [adding, setAdding] = useState(false); + const onAdd = () => { + if (!name.trim()) return; + createAgent.mutate(name.trim(), { onSuccess: () => setName("") }); + }; - const addAgent = async () => { - if (!name.trim()) return; - if (!orgId) { - toast.error("Missing org ID — cannot create agent"); - return; - } - setAdding(true); - try { - const result = await createAgentAction({ - organizationId: orgId, - data: { name: name.trim(), description: "" }, - }); - if (!result?.data?.data) { - toast.error(result?.serverError ?? `Failed to create agent "${name.trim()}"`); - return; - } - const newAgent: OnboardingAgent = { - id: result.data.data.id, - name: result.data.data.name, - notifierId: defaults.notifierId, - storageId: defaults.storageId, - }; - const updated = [...agents, newAgent]; - setName(""); - await updateContext({ flowData: { ...state?.context.flowData, agents: updated } }); - } finally { - setAdding(false); - } - }; + return ( +
+
+

Create an agent

+

+ Optional — agents will use your default notifier and storage. +

+
- const removeAgent = async (id: string) => { - const updated = agents.filter((a) => a.id !== id); - const result = await deleteAgentAction({ agentId: id, organizationId: orgId }); - if (result?.data?.success === false) { - toast.error("Failed to delete agent"); - } else { - await updateContext({ flowData: { ...state?.context.flowData, agents: updated } }); - } - }; - - const onContinue = async () => { - await next(); - }; - - return ( -
-
-

Create an agent

-

- Optional — agents will use your default notifier and storage. -

+ {agents.length > 0 && ( +
+ {agents.map((agent) => ( +
+ {agent.name} +
- - {agents.length > 0 && ( -
- {agents.map((agent) => ( -
- {agent.name} - -
- ))} -
- )} - -
- setName(e.target.value)} - placeholder="agent-prod" - onKeyDown={(e) => e.key === "Enter" && addAgent()} - disabled={adding} - /> - -
- - + ))}
- ); + )} + +
+ setName(e.target.value)} + placeholder="agent-prod" + onKeyDown={(e) => e.key === "Enter" && onAdd()} + disabled={createAgent.isPending} + /> + +
+ + +
+ ); };