From 01346abb850efb54331fcdbfe348f213985456bd Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:36:46 +0000 Subject: [PATCH] feat: Add personal v3 onboarding flow (#24681) * WIP personal onbaording flow * Add i18n * Fix overflow clip blockin timezone select * Remove duplicate label * Move to two column approach * Restore lock fle * Remove duplicate key in common json * Apply suggestion from @cubic-dev-ai[bot] Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Apply suggestion from @cubic-dev-ai[bot] Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update personal-settings-view.tsx * Fix endpoint --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .../onboarding/personal/calendar/page.tsx | 34 +++ .../onboarding/personal/profile/page.tsx | 34 +++ .../onboarding/personal/settings/page.tsx | 35 +++ .../onboarding/personal/video/page.tsx | 34 +++ .../getting-started/onboarding-view.tsx | 4 +- .../calendar/personal-calendar-view.tsx | 124 ++++++++++ .../profile/personal-profile-view.tsx | 220 ++++++++++++++++++ .../settings/personal-settings-view.tsx | 179 ++++++++++++++ .../personal/video/personal-video-view.tsx | 203 ++++++++++++++++ .../onboarding/store/onboarding-store.ts | 27 +++ apps/web/public/static/locales/en/common.json | 10 + 11 files changed, 903 insertions(+), 1 deletion(-) create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/personal/calendar/page.tsx create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/personal/profile/page.tsx create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/personal/settings/page.tsx create mode 100644 apps/web/app/(use-page-wrapper)/onboarding/personal/video/page.tsx create mode 100644 apps/web/modules/onboarding/personal/calendar/personal-calendar-view.tsx create mode 100644 apps/web/modules/onboarding/personal/profile/personal-profile-view.tsx create mode 100644 apps/web/modules/onboarding/personal/settings/personal-settings-view.tsx create mode 100644 apps/web/modules/onboarding/personal/video/personal-video-view.tsx diff --git a/apps/web/app/(use-page-wrapper)/onboarding/personal/calendar/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/personal/calendar/page.tsx new file mode 100644 index 0000000000..68d29f225b --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/onboarding/personal/calendar/page.tsx @@ -0,0 +1,34 @@ +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 { PersonalCalendarView } from "~/onboarding/personal/calendar/personal-calendar-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${APP_NAME} - ${t("connect_calendar")}`, + () => "", + true, + undefined, + "/onboarding/personal/calendar" + ); +}; + +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 ; +}; + +export default ServerPage; diff --git a/apps/web/app/(use-page-wrapper)/onboarding/personal/profile/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/personal/profile/page.tsx new file mode 100644 index 0000000000..dc70908423 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/onboarding/personal/profile/page.tsx @@ -0,0 +1,34 @@ +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 { PersonalProfileView } from "~/onboarding/personal/profile/personal-profile-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${APP_NAME} - ${t("personal_profile")}`, + () => "", + true, + undefined, + "/onboarding/personal/profile" + ); +}; + +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 ; +}; + +export default ServerPage; diff --git a/apps/web/app/(use-page-wrapper)/onboarding/personal/settings/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/personal/settings/page.tsx new file mode 100644 index 0000000000..114be2a853 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/onboarding/personal/settings/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 { PersonalSettingsView } from "~/onboarding/personal/settings/personal-settings-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${APP_NAME} - ${t("personal_settings")}`, + () => "", + true, + undefined, + "/onboarding/personal/settings" + ); +}; + +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 || ""; + const userName = session.user.name || ""; + + return ; +}; + +export default ServerPage; diff --git a/apps/web/app/(use-page-wrapper)/onboarding/personal/video/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/personal/video/page.tsx new file mode 100644 index 0000000000..1ec119859f --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/onboarding/personal/video/page.tsx @@ -0,0 +1,34 @@ +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 { PersonalVideoView } from "~/onboarding/personal/video/personal-video-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${APP_NAME} - ${t("connect_video")}`, + () => "", + true, + undefined, + "/onboarding/personal/video" + ); +}; + +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 ; +}; + +export default ServerPage; diff --git a/apps/web/modules/onboarding/getting-started/onboarding-view.tsx b/apps/web/modules/onboarding/getting-started/onboarding-view.tsx index e5f263eb05..e2d0f8252c 100644 --- a/apps/web/modules/onboarding/getting-started/onboarding-view.tsx +++ b/apps/web/modules/onboarding/getting-started/onboarding-view.tsx @@ -25,7 +25,9 @@ export const OnboardingView = ({ userName, userEmail }: OnboardingViewProps) => router.push("/onboarding/organization/details"); } else if (selectedPlan === "team") { router.push("/onboarding/teams/details"); - } // TODO: Handle other plan types + } else if (selectedPlan === "personal") { + router.push("/onboarding/personal/settings"); + } }; const allPlans = [ diff --git a/apps/web/modules/onboarding/personal/calendar/personal-calendar-view.tsx b/apps/web/modules/onboarding/personal/calendar/personal-calendar-view.tsx new file mode 100644 index 0000000000..094dfbb9e9 --- /dev/null +++ b/apps/web/modules/onboarding/personal/calendar/personal-calendar-view.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import { Button } from "@calcom/ui/components/button"; +import { Logo } from "@calcom/ui/components/logo"; +import { SkeletonText } from "@calcom/ui/components/skeleton"; + +type PersonalCalendarViewProps = { + userEmail: string; +}; + +export const PersonalCalendarView = ({ userEmail }: PersonalCalendarViewProps) => { + const router = useRouter(); + const { t } = useLocale(); + + const queryIntegrations = trpc.viewer.apps.integrations.useQuery({ + variant: "calendar", + onlyInstalled: false, + sortByMostPopular: true, + sortByInstalledFirst: true, + }); + + const handleContinue = () => { + router.push("/onboarding/personal/video"); + }; + + const handleSkip = () => { + router.push("/onboarding/personal/video"); + }; + + return ( +
+ {/* Header */} +
+ + + {/* Progress dots - centered */} +
+
+
+
+
+
+ +
+

{userEmail}

+
+
+ + {/* Main content */} +
+
+ {/* Card */} +
+
+ {/* Card Header */} +
+
+

{t("connect_your_calendar")}

+

+ {t("connect_calendar_to_prevent_conflicts")} +

+
+
+ + {/* Content */} +
+ {queryIntegrations.isPending ? ( +
+ + +
+ ) : ( +
+ {queryIntegrations.data?.items.map((app) => ( +
+ {app.logo && {app.name}} +

+ {app.name} +

+

+ {app.description} +

+ +
+ ))} +
+ )} +
+ + {/* Footer */} +
+ +
+
+
+ + {/* Skip button */} +
+ +
+
+
+
+ ); +}; diff --git a/apps/web/modules/onboarding/personal/profile/personal-profile-view.tsx b/apps/web/modules/onboarding/personal/profile/personal-profile-view.tsx new file mode 100644 index 0000000000..8fbdb6beda --- /dev/null +++ b/apps/web/modules/onboarding/personal/profile/personal-profile-view.tsx @@ -0,0 +1,220 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { useForm } from "react-hook-form"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { md } from "@calcom/lib/markdownIt"; +import turndown from "@calcom/lib/turndownService"; +import { trpc } from "@calcom/trpc/react"; +import { UserAvatar } from "@calcom/ui/components/avatar"; +import { Button } from "@calcom/ui/components/button"; +import { Editor } from "@calcom/ui/components/editor"; +import { Label } from "@calcom/ui/components/form"; +import { ImageUploader } from "@calcom/ui/components/image-uploader"; +import { Logo } from "@calcom/ui/components/logo"; +import { showToast } from "@calcom/ui/components/toast"; + +import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; + +import { useOnboardingStore } from "../../store/onboarding-store"; + +type PersonalProfileViewProps = { + userEmail: string; +}; + +type FormData = { + bio: string; +}; + +export const PersonalProfileView = ({ userEmail }: PersonalProfileViewProps) => { + const { data: user } = trpc.viewer.me.get.useQuery(); + const router = useRouter(); + const { t } = useLocale(); + const { personalDetails, setPersonalDetails } = useOnboardingStore(); + + const avatarRef = useRef(null); + const [imageSrc, setImageSrc] = useState(""); + const [firstRender, setFirstRender] = useState(true); + + // Update imageSrc when user loads + useEffect(() => { + if (user) { + setImageSrc(personalDetails.avatar || user.avatar || ""); + } + }, [user, personalDetails.avatar]); + + const { setValue, handleSubmit, getValues } = useForm({ + defaultValues: { bio: personalDetails.bio || user?.bio || "" }, + }); + + const utils = trpc.useUtils(); + + // Avatar mutation + const avatarMutation = trpc.viewer.me.updateProfile.useMutation({ + onSuccess: async (data) => { + showToast(t("your_user_profile_updated_successfully"), "success"); + setImageSrc(data.avatarUrl ?? ""); + setPersonalDetails({ avatar: data.avatarUrl ?? null }); + }, + onError: () => { + showToast(t("problem_saving_user_profile"), "error"); + }, + }); + + // Profile mutation + const mutation = trpc.viewer.me.updateProfile.useMutation({ + onSuccess: async () => { + await utils.viewer.me.invalidate(); + }, + }); + + const onSubmit = handleSubmit(async (data: { bio: string }) => { + const { bio } = data; + + // Save to store + setPersonalDetails({ + bio, + }); + + // Save to backend + await mutation.mutateAsync({ + bio, + }); + + router.push("/onboarding/personal/calendar"); + }); + + async function updateProfileHandler(newAvatar: string) { + avatarMutation.mutate({ + avatarUrl: newAvatar, + }); + } + + if (!user) { + return null; // or a loading spinner + } + + return ( +
+ {/* Header */} +
+ + + {/* Progress dots - centered */} +
+
+
+
+
+
+ +
+

{userEmail}

+
+
+ + {/* Main content */} +
+
+ {/* Card */} +
+
+ {/* Card Header */} +
+
+

{t("complete_your_profile")}

+

+ {t("personal_profile_subtitle")} +

+
+
+ + {/* Form */} +
+
+
+
+
+ {/* Avatar */} +
+ +
+ {user && } + + { + if (avatarRef.current) { + avatarRef.current.value = newAvatar; + } + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + "value" + )?.set; + nativeInputValueSetter?.call(avatarRef.current, newAvatar); + const ev2 = new Event("input", { bubbles: true }); + avatarRef.current?.dispatchEvent(ev2); + updateProfileHandler(newAvatar); + }} + imageSrc={imageSrc} + /> +
+
+ + {/* Username */} +
+ +
+ + {/* Bio */} +
+ + md.render(getValues("bio") || user?.bio || "")} + setText={(value: string) => setValue("bio", turndown(value))} + excludedToolbarItems={["blockType"]} + firstRender={firstRender} + setFirstRender={setFirstRender} + /> +

+ {t("few_sentences_about_yourself")} +

+
+
+
+
+
+
+ + {/* Footer */} +
+ +
+
+
+
+
+
+ ); +}; diff --git a/apps/web/modules/onboarding/personal/settings/personal-settings-view.tsx b/apps/web/modules/onboarding/personal/settings/personal-settings-view.tsx new file mode 100644 index 0000000000..04238f116c --- /dev/null +++ b/apps/web/modules/onboarding/personal/settings/personal-settings-view.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import dayjs from "@calcom/dayjs"; +import { useTimePreferences } from "@calcom/features/bookings/lib"; +import { TimezoneSelect } from "@calcom/features/components/timezone-select"; +import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import { Button } from "@calcom/ui/components/button"; +import { Label, TextField } from "@calcom/ui/components/form"; +import { Logo } from "@calcom/ui/components/logo"; + +import { OnboardingContinuationPrompt } from "../../components/onboarding-continuation-prompt"; +import { useOnboardingStore } from "../../store/onboarding-store"; + +type PersonalSettingsViewProps = { + userEmail: string; + userName?: string; +}; + +export const PersonalSettingsView = ({ userEmail, userName }: PersonalSettingsViewProps) => { + const router = useRouter(); + const { t } = useLocale(); + const { personalDetails, setPersonalDetails } = useOnboardingStore(); + const { setTimezone: setSelectedTimeZone, timezone: selectedTimeZone } = useTimePreferences(); + + const [name, setName] = useState(""); + + useEffect(() => { + setName(personalDetails.name || userName || ""); + if (personalDetails.timezone) { + setSelectedTimeZone(personalDetails.timezone); + } + }, [personalDetails, userName, setSelectedTimeZone]); + + const formSchema = z.object({ + name: z + .string() + .min(1, t("name_required")) + .max(FULL_NAME_LENGTH_MAX_LIMIT, { + message: t("max_limit_allowed_hint", { limit: FULL_NAME_LENGTH_MAX_LIMIT }), + }), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: personalDetails.name || userName || "", + }, + }); + + const utils = trpc.useUtils(); + const mutation = trpc.viewer.me.updateProfile.useMutation({ + onSuccess: async () => { + await utils.viewer.me.invalidate(); + }, + }); + + const handleContinue = form.handleSubmit(async (data) => { + // Save to store + setPersonalDetails({ + name: data.name, + timezone: selectedTimeZone, + }); + + // Save to backend + await mutation.mutateAsync({ + name: data.name, + timeZone: selectedTimeZone, + }); + + router.push("/onboarding/personal/profile"); + }); + + return ( +
+ + {/* Header */} +
+ + + {/* Progress dots - centered */} +
+
+
+
+
+
+ +
+

{userEmail}

+
+
+ + {/* Main content */} +
+
+ {/* Card */} +
+
+ {/* Card Header */} +
+
+

{t("welcome_to_cal_com")}

+

+ {t("personal_settings_subtitle")} +

+
+
+ + {/* Form */} +
+
+
+
+
+ {/* Name */} +
+ { + setName(e.target.value); + form.setValue("name", e.target.value); + }} + placeholder="John Doe" + className="border-default h-7 rounded-[10px] border px-2 py-1.5 text-sm" + /> + {form.formState.errors.name && ( +

{form.formState.errors.name.message}

+ )} +
+ + {/* Timezone */} +
+ + setSelectedTimeZone(value)} + className="rounded-[10px] text-sm" + /> +

+ {t("current_time")}{" "} + {dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()} +

+
+
+
+
+
+
+ + {/* Footer */} +
+ +
+
+
+
+
+
+ ); +}; diff --git a/apps/web/modules/onboarding/personal/video/personal-video-view.tsx b/apps/web/modules/onboarding/personal/video/personal-video-view.tsx new file mode 100644 index 0000000000..8a1809ac70 --- /dev/null +++ b/apps/web/modules/onboarding/personal/video/personal-video-view.tsx @@ -0,0 +1,203 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { useTelemetry } from "@calcom/lib/hooks/useTelemetry"; +import { telemetryEventTypes } from "@calcom/lib/telemetry"; +import { trpc } from "@calcom/trpc/react"; +import { Button } from "@calcom/ui/components/button"; +import { Logo } from "@calcom/ui/components/logo"; +import { SkeletonText } from "@calcom/ui/components/skeleton"; + +type PersonalVideoViewProps = { + userEmail: string; +}; + +const DEFAULT_EVENT_TYPES = [ + { + title: "15min_meeting", + slug: "15min", + length: 15, + }, + { + title: "30min_meeting", + slug: "30min", + length: 30, + }, + { + title: "secret_meeting", + slug: "secret", + length: 15, + hidden: true, + }, +]; + +export const PersonalVideoView = ({ userEmail }: PersonalVideoViewProps) => { + const { data: user } = trpc.viewer.me.get.useQuery(); + const router = useRouter(); + const { t } = useLocale(); + const telemetry = useTelemetry(); + + const { data: queryConnectedVideoApps, isPending } = trpc.viewer.apps.integrations.useQuery({ + variant: "conferencing", + onlyInstalled: false, + sortByMostPopular: true, + sortByInstalledFirst: true, + }); + + const { data: eventTypes } = trpc.viewer.eventTypes.list.useQuery(); + const createEventType = trpc.viewer.eventTypesHeavy.create.useMutation(); + + const utils = trpc.useUtils(); + const mutation = trpc.viewer.me.updateProfile.useMutation({ + onSuccess: async () => { + try { + // Create default event types if user has none + if (eventTypes?.length === 0) { + await Promise.all( + DEFAULT_EVENT_TYPES.map(async (event) => { + return createEventType.mutateAsync({ + title: t(event.title), + slug: event.slug, + length: event.length, + hidden: event.hidden, + }); + }) + ); + } + } catch (error) { + console.error(error); + } + + await utils.viewer.me.get.refetch(); + router.push("/event-types"); + }, + }); + + const handleContinue = async () => { + telemetry.event(telemetryEventTypes.onboardingFinished); + + // Complete onboarding + mutation.mutate({ + completedOnboarding: true, + }); + }; + + const handleSkip = async () => { + telemetry.event(telemetryEventTypes.onboardingFinished); + + // Complete onboarding + mutation.mutate({ + completedOnboarding: true, + }); + }; + + if (!user) { + return null; // or a loading spinner + } + + return ( +
+ {/* Header */} +
+ + + {/* Progress dots - centered */} +
+
+
+
+
+
+ +
+

{userEmail}

+
+
+ + {/* Main content */} +
+
+ {/* Card */} +
+
+ {/* Card Header */} +
+
+

{t("connect_video_app")}

+

+ {t("video_app_connection_subtitle")} +

+
+
+ + {/* Content */} +
+ {isPending ? ( +
+ + +
+ ) : ( +
+ {queryConnectedVideoApps?.items + .filter((app) => app.slug !== "daily-video") + .map((app) => ( +
+ {app.userCredentialIds.length > 0 && ( + + {t("connected")} + + )} + {app.logo && {app.name}} +

+ {app.name} +

+

+ {app.description} +

+ +
+ ))} +
+ )} +
+ + {/* Footer */} +
+ +
+
+
+ + {/* Skip button */} +
+ +
+
+
+
+ ); +}; diff --git a/apps/web/modules/onboarding/store/onboarding-store.ts b/apps/web/modules/onboarding/store/onboarding-store.ts index b352b1232d..46a2d38d90 100644 --- a/apps/web/modules/onboarding/store/onboarding-store.ts +++ b/apps/web/modules/onboarding/store/onboarding-store.ts @@ -36,6 +36,14 @@ export interface TeamBrand { logo: string | null; // base64 or URL } +export interface PersonalDetails { + name: string; + username: string; + timezone: string; + bio: string; + avatar: string | null; +} + export interface OnboardingState { selectedPlan: PlanType | null; @@ -53,6 +61,9 @@ export interface OnboardingState { teamBrand: TeamBrand; teamInvites: Invite[]; + // Personal user state + personalDetails: PersonalDetails; + // Actions setSelectedPlan: (plan: PlanType) => void; setOrganizationDetails: (details: Partial) => void; @@ -66,6 +77,9 @@ export interface OnboardingState { setTeamBrand: (brand: Partial) => void; setTeamInvites: (invites: Invite[]) => void; + // Personal actions + setPersonalDetails: (details: Partial) => void; + // Reset resetOnboarding: () => void; } @@ -94,6 +108,13 @@ const initialState = { logo: null, }, teamInvites: [], + personalDetails: { + name: "", + username: "", + timezone: "", + bio: "", + avatar: null, + }, }; export const useOnboardingStore = create()( @@ -131,6 +152,11 @@ export const useOnboardingStore = create()( setTeamInvites: (invites) => set({ teamInvites: invites }), + setPersonalDetails: (details) => + set((state) => ({ + personalDetails: { ...state.personalDetails, ...details }, + })), + resetOnboarding: () => set(initialState), }), { @@ -146,6 +172,7 @@ export const useOnboardingStore = create()( teamDetails: state.teamDetails, teamBrand: state.teamBrand, teamInvites: state.teamInvites, + personalDetails: state.personalDetails, }), } ) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index d16543f383..b178ff4523 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1729,6 +1729,16 @@ "set_availability_getting_started_subtitle_2": "Customize it later in the availability page.", "connect_calendar_first": "Connect a calendar first", "complete_profile": "Complete your profile", + "complete_your_profile": "Complete your profile", + "personal_profile_subtitle": "Add details to personalize your scheduling experience", + "personal_settings_subtitle": "Let's set up your personal account", + "profile_photo": "Profile photo", + "connect_calendar_to_prevent_conflicts": "Connect your calendar to prevent double bookings and conflicts", + "connect_video_app": "Connect your video app", + "video_app_connection_subtitle": "Connect your preferred video conferencing app", + "welcome_to_cal_com": "Welcome to Cal.com", + "name_required": "Name is required", + "connected": "Connected", "connect_calendars_from_app_store": "You can add more calendars from the app store", "connect_conference_apps": "Connect conference apps", "connect_calendar_apps": "Connect calendar apps",