From 70683a89b9fcf86f4ce140ca121cf6f96b834c71 Mon Sep 17 00:00:00 2001
From: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
Date: Wed, 12 Jan 2022 01:29:20 -0800
Subject: [PATCH] Added "New Event Type" button on Team settings (#1411)
- Moved CreateNewEventButton in pages/event-types/index to dedicated component as this is used in two places now.
- Implemented CreateEventType button on Team settings screen and replaced old markup in on event types page with new component.
- Upgrade vanilla JS inputs to library primitives.
- Created TextArea & TextAreaField components in components/form.
- [Bugfix] Changed back button behavior in Shell to have a specified back path as CreateEventType's modal interfered with the router.goBack behavior.
- Ensure modal data is preserved in URL params for router accuracy and removed on exit.
---
components/Shell.tsx | 9 +-
components/eventtype/CreateEventType.tsx | 242 +++++++++++++++++
components/form/fields.tsx | 68 ++++-
components/team/TeamSettingsRightSidebar.tsx | 8 +
ee/pages/settings/teams/[id]/availability.tsx | 2 +-
pages/event-types/index.tsx | 245 +-----------------
pages/integrations/index.tsx | 10 +-
pages/settings/teams/[id].tsx | 2 +-
8 files changed, 334 insertions(+), 252 deletions(-)
create mode 100644 components/eventtype/CreateEventType.tsx
diff --git a/components/Shell.tsx b/components/Shell.tsx
index a102128770..3d7f2e47be 100644
--- a/components/Shell.tsx
+++ b/components/Shell.tsx
@@ -124,7 +124,7 @@ export default function Shell(props: {
children: ReactNode;
CTA?: ReactNode;
HeadingLeftIcon?: ReactNode;
- showBackButton?: boolean;
+ backPath?: string; // renders back button to specified path
// use when content needs to expand with flex
flexChildrenContainer?: boolean;
}) {
@@ -289,9 +289,12 @@ export default function Shell(props: {
props.flexChildrenContainer && "flex flex-col flex-1",
"py-8"
)}>
- {props.showBackButton && (
+ {!!props.backPath && (
-
diff --git a/components/eventtype/CreateEventType.tsx b/components/eventtype/CreateEventType.tsx
new file mode 100644
index 0000000000..002231aac0
--- /dev/null
+++ b/components/eventtype/CreateEventType.tsx
@@ -0,0 +1,242 @@
+import { ChevronDownIcon, PlusIcon } from "@heroicons/react/solid";
+import { SchedulingType } from "@prisma/client";
+import { useRouter } from "next/router";
+import React, { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { useMutation } from "react-query";
+
+import { HttpError } from "@lib/core/http/error";
+import { useLocale } from "@lib/hooks/useLocale";
+import { useToggleQuery } from "@lib/hooks/useToggleQuery";
+import createEventType from "@lib/mutations/event-types/create-event-type";
+import showToast from "@lib/notification";
+import { CreateEventType } from "@lib/types/event-type";
+
+import { Dialog, DialogClose, DialogContent } from "@components/Dialog";
+import { TextField, InputLeading, TextAreaField, Form } from "@components/form/fields";
+import Avatar from "@components/ui/Avatar";
+import { Button } from "@components/ui/Button";
+import Dropdown, {
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@components/ui/Dropdown";
+import * as RadioArea from "@components/ui/form/radio-area";
+
+// this describes the uniform data needed to create a new event type on Profile or Team
+interface EventTypeParent {
+ teamId: number | null | undefined; // if undefined, then it's a profile
+ name?: string | null;
+ slug?: string | null;
+ image?: string | null;
+}
+
+interface Props {
+ // set true for use on the team settings page
+ canAddEvents: boolean;
+ // set true when in use on the team settings page
+ isIndividualTeam?: boolean;
+ // EventTypeParent can be a profile (as first option) or a team for the rest.
+ options: EventTypeParent[];
+}
+
+export default function CreateEventTypeButton(props: Props) {
+ const { t } = useLocale();
+ const router = useRouter();
+ const modalOpen = useToggleQuery("new");
+
+ const form = useForm({
+ defaultValues: { length: 15 },
+ });
+ const { setValue, watch, register } = form;
+
+ useEffect(() => {
+ const subscription = watch((value, { name, type }) => {
+ if (name === "title" && type === "change") {
+ if (value.title) setValue("slug", value.title.replace(/\s+/g, "-").toLowerCase());
+ else setValue("slug", "");
+ }
+ });
+ return () => subscription.unsubscribe();
+ }, [watch, setValue]);
+
+ // URL encoded params
+ const teamId: number | null = Number(router.query.teamId) || null;
+ const pageSlug = router.query.eventPage || props.options[0].slug;
+
+ const hasTeams = !!props.options.find((option) => option.teamId);
+
+ const createMutation = useMutation(createEventType, {
+ onSuccess: async ({ eventType }) => {
+ await router.push("/event-types/" + eventType.id);
+ showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success");
+ },
+ onError: (err: HttpError) => {
+ const message = `${err.statusCode}: ${err.message}`;
+ showToast(message, "error");
+ },
+ });
+
+ // inject selection data into url for correct router history
+ const openModal = (option: EventTypeParent) => {
+ router.push({
+ pathname: router.pathname,
+ query: {
+ ...router.query,
+ new: "1",
+ eventPage: option.slug,
+ ...(option.teamId
+ ? {
+ teamId: option.teamId,
+ }
+ : {}),
+ },
+ });
+ };
+
+ // remove url params after close modal to reset state
+ const closeModal = () => {
+ router.replace({
+ pathname: router.pathname,
+ query: { id: router.query.id },
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/components/form/fields.tsx b/components/form/fields.tsx
index 96e0fe4fa6..ad81c400e7 100644
--- a/components/form/fields.tsx
+++ b/components/form/fields.tsx
@@ -10,6 +10,7 @@ import showToast from "@lib/notification";
import { Alert } from "@components/ui/Alert";
type InputProps = Omit & { name: string };
+
export const Input = forwardRef(function Input(props, ref) {
return (
+ {props.children}
+
+ );
+}
+
type InputFieldProps = {
label?: ReactNode;
addOnLeading?: ReactNode;
@@ -50,7 +59,7 @@ const InputField = forwardRef(function InputF
: "",
className,
addOnLeading,
- ...passThroughToInput
+ ...passThrough
} = props;
return (