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 (
@@ -65,13 +74,13 @@ const InputField = forwardRef(function InputF
) : (
-
+
)}
{methods?.formState?.errors[props.name] && (
@@ -112,6 +121,57 @@ export const EmailField = forwardRef(function
return ;
});
+type TextAreaProps = Omit & { name: string };
+
+export const TextArea = forwardRef(function TextAreaInput(props, ref) {
+ return (
+
+ );
+});
+
+type TextAreaFieldProps = {
+ label?: ReactNode;
+} & React.ComponentProps & {
+ labelProps?: React.ComponentProps;
+ };
+
+export const TextAreaField = forwardRef(function TextField(
+ props,
+ ref
+) {
+ const id = useId();
+ const { t } = useLocale();
+ const methods = useFormContext();
+ const {
+ label = t(props.name as string),
+ labelProps,
+ placeholder = t(props.name + "_placeholder") !== props.name + "_placeholder"
+ ? t(props.name + "_placeholder")
+ : "",
+ ...passThrough
+ } = props;
+ return (
+
+ {!!props.name && (
+
+ )}
+
+ {methods?.formState?.errors[props.name] && (
+
+ )}
+
+ );
+});
+
type FormProps = { form: UseFormReturn; handleSubmit: SubmitHandler } & Omit<
JSX.IntrinsicElements["form"],
"onSubmit"
diff --git a/components/team/TeamSettingsRightSidebar.tsx b/components/team/TeamSettingsRightSidebar.tsx
index 1d07cc4ced..f2635c2c96 100644
--- a/components/team/TeamSettingsRightSidebar.tsx
+++ b/components/team/TeamSettingsRightSidebar.tsx
@@ -10,6 +10,7 @@ import { trpc } from "@lib/trpc";
import { Dialog, DialogTrigger } from "@components/Dialog";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
+import CreateEventTypeButton from "@components/eventtype/CreateEventType";
import LinkIconButton from "@components/ui/LinkIconButton";
import { MembershipRole } from ".prisma/client";
@@ -49,6 +50,13 @@ export default function TeamSettingsRightSidebar(props: { team: TeamWithMembers;
return (
+
{/*
["profiles"];
@@ -64,7 +46,7 @@ const CreateFirstEventTypeView = ({ canAddEvents, profiles }: CreateEventTypePro
{t("new_event_type_heading")}
{t("new_event_type_description")}
-
+
);
@@ -332,9 +314,9 @@ const EventTypesPage = () => {
CTA={
query.data &&
query.data.eventTypeGroups.length !== 0 && (
-
)
}>
@@ -385,217 +367,4 @@ const EventTypesPage = () => {
);
};
-const CreateNewEventButton = ({ profiles, canAddEvents }: CreateEventTypeProps) => {
- const router = useRouter();
- const teamId: number | null = Number(router.query.teamId) || null;
- const modalOpen = useToggleQuery("new");
- const { t } = useLocale();
-
- 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");
- },
- });
-
- const slugRef = useRef(null);
-
- return (
-
- );
-};
-
export default EventTypesPage;
diff --git a/pages/integrations/index.tsx b/pages/integrations/index.tsx
index 3b01b369e0..c1fb8d7d15 100644
--- a/pages/integrations/index.tsx
+++ b/pages/integrations/index.tsx
@@ -19,7 +19,7 @@ import Loader from "@components/Loader";
import Shell, { ShellSubHeading } from "@components/Shell";
import { Tooltip } from "@components/Tooltip";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
-import { FieldsetLegend, Form, InputGroupBox, TextField } from "@components/form/fields";
+import { FieldsetLegend, Form, InputGroupBox, TextField, TextArea } from "@components/form/fields";
import { CalendarListContainer } from "@components/integrations/CalendarListContainer";
import ConnectIntegration from "@components/integrations/ConnectIntegrations";
import DisconnectIntegration from "@components/integrations/DisconnectIntegration";
@@ -270,11 +270,11 @@ function WebhookDialogForm(props: {
{useCustomPayloadTemplate && (
-
+ defaultValue={useCustomPayloadTemplate && (defaultValues.payloadTemplate || "")}
+ rows={3}
+ />
)}
diff --git a/pages/settings/teams/[id].tsx b/pages/settings/teams/[id].tsx
index 41975fbfdd..cce417b13e 100644
--- a/pages/settings/teams/[id].tsx
+++ b/pages/settings/teams/[id].tsx
@@ -33,7 +33,7 @@ export function TeamSettingsPage() {
return (