diff --git a/src/features/onboarding/hooks/use-create-org.ts b/src/features/onboarding/hooks/use-create-org.ts new file mode 100644 index 00000000..d5a4bfbd --- /dev/null +++ b/src/features/onboarding/hooks/use-create-org.ts @@ -0,0 +1,49 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { useOnboarding } from "@onboardjs/react"; +import { toast } from "sonner"; +import { + createOrganizationAction, + updateOrganizationAction, +} from "@/features/organizations/organization.action"; +import { slugify } from "@/utils/slugify"; + +export const useCreateOrg = () => { + const { state, updateContext, next } = useOnboarding(); + + return useMutation({ + mutationFn: async (name: string) => { + const trimmed = name.trim(); + if (!trimmed) throw new Error("Organisation name is required"); + const existingOrg = state?.context.flowData.org; + + if (existingOrg) { + const result = await updateOrganizationAction({ + organizationId: existingOrg.id, + data: { name: trimmed, slug: slugify(trimmed), users: [] }, + }); + if (!result?.data?.success) { + const err = result?.data as { success: false; actionError?: any }; + throw new Error(err?.actionError?.message ?? "Failed to update organisation"); + } + await updateContext({ + flowData: { ...state?.context.flowData, org: { id: existingOrg.id, name: trimmed } }, + }); + } else { + const result = await createOrganizationAction({ name: trimmed }); + if (!result?.data?.success) { + const err = result?.data as { success: false; actionError?: any }; + throw new Error(err?.actionError?.message ?? "Failed to create organisation"); + } + const org = result.data.value; + if (!org) throw new Error("Failed to create organisation"); + await updateContext({ + flowData: { ...state?.context.flowData, org: { id: org.id, name: org.name } }, + }); + } + await next(); + }, + onError: (err: Error) => toast.error(err.message), + }); +}; diff --git a/src/features/onboarding/hooks/use-create-project.ts b/src/features/onboarding/hooks/use-create-project.ts new file mode 100644 index 00000000..9d5851ad --- /dev/null +++ b/src/features/onboarding/hooks/use-create-project.ts @@ -0,0 +1,61 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { useOnboarding } from "@onboardjs/react"; +import { toast } from "sonner"; +import { + createProjectAction, + updateProjectAction, +} from "@/features/projects/projects.action"; +import type { OnboardingProjectData } from "@/features/onboarding/types"; + +type ProjectInput = { name: string; description: string; databaseIds: string[] }; + +export const useCreateProject = () => { + const { state, updateContext, next } = useOnboarding(); + + return useMutation({ + mutationFn: async ({ name, description, databaseIds }: ProjectInput) => { + const orgId = (state?.context.flowData.org as any)?.id as string | undefined; + if (!orgId) throw new Error("No organisation ID found"); + const existingProject = state?.context.flowData.project as OnboardingProjectData | undefined; + + if (existingProject?.id) { + const result = await updateProjectAction({ + data: { name, databases: databaseIds }, + organizationId: orgId, + projectId: existingProject.id, + }); + const updateData = result?.data; + if (!updateData?.success) { + throw new Error(updateData?.actionError?.message ?? "Failed to update project"); + } + await updateContext({ + flowData: { + ...state?.context.flowData, + project: { id: existingProject.id, name, description, databaseIds }, + }, + }); + } else { + const result = await createProjectAction({ + data: { name, databases: databaseIds }, + organizationId: orgId, + }); + const createData = result?.data; + if (!createData?.success) { + throw new Error(createData?.actionError?.message ?? "Failed to create project"); + } + const project = createData.value; + if (!project) throw new Error("Failed to create project"); + await updateContext({ + flowData: { + ...state?.context.flowData, + project: { id: project.id, name: project.name, description, databaseIds }, + }, + }); + } + await next(); + }, + onError: (err: Error) => toast.error(err.message), + }); +}; diff --git a/src/features/onboarding/steps/step-org-create.tsx b/src/features/onboarding/steps/step-org-create.tsx index c46e78eb..0e0a69c2 100644 --- a/src/features/onboarding/steps/step-org-create.tsx +++ b/src/features/onboarding/steps/step-org-create.tsx @@ -2,46 +2,28 @@ import { useState } from "react"; import { useOnboarding } from "@onboardjs/react"; -import { useMutation } from "@tanstack/react-query"; -import { toast } from "sonner"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { createOrganizationAction } from "@/features/organizations/organization.action"; +import { useCreateOrg } from "@/features/onboarding/hooks/use-create-org"; export const StepOrgCreate = () => { - const { next, updateContext, state } = useOnboarding(); + const { state } = useOnboarding(); const existingOrg = state?.context.flowData.org; + const isEditMode = !!existingOrg; const [name, setName] = useState(existingOrg?.name ?? ""); - const mutation = useMutation({ - mutationFn: async () => { - if (!name.trim()) throw new Error("Organisation name is required"); - const result = await createOrganizationAction({ name: name.trim() }); - if (!result?.data?.success) { - const errorData = result?.data as { success: false; actionError?: any }; - throw new Error(errorData?.actionError?.message ?? "Failed to create organisation"); - } - const org = result.data.value; - if (!org) throw new Error("Failed to create organisation"); - await updateContext({ - flowData: { - ...state?.context.flowData, - org: { id: org.id, name: org.name }, - }, - }); - await next(); - }, - onError: (err: Error) => { - toast.error(err.message); - }, - }); + const mutation = useCreateOrg(); return (
-

Create your organisation

-

This step can't be skipped.

+

+ {isEditMode ? "Edit your organisation" : "Create your organisation"} +

+

+ {isEditMode ? "Rename your organisation." : "This step can't be skipped."} +

@@ -54,10 +36,12 @@ export const StepOrgCreate = () => {
); diff --git a/src/features/onboarding/steps/step-project-create.tsx b/src/features/onboarding/steps/step-project-create.tsx index 6da31175..dc0ed29f 100644 --- a/src/features/onboarding/steps/step-project-create.tsx +++ b/src/features/onboarding/steps/step-project-create.tsx @@ -2,74 +2,28 @@ import { useState } from "react"; import { useOnboarding } from "@onboardjs/react"; -import { useMutation } from "@tanstack/react-query"; -import { toast } from "sonner"; import { Check, Database } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; -import { createProjectAction, updateProjectAction } from "@/features/projects/projects.action"; +import { useCreateProject } from "@/features/onboarding/hooks/use-create-project"; import type { OnboardingDatabase, OnboardingProjectData } from "@/features/onboarding/types"; export const StepProjectCreate = () => { - const { next, updateContext, state } = useOnboarding(); + const { state } = useOnboarding(); const existingProject = state?.context.flowData.project as OnboardingProjectData | undefined; const databases = (state?.context.flowData.databases ?? []) as OnboardingDatabase[]; - const orgId = (state?.context.flowData.org as any)?.id as string | undefined; const isUpdateMode = !!existingProject; const [name, setName] = useState(existingProject?.name ?? ""); const [description, setDescription] = useState(existingProject?.description ?? ""); const [databaseIds, setDatabaseIds] = useState(existingProject?.databaseIds ?? []); - const toggleDb = (id: string) => { - setDatabaseIds((prev) => (prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id])); - }; + const mutation = useCreateProject(); - const mutation = useMutation({ - mutationFn: async () => { - if (!orgId) throw new Error("No organisation ID found"); - if (isUpdateMode && existingProject?.id) { - const result = await updateProjectAction({ - data: { name: name.trim(), databases: databaseIds }, - organizationId: orgId, - projectId: existingProject.id, - }); - const updateData = result?.data; - if (!updateData?.success) { - throw new Error(updateData?.actionError?.message ?? "Failed to update project"); - } - await updateContext({ - flowData: { - ...state?.context.flowData, - project: { id: existingProject.id, name: name.trim(), description, databaseIds }, - }, - }); - } else { - const result = await createProjectAction({ - data: { name: name.trim(), databases: databaseIds }, - organizationId: orgId, - }); - const createData = result?.data; - if (!createData?.success) { - throw new Error(createData?.actionError?.message ?? "Failed to create project"); - } - const project = createData.value; - if (!project) throw new Error("Failed to create project"); - await updateContext({ - flowData: { - ...state?.context.flowData, - project: { id: project.id, name: project.name, description, databaseIds }, - }, - }); - } - await next(); - }, - onError: (err: Error) => { - toast.error(err.message); - }, - }); + const toggleDb = (id: string) => + setDatabaseIds((prev) => (prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id])); return (
@@ -133,7 +87,7 @@ export const StepProjectCreate = () => { )}