Skip to content

Commit e6e6784

Browse files
authored
fix: Polish, fixes, and i18n updates for onboarding (calcom#24949)
## What does this PR do? <!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --> - Fixes #XXXX (GitHub issue number) - Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description) ## Visual Demo (For contributors especially) A visual demonstration is strongly recommended, for both the original and new change **(video / image - any one)**. #### Video Demo (if applicable): - Show screen recordings of the issue or feature. - Demonstrate how to reproduce the issue, the behavior before and after the change. #### Image Demo (if applicable): - Add side-by-side screenshots of the original and updated change. - Highlight any significant change(s). ## Mandatory Tasks (DO NOT REMOVE) - [ ] I have self-reviewed the code (A decent size PR without self-review might be rejected). - [ ] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). 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? <!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests --> - Are there environment variables that should be set? - What are the minimal test data to have? - What is expected (happy path) to have (input and output)? - Any other important info that could help to test that PR ## Checklist <!-- Remove bullet points below that don't apply to you --> - I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md) - My code doesn't follow the style guidelines of this project - I haven't commented my code, particularly in hard-to-understand areas - I haven't checked if my changes generate no new warnings <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Polished the onboarding experience with a live calendar preview, a team bio field, and improved username handling. Updated i18n strings and added responsive breakpoints for large screens. - **New Features** - Added right-column previews: OnboardingBrowserView in org teams and a live weekly OnboardingCalendarBrowserView for personal calendar setup. - Introduced team bio in onboarding store and UI. - Expanded i18n: team bio, browser view labels, and CSV invite flow. - Added Tailwind screens for 3xl and 4xl to improve large-display layouts. - **Bug Fixes** - Normalized usernames with slugify on default and change to prevent invalid slugs. - Unified disabled/readonly handling for premium vs. standard username fields; respects external disabled prop and org membership. <sup>Written for commit 7804fad. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
1 parent bf42cd4 commit e6e6784

6 files changed

Lines changed: 102 additions & 89 deletions

File tree

apps/web/components/ui/UsernameAvailability/index.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Controller, useForm } from "react-hook-form";
66

77
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
88
import { WEBSITE_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
9+
import slugify from "@calcom/lib/slugify";
910
import { trpc } from "@calcom/trpc/react";
1011
import type { AppRouter } from "@calcom/trpc/types/server/routers/_app";
1112

@@ -16,6 +17,7 @@ import type { TRPCClientErrorLike } from "@trpc/client";
1617
interface UsernameAvailabilityFieldProps {
1718
onSuccessMutation?: () => void;
1819
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
20+
disabled?: boolean;
1921
}
2022

2123
interface ICustomUsernameProps extends UsernameAvailabilityFieldProps {
@@ -37,14 +39,19 @@ const UsernameTextfield = dynamic(() => import("./UsernameTextfield").then((m) =
3739
});
3840

3941
export const UsernameAvailability = (props: ICustomUsernameProps) => {
40-
const { isPremium, ...otherProps } = props;
42+
const { isPremium, disabled, ...otherProps } = props;
4143
const UsernameAvailabilityComponent = isPremium ? PremiumTextfield : UsernameTextfield;
42-
return <UsernameAvailabilityComponent {...otherProps} />;
44+
// PremiumTextfield uses `readonly` prop, UsernameTextfield uses `disabled` prop
45+
const componentProps = isPremium
46+
? { ...otherProps, readonly: disabled }
47+
: { ...otherProps, disabled };
48+
return <UsernameAvailabilityComponent {...componentProps} />;
4349
};
4450

4551
export const UsernameAvailabilityField = ({
4652
onSuccessMutation,
4753
onErrorMutation,
54+
disabled,
4855
}: UsernameAvailabilityFieldProps) => {
4956
const searchParams = useSearchParams();
5057
const [user] = trpc.viewer.me.get.useSuspenseQuery();
@@ -56,7 +63,7 @@ export const UsernameAvailabilityField = ({
5663
: { username: currentUsernameState || "", setQuery: setCurrentUsernameState };
5764
const formMethods = useForm({
5865
defaultValues: {
59-
username: currentUsername,
66+
username: slugify(currentUsername || user.username || ""),
6067
},
6168
});
6269

@@ -78,10 +85,14 @@ export const UsernameAvailabilityField = ({
7885
setCurrentUsername={setCurrentUsername}
7986
inputUsernameValue={value}
8087
usernameRef={ref}
81-
setInputUsernameValue={onChange}
88+
setInputUsernameValue={(val) => {
89+
const displayValue = slugify(val, true);
90+
formMethods.setValue("username", displayValue);
91+
onChange?.(displayValue);
92+
}}
8293
onSuccessMutation={onSuccessMutation}
8394
onErrorMutation={onErrorMutation}
84-
disabled={!!user.organization?.id}
95+
disabled={disabled ?? !!user.organization?.id}
8596
addOnLeading={`${usernamePrefix}/`}
8697
isPremium={isPremium}
8798
/>

apps/web/modules/onboarding/components/onboarding-calendar-browser-view.tsx

Lines changed: 47 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,58 @@
11
"use client";
22

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";
3+
import { useMemo } from "react";
4+
5+
import dayjs from "@calcom/dayjs";
6+
import { Calendar } from "@calcom/features/calendars/weeklyview";
7+
import { weekdayDates } from "@calcom/features/calendars/weeklyview/utils";
8+
import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants";
9+
10+
import { useOnboardingCalendarEvents } from "../hooks/useOnboardingCalendarEvents";
611

712
export const OnboardingCalendarBrowserView = () => {
8-
const { t } = useLocale();
9-
const webappUrl = WEBAPP_URL.replace(/^https?:\/\//, "");
13+
const { startDate, endDate } = useMemo(() => {
14+
return weekdayDates(0, new Date(), 6);
15+
}, []);
1016

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-
];
17+
const { events } = useOnboardingCalendarEvents({ startDate, endDate });
18+
19+
// Memoize calendar props to prevent unnecessary re-initializations
20+
const calendarProps = useMemo(
21+
() => ({
22+
timezone: CURRENT_TIMEZONE,
23+
startDate,
24+
endDate,
25+
events: events || [],
26+
gridCellsPerHour: 4,
27+
hoverEventDuration: 0,
28+
showBackgroundPattern: false,
29+
showBorder: false,
30+
borderColor: "subtle" as const,
31+
hideHeader: true,
32+
eventsDisabled: true,
33+
sortEvents: true,
34+
isPending: false, // Explicitly set to false to prevent loading spinner
35+
loading: false, // Also set loading to false just in case
36+
onEventClick: () => {},
37+
onEmptyCellClick: () => {},
38+
onDateChange: () => {},
39+
showTimezone: true,
40+
}),
41+
[startDate, endDate, events]
42+
);
3243

3344
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" />
45+
<div className="bg-default border-muted flex h-full w-full flex-col overflow-hidden rounded-xl border">
46+
<div className="flex items-center gap-2 px-4 py-3">
47+
<span className="text-default text-sm font-semibold">
48+
{dayjs(startDate).format("MMM D")}
49+
<span className="mx-1"></span>
50+
{dayjs(endDate).format("MMM D, YYYY")}
51+
</span>
4852
</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>
53+
{/* Calendar View */}
54+
<div className="flex h-full flex-col overflow-hidden">
55+
<Calendar {...calendarProps} />
8956
</div>
9057
</div>
9158
);

apps/web/modules/onboarding/organization/teams/organization-teams-view.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import { Button } from "@calcom/ui/components/button";
1010
import { Form, TextField } from "@calcom/ui/components/form";
1111
import { Icon } from "@calcom/ui/components/icon";
1212

13-
import { OnboardingCard } from "../../personal/_components/OnboardingCard";
14-
import { OnboardingLayout } from "../../personal/_components/OnboardingLayout";
13+
import { OnboardingBrowserView } from "../../components/onboarding-browser-view";
14+
import { OnboardingCard } from "../../components/OnboardingCard";
15+
import { OnboardingLayout } from "../../components/OnboardingLayout";
1516
import { useOnboardingStore } from "../../store/onboarding-store";
1617

1718
type OrganizationTeamsViewProps = {
@@ -65,6 +66,7 @@ export const OrganizationTeamsView = ({ userEmail }: OrganizationTeamsViewProps)
6566

6667
return (
6768
<OnboardingLayout userEmail={userEmail} currentStep={3}>
69+
{/* Left column - Main content */}
6870
<OnboardingCard
6971
title={t("onboarding_org_teams_title")}
7072
subtitle={t("onboarding_org_teams_subtitle")}
@@ -132,6 +134,9 @@ export const OrganizationTeamsView = ({ userEmail }: OrganizationTeamsViewProps)
132134
</div>
133135
</Form>
134136
</OnboardingCard>
137+
138+
{/* Right column - Browser view */}
139+
<OnboardingBrowserView />
135140
</OnboardingLayout>
136141
);
137142
};

apps/web/modules/onboarding/personal/calendar/personal-calendar-view.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
44
import { trpc } from "@calcom/trpc/react";
55
import { Button } from "@calcom/ui/components/button";
66

7+
import { OnboardingCard } from "../../components/OnboardingCard";
8+
import { OnboardingLayout } from "../../components/OnboardingLayout";
79
import { OnboardingCalendarBrowserView } from "../../components/onboarding-calendar-browser-view";
810
import { useSubmitPersonalOnboarding } from "../../hooks/useSubmitPersonalOnboarding";
911
import { InstallableAppCard } from "../_components/InstallableAppCard";
10-
import { OnboardingCard } from "../../components/OnboardingCard";
11-
import { OnboardingLayout } from "../../components/OnboardingLayout";
1212
import { useAppInstallation } from "../_components/useAppInstallation";
1313

1414
type PersonalCalendarViewProps = {

apps/web/public/static/locales/en/common.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3972,5 +3972,33 @@
39723972
"repeats_num_times": "Repeats {{count}} times",
39733973
"view_booking_details": "View Booking Details",
39743974
"view": "View",
3975+
"team_bio": "Team Bio",
3976+
"team_bio_placeholder": "Tell us about your team...",
3977+
"onboarding_invite_subtitle": "Connect your Google workspace, invite via email, upload a CSV file or copy the invite link and share it with your teammates to add them to your Team.",
3978+
"upload_csv_subtitle": "Upload a CSV file with team member emails to bulk invite",
3979+
"need_template": "Need a template?",
3980+
"download_csv_template_description": "Download our CSV template to ensure your file is formatted correctly",
3981+
"download_template": "Download template",
3982+
"upload_your_file": "Upload your file",
3983+
"upload_csv_description": "Select a CSV file with email addresses and optional roles",
3984+
"choose_file": "Choose file",
3985+
"please_upload_csv_file": "Please upload a CSV file",
3986+
"template_downloaded": "Template downloaded successfully",
3987+
"please_select_file": "Please select a file to upload",
3988+
"csv_file_empty": "CSV file is empty or invalid",
3989+
"csv_missing_email_column": "CSV file must contain an 'email' column",
3990+
"csv_uploaded_successfully": "Successfully uploaded {{count}} invites",
3991+
"error_uploading_csv": "Error uploading CSV file",
3992+
"onboarding_browser_view_demo": "Demo",
3993+
"onboarding_browser_view_demo_description": "Schedule a demo call with us",
3994+
"onboarding_browser_view_quick_meeting": "Quick meeting",
3995+
"onboarding_browser_view_quick_meeting_description": "A quick chat",
3996+
"onboarding_browser_view_longer_meeting": "Longer meeting",
3997+
"onboarding_browser_view_longer_meeting_description": "A longer chat",
3998+
"onboarding_browser_view_in_person_description": "Meet in person",
3999+
"onboarding_browser_view_ask_question": "Ask a question",
4000+
"onboarding_browser_view_ask_question_description": "Ask a question",
4001+
"onboarding_browser_view_default_bio": "Find a time that suits you",
4002+
"book_now": "Book now",
39754003
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
39764004
}

packages/config/tailwind-preset.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ module.exports = {
220220
},
221221
screens: {
222222
pwa: { raw: "(display-mode: standalone)" },
223+
"3xl": "1920px",
224+
"4xl": "2560px",
223225
},
224226
keyframes: {
225227
"fade-in-up": {

0 commit comments

Comments
 (0)