Files
calendar/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts
T
sean-brydonandGitHub 8e0cdf3671 feat: Onboarding V3 (#24299)
* feat: redirect to new onboarding flow

* Getting started

* Brand details

* Preview organization brands

* Orgs team pages

* Invite team steps

* Move to global zustand store

* Few darkmdoe fixes

* Wip onboarding + stripe flow

* Default plan state

Server Action for gettting slug satus of org

* Remove onboardingId

* Confirmation prompt

* Update old onboarding flow handlers to handle new fields

* update onboarding hook

* Filter out organization section for none -company emails

* Match placeholders to users domain

* Drop migration

* Wip new onboarding intent

* WIP flow for self-hosted. Same service call just split logic

* WIP

* Add TODO

* Use onboarding user type instead of trpc session

* WIP

* WIP

* pass role and team name from onboarding to save in schema

* Add test to ensure role + name + team are persisted into onboarding table

* migrate roles to enum values

* Update ENUM

* Fix type error

* Redirect if flag is disabled

* Revert packages

* Revert all packages/* changes to original branch point

* Layout fixes + design

* Add slugify

* Support saving brand,logo and banner

* Cleanup

* iMobile fixes

* More mobile and darkmdoe fixes

* Add I18n

* Fix lock file

* Fix types

* Fix types errors

* Copy changes
2025-10-23 20:01:54 +01:00

100 lines
3.0 KiB
TypeScript

import { useRouter } from "next/navigation";
import { useState } from "react";
import { CreationSource } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { showToast } from "@calcom/ui/components/toast";
import type { OnboardingState } from "../store/onboarding-store";
export const useSubmitOnboarding = () => {
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const intentToCreateOrg = trpc.viewer.organizations.intentToCreateOrg.useMutation();
const submitOnboarding = async (store: OnboardingState, userEmail: string) => {
setIsSubmitting(true);
setError(null);
try {
const {
selectedPlan,
organizationDetails,
organizationBrand,
teams,
invites,
inviteRole,
resetOnboarding,
} = store;
if (selectedPlan !== "organization") {
throw new Error("Only organization plan is currently supported");
}
// Prepare teams data
const teamsData = teams
.filter((team) => team.name.trim().length > 0)
.map((team) => ({
id: -1, // New team
name: team.name,
isBeingMigrated: false,
slug: null,
}));
// Prepare invites data
const invitedMembersData = invites
.filter((invite) => invite.email.trim().length > 0)
.map((invite) => ({
email: invite.email,
teamName: invite.team,
teamId: -1,
role: inviteRole,
}));
const result = await intentToCreateOrg.mutateAsync({
name: organizationDetails.name,
slug: organizationDetails.link,
bio: organizationDetails.bio || null,
logo: organizationBrand.logo,
brandColor: organizationBrand.color,
bannerUrl: organizationBrand.banner,
orgOwnerEmail: userEmail,
seats: null,
pricePerSeat: null,
isPlatform: false,
creationSource: CreationSource.WEBAPP,
teams: teamsData,
invitedMembers: invitedMembersData,
});
// If there's a checkout URL, redirect to Stripe (billing enabled flow)
if (result.checkoutUrl) {
window.location.href = result.checkoutUrl;
return;
}
// No checkout URL means billing is disabled (self-hosted flow)
// Organization has already been created by the backend
showToast("Organization created successfully!", "success");
// TODO: after this redirect we need to hard refresh the page to see org
resetOnboarding();
router.push("/getting-started");
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to create organization";
setError(errorMessage);
showToast(errorMessage, "error");
console.error("Organization creation error:", err);
} finally {
setIsSubmitting(false);
}
};
return {
submitOnboarding,
isSubmitting,
error,
};
};