From 8d111cede52870474dcc22bb0c205e5c7197f405 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Wed, 28 Sep 2022 23:54:41 +0100 Subject: [PATCH] Starting to take shape --- apps/web/pages/availability/[schedule].tsx | 191 ------------------ apps/web/pages/availability/index.tsx | 74 ------- apps/web/pages/availability/troubleshoot.tsx | 121 ----------- apps/web/pages/v2/availability/[schedule].tsx | 154 ++++++++++++-- packages/trpc/server/routers/viewer.tsx | 1 + packages/ui/v2/core/Button.tsx | 2 +- packages/ui/v2/core/form/Select.tsx | 8 +- packages/ui/v2/core/form/fields.tsx | 80 +++++++- 8 files changed, 217 insertions(+), 414 deletions(-) delete mode 100644 apps/web/pages/availability/[schedule].tsx delete mode 100644 apps/web/pages/availability/index.tsx delete mode 100644 apps/web/pages/availability/troubleshoot.tsx diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx deleted file mode 100644 index 548072c2f7..0000000000 --- a/apps/web/pages/availability/[schedule].tsx +++ /dev/null @@ -1,191 +0,0 @@ -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/[schedule].tsx` instead - */ -import { GetStaticPaths, GetStaticProps } from "next"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { z } from "zod"; - -import { DEFAULT_SCHEDULE, availabilityAsString } from "@calcom/lib/availability"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import showToast from "@calcom/lib/notification"; -import { stringOrNumber } from "@calcom/prisma/zod-utils"; -import { inferQueryOutput, trpc } from "@calcom/trpc/react"; -import Button from "@calcom/ui/Button"; -import { BadgeCheckIcon } from "@calcom/ui/Icon"; -import Shell from "@calcom/ui/Shell"; -import Switch from "@calcom/ui/Switch"; -import TimezoneSelect from "@calcom/ui/form/TimezoneSelect"; -import { Form } from "@calcom/ui/form/fields"; - -import { QueryCell } from "@lib/QueryCell"; -import { HttpError } from "@lib/core/http/error"; - -import Schedule from "@components/availability/Schedule"; -import EditableHeading from "@components/ui/EditableHeading"; - -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/[schedule].tsx` instead - */ -export function AvailabilityForm(props: inferQueryOutput<"viewer.availability.schedule">) { - const { t } = useLocale(); - const router = useRouter(); - const utils = trpc.useContext(); - - const form = useForm({ - defaultValues: { - schedule: props.availability || DEFAULT_SCHEDULE, - isDefault: !!props.isDefault, - timeZone: props.timeZone, - }, - }); - - const updateMutation = trpc.useMutation("viewer.availability.schedule.update", { - onSuccess: async ({ schedule }) => { - await utils.invalidateQueries(["viewer.availability.schedule"]); - await router.push("/availability"); - showToast( - t("availability_updated_successfully", { - scheduleName: schedule.name, - }), - "success" - ); - }, - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - }); - - return ( -
{ - updateMutation.mutate({ - scheduleId: parseInt(router.query.schedule as string, 10), - name: props.schedule.name, - ...values, - }); - }} - className="grid grid-cols-3 gap-2"> -
-
-

{t("change_start_end")}

- -
-
- - -
-
-
- {props.isDefault ? ( -
- - {t("default")} - -
- ) : ( - ( - - )} - /> - )} -
- -
- ( - onChange(timezone.value)} - /> - )} - /> -
-
-
-

- {t("something_doesnt_look_right")} -

-
-

{t("troubleshoot_availability")}

-
-
- -
-
-
-
- ); -} - -const querySchema = z.object({ - schedule: stringOrNumber, -}); - -export default function Availability() { - const router = useRouter(); - const { i18n } = useLocale(); - const { schedule: scheduleId } = router.isReady ? querySchema.parse(router.query) : { schedule: -1 }; - const query = trpc.useQuery(["viewer.availability.schedule", { scheduleId }], { enabled: router.isReady }); - const [name, setName] = useState(); - return ( -
- { - return ( - } - subtitle={data.schedule.availability.map((availability) => ( - - {availabilityAsString(availability, { locale: i18n.language })} -
-
- ))}> - -
- ); - }} - /> -
- ); -} - -export const getStaticProps: GetStaticProps = (ctx) => { - const params = querySchema.safeParse(ctx.params); - - if (!params.success) return { notFound: true }; - - return { - props: { - schedule: params.data.schedule, - }, - revalidate: 10, // seconds - }; -}; - -export const getStaticPaths: GetStaticPaths = () => { - return { - paths: [], - fallback: "blocking", - }; -}; diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx deleted file mode 100644 index f4434ea9c1..0000000000 --- a/apps/web/pages/availability/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/index.tsx` instead - */ -import { ScheduleListItem } from "@calcom/features/schedules/components/ScheduleListItem"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import showToast from "@calcom/lib/notification"; -import { inferQueryOutput, trpc } from "@calcom/trpc/react"; -import EmptyScreen from "@calcom/ui/EmptyScreen"; -import { Icon } from "@calcom/ui/Icon"; -import Shell from "@calcom/ui/Shell"; - -import { withQuery } from "@lib/QueryCell"; -import { HttpError } from "@lib/core/http/error"; - -import { NewScheduleButton } from "@components/availability/NewScheduleButton"; -import SkeletonLoader from "@components/availability/SkeletonLoader"; - -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/index.tsx` instead - */ -export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availability.list">) { - const { t } = useLocale(); - const utils = trpc.useContext(); - const deleteMutation = trpc.useMutation("viewer.availability.schedule.delete", { - onSuccess: async () => { - await utils.invalidateQueries(["viewer.availability.list"]); - showToast(t("schedule_deleted_successfully"), "success"); - }, - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - }); - return ( - <> - {schedules.length === 0 ? ( - - ) : ( -
-
    - {schedules.map((schedule) => ( - - ))} -
-
- )} - - ); -} - -const WithQuery = withQuery(["viewer.availability.list"]); - -export default function AvailabilityPage() { - const { t } = useLocale(); - return ( -
- }> - } customLoader={} /> - -
- ); -} diff --git a/apps/web/pages/availability/troubleshoot.tsx b/apps/web/pages/availability/troubleshoot.tsx deleted file mode 100644 index 7c569c3b1c..0000000000 --- a/apps/web/pages/availability/troubleshoot.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/troubleshoot.tsx` instead - */ -import type { IBusySlot } from "pages/v2/availability/troubleshoot"; -import { useState } from "react"; - -import dayjs from "@calcom/dayjs"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { inferQueryOutput, trpc } from "@calcom/trpc/react"; -import Shell from "@calcom/ui/Shell"; - -import { QueryCell } from "@lib/QueryCell"; - -import Loader from "@components/Loader"; - -type User = inferQueryOutput<"viewer.me">; - -/** - * @deprecated modifications to this file should be v2 only - * Use `/apps/web/pages/v2/availability/troubleshoot.tsx` instead - */ -const AvailabilityView = ({ user }: { user: User }) => { - const { t } = useLocale(); - const [selectedDate, setSelectedDate] = useState(dayjs()); - - const { data, isLoading } = trpc.useQuery( - [ - "viewer.availability.user", - { - username: user.username!, - dateFrom: selectedDate.startOf("day").utc().format(), - dateTo: selectedDate.endOf("day").utc().format(), - withSource: true, - }, - ], - { - enabled: !!user.username, - } - ); - - return ( -
-
- {t("overview_of_day")}{" "} - { - if (e.target.value) setSelectedDate(dayjs(e.target.value)); - }} - /> - {t("hover_over_bold_times_tip")} -
-
-
- {t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)} -
-
- {isLoading ? ( - - ) : data && data.busy.length > 0 ? ( - data.busy - .sort((a: IBusySlot, b: IBusySlot) => (a.start > b.start ? -1 : 1)) - .map((slot: IBusySlot) => ( -
-
- {t("calendar_shows_busy_between")}{" "} - - {dayjs(slot.start).format("HH:mm")} - {" "} - {t("and")}{" "} - - {dayjs(slot.end).format("HH:mm")} - {" "} - {t("on")} {dayjs(slot.start).format("D")}{" "} - {t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")} - {slot.title && ` - (${slot.title})`} - {slot.source && {` - (source: ${slot.source})`}} -
-
- )) - ) : ( -
-
{t("calendar_no_busy_slots")}
-
- )} - -
-
- {t("your_day_ends_at")} {convertMinsToHrsMins(user.endTime)} -
-
-
-
-
- ); -}; - -export default function Troubleshoot() { - const query = trpc.useQuery(["viewer.me"]); - const { t } = useLocale(); - return ( -
- - } /> - -
- ); -} - -function convertMinsToHrsMins(mins: number) { - const h = Math.floor(mins / 60); - const m = mins % 60; - const hs = h < 10 ? "0" + h : h; - const ms = m < 10 ? "0" + m : m; - return `${hs}:${ms}`; -} diff --git a/apps/web/pages/v2/availability/[schedule].tsx b/apps/web/pages/v2/availability/[schedule].tsx index fe9dd188f3..92500605bc 100644 --- a/apps/web/pages/v2/availability/[schedule].tsx +++ b/apps/web/pages/v2/availability/[schedule].tsx @@ -1,6 +1,16 @@ +import { + DropdownMenuCheckboxItem as PrimitiveDropdownMenuCheckboxItem, + DropdownMenuCheckboxItemProps, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, +} from "@radix-ui/react-dropdown-menu"; +import classNames from "classnames"; import { GetStaticPaths, GetStaticProps } from "next"; import { useRouter } from "next/router"; -import { useEffect } from "react"; +import React from "react"; +import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; @@ -14,6 +24,7 @@ import type { Schedule as ScheduleType } from "@calcom/types/schedule"; import { Icon } from "@calcom/ui"; import TimezoneSelect from "@calcom/ui/form/TimezoneSelect"; import Button from "@calcom/ui/v2/core/Button"; +import Dropdown, { DropdownMenuTrigger, DropdownMenuContent } from "@calcom/ui/v2/core/Dropdown"; import Shell from "@calcom/ui/v2/core/Shell"; import Switch from "@calcom/ui/v2/core/Switch"; import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider"; @@ -36,6 +47,101 @@ type AvailabilityFormValues = { isDefault: boolean; }; +const DropdownMenuCheckboxItem = React.forwardRef( + ({ children }, ref) => ( + + + + ) +); + +DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem"; + +const ActiveOnEventTypeSelect = () => { + const { t } = useLocale(); + const [isOpen, setOpen] = useState(false); + + const { data } = trpc.useQuery(["viewer.eventTypes"]); + + const eventTypeGroups = data?.eventTypeGroups.reduce((aggregate, eventTypeGroups) => { + if (eventTypeGroups.eventTypes[0].team !== null) { + aggregate.push({ + groupName: eventTypeGroups.eventTypes[0].team.name || "", + eventTypeNames: [...eventTypeGroups.eventTypes.map((eventType) => eventType.title)], + }); + } else { + aggregate.push({ + groupName: eventTypeGroups.eventTypes[0].users[0].name || "", + eventTypeNames: [...eventTypeGroups.eventTypes.map((eventType) => eventType.title)], + }); + } + return aggregate; + }, [] as { groupName: string; eventTypeNames: string[] }[]); + + return ( + + + + + + {(eventTypeGroups || []).map((eventTypeGroup) => ( + + + {eventTypeGroup.groupName} + + {eventTypeGroup.eventTypeNames.map((eventTypeTitle) => ( + + {eventTypeTitle} + + ))} + + ))} + +
+
+ + + + +
+
+ ); +}; + export default function Availability({ schedule }: { schedule: number }) { const { t, i18n } = useLocale(); const router = useRouter(); @@ -130,7 +236,7 @@ export default function Availability({ schedule }: { schedule: number }) { }} className="-mx-4 flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6">
-
+

{t("change_start_end")}

@@ -148,25 +254,31 @@ export default function Availability({ schedule }: { schedule: number }) {
-
-
- - - value ? ( - onChange(timezone.value)} - /> - ) : ( - - ) - } - /> +
+
+
+ + + value ? ( + onChange(timezone.value)} + /> + ) : ( + + ) + } + /> +
+

diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index b966364490..2461c16026 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -281,6 +281,7 @@ const loggedInViewerRouter = createProtectedRouter() successRedirectUrl: true, hashedLink: true, destinationCalendar: true, + scheduleId: true, team: true, users: { select: { diff --git a/packages/ui/v2/core/Button.tsx b/packages/ui/v2/core/Button.tsx index fbe678f93f..607b35c4a9 100644 --- a/packages/ui/v2/core/Button.tsx +++ b/packages/ui/v2/core/Button.tsx @@ -87,7 +87,7 @@ export const Button = forwardRef = GroupBase