Skip to content

Commit 8e0cdf3

Browse files
authored
feat: Onboarding V3 (calcom#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
1 parent fcd9480 commit 8e0cdf3

25 files changed

Lines changed: 1871 additions & 12 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { _generateMetadata } from "app/_utils";
2+
import { cookies, headers } from "next/headers";
3+
import { redirect } from "next/navigation";
4+
5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
6+
import { APP_NAME } from "@calcom/lib/constants";
7+
8+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
9+
10+
import { OnboardingView } from "~/onboarding/getting-started/onboarding-view";
11+
12+
export const generateMetadata = async () => {
13+
return await _generateMetadata(
14+
(t) => `${APP_NAME} - ${t("getting_started")}`,
15+
() => "",
16+
true,
17+
undefined,
18+
"/onboarding/getting-started"
19+
);
20+
};
21+
22+
const ServerPage = async () => {
23+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
24+
25+
if (!session?.user?.id) {
26+
return redirect("/auth/login");
27+
}
28+
29+
// Hello {username} || there. Not sure how to do this nicely with i18n just yet
30+
const userName = session.user.name || "there";
31+
const userEmail = session.user.email || "";
32+
33+
return <OnboardingView userName={userName} userEmail={userEmail} />;
34+
};
35+
36+
export default ServerPage;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { redirect } from "next/navigation";
2+
3+
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
4+
import { prisma } from "@calcom/prisma";
5+
6+
export default async function OnboardingLayout({ children }: { children: React.ReactNode }) {
7+
const featuresRepository = new FeaturesRepository(prisma);
8+
const isOnboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3");
9+
10+
if (!isOnboardingV3Enabled) {
11+
redirect("/getting-started");
12+
}
13+
14+
return <>{children}</>;
15+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { createRouterCaller } from "app/_trpc/context";
2+
import { _generateMetadata } from "app/_utils";
3+
import { cookies, headers } from "next/headers";
4+
import { redirect } from "next/navigation";
5+
6+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
7+
import { APP_NAME } from "@calcom/lib/constants";
8+
9+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
10+
11+
import { OrganizationBrandView } from "~/onboarding/organization/brand/organization-brand-view";
12+
13+
export const generateMetadata = async () => {
14+
return await _generateMetadata(
15+
(t) => `${APP_NAME} - ${t("organization_brand")}`,
16+
() => "",
17+
true,
18+
undefined,
19+
"/onboarding/organization/brand"
20+
);
21+
};
22+
23+
const ServerPage = async () => {
24+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
25+
26+
if (!session?.user?.id) {
27+
return redirect("/auth/login");
28+
}
29+
30+
const userEmail = session.user.email || "";
31+
32+
return <OrganizationBrandView userEmail={userEmail} />;
33+
};
34+
35+
export default ServerPage;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { _generateMetadata } from "app/_utils";
2+
import { cookies, headers } from "next/headers";
3+
import { redirect } from "next/navigation";
4+
5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
6+
import { APP_NAME } from "@calcom/lib/constants";
7+
8+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
9+
10+
import { OrganizationDetailsView } from "~/onboarding/organization/details/organization-details-view";
11+
12+
export const generateMetadata = async () => {
13+
return await _generateMetadata(
14+
(t) => `${APP_NAME} - ${t("organization_details")}`,
15+
() => "",
16+
true,
17+
undefined,
18+
"/onboarding/organization/details"
19+
);
20+
};
21+
22+
const ServerPage = async () => {
23+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
24+
25+
if (!session?.user?.id) {
26+
return redirect("/auth/login");
27+
}
28+
29+
const userEmail = session.user.email || "";
30+
31+
return <OrganizationDetailsView userEmail={userEmail} />;
32+
};
33+
34+
export default ServerPage;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { _generateMetadata } from "app/_utils";
2+
import { cookies, headers } from "next/headers";
3+
import { redirect } from "next/navigation";
4+
5+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
6+
import { APP_NAME } from "@calcom/lib/constants";
7+
8+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
9+
10+
import { OrganizationInviteView } from "~/onboarding/organization/invite/organization-invite-view";
11+
12+
export const generateMetadata = async () => {
13+
return await _generateMetadata(
14+
(t) => `${APP_NAME} - ${t("invite_teammates")}`,
15+
() => "",
16+
true,
17+
undefined,
18+
"/onboarding/organization/invite"
19+
);
20+
};
21+
22+
const ServerPage = async () => {
23+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
24+
25+
if (!session?.user?.id) {
26+
return redirect("/auth/login");
27+
}
28+
29+
const userEmail = session.user.email || "";
30+
31+
return <OrganizationInviteView userEmail={userEmail} />;
32+
};
33+
34+
export default ServerPage;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { createRouterCaller } from "app/_trpc/context";
2+
import { _generateMetadata } from "app/_utils";
3+
import { cookies, headers } from "next/headers";
4+
import { redirect } from "next/navigation";
5+
6+
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
7+
import { APP_NAME } from "@calcom/lib/constants";
8+
9+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
10+
11+
import { OrganizationTeamsView } from "~/onboarding/organization/teams/organization-teams-view";
12+
13+
export const generateMetadata = async () => {
14+
return await _generateMetadata(
15+
(t) => `${APP_NAME} - ${t("organization_teams")}`,
16+
() => "",
17+
true,
18+
undefined,
19+
"/onboarding/organization/teams"
20+
);
21+
};
22+
23+
const ServerPage = async () => {
24+
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
25+
26+
if (!session?.user?.id) {
27+
return redirect("/auth/login");
28+
}
29+
30+
const userEmail = session.user.email || "";
31+
32+
return <OrganizationTeamsView userEmail={userEmail} />;
33+
};
34+
35+
export default ServerPage;

apps/web/lib/signup/getServerSideProps.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
3030
"email-verification"
3131
);
3232
const signupDisabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("disable-signup");
33+
const onboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3");
3334

3435
const token = z.string().optional().parse(ctx.query.token);
3536
const redirectUrlData = z
@@ -62,6 +63,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
6263
isSAMLLoginEnabled,
6364
prepopulateFormValues: undefined,
6465
emailVerificationEnabled,
66+
onboardingV3Enabled,
6567
};
6668

6769
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || signupDisabled) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useEffect, useState } from "react";
5+
6+
import { Button } from "@calcom/ui/components/button";
7+
import { Icon } from "@calcom/ui/components/icon";
8+
9+
import { useOnboardingStore } from "../store/onboarding-store";
10+
11+
export const OnboardingContinuationPrompt = () => {
12+
const router = useRouter();
13+
const { organizationDetails, resetOnboarding } = useOnboardingStore();
14+
const [isVisible, setIsVisible] = useState(false);
15+
16+
useEffect(() => {
17+
// Check if there's existing organization data
18+
const hasExistingData = organizationDetails.name && organizationDetails.link;
19+
setIsVisible(!!hasExistingData);
20+
}, [organizationDetails]);
21+
22+
if (!isVisible) {
23+
return null;
24+
}
25+
26+
const handleContinue = () => {
27+
// Navigate to the next step in the flow (brand page)
28+
router.push("/onboarding/organization/brand");
29+
};
30+
31+
const handleStartOver = () => {
32+
resetOnboarding();
33+
setIsVisible(false);
34+
};
35+
36+
return (
37+
<div className="animate-fade-in fixed bottom-4 right-4 z-50">
38+
<div className="bg-default border-muted shadow-default relative w-[360px] rounded-lg border p-4">
39+
<button
40+
onClick={() => setIsVisible(false)}
41+
className="text-muted hover:text-emphasis absolute right-2 top-2 rounded-md p-1 transition-colors">
42+
<Icon name="x" className="h-4 w-4" />
43+
</button>
44+
45+
<div className="mb-3 pr-6">
46+
<h3 className="text-emphasis mb-1 text-base font-semibold">Continue onboarding?</h3>
47+
<p className="text-subtle text-sm">
48+
Would you like to carry on with onboarding for{" "}
49+
<span className="text-emphasis font-medium">{organizationDetails.name}</span> or start over?
50+
</p>
51+
</div>
52+
53+
<div className="ml-auto flex gap-2">
54+
<Button onClick={handleStartOver} color="secondary">
55+
Start over
56+
</Button>
57+
<Button onClick={handleContinue} color="primary">
58+
Continue
59+
</Button>
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
};

0 commit comments

Comments
 (0)