chore: refactor onboarding v3 to use reusable components (#24711)
\## What does this PR do? Refactors the onboarding UI components to use shared layout components across personal and organization onboarding flows. This change: - Creates reusable `OnboardingCard` and `OnboardingLayout` components - Implements these components across all onboarding views - Maintains consistent UI and reduces code duplication - Improves maintainability by centralizing layout logic ## Visual Demo (For contributors especially) #### Image Demo: Before this change, each onboarding view had its own layout implementation with duplicated header, footer, and card structure. After this change, the shared components provide consistent styling and structure. ## 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? - Navigate through the onboarding flow for both personal and organization accounts - Verify that all screens maintain consistent layout and styling - Check that progress indicators correctly show the current step - Ensure all functionality (form submissions, navigation between steps) works as expected ## 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
This commit is contained in:
@@ -7,8 +7,9 @@ import { HexColorPicker } from "react-colorful";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Logo } from "@calcom/ui/components/logo";
|
||||
|
||||
import { OnboardingCard } from "../../personal/_components/OnboardingCard";
|
||||
import { OnboardingLayout } from "../../personal/_components/OnboardingLayout";
|
||||
import { useOnboardingStore } from "../../store/onboarding-store";
|
||||
|
||||
type OrganizationBrandViewProps = {
|
||||
@@ -116,70 +117,137 @@ export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-default flex min-h-screen w-full flex-col items-start overflow-clip rounded-xl">
|
||||
{/* Header */}
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<Logo className="h-5 w-auto" />
|
||||
<OnboardingLayout userEmail={userEmail} currentStep={2}>
|
||||
<OnboardingCard
|
||||
title={t("onboarding_org_brand_title")}
|
||||
subtitle={t("onboarding_org_brand_subtitle")}
|
||||
footer={
|
||||
<>
|
||||
<Button color="minimal" className="rounded-[10px]" onClick={handleSkip}>
|
||||
{t("ill_do_this_later")}
|
||||
</Button>
|
||||
<Button color="primary" className="rounded-[10px]" onClick={handleContinue}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</>
|
||||
}>
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-4">
|
||||
{/* Left side - Form */}
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{/* Brand Color */}
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("brand_color")}</p>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<p className="text-subtle w-[98px] overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-4">
|
||||
{t("onboarding_primary_color_label")}
|
||||
</p>
|
||||
<BrandColorPicker
|
||||
value={brandColor}
|
||||
onChange={(value) => {
|
||||
setBrandColor(value);
|
||||
setOrganizationBrand({ color: value });
|
||||
}}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress dots - centered */}
|
||||
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center gap-1">
|
||||
<div className="bg-emphasis h-1 w-1 rounded-full" />
|
||||
<div className="bg-emphasis h-1.5 w-1.5 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
</div>
|
||||
{/* Logo Upload */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("logo")}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="bg-muted border-muted relative h-16 w-16 shrink-0 overflow-hidden rounded-md border">
|
||||
{logoPreview && (
|
||||
<img
|
||||
src={logoPreview}
|
||||
alt={t("onboarding_logo_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={() => document.getElementById("logo-upload")?.click()}>
|
||||
{t("upload")}
|
||||
</Button>
|
||||
<input
|
||||
id="logo-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => handleLogoChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-subtle text-xs font-normal leading-3">{t("onboarding_logo_size_hint")}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted flex items-center gap-2 rounded-full px-3 py-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-none">{userEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex h-full w-full items-start justify-center px-6 py-8">
|
||||
<div className="relative flex w-full max-w-[600px] flex-col gap-6">
|
||||
{/* Card */}
|
||||
<div className="bg-muted border-muted relative rounded-xl border p-1">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
{/* Card Header */}
|
||||
<div className="flex w-full gap-1.5 px-5 py-4">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<h1 className="font-cal text-xl font-semibold leading-6">{t("onboarding_org_brand_title")}</h1>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">
|
||||
{t("onboarding_org_brand_subtitle")}
|
||||
</p>
|
||||
{/* Banner Upload */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("onboarding_banner_label")}
|
||||
</p>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="bg-muted border-muted relative h-[92px] w-full overflow-hidden rounded-md border">
|
||||
{bannerPreview && (
|
||||
<img
|
||||
src={bannerPreview}
|
||||
alt={t("onboarding_banner_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
size="sm"
|
||||
className="w-fit"
|
||||
onClick={() => document.getElementById("banner-upload")?.click()}>
|
||||
{t("upload")}
|
||||
</Button>
|
||||
<input
|
||||
id="banner-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => handleBannerChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("onboarding_banner_size_hint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-4">
|
||||
{/* Left side - Form */}
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{/* Brand Color */}
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("brand_color")}</p>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<p className="text-subtle w-[98px] overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-4">
|
||||
{t("onboarding_primary_color_label")}
|
||||
</p>
|
||||
<BrandColorPicker
|
||||
value={brandColor}
|
||||
onChange={(value) => {
|
||||
setBrandColor(value);
|
||||
setOrganizationBrand({ color: value });
|
||||
}}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right side - Preview */}
|
||||
<div className="bg-muted border-muted flex hidden h-[328px] w-full grow overflow-hidden rounded-[10px] border p-5 md:block">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<p className="text-subtle text-sm font-medium leading-4">{t("preview")}</p>
|
||||
<div className="border-subtle bg-default relative flex w-[110%] flex-col gap-2.5 rounded-md border px-5 pb-5 pt-[74px]">
|
||||
{/* Banner preview */}
|
||||
<div className="bg-muted border-muted absolute left-1 top-1 h-[92px] w-[272px] overflow-hidden rounded-[4px] border">
|
||||
{bannerPreview && (
|
||||
<img
|
||||
src={bannerPreview}
|
||||
alt={t("onboarding_banner_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Logo Upload */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("logo")}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="bg-muted border-muted relative h-16 w-16 shrink-0 overflow-hidden rounded-md border">
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3 p-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Logo preview */}
|
||||
<div className="bg-muted z-20 h-9 w-9 shrink-0 overflow-hidden rounded-md border-2 border-[var(--cal-bg)]">
|
||||
{logoPreview && (
|
||||
<img
|
||||
src={logoPreview}
|
||||
@@ -188,141 +256,38 @@ export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps)
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={() => document.getElementById("logo-upload")?.click()}>
|
||||
{t("upload")}
|
||||
</Button>
|
||||
<input
|
||||
id="logo-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => handleLogoChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
<p className="text-subtle text-sm font-medium capitalize leading-4 ">
|
||||
{organizationDetails.name || t("onboarding_preview_nameless")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-cal text-xl leading-5 tracking-[0.2px]">
|
||||
{t("onboarding_preview_example_title")}
|
||||
</p>
|
||||
<p className="text-subtle text-sm font-medium leading-5">
|
||||
{t("onboarding_preview_example_description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("onboarding_logo_size_hint")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Banner Upload */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("onboarding_banner_label")}</p>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="bg-muted border-muted relative h-[92px] w-full overflow-hidden rounded-md border">
|
||||
{bannerPreview && (
|
||||
<img
|
||||
src={bannerPreview}
|
||||
alt={t("onboarding_banner_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
{[134, 104, 84, 104].map((width, i) => (
|
||||
<div key={i} className="flex items-center gap-2 p-1">
|
||||
<div className="bg-subtle h-5 w-5 shrink-0 rounded-full" />
|
||||
<div className="bg-subtle h-2.5 rounded-full" style={{ width: `${width}px` }} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
color="secondary"
|
||||
size="sm"
|
||||
className="w-fit"
|
||||
onClick={() => document.getElementById("banner-upload")?.click()}>
|
||||
{t("upload")}
|
||||
</Button>
|
||||
<input
|
||||
id="banner-upload"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => handleBannerChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("onboarding_banner_size_hint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Preview */}
|
||||
<div className="bg-muted border-muted flex hidden h-[328px] w-full grow overflow-hidden rounded-[10px] border p-5 md:block">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<p className="text-subtle text-sm font-medium leading-4">{t("preview")}</p>
|
||||
<div className="border-subtle bg-default relative flex w-[110%] flex-col gap-2.5 rounded-md border px-5 pb-5 pt-[74px]">
|
||||
{/* Banner preview */}
|
||||
<div className="bg-muted border-muted absolute left-1 top-1 h-[92px] w-[272px] overflow-hidden rounded-[4px] border">
|
||||
{bannerPreview && (
|
||||
<img
|
||||
src={bannerPreview}
|
||||
alt={t("onboarding_banner_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3 p-1">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Logo preview */}
|
||||
<div className="bg-muted z-20 h-9 w-9 shrink-0 overflow-hidden rounded-md border-2 border-[var(--cal-bg)]">
|
||||
{logoPreview && (
|
||||
<img
|
||||
src={logoPreview}
|
||||
alt={t("onboarding_logo_preview_alt")}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-subtle text-sm font-medium capitalize leading-4 ">
|
||||
{organizationDetails.name || t("onboarding_preview_nameless")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-cal text-xl leading-5 tracking-[0.2px]">
|
||||
{t("onboarding_preview_example_title")}
|
||||
</p>
|
||||
<p className="text-subtle text-sm font-medium leading-5">
|
||||
{t("onboarding_preview_example_description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{[134, 104, 84, 104].map((width, i) => (
|
||||
<div key={i} className="flex items-center gap-2 p-1">
|
||||
<div className="bg-subtle h-5 w-5 shrink-0 rounded-full" />
|
||||
<div
|
||||
className="bg-subtle h-2.5 rounded-full"
|
||||
style={{ width: `${width}px` }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex w-full items-center justify-end gap-1 px-5 py-4">
|
||||
<Button color="minimal" className="rounded-[10px]" onClick={handleSkip}>
|
||||
{t("ill_do_this_later")}
|
||||
</Button>
|
||||
<Button color="primary" className="rounded-[10px]" onClick={handleContinue}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingCard>
|
||||
</OnboardingLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@ import { useEffect, useState } from "react";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Label, TextField, TextArea } from "@calcom/ui/components/form";
|
||||
import { Logo } from "@calcom/ui/components/logo";
|
||||
|
||||
import { OnboardingContinuationPrompt } from "../../components/onboarding-continuation-prompt";
|
||||
import { OnboardingCard } from "../../personal/_components/OnboardingCard";
|
||||
import { OnboardingLayout } from "../../personal/_components/OnboardingLayout";
|
||||
import { useOnboardingStore } from "../../store/onboarding-store";
|
||||
import { ValidatedOrganizationSlug } from "./validated-organization-slug";
|
||||
|
||||
@@ -76,101 +76,64 @@ export const OrganizationDetailsView = ({ userEmail }: OrganizationDetailsViewPr
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-default flex min-h-screen w-full flex-col items-start overflow-clip rounded-xl">
|
||||
{/* Header */}
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<Logo className="h-5 w-auto" />
|
||||
<OnboardingLayout userEmail={userEmail} currentStep={2}>
|
||||
<OnboardingCard
|
||||
title={t("onboarding_org_details_title")}
|
||||
subtitle={t("onboarding_org_details_subtitle")}
|
||||
footer={
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
disabled={!isSlugValid || !organizationName || !organizationLink}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
}>
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-4 rounded-xl">
|
||||
{/* Organization Name */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("organization_name")}
|
||||
</Label>
|
||||
<TextField
|
||||
value={organizationName}
|
||||
onChange={(e) => setOrganizationName(e.target.value)}
|
||||
placeholder={t("organization_name")}
|
||||
className="border-default h-7 rounded-[10px] border px-2 py-1.5 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Progress dots - centered */}
|
||||
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center gap-1">
|
||||
<div className="bg-emphasis h-1 w-1 rounded-full" />
|
||||
<div className="bg-emphasis h-1.5 w-1.5 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
</div>
|
||||
{/* Organization Link */}
|
||||
<ValidatedOrganizationSlug
|
||||
value={organizationLink}
|
||||
onChange={handleSlugChange}
|
||||
onValidationChange={setIsSlugValid}
|
||||
/>
|
||||
|
||||
<div className="bg-muted flex items-center gap-2 rounded-full px-3 py-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-none">{userEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex h-full w-full items-start justify-center px-6 py-8">
|
||||
<div className="flex w-full max-w-[600px] flex-col gap-6">
|
||||
{/* Card */}
|
||||
<div className="bg-muted border-muted relative rounded-xl border p-1">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
{/* Card Header */}
|
||||
<div className="flex w-full gap-1.5 px-5 py-4">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<h1 className="font-cal text-xl font-semibold leading-6">
|
||||
{t("onboarding_org_details_title")}
|
||||
</h1>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">
|
||||
{t("onboarding_org_details_subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-4 rounded-xl">
|
||||
{/* Organization Name */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("organization_name")}
|
||||
</Label>
|
||||
<TextField
|
||||
value={organizationName}
|
||||
onChange={(e) => setOrganizationName(e.target.value)}
|
||||
placeholder={t("organization_name")}
|
||||
className="border-default h-7 rounded-[10px] border px-2 py-1.5 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Organization Link */}
|
||||
<ValidatedOrganizationSlug
|
||||
value={organizationLink}
|
||||
onChange={handleSlugChange}
|
||||
onValidationChange={setIsSlugValid}
|
||||
/>
|
||||
|
||||
{/* Organization Bio */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("onboarding_org_bio_label")}
|
||||
</Label>
|
||||
<TextArea
|
||||
value={organizationBio}
|
||||
onChange={(e) => setOrganizationBio(e.target.value)}
|
||||
placeholder={t("onboarding_org_bio_placeholder")}
|
||||
rows={4}
|
||||
className="border-default rounded-lg border px-2 py-2 text-sm leading-tight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Organization Bio */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("onboarding_org_bio_label")}
|
||||
</Label>
|
||||
<TextArea
|
||||
value={organizationBio}
|
||||
onChange={(e) => setOrganizationBio(e.target.value)}
|
||||
placeholder={t("onboarding_org_bio_placeholder")}
|
||||
rows={4}
|
||||
className="border-default rounded-lg border px-2 py-2 text-sm leading-tight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex w-full items-center justify-end gap-1 px-5 py-4">
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
disabled={!isSlugValid || !organizationName || !organizationLink}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingCard>
|
||||
</OnboardingLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Form, TextField } from "@calcom/ui/components/form";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { Logo } from "@calcom/ui/components/logo";
|
||||
|
||||
import { OnboardingCard } from "../../personal/_components/OnboardingCard";
|
||||
import { OnboardingLayout } from "../../personal/_components/OnboardingLayout";
|
||||
import { useOnboardingStore } from "../../store/onboarding-store";
|
||||
|
||||
type OrganizationTeamsViewProps = {
|
||||
@@ -63,107 +64,74 @@ export const OrganizationTeamsView = ({ userEmail }: OrganizationTeamsViewProps)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-default flex min-h-screen w-full flex-col items-start overflow-clip rounded-xl">
|
||||
{/* Header */}
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<Logo className="h-5 w-auto" />
|
||||
|
||||
{/* Progress dots - centered */}
|
||||
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center gap-1">
|
||||
<div className="bg-emphasis h-1 w-1 rounded-full" />
|
||||
<div className="bg-emphasis h-1 w-1 rounded-full" />
|
||||
<div className="bg-emphasis h-1.5 w-1.5 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="bg-muted flex items-center gap-2 rounded-full px-3 py-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-none">{userEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex h-full w-full items-start justify-center px-6 py-8">
|
||||
<div className="flex w-full max-w-[600px] flex-col gap-4">
|
||||
{/* Card */}
|
||||
<div className="bg-muted border-muted relative rounded-xl border p-1">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
{/* Card Header */}
|
||||
<div className="flex w-full gap-1.5 px-5 py-4">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<h1 className="font-cal text-xl font-semibold leading-6">{t("onboarding_org_teams_title")}</h1>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">
|
||||
{t("onboarding_org_teams_subtitle")}
|
||||
</p>
|
||||
<OnboardingLayout userEmail={userEmail} currentStep={3}>
|
||||
<OnboardingCard
|
||||
title={t("onboarding_org_teams_title")}
|
||||
subtitle={t("onboarding_org_teams_subtitle")}
|
||||
footer={
|
||||
<>
|
||||
<Button type="button" color="minimal" className="rounded-[10px]" onClick={handleSkip}>
|
||||
{t("onboarding_skip_for_now")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
form="teams-form"
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
disabled={!hasValidTeams}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</>
|
||||
}>
|
||||
<Form id="teams-form" form={form} handleSubmit={handleContinue} className="w-full">
|
||||
<div className="bg-default border-subtle w-full rounded-md border">
|
||||
<div className="flex w-full flex-col gap-8 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("team")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<Form form={form} handleSubmit={handleContinue} className="w-full">
|
||||
<div className="bg-default border-subtle w-full rounded-md border">
|
||||
<div className="flex w-full flex-col gap-8 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-emphasis text-sm font-medium leading-4">{t("team")}</p>
|
||||
{/* Team fields */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex w-full items-end gap-0.5">
|
||||
<div className="flex-1">
|
||||
<TextField
|
||||
labelSrOnly
|
||||
{...form.register(`teams.${index}.name`)}
|
||||
placeholder={t("team")}
|
||||
className="h-7 w-full rounded-[10px] text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Team fields */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex w-full items-end gap-0.5">
|
||||
<div className="flex-1">
|
||||
<TextField
|
||||
labelSrOnly
|
||||
{...form.register(`teams.${index}.name`)}
|
||||
placeholder={t("team")}
|
||||
className="h-7 w-full rounded-[10px] text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
color="minimal"
|
||||
variant="icon"
|
||||
size="sm"
|
||||
className="h-7 w-7"
|
||||
disabled={fields.length === 1}
|
||||
onClick={() => remove(index)}>
|
||||
<Icon name="x" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add button */}
|
||||
<Button
|
||||
type="button"
|
||||
color="secondary"
|
||||
color="minimal"
|
||||
variant="icon"
|
||||
size="sm"
|
||||
StartIcon="plus"
|
||||
className="w-fit"
|
||||
onClick={() => append({ name: "" })}>
|
||||
{t("add")}
|
||||
className="h-7 w-7"
|
||||
disabled={fields.length === 1}
|
||||
onClick={() => remove(index)}>
|
||||
<Icon name="x" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="relative flex w-full items-center justify-end gap-1 px-5 py-4">
|
||||
<Button
|
||||
type="button"
|
||||
color="minimal"
|
||||
className="absolute left-[368px] rounded-[10px]"
|
||||
onClick={handleSkip}>
|
||||
{t("onboarding_skip_for_now")}
|
||||
</Button>
|
||||
<Button type="submit" color="primary" className="rounded-[10px]" disabled={!hasValidTeams}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{/* Add button */}
|
||||
<Button
|
||||
type="button"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
StartIcon="plus"
|
||||
className="w-fit"
|
||||
onClick={() => append({ name: "" })}>
|
||||
{t("add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</OnboardingCard>
|
||||
</OnboardingLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ export const OnboardingCard = ({ title, subtitle, children, footer, isLoading }:
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex w-full flex-col gap-4 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{isLoading ? (
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<SkeletonText className="h-40 w-full" />
|
||||
|
||||
@@ -13,11 +13,12 @@ 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 { OnboardingCard } from "../_components/OnboardingCard";
|
||||
import { OnboardingLayout } from "../_components/OnboardingLayout";
|
||||
import { useOnboardingStore } from "../../store/onboarding-store";
|
||||
|
||||
type PersonalProfileViewProps = {
|
||||
@@ -93,128 +94,89 @@ export const PersonalProfileView = ({ userEmail }: PersonalProfileViewProps) =>
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null; // or a loading spinner
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-default flex min-h-screen w-full flex-col items-start overflow-clip rounded-xl">
|
||||
{/* Header */}
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<Logo className="h-5 w-auto" />
|
||||
<OnboardingLayout userEmail={userEmail} currentStep={2}>
|
||||
<OnboardingCard
|
||||
title={t("complete_your_profile")}
|
||||
subtitle={t("personal_profile_subtitle")}
|
||||
footer={
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={onSubmit}
|
||||
loading={mutation.isPending}
|
||||
disabled={mutation.isPending}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
}>
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<form onSubmit={onSubmit} className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-6 rounded-xl">
|
||||
{/* Avatar */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">{t("profile_photo")}</Label>
|
||||
<div className="flex flex-row items-center justify-start gap-4 rtl:justify-end">
|
||||
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} />}
|
||||
<input
|
||||
ref={avatarRef}
|
||||
type="hidden"
|
||||
name="avatar"
|
||||
id="avatar"
|
||||
defaultValue={imageSrc}
|
||||
/>
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("add_profile_photo")}
|
||||
handleAvatarChange={(newAvatar) => {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress dots - centered */}
|
||||
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center gap-1">
|
||||
<div className="bg-emphasis h-1 w-1 rounded-full" />
|
||||
<div className="bg-emphasis h-1.5 w-1.5 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
</div>
|
||||
{/* Username */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<UsernameAvailabilityField />
|
||||
</div>
|
||||
|
||||
<div className="bg-muted flex items-center gap-2 rounded-full px-3 py-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-none">{userEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex h-full w-full items-start justify-center px-6 py-8">
|
||||
<div className="flex w-full max-w-[600px] flex-col gap-6">
|
||||
{/* Card */}
|
||||
<div className="bg-muted border-muted relative rounded-xl border p-1">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
{/* Card Header */}
|
||||
<div className="flex w-full gap-1.5 px-5 py-4">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<h1 className="font-cal text-xl font-semibold leading-6">{t("complete_your_profile")}</h1>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">
|
||||
{t("personal_profile_subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start overflow-clip">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<form onSubmit={onSubmit} className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-6 rounded-xl">
|
||||
{/* Avatar */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<Label className="text-emphasis text-sm font-medium leading-4">
|
||||
{t("profile_photo")}
|
||||
</Label>
|
||||
<div className="flex flex-row items-center justify-start gap-4 rtl:justify-end">
|
||||
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} />}
|
||||
<input
|
||||
ref={avatarRef}
|
||||
type="hidden"
|
||||
name="avatar"
|
||||
id="avatar"
|
||||
defaultValue={imageSrc}
|
||||
/>
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("add_profile_photo")}
|
||||
handleAvatarChange={(newAvatar) => {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Username */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<UsernameAvailabilityField />
|
||||
</div>
|
||||
|
||||
{/* Bio */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis mb-1 text-sm font-medium leading-4">
|
||||
{t("about")}
|
||||
</Label>
|
||||
<Editor
|
||||
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
||||
setText={(value: string) => setValue("bio", turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("few_sentences_about_yourself")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/* Bio */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis mb-1 text-sm font-medium leading-4">{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
||||
setText={(value: string) => setValue("bio", turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("few_sentences_about_yourself")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex w-full items-center justify-end gap-1 px-5 py-4">
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={onSubmit}
|
||||
loading={mutation.isPending}
|
||||
disabled={mutation.isPending}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingCard>
|
||||
</OnboardingLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,10 +14,11 @@ 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";
|
||||
import { OnboardingCard } from "../_components/OnboardingCard";
|
||||
import { OnboardingLayout } from "../_components/OnboardingLayout";
|
||||
|
||||
type PersonalSettingsViewProps = {
|
||||
userEmail: string;
|
||||
@@ -79,101 +80,68 @@ export const PersonalSettingsView = ({ userEmail, userName }: PersonalSettingsVi
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-default flex min-h-screen w-full flex-col items-start rounded-xl">
|
||||
<>
|
||||
<OnboardingContinuationPrompt />
|
||||
{/* Header */}
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<Logo className="h-5 w-auto" />
|
||||
<OnboardingLayout userEmail={userEmail} currentStep={1}>
|
||||
<OnboardingCard
|
||||
title={t("welcome_to_cal_com")}
|
||||
subtitle={t("personal_settings_subtitle")}
|
||||
footer={
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
loading={mutation.isPending}
|
||||
disabled={mutation.isPending || !form.formState.isValid}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
}>
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-4 rounded-xl">
|
||||
{/* Name */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<TextField
|
||||
label={t("full_name")}
|
||||
{...form.register("name")}
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
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 && (
|
||||
<p className="text-error text-sm">{form.formState.errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Progress dots - centered */}
|
||||
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center gap-1">
|
||||
<div className="bg-emphasis h-1.5 w-1.5 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
<div className="bg-subtle h-1 w-1 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="bg-muted flex items-center gap-2 rounded-full px-3 py-2">
|
||||
<p className="text-emphasis text-sm font-medium leading-none">{userEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex h-full w-full items-start justify-center px-6 py-8">
|
||||
<div className="flex w-full max-w-[600px] flex-col gap-6">
|
||||
{/* Card */}
|
||||
<div className="bg-muted border-muted relative rounded-xl border p-1">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start ">
|
||||
{/* Card Header */}
|
||||
<div className="flex w-full gap-1.5 px-5 py-4">
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<h1 className="font-cal text-xl font-semibold leading-6">{t("welcome_to_cal_com")}</h1>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">
|
||||
{t("personal_settings_subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-default border-muted w-full rounded-[10px] border">
|
||||
<div className="rounded-inherit flex w-full flex-col items-start">
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<div className="flex w-full gap-6 px-5 py-5">
|
||||
<div className="flex w-full flex-col gap-4 rounded-xl">
|
||||
{/* Name */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<TextField
|
||||
label={t("full_name")}
|
||||
{...form.register("name")}
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
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 && (
|
||||
<p className="text-error text-sm">{form.formState.errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timezone */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis mb-0 text-sm font-medium leading-4">
|
||||
{t("timezone")}
|
||||
</Label>
|
||||
<TimezoneSelect
|
||||
value={selectedTimeZone}
|
||||
onChange={({ value }) => setSelectedTimeZone(value)}
|
||||
className="rounded-[10px] text-sm"
|
||||
/>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("current_time")}{" "}
|
||||
{dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Timezone */}
|
||||
<div className="flex w-full flex-col gap-1.5">
|
||||
<Label className="text-emphasis mb-0 text-sm font-medium leading-4">
|
||||
{t("timezone")}
|
||||
</Label>
|
||||
<TimezoneSelect
|
||||
value={selectedTimeZone}
|
||||
onChange={({ value }) => setSelectedTimeZone(value)}
|
||||
size="sm"
|
||||
/>
|
||||
<p className="text-subtle text-xs font-normal leading-3">
|
||||
{t("current_time")}{" "}
|
||||
{dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex w-full items-center justify-end gap-1 px-5 py-4">
|
||||
<Button
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
onClick={handleContinue}
|
||||
loading={mutation.isPending}
|
||||
disabled={mutation.isPending || !form.formState.isValid}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingCard>
|
||||
</OnboardingLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user