Skip to content

Commit

Permalink
Removed some of the UTC logic which shouldn't be there (#7425)
Browse files Browse the repository at this point in the history
* Removed some of the UTC logic which shouldn't be there

* Upgrade rhf, remove SSR on [schedule] page

* Fix potential bug with date

* Amend schedule trpc call & don't show edit availability if id is undefined

* type fix

* Order date overrides in list by date

* Changed logic to check for unavailable

* Fix bug report spotted by @CarinaWolli

I can save date overrides for march 1st and 2nd (my date/time when saving was march 1st at 11PM GMT-5). It is saved successfully in the database however the date overrides don't show up in the list.

That's why:

- new Date() is utc so it is already march 2nd
- override.date has always timestamp 00:00 so it is also smaller than the utc time which was in my case 2023-03-02T04:00

* Actual fix, gotta remember it's the schedule tz
  • Loading branch information
emrysal committed Mar 3, 2023
1 parent 295748a commit 62dcd66
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 180 deletions.
22 changes: 12 additions & 10 deletions apps/web/components/eventtype/AvailabilityTab.tsx
Expand Up @@ -113,7 +113,7 @@ const EventTypeScheduleDetails = () => {
);

const filterDays = (dayNum: number) =>
schedule?.schedule.availability.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];

return (
<div className="space-y-4 rounded border px-6 pb-4">
Expand Down Expand Up @@ -156,15 +156,17 @@ const EventTypeScheduleDetails = () => {
<FiGlobe className="ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
<Button
href={`/availability/${schedule?.schedule.id}`}
disabled={isLoading}
color="minimal"
EndIcon={FiExternalLink}
target="_blank"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
{!!schedule?.id && (
<Button
href={`/availability/${schedule.id}`}
disabled={isLoading}
color="minimal"
EndIcon={FiExternalLink}
target="_blank"
rel="noopener noreferrer">
{t("edit_availability")}
</Button>
)}
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Expand Up @@ -104,7 +104,7 @@
"react-dom": "^18.2.0",
"react-easy-crop": "^3.5.2",
"react-feather": "^2.0.10",
"react-hook-form": "^7.34.2",
"react-hook-form": "^7.43.3",
"react-hot-toast": "^2.3.0",
"react-intl": "^5.25.1",
"react-live-chat-loader": "^2.7.3",
Expand Down
72 changes: 33 additions & 39 deletions apps/web/pages/availability/[schedule].tsx
@@ -1,4 +1,3 @@
import type { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
Expand All @@ -9,7 +8,7 @@ import Shell from "@calcom/features/shell/Shell";
import { availabilityAsString } from "@calcom/lib/availability";
import { yyyymmdd } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { stringOrNumber } from "@calcom/prisma/zod-utils";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
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";
Expand All @@ -35,10 +34,8 @@ import { HttpError } from "@lib/core/http/error";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";

import { ssrInit } from "@server/lib/ssr";

const querySchema = z.object({
schedule: stringOrNumber,
schedule: z.coerce.number().positive().optional(),
});

type AvailabilityFormValues = {
Expand Down Expand Up @@ -88,16 +85,29 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
);
};

export default function Availability({ schedule }: { schedule: number }) {
export default function Availability() {
const { t, i18n } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const me = useMeQuery();
const {
data: { schedule: scheduleId },
} = useTypedQuery(querySchema);

const { timeFormat } = me.data || { timeFormat: null };
const { data, isLoading } = trpc.viewer.availability.schedule.get.useQuery({ scheduleId: schedule });
const { data: defaultValues } = trpc.viewer.availability.defaultValues.useQuery({ scheduleId: schedule });
const form = useForm<AvailabilityFormValues>({ defaultValues });
const { control } = form;
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId },
{
enabled: !!scheduleId,
}
);

const form = useForm<AvailabilityFormValues>({
values: schedule && {
...schedule,
schedule: schedule?.availability || [],
},
});
const updateMutation = trpc.viewer.availability.schedule.update.useMutation({
onSuccess: async ({ prevDefaultId, currentDefaultId, ...data }) => {
if (prevDefaultId && currentDefaultId) {
Expand Down Expand Up @@ -144,7 +154,7 @@ export default function Availability({ schedule }: { schedule: number }) {
return (
<Shell
backPath="/availability"
title={data?.schedule.name ? data.schedule.name + " | " + t("availability") : t("availability")}
title={schedule?.name ? schedule.name + " | " + t("availability") : t("availability")}
heading={
<Controller
control={form.control}
Expand All @@ -155,8 +165,8 @@ export default function Availability({ schedule }: { schedule: number }) {
/>
}
subtitle={
data ? (
data.schedule.availability
schedule ? (
schedule.schedule
.filter((availability) => !!availability.days.length)
.map((availability) => (
<span key={availability.id}>
Expand All @@ -179,7 +189,7 @@ export default function Availability({ schedule }: { schedule: number }) {
</Skeleton>
<Switch
id="hiddenSwitch"
disabled={isLoading || data?.isDefault}
disabled={isLoading || schedule?.isDefault}
checked={form.watch("isDefault")}
onCheckedChange={(e) => {
form.setValue("isDefault", e);
Expand All @@ -199,7 +209,7 @@ export default function Availability({ schedule }: { schedule: number }) {
confirmBtnText={t("delete")}
loadingText={t("delete")}
onConfirm={() => {
deleteMutation.mutate({ scheduleId: schedule });
scheduleId && deleteMutation.mutate({ scheduleId });
}}>
{t("delete_schedule_description")}
</ConfirmationDialogContent>
Expand All @@ -218,19 +228,20 @@ export default function Availability({ schedule }: { schedule: number }) {
form={form}
id="availability-form"
handleSubmit={async ({ dateOverrides, ...values }) => {
updateMutation.mutate({
scheduleId: schedule,
dateOverrides: dateOverrides.flatMap((override) => override.ranges),
...values,
});
scheduleId &&
updateMutation.mutate({
scheduleId,
dateOverrides: dateOverrides.flatMap((override) => override.ranges),
...values,
});
}}
className="flex flex-col sm:mx-0 xl:flex-row xl:space-x-6">
<div className="flex-1 flex-row xl:mr-0">
<div className="mb-6 rounded-md border">
<div>
{typeof me.data?.weekStart === "string" && (
<Schedule
control={control}
control={form.control}
name="schedule"
weekStart={
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
Expand All @@ -242,7 +253,7 @@ export default function Availability({ schedule }: { schedule: number }) {
</div>
</div>
<div className="my-6 rounded-md border">
{data?.workingHours && <DateOverride workingHours={data.workingHours} />}
{schedule?.workingHours && <DateOverride workingHours={schedule.workingHours} />}
</div>
</div>
<div className="min-w-40 col-span-3 space-y-2 lg:col-span-1">
Expand Down Expand Up @@ -282,20 +293,3 @@ export default function Availability({ schedule }: { schedule: number }) {
</Shell>
);
}

export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const params = querySchema.safeParse(ctx.params);
const ssr = await ssrInit(ctx);

if (!params.success) return { notFound: true };

const scheduleId = params.data.schedule;
await ssr.viewer.availability.schedule.get.fetch({ scheduleId });
await ssr.viewer.availability.defaultValues.fetch({ scheduleId });
return {
props: {
schedule: scheduleId,
trpcState: ssr.dehydrate(),
},
};
};
2 changes: 1 addition & 1 deletion packages/app-store/applecalendar/package.json
Expand Up @@ -7,7 +7,7 @@
"description": "Apple calendar runs both the macOS and iOS mobile operating systems. Offering online cloud backup of calendars using Apple’s iCloud service, it can sync with Google Calendar and Microsoft Exchange Server. Users can schedule events in their day that include time, location, duration, and extra notes.",
"dependencies": {
"@calcom/prisma": "*",
"react-hook-form": "^7.34.2"
"react-hook-form": "^7.43.3"
},
"devDependencies": {
"@calcom/types": "*"
Expand Down
2 changes: 1 addition & 1 deletion packages/app-store/caldavcalendar/package.json
Expand Up @@ -10,7 +10,7 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/ui": "*",
"react-hook-form": "^7.34.2"
"react-hook-form": "^7.43.3"
},
"devDependencies": {
"@calcom/types": "*"
Expand Down
2 changes: 1 addition & 1 deletion packages/app-store/exchange2013calendar/package.json
Expand Up @@ -9,7 +9,7 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/ui": "*",
"react-hook-form": "^7.34.2",
"react-hook-form": "^7.43.3",
"ews-javascript-api": "^0.11.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-store/exchange2016calendar/package.json
Expand Up @@ -9,7 +9,7 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/ui": "*",
"react-hook-form": "^7.34.2",
"react-hook-form": "^7.43.3",
"ews-javascript-api": "^0.11.0"
},
"devDependencies": {
Expand Down
83 changes: 42 additions & 41 deletions packages/features/schedules/components/DateOverrideInputDialog.tsx
@@ -1,12 +1,13 @@
import { useState, useEffect, useMemo } from "react";
import { useState, useMemo } from "react";
import { useForm } from "react-hook-form";

import dayjs, { Dayjs } from "@calcom/dayjs";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib";
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { WorkingHours } from "@calcom/types/schedule";
import type { WorkingHours } from "@calcom/types/schedule";
import {
Dialog,
DialogContent,
Expand All @@ -19,12 +20,8 @@ import {
} from "@calcom/ui";

import DatePicker from "../../calendars/DatePicker";
import { DayRanges, TimeRange } from "./Schedule";

const ALL_DAY_RANGE = {
start: new Date(dayjs.utc().hour(0).minute(0).second(0).format()),
end: new Date(dayjs.utc().hour(0).minute(0).second(0).format()),
};
import type { TimeRange } from "./Schedule";
import { DayRanges } from "./Schedule";

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
Expand Down Expand Up @@ -70,45 +67,49 @@ const DateOverrideForm = ({
[browsingDate]
);

const form = useForm<{ range: TimeRange[] }>();
const { reset } = form;

useEffect(() => {
if (value) {
reset({
range: value.map((range) => ({
start: new Date(
dayjs.utc().hour(range.start.getUTCHours()).minute(range.start.getUTCMinutes()).second(0).format()
),
end: new Date(
dayjs.utc().hour(range.end.getUTCHours()).minute(range.end.getUTCMinutes()).second(0).format()
),
})),
});
return;
}
const dayRanges = (workingHours || []).reduce((dayRanges, workingHour) => {
if (date && workingHour.days.includes(date.day())) {
dayRanges.push({
start: dayjs.utc().startOf("day").add(workingHour.startTime, "minute").toDate(),
end: dayjs.utc().startOf("day").add(workingHour.endTime, "minute").toDate(),
});
}
return dayRanges;
}, [] as TimeRange[]);
reset({
range: dayRanges,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [date, value]);
const form = useForm({
values: {
range: value
? value.map((range) => ({
start: new Date(
dayjs
.utc()
.hour(range.start.getUTCHours())
.minute(range.start.getUTCMinutes())
.second(0)
.format()
),
end: new Date(
dayjs.utc().hour(range.end.getUTCHours()).minute(range.end.getUTCMinutes()).second(0).format()
),
}))
: (workingHours || []).reduce((dayRanges, workingHour) => {
if (date && workingHour.days.includes(date.day())) {
dayRanges.push({
start: dayjs.utc().startOf("day").add(workingHour.startTime, "minute").toDate(),
end: dayjs.utc().startOf("day").add(workingHour.endTime, "minute").toDate(),
});
}
return dayRanges;
}, [] as TimeRange[]),
},
});

return (
<Form
form={form}
handleSubmit={(values) => {
if (!date) return;
onChange(
(datesUnavailable ? [ALL_DAY_RANGE] : values.range).map((item) => ({
(datesUnavailable
? [
{
start: date.utc(true).startOf("day").toDate(),
end: date.utc(true).startOf("day").add(1, "day").toDate(),
},
]
: values.range
).map((item) => ({
start: date.hour(item.start.getHours()).minute(item.start.getMinutes()).toDate(),
end: date.hour(item.end.getHours()).minute(item.end.getMinutes()).toDate(),
}))
Expand Down

1 comment on commit 62dcd66

@vercel
Copy link

@vercel vercel bot commented on 62dcd66 Mar 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ui – ./apps/storybook

timelessui.com
ui-cal.vercel.app
ui-git-main-cal.vercel.app
ui.cal.com
cal-com-storybook.vercel.app
www.timelessui.com

Please sign in to comment.