From 2803b5072c45a20d937e5d9036bc1660015f4f59 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Wed, 13 Mar 2024 20:30:28 +0000 Subject: [PATCH] fix: Remove the right date override on removal (#13988) Co-authored-by: zomars --- apps/web/pages/availability/[schedule].tsx | 277 +++++++++++------- apps/web/playwright/availability.e2e.ts | 47 ++- apps/web/public/static/locales/en/common.json | 1 + package.json | 2 + .../components/DateOverrideList.test.tsx | 112 +++++++ .../schedules/components/DateOverrideList.tsx | 48 +-- .../components/AvailabilityEditSheet.tsx | 19 +- packages/lib/getClientErrorFromUnknown.ts | 24 ++ setupVitest.ts | 7 +- vitest.workspace.ts | 17 +- yarn.lock | 117 ++++---- 11 files changed, 470 insertions(+), 201 deletions(-) create mode 100644 packages/features/schedules/components/DateOverrideList.test.tsx create mode 100644 packages/lib/getClientErrorFromUnknown.ts diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index 015855efd20231..79993835a76124 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -1,6 +1,6 @@ import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { useMemo, useState } from "react"; +import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"; import dayjs from "@calcom/dayjs"; import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules"; @@ -8,6 +8,7 @@ import Schedule from "@calcom/features/schedules/components/Schedule"; import Shell from "@calcom/features/shell/Shell"; import { classNames } from "@calcom/lib"; import { availabilityAsString } from "@calcom/lib/availability"; +import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; @@ -29,13 +30,13 @@ import { Tooltip, VerticalDivider, } from "@calcom/ui"; -import { Info, MoreVertical, ArrowLeft, Plus, Trash } from "@calcom/ui/components/icon"; +import { ArrowLeft, Info, MoreVertical, Plus, Trash } from "@calcom/ui/components/icon"; import PageWrapper from "@components/PageWrapper"; import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader"; import EditableHeading from "@components/ui/EditableHeading"; -type AvailabilityFormValues = { +export type AvailabilityFormValues = { name: string; schedule: ScheduleType; dateOverrides: { ranges: TimeRange[] }[]; @@ -43,11 +44,29 @@ type AvailabilityFormValues = { isDefault: boolean; }; +const useExcludedDates = () => { + const watchValues = useWatch({ name: "dateOverrides" }) as { + ranges: TimeRange[]; + }[]; + return useMemo(() => { + return watchValues?.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD")); + }, [watchValues]); +}; + +const useSettings = () => { + const { data } = useMeQuery(); + return { + hour12: data?.timeFormat === 12, + timeZone: data?.timeZone, + }; +}; + const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => { - const { remove, append, replace, fields } = useFieldArray({ + const { hour12 } = useSettings(); + const { append, replace, fields } = useFieldArray({ name: "dateOverrides", }); - const excludedDates = fields.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD")); + const excludedDates = useExcludedDates(); const { t } = useLocale(); return (
@@ -62,10 +81,10 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {

{t("date_overrides_subtitle")}

{ ); }; +const DeleteDialogButton = ({ + disabled, + scheduleId, + buttonClassName, + onDeleteConfirmed, +}: { + disabled?: boolean; + onDeleteConfirmed?: () => void; + buttonClassName: string; + scheduleId: number; +}) => { + const { t } = useLocale(); + const router = useRouter(); + const utils = trpc.useUtils(); + const { isPending, mutate } = trpc.viewer.availability.schedule.delete.useMutation({ + onError: withErrorFromUnknown((err) => { + showToast(err.message, "error"); + }), + onSettled: () => { + utils.viewer.availability.list.invalidate(); + }, + onSuccess: () => { + showToast(t("schedule_deleted_successfully"), "success"); + router.push("/availability"); + }, + }); + + return ( + + + + ); +}; + +// Simplify logic by assuming this will never be opened on a large screen +const SmallScreenSideBar = ({ open, children }: { open: boolean; children: JSX.Element }) => { + return ( +
+
+ {open ? children : null} +
+
+ ); +}; export default function Availability() { const searchParams = useCompatSearchParams(); const { t, i18n } = useLocale(); - const router = useRouter(); - const utils = trpc.useContext(); + const utils = trpc.useUtils(); const me = useMeQuery(); const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1; const fromEventType = searchParams?.get("fromEventType"); @@ -133,22 +226,6 @@ export default function Availability() { }, }); - const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({ - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - onSettled: () => { - utils.viewer.availability.list.invalidate(); - }, - onSuccess: () => { - showToast(t("schedule_deleted_successfully"), "success"); - router.push("/availability"); - }, - }); - return (
- - {t("set_to_default")} - - { - form.setValue("isDefault", e); - }} - /> + {!openSidebar ? ( + <> + + {t("set_to_default")} + + ( + + )} + /> + + ) : null}
- - - + -
-
+ + + <>
-
{t("name")} @@ -275,6 +322,7 @@ export default function Availability() { )} />
+
{t("set_to_default")} - { - form.setValue("isDefault", e); - }} + ( + + )} />
@@ -331,8 +383,9 @@ export default function Availability() {
-
- + + +
diff --git a/packages/features/timezone-buddy/components/AvailabilityEditSheet.tsx b/packages/features/timezone-buddy/components/AvailabilityEditSheet.tsx index 41fdee75db52de..d159a31b6ce1aa 100644 --- a/packages/features/timezone-buddy/components/AvailabilityEditSheet.tsx +++ b/packages/features/timezone-buddy/components/AvailabilityEditSheet.tsx @@ -6,6 +6,7 @@ import Schedule from "@calcom/features/schedules/components/Schedule"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import { trpc } from "@calcom/trpc/react"; +import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import type { Schedule as ScheduleType, TimeRange, WorkingHours } from "@calcom/types/schedule"; import { Button, @@ -37,8 +38,18 @@ type AvailabilityFormValues = { isDefault: boolean; }; +const useSettings = () => { + const { data } = useMeQuery(); + return { + hour12: data?.timeFormat === 12, + timeZone: data?.timeZone, + }; +}; + const DateOverride = ({ workingHours, disabled }: { workingHours: WorkingHours[]; disabled?: boolean }) => { - const { remove, append, replace, fields } = useFieldArray({ + const { hour12 } = useSettings(); + + const { append, replace, fields } = useFieldArray({ name: "dateOverrides", }); const excludedDates = fields.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD")); @@ -49,9 +60,9 @@ const DateOverride = ({ workingHours, disabled }: { workingHours: WorkingHours[]
void) => (b: unknown) => + a(getClientErrorFromUnknown(b)); diff --git a/setupVitest.ts b/setupVitest.ts index 18104fa24c0f41..5fdcc0be08791f 100644 --- a/setupVitest.ts +++ b/setupVitest.ts @@ -1,7 +1,12 @@ -import { vi } from "vitest"; +import matchers from "@testing-library/jest-dom/matchers"; +import ResizeObserver from "resize-observer-polyfill"; +import { vi, expect } from "vitest"; import createFetchMock from "vitest-fetch-mock"; +global.ResizeObserver = ResizeObserver; const fetchMocker = createFetchMock(vi); // sets globalThis.fetch and globalThis.fetchMock to our mocked version fetchMocker.enableMocks(); + +expect.extend(matchers); diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 6b48d53be377f6..4d2321e6031391 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -34,13 +34,10 @@ const workspaces = packagedEmbedTestsOnly : [ { test: { - include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"], - exclude: [ - "**/node_modules/**/*", - "**/.next/**/*", - "packages/embeds/**/*", - "packages/lib/hooks/**/*", - ], + globals: true, + name: "@calcom/features", + include: ["packages/features/**/*.{test,spec}.tsx"], + environment: "jsdom", setupFiles: ["setupVitest.ts"], }, }, @@ -56,8 +53,8 @@ const workspaces = packagedEmbedTestsOnly { test: { globals: true, - name: "ui/components", - include: ["packages/ui/components/**/*.{test,spec}.{ts,js,tsx}"], + name: "@calcom/ui", + include: ["packages/ui/components/**/*.{test,spec}.[jt]s?(x)"], environment: "jsdom", setupFiles: ["packages/ui/components/test-setup.ts"], }, @@ -66,7 +63,7 @@ const workspaces = packagedEmbedTestsOnly test: { globals: true, name: "EventTypeAppCardInterface components", - include: ["packages/app-store/_components/**/*.{test,spec}.{ts,js,tsx}"], + include: ["packages/app-store/_components/**/*.{test,spec}.[jt]s?(x)"], environment: "jsdom", setupFiles: ["packages/app-store/test-setup.ts"], }, diff --git a/yarn.lock b/yarn.lock index fa8654c6afd301..f088edb4e217cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3546,7 +3546,7 @@ __metadata: "@types/react-dom": ^18.0.9 eslint: ^8.34.0 eslint-config-next: ^13.2.1 - next: ^14.1.0 + next: ^13.5.4 next-auth: ^4.22.1 postcss: ^8.4.18 react: ^18.2.0 @@ -8296,10 +8296,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.1.1": - version: 14.1.1 - resolution: "@next/env@npm:14.1.1" - checksum: 9714e716ca26dd024b1680ca9c17be60adf4228a2bd7a1a9f71752cfd8989f005e805477453200cad0a5ed85439161563a248235fc897dc42f762cba742ec62a +"@next/env@npm:14.1.3": + version: 14.1.3 + resolution: "@next/env@npm:14.1.3" + checksum: 1645d801acc5c642c57205c354f39d0b95d5641613a0d6e2aff1ab3e7bd34402953e10246484b405086961b91baf044baf0b7a510f020a68427b63f6093bca90 languageName: node linkType: hard @@ -8319,9 +8319,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-darwin-arm64@npm:14.1.1" +"@next/swc-darwin-arm64@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-darwin-arm64@npm:14.1.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -8333,9 +8333,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-darwin-x64@npm:14.1.1" +"@next/swc-darwin-x64@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-darwin-x64@npm:14.1.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -8347,9 +8347,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-linux-arm64-gnu@npm:14.1.1" +"@next/swc-linux-arm64-gnu@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-arm64-gnu@npm:14.1.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -8361,9 +8361,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-linux-arm64-musl@npm:14.1.1" +"@next/swc-linux-arm64-musl@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-arm64-musl@npm:14.1.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -8375,9 +8375,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-linux-x64-gnu@npm:14.1.1" +"@next/swc-linux-x64-gnu@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-x64-gnu@npm:14.1.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -8389,9 +8389,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-linux-x64-musl@npm:14.1.1" +"@next/swc-linux-x64-musl@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-x64-musl@npm:14.1.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -8403,9 +8403,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-win32-arm64-msvc@npm:14.1.1" +"@next/swc-win32-arm64-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-arm64-msvc@npm:14.1.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -8417,9 +8417,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-win32-ia32-msvc@npm:14.1.1" +"@next/swc-win32-ia32-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-ia32-msvc@npm:14.1.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -8431,9 +8431,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:14.1.1": - version: 14.1.1 - resolution: "@next/swc-win32-x64-msvc@npm:14.1.1" +"@next/swc-win32-x64-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-x64-msvc@npm:14.1.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -17356,9 +17356,11 @@ __metadata: lint-staged: ^12.5.0 lucide-react: ^0.171.0 mailhog: ^4.16.0 + next-router-mock: ^0.9.12 node-ical: ^0.16.1 prettier: ^2.8.6 prismock: ^1.21.1 + resize-observer-polyfill: ^1.5.1 tsc-absolute: ^1.0.0 turbo: ^1.10.1 typescript: ^4.9.4 @@ -17515,9 +17517,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001579": - version: 1.0.30001591 - resolution: "caniuse-lite@npm:1.0.30001591" - checksum: e48f924cdefff86d29d38ee1bffe2cdb1ef55e179d08ae2f1f5546d9d563e030f13755a0096ea87a09498daffd18666d1fe0b2759aea8421bbf4c214b47d410d + version: 1.0.30001597 + resolution: "caniuse-lite@npm:1.0.30001597" + checksum: ec6a2cf0fd49f37d16732e6595939fc80a125dcd188a950bc936c61b4ad53becc0fe51bf2d9a625415de7b1cb23bd835f220e8b68d8ab951a940edeea65476fd languageName: node linkType: hard @@ -29894,6 +29896,16 @@ __metadata: languageName: node linkType: hard +"next-router-mock@npm:^0.9.12": + version: 0.9.12 + resolution: "next-router-mock@npm:0.9.12" + peerDependencies: + next: ">=10.0.0" + react: ">=17.0.0" + checksum: f630a4308686b2f6eeb1138d05ccb40dd2e6312216de1e94811403222967fd9d1ee723b2595d20bffbb1eaffce0d8e8da81983346528c6ac8334d4968338dd96 + languageName: node + linkType: hard + "next-seo@npm:^6.0.0": version: 6.1.0 resolution: "next-seo@npm:6.1.0" @@ -30005,20 +30017,20 @@ __metadata: languageName: node linkType: hard -"next@npm:^14.1, next@npm:^14.1.0": - version: 14.1.1 - resolution: "next@npm:14.1.1" +"next@npm:^14.1": + version: 14.1.3 + resolution: "next@npm:14.1.3" dependencies: - "@next/env": 14.1.1 - "@next/swc-darwin-arm64": 14.1.1 - "@next/swc-darwin-x64": 14.1.1 - "@next/swc-linux-arm64-gnu": 14.1.1 - "@next/swc-linux-arm64-musl": 14.1.1 - "@next/swc-linux-x64-gnu": 14.1.1 - "@next/swc-linux-x64-musl": 14.1.1 - "@next/swc-win32-arm64-msvc": 14.1.1 - "@next/swc-win32-ia32-msvc": 14.1.1 - "@next/swc-win32-x64-msvc": 14.1.1 + "@next/env": 14.1.3 + "@next/swc-darwin-arm64": 14.1.3 + "@next/swc-darwin-x64": 14.1.3 + "@next/swc-linux-arm64-gnu": 14.1.3 + "@next/swc-linux-arm64-musl": 14.1.3 + "@next/swc-linux-x64-gnu": 14.1.3 + "@next/swc-linux-x64-musl": 14.1.3 + "@next/swc-win32-arm64-msvc": 14.1.3 + "@next/swc-win32-ia32-msvc": 14.1.3 + "@next/swc-win32-x64-msvc": 14.1.3 "@swc/helpers": 0.5.2 busboy: 1.6.0 caniuse-lite: ^1.0.30001579 @@ -30056,7 +30068,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 58c17bf9520648cb79c3341a0a011ce19e8e5368f23be7415b4a35787b3562519cfd0dd8e008a1ce0b49dfe79e4ab89127f412cf039cb854b35f79a457a9be22 + checksum: 179a5dfd27a3ac900c0c229c523793cf28c3972cf87ec9ce7b68ead8fc2b8cd3b9c0538bba1b42d3351462ffe890b964c869e081da76c3bed29826e7eae4d7a4 languageName: node linkType: hard @@ -34749,6 +34761,13 @@ __metadata: languageName: node linkType: hard +"resize-observer-polyfill@npm:^1.5.1": + version: 1.5.1 + resolution: "resize-observer-polyfill@npm:1.5.1" + checksum: 57e7f79489867b00ba43c9c051524a5c8f162a61d5547e99333549afc23e15c44fd43f2f318ea0261ea98c0eb3158cca261e6f48d66e1ed1cd1f340a43977094 + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1"