From fced9a635574fcc1ffd258718ab84fe2843abaa6 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:03:18 +0530 Subject: [PATCH] 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 Co-authored-by: Hariom Balhara Co-authored-by: Keith Williams Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> --- .../settings/teams/[id]/event-type/page.tsx | 11 + .../pages/settings/teams/[id]/event-type.tsx | 86 +++++ .../settings/teams/[id]/onboard-members.tsx | 2 +- apps/web/pages/settings/teams/new/index.tsx | 2 +- .../web/playwright/managed-event-types.e2e.ts | 11 +- .../organization/team-management.e2e.ts | 13 +- apps/web/playwright/teams.e2e.ts | 8 +- .../ee/teams/components/AddNewTeamMembers.tsx | 4 +- .../ee/teams/components/TeamEventTypeForm.tsx | 162 ++++++++++ .../components/CreateEventTypeDialog.tsx | 300 +++++++----------- packages/lib/hooks/useCreateEventType.ts | 72 +++++ packages/ui/layouts/WizardLayout.tsx | 2 +- 12 files changed, 469 insertions(+), 204 deletions(-) create mode 100644 apps/web/app/future/settings/teams/[id]/event-type/page.tsx create mode 100644 apps/web/pages/settings/teams/[id]/event-type.tsx create mode 100644 packages/features/ee/teams/components/TeamEventTypeForm.tsx create mode 100644 packages/lib/hooks/useCreateEventType.ts 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 ( +
{ + createMutation.mutate(values); + }}> +
+ + { + form.setValue("title", e?.target.value); + if (formState.touchedFields["slug"] === undefined) { + form.setValue("slug", slugify(e?.target.value)); + } + }} + /> + {urlPrefix && urlPrefix.length >= 21 ? ( +
+ + + /{!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")}/ + + + } + {...register("slug")} + onChange={(e) => { + form.setValue("slug", slugify(e?.target.value), { shouldTouch: true }); + }} + /> + + {isManagedEventType && ( +

{t("managed_event_url_clarification")}

+ )} +
+ ) : ( +
+ + + {urlPrefix}/{!isManagedEventType ? `team/${team?.slug}` : t("username_placeholder")}/ + + + } + {...register("slug")} + onChange={(e) => { + form.setValue("slug", slugify(e?.target.value), { shouldTouch: true }); + }} + /> + {isManagedEventType && ( +

{t("managed_event_url_clarification")}

+ )} +
+ )} +
+ + {formState.errors.schedulingType && ( + + )} + { + setValue("schedulingType", val); + }} + className={classNames("mt-1 flex gap-4", isTeamAdminOrOwner && "flex-col")}> + + {t("collective")} +

{t("collective_description")}

+
+ + {t("round_robin")} +

{t("round_robin_description")}

+
+ {isTeamAdminOrOwner && ( + + {t("managed_event")} +

{t("managed_event_description")}

+
+ )} +
+
+
+ {SubmitButton(createMutation.isPending)} +
+ ); +}; 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")}> -
{ - createMutation.mutate(values); - }}> -
- {teamId && ( + {teamId ? ( + + ) : ( + { + createMutation.mutate(values); + }}> +
{ + form.setValue("title", e?.target.value); + if (form.formState.touchedFields["slug"] === undefined) { + form.setValue("slug", slugify(e?.target.value)); + } + }} /> - )} - { - form.setValue("title", e?.target.value); - if (form.formState.touchedFields["slug"] === undefined) { - form.setValue("slug", slugify(e?.target.value)); - } - }} - /> - {urlPrefix && urlPrefix.length >= 21 ? ( -
- - - /{!isManagedEventType ? pageSlug : t("username_placeholder")}/ - - - } - {...register("slug")} - onChange={(e) => { - form.setValue("slug", slugify(e?.target.value), { shouldTouch: true }); - }} - /> + {urlPrefix && urlPrefix.length >= 21 ? ( +
+ + + /{!isManagedEventType ? pageSlug : t("username_placeholder")}/ + + + } + {...register("slug")} + onChange={(e) => { + form.setValue("slug", slugify(e?.target.value), { shouldTouch: true }); + }} + /> - {isManagedEventType && ( -

{t("managed_event_url_clarification")}

- )} -
- ) : ( -
- - - {urlPrefix}/{!isManagedEventType ? pageSlug : t("username_placeholder")}/ - - - } - {...register("slug")} - /> - {isManagedEventType && ( -

{t("managed_event_url_clarification")}

- )} -
- )} - {!teamId && ( + {isManagedEventType && ( +

{t("managed_event_url_clarification")}

+ )} +
+ ) : ( +
+ + + {urlPrefix}/{!isManagedEventType ? pageSlug : t("username_placeholder")}/ + + + } + {...register("slug")} + onChange={(e) => { + form.setValue("slug", slugify(e?.target.value), { shouldTouch: true }); + }} + /> + {isManagedEventType && ( +

{t("managed_event_url_clarification")}

+ )} +
+ )} <> md.render(form.getValues("description") || "")} @@ -269,65 +242,10 @@ export default function CreateEventTypeDialog({ />
- )} - - {teamId && ( -
- - {form.formState.errors.schedulingType && ( - - )} - { - form.setValue("schedulingType", val); - }} - className={classNames("mt-1 flex gap-4", isAdmin && "flex-col")}> - - {t("collective")} -

{t("collective_description")}

-
- - {t("round_robin")} -

{t("round_robin_description")}

-
- <> - {isAdmin && ( - - {t("managed_event")} -

{t("managed_event_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 && (
-