Skip to content

Commit 02b1393

Browse files
authored
chore: Update personal and team onboarding flows (remove profile/video steps, add settings, improve team invites) (calcom#24947)
## What does this PR do? - Redesigns and streamlines the onboarding flow for personal, team, and organization accounts - Consolidates shared components and improves code organization - Adds browser preview for better user experience during onboarding - Simplifies the personal onboarding flow by removing the video integration step - Enhances team onboarding with CSV upload functionality ## Visual Demo (For contributors especially) #### Image Demo: - The PR adds a new browser preview component that shows users how their profile/team will look during onboarding - Redesigned UI with a more consistent layout across all onboarding steps - Improved mobile responsiveness with better component organization ## Mandatory Tasks (DO NOT REMOVE) - [x] I have self-reviewed the code. - [x] I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox. - [x] I confirm automated tests are in place that prove my fix is effective or that my feature works. ## How should this be tested? 1. Test the complete onboarding flow for personal accounts: - Start at `/onboarding/getting-started` - Proceed through personal details and calendar setup - Verify the flow completes successfully 2. Test the team onboarding flow: - Start at `/onboarding/getting-started` and select team - Complete team details - Test the invite options including CSV upload - Verify team creation works correctly 3. Test the organization onboarding flow: - Start at `/onboarding/getting-started` and select organization - Complete organization details and branding - Test member invitations - Verify organization creation works correctly 4. Verify browser preview functionality: - Check that the preview updates in real-time as you enter information - Confirm it displays correctly on different screen sizes ## Checklist - I have read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md) - My code follows the style guidelines of this project - I have commented my code, particularly in hard-to-understand areas - I have checked if my changes generate no new warnings
1 parent 52e27ba commit 02b1393

24 files changed

Lines changed: 1310 additions & 1262 deletions

File tree

apps/web/app/(use-page-wrapper)/onboarding/getting-started/page.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ const ServerPage = async () => {
2626
return redirect("/auth/login");
2727
}
2828

29-
// Hello {username} || there. Not sure how to do this nicely with i18n just yet
30-
const userName = session.user.name || "there";
3129
const userEmail = session.user.email || "";
3230

33-
return <OnboardingView userName={userName} userEmail={userEmail} />;
31+
return <OnboardingView userEmail={userEmail} />;
3432
};
3533

3634
export default ServerPage;

apps/web/app/(use-page-wrapper)/onboarding/personal/profile/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { APP_NAME } from "@calcom/lib/constants";
77

88
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
99

10-
import { PersonalProfileView } from "~/onboarding/personal/profile/personal-profile-view";
10+
import { PersonalSettingsView } from "~/onboarding/personal/settings/personal-settings-view";
1111

1212
export const generateMetadata = async () => {
1313
return await _generateMetadata(
@@ -28,7 +28,7 @@ const ServerPage = async () => {
2828

2929
const userEmail = session.user.email || "";
3030

31-
return <PersonalProfileView userEmail={userEmail} />;
31+
return <PersonalSettingsView userEmail={userEmail} />;
3232
};
3333

3434
export default ServerPage;

apps/web/app/(use-page-wrapper)/onboarding/personal/video/page.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

apps/web/app/(use-page-wrapper)/onboarding/teams/brand/page.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"use client";
2+
3+
import { WEBAPP_URL } from "@calcom/lib/constants";
4+
import { useLocale } from "@calcom/lib/hooks/useLocale";
5+
import { Icon, type IconName } from "@calcom/ui/components/icon";
6+
7+
export const OnboardingCalendarBrowserView = () => {
8+
const { t } = useLocale();
9+
const webappUrl = WEBAPP_URL.replace(/^https?:\/\//, "");
10+
11+
const calendarIntegrations: Array<{
12+
name: string;
13+
description: string;
14+
icon: IconName;
15+
}> = [
16+
{
17+
name: t("google_calendar"),
18+
description: t("onboarding_calendar_browser_view_google_description"),
19+
icon: "calendar",
20+
},
21+
{
22+
name: t("outlook_calendar"),
23+
description: t("onboarding_calendar_browser_view_outlook_description"),
24+
icon: "mail",
25+
},
26+
{
27+
name: t("apple_calendar"),
28+
description: t("onboarding_calendar_browser_view_apple_description"),
29+
icon: "calendar-days",
30+
},
31+
];
32+
33+
return (
34+
<div className="bg-default border-subtle hidden h-full w-full flex-col rounded-l-2xl border xl:flex">
35+
{/* Browser header */}
36+
<div className="border-subtle flex min-w-0 shrink-0 items-center gap-3 rounded-t-2xl border-b bg-white p-3">
37+
{/* Navigation buttons */}
38+
<div className="flex shrink-0 items-center gap-4 opacity-50">
39+
<Icon name="arrow-left" className="text-subtle h-4 w-4" />
40+
<Icon name="arrow-right" className="text-subtle h-4 w-4" />
41+
<Icon name="rotate-cw" className="text-subtle h-4 w-4" />
42+
</div>
43+
<div className="bg-muted flex w-full items-center gap-2 rounded-[32px] px-3 py-2">
44+
<Icon name="lock" className="text-subtle h-4 w-4" />
45+
<p className="text-default text-sm font-medium leading-tight">{webappUrl}/settings/calendars</p>
46+
</div>
47+
<Icon name="ellipsis-vertical" className="text-subtle h-4 w-4" />
48+
</div>
49+
{/* Content */}
50+
<div className="bg-muted h-full pl-11 pt-11">
51+
<div className="bg-default border-muted flex h-full w-full flex-col overflow-hidden rounded-xl border">
52+
{/* Header */}
53+
<div className="border-subtle flex flex-col gap-4 border-b p-4">
54+
<div className="flex flex-col items-start gap-2">
55+
<h2 className="text-emphasis text-xl font-semibold leading-tight">
56+
{t("connect_your_calendar")}
57+
</h2>
58+
<p className="text-subtle text-sm leading-normal">
59+
{t("onboarding_calendar_browser_view_subtitle")}
60+
</p>
61+
</div>
62+
</div>
63+
64+
{/* Calendar Integrations List */}
65+
<div className="flex flex-col overflow-y-auto">
66+
{calendarIntegrations.map((integration, index) => (
67+
<div key={integration.name} className="opacity-30">
68+
{index > 0 && <div className="border-subtle h-px border-t" />}
69+
<div className="flex items-center justify-between gap-3 px-5 py-4">
70+
<div className="flex min-w-0 flex-1 items-center gap-3">
71+
<div className="bg-muted flex h-10 w-10 shrink-0 items-center justify-center rounded-lg">
72+
<Icon name={integration.icon} className="text-emphasis h-5 w-5" />
73+
</div>
74+
<div className="flex min-w-0 flex-1 flex-col gap-1">
75+
<h3 className="text-default text-sm font-semibold leading-none">{integration.name}</h3>
76+
<p className="text-subtle text-sm font-medium leading-tight">
77+
{integration.description}
78+
</p>
79+
</div>
80+
</div>
81+
<div className="bg-emphasis flex h-6 items-center justify-center rounded-md px-2">
82+
<span className="text-emphasis text-xs font-medium leading-none">{t("connected")}</span>
83+
</div>
84+
</div>
85+
</div>
86+
))}
87+
</div>
88+
</div>
89+
</div>
90+
</div>
91+
);
92+
};

apps/web/modules/onboarding/getting-started/onboarding-view.tsx

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"use client";
22

3+
import { AnimatePresence, motion } from "framer-motion";
34
import { useRouter } from "next/navigation";
5+
import { useEffect, useRef } from "react";
46

57
import { isCompanyEmail } from "@calcom/features/ee/organizations/lib/utils";
68
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -10,20 +12,43 @@ import { Button } from "@calcom/ui/components/button";
1012
import { type IconName } from "@calcom/ui/components/icon";
1113
import { RadioAreaGroup } from "@calcom/ui/components/radio";
1214

15+
import { OnboardingCard } from "../components/OnboardingCard";
1316
import { OnboardingLayout } from "../components/OnboardingLayout";
1417
import { OnboardingContinuationPrompt } from "../components/onboarding-continuation-prompt";
1518
import { PlanIcon } from "../components/plan-icon";
1619
import { useOnboardingStore, type PlanType } from "../store/onboarding-store";
1720

1821
type OnboardingViewProps = {
19-
userName: string;
2022
userEmail: string;
2123
};
2224

23-
export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) => {
25+
export const OnboardingView = ({ userEmail }: OnboardingViewProps) => {
2426
const router = useRouter();
2527
const { t } = useLocale();
2628
const { selectedPlan, setSelectedPlan } = useOnboardingStore();
29+
const previousPlanRef = useRef<PlanType | null>(null);
30+
31+
// Plan order mapping for determining direction
32+
const planOrder: Record<PlanType, number> = {
33+
personal: 0,
34+
team: 1,
35+
organization: 2,
36+
};
37+
38+
// Calculate animation direction synchronously
39+
const getDirection = (): "up" | "down" => {
40+
if (!selectedPlan || !previousPlanRef.current) return "down";
41+
const previousOrder = planOrder[previousPlanRef.current];
42+
const currentOrder = planOrder[selectedPlan];
43+
return currentOrder > previousOrder ? "down" : "up";
44+
};
45+
46+
const direction = getDirection();
47+
48+
// Update previous plan ref after render
49+
useEffect(() => {
50+
previousPlanRef.current = selectedPlan;
51+
}, [selectedPlan]);
2752

2853
const handleContinue = () => {
2954
if (selectedPlan === "organization") {
@@ -37,7 +62,7 @@ export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) =>
3762

3863
const planIconByType: Record<PlanType, IconName> = {
3964
personal: "user",
40-
team: "users",
65+
team: "user",
4166
organization: "users",
4267
};
4368

@@ -56,7 +81,7 @@ export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) =>
5681
badge: t("onboarding_plan_team_badge"),
5782
description: t("onboarding_plan_team_description"),
5883
icon: planIconByType.team,
59-
variant: "single" as const,
84+
variant: "team" as const,
6085
},
6186
{
6287
id: "organization" as PlanType,
@@ -76,26 +101,26 @@ export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) =>
76101
return true;
77102
});
78103

104+
const selectedPlanData = plans.find((plan) => plan.id === selectedPlan);
105+
79106
return (
80107
<>
81108
<OnboardingContinuationPrompt />
82109
<OnboardingLayout userEmail={userEmail} currentStep={1}>
83-
<div className="flex w-full flex-col gap-6">
110+
{/* Left column - Main content */}
111+
<OnboardingCard
112+
title="Select plan"
113+
subtitle={t("onboarding_welcome_question")}
114+
footer={
115+
<div className="flex w-full justify-end gap-2">
116+
<Button color="primary" className="rounded-[10px]" onClick={handleContinue}>
117+
{t("continue")}
118+
</Button>
119+
</div>
120+
}>
84121
{/* Card */}
85-
<div className="bg-muted border-muted relative rounded-xl border p-1">
122+
<div className="bg-muted border-muted relative flex min-h-0 w-full flex-col overflow-hidden rounded-xl border p-1">
86123
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
87-
{/* Card Header */}
88-
<div className="flex w-full gap-1.5 px-5 py-4">
89-
<div className="flex w-full flex-col gap-1">
90-
<h1 className="font-cal text-xl font-semibold leading-6">
91-
{t("onboarding_welcome_message", { userName })}
92-
</h1>
93-
<p className="text-subtle text-sm font-medium leading-tight">
94-
{t("onboarding_welcome_question")}
95-
</p>
96-
</div>
97-
</div>
98-
99124
{/* Plan options */}
100125
<RadioAreaGroup.Group
101126
value={selectedPlan ?? undefined}
@@ -114,46 +139,45 @@ export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) =>
114139
"pr-12 [&>button]:left-auto [&>button]:right-6 [&>button]:mt-0 [&>button]:transform"
115140
)}
116141
classNames={{
117-
container: "flex w-full items-center gap-4 p-4 pl-5 pr-12 md:p-5 md:pr-14",
142+
container: "flex w-full items-center gap-3 p-5 pr-12",
118143
}}>
119-
<div className="flex w-full items-center gap-4">
120-
<PlanIcon icon={plan.icon} variant={plan.variant} />
121-
<div className="flex flex-1 flex-col gap-2">
122-
<div className="flex flex-wrap items-center gap-2">
123-
<p className="text-emphasis text-base font-semibold leading-5">{plan.title}</p>
124-
<Badge
125-
variant="gray"
126-
size="md"
127-
className="hidden h-4 rounded-md px-1 py-1 md:flex md:items-center">
128-
<span className="text-emphasis text-xs font-medium leading-3">
129-
{plan.badge}
130-
</span>
131-
</Badge>
132-
</div>
144+
<div className="flex w-full flex-col gap-1">
145+
<div className="flex flex-wrap items-center gap-1">
146+
<p className="text-emphasis text-sm font-semibold leading-4">{plan.title}</p>
133147
<Badge
134148
variant="gray"
135149
size="md"
136-
className="h-4 w-fit rounded-md px-1 py-1 md:hidden">
150+
className="hidden h-4 rounded-md px-1 py-1 md:flex md:items-center">
137151
<span className="text-emphasis text-xs font-medium leading-3">{plan.badge}</span>
138152
</Badge>
139-
<p className="text-subtle max-w-full text-sm font-normal leading-tight">
140-
{plan.description}
141-
</p>
142153
</div>
154+
<Badge variant="gray" size="md" className="h-4 w-fit rounded-md px-1 py-1 md:hidden">
155+
<span className="text-emphasis text-xs font-medium leading-3">{plan.badge}</span>
156+
</Badge>
157+
<p className="text-subtle max-w-full text-sm font-medium leading-[1.25]">
158+
{plan.description}
159+
</p>
143160
</div>
144161
</RadioAreaGroup.Item>
145162
);
146163
})}
147164
</RadioAreaGroup.Group>
148-
149-
{/* Footer */}
150-
<div className="flex w-full items-center justify-end gap-1 px-5 py-4">
151-
<Button color="primary" className="rounded-[10px]" onClick={handleContinue}>
152-
{t("continue")}
153-
</Button>
154-
</div>
155165
</div>
156166
</div>
167+
</OnboardingCard>
168+
169+
{/* Right column - Icon display */}
170+
<div className="bg-muted border-subtle hidden h-full w-full rounded-l-2xl border-b border-l border-t xl:flex xl:items-center xl:justify-center">
171+
<AnimatePresence mode="wait">
172+
{selectedPlanData && (
173+
<PlanIcon
174+
key={selectedPlan}
175+
icon={selectedPlanData.icon}
176+
variant={selectedPlanData.variant}
177+
animationDirection={direction}
178+
/>
179+
)}
180+
</AnimatePresence>
157181
</div>
158182
</OnboardingLayout>
159183
</>

0 commit comments

Comments
 (0)