From 19641ffc05acd968af05f914cc41ddad2d5a8983 Mon Sep 17 00:00:00 2001
From: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Date: Thu, 13 Nov 2025 10:45:42 +0000
Subject: [PATCH] feat: organization v3 redesign onboarding (#24967)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What does this PR do?
- Redesigns the organization onboarding flow by merging the brand and details pages
- Improves the organization details page with a scrollable interface and visual previews
- Adds a new organization-specific browser preview component
## Visual Demo (For contributors especially)
#### Image Demo:
- The PR replaces the separate brand page with an integrated details page that includes logo and banner uploads
- The new organization browser view shows a preview of the organization profile with the selected branding
## Mandatory Tasks (DO NOT REMOVE)
- [ ] I have self-reviewed the code.
- [ ] 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.
- [ ] I confirm automated tests are in place that prove my fix is effective or that my feature works.
## How should this be tested?
- Go through the organization onboarding flow
- Test uploading logos and banners
- Verify that the organization browser preview updates in real-time with the form inputs
- Confirm that the form validation works correctly for organization name and slug
- Check that the scrollable interface works properly with fade effects at top and bottom
## 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
---
## Summary by cubic
Split organization brand from the details step and added live previews for organizations and teams. Revamped org/team invites with reusable components, a dedicated email-invite page, and a CSV upload modal.
- **New Features**
- Separate Brand step with logo, banner, and color; instant org preview via OnboardingOrganizationBrowserView.
- Teams browser preview added; invites include email substep (/onboarding/organization/invite/email, /onboarding/teams/invite/email), CSV upload (template + parsing), and Google Workspace (behind flag).
- Shared components (EmailInviteForm, InviteOptions, RoleSelector) used across org and team invites.
- **Refactors**
- Updated org flow: Details → Brand → Teams → Invites; OnboardingLayout now supports dynamic step counts (org=4, team=3, personal=2).
- UI polish (OnboardingCard header padding) and org-specific previews now replace generic views across details/brand/invites/teams; ensured org welcome modal takes precedence over personal.
Written for commit d9b55c0b5505aa0d4ca1c4298a513bcd90606915. Summary will update automatically on new commits.
---
.../onboarding/organization/brand/page.tsx | 2 +-
.../organization/invite/email/page.tsx | 35 ++
.../onboarding/teams/invite/email/page.tsx | 35 ++
.../onboarding/components/EmailInviteForm.tsx | 123 +++++++
.../onboarding/components/InviteOptions.tsx | 104 ++++++
.../onboarding/components/OnboardingCard.tsx | 3 +-
.../components/OnboardingLayout.tsx | 43 ++-
.../onboarding/components/RoleSelector.tsx | 38 ++
.../components/onboarding-browser-view.tsx | 8 +-
.../onboarding-organization-browser-view.tsx | 147 ++++++++
.../onboarding-teams-browser-view.tsx | 136 +++++++
.../getting-started/onboarding-view.tsx | 2 +-
.../hooks/useSubmitPersonalOnboarding.ts | 19 +-
.../brand/organization-brand-view.tsx | 251 ++++++-------
.../details/organization-details-view.tsx | 102 +++---
.../organization/invite/csv-upload-modal.tsx | 239 ++++++++++++
.../email/organization-invite-email-view.tsx | 189 ++++++++++
.../invite/organization-invite-view.tsx | 340 ++++--------------
.../teams/organization-teams-view.tsx | 46 ++-
.../calendar/personal-calendar-view.tsx | 2 +-
.../settings/personal-settings-view.tsx | 4 +-
.../teams/details/team-details-view.tsx | 4 +-
.../invite/email/team-invite-email-view.tsx | 91 ++---
.../teams/invite/team-invite-view.tsx | 68 +---
apps/web/public/static/locales/en/common.json | 2 +-
.../shell/hooks/useWelcomeToCalcomModal.ts | 8 +
26 files changed, 1401 insertions(+), 640 deletions(-)
create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/organization/invite/email/page.tsx
create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/teams/invite/email/page.tsx
create mode 100644 apps/web/modules/onboarding/components/EmailInviteForm.tsx
create mode 100644 apps/web/modules/onboarding/components/InviteOptions.tsx
create mode 100644 apps/web/modules/onboarding/components/RoleSelector.tsx
create mode 100644 apps/web/modules/onboarding/components/onboarding-organization-browser-view.tsx
create mode 100644 apps/web/modules/onboarding/components/onboarding-teams-browser-view.tsx
create mode 100644 apps/web/modules/onboarding/organization/invite/csv-upload-modal.tsx
create mode 100644 apps/web/modules/onboarding/organization/invite/email/organization-invite-email-view.tsx
diff --git a/apps/web/app/(use-page-wrapper)/onboarding/organization/brand/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/organization/brand/page.tsx
index c7f9d7d2ab..8b55623816 100644
--- a/apps/web/app/(use-page-wrapper)/onboarding/organization/brand/page.tsx
+++ b/apps/web/app/(use-page-wrapper)/onboarding/organization/brand/page.tsx
@@ -1,4 +1,3 @@
-import { createRouterCaller } from "app/_trpc/context";
import { _generateMetadata } from "app/_utils";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
@@ -33,3 +32,4 @@ const ServerPage = async () => {
};
export default ServerPage;
+
diff --git a/apps/web/app/(use-page-wrapper)/onboarding/organization/invite/email/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/organization/invite/email/page.tsx
new file mode 100644
index 0000000000..c9fafe6006
--- /dev/null
+++ b/apps/web/app/(use-page-wrapper)/onboarding/organization/invite/email/page.tsx
@@ -0,0 +1,35 @@
+import { _generateMetadata } from "app/_utils";
+import { cookies, headers } from "next/headers";
+import { redirect } from "next/navigation";
+
+import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
+import { APP_NAME } from "@calcom/lib/constants";
+
+import { buildLegacyRequest } from "@lib/buildLegacyCtx";
+
+import { OrganizationInviteEmailView } from "~/onboarding/organization/invite/email/organization-invite-email-view";
+
+export const generateMetadata = async () => {
+ return await _generateMetadata(
+ (t) => `${APP_NAME} - ${t("invite_teammates")}`,
+ () => "",
+ true,
+ undefined,
+ "/onboarding/organization/invite/email"
+ );
+};
+
+const ServerPage = async () => {
+ const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
+
+ if (!session?.user?.id) {
+ return redirect("/auth/login");
+ }
+
+ const userEmail = session.user.email || "";
+
+ return
+
{bio || t("onboarding_browser_view_default_bio")}
{displayUrl}
++ {bio || t("onboarding_browser_view_default_bio")} +
+{event.description}
+{webappUrl}/teams
++ {hasValidTeams + ? t("onboarding_teams_browser_view_subtitle") + : t("onboarding_teams_browser_view_placeholder_subtitle")} +
++ {t("onboarding_teams_browser_view_team_description")} +
++ {t("onboarding_teams_browser_view_no_teams_title")} +
++ {t("onboarding_teams_browser_view_no_teams_description")} +
+