Merge branch 'main' into testE2E-timezone

This commit is contained in:
GitStart-Cal.com 2023-11-03 17:59:16 +05:45 committed by GitHub
commit ff84d4ae9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 184 additions and 50 deletions

View File

@ -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>

View File

@ -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 } },
},

View File

@ -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 } },
},

View File

@ -221,6 +221,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
hidePlaceholder
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
hideAdvancedText
/>
</div>
</div>

View File

@ -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

View File

@ -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(),
})

View File

@ -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",

View File

@ -421,6 +421,7 @@ export const BookEventFormChild = ({
onSuccess={() => {
setVerifiedEmail(email);
setEmailVerificationModalVisible(false);
bookEvent(bookingForm.getValues());
}}
isUserSessionRequiredToVerify={false}
/>

View File

@ -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" });
}

View File

@ -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>
);
};

View File

@ -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 = {

View File

@ -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