Merge branch 'main' into testE2E-timezone
This commit is contained in:
commit
ff84d4ae9a
|
@ -56,10 +56,6 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||
* <td>The provided id didn't correspond to any existing booking.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Cannot cancel past events</td>
|
||||
* <td>The provided id matched an existing booking with a past startDate.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>User not found</td>
|
||||
* <td>The userId did not matched an existing user.</td>
|
||||
* </tr>
|
||||
|
|
|
@ -51,6 +51,7 @@ export async function getHandler(req: NextApiRequest) {
|
|||
customInputs: true,
|
||||
team: { select: { slug: true } },
|
||||
users: true,
|
||||
hosts: { select: { userId: true, isFixed: true } },
|
||||
owner: { select: { username: true, id: true } },
|
||||
children: { select: { id: true, userId: true } },
|
||||
},
|
||||
|
|
|
@ -45,6 +45,7 @@ async function getHandler(req: NextApiRequest) {
|
|||
customInputs: true,
|
||||
team: { select: { slug: true } },
|
||||
users: true,
|
||||
hosts: { select: { userId: true, isFixed: true } },
|
||||
owner: { select: { username: true, id: true } },
|
||||
children: { select: { id: true, userId: true } },
|
||||
},
|
||||
|
|
|
@ -221,6 +221,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||
hidePlaceholder
|
||||
isLoading={mutation.isLoading}
|
||||
value={data.destinationCalendar?.externalId}
|
||||
hideAdvancedText
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -30,8 +30,7 @@ import {
|
|||
Button,
|
||||
showToast,
|
||||
} from "@calcom/ui";
|
||||
import { Plus, X, Check } from "@calcom/ui/components/icon";
|
||||
import { CornerDownRight } from "@calcom/ui/components/icon";
|
||||
import { Plus, X, Check, CornerDownRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import CheckboxField from "@components/ui/form/CheckboxField";
|
||||
import type { SingleValueLocationOption } from "@components/ui/form/LocationSelect";
|
||||
|
@ -200,23 +199,16 @@ export const EventSetupTab = (
|
|||
defaultValue={defaultValue}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
name={`locations[${index}].${eventLocationType.defaultValueVariable}`}
|
||||
type="text"
|
||||
required
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
className="my-0"
|
||||
{...rest}
|
||||
/>
|
||||
<ErrorMessage
|
||||
errors={formMethods.formState.errors.locations?.[index]}
|
||||
name={eventLocationType.defaultValueVariable}
|
||||
className="text-error my-1 text-sm"
|
||||
as="div"
|
||||
/>
|
||||
</>
|
||||
<Input
|
||||
name={`locations[${index}].${eventLocationType.defaultValueVariable}`}
|
||||
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
|
||||
type="text"
|
||||
required
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
className="my-0"
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -231,21 +223,14 @@ export const EventSetupTab = (
|
|||
defaultValue={defaultValue}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<>
|
||||
<PhoneInput
|
||||
required
|
||||
name={`locations[${index}].${eventLocationType.defaultValueVariable}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
{...rest}
|
||||
/>
|
||||
<ErrorMessage
|
||||
errors={formMethods.formState.errors.locations?.[index]}
|
||||
name={eventLocationType.defaultValueVariable}
|
||||
className="text-error my-1 text-sm"
|
||||
as="div"
|
||||
/>
|
||||
</>
|
||||
<PhoneInput
|
||||
required
|
||||
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
|
||||
name={`locations[${index}].${eventLocationType.defaultValueVariable}`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -332,11 +317,11 @@ export const EventSetupTab = (
|
|||
|
||||
{eventLocationType?.organizerInputType && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<CornerDownRight className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<CornerDownRight className="h-4 w-4" />
|
||||
</div>
|
||||
<LocationInput
|
||||
defaultValue={
|
||||
defaultLocation
|
||||
|
@ -347,6 +332,12 @@ export const EventSetupTab = (
|
|||
index={index}
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
errors={formMethods.formState.errors.locations?.[index]}
|
||||
name={eventLocationType.defaultValueVariable}
|
||||
className="text-error my-1 ml-6 text-sm"
|
||||
as="div"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-6">
|
||||
<CheckboxField
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useEffect, useMemo, useState } from "react";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getEventLocationType } from "@calcom/app-store/locations";
|
||||
import { validateCustomEventName } from "@calcom/core/event";
|
||||
import type { EventLocationType } from "@calcom/core/location";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
@ -321,6 +322,47 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||
teamName: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.superRefine((val, ctx) => {
|
||||
if (val?.link) {
|
||||
const link = val.link;
|
||||
const eventLocationType = getEventLocationType(val.type);
|
||||
if (
|
||||
eventLocationType &&
|
||||
!eventLocationType.default &&
|
||||
eventLocationType.linkType === "static" &&
|
||||
eventLocationType.urlRegExp
|
||||
) {
|
||||
const valid = z
|
||||
.string()
|
||||
.regex(new RegExp(eventLocationType.urlRegExp))
|
||||
.safeParse(link).success;
|
||||
|
||||
if (!valid) {
|
||||
const sampleUrl = eventLocationType.organizerInputPlaceholder;
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [eventLocationType?.defaultValueVariable ?? "link"],
|
||||
message: t("invalid_url_error_message", {
|
||||
label: eventLocationType.label,
|
||||
sampleUrl: sampleUrl ?? "https://cal.com",
|
||||
}),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = z.string().url().optional().safeParse(link).success;
|
||||
|
||||
if (!valid) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [eventLocationType?.defaultValueVariable ?? "link"],
|
||||
message: `Invalid URL`,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
|
|
|
@ -1222,6 +1222,7 @@
|
|||
"organizer_name_variable": "Organizer name",
|
||||
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
|
||||
"invalid_number": "Invalid phone number",
|
||||
"invalid_url_error_message": "Invalid URL for {{label}}. Sample URL: {{sampleUrl}}",
|
||||
"navigate": "Navigate",
|
||||
"open": "Open",
|
||||
"close": "Close",
|
||||
|
|
|
@ -421,6 +421,7 @@ export const BookEventFormChild = ({
|
|||
onSuccess={() => {
|
||||
setVerifiedEmail(email);
|
||||
setEmailVerificationModalVisible(false);
|
||||
bookEvent(bookingForm.getValues());
|
||||
}}
|
||||
isUserSessionRequiredToVerify={false}
|
||||
/>
|
||||
|
|
|
@ -129,10 +129,6 @@ async function handler(req: CustomRequest) {
|
|||
throw new HttpError({ statusCode: 400, message: "Booking not found" });
|
||||
}
|
||||
|
||||
if (userId !== bookingToDelete.user?.id && bookingToDelete.startTime < new Date()) {
|
||||
throw new HttpError({ statusCode: 400, message: "Cannot cancel past events" });
|
||||
}
|
||||
|
||||
if (!bookingToDelete.userId) {
|
||||
throw new HttpError({ statusCode: 400, message: "User not found" });
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ interface Props {
|
|||
destinationCalendar?: DestinationCalendar | null;
|
||||
value: string | undefined;
|
||||
maxWidth?: number;
|
||||
hideAdvancedText?: boolean;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
|
@ -51,6 +52,7 @@ const DestinationCalendarSelector = ({
|
|||
isLoading,
|
||||
value,
|
||||
hidePlaceholder,
|
||||
hideAdvancedText,
|
||||
maxWidth,
|
||||
destinationCalendar,
|
||||
}: Props): JSX.Element | null => {
|
||||
|
@ -177,7 +179,9 @@ const DestinationCalendarSelector = ({
|
|||
components={{ SingleValue: SingleValueComponent, Option: OptionComponent }}
|
||||
isMulti={false}
|
||||
/>
|
||||
<p className="text-sm leading-tight">{t("you_can_override_calendar_in_advanced_tab")}</p>
|
||||
{hideAdvancedText ? null : (
|
||||
<p className="text-sm leading-tight">{t("you_can_override_calendar_in_advanced_tab")}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
||||
|
@ -48,6 +48,106 @@ describe("processWorkingHours", () => {
|
|||
|
||||
expect(lastAvailableSlot.start.date()).toBe(31);
|
||||
});
|
||||
|
||||
it("It has the correct working hours on date of DST change (- tz)", () => {
|
||||
vi.useFakeTimers().setSystemTime(new Date("2023-11-05T13:26:14.000Z"));
|
||||
|
||||
const item = {
|
||||
days: [1, 2, 3, 4, 5],
|
||||
startTime: new Date(Date.UTC(2023, 5, 12, 9, 0)), // 9 AM
|
||||
endTime: new Date(Date.UTC(2023, 5, 12, 17, 0)), // 5 PM
|
||||
};
|
||||
|
||||
const timeZone = "America/New_York";
|
||||
|
||||
const dateFrom = dayjs();
|
||||
const dateTo = dayjs().endOf("month");
|
||||
|
||||
const results = processWorkingHours({ item, timeZone, dateFrom, dateTo });
|
||||
|
||||
expect(results).toStrictEqual([
|
||||
{
|
||||
start: dayjs("2023-11-06T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-06T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-07T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-07T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-08T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-08T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-09T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-09T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-10T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-10T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-13T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-13T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-14T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-14T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-15T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-15T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-16T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-16T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-17T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-17T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-20T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-20T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-21T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-21T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-22T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-22T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-23T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-23T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-24T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-24T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-27T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-27T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-28T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-28T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-29T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-29T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
{
|
||||
start: dayjs("2023-11-30T14:00:00.000Z").tz(timeZone),
|
||||
end: dayjs("2023-11-30T22:00:00.000Z").tz(timeZone),
|
||||
},
|
||||
]);
|
||||
|
||||
vi.setSystemTime(vi.getRealSystemTime());
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
// TEMPORAIRLY SKIPPING THIS TEST - Started failing after 29th Oct
|
||||
it.skip("should return the correct working hours in the month were DST ends", () => {
|
||||
const item = {
|
||||
|
|
|
@ -23,7 +23,7 @@ export function processWorkingHours({
|
|||
}) {
|
||||
const results = [];
|
||||
for (let date = dateFrom.tz(timeZone).startOf("day"); dateTo.isAfter(date); date = date.add(1, "day")) {
|
||||
const fromOffset = dateFrom.tz(timeZone).utcOffset();
|
||||
const fromOffset = dateFrom.tz(timeZone).startOf("day").utcOffset();
|
||||
const offset = date.tz(timeZone).utcOffset();
|
||||
|
||||
// it always has to be start of the day (midnight) even when DST changes
|
||||
|
|
Loading…
Reference in New Issue
Block a user