Event type dropdown (#2081)
This commit is contained in:
parent
c9484172a4
commit
6e4f8e67b6
14 changed files with 1350 additions and 1062 deletions
|
@ -59,12 +59,15 @@ type DialogContentProps = React.ComponentProps<typeof DialogPrimitive["Content"]
|
||||||
|
|
||||||
export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
||||||
({ children, ...props }, forwardedRef) => (
|
({ children, ...props }, forwardedRef) => (
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Portal>
|
||||||
{...props}
|
<DialogPrimitive.Overlay className="fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
className="fixed left-1/2 top-1/2 z-50 min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded bg-white p-6 text-left shadow-xl sm:w-full sm:max-w-lg sm:align-middle"
|
<DialogPrimitive.Content
|
||||||
ref={forwardedRef}>
|
{...props}
|
||||||
{children}
|
className="fixed left-1/2 top-1/2 z-[9999999999] min-w-[360px] -translate-x-1/2 -translate-y-1/2 rounded bg-white p-6 text-left shadow-xl sm:w-full sm:max-w-lg sm:align-middle"
|
||||||
</DialogPrimitive.Content>
|
ref={forwardedRef}>
|
||||||
|
{children}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,12 @@ import { useRouter } from "next/router";
|
||||||
import React, { ReactNode, useEffect, useState } from "react";
|
import React, { ReactNode, useEffect, useState } from "react";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
|
import Dropdown, {
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@calcom/ui/Dropdown";
|
||||||
import LicenseBanner from "@ee/components/LicenseBanner";
|
import LicenseBanner from "@ee/components/LicenseBanner";
|
||||||
import TrialBanner from "@ee/components/TrialBanner";
|
import TrialBanner from "@ee/components/TrialBanner";
|
||||||
import IntercomMenuItem from "@ee/lib/intercom/IntercomMenuItem";
|
import IntercomMenuItem from "@ee/lib/intercom/IntercomMenuItem";
|
||||||
|
@ -32,12 +38,6 @@ import { trpc } from "@lib/trpc";
|
||||||
import CustomBranding from "@components/CustomBranding";
|
import CustomBranding from "@components/CustomBranding";
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import { HeadSeo } from "@components/seo/head-seo";
|
||||||
import Dropdown, {
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@components/ui/Dropdown";
|
|
||||||
|
|
||||||
import pkg from "../package.json";
|
import pkg from "../package.json";
|
||||||
import { useViewerI18n } from "./I18nLanguageHandler";
|
import { useViewerI18n } from "./I18nLanguageHandler";
|
||||||
|
|
|
@ -7,6 +7,13 @@ import { useForm } from "react-hook-form";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
|
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
|
||||||
|
import Dropdown, {
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
|
@ -19,17 +26,10 @@ import { Form, InputLeading, TextAreaField, TextField } from "@components/form/f
|
||||||
import { Alert } from "@components/ui/Alert";
|
import { Alert } from "@components/ui/Alert";
|
||||||
import Avatar from "@components/ui/Avatar";
|
import Avatar from "@components/ui/Avatar";
|
||||||
import { Button } from "@components/ui/Button";
|
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";
|
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
|
// this describes the uniform data needed to create a new event type on Profile or Team
|
||||||
interface EventTypeParent {
|
export interface EventTypeParent {
|
||||||
teamId: number | null | undefined; // if undefined, then it's a profile
|
teamId: number | null | undefined; // if undefined, then it's a profile
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
slug?: string | null;
|
slug?: string | null;
|
||||||
|
@ -56,12 +56,23 @@ export default function CreateEventTypeButton(props: Props) {
|
||||||
: undefined;
|
: undefined;
|
||||||
const pageSlug = router.query.eventPage || props.options[0].slug;
|
const pageSlug = router.query.eventPage || props.options[0].slug;
|
||||||
const hasTeams = !!props.options.find((option) => option.teamId);
|
const hasTeams = !!props.options.find((option) => option.teamId);
|
||||||
|
const title: string =
|
||||||
|
typeof router.query.title === "string" && router.query.title ? router.query.title : "";
|
||||||
|
const length: number =
|
||||||
|
typeof router.query.length === "string" && router.query.length ? parseInt(router.query.length) : 15;
|
||||||
|
const description: string =
|
||||||
|
typeof router.query.description === "string" && router.query.description ? router.query.description : "";
|
||||||
|
const slug: string = typeof router.query.slug === "string" && router.query.slug ? router.query.slug : "";
|
||||||
|
const type: string = typeof router.query.type == "string" && router.query.type ? router.query.type : "";
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof createEventTypeInput>>({
|
const form = useForm<z.infer<typeof createEventTypeInput>>({
|
||||||
resolver: zodResolver(createEventTypeInput),
|
resolver: zodResolver(createEventTypeInput),
|
||||||
defaultValues: { length: 15 },
|
|
||||||
});
|
});
|
||||||
const { setValue, watch, register } = form;
|
const { setValue, watch, register } = form;
|
||||||
|
setValue("title", title);
|
||||||
|
setValue("length", length);
|
||||||
|
setValue("description", description);
|
||||||
|
setValue("slug", slug);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = watch((value, { name, type }) => {
|
const subscription = watch((value, { name, type }) => {
|
||||||
|
@ -113,7 +124,9 @@ export default function CreateEventTypeButton(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog name="new-eventtype" clearQueryParamsOnClose={["eventPage", "teamId"]}>
|
<Dialog
|
||||||
|
name="new-eventtype"
|
||||||
|
clearQueryParamsOnClose={["eventPage", "teamId", "type", "description", "title", "length", "slug"]}>
|
||||||
{!hasTeams || props.isIndividualTeam ? (
|
{!hasTeams || props.isIndividualTeam ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => openModal(props.options[0])}
|
onClick={() => openModal(props.options[0])}
|
||||||
|
@ -196,7 +209,6 @@ export default function CreateEventTypeButton(props: Props) {
|
||||||
required
|
required
|
||||||
min="10"
|
min="10"
|
||||||
placeholder="15"
|
placeholder="15"
|
||||||
defaultValue={15}
|
|
||||||
label={t("length")}
|
label={t("length")}
|
||||||
className="pr-20"
|
className="pr-20"
|
||||||
{...register("length", { valueAsNumber: true })}
|
{...register("length", { valueAsNumber: true })}
|
||||||
|
@ -222,11 +234,17 @@ export default function CreateEventTypeButton(props: Props) {
|
||||||
{...register("schedulingType")}
|
{...register("schedulingType")}
|
||||||
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
|
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
|
||||||
className="relative mt-1 flex space-x-6 rounded-sm shadow-sm rtl:space-x-reverse">
|
className="relative mt-1 flex space-x-6 rounded-sm shadow-sm rtl:space-x-reverse">
|
||||||
<RadioArea.Item value={SchedulingType.COLLECTIVE} className="w-1/2 text-sm">
|
<RadioArea.Item
|
||||||
|
value={SchedulingType.COLLECTIVE}
|
||||||
|
defaultChecked={type === SchedulingType.COLLECTIVE}
|
||||||
|
className="w-1/2 text-sm">
|
||||||
<strong className="mb-1 block">{t("collective")}</strong>
|
<strong className="mb-1 block">{t("collective")}</strong>
|
||||||
<p>{t("collective_description")}</p>
|
<p>{t("collective_description")}</p>
|
||||||
</RadioArea.Item>
|
</RadioArea.Item>
|
||||||
<RadioArea.Item value={SchedulingType.ROUND_ROBIN} className="w-1/2 text-sm">
|
<RadioArea.Item
|
||||||
|
value={SchedulingType.ROUND_ROBIN}
|
||||||
|
defaultChecked={type === SchedulingType.ROUND_ROBIN}
|
||||||
|
className="w-1/2 text-sm">
|
||||||
<strong className="mb-1 block">{t("round_robin")}</strong>
|
<strong className="mb-1 block">{t("round_robin")}</strong>
|
||||||
<p>{t("round_robin_description")}</p>
|
<p>{t("round_robin_description")}</p>
|
||||||
</RadioArea.Item>
|
</RadioArea.Item>
|
||||||
|
|
|
@ -3,6 +3,12 @@ import { ClockIcon, ExternalLinkIcon, DotsHorizontalIcon } from "@heroicons/reac
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import Dropdown, {
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@calcom/ui/Dropdown";
|
||||||
import TeamAvailabilityModal from "@ee/components/team/availability/TeamAvailabilityModal";
|
import TeamAvailabilityModal from "@ee/components/team/availability/TeamAvailabilityModal";
|
||||||
|
|
||||||
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
|
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
|
||||||
|
@ -17,12 +23,6 @@ import Avatar from "@components/ui/Avatar";
|
||||||
import Button from "@components/ui/Button";
|
import Button from "@components/ui/Button";
|
||||||
import ModalContainer from "@components/ui/ModalContainer";
|
import ModalContainer from "@components/ui/ModalContainer";
|
||||||
|
|
||||||
import Dropdown, {
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "../ui/Dropdown";
|
|
||||||
import MemberChangeRoleModal from "./MemberChangeRoleModal";
|
import MemberChangeRoleModal from "./MemberChangeRoleModal";
|
||||||
import TeamPill, { TeamRole } from "./TeamPill";
|
import TeamPill, { TeamRole } from "./TeamPill";
|
||||||
import { MembershipRole } from ".prisma/client";
|
import { MembershipRole } from ".prisma/client";
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { ExternalLinkIcon, TrashIcon, LogoutIcon, PencilIcon } from "@heroicons/
|
||||||
import { LinkIcon, DotsHorizontalIcon } from "@heroicons/react/solid";
|
import { LinkIcon, DotsHorizontalIcon } from "@heroicons/react/solid";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import Dropdown, {
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
} from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
|
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
|
@ -13,12 +20,6 @@ import { Tooltip } from "@components/Tooltip";
|
||||||
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
||||||
import Avatar from "@components/ui/Avatar";
|
import Avatar from "@components/ui/Avatar";
|
||||||
import Button from "@components/ui/Button";
|
import Button from "@components/ui/Button";
|
||||||
import Dropdown, {
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
} from "@components/ui/Dropdown";
|
|
||||||
|
|
||||||
import { TeamRole } from "./TeamPill";
|
import { TeamRole } from "./TeamPill";
|
||||||
import { MembershipRole } from ".prisma/client";
|
import { MembershipRole } from ".prisma/client";
|
||||||
|
@ -126,8 +127,9 @@ export default function TeamListItem(props: Props) {
|
||||||
<a>
|
<a>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
size="lg"
|
||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full font-normal"
|
className="w-full rounded-none font-normal"
|
||||||
StartIcon={PencilIcon}>
|
StartIcon={PencilIcon}>
|
||||||
{t("edit_team")}
|
{t("edit_team")}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -135,14 +137,14 @@ export default function TeamListItem(props: Props) {
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{isAdmin && <DropdownMenuSeparator className="h-px bg-gray-200" />}
|
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/team/${team.slug}`} passHref={true}>
|
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/team/${team.slug}`} passHref={true}>
|
||||||
<a target="_blank">
|
<a target="_blank">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
size="lg"
|
||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full font-normal"
|
className="w-full rounded-none font-normal"
|
||||||
StartIcon={ExternalLinkIcon}>
|
StartIcon={ExternalLinkIcon}>
|
||||||
{" "}
|
{" "}
|
||||||
{t("preview_team")}
|
{t("preview_team")}
|
||||||
|
@ -160,8 +162,9 @@ export default function TeamListItem(props: Props) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
color="warn"
|
color="warn"
|
||||||
|
size="lg"
|
||||||
StartIcon={TrashIcon}
|
StartIcon={TrashIcon}
|
||||||
className="w-full font-normal">
|
className="w-full rounded-none font-normal">
|
||||||
{t("disband_team")}
|
{t("disband_team")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
@ -183,8 +186,9 @@ export default function TeamListItem(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="warn"
|
color="warn"
|
||||||
|
size="lg"
|
||||||
StartIcon={LogoutIcon}
|
StartIcon={LogoutIcon}
|
||||||
className="w-full"
|
className="w-full rounded-none"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import { DotsHorizontalIcon } from "@heroicons/react/solid";
|
import { DotsHorizontalIcon } from "@heroicons/react/solid";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
|
||||||
|
import Dropdown, { DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||||
|
|
||||||
import Dropdown, {
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@components/ui/Dropdown";
|
|
||||||
|
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
|
||||||
export type ActionType = {
|
export type ActionType = {
|
||||||
|
@ -52,8 +48,9 @@ const TableActions: FC<Props> = ({ actions }) => {
|
||||||
<DropdownMenuItem key={action.id}>
|
<DropdownMenuItem key={action.id}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
size="lg"
|
||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full font-normal"
|
className="w-full rounded-none font-normal"
|
||||||
href={action.href}
|
href={action.href}
|
||||||
StartIcon={action.icon}
|
StartIcon={action.icon}
|
||||||
onClick={action.onClick}>
|
onClick={action.onClick}>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { ChatAltIcon } from "@heroicons/react/solid";
|
import { ChatAltIcon } from "@heroicons/react/solid";
|
||||||
import { useIntercom } from "react-use-intercom";
|
import { useIntercom } from "react-use-intercom";
|
||||||
|
|
||||||
|
import { DropdownMenuItem } from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
|
|
||||||
import { DropdownMenuItem } from "@components/ui/Dropdown";
|
|
||||||
|
|
||||||
export default function IntercomMenuItem() {
|
export default function IntercomMenuItem() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { boot, show } = useIntercom();
|
const { boot, show } = useIntercom();
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { ChatAltIcon } from "@heroicons/react/solid";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { DropdownMenuItem } from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
|
|
||||||
import { DropdownMenuItem } from "@components/ui/Dropdown";
|
|
||||||
|
|
||||||
const ZENDESK_KEY = process.env.NEXT_PUBLIC_ZENDESK_KEY;
|
const ZENDESK_KEY = process.env.NEXT_PUBLIC_ZENDESK_KEY;
|
||||||
|
|
||||||
export default function ZendeskMenuItem() {
|
export default function ZendeskMenuItem() {
|
||||||
|
|
|
@ -3,72 +3,72 @@ import {
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
DotsHorizontalIcon,
|
DotsHorizontalIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
|
DuplicateIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
UsersIcon,
|
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
|
TrashIcon,
|
||||||
|
PencilIcon,
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
|
import { UsersIcon } from "@heroicons/react/solid";
|
||||||
import { Trans } from "next-i18next";
|
import { Trans } from "next-i18next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import React, { Fragment, useEffect, useState } from "react";
|
import React, { Fragment, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@calcom/ui";
|
import { Button } from "@calcom/ui";
|
||||||
|
import Dropdown, {
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
} from "@calcom/ui/Dropdown";
|
||||||
|
|
||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
|
import { HttpError } from "@lib/core/http/error";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
import showToast from "@lib/notification";
|
import showToast from "@lib/notification";
|
||||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||||
|
|
||||||
|
import { Dialog, DialogTrigger } from "@components/Dialog";
|
||||||
import Shell from "@components/Shell";
|
import Shell from "@components/Shell";
|
||||||
import { Tooltip } from "@components/Tooltip";
|
import { Tooltip } from "@components/Tooltip";
|
||||||
|
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
|
||||||
import CreateEventTypeButton from "@components/eventtype/CreateEventType";
|
import CreateEventTypeButton from "@components/eventtype/CreateEventType";
|
||||||
import EventTypeDescription from "@components/eventtype/EventTypeDescription";
|
import EventTypeDescription from "@components/eventtype/EventTypeDescription";
|
||||||
import { Alert } from "@components/ui/Alert";
|
import { Alert } from "@components/ui/Alert";
|
||||||
import Avatar from "@components/ui/Avatar";
|
import Avatar from "@components/ui/Avatar";
|
||||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
import AvatarGroup from "@components/ui/AvatarGroup";
|
||||||
import Badge from "@components/ui/Badge";
|
import Badge from "@components/ui/Badge";
|
||||||
import Dropdown, {
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@components/ui/Dropdown";
|
|
||||||
import UserCalendarIllustration from "@components/ui/svg/UserCalendarIllustration";
|
import UserCalendarIllustration from "@components/ui/svg/UserCalendarIllustration";
|
||||||
|
|
||||||
type Profiles = inferQueryOutput<"viewer.eventTypes">["profiles"];
|
type Profiles = inferQueryOutput<"viewer.eventTypes">["profiles"];
|
||||||
type EventTypeGroups = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"];
|
|
||||||
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
|
|
||||||
|
|
||||||
interface CreateEventTypeProps {
|
interface CreateEventTypeProps {
|
||||||
canAddEvents: boolean;
|
canAddEvents: boolean;
|
||||||
profiles: Profiles;
|
profiles: Profiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateFirstEventTypeView = ({ canAddEvents, profiles }: CreateEventTypeProps) => {
|
type EventTypeGroups = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"];
|
||||||
const { t } = useLocale();
|
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
|
||||||
|
interface EventTypeListHeadingProps {
|
||||||
return (
|
profile: EventTypeGroupProfile;
|
||||||
<div className="md:py-20">
|
membershipCount: number;
|
||||||
<UserCalendarIllustration />
|
}
|
||||||
<div className="mx-auto block text-center md:max-w-screen-sm">
|
|
||||||
<h3 className="mt-2 text-xl font-bold text-neutral-900">{t("new_event_type_heading")}</h3>
|
|
||||||
<p className="text-md mt-1 mb-2 text-neutral-600">{t("new_event_type_description")}</p>
|
|
||||||
<CreateEventTypeButton canAddEvents={canAddEvents} options={profiles} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type EventTypeGroup = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"][number];
|
type EventTypeGroup = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"][number];
|
||||||
type EventType = EventTypeGroup["eventTypes"][number];
|
type EventType = EventTypeGroup["eventTypes"][number];
|
||||||
interface EventTypeListProps {
|
interface EventTypeListProps {
|
||||||
profile: { slug: string | null };
|
group: EventTypeGroup;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
types: EventType[];
|
types: EventType[];
|
||||||
}
|
}
|
||||||
const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.Element => {
|
|
||||||
|
export const EventTypeList = ({ group, readOnly, types }: EventTypeListProps): JSX.Element => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const mutation = trpc.useMutation("viewer.eventTypeOrder", {
|
const mutation = trpc.useMutation("viewer.eventTypeOrder", {
|
||||||
|
@ -99,6 +99,50 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteEventTypeHandler(id: number) {
|
||||||
|
const payload = { id };
|
||||||
|
deleteMutation.mutate(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inject selection data into url for correct router history
|
||||||
|
const openModal = (group: EventTypeGroup, type: EventType) => {
|
||||||
|
const query = {
|
||||||
|
...router.query,
|
||||||
|
dialog: "new-eventtype",
|
||||||
|
eventPage: group.profile.slug,
|
||||||
|
title: type.title,
|
||||||
|
slug: type.slug,
|
||||||
|
description: type.description,
|
||||||
|
length: type.length,
|
||||||
|
type: type.schedulingType,
|
||||||
|
teamId: group.teamId,
|
||||||
|
};
|
||||||
|
if (!group.teamId) {
|
||||||
|
delete query.teamId;
|
||||||
|
}
|
||||||
|
router.push(
|
||||||
|
{
|
||||||
|
pathname: router.pathname,
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shallow: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMutation = trpc.useMutation("viewer.eventTypes.delete", {
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.invalidateQueries(["viewer.eventTypes"]);
|
||||||
|
showToast(t("event_type_deleted_successfully"), "success");
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
const message = `${err.statusCode}: ${err.message}`;
|
||||||
|
showToast(message, "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const [isNativeShare, setNativeShare] = useState(true);
|
const [isNativeShare, setNativeShare] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -143,8 +187,16 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
className="flex-grow truncate text-sm"
|
className="flex-grow truncate text-sm"
|
||||||
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
||||||
<div>
|
<div>
|
||||||
<span className="truncate font-medium text-neutral-900">{type.title} </span>
|
<span
|
||||||
<small className="hidden text-neutral-500 sm:inline">{`/${profile.slug}/${type.slug}`}</small>
|
className="truncate font-medium text-neutral-900"
|
||||||
|
data-testid={"event-type-title-" + type.id}>
|
||||||
|
{type.title}
|
||||||
|
</span>
|
||||||
|
<small
|
||||||
|
className="hidden text-neutral-500 sm:inline"
|
||||||
|
data-testid={
|
||||||
|
"event-type-slug-" + type.id
|
||||||
|
}>{`/${group.profile.slug}/${type.slug}`}</small>
|
||||||
{type.hidden && (
|
{type.hidden && (
|
||||||
<span className="rtl:mr-2inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800 ltr:ml-2">
|
<span className="rtl:mr-2inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800 ltr:ml-2">
|
||||||
{t("hidden")}
|
{t("hidden")}
|
||||||
|
@ -161,7 +213,7 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
|
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
|
||||||
<div className="flex items-center space-x-2 overflow-hidden rtl:space-x-reverse">
|
<div className="flex justify-between rtl:space-x-reverse">
|
||||||
{type.users?.length > 1 && (
|
{type.users?.length > 1 && (
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
border="border-2 border-white"
|
border="border-2 border-white"
|
||||||
|
@ -175,7 +227,7 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
)}
|
)}
|
||||||
<Tooltip content={t("preview")}>
|
<Tooltip content={t("preview")}>
|
||||||
<a
|
<a
|
||||||
href={`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`}
|
href={`${process.env.NEXT_PUBLIC_APP_URL}/${group.profile.slug}/${type.slug}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="btn-icon appearance-none">
|
className="btn-icon appearance-none">
|
||||||
|
@ -188,13 +240,74 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showToast(t("link_copied"), "success");
|
showToast(t("link_copied"), "success");
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`
|
`${process.env.NEXT_PUBLIC_APP_URL}/${group.profile.slug}/${type.slug}`
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="btn-icon">
|
className="btn-icon">
|
||||||
<LinkIcon className="h-5 w-5 group-hover:text-black" />
|
<LinkIcon className="h-5 w-5 group-hover:text-black" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
className="h-[38px] w-[38px] cursor-pointer rounded-sm border border-transparent text-neutral-500 hover:border-gray-300 hover:text-neutral-900"
|
||||||
|
data-testid={"event-type-options-" + type.id}>
|
||||||
|
<DotsHorizontalIcon className="h-5 w-5 group-hover:text-gray-800" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Link href={"/event-types/" + type.id} passHref={true}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="lg"
|
||||||
|
color="minimal"
|
||||||
|
className="w-full rounded-none font-normal"
|
||||||
|
StartIcon={PencilIcon}>
|
||||||
|
{" "}
|
||||||
|
{t("edit")}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="minimal"
|
||||||
|
size="lg"
|
||||||
|
className="w-full rounded-none font-normal"
|
||||||
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
|
StartIcon={DuplicateIcon}
|
||||||
|
onClick={() => openModal(group, type)}>
|
||||||
|
{t("duplicate")}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
color="warn"
|
||||||
|
size="lg"
|
||||||
|
StartIcon={TrashIcon}
|
||||||
|
className="w-full rounded-none font-normal">
|
||||||
|
{t("delete")}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<ConfirmationDialogContent
|
||||||
|
variety="danger"
|
||||||
|
title={t("delete_event_type")}
|
||||||
|
confirmBtnText={t("confirm_delete_event_type")}
|
||||||
|
onConfirm={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteEventTypeHandler(type.id);
|
||||||
|
}}>
|
||||||
|
{t("delete_event_type_description")}
|
||||||
|
</ConfirmationDialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,9 +318,13 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent portalled>
|
<DropdownMenuContent portalled>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`}>
|
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/${group.profile.slug}/${type.slug}`}>
|
||||||
<a target="_blank">
|
<a target="_blank">
|
||||||
<Button color="minimal" StartIcon={ExternalLinkIcon} className="w-full font-normal">
|
<Button
|
||||||
|
color="minimal"
|
||||||
|
size="lg"
|
||||||
|
StartIcon={ExternalLinkIcon}
|
||||||
|
className="w-full rounded-none font-normal">
|
||||||
{t("preview")}
|
{t("preview")}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
@ -217,12 +334,13 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full font-normal"
|
size="lg"
|
||||||
|
className="w-full rounded-none text-left font-normal"
|
||||||
data-testid={"event-type-duplicate-" + type.id}
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
StartIcon={ClipboardCopyIcon}
|
StartIcon={ClipboardCopyIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`
|
`${process.env.NEXT_PUBLIC_APP_URL}/${group.profile.slug}/${type.slug}`
|
||||||
);
|
);
|
||||||
showToast(t("link_copied"), "success");
|
showToast(t("link_copied"), "success");
|
||||||
}}>
|
}}>
|
||||||
|
@ -234,7 +352,8 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full font-normal"
|
size="lg"
|
||||||
|
className="w-full rounded-none font-normal"
|
||||||
data-testid={"event-type-duplicate-" + type.id}
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
StartIcon={UploadIcon}
|
StartIcon={UploadIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -242,7 +361,7 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
.share({
|
.share({
|
||||||
title: t("share"),
|
title: t("share"),
|
||||||
text: t("share_event"),
|
text: t("share_event"),
|
||||||
url: `${process.env.NEXT_PUBLIC_APP_URL}/${profile.slug}/${type.slug}`,
|
url: `${process.env.NEXT_PUBLIC_APP_URL}/${group.profile.slug}/${type.slug}`,
|
||||||
})
|
})
|
||||||
.then(() => showToast(t("link_shared"), "success"))
|
.then(() => showToast(t("link_shared"), "success"))
|
||||||
.catch(() => showToast(t("failed"), "error"));
|
.catch(() => showToast(t("failed"), "error"));
|
||||||
|
@ -251,6 +370,58 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
) : null}
|
) : null}
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Link href={"/event-types/" + type.id} passHref={true}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="lg"
|
||||||
|
color="minimal"
|
||||||
|
className="w-full rounded-none font-normal"
|
||||||
|
StartIcon={PencilIcon}>
|
||||||
|
{" "}
|
||||||
|
{t("edit")}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="minimal"
|
||||||
|
size="lg"
|
||||||
|
className="w-full rounded-none font-normal"
|
||||||
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
|
StartIcon={DuplicateIcon}
|
||||||
|
onClick={() => openModal(group, type)}>
|
||||||
|
{t("duplicate")}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
color="warn"
|
||||||
|
size="lg"
|
||||||
|
StartIcon={TrashIcon}
|
||||||
|
className="w-full rounded-none font-normal">
|
||||||
|
{t("delete")}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<ConfirmationDialogContent
|
||||||
|
variety="danger"
|
||||||
|
title={t("delete_event_type")}
|
||||||
|
confirmBtnText={t("confirm_delete_event_type")}
|
||||||
|
onConfirm={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteEventTypeHandler(type.id);
|
||||||
|
}}>
|
||||||
|
{t("delete_event_type_description")}
|
||||||
|
</ConfirmationDialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -262,10 +433,6 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface EventTypeListHeadingProps {
|
|
||||||
profile: EventTypeGroupProfile;
|
|
||||||
membershipCount: number;
|
|
||||||
}
|
|
||||||
const EventTypeListHeading = ({ profile, membershipCount }: EventTypeListHeadingProps): JSX.Element => (
|
const EventTypeListHeading = ({ profile, membershipCount }: EventTypeListHeadingProps): JSX.Element => (
|
||||||
<div className="mb-4 flex">
|
<div className="mb-4 flex">
|
||||||
<Link href="/settings/teams">
|
<Link href="/settings/teams">
|
||||||
|
@ -306,6 +473,21 @@ const EventTypeListHeading = ({ profile, membershipCount }: EventTypeListHeading
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CreateFirstEventTypeView = ({ canAddEvents, profiles }: CreateEventTypeProps) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="md:py-20">
|
||||||
|
<UserCalendarIllustration />
|
||||||
|
<div className="mx-auto block text-center md:max-w-screen-sm">
|
||||||
|
<h3 className="mt-2 text-xl font-bold text-neutral-900">{t("new_event_type_heading")}</h3>
|
||||||
|
<p className="text-md mt-1 mb-2 text-neutral-600">{t("new_event_type_description")}</p>
|
||||||
|
<CreateEventTypeButton canAddEvents={canAddEvents} options={profiles} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const EventTypesPage = () => {
|
const EventTypesPage = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const query = trpc.useQuery(["viewer.eventTypes"]);
|
const query = trpc.useQuery(["viewer.eventTypes"]);
|
||||||
|
@ -357,11 +539,7 @@ const EventTypesPage = () => {
|
||||||
membershipCount={group.metadata.membershipCount}
|
membershipCount={group.metadata.membershipCount}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EventTypeList
|
<EventTypeList types={group.eventTypes} group={group} readOnly={group.metadata.readOnly} />
|
||||||
types={group.eventTypes}
|
|
||||||
profile={group.profile}
|
|
||||||
readOnly={group.metadata.readOnly}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
||||||
value: locale,
|
value: locale,
|
||||||
// FIXME
|
// FIXME
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
label: new Intl.DisplayNames(props.localeProp, { type: "language" }).of(locale),
|
label: new Intl.DisplayNames(props.localeProp, { type: "language" }).of(locale) || "",
|
||||||
}));
|
}));
|
||||||
}, [props.localeProp, router.locales]);
|
}, [props.localeProp, router.locales]);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,27 @@ test.describe("pro user", () => {
|
||||||
|
|
||||||
await expect(page.locator(`text='${eventTitle}'`)).toBeVisible();
|
await expect(page.locator(`text='${eventTitle}'`)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can duplicate an existing event type", async ({ page }) => {
|
||||||
|
const firstTitle = await page.locator("[data-testid=event-type-title-3]").innerText();
|
||||||
|
const firstFullSlug = await page.locator("[data-testid=event-type-slug-3]").innerText();
|
||||||
|
const firstSlug = firstFullSlug.split("/")[2];
|
||||||
|
|
||||||
|
await page.click("[data-testid=event-type-options-3]");
|
||||||
|
await page.click("[data-testid=event-type-duplicate-3]");
|
||||||
|
|
||||||
|
const url = await page.url();
|
||||||
|
const params = new URLSearchParams(url);
|
||||||
|
|
||||||
|
await expect(params.get("title")).toBe(firstTitle);
|
||||||
|
await expect(params.get("slug")).toBe(firstSlug);
|
||||||
|
|
||||||
|
const formTitle = await page.inputValue("[name=title]");
|
||||||
|
const formSlug = await page.inputValue("[name=slug]");
|
||||||
|
|
||||||
|
await expect(formTitle).toBe(firstTitle);
|
||||||
|
await expect(formSlug).toBe(firstSlug);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("free user", () => {
|
test.describe("free user", () => {
|
||||||
|
|
|
@ -449,7 +449,7 @@
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"disband_team": "Disband Team",
|
"disband_team": "Disband Team",
|
||||||
"disband_team_confirmation_message": "Are you sure you want to disband this team? Anyone who you've shared this team link with will no longer be able to book using it.",
|
"disband_team_confirmation_message": "Are you sure you want to disband this team? Anyone who you've shared this team link with will no longer be able to book using it.",
|
||||||
"remove_member_confirmation_message": "Are you sure you want to remove this member from the team?",
|
"remove_member_confirmation_message": "Are you sure you want to remove this member from the team?",
|
||||||
"confirm_disband_team": "Yes, disband team",
|
"confirm_disband_team": "Yes, disband team",
|
||||||
"confirm_remove_member": "Yes, remove member",
|
"confirm_remove_member": "Yes, remove member",
|
||||||
|
@ -674,5 +674,6 @@
|
||||||
"example_name": "John Doe",
|
"example_name": "John Doe",
|
||||||
"time_format": "Time format",
|
"time_format": "Time format",
|
||||||
"12_hour": "12 hour",
|
"12_hour": "12 hour",
|
||||||
"24_hour": "24 hour"
|
"24_hour": "24 hour",
|
||||||
|
"duplicate": "Duplicate"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const DropdownMenuTrigger = forwardRef<HTMLButtonElement, DropdownMenuTri
|
||||||
className={
|
className={
|
||||||
props.asChild
|
props.asChild
|
||||||
? className
|
? className
|
||||||
: `relative inline-flex items-center rounded-sm bg-transparent px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-1 group-hover:text-black ltr:ml-2 rtl:mr-2 ${className}`
|
: `inline-flex items-center rounded-sm bg-transparent px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-1 group-hover:text-black ${className}`
|
||||||
}
|
}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
/>
|
/>
|
||||||
|
@ -20,6 +20,8 @@ export const DropdownMenuTrigger = forwardRef<HTMLButtonElement, DropdownMenuTri
|
||||||
);
|
);
|
||||||
DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
|
DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
|
||||||
|
|
||||||
|
export const DropdownMenuTriggerItem = DropdownMenuPrimitive.TriggerItem;
|
||||||
|
|
||||||
type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuPrimitive["Content"]>;
|
type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuPrimitive["Content"]>;
|
||||||
export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
|
export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
|
||||||
({ children, ...props }, forwardedRef) => {
|
({ children, ...props }, forwardedRef) => {
|
||||||
|
@ -27,9 +29,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
|
||||||
<DropdownMenuPrimitive.Content
|
<DropdownMenuPrimitive.Content
|
||||||
portalled={props.portalled}
|
portalled={props.portalled}
|
||||||
{...props}
|
{...props}
|
||||||
className={`${
|
className={`z-10 mt-1 -ml-0 w-48 origin-top-right rounded-sm bg-white text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}
|
||||||
props.portalled ? `` : `md:-ml-[55px]`
|
|
||||||
} z-10 mt-1 -ml-0 w-full origin-top-right rounded-sm bg-white text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}
|
|
||||||
ref={forwardedRef}>
|
ref={forwardedRef}>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.Content>
|
</DropdownMenuPrimitive.Content>
|
Loading…
Reference in a new issue