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 ; +}; + +export default ServerPage; + diff --git a/apps/web/app/(use-page-wrapper)/onboarding/teams/invite/email/page.tsx b/apps/web/app/(use-page-wrapper)/onboarding/teams/invite/email/page.tsx new file mode 100644 index 0000000000..10bb4d25eb --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/onboarding/teams/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 { TeamInviteEmailView } from "~/onboarding/teams/invite/email/team-invite-email-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${APP_NAME} - ${t("invite_teammates")}`, + () => "", + true, + undefined, + "/onboarding/teams/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 ; +}; + +export default ServerPage; + diff --git a/apps/web/modules/onboarding/components/EmailInviteForm.tsx b/apps/web/modules/onboarding/components/EmailInviteForm.tsx new file mode 100644 index 0000000000..a1f3b47631 --- /dev/null +++ b/apps/web/modules/onboarding/components/EmailInviteForm.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useFormContext } from "react-hook-form"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Button } from "@calcom/ui/components/button"; +import { Label, TextField, Select } from "@calcom/ui/components/form"; +import { Icon } from "@calcom/ui/components/icon"; + +import type { InviteRole } from "../store/onboarding-store"; + +type BaseInviteFormData = { + email: string; + role: InviteRole; +}; + +type InviteFormDataWithOptionalTeam = BaseInviteFormData & { + team?: string; +}; + +type InviteFormDataWithRequiredTeam = BaseInviteFormData & { + team: string; +}; + +type InviteFormData = InviteFormDataWithOptionalTeam | InviteFormDataWithRequiredTeam; + +type EmailInviteFormProps = { + fields: Array<{ id: string }>; + append: (value: InviteFormData) => void; + remove: (index: number) => void; + defaultRole: InviteRole; + showTeamSelect?: boolean; + teams?: Array<{ value: string; label: string }>; + emailPlaceholder?: string; +}; + +export function EmailInviteForm({ + fields, + append, + remove, + defaultRole, + showTeamSelect = false, + teams = [], + emailPlaceholder, +}: EmailInviteFormProps) { + const { t } = useLocale(); + const { register, watch, setValue } = useFormContext(); + + return ( +
+
+ {showTeamSelect ? ( +
+ + +
+ ) : ( + + )} + +
+ {fields.map((field, index) => ( +
+
+ + {showTeamSelect && ( + { - const newValue = e.target.value.startsWith("#") ? e.target.value : `#${e.target.value}`; - onChange(newValue); - }} - className="text-emphasis grow border-none bg-transparent text-sm font-medium leading-4 outline-none" - maxLength={6} - /> -
- ); -}; - export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps) => { const router = useRouter(); const { t } = useLocale(); - const { organizationBrand, setOrganizationBrand, organizationDetails } = useOnboardingStore(); + const { organizationDetails, organizationBrand, setOrganizationBrand } = useOnboardingStore(); - const [brandColor, setBrandColor] = useState("#000000"); const [_logoFile, setLogoFile] = useState(null); const [_bannerFile, setBannerFile] = useState(null); const [logoPreview, setLogoPreview] = useState(null); const [bannerPreview, setBannerPreview] = useState(null); + const [brandColor, setBrandColor] = useState("#000000"); // Load from store on mount useEffect(() => { - setBrandColor(organizationBrand.color); setLogoPreview(organizationBrand.logo); setBannerPreview(organizationBrand.banner); + setBrandColor(organizationBrand.color); }, [organizationBrand]); const handleLogoChange = (file: File | null) => { @@ -106,129 +66,116 @@ export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps) } }; - const handleContinue = () => { - // Save brand color to store - setOrganizationBrand({ color: brandColor }); - router.push("/onboarding/organization/teams"); + const handleColorChange = (color: string) => { + setBrandColor(color); + setOrganizationBrand({ color }); }; - const handleSkip = () => { - // Skip brand setup and go to teams + const handleContinue = () => { + // Save to store (already saved on change, but ensure it's persisted) + setOrganizationBrand({ + logo: logoPreview, + banner: bannerPreview, + color: brandColor, + }); router.push("/onboarding/organization/teams"); }; return ( - + {/* Left column - Main content */} - - +
}> {/* Form */} -
-
-
-
- {/* Left side - Form */} -
- {/* Brand Color */} -
-

{t("brand_color")}

-
-

- {t("onboarding_primary_color_label")} -

- { - setBrandColor(value); - setOrganizationBrand({ color: value }); - }} - t={t} +
+ {/* Scrollable content container */} +
+
+ {/* Banner Upload */} +
+

{t("onboarding_banner_label")}

+
+
+ {bannerPreview && ( + {t("onboarding_banner_preview_alt")} -
+ )}
- - {/* Logo Upload */} -
-

{t("logo")}

-
-
- {logoPreview && ( - {t("onboarding_logo_preview_alt")} - )} -
-
- - handleLogoChange(e.target.files?.[0] || null)} - /> -
-
-

- {t("onboarding_logo_size_hint")} -

-
- - {/* Banner Upload */} -
-

- {t("onboarding_banner_label")} -

-
-
- {bannerPreview && ( - {t("onboarding_banner_preview_alt")} - )} -
-
- - handleBannerChange(e.target.files?.[0] || null)} - /> -
-
-

- {t("onboarding_banner_size_hint")} -

+
+ + handleBannerChange(e.target.files?.[0] || null)} + />
+

+ {t("onboarding_banner_size_hint")} +

+
+ {/* Logo Upload */} +
+

{t("logo")}

+
+
+ {logoPreview && ( + {t("onboarding_logo_preview_alt")} + )} +
+
+ + handleLogoChange(e.target.files?.[0] || null)} + /> +
+
+

{t("onboarding_logo_size_hint")}

+
+ {/* Brand Color */} +
+ +
@@ -236,7 +183,13 @@ export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps) {/* Right column - Browser view */} - + ); }; diff --git a/apps/web/modules/onboarding/organization/details/organization-details-view.tsx b/apps/web/modules/onboarding/organization/details/organization-details-view.tsx index 10542bfc8d..7ab0868fe9 100644 --- a/apps/web/modules/onboarding/organization/details/organization-details-view.tsx +++ b/apps/web/modules/onboarding/organization/details/organization-details-view.tsx @@ -1,15 +1,15 @@ "use client"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, 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 { OnboardingBrowserView } from "../../components/onboarding-browser-view"; import { OnboardingCard } from "../../components/OnboardingCard"; import { OnboardingLayout } from "../../components/OnboardingLayout"; +import { OnboardingOrganizationBrowserView } from "../../components/onboarding-organization-browser-view"; import { useOnboardingStore } from "../../store/onboarding-store"; import { ValidatedOrganizationSlug } from "./validated-organization-slug"; @@ -77,60 +77,60 @@ export const OrganizationDetailsView = ({ userEmail }: OrganizationDetailsViewPr }; return ( - + {/* Left column - Main content */} - {t("continue")} - +
+ +
}> {/* Form */} -
-
-
-
-
- {/* Organization Name */} -
- - setOrganizationName(e.target.value)} - placeholder={t("organization_name")} - className="border-default h-7 rounded-[10px] border px-2 py-1.5 text-sm" - /> -
+
+ {/* Scrollable content container */} +
+ {/* Top fade overlay */} - {/* Organization Link */} - +
+ {/* Organization Name */} +
+ + setOrganizationName(e.target.value)} + placeholder={t("organization_name")} + /> +
- {/* Organization Bio */} -
- -