Skip to content

Commit f522a00

Browse files
authored
Merge branch 'main' into fix/rescheduling-duplicate-bookings
2 parents bdaac14 + 1512551 commit f522a00

7 files changed

Lines changed: 62 additions & 56 deletions

File tree

apps/web/components/booking/AvailableTimes.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
9494
slots.map((slot) => {
9595
type BookingURL = {
9696
pathname: string;
97-
query: Record<string, string | number | string[] | undefined>;
97+
query: Record<string, string | number | string[] | undefined | TimeFormat>;
9898
};
9999
const bookingUrl: BookingURL = {
100100
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
@@ -103,6 +103,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
103103
date: dayjs.utc(slot.time).tz(timeZone()).format(),
104104
type: eventTypeId,
105105
slug: eventTypeSlug,
106+
timeFormat,
106107
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
107108
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
108109
...(ethSignature ? { ethSignature } : {}),

apps/web/components/booking/pages/BookingPage.tsx

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useSession } from "next-auth/react";
44
import dynamic from "next/dynamic";
55
import Head from "next/head";
66
import { useRouter } from "next/router";
7-
import { useCallback, useEffect, useMemo, useReducer, useState } from "react";
7+
import { useEffect, useMemo, useReducer, useState } from "react";
88
import { useForm, useFormContext } from "react-hook-form";
99
import { v4 as uuidv4 } from "uuid";
1010
import { z } from "zod";
@@ -15,7 +15,6 @@ import { getEventLocationType, locationKeyToString } from "@calcom/app-store/loc
1515
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
1616
import { getEventTypeAppData } from "@calcom/app-store/utils";
1717
import type { LocationObject } from "@calcom/core/location";
18-
import type { Dayjs } from "@calcom/dayjs";
1918
import dayjs from "@calcom/dayjs";
2019
import {
2120
useEmbedNonStylesConfig,
@@ -36,19 +35,19 @@ import classNames from "@calcom/lib/classNames";
3635
import { APP_NAME } from "@calcom/lib/constants";
3736
import { useLocale } from "@calcom/lib/hooks/useLocale";
3837
import useTheme from "@calcom/lib/hooks/useTheme";
38+
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
3939
import { HttpError } from "@calcom/lib/http-error";
4040
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
4141
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
42-
import type { RecurringEvent } from "@calcom/types/Calendar";
42+
import { TimeFormat } from "@calcom/lib/timeFormat";
4343
import { Button, Form, Tooltip } from "@calcom/ui";
4444
import { FiAlertTriangle, FiCalendar, FiRefreshCw, FiUser } from "@calcom/ui/components/icon";
4545

46-
import { asStringOrNull } from "@lib/asStringOrNull";
4746
import { timeZone } from "@lib/clock";
4847
import useRouterQuery from "@lib/hooks/useRouterQuery";
4948
import createBooking from "@lib/mutations/bookings/create-booking";
5049
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
51-
import { parseDate, parseRecurringDates } from "@lib/parseDate";
50+
import { parseRecurringDates, parseDate } from "@lib/parseDate";
5251

5352
import type { Gate, GateState } from "@components/Gates";
5453
import Gates from "@components/Gates";
@@ -187,6 +186,22 @@ const BookingFields = ({
187186
);
188187
};
189188

189+
const routerQuerySchema = z
190+
.object({
191+
timeFormat: z.nativeEnum(TimeFormat),
192+
rescheduleUid: z.string().optional(),
193+
date: z
194+
.string()
195+
.optional()
196+
.transform((date) => {
197+
if (date === undefined) {
198+
return null;
199+
}
200+
return date;
201+
}),
202+
})
203+
.passthrough();
204+
190205
const BookingPage = ({
191206
eventType,
192207
booking,
@@ -225,20 +240,6 @@ const BookingPage = ({
225240
duration = Number(queryDuration);
226241
}
227242

228-
// This is a workaround for forcing the same time format for both server side rendering and client side rendering
229-
// At initial render, we use the default time format which is 12H
230-
const [withDefaultTimeFormat, setWithDefaultTimeFormat] = useState(true);
231-
const parseDateFunc = useCallback(
232-
(date: string | null | Dayjs) => {
233-
return parseDate(date, i18n, withDefaultTimeFormat);
234-
},
235-
[withDefaultTimeFormat]
236-
);
237-
// After intial render on client side, we let parseDateFunc to use the time format from the localStorage
238-
useEffect(() => {
239-
setWithDefaultTimeFormat(false);
240-
}, []);
241-
242243
useEffect(() => {
243244
if (top !== window) {
244245
//page_view will be collected automatically by _middleware.ts
@@ -295,9 +296,12 @@ const BookingPage = ({
295296
},
296297
});
297298

298-
const rescheduleUid = router.query.rescheduleUid as string;
299+
const {
300+
data: { timeFormat, rescheduleUid, date },
301+
} = useTypedQuery(routerQuerySchema);
302+
299303
useTheme(profile.theme);
300-
const date = asStringOrNull(router.query.date);
304+
301305
const querySchema = getBookingResponsesPartialSchema({
302306
eventType: {
303307
bookingFields: getBookingFieldsWithSystemFields(eventType),
@@ -401,26 +405,17 @@ const BookingPage = ({
401405
// Calculate the booking date(s)
402406
let recurringStrings: string[] = [],
403407
recurringDates: Date[] = [];
404-
const parseRecurringDatesFunc = useCallback(
405-
(date: string | null | Dayjs, recurringEvent: RecurringEvent, recurringCount: number) => {
406-
return parseRecurringDates(
407-
{
408-
startDate: date,
409-
timeZone: timeZone(),
410-
recurringEvent: recurringEvent,
411-
recurringCount: recurringCount,
412-
withDefaultTimeFormat: withDefaultTimeFormat,
413-
},
414-
i18n
415-
);
416-
},
417-
[withDefaultTimeFormat, date, eventType.recurringEvent, recurringEventCount]
418-
);
408+
419409
if (eventType.recurringEvent?.freq && recurringEventCount !== null) {
420-
[recurringStrings, recurringDates] = parseRecurringDatesFunc(
421-
date,
422-
eventType.recurringEvent,
423-
parseInt(recurringEventCount.toString())
410+
[recurringStrings, recurringDates] = parseRecurringDates(
411+
{
412+
startDate: date,
413+
timeZone: timeZone(),
414+
recurringEvent: eventType.recurringEvent,
415+
recurringCount: parseInt(recurringEventCount.toString()),
416+
selectedTimeFormat: timeFormat,
417+
},
418+
i18n
424419
);
425420
}
426421

@@ -550,7 +545,7 @@ const BookingPage = ({
550545
<div className="text-sm font-medium">
551546
{isClientTimezoneAvailable &&
552547
(rescheduleUid || !eventType.recurringEvent?.freq) &&
553-
`${parseDateFunc(date)}`}
548+
`${parseDate(date, i18n, timeFormat)}`}
554549
{isClientTimezoneAvailable &&
555550
!rescheduleUid &&
556551
eventType.recurringEvent?.freq &&
@@ -580,7 +575,7 @@ const BookingPage = ({
580575
<FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
581576
{isClientTimezoneAvailable &&
582577
typeof booking.startTime === "string" &&
583-
parseDateFunc(dayjs(booking.startTime))}
578+
parseDate(dayjs(booking.startTime), i18n, timeFormat)}
584579
</p>
585580
</div>
586581
)}

apps/web/components/eventtype/EventLimitsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ const IntervalLimitItem = ({
424424
<TextField
425425
required
426426
type="number"
427-
containerClassName={`${textFieldSuffix ? "w-36" : "w-16"} -mb-1`}
427+
containerClassName={textFieldSuffix ? "w-44 -mb-1" : "w-16 mb-0"}
428428
placeholder={`${value}`}
429429
min={step}
430430
step={step}

apps/web/lib/parseDate.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,22 @@ import { RRule } from "rrule";
33

44
import type { Dayjs } from "@calcom/dayjs";
55
import dayjs from "@calcom/dayjs";
6-
import { detectBrowserTimeFormat, TimeFormat } from "@calcom/lib/timeFormat";
6+
import type { TimeFormat } from "@calcom/lib/timeFormat";
7+
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
78
import type { RecurringEvent } from "@calcom/types/Calendar";
89

910
import { parseZone } from "./parseZone";
1011

11-
const processDate = (date: string | null | Dayjs, i18n: I18n, withDefaultTimeFormat: boolean) => {
12+
const processDate = (date: string | null | Dayjs, i18n: I18n, selectedTimeFormat?: TimeFormat) => {
1213
const parsedZone = parseZone(date);
1314
if (!parsedZone?.isValid()) return "Invalid date";
14-
const formattedTime = parsedZone?.format(
15-
withDefaultTimeFormat ? TimeFormat.TWELVE_HOUR : detectBrowserTimeFormat
16-
);
15+
const formattedTime = parsedZone?.format(selectedTimeFormat || detectBrowserTimeFormat);
1716
return formattedTime + ", " + dayjs(date).toDate().toLocaleString(i18n.language, { dateStyle: "full" });
1817
};
1918

20-
export const parseDate = (date: string | null | Dayjs, i18n: I18n, withDefaultTimeFormat: boolean) => {
19+
export const parseDate = (date: string | null | Dayjs, i18n: I18n, selectedTimeFormat?: TimeFormat) => {
2120
if (!date) return ["No date"];
22-
return processDate(date, i18n, withDefaultTimeFormat);
21+
return processDate(date, i18n, selectedTimeFormat);
2322
};
2423

2524
export const parseRecurringDates = (
@@ -28,13 +27,13 @@ export const parseRecurringDates = (
2827
timeZone,
2928
recurringEvent,
3029
recurringCount,
31-
withDefaultTimeFormat,
30+
selectedTimeFormat,
3231
}: {
3332
startDate: string | null | Dayjs;
3433
timeZone?: string;
3534
recurringEvent: RecurringEvent | null;
3635
recurringCount: number;
37-
withDefaultTimeFormat: boolean;
36+
selectedTimeFormat?: TimeFormat;
3837
},
3938
i18n: I18n
4039
): [string[], Date[]] => {
@@ -54,7 +53,7 @@ export const parseRecurringDates = (
5453
});
5554
const dateStrings = times.map((t) => {
5655
// finally; show in local timeZone again
57-
return processDate(t.tz(timeZone), i18n, withDefaultTimeFormat);
56+
return processDate(t.tz(timeZone), i18n, selectedTimeFormat);
5857
});
5958

6059
return [dateStrings, times.map((t) => t.toDate())];

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@calcom/web",
3-
"version": "2.7.2",
3+
"version": "2.7.3",
44
"private": true,
55
"scripts": {
66
"analyze": "ANALYZE=true next build",

packages/features/bookings/lib/getBookingFields.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import {
1111

1212
export const SMS_REMINDER_NUMBER_FIELD = "smsReminderNumber";
1313

14+
/**
15+
* PHONE -> Phone
16+
*/
17+
function upperCaseToCamelCase(upperCaseString: string) {
18+
return upperCaseString[0].toUpperCase() + upperCaseString.slice(1).toLowerCase();
19+
}
20+
1421
export const getSmsReminderNumberField = () =>
1522
({
1623
name: SMS_REMINDER_NUMBER_FIELD,
@@ -296,8 +303,9 @@ export const ensureBookingInputsHaveSystemFields = ({
296303
// Backward Compatibility: If we are migrating from old system, we need to map `customInputs` to `bookingFields`
297304
if (handleMigration) {
298305
customInputs.forEach((input, index) => {
306+
const label = input.label || `${upperCaseToCamelCase(input.type)}`;
299307
bookingFields.push({
300-
label: input.label,
308+
label: label,
301309
editable: "user",
302310
// Custom Input's slugified label was being used as query param for prefilling. So, make that the name of the field
303311
// Also Custom Input's label could have been empty string as well. But it's not possible to have empty name. So generate a name automatically.

packages/features/bookings/lib/handleNewBooking.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,9 @@ async function handler(
14851485
newBookingData.recurringEventId = reqBody.recurringEventId;
14861486
}
14871487
if (originalRescheduledBooking) {
1488+
newBookingData.metadata = {
1489+
...(typeof originalRescheduledBooking.metadata === "object" && originalRescheduledBooking.metadata),
1490+
};
14881491
newBookingData["paid"] = originalRescheduledBooking.paid;
14891492
newBookingData["fromReschedule"] = originalRescheduledBooking.uid;
14901493
if (newBookingData.attendees?.createMany?.data) {

0 commit comments

Comments
 (0)