Team Workflows (#7038)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Carina Wollendorfer
2023-02-27 12:54:43 +05:30
committed by GitHub
co-authored by Hariom Balhara CarinaWolli zomars Peer Richelsen
parent c20835a4c8
commit 0ec71e52ef
27 changed files with 1219 additions and 476 deletions
@@ -5,7 +5,6 @@ import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
@@ -14,27 +13,16 @@ import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
Avatar,
Button,
Dialog,
DialogClose,
DialogContent,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Form,
RadioGroup as RadioArea,
showToast,
TextAreaField,
TextField,
} from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
import { DuplicateDialog } from "./DuplicateDialog";
// this describes the uniform data needed to create a new event type on Profile or Team
export interface EventTypeParent {
@@ -44,15 +32,6 @@ export interface EventTypeParent {
image?: string | null;
}
interface CreateEventTypeBtnProps {
// 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[];
}
const locationFormSchema = z.array(
z.object({
locationType: z.string(),
@@ -71,10 +50,7 @@ const querySchema = z.object({
teamId: z.union([z.string().transform((val) => +val), z.number()]).optional(),
title: z.string().optional(),
slug: z.string().optional(),
length: z
.union([z.string().transform((val) => +val), z.number()])
.optional()
.default(15),
length: z.union([z.string().transform((val) => +val), z.number()]).optional(),
description: z.string().optional(),
schedulingType: z.nativeEnum(SchedulingType).optional(),
locations: z
@@ -83,17 +59,19 @@ const querySchema = z.object({
.optional(),
});
const CreateEventTypeDialog = () => {
export default function CreateEventTypeDialog() {
const { t } = useLocale();
const router = useRouter();
const {
data: { teamId, eventPage: pageSlug, ...defaultValues },
data: { teamId, eventPage: pageSlug },
} = useTypedQuery(querySchema);
const form = useForm<z.infer<typeof createEventTypeInput>>({
defaultValues: {
length: 15,
},
resolver: zodResolver(createEventTypeInput),
defaultValues,
});
const { register } = form;
@@ -123,7 +101,7 @@ const CreateEventTypeDialog = () => {
return (
<Dialog
name="new-eventtype"
name="new"
clearQueryParamsOnClose={[
"eventPage",
"teamId",
@@ -249,81 +227,4 @@ const CreateEventTypeDialog = () => {
</DialogContent>
</Dialog>
);
};
export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
const { t } = useLocale();
const router = useRouter();
const hasTeams = !!props.options.find((option) => option.teamId);
// inject selection data into url for correct router history
const openModal = (option: EventTypeParent) => {
const query = {
...router.query,
dialog: "new-eventtype",
eventPage: option.slug,
teamId: option.teamId,
};
if (!option.teamId) {
delete query.teamId;
}
router.push(
{
pathname: router.pathname,
query,
},
undefined,
{ shallow: true }
);
};
return (
<>
{!hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={FiPlus}
variant="fab"
disabled={!props.canAddEvents}>
{t("new")}
</Button>
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button variant="fab" StartIcon={FiPlus}>
{t("new")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={14} align="end">
<DropdownMenuLabel>
<div className="max-w-48">{t("new_event_subtitle")}</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{props.options.map((option) => (
<DropdownMenuItem key={option.slug}>
<DropdownItem
type="button"
StartIcon={(props: any) => (
<Avatar
alt={option.name || ""}
imageSrc={option.image || `${WEBAPP_URL}/${option.slug}/avatar.png`} // if no image, use default avatar
size="sm"
{...props}
/>
)}
onClick={() => openModal(option)}>
<span>{option.name ? option.name : option.slug}</span>
</DropdownItem>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</Dropdown>
)}
{/* Dialog for duplicate event type */}
{router.query.dialog === "duplicate-event-type" && <DuplicateDialog />}
{router.query.dialog === "new-eventtype" && <CreateEventTypeDialog />}
</>
);
}
@@ -1,4 +1,5 @@
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -44,6 +45,16 @@ const DuplicateDialog = () => {
});
const { register } = form;
useEffect(() => {
if (router.query.dialog === "duplicate") {
form.setValue("id", Number(router.query.id as string) || -1);
form.setValue("title", (router.query.title as string) || "");
form.setValue("slug", t("event_type_duplicate_copy_text", { slug: router.query.slug as string }));
form.setValue("description", (router.query.description as string) || "");
form.setValue("length", Number(router.query.length) || 30);
}
}, [router.query.dialog]);
const duplicateMutation = trpc.viewer.eventTypes.duplicate.useMutation({
onSuccess: async ({ eventType }) => {
await router.replace("/event-types/" + eventType.id);
@@ -69,7 +80,7 @@ const DuplicateDialog = () => {
return (
<Dialog
name="duplicate-event-type"
name="duplicate"
clearQueryParamsOnClose={["description", "title", "length", "slug", "name", "id", "pageSlug"]}>
<DialogContent type="creation" className="overflow-y-auto" title="Duplicate Event Type">
<Form
@@ -2,6 +2,6 @@ import dynamic from "next/dynamic";
export { default as CheckedTeamSelect } from "./CheckedTeamSelect";
export { default as CustomInputItem } from "./CustomInputItem";
export { default as CreateEventTypeButton } from "./CreateEventTypeButton";
export { default as CreateEventTypeDialog } from "./CreateEventTypeDialog";
export { default as EventTypeDescription } from "./EventTypeDescription";
export const EventTypeDescriptionLazy = dynamic(() => import("./EventTypeDescription"));