feat: Add step in team creation form for selecting event type and create a team event (#15286)

* feat: Create team event on a new team creation

* fix

* fixed failed tests

* move team event type form

* fix type error

* requested chnages

* update

* some update and refactor

* fix e2e test

* Update useCreateEventType.ts

* update

* fix type error

* fix e2e

* update

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Anik Dhabal Babu
2024-09-05 12:33:18 -04:00
committed by GitHub
co-authored by Somay Chauhan Hariom Balhara Keith Williams Udit Takkar
parent 6b57dd6794
commit fced9a6355
12 changed files with 469 additions and 204 deletions
@@ -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">;
@@ -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 (
<Button
data-testid="finish-button"
type="submit"
color="primary"
className="w-full justify-center"
disabled={isPending}>
{t("finish")}
</Button>
);
};
return (
<TeamEventTypeForm
isTeamAdminOrOwner={true}
teamId={teamId}
SubmitButton={SubmitButton}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
isInfiniteScrollEnabled={false}
/>
);
};
const TeamEventTypePage = () => {
const { t } = useLocale();
return (
<>
<Head>
<title>{t("add_new_team_event_type")}</title>
<meta name="description" content={t("new_event_type_to_book_description")} />
</Head>
<CreateTeamEventType />
</>
);
};
export const GetLayout = (page: React.ReactElement) => {
const router = useRouter();
const searchParams = useCompatSearchParams();
const teamId = searchParams?.get("id") ? Number(searchParams.get("id")) : -1;
return (
<WizardLayout
currentStep={3}
maxSteps={3}
isOptionalCallback={() => {
router.push(`/settings/teams/${teamId}/profile`);
}}>
{page}
</WizardLayout>
);
};
TeamEventTypePage.getLayout = GetLayout;
TeamEventTypePage.PageWrapper = PageWrapper;
export default TeamEventTypePage;
@@ -22,7 +22,7 @@ const OnboardTeamMembersPage = () => {
};
export const GetLayout = (page: React.ReactElement) => (
<WizardLayout currentStep={2} maxSteps={2}>
<WizardLayout currentStep={2} maxSteps={3}>
{page}
</WizardLayout>
);
+1 -1
View File
@@ -63,7 +63,7 @@ const CreateNewTeamPage = () => {
};
export const LayoutWrapper = (page: React.ReactElement) => {
return (
<WizardLayout currentStep={1} maxSteps={2}>
<WizardLayout currentStep={1} maxSteps={3}>
{page}
</WizardLayout>
);
@@ -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();
@@ -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");
+6 -2
View File
@@ -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 () => {
@@ -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")}
</Button>
</>
);
@@ -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 (
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-6 pb-11">
<TextField
type="hidden"
labelProps={{ style: { display: "none" } }}
{...register("teamId", { valueAsNumber: true })}
value={teamId}
/>
<TextField
label={t("title")}
placeholder={t("quick_chat")}
data-testid="event-type-quick-chat"
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
{urlPrefix && urlPrefix.length >= 21 ? (
<div>
<TextField
label={`${t("url")}: ${urlPrefix}`}
required
addOnLeading={
<Tooltip content={!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")}>
<span className="max-w-24 md:max-w-56">
/{!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
) : (
<div>
<TextField
label={t("url")}
required
addOnLeading={
<Tooltip
content={`${urlPrefix}/${
!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")
}/`}>
<span className="max-w-24 md:max-w-56">
{urlPrefix}/{!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
)}
<div className="mb-4">
<label htmlFor="schedulingType" className="text-default block text-sm font-bold">
{t("assignment")}
</label>
{formState.errors.schedulingType && (
<Alert className="mt-1" severity="error" message={formState.errors.schedulingType.message} />
)}
<RadioArea.Group
onValueChange={(val: SchedulingType) => {
setValue("schedulingType", val);
}}
className={classNames("mt-1 flex gap-4", isTeamAdminOrOwner && "flex-col")}>
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.COLLECTIVE}
className={classNames("w-full text-sm", !isTeamAdminOrOwner && "w-1/2")}
classNames={{ container: classNames(isTeamAdminOrOwner && "w-full") }}>
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.ROUND_ROBIN}
className={classNames("text-sm", !isTeamAdminOrOwner && "w-1/2")}
classNames={{ container: classNames(isTeamAdminOrOwner && "w-full") }}>
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
{isTeamAdminOrOwner && (
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.MANAGED}
className={classNames("text-sm", !isTeamAdminOrOwner && "w-1/2")}
classNames={{ container: classNames(isTeamAdminOrOwner && "w-full") }}
data-testid="managed-event-type">
<strong className="mb-1 block">{t("managed_event")}</strong>
<p>{t("managed_event_description")}</p>
</RadioArea.Item>
)}
</RadioArea.Group>
</div>
</div>
{SubmitButton(createMutation.isPending)}
</Form>
);
};
@@ -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<z.infer<typeof createEventTypeInput>>({
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 (
<DialogFooter showDivider>
<DialogClose />
<Button type="submit" loading={isPending}>
{t("continue")}
</Button>
</DialogFooter>
);
};
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")}>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-6 pb-11">
{teamId && (
{teamId ? (
<TeamEventTypeForm
isTeamAdminOrOwner={isTeamAdminOrOwner}
teamId={teamId}
SubmitButton={SubmitButton}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
isInfiniteScrollEnabled={isInfiniteScrollEnabled}
/>
) : (
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-6 pb-11">
<TextField
type="hidden"
labelProps={{ style: { display: "none" } }}
{...register("teamId", { valueAsNumber: true })}
value={teamId}
label={t("title")}
placeholder={t("quick_chat")}
data-testid="event-type-quick-chat"
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (form.formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
)}
<TextField
label={t("title")}
placeholder={t("quick_chat")}
data-testid="event-type-quick-chat"
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (form.formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
{urlPrefix && urlPrefix.length >= 21 ? (
<div>
<TextField
label={`${t("url")}: ${urlPrefix}`}
required
addOnLeading={
<Tooltip content={!isManagedEventType ? pageSlug : t("username_placeholder")}>
<span className="max-w-24 md:max-w-56">
/{!isManagedEventType ? pageSlug : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
{urlPrefix && urlPrefix.length >= 21 ? (
<div>
<TextField
label={`${t("url")}: ${urlPrefix}`}
required
addOnLeading={
<Tooltip content={!isManagedEventType ? pageSlug : t("username_placeholder")}>
<span className="max-w-24 md:max-w-56">
/{!isManagedEventType ? pageSlug : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
) : (
<div>
<TextField
label={t("url")}
required
addOnLeading={
<Tooltip
content={`${urlPrefix}/${!isManagedEventType ? pageSlug : t("username_placeholder")}/`}>
<span className="max-w-24 md:max-w-56">
{urlPrefix}/{!isManagedEventType ? pageSlug : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
/>
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
)}
{!teamId && (
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
) : (
<div>
<TextField
label={t("url")}
required
addOnLeading={
<Tooltip
content={`${urlPrefix}/${
!isManagedEventType ? pageSlug : t("username_placeholder")
}/`}>
<span className="max-w-24 md:max-w-56">
{urlPrefix}/{!isManagedEventType ? pageSlug : t("username_placeholder")}/
</span>
</Tooltip>
}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
{isManagedEventType && (
<p className="mt-2 text-sm text-gray-600">{t("managed_event_url_clarification")}</p>
)}
</div>
)}
<>
<Editor
getText={() => md.render(form.getValues("description") || "")}
@@ -269,65 +242,10 @@ export default function CreateEventTypeDialog({
/>
</div>
</>
)}
{teamId && (
<div className="mb-4">
<label htmlFor="schedulingType" className="text-default block text-sm font-bold">
{t("assignment")}
</label>
{form.formState.errors.schedulingType && (
<Alert
className="mt-1"
severity="error"
message={form.formState.errors.schedulingType.message}
/>
)}
<RadioArea.Group
onValueChange={(val: SchedulingType) => {
form.setValue("schedulingType", val);
}}
className={classNames("mt-1 flex gap-4", isAdmin && "flex-col")}>
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.COLLECTIVE}
className={classNames("w-full text-sm", !isAdmin && "w-1/2")}
classNames={{ container: classNames(isAdmin && "w-full") }}>
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.ROUND_ROBIN}
className={classNames("text-sm", !isAdmin && "w-1/2")}
classNames={{ container: classNames(isAdmin && "w-full") }}>
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
<>
{isAdmin && (
<RadioArea.Item
{...register("schedulingType")}
value={SchedulingType.MANAGED}
className={classNames("text-sm", !isAdmin && "w-1/2")}
classNames={{ container: classNames(isAdmin && "w-full") }}
data-testid="managed-event-type">
<strong className="mb-1 block">{t("managed_event")}</strong>
<p>{t("managed_event_description")}</p>
</RadioArea.Item>
)}
</>
</RadioArea.Group>
</div>
)}
</div>
<DialogFooter showDivider>
<DialogClose />
<Button type="submit" loading={createMutation.isPending}>
{t("continue")}
</Button>
</DialogFooter>
</Form>
</div>
{SubmitButton(createMutation.isPending)}
</Form>
)}
</DialogContent>
</Dialog>
);
+72
View File
@@ -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<z.infer<typeof createEventTypeInput>>({
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 };
};
+1 -1
View File
@@ -63,7 +63,7 @@ export function WizardLayout({
</div>
{isOptionalCallback && (
<div className="mt-4 flex justify-center">
<Button color="minimal" onClick={isOptionalCallback}>
<Button data-testid="handle-later-button" color="minimal" onClick={isOptionalCallback}>
{t("ill_do_this_later")}
</Button>
</div>