Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7b4d5932d | ||
|
|
3c7fc85b34 | ||
|
|
af018f0e8b | ||
|
|
6573f75a83 | ||
|
|
a3b26cd1e7 | ||
|
|
4f99746f68 | ||
|
|
fd76bc9cab | ||
|
|
602c813928 | ||
|
|
2c54ea90f9 | ||
|
+17 |
2698cde311 | ||
|
|
1433ec6946 | ||
|
|
48e5d18ea1 |
@@ -2,4 +2,9 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import "../../../packages/ui/styles/shared-globals.css"
|
||||
@import "../../../packages/ui/styles/shared-globals.css";
|
||||
|
||||
:root {
|
||||
--font-inter: "Inter var";
|
||||
--font-cal: "Cal Sans";
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
Switch,
|
||||
TextArea,
|
||||
TextField,
|
||||
ColorPicker,
|
||||
} from "@calcom/ui";
|
||||
import { FiCode, FiTrello, FiSun, FiArrowLeft, FiChevronRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import ColorPicker from "@components/ui/colorpicker";
|
||||
import Select from "@components/ui/form/Select";
|
||||
|
||||
type EmbedType = "inline" | "floating-popup" | "element-click";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import type { FC } from "react";
|
||||
@@ -39,6 +40,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
seatsPerTimeSlot,
|
||||
ethSignature,
|
||||
}) => {
|
||||
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
const { rescheduleUid } = router.query;
|
||||
@@ -49,122 +51,124 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
setBrand(getComputedStyle(document.documentElement).getPropertyValue("--brand-color").trim());
|
||||
}, []);
|
||||
|
||||
if (!date) return null;
|
||||
|
||||
return (
|
||||
<div className="dark:bg-darkgray-100 mt-8 flex h-full w-full flex-col px-4 text-center sm:mt-0 sm:p-5 md:-mb-5 md:min-w-[200px] lg:min-w-[300px]">
|
||||
<div className="mb-6 flex items-center text-left text-base">
|
||||
<div className="mr-4">
|
||||
<span className="text-bookingdarker dark:text-darkgray-800 font-semibold text-gray-900">
|
||||
{nameOfDay(i18n.language, Number(date.format("d")), "short")}
|
||||
</span>
|
||||
<span className="text-bookinglight font-medium">
|
||||
, {date.toDate().toLocaleString(i18n.language, { month: "short" })} {date.format(" D ")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<ToggleGroup
|
||||
onValueChange={(timeFormat) => onTimeFormatChange(timeFormat === "24")}
|
||||
defaultValue={timeFormat === TimeFormat.TWELVE_HOUR ? "12" : "24"}
|
||||
options={[
|
||||
{ value: "12", label: t("12_hour_short") },
|
||||
{ value: "24", label: t("24_hour_short") },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto sm:block md:h-[364px]">
|
||||
{slots.length > 0 &&
|
||||
slots.map((slot) => {
|
||||
type BookingURL = {
|
||||
pathname: string;
|
||||
query: Record<string, string | number | string[] | undefined>;
|
||||
};
|
||||
const bookingUrl: BookingURL = {
|
||||
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
|
||||
query: {
|
||||
...router.query,
|
||||
date: dayjs.utc(slot.time).tz(timeZone()).format(),
|
||||
type: eventTypeId,
|
||||
slug: eventTypeSlug,
|
||||
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
|
||||
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
|
||||
...(ethSignature ? { ethSignature } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
if (rescheduleUid) {
|
||||
bookingUrl.query.rescheduleUid = rescheduleUid as string;
|
||||
}
|
||||
|
||||
// If event already has an attendee add booking id
|
||||
if (slot.bookingUid) {
|
||||
bookingUrl.query.bookingUid = slot.bookingUid;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-slot-owner={(slot.userIds || []).join(",")} key={`${dayjs(slot.time).format()}`}>
|
||||
{/* ^ data-slot-owner is helpful in debugging and used to identify the owners of the slot. Owners are the users which have the timeslot in their schedule. It doesn't consider if a user has that timeslot booked */}
|
||||
{/* Current there is no way to disable Next.js Links */}
|
||||
{seatsPerTimeSlot && slot.attendees && slot.attendees >= seatsPerTimeSlot ? (
|
||||
<div
|
||||
className={classNames(
|
||||
"text-primary-500 dark:bg-darkgray-200 dark:text-darkgray-900 mb-2 block rounded-sm border bg-white py-2 font-medium opacity-25 dark:border-transparent ",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}>
|
||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href={bookingUrl}
|
||||
prefetch={false}
|
||||
className={classNames(
|
||||
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
|
||||
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand dark:text-darkgray-800 mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}
|
||||
data-testid="time">
|
||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||
{!!seatsPerTimeSlot && (
|
||||
<p
|
||||
className={`${
|
||||
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
|
||||
? "text-rose-600"
|
||||
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-emerald-400"
|
||||
} text-sm`}>
|
||||
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
|
||||
{seatsPerTimeSlot} {t("seats_available")}
|
||||
</p>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{!isLoading && !slots.length && (
|
||||
<div className="-mt-4 flex h-full w-full flex-col content-center items-center justify-center">
|
||||
<h1 className="my-6 text-xl text-black dark:text-white">{t("all_booked_today")}</h1>
|
||||
<div ref={slotPickerRef}>
|
||||
{!!date ? (
|
||||
<div className="dark:bg-darkgray-100 mt-8 flex h-full w-full flex-col px-4 text-center sm:mt-0 sm:p-5 md:-mb-5 md:min-w-[200px] lg:min-w-[300px]">
|
||||
<div className="mb-6 flex items-center text-left text-base">
|
||||
<div className="mr-4">
|
||||
<span className="text-bookingdarker dark:text-darkgray-800 font-semibold text-gray-900">
|
||||
{nameOfDay(i18n.language, Number(date.format("d")), "short")}
|
||||
</span>
|
||||
<span className="text-bookinglight font-medium">
|
||||
, {date.toDate().toLocaleString(i18n.language, { month: "short" })} {date.format(" D ")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<ToggleGroup
|
||||
onValueChange={(timeFormat) => onTimeFormatChange(timeFormat === "24")}
|
||||
defaultValue={timeFormat === TimeFormat.TWELVE_HOUR ? "12" : "24"}
|
||||
options={[
|
||||
{ value: "12", label: t("12_hour_short") },
|
||||
{ value: "24", label: t("24_hour_short") },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-grow overflow-y-auto sm:block md:h-[364px]">
|
||||
{slots.length > 0 &&
|
||||
slots.map((slot) => {
|
||||
type BookingURL = {
|
||||
pathname: string;
|
||||
query: Record<string, string | number | string[] | undefined>;
|
||||
};
|
||||
const bookingUrl: BookingURL = {
|
||||
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
|
||||
query: {
|
||||
...router.query,
|
||||
date: dayjs.utc(slot.time).tz(timeZone()).format(),
|
||||
type: eventTypeId,
|
||||
slug: eventTypeSlug,
|
||||
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
|
||||
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
|
||||
...(ethSignature ? { ethSignature } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
{isLoading && !slots.length && (
|
||||
<>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
if (rescheduleUid) {
|
||||
bookingUrl.query.rescheduleUid = rescheduleUid as string;
|
||||
}
|
||||
|
||||
// If event already has an attendee add booking id
|
||||
if (slot.bookingUid) {
|
||||
bookingUrl.query.bookingUid = slot.bookingUid;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-slot-owner={(slot.userIds || []).join(",")} key={`${dayjs(slot.time).format()}`}>
|
||||
{/* ^ data-slot-owner is helpful in debugging and used to identify the owners of the slot. Owners are the users which have the timeslot in their schedule. It doesn't consider if a user has that timeslot booked */}
|
||||
{/* Current there is no way to disable Next.js Links */}
|
||||
{seatsPerTimeSlot && slot.attendees && slot.attendees >= seatsPerTimeSlot ? (
|
||||
<div
|
||||
className={classNames(
|
||||
"text-primary-500 dark:bg-darkgray-200 dark:text-darkgray-900 mb-2 block rounded-sm border bg-white py-2 font-medium opacity-25 dark:border-transparent ",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}>
|
||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href={bookingUrl}
|
||||
prefetch={false}
|
||||
className={classNames(
|
||||
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
|
||||
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand dark:text-darkgray-800 mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}
|
||||
data-testid="time">
|
||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||
{!!seatsPerTimeSlot && (
|
||||
<p
|
||||
className={`${
|
||||
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
|
||||
? "text-rose-600"
|
||||
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-emerald-400"
|
||||
} text-sm`}>
|
||||
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
|
||||
{seatsPerTimeSlot} {t("seats_available")}
|
||||
</p>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{!isLoading && !slots.length && (
|
||||
<div className="-mt-4 flex h-full w-full flex-col content-center items-center justify-center">
|
||||
<h1 className="my-6 text-xl text-black dark:text-white">{t("all_booked_today")}</h1>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && !slots.length && (
|
||||
<>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FC, ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Badge } from "@calcom/ui";
|
||||
import { FiCheckSquare, FiClock, FiInfo } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import type { EventType } from "@prisma/client";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import DatePicker from "@calcom/features/calendars/DatePicker";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
|
||||
const AvailableTimes = dynamic(() => import("@components/booking/AvailableTimes"));
|
||||
|
||||
const getRefetchInterval = (refetchCount: number): number => {
|
||||
const intervals = [3000, 3000, 5000, 10000, 20000, 30000] as const;
|
||||
return intervals[refetchCount] || intervals[intervals.length - 1];
|
||||
};
|
||||
|
||||
const useSlots = ({
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
startTime,
|
||||
endTime,
|
||||
usernameList,
|
||||
timeZone,
|
||||
duration,
|
||||
enabled = true,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
eventTypeSlug: string;
|
||||
startTime?: Dayjs;
|
||||
endTime?: Dayjs;
|
||||
usernameList: string[];
|
||||
timeZone?: string;
|
||||
duration?: string;
|
||||
enabled?: boolean;
|
||||
}) => {
|
||||
const [refetchCount, setRefetchCount] = useState(0);
|
||||
const refetchInterval = getRefetchInterval(refetchCount);
|
||||
const { data, isLoading, isPaused, fetchStatus } = trpc.viewer.public.slots.getSchedule.useQuery(
|
||||
{
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
usernameList,
|
||||
startTime: startTime?.toISOString() || "",
|
||||
endTime: endTime?.toISOString() || "",
|
||||
timeZone,
|
||||
duration,
|
||||
},
|
||||
{
|
||||
enabled: !!startTime && !!endTime && enabled,
|
||||
refetchInterval,
|
||||
trpc: { context: { skipBatch: true } },
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!!data && fetchStatus === "idle") {
|
||||
setRefetchCount(refetchCount + 1);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fetchStatus, data]);
|
||||
|
||||
// The very first time isPaused is set if auto-fetch is disabled, so isPaused should also be considered a loading state.
|
||||
return { slots: data?.slots || {}, isLoading: isLoading || isPaused };
|
||||
};
|
||||
|
||||
export const SlotPicker = ({
|
||||
eventType,
|
||||
timeFormat,
|
||||
onTimeFormatChange,
|
||||
timeZone,
|
||||
recurringEventCount,
|
||||
users,
|
||||
seatsPerTimeSlot,
|
||||
weekStart = 0,
|
||||
ethSignature,
|
||||
}: {
|
||||
eventType: Pick<
|
||||
EventType & { metadata: z.infer<typeof EventTypeMetaDataSchema> },
|
||||
"id" | "schedulingType" | "slug" | "length" | "metadata"
|
||||
>;
|
||||
timeFormat: TimeFormat;
|
||||
onTimeFormatChange: (is24Hour: boolean) => void;
|
||||
timeZone?: string;
|
||||
seatsPerTimeSlot?: number;
|
||||
recurringEventCount?: number;
|
||||
users: string[];
|
||||
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
ethSignature?: string;
|
||||
}) => {
|
||||
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
||||
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
|
||||
let { duration = eventType.length.toString() } = useRouterQuery("duration");
|
||||
const { date, setQuery: setDate } = useRouterQuery("date");
|
||||
const { month, setQuery: setMonth } = useRouterQuery("month");
|
||||
const router = useRouter();
|
||||
|
||||
if (!eventType.metadata?.multipleDuration) {
|
||||
duration = eventType.length.toString();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return;
|
||||
|
||||
// Etc/GMT is not actually a timeZone, so handle this select option explicitly to prevent a hard crash.
|
||||
if (timeZone === "Etc/GMT") {
|
||||
setBrowsingDate(dayjs.utc(month).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0));
|
||||
if (date) {
|
||||
setSelectedDate(dayjs.utc(date));
|
||||
}
|
||||
} else {
|
||||
// Set the start of the month without shifting time like startOf() may do.
|
||||
setBrowsingDate(
|
||||
dayjs.tz(month, timeZone).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0)
|
||||
);
|
||||
if (date) {
|
||||
// It's important to set the date immediately to the timeZone, dayjs(date) will convert to browsertime.
|
||||
setSelectedDate(dayjs.tz(date, timeZone));
|
||||
}
|
||||
}
|
||||
}, [router.isReady, month, date, duration, timeZone]);
|
||||
|
||||
const { i18n, isLocaleReady } = useLocale();
|
||||
const { slots: monthSlots, isLoading } = useSlots({
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
usernameList: users,
|
||||
startTime:
|
||||
browsingDate === undefined || browsingDate.get("month") === dayjs.tz(undefined, timeZone).get("month")
|
||||
? dayjs.tz(undefined, timeZone).subtract(2, "days").startOf("day")
|
||||
: browsingDate?.startOf("month"),
|
||||
endTime: browsingDate?.endOf("month"),
|
||||
timeZone,
|
||||
duration,
|
||||
});
|
||||
const { slots: selectedDateSlots, isLoading: _isLoadingSelectedDateSlots } = useSlots({
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
usernameList: users,
|
||||
startTime: selectedDate?.startOf("day"),
|
||||
endTime: selectedDate?.endOf("day"),
|
||||
timeZone,
|
||||
duration,
|
||||
/** Prevent refetching is we already have this data from month slots */
|
||||
enabled: !!selectedDate,
|
||||
});
|
||||
|
||||
/** Hide skeleton if we have the slot loaded in the month query */
|
||||
const isLoadingSelectedDateSlots = (() => {
|
||||
if (!selectedDate) return _isLoadingSelectedDateSlots;
|
||||
if (!!selectedDateSlots[selectedDate.format("YYYY-MM-DD")]) return false;
|
||||
if (!!monthSlots[selectedDate.format("YYYY-MM-DD")]) return false;
|
||||
return false;
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DatePicker
|
||||
isLoading={isLoading}
|
||||
className={classNames(
|
||||
"mt-8 px-4 pb-4 sm:mt-0 md:min-w-[300px] md:px-5 lg:min-w-[455px]",
|
||||
selectedDate ? "sm:dark:border-darkgray-200 border-gray-200 sm:border-r sm:p-4 sm:pr-6" : "sm:p-4"
|
||||
)}
|
||||
includedDates={Object.keys(monthSlots).filter((k) => monthSlots[k].length > 0)}
|
||||
locale={isLocaleReady ? i18n.language : "en"}
|
||||
selected={selectedDate}
|
||||
onChange={(newDate) => {
|
||||
setDate(newDate.format("YYYY-MM-DD"));
|
||||
}}
|
||||
onMonthChange={(newMonth) => {
|
||||
setMonth(newMonth.format("YYYY-MM"));
|
||||
}}
|
||||
browsingDate={browsingDate}
|
||||
weekStart={weekStart}
|
||||
/>
|
||||
<AvailableTimes
|
||||
isLoading={isLoadingSelectedDateSlots}
|
||||
slots={
|
||||
selectedDate &&
|
||||
(selectedDateSlots[selectedDate.format("YYYY-MM-DD")] ||
|
||||
monthSlots[selectedDate.format("YYYY-MM-DD")])
|
||||
}
|
||||
date={selectedDate}
|
||||
timeFormat={timeFormat}
|
||||
onTimeFormatChange={onTimeFormatChange}
|
||||
eventTypeId={eventType.id}
|
||||
eventTypeSlug={eventType.slug}
|
||||
seatsPerTimeSlot={seatsPerTimeSlot}
|
||||
recurringCount={recurringEventCount}
|
||||
ethSignature={ethSignature}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FiGlobe } from "@calcom/ui/components/icon";
|
||||
|
||||
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
||||
|
||||
import TimeOptions from "@components/booking/TimeOptions";
|
||||
|
||||
export function TimezoneDropdown({
|
||||
onChangeTimeZone,
|
||||
}: {
|
||||
onChangeTimeZone: (newTimeZone: string) => void;
|
||||
timeZone?: string;
|
||||
}) {
|
||||
const handleSelectTimeZone = (newTimeZone: string) => {
|
||||
onChangeTimeZone(newTimeZone);
|
||||
localStorageTimeZone(newTimeZone);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dark:focus-within:bg-darkgray-200 dark:bg-darkgray-100 dark:hover:bg-darkgray-200 -mx-[2px] !mt-3 flex w-fit items-center rounded-[4px] px-1 py-[2px] text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white">
|
||||
<FiGlobe className="dark:text-darkgray-600 flex h-4 w-4 text-gray-600 ltr:mr-[2px] rtl:ml-[2px]" />
|
||||
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import type { EventType } from "@prisma/client";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useReducer, useState } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
import { z } from "zod";
|
||||
|
||||
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
useEmbedNonStylesConfig,
|
||||
@@ -18,7 +14,6 @@ import {
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import DatePicker from "@calcom/features/calendars/DatePicker";
|
||||
import CustomBranding from "@calcom/lib/CustomBranding";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
@@ -28,222 +23,30 @@ import notEmpty from "@calcom/lib/notEmpty";
|
||||
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
import { FiCreditCard, FiGlobe, FiRefreshCcw } from "@calcom/ui/components/icon";
|
||||
import { FiCreditCard, FiRefreshCcw } from "@calcom/ui/components/icon";
|
||||
|
||||
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
|
||||
import type { Gate, GateState } from "@components/Gates";
|
||||
import Gates from "@components/Gates";
|
||||
import BookingDescription from "@components/booking/BookingDescription";
|
||||
import TimeOptions from "@components/booking/TimeOptions";
|
||||
import { SlotPicker } from "@components/booking/SlotPicker";
|
||||
|
||||
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
|
||||
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
|
||||
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
|
||||
|
||||
const PoweredByCal = dynamic(() => import("@components/ui/PoweredByCal"));
|
||||
const AvailableTimes = dynamic(() => import("@components/booking/AvailableTimes"));
|
||||
|
||||
const useSlots = ({
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
startTime,
|
||||
endTime,
|
||||
usernameList,
|
||||
timeZone,
|
||||
duration,
|
||||
enabled = true,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
eventTypeSlug: string;
|
||||
startTime?: Dayjs;
|
||||
endTime?: Dayjs;
|
||||
usernameList: string[];
|
||||
timeZone?: string;
|
||||
duration?: string;
|
||||
enabled?: boolean;
|
||||
}) => {
|
||||
const { data, isLoading, isPaused } = trpc.viewer.public.slots.getSchedule.useQuery(
|
||||
{
|
||||
eventTypeId,
|
||||
eventTypeSlug,
|
||||
usernameList,
|
||||
startTime: startTime?.toISOString() || "",
|
||||
endTime: endTime?.toISOString() || "",
|
||||
timeZone,
|
||||
duration,
|
||||
},
|
||||
{
|
||||
enabled: !!startTime && !!endTime && enabled,
|
||||
refetchInterval: 3000,
|
||||
trpc: { context: { skipBatch: true } },
|
||||
}
|
||||
);
|
||||
|
||||
// The very first time isPaused is set if auto-fetch is disabled, so isPaused should also be considered a loading state.
|
||||
return { slots: data?.slots || {}, isLoading: isLoading || isPaused };
|
||||
};
|
||||
|
||||
const SlotPicker = ({
|
||||
eventType,
|
||||
timeFormat,
|
||||
onTimeFormatChange,
|
||||
timeZone,
|
||||
recurringEventCount,
|
||||
users,
|
||||
seatsPerTimeSlot,
|
||||
weekStart = 0,
|
||||
ethSignature,
|
||||
}: {
|
||||
eventType: Pick<
|
||||
EventType & { metadata: z.infer<typeof EventTypeMetaDataSchema> },
|
||||
"id" | "schedulingType" | "slug" | "length" | "metadata"
|
||||
>;
|
||||
timeFormat: TimeFormat;
|
||||
onTimeFormatChange: (is24Hour: boolean) => void;
|
||||
timeZone?: string;
|
||||
seatsPerTimeSlot?: number;
|
||||
recurringEventCount?: number;
|
||||
users: string[];
|
||||
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
ethSignature?: string;
|
||||
}) => {
|
||||
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
||||
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
|
||||
let { duration = eventType.length.toString() } = useRouterQuery("duration");
|
||||
const { date, setQuery: setDate } = useRouterQuery("date");
|
||||
const { month, setQuery: setMonth } = useRouterQuery("month");
|
||||
const router = useRouter();
|
||||
|
||||
if (!eventType.metadata?.multipleDuration) {
|
||||
duration = eventType.length.toString();
|
||||
}
|
||||
|
||||
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return;
|
||||
|
||||
// Etc/GMT is not actually a timeZone, so handle this select option explicitly to prevent a hard crash.
|
||||
if (timeZone === "Etc/GMT") {
|
||||
setBrowsingDate(dayjs.utc(month).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0));
|
||||
if (date) {
|
||||
setSelectedDate(dayjs.utc(date));
|
||||
}
|
||||
} else {
|
||||
// Set the start of the month without shifting time like startOf() may do.
|
||||
setBrowsingDate(
|
||||
dayjs.tz(month, timeZone).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0)
|
||||
);
|
||||
if (date) {
|
||||
// It's important to set the date immediately to the timeZone, dayjs(date) will convert to browsertime.
|
||||
setSelectedDate(dayjs.tz(date, timeZone));
|
||||
}
|
||||
}
|
||||
}, [router.isReady, month, date, duration, timeZone]);
|
||||
|
||||
const { i18n, isLocaleReady } = useLocale();
|
||||
const { slots: monthSlots, isLoading } = useSlots({
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
usernameList: users,
|
||||
startTime:
|
||||
browsingDate === undefined || browsingDate.get("month") === dayjs.tz(undefined, timeZone).get("month")
|
||||
? dayjs.tz(undefined, timeZone).subtract(2, "days").startOf("day")
|
||||
: browsingDate?.startOf("month"),
|
||||
endTime: browsingDate?.endOf("month"),
|
||||
timeZone,
|
||||
duration,
|
||||
});
|
||||
const { slots: selectedDateSlots, isLoading: _isLoadingSelectedDateSlots } = useSlots({
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
usernameList: users,
|
||||
startTime: selectedDate?.startOf("day"),
|
||||
endTime: selectedDate?.endOf("day"),
|
||||
timeZone,
|
||||
duration,
|
||||
/** Prevent refetching is we already have this data from month slots */
|
||||
enabled: !!selectedDate,
|
||||
});
|
||||
|
||||
/** Hide skeleton if we have the slot loaded in the month query */
|
||||
const isLoadingSelectedDateSlots = (() => {
|
||||
if (!selectedDate) return _isLoadingSelectedDateSlots;
|
||||
if (!!selectedDateSlots[selectedDate.format("YYYY-MM-DD")]) return false;
|
||||
if (!!monthSlots[selectedDate.format("YYYY-MM-DD")]) return false;
|
||||
return false;
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DatePicker
|
||||
isLoading={isLoading}
|
||||
className={classNames(
|
||||
"mt-8 px-4 pb-4 sm:mt-0 md:min-w-[300px] md:px-5 lg:min-w-[455px]",
|
||||
selectedDate ? "sm:dark:border-darkgray-200 border-gray-200 sm:border-r sm:p-4 sm:pr-6" : "sm:p-4"
|
||||
)}
|
||||
includedDates={Object.keys(monthSlots).filter((k) => monthSlots[k].length > 0)}
|
||||
locale={isLocaleReady ? i18n.language : "en"}
|
||||
selected={selectedDate}
|
||||
onChange={(newDate) => {
|
||||
setDate(newDate.format("YYYY-MM-DD"));
|
||||
}}
|
||||
onMonthChange={(newMonth) => {
|
||||
setMonth(newMonth.format("YYYY-MM"));
|
||||
}}
|
||||
browsingDate={browsingDate}
|
||||
weekStart={weekStart}
|
||||
/>
|
||||
|
||||
<div ref={slotPickerRef}>
|
||||
{selectedDate ? (
|
||||
<AvailableTimes
|
||||
isLoading={isLoadingSelectedDateSlots}
|
||||
slots={
|
||||
selectedDate &&
|
||||
(selectedDateSlots[selectedDate.format("YYYY-MM-DD")] ||
|
||||
monthSlots[selectedDate.format("YYYY-MM-DD")])
|
||||
}
|
||||
date={selectedDate}
|
||||
timeFormat={timeFormat}
|
||||
onTimeFormatChange={onTimeFormatChange}
|
||||
eventTypeId={eventType.id}
|
||||
eventTypeSlug={eventType.slug}
|
||||
seatsPerTimeSlot={seatsPerTimeSlot}
|
||||
recurringCount={recurringEventCount}
|
||||
ethSignature={ethSignature}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function TimezoneDropdown({
|
||||
onChangeTimeZone,
|
||||
}: {
|
||||
onChangeTimeZone: (newTimeZone: string) => void;
|
||||
timeZone?: string;
|
||||
}) {
|
||||
const handleSelectTimeZone = (newTimeZone: string) => {
|
||||
onChangeTimeZone(newTimeZone);
|
||||
localStorageTimeZone(newTimeZone);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dark:focus-within:bg-darkgray-200 dark:bg-darkgray-100 dark:hover:bg-darkgray-200 -mx-[2px] !mt-3 flex w-fit items-center rounded-[4px] px-1 py-[2px] text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white">
|
||||
<FiGlobe className="dark:text-darkgray-600 flex h-4 w-4 text-gray-600 ltr:mr-[2px] rtl:ml-[2px]" />
|
||||
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const Toaster = dynamic(() => import("react-hot-toast").then((mod) => mod.Toaster), { ssr: false });
|
||||
/*const SlotPicker = dynamic(() => import("../SlotPicker").then((mod) => mod.SlotPicker), {
|
||||
ssr: false,
|
||||
loading: () => <div className="mt-8 px-4 pb-4 sm:mt-0 sm:p-4 md:min-w-[300px] md:px-5 lg:min-w-[455px]" />,
|
||||
});*/
|
||||
const TimezoneDropdown = dynamic(() => import("../TimezoneDropdown").then((mod) => mod.TimezoneDropdown), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const dateQuerySchema = z.object({
|
||||
rescheduleUid: z.string().optional().default(""),
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { HexColorInput, HexColorPicker } from "react-colorful";
|
||||
|
||||
import { isValidHexCode, fallBackHex } from "@calcom/lib/CustomBranding";
|
||||
import { Swatch } from "@calcom/ui";
|
||||
|
||||
type Handler = (event: MouseEvent | Event) => void;
|
||||
function useEventListener<
|
||||
KW extends keyof WindowEventMap,
|
||||
KH extends keyof HTMLElementEventMap,
|
||||
T extends HTMLElement | void = void
|
||||
>(
|
||||
eventName: KW | KH,
|
||||
handler: (event: WindowEventMap[KW] | HTMLElementEventMap[KH] | Event) => void,
|
||||
element?: React.RefObject<T>
|
||||
) {
|
||||
// Create a ref that stores handler
|
||||
const savedHandler = useRef<typeof handler>();
|
||||
useEffect(() => {
|
||||
// Define the listening target
|
||||
const targetElement: T | Window = element?.current || window;
|
||||
if (!(targetElement && targetElement.addEventListener)) {
|
||||
return;
|
||||
}
|
||||
// Update saved handler if necessary
|
||||
if (savedHandler.current !== handler) {
|
||||
savedHandler.current = handler;
|
||||
}
|
||||
// Create event listener that calls handler function stored in ref
|
||||
const eventListener: typeof handler = (event) => {
|
||||
// eslint-disable-next-line no-extra-boolean-cast
|
||||
if (!!savedHandler?.current) {
|
||||
savedHandler.current(event);
|
||||
}
|
||||
};
|
||||
targetElement.addEventListener(eventName, eventListener);
|
||||
// Remove event listener on cleanup
|
||||
return () => {
|
||||
targetElement.removeEventListener(eventName, eventListener);
|
||||
};
|
||||
}, [eventName, element, handler]);
|
||||
}
|
||||
|
||||
function useOnClickOutside<T extends HTMLElement = HTMLElement>(
|
||||
ref: React.RefObject<T>,
|
||||
handler: Handler,
|
||||
mouseEvent: "mousedown" | "mouseup" = "mousedown"
|
||||
): void {
|
||||
useEventListener(mouseEvent, (event) => {
|
||||
const el = ref?.current;
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!el || el.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
handler(event);
|
||||
});
|
||||
}
|
||||
export type ColorPickerProps = {
|
||||
defaultValue: string;
|
||||
onChange: (text: string) => void;
|
||||
};
|
||||
|
||||
const ColorPicker = (props: ColorPickerProps) => {
|
||||
const init = !isValidHexCode(props.defaultValue)
|
||||
? fallBackHex(props.defaultValue, false)
|
||||
: props.defaultValue;
|
||||
const [color, setColor] = useState(init);
|
||||
const [isOpen, toggle] = useState(false);
|
||||
const popover = useRef() as React.MutableRefObject<HTMLInputElement>;
|
||||
const close = useCallback(() => toggle(false), []);
|
||||
useOnClickOutside(popover, close);
|
||||
return (
|
||||
<div className="relative mt-1 flex items-center justify-center">
|
||||
<Swatch size="sm" backgroundColor={color} onClick={() => toggle(!isOpen)} />
|
||||
|
||||
{isOpen && (
|
||||
<div className="popover" ref={popover}>
|
||||
<HexColorPicker
|
||||
className="!absolute !top-10 !left-0 !z-10 !h-32 !w-32"
|
||||
color={color}
|
||||
onChange={(val) => {
|
||||
setColor(val);
|
||||
props.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<HexColorInput
|
||||
className="ml-1 block w-full rounded-r-md border border-gray-300 px-3 py-2 sm:text-sm"
|
||||
color={color}
|
||||
onChange={(val) => {
|
||||
setColor(val);
|
||||
props.onChange(val);
|
||||
}}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
||||
@@ -29,7 +29,7 @@ function Select<
|
||||
IsMulti extends boolean = false,
|
||||
Group extends GroupBase<Option> = GroupBase<Option>
|
||||
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
|
||||
const { resolvedTheme, forcedTheme, isReady } = useTheme();
|
||||
const { resolvedTheme, forcedTheme /*isReady*/ } = useTheme();
|
||||
const hasDarkTheme = !forcedTheme && resolvedTheme === "dark";
|
||||
const darkThemeColors = {
|
||||
/** Dark Theme starts */
|
||||
@@ -74,9 +74,9 @@ function Select<
|
||||
};
|
||||
|
||||
// Till we know in JS the theme is ready, we can't render react-select as it would render with light theme instead
|
||||
if (!isReady) {
|
||||
return <input type="text" className={className} />;
|
||||
}
|
||||
// if (!isReady) {
|
||||
// return <input type="text" className={className} />;
|
||||
// }
|
||||
return (
|
||||
<ReactSelect
|
||||
theme={(theme) => ({
|
||||
|
||||
Binary file not shown.
@@ -44,7 +44,8 @@
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.4",
|
||||
"@next/bundle-analyzer": "^12.2.5",
|
||||
"@next/bundle-analyzer": "^13.1.6",
|
||||
"@next/font": "^13.1.1",
|
||||
"@radix-ui/react-avatar": "^1.0.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
@@ -80,6 +81,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lottie-react": "^2.3.1",
|
||||
"markdown-it": "^13.0.1",
|
||||
"md5": "^2.3.0",
|
||||
"memory-cache": "^0.2.0",
|
||||
"micro": "^10.0.1",
|
||||
"mime-types": "^2.1.35",
|
||||
@@ -139,6 +141,7 @@
|
||||
"@types/glidejs__glide": "^3.4.2",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/md5": "^2.3.2",
|
||||
"@types/memory-cache": "^0.2.2",
|
||||
"@types/micro": "7.3.7",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import MarkdownIt from "markdown-it";
|
||||
import type { GetStaticPaths, GetStaticPropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { LocationObject } from "@calcom/app-store/locations";
|
||||
import { privacyFilteredLocations } from "@calcom/app-store/locations";
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import {
|
||||
EventTypeMetaDataSchema,
|
||||
teamMetadataSchema,
|
||||
userMetadata as userMetadataSchema,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -65,10 +54,19 @@ export default function Type(props: AvailabilityPageProps) {
|
||||
|
||||
Type.isThemeSupported = true;
|
||||
|
||||
const paramsSchema = z.object({ type: z.string(), user: z.string() });
|
||||
async function getUserPageProps(context: GetStaticPropsContext) {
|
||||
const { type: slug, user: username } = paramsSchema.parse(context.params);
|
||||
// load server side dependencies
|
||||
const MarkdownIt = await import("markdown-it").then((mod) => mod.default);
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
|
||||
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
|
||||
const { EventTypeMetaDataSchema, teamMetadataSchema } = await import("@calcom/prisma/zod-utils");
|
||||
const { ssgInit } = await import("@server/lib/ssg");
|
||||
|
||||
const { type: slug, user: username } = paramsSchema.parse(context.params);
|
||||
const ssg = await ssgInit(context);
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
username,
|
||||
@@ -191,7 +189,17 @@ async function getUserPageProps(context: GetStaticPropsContext) {
|
||||
}
|
||||
|
||||
async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
|
||||
// load server side dependencies
|
||||
const { getDefaultEvent, getGroupName, getUsernameList } = await import("@calcom/lib/defaultEvents");
|
||||
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
|
||||
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const { EventTypeMetaDataSchema, userMetadata: userMetadataSchema } = await import(
|
||||
"@calcom/prisma/zod-utils"
|
||||
);
|
||||
const { ssgInit } = await import("@server/lib/ssg");
|
||||
const { getAppFromSlug } = await import("@calcom/app-store/utils");
|
||||
|
||||
const ssg = await ssgInit(context);
|
||||
const { type: typeParam, user: userParam } = paramsSchema.parse(context.params);
|
||||
const usernameList = getUsernameList(userParam);
|
||||
@@ -310,8 +318,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const paramsSchema = z.object({ type: z.string(), user: z.string() });
|
||||
|
||||
export const getStaticProps = async (context: GetStaticPropsContext) => {
|
||||
const { user: userParam } = paramsSchema.parse(context.params);
|
||||
// dynamic groups are not generated at build time, but otherwise are probably cached until infinity.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Inter } from "@next/font/google";
|
||||
import localFont from "@next/font/local";
|
||||
import { DefaultSeo } from "next-seo";
|
||||
import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
@@ -14,6 +16,14 @@ import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||
|
||||
import "../styles/globals.css";
|
||||
|
||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||
const calFont = localFont({
|
||||
src: "../fonts/CalSans-SemiBold.woff2",
|
||||
variable: "--font-cal",
|
||||
preload: true,
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
function MyApp(props: AppProps) {
|
||||
const { Component, pageProps, err, router } = props;
|
||||
let pageStatus = "200";
|
||||
@@ -46,6 +56,12 @@ function MyApp(props: AppProps) {
|
||||
id="page-status"
|
||||
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
||||
/>
|
||||
<style jsx global>{`
|
||||
:root {
|
||||
--font-inter: ${interFont.style.fontFamily};
|
||||
--font-cal: ${calFont.style.fontFamily};
|
||||
}
|
||||
`}</style>
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
</Head>
|
||||
|
||||
@@ -39,14 +39,6 @@ class MyDocument extends Document<Props> {
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
|
||||
<meta name="msapplication-TileColor" content="#ff0000" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/Inter-roman.var.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link rel="preload" href="/fonts/cal.ttf" as="font" type="font/ttf" crossOrigin="anonymous" />
|
||||
<Script src="/embed-init-iframe.js" strategy="beforeInteractive" />
|
||||
</Head>
|
||||
|
||||
|
||||
@@ -507,6 +507,10 @@ export default NextAuth({
|
||||
existingUserWithEmail.identityProvider === IdentityProvider.CAL &&
|
||||
(idP === IdentityProvider.GOOGLE || idP === IdentityProvider.SAML)
|
||||
) {
|
||||
await prisma.user.update({
|
||||
where: { email: existingUserWithEmail.email },
|
||||
data: { password: null },
|
||||
});
|
||||
return true;
|
||||
} else if (existingUserWithEmail.identityProvider === IdentityProvider.CAL) {
|
||||
return "/auth/error?error=use-password-login";
|
||||
|
||||
@@ -128,11 +128,17 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
||||
data-testid={"event-type-title-" + type.id}>
|
||||
{type.title}
|
||||
</span>
|
||||
<small
|
||||
className="hidden font-normal leading-4 text-gray-600 sm:inline"
|
||||
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
|
||||
{group.profile.slug ? (
|
||||
<small
|
||||
className="hidden font-normal leading-4 text-gray-600 sm:inline"
|
||||
data-testid={"event-type-slug-" + type.id}>
|
||||
{`/${group.profile.slug}/${type.slug}`}
|
||||
</small>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{readOnly && (
|
||||
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
|
||||
<span className="inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2 rtl:ml-2">
|
||||
{t("readonly")}
|
||||
</span>
|
||||
)}
|
||||
@@ -557,6 +563,18 @@ const EventTypeListHeading = ({
|
||||
membershipCount,
|
||||
teamId,
|
||||
}: EventTypeListHeadingProps): JSX.Element => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
const publishTeamMutation = trpc.viewer.teams.publish.useMutation({
|
||||
onSuccess(data) {
|
||||
router.push(data.url);
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(error.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Avatar
|
||||
@@ -588,6 +606,13 @@ const EventTypeListHeading = ({
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
{!profile?.slug && !!teamId && (
|
||||
<button onClick={() => publishTeamMutation.mutate({ teamId })}>
|
||||
<Badge variant="gray" className="mb-1 -ml-2">
|
||||
{t("upgrade")}
|
||||
</Badge>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import "../../../packages/ui/styles/shared-globals.css";
|
||||
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Script from "next/script";
|
||||
|
||||
import { getEventTypeAppData } from "@calcom/app-store/_utils/getEventTypeAppData";
|
||||
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
|
||||
import type { appDataSchemas } from "./apps.schemas.generated";
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { EventTypeModel } from "@calcom/prisma/zod";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export type EventTypeApps = NonNullable<NonNullable<z.infer<typeof EventTypeMetaDataSchema>>["apps"]>;
|
||||
export type EventTypeAppsList = keyof EventTypeApps;
|
||||
|
||||
export const getEventTypeAppData = <T extends EventTypeAppsList>(
|
||||
eventType: Pick<z.infer<typeof EventTypeModel>, "price" | "currency" | "metadata">,
|
||||
appId: T,
|
||||
forcedGet?: boolean
|
||||
): EventTypeApps[T] => {
|
||||
const metadata = eventType.metadata;
|
||||
const appMetadata = metadata?.apps && metadata.apps[appId];
|
||||
if (appMetadata) {
|
||||
const allowDataGet = forcedGet ? true : appMetadata.enabled;
|
||||
return allowDataGet ? appMetadata : null;
|
||||
}
|
||||
|
||||
// Backward compatibility for existing event types.
|
||||
// TODO: After the new AppStore EventType App flow is stable, write a migration to migrate metadata to new format which will let us remove this compatibility code
|
||||
// Migration isn't being done right now, to allow a revert if needed
|
||||
const legacyAppsData = {
|
||||
stripe: {
|
||||
enabled: eventType.price > 0,
|
||||
// Price default is 0 in DB. So, it would always be non nullish.
|
||||
price: eventType.price,
|
||||
// Currency default is "usd" in DB.So, it would also be available always
|
||||
currency: eventType.currency,
|
||||
},
|
||||
rainbow: {
|
||||
enabled: !!(eventType.metadata?.smartContractAddress && eventType.metadata?.blockchainId),
|
||||
smartContractAddress: eventType.metadata?.smartContractAddress || "",
|
||||
blockchainId: eventType.metadata?.blockchainId || 0,
|
||||
},
|
||||
giphy: {
|
||||
enabled: !!eventType.metadata?.giphyThankYouPage,
|
||||
thankYouPage: eventType.metadata?.giphyThankYouPage || "",
|
||||
},
|
||||
} as const;
|
||||
|
||||
// TODO: This assertion helps typescript hint that only one of the app's data can be returned
|
||||
const legacyAppData = legacyAppsData[appId as Extract<T, keyof typeof legacyAppsData>];
|
||||
const allowDataGet = forcedGet ? true : legacyAppData?.enabled;
|
||||
return allowDataGet ? legacyAppData : null;
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import routing_forms_config_json from "./ee/routing-forms/config.json";
|
||||
import { metadata as exchange2013calendar__metadata_ts } from "./exchange2013calendar/_metadata";
|
||||
import { metadata as exchange2016calendar__metadata_ts } from "./exchange2016calendar/_metadata";
|
||||
import exchangecalendar_config_json from "./exchangecalendar/config.json";
|
||||
import facetime_config_json from "./facetime/config.json";
|
||||
import fathom_config_json from "./fathom/config.json";
|
||||
import ga4_config_json from "./ga4/config.json";
|
||||
import { metadata as giphy__metadata_ts } from "./giphy/_metadata";
|
||||
@@ -68,6 +69,7 @@ export const appStoreMetadata = {
|
||||
exchange2013calendar: exchange2013calendar__metadata_ts,
|
||||
exchange2016calendar: exchange2016calendar__metadata_ts,
|
||||
exchangecalendar: exchangecalendar_config_json,
|
||||
facetime: facetime_config_json,
|
||||
fathom: fathom_config_json,
|
||||
ga4: ga4_config_json,
|
||||
giphy: giphy__metadata_ts,
|
||||
|
||||
@@ -13,6 +13,7 @@ export const apiHandlers = {
|
||||
exchange2013calendar: import("./exchange2013calendar/api"),
|
||||
exchange2016calendar: import("./exchange2016calendar/api"),
|
||||
exchangecalendar: import("./exchangecalendar/api"),
|
||||
facetime: import("./facetime/api"),
|
||||
fathom: import("./fathom/api"),
|
||||
ga4: import("./ga4/api"),
|
||||
giphy: import("./giphy/api"),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
items:
|
||||
- /api/app-store/facetime/facetime1.png
|
||||
- /api/app-store/facetime/facetime2.png
|
||||
---
|
||||
|
||||
With FaceTime, it’s easy to stay in touch. You can make audio and video calls with up to 32 people, share your screen, enjoy films and music together, and more.
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import { createDefaultInstallation } from "../../_utils/installation";
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: ({ appType, user, slug }) =>
|
||||
createDefaultInstallation({ appType, userId: user.id, slug, key: {} }),
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Facetime",
|
||||
"title": "Facetime",
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"imageSrc": "/api/app-store/facetime/icon.svg",
|
||||
"logo": "/api/app-store/facetime/icon.svg",
|
||||
"url": "https://cal.com/apps/facetime",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
"publisher": "Mythie",
|
||||
"email": "help@cal.com",
|
||||
"description": "Facetime makes it super simple for collaborating teams to jump on a video call.",
|
||||
"__createdUsingCli": true,
|
||||
"appData": {
|
||||
"location": {
|
||||
"linkType": "static",
|
||||
"type": "integrations:facetime_video",
|
||||
"label": "Facetime",
|
||||
"organizerInputPlaceholder": "https://facetime.apple.com/join... link copied from the FaceTime app",
|
||||
"urlRegExp": "^https?:\\/\\/facetime\\.apple\\.com\\/join.+$"
|
||||
}
|
||||
},
|
||||
"isTemplate": false
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/facetime",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"description": "Facetime makes it super simple for collaborating teams to jump on a video call.",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 510 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 600 600">
|
||||
<linearGradient id="a" x1="-137.5424" x2="-133.9618" y1="785.878" y2="197.7213" gradientTransform="matrix(1 0 0 -1 435.7924 798.4074)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5df777"/>
|
||||
<stop offset="1" stop-color="#0abc28"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#a)" d="M137.7 0h324.6C538.6 0 600 61.4 600 137.7v324.6c0 76.3-61.4 137.7-137.7 137.7H137.7C61.4 600 0 538.6 0 462.3V137.7C0 61.4 61.4 0 137.7 0z"/>
|
||||
<path fill="#fff" d="M91.5 227.3v146.1c0 31.9 25.9 57.7 57.7 57.7H325c31.9 0 57.7-25.9 57.7-57.7V227.3c0-31.9-25.9-57.7-57.7-57.7H149.3c-31.9-.1-57.8 25.8-57.8 57.7zm379.3-39.1-66.2 54.6c-5.9 4.9-9.3 12.1-9.3 19.7v75.6c0 7.6 3.3 14.7 9.1 19.6l66.2 55.6c15.1 12.6 38 1.9 38-17.7V206c.1-19.5-22.7-30.3-37.8-17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 600 600">
|
||||
<linearGradient id="a" x1="-137.5424" x2="-133.9618" y1="785.878" y2="197.7213" gradientTransform="matrix(1 0 0 -1 435.7924 798.4074)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5df777"/>
|
||||
<stop offset="1" stop-color="#0abc28"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#a)" d="M137.7 0h324.6C538.6 0 600 61.4 600 137.7v324.6c0 76.3-61.4 137.7-137.7 137.7H137.7C61.4 600 0 538.6 0 462.3V137.7C0 61.4 61.4 0 137.7 0z"/>
|
||||
<path fill="#fff" d="M91.5 227.3v146.1c0 31.9 25.9 57.7 57.7 57.7H325c31.9 0 57.7-25.9 57.7-57.7V227.3c0-31.9-25.9-57.7-57.7-57.7H149.3c-31.9-.1-57.8 25.8-57.8 57.7zm379.3-39.1-66.2 54.6c-5.9 4.9-9.3 12.1-9.3 19.7v75.6c0 7.6 3.3 14.7 9.1 19.6l66.2 55.6c15.1 12.6 38 1.9 38-17.7V206c.1-19.5-22.7-30.3-37.8-17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -6,6 +6,7 @@ import * as dailyvideo from "./dailyvideo";
|
||||
import * as exchange2013calendar from "./exchange2013calendar";
|
||||
import * as exchange2016calendar from "./exchange2016calendar";
|
||||
import * as exchangecalendar from "./exchangecalendar";
|
||||
import * as facetime from "./facetime";
|
||||
import * as giphy from "./giphy";
|
||||
import * as googlecalendar from "./googlecalendar";
|
||||
import * as googlevideo from "./googlevideo";
|
||||
@@ -52,6 +53,7 @@ const appStore = {
|
||||
exchange2013calendar,
|
||||
exchange2016calendar,
|
||||
exchangecalendar,
|
||||
facetime,
|
||||
};
|
||||
|
||||
export default appStore;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import type { TFunction } from "next-i18next";
|
||||
import type { z } from "zod";
|
||||
|
||||
// If you import this file on any app it should produce circular dependency
|
||||
// import appStore from "./index";
|
||||
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { defaultLocations } from "@calcom/app-store/locations";
|
||||
import type { EventTypeModel } from "@calcom/prisma/zod";
|
||||
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { App, AppMeta } from "@calcom/types/App";
|
||||
|
||||
export * from "./_utils/getEventTypeAppData";
|
||||
|
||||
type LocationOption = {
|
||||
label: string;
|
||||
value: EventLocationType["type"];
|
||||
@@ -18,9 +17,6 @@ type LocationOption = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type EventTypeApps = NonNullable<NonNullable<z.infer<typeof EventTypeMetaDataSchema>>["apps"]>;
|
||||
export type EventTypeAppsList = keyof EventTypeApps;
|
||||
|
||||
const ALL_APPS_MAP = Object.keys(appStoreMetadata).reduce((store, key) => {
|
||||
const metadata = appStoreMetadata[key as keyof typeof appStoreMetadata] as AppMeta;
|
||||
if (metadata.logo && !metadata.logo.includes("/")) {
|
||||
@@ -193,44 +189,4 @@ export function getAppFromSlug(slug: string | undefined): AppMeta | undefined {
|
||||
return ALL_APPS.find((app) => app.slug === slug);
|
||||
}
|
||||
|
||||
export const getEventTypeAppData = <T extends EventTypeAppsList>(
|
||||
eventType: Pick<z.infer<typeof EventTypeModel>, "price" | "currency" | "metadata">,
|
||||
appId: T,
|
||||
forcedGet?: boolean
|
||||
): EventTypeApps[T] => {
|
||||
const metadata = eventType.metadata;
|
||||
const appMetadata = metadata?.apps && metadata.apps[appId];
|
||||
if (appMetadata) {
|
||||
const allowDataGet = forcedGet ? true : appMetadata.enabled;
|
||||
return allowDataGet ? appMetadata : null;
|
||||
}
|
||||
|
||||
// Backward compatibility for existing event types.
|
||||
// TODO: After the new AppStore EventType App flow is stable, write a migration to migrate metadata to new format which will let us remove this compatibility code
|
||||
// Migration isn't being done right now, to allow a revert if needed
|
||||
const legacyAppsData = {
|
||||
stripe: {
|
||||
enabled: eventType.price > 0,
|
||||
// Price default is 0 in DB. So, it would always be non nullish.
|
||||
price: eventType.price,
|
||||
// Currency default is "usd" in DB.So, it would also be available always
|
||||
currency: eventType.currency,
|
||||
},
|
||||
rainbow: {
|
||||
enabled: !!(eventType.metadata?.smartContractAddress && eventType.metadata?.blockchainId),
|
||||
smartContractAddress: eventType.metadata?.smartContractAddress || "",
|
||||
blockchainId: eventType.metadata?.blockchainId || 0,
|
||||
},
|
||||
giphy: {
|
||||
enabled: !!eventType.metadata?.giphyThankYouPage,
|
||||
thankYouPage: eventType.metadata?.giphyThankYouPage || "",
|
||||
},
|
||||
} as const;
|
||||
|
||||
// TODO: This assertion helps typescript hint that only one of the app's data can be returned
|
||||
const legacyAppData = legacyAppsData[appId as Extract<T, keyof typeof legacyAppsData>];
|
||||
const allowDataGet = forcedGet ? true : legacyAppData?.enabled;
|
||||
return allowDataGet ? legacyAppData : null;
|
||||
};
|
||||
|
||||
export default getApps;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
const { fontFamily } = require("tailwindcss/defaultTheme");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
@@ -12,10 +13,6 @@ module.exports = {
|
||||
],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
fontFamily: {
|
||||
cal: ['"Cal Sans"', "sans-serif"],
|
||||
sans: ['"Inter var"', "sans-serif"],
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
/* your primary brand color */
|
||||
@@ -162,9 +159,9 @@ module.exports = {
|
||||
dropdown: "0px 2px 6px -1px rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
fontFamily: {
|
||||
cal: ['"Cal Sans"', "sans-serif"],
|
||||
cal: ["var(--font-cal)", ...fontFamily.serif],
|
||||
sans: ["var(--font-inter)", ...fontFamily.sans],
|
||||
mono: ["Roboto Mono", "monospace"],
|
||||
sans: ['"Inter var"', "sans-serif"],
|
||||
},
|
||||
maxHeight: (theme) => ({
|
||||
0: "0",
|
||||
|
||||
@@ -8,14 +8,8 @@ import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
|
||||
import { MeetLocationType } from "@calcom/app-store/locations";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Attendee } from "@calcom/prisma/client";
|
||||
import { createdEventSchema } from "@calcom/prisma/zod-utils";
|
||||
import type {
|
||||
AdditionalInformation,
|
||||
CalendarEvent,
|
||||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { Event } from "@calcom/types/Event";
|
||||
import type {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BookingStatus, Credential, SelectedCalendar } from "@prisma/client";
|
||||
import { BookingStatus, Credential } from "@prisma/client";
|
||||
|
||||
import { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { APP_NAME, WEBAPP_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
|
||||
@@ -37,26 +37,27 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!team) {
|
||||
const prevTeam = await prisma.team.findFirstOrThrow({ where: { id } });
|
||||
const metadata = teamMetadataSchema.parse(prevTeam.metadata);
|
||||
/** We save the metadata first to prevent duplicate payments */
|
||||
team = await prisma.team.update({
|
||||
where: { id },
|
||||
data: {
|
||||
metadata: {
|
||||
paymentId: checkoutSession.id,
|
||||
subscriptionId: subscription.id || null,
|
||||
subscriptionItemId: subscription.items.data[0].id || null,
|
||||
},
|
||||
},
|
||||
});
|
||||
/** Legacy teams already have a slug, this will allow them to upgrade as well */
|
||||
const slug = prevTeam.slug || metadata?.requestedSlug;
|
||||
if (!slug) throw new HttpError({ statusCode: 400, message: "Missing team slug" });
|
||||
try {
|
||||
/** We save the metadata first to prevent duplicate payments */
|
||||
await prisma.team.update({
|
||||
where: { id },
|
||||
data: {
|
||||
metadata: {
|
||||
paymentId: checkoutSession.id,
|
||||
subscriptionId: subscription.id || null,
|
||||
subscriptionItemId: subscription.items.data[0].id || null,
|
||||
},
|
||||
},
|
||||
});
|
||||
/** Then we try to upgrade the slug, which may fail if a conflict came up since team creation */
|
||||
team = await prisma.team.update({ where: { id }, data: { slug } });
|
||||
} catch (error) {
|
||||
const { message, statusCode } = getRequestedSlugError(error, slug);
|
||||
return res.status(statusCode).json({ message });
|
||||
if (slug) {
|
||||
try {
|
||||
/** Then we try to upgrade the slug, which may fail if a conflict came up since team creation */
|
||||
team = await prisma.team.update({ where: { id }, data: { slug } });
|
||||
} catch (error) {
|
||||
const { message, statusCode } = getRequestedSlugError(error, slug);
|
||||
return res.status(statusCode).json({ message });
|
||||
}
|
||||
}
|
||||
|
||||
// Sync Services: Close.com
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Card, showToast } from "@calcom/ui";
|
||||
import { FiUserPlus, FiUsers, FiUnlock, FiEdit } from "@calcom/ui/components/icon";
|
||||
import { FiUserPlus, FiUsers, FiEdit } from "@calcom/ui/components/icon";
|
||||
|
||||
import TeamListItem from "./TeamListItem";
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import dayjs from "@calcom/dayjs";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { getSenderId } from "../alphanumericSenderIdSupport";
|
||||
import * as twilio from "./smsProviders/twilioProvider";
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
TextAreaField,
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
import { FiPlus, FiChevronDown } from "@calcom/ui/components/icon";
|
||||
import { FiPlus } from "@calcom/ui/components/icon";
|
||||
|
||||
import { DuplicateDialog } from "./DuplicateDialog";
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
FiChevronRight,
|
||||
FiPlus,
|
||||
FiMenu,
|
||||
FiExternalLink,
|
||||
} from "@calcom/ui/components/icon";
|
||||
|
||||
const tabs: VerticalTabItemProps[] = [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import isCalcom from "@calcom/lib/isCalcom";
|
||||
import { EmptyScreen } from "@calcom/ui";
|
||||
import { FiUsers } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -33,13 +33,13 @@ export function UpgradeTip({
|
||||
isParentLoading?: ReactNode;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const { isLoading, hasPaidPlan } = useHasPaidPlan();
|
||||
const { isLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
if (hasPaidPlan) return children;
|
||||
if (hasTeamPlan) return children;
|
||||
|
||||
if (isParentLoading || isLoading) return <>{isParentLoading}</>;
|
||||
|
||||
if (!isCalcom)
|
||||
if (IS_SELF_HOSTED)
|
||||
return (
|
||||
<EmptyScreen
|
||||
Icon={FiUsers}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Prisma } from "@prisma/client";
|
||||
import ICAL from "ical.js";
|
||||
import type { Attendee, DateArray, DurationObject, Person } from "ics";
|
||||
import { createEvent } from "ics";
|
||||
import type { DAVAccount } from "tsdav";
|
||||
import type { DAVAccount, DAVCalendar } from "tsdav";
|
||||
import {
|
||||
createAccount,
|
||||
createCalendarObject,
|
||||
@@ -421,17 +421,20 @@ export default abstract class BaseCalendarService implements Calendar {
|
||||
try {
|
||||
const account = await this.getAccount();
|
||||
|
||||
const calendars = await fetchCalendars({
|
||||
const calendars = (await fetchCalendars({
|
||||
account,
|
||||
headers: this.headers,
|
||||
});
|
||||
})) /** @url https://github.com/natelindev/tsdav/pull/139 */ as (Omit<DAVCalendar, "displayName"> & {
|
||||
displayName?: string | Record<string, unknown>;
|
||||
})[];
|
||||
|
||||
return calendars.reduce<IntegrationCalendar[]>((newCalendars, calendar) => {
|
||||
if (!calendar.components?.includes("VEVENT")) return newCalendars;
|
||||
|
||||
newCalendars.push({
|
||||
externalId: calendar.url,
|
||||
name: calendar.displayName ?? "",
|
||||
/** @url https://github.com/calcom/cal.com/issues/7186 */
|
||||
name: typeof calendar.displayName === "string" ? calendar.displayName : "",
|
||||
primary: event?.destinationCalendar?.externalId
|
||||
? event.destinationCalendar.externalId === calendar.url
|
||||
: false,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import crypto from "crypto";
|
||||
import md5Parser from "md5";
|
||||
|
||||
export const defaultAvatarSrc = function ({ email, md5 }: { md5?: string; email?: string }) {
|
||||
if (!email && !md5) return "";
|
||||
|
||||
if (email && !md5) {
|
||||
md5 = crypto.createHash("md5").update(email).digest("hex");
|
||||
md5 = md5Parser(email);
|
||||
}
|
||||
|
||||
return `https://www.gravatar.com/avatar/${md5}?s=160&d=mp&r=PG`;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import { getEventTypeAppData } from "@calcom/app-store/_utils/getEventTypeAppData";
|
||||
import type { appDataSchemas } from "@calcom/app-store/apps.schemas.generated";
|
||||
import type { appDataSchema } from "@calcom/app-store/stripepayment/zod";
|
||||
import type { EventTypeAppsList } from "@calcom/app-store/utils";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
|
||||
export default function getPaymentAppData(
|
||||
eventType: Parameters<typeof getEventTypeAppData>[0],
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import { IS_SELF_HOSTED } from "../constants";
|
||||
import hasKeyInMetadata from "../hasKeyInMetadata";
|
||||
|
||||
export function useHasPaidPlan() {
|
||||
if (IS_SELF_HOSTED) return { isLoading: false, hasPaidPlan: true };
|
||||
|
||||
const { data: hasTeamPlan, isLoading: isLoadingTeamQuery } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
const { data: user, isLoading: isLoadingUserQuery } = trpc.viewer.me.useQuery();
|
||||
@@ -22,4 +25,10 @@ export function useTeamInvites() {
|
||||
return { isLoading: listInvites.isLoading, listInvites: listInvites.data };
|
||||
}
|
||||
|
||||
export function useHasTeamPlan() {
|
||||
const { data: hasTeamPlan, isLoading } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||
|
||||
return { isLoading, hasTeamPlan: hasTeamPlan?.hasTeamPlan };
|
||||
}
|
||||
|
||||
export default useHasPaidPlan;
|
||||
|
||||
@@ -197,5 +197,12 @@
|
||||
"slug": "basic",
|
||||
"type": "basic_other",
|
||||
"isTemplate": true
|
||||
},
|
||||
{
|
||||
"dirName": "facetime",
|
||||
"categories": ["video"],
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"isTemplate": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -338,7 +338,7 @@ export const eventTypesRouter = router({
|
||||
profile: {
|
||||
name: membership.team.name,
|
||||
image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
|
||||
slug: "team/" + membership.team.slug,
|
||||
slug: membership.team.slug ? "team/" + membership.team.slug : null,
|
||||
},
|
||||
metadata: {
|
||||
membershipCount: membership.team.members.length,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Prisma, PrismaPromise } from "@prisma/client";
|
||||
import {
|
||||
Prisma,
|
||||
PrismaPromise,
|
||||
WorkflowTemplates,
|
||||
WorkflowActions,
|
||||
WorkflowTriggerEvents,
|
||||
@@ -32,15 +31,15 @@ import {
|
||||
verifyPhoneNumber,
|
||||
sendVerificationCode,
|
||||
} from "@calcom/features/ee/workflows/lib/reminders/verifyPhoneNumber";
|
||||
import { SENDER_ID } from "@calcom/lib/constants";
|
||||
import { IS_SELF_HOSTED, SENDER_ID } from "@calcom/lib/constants";
|
||||
import { SENDER_NAME } from "@calcom/lib/constants";
|
||||
// import { getErrorFromUnknown } from "@calcom/lib/errors";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { WorkflowStep } from "@calcom/prisma/client";
|
||||
import type { WorkflowStep } from "@calcom/prisma/client";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { router, authedProcedure, authedRateLimitedProcedure } from "../../trpc";
|
||||
import { router, authedProcedure } from "../../trpc";
|
||||
import { viewerTeamsRouter } from "./teams";
|
||||
|
||||
function getSender(
|
||||
@@ -1127,6 +1126,6 @@ action === WorkflowActions.EMAIL_ADDRESS*/
|
||||
getWorkflowActionOptions: authedProcedure.query(async ({ ctx }) => {
|
||||
const { hasTeamPlan } = await viewerTeamsRouter.createCaller(ctx).hasTeamPlan();
|
||||
const t = await getTranslation(ctx.user.locale, "common");
|
||||
return getWorkflowActionOptions(t, !!hasTeamPlan);
|
||||
return getWorkflowActionOptions(t, IS_SELF_HOSTED || !!hasTeamPlan);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -50,13 +50,6 @@ export const Slider = <T extends string | unknown>({
|
||||
|
||||
return (
|
||||
<div className={`mb-2 ${className}`}>
|
||||
<style jsx global>
|
||||
{`
|
||||
.glide__slide {
|
||||
height: auto !important;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className="glide" ref={glide}>
|
||||
<div className="flex cursor-default items-center pb-3">
|
||||
{isLocaleReady ? (
|
||||
|
||||
@@ -1,61 +1,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { useState } from "react";
|
||||
import { HexColorInput, HexColorPicker } from "react-colorful";
|
||||
|
||||
import { fallBackHex, isValidHexCode } from "@calcom/lib/CustomBranding";
|
||||
|
||||
import { Swatch } from "../../..";
|
||||
|
||||
type Handler = (event: MouseEvent | Event) => void;
|
||||
function useEventListener<
|
||||
KW extends keyof WindowEventMap,
|
||||
KH extends keyof HTMLElementEventMap,
|
||||
T extends HTMLElement | void = void
|
||||
>(
|
||||
eventName: KW | KH,
|
||||
handler: (event: WindowEventMap[KW] | HTMLElementEventMap[KH] | Event) => void,
|
||||
element?: React.RefObject<T>
|
||||
) {
|
||||
// Create a ref that stores handler
|
||||
const savedHandler = useRef<typeof handler>();
|
||||
useEffect(() => {
|
||||
// Define the listening target
|
||||
const targetElement: T | Window = element?.current || window;
|
||||
if (!(targetElement && targetElement.addEventListener)) {
|
||||
return;
|
||||
}
|
||||
// Update saved handler if necessary
|
||||
if (savedHandler.current !== handler) {
|
||||
savedHandler.current = handler;
|
||||
}
|
||||
// Create event listener that calls handler function stored in ref
|
||||
const eventListener: typeof handler = (event) => {
|
||||
// eslint-disable-next-line no-extra-boolean-cast
|
||||
if (!!savedHandler?.current) {
|
||||
savedHandler.current(event);
|
||||
}
|
||||
};
|
||||
targetElement.addEventListener(eventName, eventListener);
|
||||
// Remove event listener on cleanup
|
||||
return () => {
|
||||
targetElement.removeEventListener(eventName, eventListener);
|
||||
};
|
||||
}, [eventName, element, handler]);
|
||||
}
|
||||
|
||||
function useOnClickOutside<T extends HTMLElement = HTMLElement>(
|
||||
ref: React.RefObject<T>,
|
||||
handler: Handler,
|
||||
mouseEvent: "mousedown" | "mouseup" = "mousedown"
|
||||
): void {
|
||||
useEventListener(mouseEvent, (event) => {
|
||||
const el = ref?.current;
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!el || el.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
handler(event);
|
||||
});
|
||||
}
|
||||
export type ColorPickerProps = {
|
||||
defaultValue: string;
|
||||
onChange: (text: string) => void;
|
||||
@@ -66,27 +14,35 @@ const ColorPicker = (props: ColorPickerProps) => {
|
||||
? fallBackHex(props.defaultValue, false)
|
||||
: props.defaultValue;
|
||||
const [color, setColor] = useState(init);
|
||||
const [isOpen, toggle] = useState(false);
|
||||
const popover = useRef() as React.MutableRefObject<HTMLInputElement>;
|
||||
const close = useCallback(() => toggle(false), []);
|
||||
useOnClickOutside(popover, close);
|
||||
|
||||
return (
|
||||
<div className="relative mt-1 flex items-center justify-center">
|
||||
<Swatch size="sm" backgroundColor={color} onClick={() => toggle(!isOpen)} />
|
||||
{isOpen && (
|
||||
<div className="popover" ref={popover}>
|
||||
<HexColorPicker
|
||||
className="!absolute !top-10 !left-0 !z-10 !h-32 !w-32"
|
||||
color={color}
|
||||
onChange={(val) => {
|
||||
setColor(val);
|
||||
props.onChange(val);
|
||||
}}
|
||||
/>
|
||||
<div className=" mt-1 flex items-center justify-center">
|
||||
<Popover.Root>
|
||||
<div className="flex items-center rounded-l-md border border-r-0 border-gray-300 p-1.5">
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
className="h-6 w-6 rounded-sm border-2 border-gray-900"
|
||||
aria-label="pick colors"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
</Popover.Trigger>
|
||||
</div>
|
||||
)}
|
||||
<Popover.Portal>
|
||||
<Popover.Content align="center" sideOffset={10}>
|
||||
<HexColorPicker
|
||||
color={color}
|
||||
className="!h-32 !w-32"
|
||||
onChange={(val) => {
|
||||
setColor(val);
|
||||
props.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
|
||||
<HexColorInput
|
||||
className="block w-full border border-gray-300 px-3 py-2 ltr:ml-1 ltr:rounded-r-md rtl:mr-1 rtl:rounded-l-md sm:text-sm"
|
||||
className="block w-full border border-gray-300 px-3 py-2 ltr:rounded-r-md rtl:rounded-l-md sm:text-sm"
|
||||
color={color}
|
||||
onChange={(val) => {
|
||||
setColor(val);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
export type SwatchProps = {
|
||||
size?: "base" | "sm" | "lg";
|
||||
backgroundColor: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
const Swatch = (props: SwatchProps) => {
|
||||
const { size, backgroundColor, onClick } = props;
|
||||
return (
|
||||
<div className="border-2 border-gray-200 p-1">
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{ backgroundColor }}
|
||||
className={classNames(
|
||||
"cursor-pointer",
|
||||
size === "sm" && "h-6 w-6 rounded-sm",
|
||||
size === "base" && "h-16 w-16 rounded-sm",
|
||||
size === "lg" && "h-24 w-24 rounded-sm"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Swatch;
|
||||
@@ -1 +0,0 @@
|
||||
export { default as Swatch } from "./Swatch";
|
||||
@@ -109,7 +109,6 @@ export {
|
||||
export type { DialogProps, ConfirmationDialogContentProps } from "./components/dialog";
|
||||
export { showToast } from "./components/toast"; // We don't export the toast components as they are only used in local storybook file
|
||||
export { Meta, MetaProvider, useMeta } from "./components/meta";
|
||||
export { Swatch } from "./components/swatch";
|
||||
export { ShellSubHeading } from "./components/layout";
|
||||
|
||||
/** ⬇️ TODO - Move these to components */
|
||||
|
||||
@@ -3802,10 +3802,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86"
|
||||
integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==
|
||||
|
||||
"@floating-ui/core@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.0.tgz#ae7ae7923d41f3d84cb2fd88740a89436610bbec"
|
||||
integrity sha512-GHUXPEhMEmTpnpIfesFA2KAoMJPb1SPQw964tToQwt+BbGXdhqTCWT1rOb0VURGylsxsYxiGMnseJ3IlclVpVA==
|
||||
"@floating-ui/core@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.1.tgz#074182a1d277f94569c50a6b456e62585d463c8e"
|
||||
integrity sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==
|
||||
|
||||
"@floating-ui/dom@^0.5.3":
|
||||
version "0.5.4"
|
||||
@@ -3814,12 +3814,12 @@
|
||||
dependencies:
|
||||
"@floating-ui/core" "^0.7.3"
|
||||
|
||||
"@floating-ui/dom@^1.1.1":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.0.tgz#a60212069cc58961c478037c30eba4b191c75316"
|
||||
integrity sha512-QXzg57o1cjLz3cGETzKXjI3kx1xyS49DW9l7kV2jw2c8Yftd434t2hllX0sVGn2Q8MtcW/4pNm8bfE1/4n6mng==
|
||||
"@floating-ui/dom@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.1.tgz#8f93906e1a3b9f606ce78afb058e874344dcbe07"
|
||||
integrity sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.2.0"
|
||||
"@floating-ui/core" "^1.2.1"
|
||||
|
||||
"@floating-ui/react-dom@0.7.2":
|
||||
version "0.7.2"
|
||||
@@ -3830,11 +3830,11 @@
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
"@floating-ui/react-dom@^1.0.0":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.2.2.tgz#ed256992fd44fcfcddc96da68b4b92f123d61871"
|
||||
integrity sha512-DbmFBLwFrZhtXgCI2ra7wXYT8L2BN4/4AMQKyu05qzsVji51tXOfF36VE2gpMB6nhJGHa85PdEg75FB4+vnLFQ==
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
|
||||
integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.1.1"
|
||||
"@floating-ui/dom" "^1.2.1"
|
||||
|
||||
"@formatjs/ecma402-abstract@1.11.4":
|
||||
version "1.11.4"
|
||||
@@ -3934,9 +3934,9 @@
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@headlessui/react@^1.5.0":
|
||||
version "1.7.10"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.10.tgz#0971a33843a76f2bf4b801a43f76e3730fe15884"
|
||||
integrity sha512-1m66h/5eayTEZVT2PI13/2PG3EVC7a9XalmUtVSC8X76pcyKYMuyX1XAL2RUtCr8WhoMa/KrDEyoeU5v+kSQOw==
|
||||
version "1.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.11.tgz#1cc5750226abe5af2c94f72e975c0c8d2f5cc5a6"
|
||||
integrity sha512-EaDbVgcyiylhtskZZf4Qb/JiiByY7cYbd0qgZ9xm2pm2X7hKojG0P4TaQYKgPOV3vojPhd/pZyQh3nmRkkcSyw==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
|
||||
@@ -4944,12 +4944,19 @@
|
||||
integrity sha512-jIOM6CzCbl2/Mzbx9kb2IjtHoJOeRN9wtQgLk4EUm5bhneSVGv1rtz5TDskvp2UfCa+EK9nDmug+lje41z80Gg==
|
||||
|
||||
"@next/bundle-analyzer@^12.2.5":
|
||||
version "12.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-12.3.1.tgz#df168652c43ed0dc3e91a5108e34cdcc8fc7f05a"
|
||||
integrity sha512-2f/eei0YqZZBMTs4g1+HbgHyAFH5MbI/w9wLXmE8ly9SFze2D40sRH46JcC//EFVM/TIynVBh5sxn9CVO/vtxg==
|
||||
version "12.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-12.3.4.tgz#37c587525288a3dea64213c991590532246e8bb8"
|
||||
integrity sha512-eKjgRICzbLTmod0UnJcArFVs5uEAiuZwB6NCf84m+btW7jdylUVoOYf1wi5tA14xk5L9Lho7Prm6/XJ8gxYzfQ==
|
||||
dependencies:
|
||||
webpack-bundle-analyzer "4.3.0"
|
||||
|
||||
"@next/bundle-analyzer@^13.1.6":
|
||||
version "13.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.1.6.tgz#36ff2f9c4b19aa75cd589ee080b1e2edb24f1192"
|
||||
integrity sha512-rJS9CtLoGT58mL+v2ISKANosFFWP/0YKYByHQ3vTaZrbQP8b1rYRxd2QVMJmnSXaFkiP9URt1XJ6OdGyVq5b6g==
|
||||
dependencies:
|
||||
webpack-bundle-analyzer "4.7.0"
|
||||
|
||||
"@next/env@13.1.1":
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.1.1.tgz#6ff26488dc7674ef2bfdd1ca28fe43eed1113bea"
|
||||
@@ -4962,6 +4969,11 @@
|
||||
dependencies:
|
||||
glob "7.1.7"
|
||||
|
||||
"@next/font@^13.1.1":
|
||||
version "13.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@next/font/-/font-13.1.6.tgz#2bf99e3321ec9b4d65781c0d0ebff072e8752e1a"
|
||||
integrity sha512-AITjmeb1RgX1HKMCiA39ztx2mxeAyxl4ljv2UoSBUGAbFFMg8MO7YAvjHCgFhD39hL7YTbFjol04e/BPBH5RzQ==
|
||||
|
||||
"@next/swc-android-arm-eabi@13.1.1":
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz#b5c3cd1f79d5c7e6a3b3562785d4e5ac3555b9e1"
|
||||
@@ -5183,10 +5195,10 @@
|
||||
ms "2.1.3"
|
||||
strip-ansi "6.0.1"
|
||||
|
||||
"@prisma/debug@4.10.0":
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-4.10.0.tgz#5b1ab6b6d4812765a60ad8cccb7b5d38afb3b802"
|
||||
integrity sha512-rxVOZKsEyjlQCwN/pkkJO7wEdARt1yRyukSjLa+BF2QTvy2+VgtBmrfys4WDQSnj3jVWeHMpi5GeAoJjKkSKyA==
|
||||
"@prisma/debug@4.10.1":
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-4.10.1.tgz#02d0f98aec5b0bb8b5358e1fcf6cb9462fbb6de2"
|
||||
integrity sha512-NCWX+uJiEItzQsOS/4kiAKsT1hgSbN7n+1cNCmzoA6TsEn+WKzN0ZjBKyKuR937z57n2WVGXo5DfnCiW9ClqUg==
|
||||
dependencies:
|
||||
"@types/debug" "4.1.7"
|
||||
debug "4.3.4"
|
||||
@@ -5203,11 +5215,11 @@
|
||||
integrity sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==
|
||||
|
||||
"@prisma/generator-helper@^4.0.0":
|
||||
version "4.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/generator-helper/-/generator-helper-4.10.0.tgz#dc47265cbd55dc14ce08822f99c14d08f7e2cdb7"
|
||||
integrity sha512-NkQOfZpHUjVjqJ7NN2FymHSLkGd/E0fz5c3RkyESKvQqBy2sFBxt+aFxGsUbUy3FfwvkckC04HdQOXpisAko0A==
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/generator-helper/-/generator-helper-4.10.1.tgz#6a71043855ec670d9b8a497e4a77f181cedfbc20"
|
||||
integrity sha512-OS2hAnRAiWpy+icccG6wp/yx/2jaeLdfYG6ZwD8RR0vZC2PC5VsrJz8tv6kYmuOs3qQwzVcPt9YSj3frWfD5WA==
|
||||
dependencies:
|
||||
"@prisma/debug" "4.10.0"
|
||||
"@prisma/debug" "4.10.1"
|
||||
"@types/cross-spawn" "6.0.2"
|
||||
chalk "4.1.2"
|
||||
cross-spawn "7.0.3"
|
||||
@@ -6311,7 +6323,7 @@
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.0":
|
||||
"@sideway/formula@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
|
||||
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
|
||||
@@ -8248,6 +8260,11 @@
|
||||
"@types/linkify-it" "*"
|
||||
"@types/mdurl" "*"
|
||||
|
||||
"@types/md5@^2.3.2":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.2.tgz#529bb3f8a7e9e9f621094eb76a443f585d882528"
|
||||
integrity sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og==
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
|
||||
@@ -8812,9 +8829,9 @@
|
||||
integrity sha512-aW6CfMMToX4a+baLuVxwcT0FSACjX3xrNt8wdi/3LLRlLAfhyue8OK7kJxhcYNZfydBeWTP59aRy8p5FUTIeew==
|
||||
|
||||
"@vercel/analytics@^0.1.6":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-0.1.8.tgz#71f1f8c7bb98ac0c5c47eb3fb8ccbe8141b9fe47"
|
||||
integrity sha512-PQrOI8BJ9qUiVJuQfnKiJd15eDjDJH9TBKsNeMrtelT4NAk7d9mBVz1CoZkvoFnHQ0OW7Xnqmr1F2nScfAnznQ==
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-0.1.9.tgz#434f14a0dbe635a6d92c693a3607dbac0468dd47"
|
||||
integrity sha512-Lfus/6HAZXQBr4uv5eCtNKrEvthKWQFFcUNh5wC3fWn+4PbHxT8bqcm1TMQfYvGJ8O4TWzn0IfrQFNWh1DHDbQ==
|
||||
|
||||
"@vercel/edge-config@^0.1.1":
|
||||
version "0.1.1"
|
||||
@@ -11775,6 +11792,11 @@ commander@^6.2.0, commander@^6.2.1:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
commander@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
@@ -15443,9 +15465,9 @@ gifwrap@^0.9.2:
|
||||
omggif "^1.0.10"
|
||||
|
||||
github-buttons@^2.22.0:
|
||||
version "2.22.3"
|
||||
resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.22.3.tgz#ad0766618dadfbb46c12c5dca1f8c14c1e7d90f3"
|
||||
integrity sha512-09/pJ7X6D1whEeFz+fKSjOBEEDnY/nxbPN7zduxAU5zGwo4RkXjL86AiOVHgIYDothbHAS43eaVk76a8Lp4YYw==
|
||||
version "2.23.0"
|
||||
resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.23.0.tgz#b113ec9ed733f55bf5ab07652015bd0e5128dd1d"
|
||||
integrity sha512-2REUOV3ue6NmT0QThhfzfYmeSoYpCG73+tL7Ir2C7P+gshRerI05WuIQuhDkE2Zlg5Wc39hc2DHj+pE23mGJvw==
|
||||
|
||||
github-slugger@^1.0.0, github-slugger@^1.3.0:
|
||||
version "1.4.0"
|
||||
@@ -15999,9 +16021,9 @@ hast-util-from-parse5@^6.0.0:
|
||||
web-namespaces "^1.0.0"
|
||||
|
||||
hast-util-from-parse5@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.1.tgz#1887b4dd4e19f29d9c48c2e1c8bfeaac13a1f3a0"
|
||||
integrity sha512-R6PoNcUs89ZxLJmMWsVbwSWuz95/9OriyQZ3e2ybwqGsRXzhA6gv49rgGmQvLbZuSNDv9fCg7vV7gXUsvtUFaA==
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz#aecfef73e3ceafdfa4550716443e4eb7b02e22b0"
|
||||
integrity sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
@@ -17951,14 +17973,14 @@ jimp@^0.16.1:
|
||||
regenerator-runtime "^0.13.3"
|
||||
|
||||
joi@^17.7.0:
|
||||
version "17.7.0"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
|
||||
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
|
||||
version "17.7.1"
|
||||
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.1.tgz#854fc85c7fa3cfc47c91124d30bffdbb58e06cec"
|
||||
integrity sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.3"
|
||||
"@sideway/formula" "^3.0.0"
|
||||
"@sideway/formula" "^3.0.1"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
jose@4.11.1, jose@^4.9.3:
|
||||
@@ -19939,11 +19961,16 @@ minimist-options@4.1.0:
|
||||
is-plain-obj "^1.1.0"
|
||||
kind-of "^6.0.3"
|
||||
|
||||
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.7:
|
||||
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
|
||||
minimist@^1.2.7:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
minipass-collect@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
|
||||
@@ -22121,9 +22148,9 @@ prism-react-renderer@^1.3.5:
|
||||
integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==
|
||||
|
||||
prisma-field-encryption@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/prisma-field-encryption/-/prisma-field-encryption-1.4.0.tgz#fb644cf98b782c81e443f022e27b0108fe31c7ed"
|
||||
integrity sha512-ZJLIZL4IzdkZSmeVnpvW7E4/P74txf2OLZWzzfHQlkLcDJOddfIcXPytCxwHiPZX12aXfsVt0bA2T11mdA/lMw==
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/prisma-field-encryption/-/prisma-field-encryption-1.4.1.tgz#caec1712d0d1ac1525f64eaae3d76501f0e33a7f"
|
||||
integrity sha512-J9uOdp/biLw4Gb3Z2dmXpDYXWG6jmqn0Wnp9/ga0Adg53jP0jVeIfO6+iIIY+H07xDUZa635seYs0pTMqYzyhA==
|
||||
dependencies:
|
||||
"@47ng/cloak" "^1.1.0"
|
||||
"@prisma/generator-helper" "^4.0.0"
|
||||
@@ -22613,11 +22640,11 @@ react-debounce-input@=3.2.4:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-device-detect@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.2.tgz#dbabbce798ec359c83f574c3edb24cf1cca641a5"
|
||||
integrity sha512-zSN1gIAztUekp5qUT/ybHwQ9fmOqVT1psxpSlTn1pe0CO+fnJHKRLOWWac5nKxOxvOpD/w84hk1I+EydrJp7SA==
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca"
|
||||
integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==
|
||||
dependencies:
|
||||
ua-parser-js "^1.0.2"
|
||||
ua-parser-js "^1.0.33"
|
||||
|
||||
react-devtools-core@^4.19.1:
|
||||
version "4.24.6"
|
||||
@@ -26318,7 +26345,7 @@ tzdata@^1.0.30:
|
||||
resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.36.tgz#e5f3998d5e95e0e65dee417ae917a16314df69e9"
|
||||
integrity sha512-QxRODDsXS8UVxlPazCZXplqVuD6mCe7tSyDE5SCSHXv1nmrfflXxNH4pD+fjU8KJwePhWqhp6n+FpygQeGLv7w==
|
||||
|
||||
ua-parser-js@^1.0.2:
|
||||
ua-parser-js@^1.0.33:
|
||||
version "1.0.33"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.33.tgz#f21f01233e90e7ed0f059ceab46eb190ff17f8f4"
|
||||
integrity sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==
|
||||
@@ -27389,6 +27416,21 @@ webpack-bundle-analyzer@4.3.0:
|
||||
sirv "^1.0.7"
|
||||
ws "^7.3.1"
|
||||
|
||||
webpack-bundle-analyzer@4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz#33c1c485a7fcae8627c547b5c3328b46de733c66"
|
||||
integrity sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==
|
||||
dependencies:
|
||||
acorn "^8.0.4"
|
||||
acorn-walk "^8.0.0"
|
||||
chalk "^4.1.0"
|
||||
commander "^7.2.0"
|
||||
gzip-size "^6.0.0"
|
||||
lodash "^4.17.20"
|
||||
opener "^1.5.2"
|
||||
sirv "^1.0.7"
|
||||
ws "^7.3.1"
|
||||
|
||||
webpack-dev-middleware@^3.7.3:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5"
|
||||
@@ -28118,7 +28160,12 @@ zod-prisma@^0.5.4:
|
||||
parenthesis "^3.1.8"
|
||||
ts-morph "^13.0.2"
|
||||
|
||||
zod@^3.17.3, zod@^3.20.2:
|
||||
zod@^3.17.3:
|
||||
version "3.20.6"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.20.6.tgz#2f2f08ff81291d47d99e86140fedb4e0db08361a"
|
||||
integrity sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==
|
||||
|
||||
zod@^3.20.2:
|
||||
version "3.20.2"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.20.2.tgz#068606642c8f51b3333981f91c0a8ab37dfc2807"
|
||||
integrity sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==
|
||||
|
||||
Reference in New Issue
Block a user