Files
calendar/apps/web/modules/onboarding/hooks/useSubmitOnboarding.ts
T
Eunjae LeeGitHubClaude Opus 4.5Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
81e4241f85 refactor: apply biome formatting to apps/web (#27692)
* refactor: apply biome formatting to apps/web (batch 1)

Formats apps/web non-modules directories:
- apps/web/app
- apps/web/lib
- apps/web/pages
- apps/web/styles
- apps/web/server
- apps/web/test
- Root-level .ts and .mjs files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules (batch 2)

Formats smaller apps/web/modules directories:
- onboarding, data-table, users, apps, auth
- integration-attribute-sync, shell, availability
- feature-flags, team, webhooks, getting-started
- feature-opt-in, troubleshooter, schedules, notifications
- signup-view.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules (batch 3)

Formats medium apps/web/modules directories:
- bookings
- event-types
- insights
- embed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules/ee (batch 4)

Formats apps/web/modules/ee directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web (batch 5)

Formats remaining apps/web directories:
- apps/web/modules/settings
- apps/web/modules/booking-audit
- apps/web/playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/components (batch 6)

Formats remaining apps/web/components files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to new apps/web files from main

Formats newly added/modified files from main merge:
- WorkflowStepContainer.tsx (resolved merge conflicts)
- Newly moved files (blocklist, form-builder, schedules, etc.)
- Other files modified in main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to new apps/web files from main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-10 23:25:24 +05:30

148 lines
5.0 KiB
TypeScript

import { useState } from "react";
import { setShowNewOrgModalFlag } from "@calcom/web/modules/ee/organizations/hooks/useWelcomeModal";
import { useFlagMap } from "@calcom/features/flags/context/provider";
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 [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const flags = useFlagMap();
const intentToCreateOrg = trpc.viewer.organizations.intentToCreateOrg.useMutation();
const submitOnboarding = async (
store: OnboardingState,
userEmail: string,
invitesToSubmit: OnboardingState["invites"]
) => {
setIsSubmitting(true);
setError(null);
try {
const {
selectedPlan,
organizationDetails,
organizationBrand,
teams,
inviteRole,
resetOnboarding,
migratedMembers,
} = store;
if (selectedPlan !== "organization") {
throw new Error("Only organization plan is currently supported");
}
const teamsData = teams
.filter((team) => team.name.trim().length > 0)
.map((team) => ({
id: team.id,
name: team.name,
isBeingMigrated: team.isBeingMigrated,
slug: team.slug,
}));
const invitedMembersData = invitesToSubmit
.filter((invite) => invite.email.trim().length > 0)
.map((invite) => {
// If invite has a team name, try to find the team ID (for migrated teams)
let teamId: number | undefined = undefined;
let teamName: string | undefined = undefined;
if (invite.team && invite.team.trim().length > 0) {
const matchingTeam = teams.find((team) => team.name.toLowerCase() === invite.team.toLowerCase());
if (matchingTeam?.isBeingMigrated && matchingTeam.id !== -1) {
// Use team ID for migrated teams
teamId = matchingTeam.id;
} else {
// Use team name for new teams (will be matched after creation)
teamName = invite.team;
teamId = -1;
}
}
return {
email: invite.email,
teamName,
teamId,
role: inviteRole,
};
});
const migratedMembersData = migratedMembers.map((member) => ({
email: member.email,
teamId: member.teamId,
role: member.role,
}));
const allInvitedMembers = [...invitedMembersData, ...migratedMembersData];
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: allInvitedMembers,
});
// 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");
// Set flag to show welcome modal after redirect
setShowNewOrgModalFlag();
// Check if this is a migration flow (user has already completed onboarding)
const hasMigratedTeams = teams.some((team) => team.isBeingMigrated);
if (hasMigratedTeams) {
// Migration flow - user already completed onboarding, redirect to event-types
resetOnboarding();
window.location.href = "/event-types?newOrganizationModal=true";
} else {
// Regular flow - redirect to personal onboarding
skipToPersonal(resetOnboarding);
}
} 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);
}
};
const skipToPersonal = (resetOnboarding: () => void) => {
resetOnboarding();
const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/personal/settings" : "/getting-started";
// Use window.location.href for a full page reload to ensure JWT callback runs
// without trigger="update", which will call autoMergeIdentities() and fetch org data
window.location.href = gettingStartedPath;
};
return {
submitOnboarding,
skipToPersonal,
isSubmitting,
error,
};
};