diff --git a/apps/web/app/future/settings/teams/[id]/event-type/page.tsx b/apps/web/app/future/settings/teams/[id]/event-type/page.tsx
new file mode 100644
index 0000000000..c741324ceb
--- /dev/null
+++ b/apps/web/app/future/settings/teams/[id]/event-type/page.tsx
@@ -0,0 +1,11 @@
+import LegacyPage, { GetLayout } from "@pages/settings/teams/[id]/event-type";
+import { _generateMetadata } from "app/_utils";
+import { WithLayout } from "app/layoutHOC";
+
+export const generateMetadata = async () =>
+ await _generateMetadata(
+ (t) => t("add_new_team_event_type"),
+ (t) => t("new_event_type_to_book_description")
+ );
+
+export default WithLayout({ Page: LegacyPage, getLayout: GetLayout })<"P">;
diff --git a/apps/web/pages/settings/teams/[id]/event-type.tsx b/apps/web/pages/settings/teams/[id]/event-type.tsx
new file mode 100644
index 0000000000..101fa61042
--- /dev/null
+++ b/apps/web/pages/settings/teams/[id]/event-type.tsx
@@ -0,0 +1,86 @@
+"use client";
+
+import Head from "next/head";
+import { useRouter } from "next/navigation";
+
+import { TeamEventTypeForm } from "@calcom/features/ee/teams/components/TeamEventTypeForm";
+import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { WizardLayout } from "@calcom/ui";
+import { Button, showToast } from "@calcom/ui";
+
+import PageWrapper from "@components/PageWrapper";
+
+export const CreateTeamEventType = () => {
+ const searchParams = useCompatSearchParams();
+ const { t } = useLocale();
+ const router = useRouter();
+ const teamId = searchParams?.get("id") ? Number(searchParams.get("id")) : -1;
+
+ const onSuccessMutation = () => {
+ router.push(`/settings/teams/${teamId}/profile`);
+ };
+
+ const onErrorMutation = (err: string) => {
+ showToast(err, "error");
+ };
+
+ const SubmitButton = (isPending: boolean) => {
+ return (
+
+ );
+ };
+
+ return (
+
+ );
+};
+
+const TeamEventTypePage = () => {
+ const { t } = useLocale();
+ return (
+ <>
+
+ {t("add_new_team_event_type")}
+
+
+
+ >
+ );
+};
+
+export const GetLayout = (page: React.ReactElement) => {
+ const router = useRouter();
+ const searchParams = useCompatSearchParams();
+ const teamId = searchParams?.get("id") ? Number(searchParams.get("id")) : -1;
+
+ return (
+ {
+ router.push(`/settings/teams/${teamId}/profile`);
+ }}>
+ {page}
+
+ );
+};
+
+TeamEventTypePage.getLayout = GetLayout;
+TeamEventTypePage.PageWrapper = PageWrapper;
+
+export default TeamEventTypePage;
diff --git a/apps/web/pages/settings/teams/[id]/onboard-members.tsx b/apps/web/pages/settings/teams/[id]/onboard-members.tsx
index 36ae12050e..aaf3dd9bfa 100644
--- a/apps/web/pages/settings/teams/[id]/onboard-members.tsx
+++ b/apps/web/pages/settings/teams/[id]/onboard-members.tsx
@@ -22,7 +22,7 @@ const OnboardTeamMembersPage = () => {
};
export const GetLayout = (page: React.ReactElement) => (
-
+
{page}
);
diff --git a/apps/web/pages/settings/teams/new/index.tsx b/apps/web/pages/settings/teams/new/index.tsx
index 3ea1090905..a019cdfbbe 100644
--- a/apps/web/pages/settings/teams/new/index.tsx
+++ b/apps/web/pages/settings/teams/new/index.tsx
@@ -63,7 +63,7 @@ const CreateNewTeamPage = () => {
};
export const LayoutWrapper = (page: React.ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts
index 4097beb72f..28a567b620 100644
--- a/apps/web/playwright/managed-event-types.e2e.ts
+++ b/apps/web/playwright/managed-event-types.e2e.ts
@@ -38,10 +38,15 @@ test.describe("Managed Event Types", () => {
await page.getByTestId("invite-new-member-button").click();
// wait for the second member to be added to the pending-member-list.
await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor();
- // and publish
await page.locator("[data-testid=publish-button]").click();
- await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
- // Going to create an event type
+ await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/event-type$/i);
+ // create a round-robin event type and finish
+ await expect(page.locator('button[value="ROUND_ROBIN"]')).toBeVisible();
+ await page.click('button[value="ROUND_ROBIN"]');
+ await page.fill("[name=title]", "roundRobin");
+ await page.getByTestId("finish-button").click();
+ await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
+ // Going to create an managed event type
await page.goto("/event-types");
await page.getByTestId("new-event-type").click();
await page.getByTestId("option-team-1").click();
diff --git a/apps/web/playwright/organization/team-management.e2e.ts b/apps/web/playwright/organization/team-management.e2e.ts
index 90ffae5029..36d9db9d05 100644
--- a/apps/web/playwright/organization/team-management.e2e.ts
+++ b/apps/web/playwright/organization/team-management.e2e.ts
@@ -64,13 +64,20 @@ test.describe("Teams", () => {
await prisma.user.delete({ where: { email: inviteeEmail } });
});
- await test.step("Can finish team creation", async () => {
+ await test.step("Can finish team invitation", async () => {
await page.getByTestId("publish-button").click();
- await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
+ await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/event-type$/i);
+ });
+
+ await test.step("Can finish team creation", async () => {
+ await expect(page.locator('button[value="ROUND_ROBIN"]')).toBeVisible();
+ await page.click('button[value="ROUND_ROBIN"]');
+ await page.fill("[name=title]", "roundRobin");
+ await page.getByTestId("finish-button").click();
+ await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can disband team", async () => {
- await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
await page.getByTestId("disband-team-button").click();
await page.getByTestId("dialog-confirmation").click();
await page.waitForURL("/teams");
diff --git a/apps/web/playwright/teams.e2e.ts b/apps/web/playwright/teams.e2e.ts
index 2733bd02f2..efa0a17e2a 100644
--- a/apps/web/playwright/teams.e2e.ts
+++ b/apps/web/playwright/teams.e2e.ts
@@ -68,7 +68,9 @@ testBothFutureAndLegacyRoutes.describe("Teams - NonOrg", (routeVariant) => {
await test.step("Finishing brings you to team profile page", async () => {
await page.locator("[data-testid=publish-button]").click();
- await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
+ await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/event-type$/i);
+ await page.locator("[data-testid=handle-later-button]").click();
+ await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can disband team", async () => {
@@ -220,7 +222,9 @@ testBothFutureAndLegacyRoutes.describe("Teams - NonOrg", (routeVariant) => {
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
// Click text=Continue
await page.locator("[data-testid=publish-button]").click();
- await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
+ await page.waitForURL(/\/settings\/teams\/(\d+)\/event-type*$/i);
+ await page.locator("[data-testid=handle-later-button]").click();
+ await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can access user and team with same slug", async () => {
diff --git a/packages/features/ee/teams/components/AddNewTeamMembers.tsx b/packages/features/ee/teams/components/AddNewTeamMembers.tsx
index 0346ccd0ca..13b900d0fd 100644
--- a/packages/features/ee/teams/components/AddNewTeamMembers.tsx
+++ b/packages/features/ee/teams/components/AddNewTeamMembers.tsx
@@ -199,13 +199,13 @@ export const AddNewTeamMembersForm = ({
className="w-full justify-center"
disabled={publishTeamMutation.isPending}
onClick={() => {
- let uri = `/settings/teams/${teamId}/profile`;
+ let uri = `/settings/teams/${teamId}/event-type`;
if (isOrg) {
uri = `/settings/organizations/${teamId}/add-teams`;
}
router.push(uri);
}}>
- {isOrg ? t("continue") : t("finish")}
+ {t("continue")}
>
);
diff --git a/packages/features/ee/teams/components/TeamEventTypeForm.tsx b/packages/features/ee/teams/components/TeamEventTypeForm.tsx
new file mode 100644
index 0000000000..c08c516246
--- /dev/null
+++ b/packages/features/ee/teams/components/TeamEventTypeForm.tsx
@@ -0,0 +1,162 @@
+import type { EventType } from "@prisma/client";
+import type { ReactNode } from "react";
+
+import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
+import { classNames } from "@calcom/lib";
+import { useCreateEventType } from "@calcom/lib/hooks/useCreateEventType";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import slugify from "@calcom/lib/slugify";
+import { SchedulingType } from "@calcom/prisma/enums";
+import { trpc } from "@calcom/trpc/react";
+import { Form, TextField, Tooltip } from "@calcom/ui";
+import { Alert, RadioGroup as RadioArea } from "@calcom/ui";
+
+type props = {
+ isTeamAdminOrOwner: boolean;
+ teamId: number;
+ SubmitButton: (isPending: boolean) => ReactNode;
+ onSuccessMutation: (eventType: EventType) => void;
+ onErrorMutation: (message: string) => void;
+ isInfiniteScrollEnabled: boolean;
+};
+export const TeamEventTypeForm = ({
+ isTeamAdminOrOwner,
+ teamId,
+ SubmitButton,
+ onSuccessMutation,
+ onErrorMutation,
+ isInfiniteScrollEnabled,
+}: props) => {
+ const { t } = useLocale();
+ const orgBranding = useOrgBranding();
+ const { data: team } = trpc.viewer.teams.getMinimal.useQuery(
+ { teamId, isOrg: false },
+ { enabled: !!teamId }
+ );
+ const urlPrefix = orgBranding?.fullDomain ?? process.env.NEXT_PUBLIC_WEBSITE_URL;
+
+ const { form, createMutation, isManagedEventType } = useCreateEventType(
+ onSuccessMutation,
+ onErrorMutation,
+ isInfiniteScrollEnabled
+ );
+ const { register, setValue, formState } = form;
+
+ return (
+
+ );
+};
diff --git a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
index 11d32f494c..86dc42502d 100644
--- a/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
+++ b/packages/features/eventtypes/components/CreateEventTypeDialog.tsx
@@ -1,24 +1,19 @@
-import { zodResolver } from "@hookform/resolvers/zod";
+import type { EventType } from "@prisma/client";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useRouter } from "next/navigation";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
+import { useState } from "react";
import { z } from "zod";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
-import { classNames } from "@calcom/lib";
+import { TeamEventTypeForm } from "@calcom/features/ee/teams/components/TeamEventTypeForm";
+import { useCreateEventType } from "@calcom/lib/hooks/useCreateEventType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
-import { HttpError } from "@calcom/lib/http-error";
import { md } from "@calcom/lib/markdownIt";
import slugify from "@calcom/lib/slugify";
import turndown from "@calcom/lib/turndownService";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
-import { unlockedManagedEventTypeProps } from "@calcom/prisma/zod-utils";
-import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
-import { trpc } from "@calcom/trpc/react";
import {
- Alert,
Button,
Dialog,
DialogClose,
@@ -26,7 +21,6 @@ import {
DialogFooter,
Editor,
Form,
- RadioGroup as RadioArea,
showToast,
TextField,
Tooltip,
@@ -80,7 +74,6 @@ export default function CreateEventTypeDialog({
}[];
isInfiniteScrollEnabled: boolean;
}) {
- const utils = trpc.useUtils();
const { t } = useLocale();
const router = useRouter();
const [firstRender, setFirstRender] = useState(true);
@@ -91,70 +84,44 @@ export default function CreateEventTypeDialog({
} = useTypedQuery(querySchema);
const teamProfile = profileOptions.find((profile) => profile.teamId === teamId);
- const form = useForm>({
- defaultValues: {
- length: 15,
- },
- resolver: zodResolver(createEventTypeInput),
- });
- const schedulingTypeWatch = form.watch("schedulingType");
- const isManagedEventType = schedulingTypeWatch === SchedulingType.MANAGED;
-
- useEffect(() => {
- if (isManagedEventType) {
- form.setValue("metadata.managedEventConfig.unlockedFields", unlockedManagedEventTypeProps);
- } else {
- form.setValue("metadata", null);
- }
- }, [schedulingTypeWatch]);
-
- const { register } = form;
-
- const isAdmin =
+ const isTeamAdminOrOwner =
teamId !== undefined &&
(teamProfile?.membershipRole === MembershipRole.OWNER ||
teamProfile?.membershipRole === MembershipRole.ADMIN);
- const createMutation = trpc.viewer.eventTypes.create.useMutation({
- onSuccess: async ({ eventType }) => {
- await router.replace(`/event-types/${eventType.id}${teamId ? "?tabName=team" : ""}`);
+ const onSuccessMutation = (eventType: EventType) => {
+ router.replace(`/event-types/${eventType.id}${teamId ? "?tabName=team" : ""}`);
+ showToast(
+ t("event_type_created_successfully", {
+ eventTypeTitle: eventType.title,
+ }),
+ "success"
+ );
+ };
- if (isInfiniteScrollEnabled) {
- await utils.viewer.eventTypes.getEventTypesFromGroup.fetchInfinite({
- group: { teamId: eventType.teamId, parentId: eventType.parentId },
- limit: 10,
- });
- } else {
- await utils.viewer.eventTypes.getByViewer.invalidate();
- }
+ const onErrorMutation = (err: string) => {
+ showToast(err, "error");
+ };
- showToast(
- t("event_type_created_successfully", {
- eventTypeTitle: eventType.title,
- }),
- "success"
- );
- form.reset();
- },
- onError: (err) => {
- if (err instanceof HttpError) {
- const message = `${err.statusCode}: ${err.message}`;
- showToast(message, "error");
- }
+ const SubmitButton = (isPending: boolean) => {
+ return (
+
+
+
+
+ );
+ };
- if (err.data?.code === "BAD_REQUEST") {
- const message = `${err.data.code}: ${t("error_event_type_url_duplicate")}`;
- showToast(message, "error");
- }
-
- if (err.data?.code === "UNAUTHORIZED") {
- const message = `${err.data.code}: ${t("error_event_type_unauthorized_create")}`;
- showToast(message, "error");
- }
- },
- });
+ const { form, createMutation, isManagedEventType } = useCreateEventType(
+ onSuccessMutation,
+ onErrorMutation,
+ isInfiniteScrollEnabled
+ );
+ const { register } = form;
const urlPrefix = orgBranding?.fullDomain ?? process.env.NEXT_PUBLIC_WEBSITE_URL;
return (
@@ -175,76 +142,82 @@ export default function CreateEventTypeDialog({
enableOverflow
title={teamId ? t("add_new_team_event_type") : t("add_new_event_type")}
description={t("new_event_type_to_book_description")}>
-
+
+ {SubmitButton(createMutation.isPending)}
+
+ )}
);
diff --git a/packages/lib/hooks/useCreateEventType.ts b/packages/lib/hooks/useCreateEventType.ts
new file mode 100644
index 0000000000..b78244535f
--- /dev/null
+++ b/packages/lib/hooks/useCreateEventType.ts
@@ -0,0 +1,72 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import type { EventType } from "@prisma/client";
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import type { z } from "zod";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { HttpError } from "@calcom/lib/http-error";
+import { SchedulingType } from "@calcom/prisma/enums";
+import { unlockedManagedEventTypeProps } from "@calcom/prisma/zod-utils";
+import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
+import { trpc } from "@calcom/trpc/react";
+
+export const useCreateEventType = (
+ onSuccessMutation: (eventType: EventType) => void,
+ onErrorMutation: (message: string) => void,
+ isInfiniteScrollEnabled: boolean
+) => {
+ const utils = trpc.useUtils();
+ const { t } = useLocale();
+ const form = useForm>({
+ defaultValues: {
+ length: 15,
+ },
+ resolver: zodResolver(createEventTypeInput),
+ });
+
+ const schedulingTypeWatch = form.watch("schedulingType");
+ const isManagedEventType = schedulingTypeWatch === SchedulingType.MANAGED;
+
+ useEffect(() => {
+ if (isManagedEventType) {
+ form.setValue("metadata.managedEventConfig.unlockedFields", unlockedManagedEventTypeProps);
+ } else {
+ form.setValue("metadata", null);
+ }
+ }, [schedulingTypeWatch]);
+
+ const createMutation = trpc.viewer.eventTypes.create.useMutation({
+ onSuccess: async ({ eventType }) => {
+ onSuccessMutation(eventType);
+
+ if (isInfiniteScrollEnabled) {
+ await utils.viewer.eventTypes.getEventTypesFromGroup.fetchInfinite({
+ group: { teamId: eventType.teamId, parentId: eventType.parentId },
+ limit: 10,
+ });
+ } else {
+ await utils.viewer.eventTypes.getByViewer.invalidate();
+ }
+
+ form.reset();
+ },
+ onError: (err) => {
+ let error = err.message;
+ if (err instanceof HttpError) {
+ error = `${err.statusCode}: ${err.message}`;
+ }
+
+ if (err.data?.code === "BAD_REQUEST") {
+ error = `${err.data.code}: ${t("error_event_type_url_duplicate")}`;
+ }
+
+ if (err.data?.code === "UNAUTHORIZED") {
+ error = `${err.data.code}: ${t("error_event_type_unauthorized_create")}`;
+ }
+ onErrorMutation(error);
+ },
+ });
+
+ return { form, createMutation, isManagedEventType };
+};
diff --git a/packages/ui/layouts/WizardLayout.tsx b/packages/ui/layouts/WizardLayout.tsx
index f0178b0283..4d129f6593 100644
--- a/packages/ui/layouts/WizardLayout.tsx
+++ b/packages/ui/layouts/WizardLayout.tsx
@@ -63,7 +63,7 @@ export function WizardLayout({
{isOptionalCallback && (
-