diff --git a/apps/web/app/(use-page-wrapper)/settings/teams/new/invite/email/page.tsx b/apps/web/app/(use-page-wrapper)/settings/teams/new/invite/email/page.tsx deleted file mode 100644 index c23bf65896..0000000000 --- a/apps/web/app/(use-page-wrapper)/settings/teams/new/invite/email/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -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 "~/settings/teams/new/invite/email/team-invite-email-view"; - -export const generateMetadata = async () => { - return await _generateMetadata( - (t) => `${APP_NAME} - ${t("invite")}`, - () => "", - true, - undefined, - "/settings/teams/new/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)/settings/teams/new/invite/page.tsx b/apps/web/app/(use-page-wrapper)/settings/teams/new/invite/page.tsx deleted file mode 100644 index 2d71aa5e93..0000000000 --- a/apps/web/app/(use-page-wrapper)/settings/teams/new/invite/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -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 { TeamInviteView } from "~/settings/teams/new/invite/team-invite-view"; - -export const generateMetadata = async () => { - return await _generateMetadata( - (t) => `${APP_NAME} - ${t("invite")}`, - () => "", - true, - undefined, - "/settings/teams/new/invite" - ); -}; - -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)/settings/teams/new/page.tsx b/apps/web/app/(use-page-wrapper)/settings/teams/new/page.tsx index 83a6019ab8..a37b72d979 100644 --- a/apps/web/app/(use-page-wrapper)/settings/teams/new/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/teams/new/page.tsx @@ -1,12 +1,6 @@ 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 { buildLegacyRequest } from "@lib/buildLegacyCtx"; - -import { CreateNewTeamView, LayoutWrapper } from "~/settings/teams/new/create-new-team-view"; +import CreateNewTeamView, { LayoutWrapper } from "~/settings/teams/new/create-new-team-view"; export const generateMetadata = async () => await _generateMetadata( @@ -18,17 +12,9 @@ export const generateMetadata = async () => ); 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 ( - + ); }; diff --git a/apps/web/modules/onboarding/components/onboarding-browser-view.tsx b/apps/web/modules/onboarding/components/onboarding-browser-view.tsx index 605bb347a0..237936849d 100644 --- a/apps/web/modules/onboarding/components/onboarding-browser-view.tsx +++ b/apps/web/modules/onboarding/components/onboarding-browser-view.tsx @@ -134,14 +134,12 @@ export const OnboardingBrowserView = ({ {/* Profile Header */}
- {avatar && ( - - )} +

{name || t("your_name")} diff --git a/apps/web/modules/onboarding/components/onboarding-invite-browser-view.tsx b/apps/web/modules/onboarding/components/onboarding-invite-browser-view.tsx index b23708334d..79e4dc5d84 100644 --- a/apps/web/modules/onboarding/components/onboarding-invite-browser-view.tsx +++ b/apps/web/modules/onboarding/components/onboarding-invite-browser-view.tsx @@ -8,7 +8,6 @@ import { trpc } from "@calcom/trpc/react"; import { Avatar } from "@calcom/ui/components/avatar"; import { useOnboardingStore, type Invite } from "../store/onboarding-store"; -import classNames from "@calcom/ui/classNames"; type OnboardingInviteBrowserViewProps = { teamName?: string; @@ -36,14 +35,8 @@ export const OnboardingInviteBrowserView = ({ }: OnboardingInviteBrowserViewProps) => { const pathname = usePathname(); const { data: user } = trpc.viewer.me.get.useQuery(); - const { - teamBrand, - teamInvites, - invites, - teamDetails, - organizationBrand, - organizationDetails, - } = useOnboardingStore(); + const { teamBrand, teamInvites, invites, teamDetails, organizationBrand, organizationDetails } = + useOnboardingStore(); const { t } = useLocale(); // Animation variants for entry and exit @@ -64,19 +57,14 @@ export const OnboardingInviteBrowserView = ({ // Use default values if not provided const rawInviterName = user?.name || user?.username || "Alex"; - const displayInviterName = - rawInviterName.charAt(0).toUpperCase() + rawInviterName.slice(1); + const displayInviterName = rawInviterName.charAt(0).toUpperCase() + rawInviterName.slice(1); // Use organization or team data based on context const displayName = useOrganizationInvites ? organizationDetails.name || teamName || "Deel" : teamName || teamDetails.name || "Deel"; - const displayBio = useOrganizationInvites - ? organizationDetails.bio || "" - : teamDetails.bio || ""; - const avatar = useOrganizationInvites - ? organizationBrand.logo || null - : teamBrand.logo || null; + const displayBio = useOrganizationInvites ? organizationDetails.bio || "" : teamDetails.bio || ""; + const avatar = useOrganizationInvites ? organizationBrand.logo || null : teamBrand.logo || null; // Get invites based on context - use watched invites if provided, otherwise fall back to store let actualInvites: Invite[] = []; @@ -93,9 +81,7 @@ export const OnboardingInviteBrowserView = ({ } // Filter out empty invites (where email is empty or just whitespace) - const validInvites = actualInvites.filter( - (invite) => invite.email && invite.email.trim().length > 0 - ); + const validInvites = actualInvites.filter((invite) => invite.email && invite.email.trim().length > 0); // Create empty state items const emptyStateItem = { @@ -146,44 +132,31 @@ export const OnboardingInviteBrowserView = ({ transition={{ duration: 0.5, ease: "backOut", - }} - > + }}>
- {organizationBrand.banner || avatar ? ( -
- {/* Banner Image */} - {organizationBrand.banner && ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {displayName} -
- )} +
+ {/* Banner Image */} + {organizationBrand.banner && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {displayName} +
+ )} - {/* Organization Avatar - Overlaying the banner */} - {organizationBrand.banner && avatar && ( -
- -
- )} -
- ) : null} + {/* Organization Avatar - Overlaying the banner */} + {organizationBrand.banner && avatar && ( +
+ +
+ )} +
{/* Organization Info */} -
+
{!organizationBrand.banner && avatar && ( - + }`}> +

diff --git a/apps/web/modules/onboarding/hooks/useCreateTeam.ts b/apps/web/modules/onboarding/hooks/useCreateTeam.ts index 0debc8080e..a65971c254 100644 --- a/apps/web/modules/onboarding/hooks/useCreateTeam.ts +++ b/apps/web/modules/onboarding/hooks/useCreateTeam.ts @@ -9,12 +9,7 @@ import { trpc } from "@calcom/trpc/react"; import type { OnboardingState } from "../store/onboarding-store"; import { useOnboardingStore } from "../store/onboarding-store"; -type UseCreateTeamOptions = { - redirectBasePath?: string; -}; - -export function useCreateTeam(options: UseCreateTeamOptions = {}) { - const { redirectBasePath = "/onboarding/teams" } = options; +export function useCreateTeam() { const router = useRouter(); const [isSubmitting, setIsSubmitting] = useState(false); const flags = useFlagMap(); @@ -33,7 +28,7 @@ export function useCreateTeam(options: UseCreateTeamOptions = {}) { // Validate team details - if empty, redirect back to team details step if (!teamDetails.name || !teamDetails.name.trim() || !teamDetails.slug || !teamDetails.slug.trim()) { - router.push(`${redirectBasePath}/details`); + router.push("/onboarding/teams/details"); setIsSubmitting(false); return; } @@ -56,7 +51,7 @@ export function useCreateTeam(options: UseCreateTeamOptions = {}) { if (result.team) { // Store the teamId and redirect to invite flow after team creation setTeamId(result.team.id); - router.push(`${redirectBasePath}/invite/email?teamId=${result.team.id}`); + router.push(`/onboarding/teams/invite?teamId=${result.team.id}`); } } catch (error) { console.error("Failed to create team:", error); diff --git a/apps/web/modules/settings/teams/new/create-new-team-view.tsx b/apps/web/modules/settings/teams/new/create-new-team-view.tsx index d320d521fe..0b84fcf2a5 100644 --- a/apps/web/modules/settings/teams/new/create-new-team-view.tsx +++ b/apps/web/modules/settings/teams/new/create-new-team-view.tsx @@ -1,210 +1,58 @@ "use client"; import { useRouter } from "next/navigation"; -import posthog from "posthog-js"; -import React, { useEffect, useRef, useState, type FormEvent } from "react"; +import React from "react"; +import { z } from "zod"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import slugify from "@calcom/lib/slugify"; -import { Avatar } from "@calcom/ui/components/avatar"; -import { Button } from "@calcom/ui/components/button"; -import { Label, TextField, TextArea } from "@calcom/ui/components/form"; -import { ImageUploader } from "@calcom/ui/components/image-uploader"; -import { showToast } from "@calcom/ui/components/toast"; +import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; +import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; +import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; +import type { RouterOutputs } from "@calcom/trpc/react"; +import { WizardLayout } from "@calcom/ui/components/layout"; +import { CreateANewTeamForm } from "@calcom/web/modules/ee/teams/components/CreateANewTeamForm"; -import { OnboardingCard } from "~/onboarding/components/OnboardingCard"; -import { OnboardingLayout } from "~/onboarding/components/OnboardingLayout"; -import { OnboardingBrowserView } from "~/onboarding/components/onboarding-browser-view"; -import { useCreateTeam } from "~/onboarding/hooks/useCreateTeam"; -import { useOnboardingStore } from "~/onboarding/store/onboarding-store"; -import { ValidatedTeamSlug } from "~/onboarding/teams/details/validated-team-slug"; +const querySchema = z.object({ + returnTo: z.string().optional(), + slug: z.string().optional(), +}); -type CreateNewTeamViewProps = { - userEmail: string; -}; - -export const CreateNewTeamView = ({ userEmail }: CreateNewTeamViewProps) => { +const CreateNewTeamPage = () => { + const params = useParamsWithFallback(); + const parsedQuery = querySchema.safeParse(params); const router = useRouter(); - const { t } = useLocale(); - const store = useOnboardingStore(); - const { teamDetails, teamBrand, setTeamDetails, setTeamBrand, resetOnboardingPreservingPlan } = store; - const { createTeam, isSubmitting } = useCreateTeam({ redirectBasePath: "/settings/teams/new" }); - const logoRef = useRef(null); - const [teamName, setTeamName] = useState(""); - const [teamSlug, setTeamSlug] = useState(""); - const [teamBio, setTeamBio] = useState(""); - const [teamLogo, setTeamLogo] = useState(""); - const [isSlugValid, setIsSlugValid] = useState(false); - const [isSlugManuallyEdited, setIsSlugManuallyEdited] = useState(false); + const isTeamBillingEnabledClient = !!process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY && HOSTED_CAL_FEATURES; + const flag = isTeamBillingEnabledClient + ? { + submitLabel: "checkout", + } + : { + submitLabel: "continue", + }; - // Reset onboarding store when entering the team creation flow from settings - useEffect(() => { - resetOnboardingPreservingPlan(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const returnToParam = + (parsedQuery.success ? getSafeRedirectUrl(parsedQuery.data.returnTo) : "/teams") || "/teams"; - useEffect(() => { - setTeamName(teamDetails.name); - setTeamSlug(teamDetails.slug); - setTeamBio(teamDetails.bio); - setTeamLogo(teamBrand.logo || ""); - if (teamDetails.slug) { - setIsSlugManuallyEdited(true); - } - }, [teamDetails, teamBrand]); - - useEffect(() => { - if (!isSlugManuallyEdited && teamName) { - const slugifiedName = slugify(teamName); - setTeamSlug(slugifiedName); - } - }, [teamName, isSlugManuallyEdited]); - - const handleSlugChange = (value: string) => { - setTeamSlug(value); - setIsSlugManuallyEdited(true); - }; - - const handleLogoChange = (newLogo: string) => { - if (logoRef.current) { - logoRef.current.value = newLogo; - } - setTeamLogo(newLogo); - }; - - const handleContinue = async (e?: FormEvent) => { - e?.preventDefault(); - - if (!isSlugValid) { - return; - } - - posthog.capture("settings_team_details_continue_clicked", { - has_logo: !!teamLogo, - has_bio: !!teamBio, - }); - - setTeamDetails({ - name: teamName, - slug: teamSlug, - bio: teamBio, - }); - - setTeamBrand({ - logo: teamLogo || null, - }); - - try { - await createTeam(); - } catch (error) { - const message = error instanceof Error ? error.message : t("something_went_wrong"); - showToast(message, "error"); - } - }; - - const handleCancel = () => { - posthog.capture("settings_team_details_cancel_clicked"); - resetOnboardingPreservingPlan(); - router.push("/teams"); + const onSuccess = (data: RouterOutputs["viewer"]["teams"]["create"]) => { + // telemetry.event(flag.telemetryEvent); + router.push(data.url); }; return ( - - {/* Left column - Main content */} -

-
- - - -
- }> -
- {/* Team Profile Picture */} -
- -
-
- -
- - -
-

{t("onboarding_logo_size_hint")}

-
- - {/* Team Name */} -
- - setTeamName(e.target.value)} - placeholder="Acme Inc." - className="border-default h-7 rounded-[10px] border px-2 py-1.5 text-sm" - /> -
- - {/* Team Slug */} - - - {/* Team Bio */} -
- -