Compare commits

...
21 Commits
Author SHA1 Message Date
Keith Williams 9b22f44cba v3.7.8
release.yml / release (push) Canceled after 0s
2024-01-24 16:01:51 -03:00
Morgan c84f75df99 fix: Booker shows toggle external calendar on external links (#13395) 2024-01-24 15:38:17 -03:00
Crowdin Bot 8f9a4fc78f New Crowdin translations by Github Action 2024-01-24 16:06:17 +00:00
7b30ade284 test: Create E2E tests to check the responsive design for a regular booking and edit a regular event type (responsiveLayoute2e) (#12432)
* add tests for responsive layout

* test: Create E2E tests to check the responsive design for a regular booking and edit a regular event type

* test: Create E2E tests to check the responsive design for a regular booking and edit a regular event type

* test: Create E2E tests to check the responsive design for a regular booking and edit a regular event type

* Create tests

* Create tests

* Create tests

* add changes

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-24 13:02:12 -03:00
Keith WilliamsandJoe Au-Yeung 24a4646fdc fix: sync/async calls in getSchedule (#13387)
* Fixed the sync/async calls

* fixing type errors

* fixing more type errors

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-01-24 10:05:25 -05:00
Keith Williams 0860036607 v3.7.7
release.yml / release (push) Canceled after 0s
2024-01-24 11:06:38 -03:00
Peer RichelsenandOmar López d080a0ef2d feat: notion calendar (#13299)
* replaced cron with notion calendar

* fixed app store for cron

* fixed slug

* Apply suggestions from code review

---------

Co-authored-by: Omar López <zomars@me.com>
2024-01-24 12:10:15 +00:00
a6eab74fd1 feat: make org members list private (#13373)
* feat: make org members list private

* fix: add isOrg to MakeTeamPrivateSwitch in other-team-members-view

---------

Co-authored-by: supalarry <lauris.skraucis@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-01-24 11:28:34 +00:00
0a2e275908 fix: UI mismatch with form state (#10651)
* fix: UI mismatch with form state

* Fix: Sync form state with ui for more fields & slight code refactor

* Ensure Stripe app is enabled to disable seats

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-01-24 10:55:31 +00:00
Crowdin Bot 3b05ebc5b5 New Crowdin translations by Github Action 2024-01-24 10:46:13 +00:00
Omar López e5b4d2fb80 fix: API slots endpoint return format (#13377) 2024-01-24 10:43:13 +00:00
Syed Ali Shahbaz 00a2a3d8a8 Fix slot wrapper (#13379) 2024-01-24 07:01:57 +00:00
30ba168fc3 chore: bump @trpc/* and @tanstack/react-query (#13335)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Gergő Móricz <mo.geryy@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-23 16:42:29 -07:00
SashaandDmytro Hryshyn 12eec186b2 chore: [app-router-migration 31] migrate the "/auth/setup" page (#13196)
* wip

* chore: setup page migration

* fix: conventions

* fix: convention

* use client

* fix: rebase on 13.5

---------

Co-authored-by: Dmytro Hryshyn <dev.dmytroh@gmail.com>
2024-01-23 18:05:47 -03:00
MorganandOmar López 4417d649ea refactor: remove @calcom/features dependency to @calcom/web (#13365)
* refactor: remove @calcom/features dependency to @calcom/web

* refactor: remove IS_VISUAL_REGRESSION_TESTING from web

* fix: wrong imports

---------

Co-authored-by: Omar López <zomars@me.com>
2024-01-23 17:17:36 +00:00
Alex van Andel 7058da70e6 v3.7.6
release.yml / release (push) Canceled after 0s
2024-01-23 14:10:48 +00:00
Alex van Andel 2cb383c337 fix: Remove type from import getServerSideProps 2024-01-23 14:09:59 +00:00
Alex van Andel fe5eca8eb3 v3.7.5
release.yml / release (push) Canceled after 0s
2024-01-23 13:50:01 +00:00
Alex van Andel 7c6468953a fix: Update yarn.lock to include new apps 2024-01-23 13:48:15 +00:00
Alex van Andel 28a0553d18 v3.7.4
release.yml / release (push) Canceled after 0s
2024-01-23 13:42:40 +00:00
Alex van Andel ffcb04fa99 fix: Broken Cal.video due to missing export 2024-01-23 13:38:23 +00:00
231 changed files with 1877 additions and 1386 deletions

No files matched your search

+7 -1
View File
@@ -13,6 +13,12 @@
} }
] ]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"../../packages/types/window.d.ts"
],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }
+98
View File
@@ -0,0 +1,98 @@
import prismock from "../../../../../tests/libs/__mocks__/prisma";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, expect, test } from "vitest";
import dayjs from "@calcom/dayjs";
import handler from "./_get";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
function buildMockData() {
prismock.user.create({
data: {
id: 1,
username: "test",
name: "Test User",
email: "test@example.com",
},
});
prismock.eventType.create({
data: {
id: 1,
slug: "test",
length: 30,
title: "Test Event Type",
userId: 1,
},
});
}
describe("GET /api/slots", () => {
describe("Errors", () => {
test("Missing required data", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
});
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
{
"message": "invalid_type in 'startTime': Required; invalid_type in 'endTime': Required",
}
`);
});
});
describe("Success", () => {
describe("Regular event-type", () => {
test("Returns and event type available slots", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
query: {
eventTypeId: 1,
startTime: dayjs().format(),
endTime: dayjs().add(1, "day").format(),
usernameList: "test",
},
prisma: prismock,
});
buildMockData();
await handler(req, res);
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
{
"slots": {},
}
`);
});
test("Returns and event type available slots with passed timeZone", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
query: {
eventTypeId: 1,
startTime: dayjs().format(),
endTime: dayjs().add(1, "day").format(),
usernameList: "test",
timeZone: "UTC",
},
prisma: prismock,
});
buildMockData();
await handler(req, res);
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
{
"slots": {},
}
`);
});
});
});
});
+1 -1
View File
@@ -38,7 +38,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}, },
{} {}
) )
: availableSlots; : availableSlots.slots;
return { slots: slotsInProvidedTimeZone }; return { slots: slotsInProvidedTimeZone };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
+9 -4
View File
@@ -1,11 +1,16 @@
"use client"; "use client";
import { type DehydratedState, Hydrate } from "@tanstack/react-query"; import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query";
import { useMemo } from "react"; import { useMemo } from "react";
import type { DataTransformer } from "@trpc/server"; type DataTransformer = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
serialize: (data: any) => any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deserialize: (data: any) => any;
};
export function createHydrateClient(opts: { transformer?: DataTransformer }) { export function createHydrateClient(opts: { transformer: DataTransformer }) {
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) { return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
const { state, children } = props; const { state, children } = props;
@@ -16,6 +21,6 @@ export function createHydrateClient(opts: { transformer?: DataTransformer }) {
return state; return state;
}, [state]); }, [state]);
return <Hydrate state={transformedState}>{children}</Hydrate>; return <HydrationBoundary state={transformedState}>{children}</HydrationBoundary>;
}; };
} }
+1 -4
View File
@@ -4,10 +4,7 @@ import { trpc } from "app/_trpc/client";
import { useState } from "react"; import { useState } from "react";
import superjson from "superjson"; import superjson from "superjson";
import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink"; import { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client";
import { httpLink } from "@calcom/trpc/client/links/httpLink";
import { loggerLink } from "@calcom/trpc/client/links/loggerLink";
import { splitLink } from "@calcom/trpc/client/links/splitLink";
import { ENDPOINTS } from "@calcom/trpc/react/shared"; import { ENDPOINTS } from "@calcom/trpc/react/shared";
export type Endpoint = (typeof ENDPOINTS)[number]; export type Endpoint = (typeof ENDPOINTS)[number];
+12
View File
@@ -0,0 +1,12 @@
import Setup from "@pages/auth/setup";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { WithLayout } from "app/layoutHOC";
import type { InferGetServerSidePropsType } from "next";
import { getServerSideProps } from "@server/lib/setup/getServerSideProps";
export default WithLayout({
getLayout: null,
Page: Setup,
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
})<"P">;
@@ -16,10 +16,10 @@ import { Plus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
interface AdditionalCalendarSelectorProps { interface AdditionalCalendarSelectorProps {
isLoading?: boolean; isPending?: boolean;
} }
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => { const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorProps): JSX.Element | null => {
const { t } = useLocale(); const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true }); const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
@@ -42,7 +42,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
return ( return (
<Dropdown modal={false}> <Dropdown modal={false}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button StartIcon={Plus} color="secondary" {...(isLoading && { loading: isLoading })}> <Button StartIcon={Plus} color="secondary" {...(isPending && { loading: isPending })}>
{t("add")} {t("add")}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
+18 -16
View File
@@ -1,6 +1,6 @@
import Link from "next/link"; import Link from "next/link";
import type { IframeHTMLAttributes } from "react"; import type { IframeHTMLAttributes } from "react";
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation"; import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components"; import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
@@ -92,17 +92,19 @@ export const AppPage = ({
const [existingCredentials, setExistingCredentials] = useState<number[]>([]); const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false); const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false);
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery( const appDbQuery = trpc.viewer.appCredentialsByType.useQuery({ appType: type });
{ appType: type },
{ useEffect(
onSettled(data) { function refactorMeWithoutEffect() {
const credentialsCount = data?.credentials.length || 0; const data = appDbQuery.data;
setShowDisconnectIntegration(
data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0 const credentialsCount = data?.credentials.length || 0;
); setShowDisconnectIntegration(
setExistingCredentials(data?.credentials.map((credential) => credential.id) || []); data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0
}, );
} setExistingCredentials(data?.credentials.map((credential) => credential.id) || []);
},
[appDbQuery.data]
); );
const dependencyData = trpc.viewer.appsRouter.queryForDependencies.useQuery(dependencies, { const dependencyData = trpc.viewer.appsRouter.queryForDependencies.useQuery(dependencies, {
@@ -190,7 +192,7 @@ export const AppPage = ({
)} )}
</header> </header>
</div> </div>
{!appDbQuery.isLoading ? ( {!appDbQuery.isPending ? (
isGlobal || isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? ( (existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3"> <div className="flex space-x-3">
@@ -211,7 +213,7 @@ export const AppPage = ({
onClick: () => { onClick: () => {
mutation.mutate({ type, variant, slug }); mutation.mutate({ type, variant, slug });
}, },
loading: mutation.isLoading, loading: mutation.isPending,
}; };
} }
return ( return (
@@ -250,7 +252,7 @@ export const AppPage = ({
onClick: () => { onClick: () => {
mutation.mutate({ type, variant, slug }); mutation.mutate({ type, variant, slug });
}, },
loading: mutation.isLoading, loading: mutation.isPending,
}; };
} }
return ( return (
@@ -272,7 +274,7 @@ export const AppPage = ({
)} )}
{dependencies && {dependencies &&
(!dependencyData.isLoading ? ( (!dependencyData.isPending ? (
<div className="mt-6"> <div className="mt-6">
<AppDependencyComponent appName={name} dependencyData={dependencyData.data} /> <AppDependencyComponent appName={name} dependencyData={dependencyData.data} />
</div> </div>
@@ -28,7 +28,7 @@ type Props = {
onChanged: () => unknown | Promise<unknown>; onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean; fromOnboarding?: boolean;
destinationCalendarId?: string; destinationCalendarId?: string;
isLoading?: boolean; isPending?: boolean;
}; };
function CalendarList(props: Props) { function CalendarList(props: Props) {
@@ -74,7 +74,7 @@ function ConnectedCalendarsList(props: Props) {
suspense: true, suspense: true,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}); });
const { fromOnboarding, isLoading } = props; const { fromOnboarding, isPending } = props;
return ( return (
<QueryCell <QueryCell
query={query} query={query}
@@ -97,7 +97,7 @@ function ConnectedCalendarsList(props: Props) {
<div className="flex flex-col xl:flex-row xl:space-x-5"> <div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && ( {!!data.connectedCalendars.length && (
<div className="flex items-center"> <div className="flex items-center">
<AdditionalCalendarSelector isLoading={isLoading} /> <AdditionalCalendarSelector isPending={isPending} />
</div> </div>
)} )}
</div> </div>
@@ -229,7 +229,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
hidePlaceholder hidePlaceholder
value={data.destinationCalendar?.externalId} value={data.destinationCalendar?.externalId}
onChange={mutation.mutate} onChange={mutation.mutate}
isLoading={mutation.isLoading} isPending={mutation.isPending}
/> />
</div> </div>
</div> </div>
@@ -239,7 +239,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
onChanged={onChanged} onChanged={onChanged}
fromOnboarding={fromOnboarding} fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId} destinationCalendarId={data.destinationCalendar?.externalId}
isLoading={mutation.isLoading} isPending={mutation.isPending}
/> />
</> </>
)} )}
@@ -100,9 +100,9 @@ export const InstallAppButtonChild = ({
<DropdownMenuContent <DropdownMenuContent
className="w-auto" className="w-auto"
onInteractOutside={(event) => { onInteractOutside={(event) => {
if (mutation.isLoading) event.preventDefault(); if (mutation.isPending) event.preventDefault();
}}> }}>
{mutation.isLoading && ( {mutation.isPending && (
<div className="z-1 fixed inset-0 flex items-center justify-center"> <div className="z-1 fixed inset-0 flex items-center justify-center">
<Spinner /> <Spinner />
</div> </div>
@@ -139,7 +139,7 @@ function BookingListItem(booking: BookingItemProps) {
setRejectionDialogIsOpen(true); setRejectionDialogIsOpen(true);
}, },
icon: Ban, icon: Ban,
disabled: mutation.isLoading, disabled: mutation.isPending,
}, },
// For bookings with payment, only confirm if the booking is paid for // For bookings with payment, only confirm if the booking is paid for
...((isPending && !paymentAppData.enabled) || ...((isPending && !paymentAppData.enabled) ||
@@ -153,7 +153,7 @@ function BookingListItem(booking: BookingItemProps) {
bookingConfirm(true); bookingConfirm(true);
}, },
icon: Check, icon: Check,
disabled: mutation.isLoading, disabled: mutation.isPending,
}, },
] ]
: []), : []),
@@ -293,7 +293,7 @@ function BookingListItem(booking: BookingItemProps) {
setViewRecordingsDialogIsOpen(true); setViewRecordingsDialogIsOpen(true);
}, },
color: showCheckRecordingButton ? "secondary" : "primary", color: showCheckRecordingButton ? "secondary" : "primary",
disabled: mutation.isLoading, disabled: mutation.isPending,
}, },
]; ];
@@ -348,7 +348,7 @@ function BookingListItem(booking: BookingItemProps) {
<DialogFooter> <DialogFooter>
<DialogClose /> <DialogClose />
<Button <Button
disabled={mutation.isLoading} disabled={mutation.isPending}
data-testid="rejection-confirm" data-testid="rejection-confirm"
onClick={() => { onClick={() => {
bookingConfirm(false); bookingConfirm(false);
@@ -65,7 +65,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
<DialogClose /> <DialogClose />
<Button <Button
data-testid="send_request" data-testid="send_request"
disabled={chargeCardMutation.isLoading || chargeError} disabled={chargeCardMutation.isPending || chargeError}
onClick={() => onClick={() =>
chargeCardMutation.mutate({ chargeCardMutation.mutate({
bookingId, bookingId,
@@ -27,7 +27,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props; const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
const [rescheduleReason, setRescheduleReason] = useState(""); const [rescheduleReason, setRescheduleReason] = useState("");
const { mutate: rescheduleApi, isLoading } = trpc.viewer.bookings.requestReschedule.useMutation({ const { mutate: rescheduleApi, isPending } = trpc.viewer.bookings.requestReschedule.useMutation({
async onSuccess() { async onSuccess() {
showToast(t("reschedule_request_sent"), "success"); showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false); setIsOpenDialog(false);
@@ -65,7 +65,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<DialogClose /> <DialogClose />
<Button <Button
data-testid="send_request" data-testid="send_request"
disabled={isLoading} disabled={isPending}
onClick={() => { onClick={() => {
rescheduleApi({ rescheduleApi({
bookingId, bookingId,
@@ -21,6 +21,7 @@ import type { EditableSchema } from "@calcom/features/form-builder/schema";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector"; import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants"; import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { Prisma } from "@calcom/prisma/client"; import type { Prisma } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
@@ -35,7 +36,6 @@ import {
Tooltip, Tooltip,
} from "@calcom/ui"; } from "@calcom/ui";
import { Copy, Edit, Info } from "@calcom/ui/components/icon"; import { Copy, Edit, Info } from "@calcom/ui/components/icon";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/web/constants";
import RequiresConfirmationController from "./RequiresConfirmationController"; import RequiresConfirmationController from "./RequiresConfirmationController";
@@ -55,8 +55,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const { t } = useLocale(); const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false); const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink); const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl); const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link); const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const bookingFields: Prisma.JsonObject = {}; const bookingFields: Prisma.JsonObject = {};
@@ -66,27 +66,31 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
user?.theme === "dark" || user?.theme === "dark" ||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark")); (!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
eventType.bookingFields.forEach(({ name }) => { formMethods.getValues().bookingFields.forEach(({ name }) => {
bookingFields[name] = `${name} input`; bookingFields[name] = `${name} input`;
}); });
const eventNameObject: EventNameObjectType = { const eventNameObject: EventNameObjectType = {
attendeeName: t("scheduler"), attendeeName: t("scheduler"),
eventType: eventType.title, eventType: formMethods.getValues("title"),
eventName: eventType.eventName, eventName: formMethods.getValues("eventName"),
host: eventType.users[0]?.name || "Nameless", host: formMethods.getValues("users")[0]?.name || "Nameless",
bookingFields: bookingFields, bookingFields: bookingFields,
t, t,
}; };
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation); const [requiresConfirmation, setRequiresConfirmation] = useState(
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`; formMethods.getValues("requiresConfirmation")
);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled"); const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const noShowFeeEnabled = eventType.metadata?.apps?.stripe?.paymentOption === "HOLD"; const noShowFeeEnabled =
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
formMethods.getValues("metadata")?.apps?.stripe?.paymentOption === "HOLD";
useEffect(() => { useEffect(() => {
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id)); !hashedUrl && setHashedUrl(generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id));
}, [eventType.users, hashedUrl, team?.id]); }, [formMethods.getValues("users"), hashedUrl, team?.id]);
const toggleGuests = (enabled: boolean) => { const toggleGuests = (enabled: boolean) => {
const bookingFields = formMethods.getValues("bookingFields"); const bookingFields = formMethods.getValues("bookingFields");
@@ -108,7 +112,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}; };
const { shouldLockDisableProps } = useLockedFieldsManager( const { shouldLockDisableProps } = useLockedFieldsManager(
eventType, formMethods.getValues(),
t("locked_fields_admin_description"), t("locked_fields_admin_description"),
t("locked_fields_member_description") t("locked_fields_member_description")
); );
@@ -122,7 +126,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const closeEventNameTip = () => setShowEventNameTip(false); const closeEventNameTip = () => setShowEventNameTip(false);
const setEventName = (value: string) => formMethods.setValue("eventName", value);
return ( return (
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
{/** {/**
@@ -145,12 +148,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</Link> </Link>
</div> </div>
<Controller <Controller
control={formMethods.control}
name="destinationCalendar" name="destinationCalendar"
defaultValue={eventType.destinationCalendar || undefined}
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<DestinationCalendarSelector <DestinationCalendarSelector
destinationCalendar={eventType.destinationCalendar}
value={value ? value.externalId : undefined} value={value ? value.externalId : undefined}
onChange={onChange} onChange={onChange}
hidePlaceholder hidePlaceholder
@@ -167,7 +167,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
type="text" type="text"
{...shouldLockDisableProps("eventName")} {...shouldLockDisableProps("eventName")}
placeholder={eventNamePlaceholder} placeholder={eventNamePlaceholder}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")} {...formMethods.register("eventName")}
addOnSuffix={ addOnSuffix={
<Button <Button
@@ -194,7 +193,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
{...shouldLockDisableProps("bookingFields")} {...shouldLockDisableProps("bookingFields")}
dataStore={{ dataStore={{
options: { options: {
locations: getLocationsOptionsForSelect(eventType?.locations ?? [], t), locations: getLocationsOptionsForSelect(formMethods.getValues("locations") ?? [], t),
}, },
}} }}
/> />
@@ -203,15 +202,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<RequiresConfirmationController <RequiresConfirmationController
eventType={eventType} eventType={eventType}
seatsEnabled={seatsEnabled} seatsEnabled={seatsEnabled}
metadata={eventType.metadata} metadata={formMethods.getValues("metadata")}
requiresConfirmation={requiresConfirmation} requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation} onRequiresConfirmation={setRequiresConfirmation}
/> />
<Controller <Controller
name="requiresBookerEmailVerification" name="requiresBookerEmailVerification"
control={formMethods.control}
defaultValue={eventType.requiresBookerEmailVerification}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<SettingsToggle <SettingsToggle
labelClassName="text-sm" labelClassName="text-sm"
@@ -228,8 +225,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Controller <Controller
name="hideCalendarNotes" name="hideCalendarNotes"
control={formMethods.control}
defaultValue={eventType.hideCalendarNotes}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<SettingsToggle <SettingsToggle
labelClassName="text-sm" labelClassName="text-sm"
@@ -246,7 +241,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Controller <Controller
name="successRedirectUrl" name="successRedirectUrl"
control={formMethods.control}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<> <>
<SettingsToggle <SettingsToggle
@@ -274,7 +268,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
placeholder={t("external_redirect_url")} placeholder={t("external_redirect_url")}
required={redirectUrlVisible} required={redirectUrlVisible}
type="text" type="text"
defaultValue={eventType.successRedirectUrl || ""}
{...formMethods.register("successRedirectUrl")} {...formMethods.register("successRedirectUrl")}
/> />
<div <div
@@ -327,7 +320,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
hint={t("private_link_hint")} hint={t("private_link_hint")}
defaultValue={placeholderHashedLink} defaultValue={placeholderHashedLink}
addOnSuffix={ addOnSuffix={
<Tooltip content={eventType.hashedLink ? t("copy_to_clipboard") : t("enabled_after_update")}> <Tooltip
content={
formMethods.getValues("hashedLink") ? t("copy_to_clipboard") : t("enabled_after_update")
}>
<Button <Button
color="minimal" color="minimal"
size="sm" size="sm"
@@ -336,7 +332,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
aria-label="copy link" aria-label="copy link"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(placeholderHashedLink); navigator.clipboard.writeText(placeholderHashedLink);
if (eventType.hashedLink) { if (formMethods.getValues("hashedLink")) {
showToast(t("private_link_copied"), "success"); showToast(t("private_link_copied"), "success");
} else { } else {
showToast(t("enabled_after_update_description"), "warning"); showToast(t("enabled_after_update_description"), "warning");
@@ -353,8 +349,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Controller <Controller
name="seatsPerTimeSlotEnabled" name="seatsPerTimeSlotEnabled"
control={formMethods.control}
defaultValue={!!eventType.seatsPerTimeSlot}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<> <>
<SettingsToggle <SettingsToggle
@@ -378,7 +372,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
formMethods.setValue("requiresConfirmation", false); formMethods.setValue("requiresConfirmation", false);
setRequiresConfirmation(false); setRequiresConfirmation(false);
formMethods.setValue("metadata.multipleDuration", undefined); formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("seatsPerTimeSlot", 2); formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
} else { } else {
formMethods.setValue("seatsPerTimeSlot", null); formMethods.setValue("seatsPerTimeSlot", null);
toggleGuests(true); toggleGuests(true);
@@ -388,8 +382,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<div className="border-subtle rounded-b-lg border border-t-0 p-6"> <div className="border-subtle rounded-b-lg border border-t-0 p-6">
<Controller <Controller
name="seatsPerTimeSlot" name="seatsPerTimeSlot"
control={formMethods.control}
defaultValue={eventType.seatsPerTimeSlot}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<div> <div>
<TextField <TextField
@@ -399,7 +391,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
label={t("number_of_seats")} label={t("number_of_seats")}
type="number" type="number"
disabled={seatsLocked.disabled} disabled={seatsLocked.disabled}
defaultValue={value || 2} defaultValue={value}
min={1} min={1}
containerClassName="max-w-80" containerClassName="max-w-80"
addOnSuffix={<>{t("seats")}</>} addOnSuffix={<>{t("seats")}</>}
@@ -408,21 +400,29 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}} }}
/> />
<div className="mt-4"> <div className="mt-4">
<CheckboxField <Controller
description={t("show_attendees")} name="seatsShowAttendees"
disabled={seatsLocked.disabled} render={({ field: { value, onChange } }) => (
onChange={(e) => formMethods.setValue("seatsShowAttendees", e.target.checked)} <CheckboxField
defaultChecked={!!eventType.seatsShowAttendees} description={t("show_attendees")}
disabled={seatsLocked.disabled}
onChange={(e) => onChange(e)}
checked={value}
/>
)}
/> />
</div> </div>
<div className="mt-2"> <div className="mt-2">
<CheckboxField <Controller
description={t("show_available_seats_count")} name="seatsShowAvailabilityCount"
disabled={seatsLocked.disabled} render={({ field: { value, onChange } }) => (
onChange={(e) => <CheckboxField
formMethods.setValue("seatsShowAvailabilityCount", e.target.checked) description={t("show_available_seats_count")}
} disabled={seatsLocked.disabled}
defaultChecked={!!eventType.seatsShowAvailabilityCount} onChange={(e) => onChange(e)}
checked={value}
/>
)}
/> />
</div> </div>
</div> </div>
@@ -436,8 +436,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/> />
<Controller <Controller
name="lockTimeZoneToggleOnBookingPage" name="lockTimeZoneToggleOnBookingPage"
control={formMethods.control}
defaultValue={eventType.lockTimeZoneToggleOnBookingPage}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<SettingsToggle <SettingsToggle
labelClassName="text-sm" labelClassName="text-sm"
@@ -452,59 +450,47 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)} )}
/> />
{allowDisablingAttendeeConfirmationEmails(workflows) && ( {allowDisablingAttendeeConfirmationEmails(workflows) && (
<> <Controller
<Controller name="metadata.disableStandardEmails.confirmation.attendee"
name="metadata.disableStandardEmails.confirmation.attendee" render={({ field: { value, onChange } }) => (
control={formMethods.control} <>
render={({ field: { value, onChange } }) => ( <SettingsToggle
<> labelClassName="text-sm"
<SettingsToggle toggleSwitchAtTheEnd={true}
labelClassName="text-sm" switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
toggleSwitchAtTheEnd={true} title={t("disable_attendees_confirmation_emails")}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6" description={t("disable_attendees_confirmation_emails_description")}
title={t("disable_attendees_confirmation_emails")} checked={value}
description={t("disable_attendees_confirmation_emails_description")} onCheckedChange={(e) => onChange(e)}
checked={value || false} />
onCheckedChange={(e) => { </>
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e); )}
onChange(e); />
}}
/>
</>
)}
/>
</>
)} )}
{allowDisablingHostConfirmationEmails(workflows) && ( {allowDisablingHostConfirmationEmails(workflows) && (
<> <Controller
<Controller name="metadata.disableStandardEmails.confirmation.host"
name="metadata.disableStandardEmails.confirmation.host" defaultValue={!!formMethods.getValues("seatsPerTimeSlot")}
control={formMethods.control} render={({ field: { value, onChange } }) => (
defaultValue={!!eventType.seatsPerTimeSlot} <>
render={({ field: { value, onChange } }) => ( <SettingsToggle
<> labelClassName="text-sm"
<SettingsToggle toggleSwitchAtTheEnd={true}
labelClassName="text-sm" switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
toggleSwitchAtTheEnd={true} title={t("disable_host_confirmation_emails")}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6" description={t("disable_host_confirmation_emails_description")}
title={t("disable_host_confirmation_emails")} checked={value}
description={t("disable_host_confirmation_emails_description")} onCheckedChange={(e) => onChange(e)}
checked={value || false} />
onCheckedChange={(e) => { </>
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e); )}
onChange(e); />
}}
/>
</>
)}
/>
</>
)} )}
{showEventNameTip && ( {showEventNameTip && (
<CustomEventTypeModal <CustomEventTypeModal
close={closeEventNameTip} close={closeEventNameTip}
setValue={setEventName} setValue={(val: string) => formMethods.setValue("eventName", val)}
defaultValue={formMethods.getValues("eventName") || eventType.eventName || ""} defaultValue={formMethods.getValues("eventName")}
placeHolder={eventNamePlaceholder} placeHolder={eventNamePlaceholder}
event={eventNameObject} event={eventNameObject}
/> />
+9 -10
View File
@@ -18,21 +18,21 @@ export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const { t } = useLocale(); const { t } = useLocale();
const { data: eventTypeApps, isLoading } = trpc.viewer.integrations.useQuery({ const { data: eventTypeApps, isPending } = trpc.viewer.integrations.useQuery({
extendsFeature: "EventType", extendsFeature: "EventType",
teamId: eventType.team?.id || eventType.parent?.teamId, teamId: eventType.team?.id || eventType.parent?.teamId,
}); });
const methods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
const installedApps = const installedApps =
eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || []; eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || [];
const notInstalledApps = const notInstalledApps =
eventTypeApps?.items.filter((app) => !app.userCredentialIds.length && !app.teams.length) || []; eventTypeApps?.items.filter((app) => !app.userCredentialIds.length && !app.teams.length) || [];
const allAppsData = methods.watch("metadata")?.apps || {}; const allAppsData = formMethods.watch("metadata")?.apps || {};
const setAllAppsData = (_allAppsData: typeof allAppsData) => { const setAllAppsData = (_allAppsData: typeof allAppsData) => {
methods.setValue("metadata", { formMethods.setValue("metadata", {
...methods.getValues("metadata"), ...formMethods.getValues("metadata"),
apps: _allAppsData, apps: _allAppsData,
}); });
}; };
@@ -47,7 +47,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
}; };
}; };
const eventTypeFormMetadata = methods.getValues("metadata"); const eventTypeFormMetadata = formMethods.getValues("metadata");
const getAppDataSetter = ( const getAppDataSetter = (
appId: EventTypeAppsList, appId: EventTypeAppsList,
@@ -56,8 +56,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
): SetAppData => { ): SetAppData => {
return function (key, value) { return function (key, value) {
// Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render) // Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render)
const allAppsDataFromForm = methods.getValues("metadata")?.apps || {}; const allAppsDataFromForm = formMethods.getValues("metadata")?.apps || {};
const appData = allAppsDataFromForm[appId]; const appData = allAppsDataFromForm[appId];
setAllAppsData({ setAllAppsData({
...allAppsDataFromForm, ...allAppsDataFromForm,
@@ -138,7 +137,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
message={t("locked_apps_description")} message={t("locked_apps_description")}
/> />
)} )}
{!isLoading && !installedApps?.length ? ( {!isPending && !installedApps?.length ? (
<EmptyScreen <EmptyScreen
Icon={Grid} Icon={Grid}
headline={t("empty_installed_apps_headline")} headline={t("empty_installed_apps_headline")}
@@ -179,7 +178,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
</div> </div>
{!shouldLockDisableProps("apps").disabled && ( {!shouldLockDisableProps("apps").disabled && (
<div className="bg-muted mt-6 rounded-md p-8"> <div className="bg-muted mt-6 rounded-md p-8">
{!isLoading && notInstalledApps?.length ? ( {!isPending && notInstalledApps?.length ? (
<> <>
<h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]"> <h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]">
{t("available_apps_lower_case")} {t("available_apps_lower_case")}
@@ -85,7 +85,7 @@ const EventTypeScheduleDetails = memo(
const { watch } = useFormContext<FormValues>(); const { watch } = useFormContext<FormValues>();
const scheduleId = watch("schedule"); const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery( const { isPending, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
{ {
scheduleId: scheduleId:
scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined, scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
@@ -112,7 +112,7 @@ const EventTypeScheduleDetails = memo(
)}> )}>
{day} {day}
</span> </span>
{isLoading ? ( {isPending ? (
<SkeletonText className="block h-5 w-60" /> <SkeletonText className="block h-5 w-60" />
) : isAvailable ? ( ) : isAvailable ? (
<div className="space-y-3 text-right"> <div className="space-y-3 text-right">
@@ -142,7 +142,7 @@ const EventTypeScheduleDetails = memo(
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && ( {!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
<Button <Button
href={`/availability/${schedule.id}`} href={`/availability/${schedule.id}`}
disabled={isLoading} disabled={isPending}
color="minimal" color="minimal"
EndIcon={ExternalLink} EndIcon={ExternalLink}
target="_blank" target="_blank"
@@ -169,8 +169,15 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
const watchSchedule = watch("schedule"); const watchSchedule = watch("schedule");
const [options, setOptions] = useState<AvailabilityOption[]>([]); const [options, setOptions] = useState<AvailabilityOption[]>([]);
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, { const { data, isPending } = trpc.viewer.availability.list.useQuery(undefined);
onSuccess: ({ schedules }) => {
useEffect(
function refactorMeWithoutEffect() {
if (!data) {
return;
}
const schedules = data.schedules;
const options = schedules.map((schedule) => ({ const options = schedules.map((schedule) => ({
value: schedule.id, value: schedule.id,
label: schedule.name, label: schedule.name,
@@ -225,8 +232,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
setValue("availability", value); setValue("availability", value);
}, },
}); [data]
);
const availabilityValue = watch("availability"); const availabilityValue = watch("availability");
useEffect(() => { useEffect(() => {
@@ -241,8 +248,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
{t("availability")} {t("availability")}
{shouldLockIndicator("availability")} {shouldLockIndicator("availability")}
</label> </label>
{isLoading && <SelectSkeletonLoader />} {isPending && <SelectSkeletonLoader />}
{!isLoading && ( {!isPending && (
<Controller <Controller
name="schedule" name="schedule"
render={({ field }) => { render={({ field }) => {
@@ -1,10 +1,10 @@
import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group"; import * as RadioGroup from "@radix-ui/react-radio-group";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import type { FormValues } from "pages/event-types/[type]";
import type { Key } from "react"; import type { Key } from "react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import type { UseFormRegisterReturn } from "react-hook-form"; import type { UseFormRegisterReturn } from "react-hook-form";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext } from "react-hook-form";
import type { SingleValue } from "react-select"; import type { SingleValue } from "react-select";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
@@ -107,7 +107,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
); );
}); });
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => { export const EventLimitsTab = () => {
const { t, i18n } = useLocale(); const { t, i18n } = useLocale();
const formMethods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
@@ -126,36 +126,20 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
}, },
]; ];
const periodType = const watchPeriodType = formMethods.watch("periodType");
PERIOD_TYPES.find((s) => s.type === eventType.periodType) ||
PERIOD_TYPES.find((s) => s.type === "UNLIMITED");
const [periodDates] = useState<{ startDate: Date; endDate: Date }>({
startDate: new Date(eventType.periodStartDate || Date.now()),
endDate: new Date(eventType.periodEndDate || Date.now()),
});
const watchPeriodType = useWatch({
control: formMethods.control,
name: "periodType",
defaultValue: periodType?.type,
});
const optionsPeriod = [ const optionsPeriod = [
{ value: 1, label: t("calendar_days") },
{ value: 0, label: t("business_days") }, { value: 0, label: t("business_days") },
{ value: 1, label: t("calendar_days") },
]; ];
// offsetStart toggle is client-side only, opened by default if offsetStart is set const [offsetToggle, setOffsetToggle] = useState(formMethods.getValues("offsetStart") > 0);
const offsetStartValue = useWatch({
control: formMethods.control,
name: "offsetStart",
});
const [offsetToggle, setOffsetToggle] = useState(() => offsetStartValue > 0);
// Preview how the offset will affect start times // Preview how the offset will affect start times
const watchOffsetStartValue = formMethods.watch("offsetStart");
const offsetOriginalTime = new Date(); const offsetOriginalTime = new Date();
offsetOriginalTime.setHours(9, 0, 0, 0); offsetOriginalTime.setHours(9, 0, 0, 0);
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + offsetStartValue * 60 * 1000); const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + watchOffsetStartValue * 60 * 1000);
return ( return (
<div> <div>
@@ -165,8 +149,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
<Label htmlFor="beforeBufferTime">{t("before_event")}</Label> <Label htmlFor="beforeBufferTime">{t("before_event")}</Label>
<Controller <Controller
name="beforeBufferTime" name="beforeBufferTime"
control={formMethods.control}
defaultValue={eventType.beforeEventBuffer || 0}
render={({ field: { onChange, value } }) => { render={({ field: { onChange, value } }) => {
const beforeBufferOptions = [ const beforeBufferOptions = [
{ {
@@ -197,8 +179,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
<Label htmlFor="afterBufferTime">{t("after_event")}</Label> <Label htmlFor="afterBufferTime">{t("after_event")}</Label>
<Controller <Controller
name="afterBufferTime" name="afterBufferTime"
control={formMethods.control}
defaultValue={eventType.afterEventBuffer || 0}
render={({ field: { onChange, value } }) => { render={({ field: { onChange, value } }) => {
const afterBufferOptions = [ const afterBufferOptions = [
{ {
@@ -235,7 +215,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
<Label htmlFor="slotInterval">{t("slot_interval")}</Label> <Label htmlFor="slotInterval">{t("slot_interval")}</Label>
<Controller <Controller
name="slotInterval" name="slotInterval"
control={formMethods.control}
render={() => { render={() => {
const slotIntervalOptions = [ const slotIntervalOptions = [
{ {
@@ -254,8 +233,9 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null); formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
}} }}
defaultValue={ defaultValue={
slotIntervalOptions.find((option) => option.value === eventType.slotInterval) || slotIntervalOptions.find(
slotIntervalOptions[0] (option) => option.value === formMethods.getValues("slotInterval")
) || slotIntervalOptions[0]
} }
options={slotIntervalOptions} options={slotIntervalOptions}
/> />
@@ -267,7 +247,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</div> </div>
<Controller <Controller
name="bookingLimits" name="bookingLimits"
control={formMethods.control}
render={({ field: { value } }) => { render={({ field: { value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0; const isChecked = Object.keys(value ?? {}).length > 0;
return ( return (
@@ -300,7 +279,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/> />
<Controller <Controller
name="onlyShowFirstAvailableSlot" name="onlyShowFirstAvailableSlot"
control={formMethods.control}
render={({ field: { value } }) => { render={({ field: { value } }) => {
const isChecked = value; const isChecked = value;
return ( return (
@@ -323,7 +301,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/> />
<Controller <Controller
name="durationLimits" name="durationLimits"
control={formMethods.control}
render={({ field: { value } }) => { render={({ field: { value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0; const isChecked = Object.keys(value ?? {}).length > 0;
return ( return (
@@ -361,7 +338,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/> />
<Controller <Controller
name="periodType" name="periodType"
control={formMethods.control}
render={({ field: { value } }) => { render={({ field: { value } }) => {
const isChecked = value && value !== "UNLIMITED"; const isChecked = value && value !== "UNLIMITED";
@@ -380,7 +356,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}> onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6"> <div className="border-subtle rounded-b-lg border border-t-0 p-6">
<RadioGroup.Root <RadioGroup.Root
defaultValue={watchPeriodType}
value={watchPeriodType} value={watchPeriodType}
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}> onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
{PERIOD_TYPES.map((period) => { {PERIOD_TYPES.map((period) => {
@@ -408,22 +383,23 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
className="border-default my-0 block w-16 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2" className="border-default my-0 block w-16 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
placeholder="30" placeholder="30"
{...formMethods.register("periodDays", { valueAsNumber: true })} {...formMethods.register("periodDays", { valueAsNumber: true })}
defaultValue={eventType.periodDays || 30}
/> />
<Select <Select
options={optionsPeriod} options={optionsPeriod}
isSearchable={false} isSearchable={false}
onChange={(opt) => { onChange={(opt) =>
formMethods.setValue( formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
"periodCountCalendarDays",
opt?.value.toString() as "0" | "1"
);
}}
defaultValue={
optionsPeriod.find(
(opt) => opt.value === (eventType.periodCountCalendarDays ? 1 : 0)
) ?? optionsPeriod[0]
} }
name="periodCoundCalendarDays"
value={optionsPeriod.find((opt) => {
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
})}
defaultValue={optionsPeriod.find(
(opt) =>
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
)}
/> />
</div> </div>
)} )}
@@ -431,8 +407,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse"> <div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
<Controller <Controller
name="periodDates" name="periodDates"
control={formMethods.control}
defaultValue={periodDates}
render={() => ( render={() => (
<DateRangePicker <DateRangePicker
startDate={formMethods.getValues("periodDates").startDate} startDate={formMethods.getValues("periodDates").startDate}
@@ -481,7 +455,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
type="number" type="number"
containerClassName="max-w-80" containerClassName="max-w-80"
label={t("offset_start")} label={t("offset_start")}
{...formMethods.register("offsetStart")} {...formMethods.register("offsetStart", { setValueAs: (value) => Number(value) })}
addOnSuffix={<>{t("minutes")}</>} addOnSuffix={<>{t("minutes")}</>}
hint={t("offset_start_description", { hint={t("offset_start_description", {
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }), originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
@@ -9,9 +9,5 @@ export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupProps, "even
const requirePayment = paymentAppData.price > 0; const requirePayment = paymentAppData.price > 0;
return ( return <RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />;
<div className="">
<RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />
</div>
);
}; };
+27 -42
View File
@@ -48,21 +48,19 @@ const getLocationFromType = (
} }
}; };
const getLocationInfo = (props: Pick<EventTypeSetupProps, "eventType" | "locationOptions">) => { const getLocationInfo = ({
eventType,
locationOptions,
}: Pick<EventTypeSetupProps, "eventType" | "locationOptions">) => {
const locationAvailable = const locationAvailable =
props.eventType.locations && eventType.locations &&
props.eventType.locations.length > 0 && eventType.locations.length > 0 &&
props.locationOptions.some((op) => locationOptions.some((op) => op.options.find((opt) => opt.value === eventType.locations[0].type));
op.options.find((opt) => opt.value === props.eventType.locations[0].type) const locationDetails = eventType.locations &&
); eventType.locations.length > 0 &&
const locationDetails = props.eventType.locations &&
props.eventType.locations.length > 0 &&
!locationAvailable && { !locationAvailable && {
slug: props.eventType.locations[0].type slug: eventType.locations[0].type.replace("integrations:", "").replace(":", "-").replace("_video", ""),
.replace("integrations:", "") name: eventType.locations[0].type
.replace(":", "-")
.replace("_video", ""),
name: props.eventType.locations[0].type
.replace("integrations:", "") .replace("integrations:", "")
.replace(":", " ") .replace(":", " ")
.replace("_video", "") .replace("_video", "")
@@ -72,16 +70,11 @@ const getLocationInfo = (props: Pick<EventTypeSetupProps, "eventType" | "locatio
}; };
return { locationAvailable, locationDetails }; return { locationAvailable, locationDetails };
}; };
interface DescriptionEditorProps {
description?: string | null;
editable?: boolean;
}
const DescriptionEditor = (props: DescriptionEditorProps) => { const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
const formMethods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
const [mounted, setIsMounted] = useState(false); const [mounted, setIsMounted] = useState(false);
const { t } = useLocale(); const { t } = useLocale();
const { description } = props;
const [firstRender, setFirstRender] = useState(true); const [firstRender, setFirstRender] = useState(true);
useEffect(() => { useEffect(() => {
setIsMounted(true); setIsMounted(true);
@@ -89,11 +82,11 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
return mounted ? ( return mounted ? (
<Editor <Editor
getText={() => md.render(formMethods.getValues("description") || description || "")} getText={() => md.render(formMethods.getValues("description") || "")}
setText={(value: string) => formMethods.setValue("description", turndown(value))} setText={(value: string) => formMethods.setValue("description", turndown(value))}
excludedToolbarItems={["blockType"]} excludedToolbarItems={["blockType"]}
placeholder={t("quick_video_meeting")} placeholder={t("quick_video_meeting")}
editable={props.editable} editable={isEditable}
firstRender={firstRender} firstRender={firstRender}
setFirstRender={setFirstRender} setFirstRender={setFirstRender}
/> />
@@ -113,7 +106,9 @@ export const EventSetupTab = (
const { t } = useLocale(); const { t } = useLocale();
const formMethods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
const { eventType, team, destinationCalendar } = props; const { eventType, team, destinationCalendar } = props;
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration); const [multipleDuration, setMultipleDuration] = useState(
formMethods.getValues("metadata")?.multipleDuration
);
const orgBranding = useOrgBranding(); const orgBranding = useOrgBranding();
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled"); const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
@@ -143,12 +138,12 @@ export const EventSetupTab = (
}> }>
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value))); >(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
const [defaultDuration, setDefaultDuration] = useState( const [defaultDuration, setDefaultDuration] = useState(
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null selectedMultipleDuration.find((opt) => opt.value === formMethods.getValues("length")) ?? null
); );
const { isChildrenManagedEventType, isManagedEventType, shouldLockIndicator, shouldLockDisableProps } = const { isChildrenManagedEventType, isManagedEventType, shouldLockIndicator, shouldLockDisableProps } =
useLockedFieldsManager( useLockedFieldsManager(
eventType, formMethods.getValues(),
t("locked_fields_admin_description"), t("locked_fields_admin_description"),
t("locked_fields_member_description") t("locked_fields_member_description")
); );
@@ -195,7 +190,6 @@ export const EventSetupTab = (
return ( return (
<Controller <Controller
name={`locations.${index}.${eventLocationType.defaultValueVariable}`} name={`locations.${index}.${eventLocationType.defaultValueVariable}`}
control={formMethods.control}
defaultValue={defaultValue} defaultValue={defaultValue}
render={({ field: { onChange, value } }) => { render={({ field: { onChange, value } }) => {
return ( return (
@@ -219,7 +213,6 @@ export const EventSetupTab = (
return ( return (
<Controller <Controller
name={`locations.${index}.${eventLocationType.defaultValueVariable}`} name={`locations.${index}.${eventLocationType.defaultValueVariable}`}
control={formMethods.control}
defaultValue={defaultValue} defaultValue={defaultValue}
render={({ field: { onChange, value } }) => { render={({ field: { onChange, value } }) => {
return ( return (
@@ -339,7 +332,7 @@ export const EventSetupTab = (
defaultChecked={defaultLocation?.displayLocationPublicly} defaultChecked={defaultLocation?.displayLocationPublicly}
description={t("display_location_label")} description={t("display_location_label")}
onChange={(e) => { onChange={(e) => {
const fieldValues = formMethods.getValues().locations[index]; const fieldValues = formMethods.getValues("locations")[index];
updateLocationField(index, { updateLocationField(index, {
...fieldValues, ...fieldValues,
displayLocationPublicly: e.target.checked, displayLocationPublicly: e.target.checked,
@@ -408,7 +401,7 @@ export const EventSetupTab = (
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work. The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
Change it{" "} Change it{" "}
<Link <Link
href={`${CAL_URL}/event-types/${eventType.id}?tabName=advanced`} href={`${CAL_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
className="underline"> className="underline">
here. here.
</Link>{" "} </Link>{" "}
@@ -463,7 +456,6 @@ export const EventSetupTab = (
required required
label={t("title")} label={t("title")}
{...shouldLockDisableProps("title")} {...shouldLockDisableProps("title")}
defaultValue={eventType.title}
{...formMethods.register("title")} {...formMethods.register("title")}
/> />
<div> <div>
@@ -471,23 +463,19 @@ export const EventSetupTab = (
{t("description")} {t("description")}
{shouldLockIndicator("description")} {shouldLockIndicator("description")}
</Label> </Label>
<DescriptionEditor <DescriptionEditor isEditable={!descriptionLockedProps.disabled} />
description={eventType?.description}
editable={!descriptionLockedProps.disabled}
/>
</div> </div>
<TextField <TextField
required required
label={t("URL")} label={t("URL")}
{...shouldLockDisableProps("slug")} {...shouldLockDisableProps("slug")}
defaultValue={eventType.slug}
addOnLeading={ addOnLeading={
<> <>
{urlPrefix}/ {urlPrefix}/
{!isManagedEventType {!isManagedEventType
? team ? team
? (orgBranding ? "" : "team/") + team.slug ? (orgBranding ? "" : "team/") + team.slug
: eventType.users[0].username : formMethods.getValues("users")[0].username
: t("username_placeholder")} : t("username_placeholder")}
/ /
</> </>
@@ -564,7 +552,7 @@ export const EventSetupTab = (
type="number" type="number"
{...lengthLockedProps} {...lengthLockedProps}
label={t("duration")} label={t("duration")}
defaultValue={eventType.length ?? 15} defaultValue={formMethods.getValues("length") ?? 15}
{...formMethods.register("length")} {...formMethods.register("length")}
addOnSuffix={<>{t("minutes")}</>} addOnSuffix={<>{t("minutes")}</>}
min={1} min={1}
@@ -580,6 +568,8 @@ export const EventSetupTab = (
onCheckedChange={() => { onCheckedChange={() => {
if (multipleDuration !== undefined) { if (multipleDuration !== undefined) {
setMultipleDuration(undefined); setMultipleDuration(undefined);
setSelectedMultipleDuration([]);
setDefaultDuration(null);
formMethods.setValue("metadata.multipleDuration", undefined); formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length); formMethods.setValue("length", eventType.length);
} else { } else {
@@ -600,12 +590,7 @@ export const EventSetupTab = (
{shouldLockIndicator("locations")} {shouldLockIndicator("locations")}
</Skeleton> </Skeleton>
<Controller <Controller name="locations" render={() => <Locations />} />
name="locations"
control={formMethods.control}
defaultValue={eventType.locations || []}
render={() => <Locations />}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -67,55 +67,66 @@ type Props = {
availability?: AvailabilityOption; availability?: AvailabilityOption;
isUserOrganizationAdmin: boolean; isUserOrganizationAdmin: boolean;
bookerUrl: string; bookerUrl: string;
activeWebhooksNumber: number;
}; };
function getNavigation(props: { type getNavigationProps = {
t: TFunction; t: TFunction;
eventType: Props["eventType"]; length: number;
id: number;
multipleDuration?: EventTypeSetupProps["eventType"]["metadata"]["multipleDuration"];
enabledAppsNumber: number; enabledAppsNumber: number;
enabledWorkflowsNumber: number; enabledWorkflowsNumber: number;
installedAppsNumber: number; installedAppsNumber: number;
availability: AvailabilityOption | undefined; availability: AvailabilityOption | undefined;
}) { };
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const duration = function getNavigation({
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length; length,
id,
multipleDuration,
t,
enabledAppsNumber,
installedAppsNumber,
enabledWorkflowsNumber,
}: getNavigationProps) {
const duration = multipleDuration?.map((duration) => ` ${duration}`) || length;
return [ return [
{ {
name: "event_setup_tab_title", name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`, href: `/event-types/${id}?tabName=setup`,
icon: LinkIcon, icon: LinkIcon,
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
}, },
{ {
name: "event_limit_tab_title", name: "event_limit_tab_title",
href: `/event-types/${eventType.id}?tabName=limits`, href: `/event-types/${id}?tabName=limits`,
icon: Clock, icon: Clock,
info: `event_limit_tab_description`, info: `event_limit_tab_description`,
}, },
{ {
name: "event_advanced_tab_title", name: "event_advanced_tab_title",
href: `/event-types/${eventType.id}?tabName=advanced`, href: `/event-types/${id}?tabName=advanced`,
icon: Sliders, icon: Sliders,
info: `event_advanced_tab_description`, info: `event_advanced_tab_description`,
}, },
{ {
name: "recurring", name: "recurring",
href: `/event-types/${eventType.id}?tabName=recurring`, href: `/event-types/${id}?tabName=recurring`,
icon: Repeat, icon: Repeat,
info: `recurring_event_tab_description`, info: `recurring_event_tab_description`,
}, },
{ {
name: "apps", name: "apps",
href: `/event-types/${eventType.id}?tabName=apps`, href: `/event-types/${id}?tabName=apps`,
icon: Grid, icon: Grid,
//TODO: Handle proper translation with count handling //TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`, info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
}, },
{ {
name: "workflows", name: "workflows",
href: `/event-types/${eventType.id}?tabName=workflows`, href: `/event-types/${id}?tabName=workflows`,
icon: Zap, icon: Zap,
info: `${enabledWorkflowsNumber} ${t("active")}`, info: `${enabledWorkflowsNumber} ${t("active")}`,
}, },
@@ -136,6 +147,7 @@ function EventTypeSingleLayout({
availability, availability,
isUserOrganizationAdmin, isUserOrganizationAdmin,
bookerUrl, bookerUrl,
activeWebhooksNumber,
}: Props) { }: Props) {
const utils = trpc.useContext(); const utils = trpc.useContext();
const { t } = useLocale(); const { t } = useLocale();
@@ -145,7 +157,7 @@ function EventTypeSingleLayout({
const hasPermsToDelete = const hasPermsToDelete =
currentUserMembership?.role !== "MEMBER" || currentUserMembership?.role !== "MEMBER" ||
!currentUserMembership || !currentUserMembership ||
eventType.schedulingType === SchedulingType.MANAGED || formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
isUserOrganizationAdmin; isUserOrganizationAdmin;
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({ const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
@@ -167,16 +179,23 @@ function EventTypeSingleLayout({
}); });
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager( const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
eventType, formMethods.getValues(),
t("locked_fields_admin_description"), t("locked_fields_admin_description"),
t("locked_fields_member_description") t("locked_fields_member_description")
); );
const length = formMethods.watch("length");
const multipleDuration = formMethods.watch("metadata")?.multipleDuration;
const watchSchedulingType = formMethods.watch("schedulingType");
const watchChildrenCount = formMethods.watch("children").length;
// Define tab navigation here // Define tab navigation here
const EventTypeTabs = useMemo(() => { const EventTypeTabs = useMemo(() => {
const navigation = getNavigation({ const navigation = getNavigation({
t, t,
eventType, length,
multipleDuration,
id: formMethods.getValues("id"),
enabledAppsNumber, enabledAppsNumber,
installedAppsNumber, installedAppsNumber,
enabledWorkflowsNumber, enabledWorkflowsNumber,
@@ -185,31 +204,29 @@ function EventTypeSingleLayout({
navigation.splice(1, 0, { navigation.splice(1, 0, {
name: "availability", name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`, href: `/event-types/${formMethods.getValues("id")}?tabName=availability`,
icon: Calendar, icon: Calendar,
info: info:
isManagedEventType || isChildrenManagedEventType isManagedEventType || isChildrenManagedEventType
? eventType.schedule === null ? formMethods.getValues("schedule") === null
? "members_default_schedule" ? "members_default_schedule"
: isChildrenManagedEventType : isChildrenManagedEventType
? `${ ? `${
eventType.scheduleName formMethods.getValues("scheduleName")
? `${eventType.scheduleName} - ${t("managed")}` ? `${formMethods.getValues("scheduleName")} - ${t("managed")}`
: `default_schedule_name` : `default_schedule_name`
}` }`
: eventType.scheduleName ?? `default_schedule_name` : formMethods.getValues("scheduleName") ?? `default_schedule_name`
: eventType.scheduleName ?? `default_schedule_name`, : formMethods.getValues("scheduleName") ?? `default_schedule_name`,
}); });
// If there is a team put this navigation item within the tabs // If there is a team put this navigation item within the tabs
if (team) { if (team) {
navigation.splice(2, 0, { navigation.splice(2, 0, {
name: "assignment", name: "assignment",
href: `/event-types/${eventType.id}?tabName=team`, href: `/event-types/${formMethods.getValues("id")}?tabName=team`,
icon: Users, icon: Users,
info: `${t(eventType.schedulingType?.toLowerCase() ?? "")}${ info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
isManagedEventType isManagedEventType ? ` - ${t("number_member", { count: watchChildrenCount || 0 })}` : ""
? ` - ${t("number_member", { count: formMethods.watch("children").length || 0 })}`
: ""
}`, }`,
}); });
} }
@@ -225,15 +242,14 @@ function EventTypeSingleLayout({
} }
navigation.push({ navigation.push({
name: "webhooks", name: "webhooks",
href: `/event-types/${eventType.id}?tabName=webhooks`, href: `/event-types/${formMethods.getValues("id")}?tabName=webhooks`,
icon: TbWebhook, icon: TbWebhook,
info: `${eventType.webhooks.filter((webhook) => webhook.active).length} ${t("active")}`, info: `${activeWebhooksNumber} ${t("active")}`,
}); });
} }
return navigation; return navigation;
}, [ }, [
t, t,
eventType,
enabledAppsNumber, enabledAppsNumber,
installedAppsNumber, installedAppsNumber,
enabledWorkflowsNumber, enabledWorkflowsNumber,
@@ -241,16 +257,23 @@ function EventTypeSingleLayout({
isManagedEventType, isManagedEventType,
isChildrenManagedEventType, isChildrenManagedEventType,
team, team,
formMethods, length,
multipleDuration,
formMethods.getValues("id"),
watchSchedulingType,
watchChildrenCount,
activeWebhooksNumber,
]); ]);
const permalink = `${bookerUrl}/${ const permalink = `${bookerUrl}/${
team ? `${!team.parentId ? "team/" : ""}${team.slug}` : eventType.users[0].username team ? `${!team.parentId ? "team/" : ""}${team.slug}` : formMethods.getValues("users")[0].username
}/${eventType.slug}`; }/${eventType.slug}`;
const embedLink = `${team ? `team/${team.slug}` : eventType.users[0].username}/${eventType.slug}`; const embedLink = `${
const isManagedEvent = eventType.schedulingType === SchedulingType.MANAGED ? "_managed" : ""; team ? `team/${team.slug}` : formMethods.getValues("users")[0].username
}/${formMethods.getValues("slug")}`;
const isManagedEvent = formMethods.getValues("schedulingType") === SchedulingType.MANAGED ? "_managed" : "";
// const title = formMethods.watch("title");
return ( return (
<Shell <Shell
backPath="/event-types" backPath="/event-types"
@@ -258,7 +281,7 @@ function EventTypeSingleLayout({
heading={eventType.title} heading={eventType.title}
CTA={ CTA={
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
{!eventType.metadata?.managedEventConfig && ( {!formMethods.getValues("metadata")?.managedEventConfig && (
<> <>
<div <div
className={classNames( className={classNames(
@@ -333,7 +356,7 @@ function EventTypeSingleLayout({
tooltip={t("embed")} tooltip={t("embed")}
tooltipSide="bottom" tooltipSide="bottom"
tooltipOffset={4} tooltipOffset={4}
eventId={eventType.id} eventId={formMethods.getValues("id")}
/> />
</> </>
)} )}
@@ -446,14 +469,14 @@ function EventTypeSingleLayout({
</Suspense> </Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={deleteMutation.isLoading} isPending={deleteMutation.isPending}
variety="danger" variety="danger"
title={t(`delete${isManagedEvent}_event_type`)} title={t(`delete${isManagedEvent}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)} confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)} loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => { onConfirm={(e) => {
e.preventDefault(); e.preventDefault();
deleteMutation.mutate({ id: eventType.id }); deleteMutation.mutate({ id: formMethods.getValues("id") });
}}> }}>
<p className="mt-5"> <p className="mt-5">
<Trans <Trans
@@ -35,8 +35,9 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({ const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({
async onSuccess() { async onSuccess() {
setEditModalOpen(false); setEditModalOpen(false);
await utils.viewer.webhook.list.invalidate();
showToast(t("webhook_updated_successfully"), "success"); showToast(t("webhook_updated_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
await utils.viewer.eventTypes.get.invalidate();
}, },
onError(error) { onError(error) {
showToast(`${error.message}`, "error"); showToast(`${error.message}`, "error");
@@ -45,9 +46,10 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({ const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
async onSuccess() { async onSuccess() {
setCreateModalOpen(false);
showToast(t("webhook_created_successfully"), "success"); showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate(); await utils.viewer.webhook.list.invalidate();
setCreateModalOpen(false); await utils.viewer.eventTypes.get.invalidate();
}, },
onError(error) { onError(error) {
showToast(`${error.message}`, "error"); showToast(`${error.message}`, "error");
@@ -108,7 +108,7 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
eventTypeId: eventType.id, eventTypeId: eventType.id,
eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING], eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING],
}); });
const { data: installedApps, isLoading } = trpc.viewer.integrations.useQuery({ const { data: installedApps, isPending } = trpc.viewer.integrations.useQuery({
variant: "other", variant: "other",
onlyInstalled: true, onlyInstalled: true,
}); });
@@ -188,7 +188,7 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
return ( return (
<div> <div>
{webhooks && !isLoading && ( {webhooks && !isPending && (
<> <>
<div> <div>
{webhooks.length ? ( {webhooks.length ? (
@@ -8,18 +8,18 @@ interface ManagedEventDialogProps {
slugExistsChildrenDialogOpen: ChildrenEventType[]; slugExistsChildrenDialogOpen: ChildrenEventType[];
slug: string; slug: string;
onOpenChange: () => void; onOpenChange: () => void;
isLoading: boolean; isPending: boolean;
onConfirm: (e: { preventDefault: () => void }) => void; onConfirm: (e: { preventDefault: () => void }) => void;
} }
export default function ManagedEventDialog(props: ManagedEventDialogProps) { export default function ManagedEventDialog(props: ManagedEventDialogProps) {
const { t } = useLocale(); const { t } = useLocale();
const { slugExistsChildrenDialogOpen, slug, onOpenChange, isLoading, onConfirm } = props; const { slugExistsChildrenDialogOpen, slug, onOpenChange, isPending, onConfirm } = props;
return ( return (
<Dialog open={slugExistsChildrenDialogOpen.length > 0} onOpenChange={onOpenChange}> <Dialog open={slugExistsChildrenDialogOpen.length > 0} onOpenChange={onOpenChange}>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={isLoading} isPending={isPending}
variety="warning" variety="warning"
title={t("managed_event_dialog_title", { title={t("managed_event_dialog_title", {
slug, slug,
@@ -19,11 +19,10 @@ export default function RecurringEventController({
paymentEnabled, paymentEnabled,
}: RecurringEventControllerProps) { }: RecurringEventControllerProps) {
const { t } = useLocale(); const { t } = useLocale();
const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(
eventType.recurringEvent
);
const formMethods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(
formMethods.getValues("recurringEvent")
);
/* Just yearly-0, monthly-1 and weekly-2 */ /* Just yearly-0, monthly-1 and weekly-2 */
const recurringEventFreqOptions = Object.entries(Frequency) const recurringEventFreqOptions = Object.entries(Frequency)
.filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3) .filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3)
@@ -28,7 +28,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
color="secondary" color="secondary"
disabled={installed} disabled={installed}
type="button" type="button"
loading={buttonProps?.isLoading} loading={buttonProps?.isPending}
onClick={(event) => { onClick={(event) => {
// Save cookie key to return url step // Save cookie key to return url step
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`; document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
@@ -31,7 +31,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
return ( return (
<> <>
{/* Already connected calendars */} {/* Already connected calendars */}
{!queryConnectedCalendars.isLoading && {!queryConnectedCalendars.isPending &&
firstCalendar && firstCalendar &&
firstCalendar.integration && firstCalendar.integration &&
firstCalendar.integration.title && firstCalendar.integration.title &&
@@ -76,7 +76,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
</List> </List>
)} )}
{queryIntegrations.isLoading && <StepConnectionLoader />} {queryIntegrations.isPending && <StepConnectionLoader />}
<button <button
type="button" type="button"
@@ -13,7 +13,7 @@ interface ConnectedAppStepProps {
const ConnectedVideoStep = (props: ConnectedAppStepProps) => { const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
const { nextStep } = props; const { nextStep } = props;
const { data: queryConnectedVideoApps, isLoading } = trpc.viewer.integrations.useQuery({ const { data: queryConnectedVideoApps, isPending } = trpc.viewer.integrations.useQuery({
variant: "conferencing", variant: "conferencing",
onlyInstalled: false, onlyInstalled: false,
sortByMostPopular: true, sortByMostPopular: true,
@@ -26,7 +26,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
return ( return (
<> <>
{!isLoading && ( {!isPending && (
<List className="bg-default border-subtle divide-subtle scroll-bar mx-1 max-h-[45vh] divide-y !overflow-y-scroll rounded-md border p-0 sm:mx-0"> <List className="bg-default border-subtle divide-subtle scroll-bar mx-1 max-h-[45vh] divide-y !overflow-y-scroll rounded-md border p-0 sm:mx-0">
{queryConnectedVideoApps?.items && {queryConnectedVideoApps?.items &&
queryConnectedVideoApps?.items.map((item) => { queryConnectedVideoApps?.items.map((item) => {
@@ -48,7 +48,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
</List> </List>
)} )}
{isLoading && <StepConnectionLoader />} {isPending && <StepConnectionLoader />}
<button <button
type="button" type="button"
data-testid="save-video-button" data-testid="save-video-button"
@@ -113,7 +113,7 @@ const UserSettings = (props: IUserSettingsProps) => {
<Button <Button
type="submit" type="submit"
className="mt-8 flex w-full flex-row justify-center" className="mt-8 flex w-full flex-row justify-center"
disabled={mutation.isLoading}> disabled={mutation.isPending}>
{t("next_step_text")} {t("next_step_text")}
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" /> <ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</Button> </Button>
+2 -2
View File
@@ -7,7 +7,7 @@ import { Check } from "@calcom/ui/components/icon";
const StepDone = (props: { const StepDone = (props: {
currentStep: number; currentStep: number;
nextStepPath: string; nextStepPath: string;
setIsLoading: Dispatch<SetStateAction<boolean>>; setIsPending: Dispatch<SetStateAction<boolean>>;
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { t } = useLocale(); const { t } = useLocale();
@@ -18,7 +18,7 @@ const StepDone = (props: {
name={`wizard-step-${props.currentStep}`} name={`wizard-step-${props.currentStep}`}
className="flex justify-center space-y-4" className="flex justify-center space-y-4"
onSubmit={(e) => { onSubmit={(e) => {
props.setIsLoading(true); props.setIsPending(true);
e.preventDefault(); e.preventDefault();
router.replace(props.nextStepPath); router.replace(props.nextStepPath);
}}> }}>
@@ -293,7 +293,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && ( {usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
<Button <Button
type="button" type="button"
loading={updateUsername.isLoading} loading={updateUsername.isPending}
data-testid="go-to-billing" data-testid="go-to-billing"
href={paymentLink}> href={paymentLink}>
<> <>
@@ -305,7 +305,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
{usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE && ( {usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE && (
<Button <Button
type="button" type="button"
loading={updateUsername.isLoading} loading={updateUsername.isPending}
data-testid="save-username" data-testid="save-username"
onClick={() => { onClick={() => {
saveUsername(); saveUsername();
@@ -177,7 +177,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
<DialogFooter className="mt-4"> <DialogFooter className="mt-4">
<Button <Button
type="button" type="button"
loading={updateUsernameMutation.isLoading} loading={updateUsernameMutation.isPending}
data-testid="save-username" data-testid="save-username"
onClick={updateUsername}> onClick={updateUsername}>
{t("save")} {t("save")}
@@ -2,8 +2,7 @@ import type { InputHTMLAttributes } from "react";
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import classNames from "@calcom/lib/classNames"; import classNames from "@calcom/lib/classNames";
import { InfoBadge } from "@calcom/ui";
import InfoBadge from "@components/ui/InfoBadge";
type Props = InputHTMLAttributes<HTMLInputElement> & { type Props = InputHTMLAttributes<HTMLInputElement> & {
label?: React.ReactNode; label?: React.ReactNode;
-1
View File
@@ -1 +0,0 @@
export const IS_VISUAL_REGRESSION_TESTING = Boolean(globalThis.window?.Meticulous?.isRunningAsTest);
+8 -36
View File
@@ -1,21 +1,15 @@
import type { import type {
QueryObserverLoadingErrorResult, QueryObserverPendingResult,
QueryObserverLoadingResult,
QueryObserverRefetchErrorResult, QueryObserverRefetchErrorResult,
QueryObserverSuccessResult, QueryObserverSuccessResult,
QueryObserverLoadingErrorResult,
UseQueryResult, UseQueryResult,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { DecorateProcedure } from "@calcom/trpc/react/shared";
import type { AnyQueryProcedure, inferProcedureInput, inferProcedureOutput } from "@calcom/trpc/server";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Alert, Loader } from "@calcom/ui"; import { Alert, Loader } from "@calcom/ui";
import type { UseTRPCQueryOptions } from "@trpc/react-query/shared";
type ErrorLike = { type ErrorLike = {
message: string; message: string;
}; };
@@ -27,7 +21,7 @@ interface QueryCellOptionsBase<TData, TError extends ErrorLike> {
error?: ( error?: (
query: QueryObserverLoadingErrorResult<TData, TError> | QueryObserverRefetchErrorResult<TData, TError> query: QueryObserverLoadingErrorResult<TData, TError> | QueryObserverRefetchErrorResult<TData, TError>
) => JSXElementOrNull; ) => JSXElementOrNull;
loading?: (query: QueryObserverLoadingResult<TData, TError> | null) => JSXElementOrNull; loading?: (query: QueryObserverPendingResult<TData, TError> | null) => JSXElementOrNull;
} }
interface QueryCellOptionsNoEmpty<TData, TError extends ErrorLike> interface QueryCellOptionsNoEmpty<TData, TError extends ErrorLike>
@@ -58,8 +52,11 @@ export function QueryCell<TData, TError extends ErrorLike>(
const { isLocaleReady } = useLocale(); const { isLocaleReady } = useLocale();
const StatusLoader = opts.customLoader || <Loader />; // Fixes edge case where this can return null form query cell const StatusLoader = opts.customLoader || <Loader />; // Fixes edge case where this can return null form query cell
if (query.status === "loading" || !isLocaleReady) { if (!isLocaleReady) {
return opts.loading?.(query.status === "loading" ? query : null) ?? StatusLoader; return opts.loading?.(query.status === "pending" ? query : null) ?? StatusLoader;
}
if (query.status === "pending") {
return opts.loading?.(query) ?? StatusLoader;
} }
if (query.status === "success") { if (query.status === "success") {
@@ -81,28 +78,3 @@ export function QueryCell<TData, TError extends ErrorLike>(
// impossible state // impossible state
return null; return null;
} }
type TError = TRPCClientErrorLike<AppRouter>;
const withQuery = <
TQuery extends AnyQueryProcedure,
TInput = inferProcedureInput<TQuery>,
TOutput = inferProcedureOutput<TQuery>
>(
queryProcedure: DecorateProcedure<TQuery, inferProcedureInput<TQuery>, inferProcedureOutput<TQuery>>,
input?: TInput,
params?: UseTRPCQueryOptions<TQuery, TInput, TOutput, TOutput, TError>
) => {
return function WithQuery(
opts: Omit<
Partial<QueryCellOptionsWithEmpty<TOutput, TError>> & QueryCellOptionsNoEmpty<TOutput, TError>,
"query"
>
) {
const query = queryProcedure.useQuery(input, params);
return <QueryCell query={query} {...opts} />;
};
};
export { withQuery };
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@calcom/web", "name": "@calcom/web",
"version": "3.7.3", "version": "3.7.8",
"private": true, "private": true,
"scripts": { "scripts": {
"analyze": "ANALYZE=true next build", "analyze": "ANALYZE=true next build",
@@ -60,7 +60,7 @@
"@sentry/nextjs": "^7.73.0", "@sentry/nextjs": "^7.73.0",
"@stripe/react-stripe-js": "^1.10.0", "@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0", "@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9", "@tanstack/react-query": "^5.17.15",
"@tremor/react": "^2.0.0", "@tremor/react": "^2.0.0",
"@types/turndown": "^5.0.1", "@types/turndown": "^5.0.1",
"@upstash/ratelimit": "^0.4.3", "@upstash/ratelimit": "^0.4.3",
@@ -1,9 +1,9 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import type { OAuthTokenPayload } from "pages/api/auth/oauth/token";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler"; import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler";
import type { OAuthTokenPayload } from "@calcom/types/oauth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") { if (req.method !== "POST") {
+1 -8
View File
@@ -3,14 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler"; import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler";
import type { OAuthTokenPayload } from "@calcom/types/oauth";
export type OAuthTokenPayload = {
userId?: number | null;
teamId?: number | null;
token_type: string;
scope: string[];
clientId: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") { if (req.method !== "POST") {
+11 -6
View File
@@ -5,7 +5,7 @@ import classNames from "classnames";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState, useEffect } from "react";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa"; import { FaGoogle } from "react-icons/fa";
import { z } from "zod"; import { z } from "zod";
@@ -157,15 +157,20 @@ inferSSRProps<typeof getServerSideProps> & WithNonceProps<{}>) {
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong")); else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
}; };
const { data, isLoading } = trpc.viewer.public.ssoConnections.useQuery(undefined, { const { data, isPending, error } = trpc.viewer.public.ssoConnections.useQuery();
onError: (err) => {
setErrorMessage(err.message); useEffect(
function refactorMeWithoutEffect() {
if (error) {
setErrorMessage(error.message);
}
}, },
}); [error]
);
const displaySSOLogin = HOSTED_CAL_FEATURES const displaySSOLogin = HOSTED_CAL_FEATURES
? true ? true
: isSAMLLoginEnabled && !isLoading && data?.connectionExists; : isSAMLLoginEnabled && !isPending && data?.connectionExists;
return ( return (
<div className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand-emphasis:#101010] [--cal-brand-subtle:#9CA3AF] [--cal-brand-text:white] [--cal-brand:#111827] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand-text:black] dark:[--cal-brand:white]"> <div className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand-emphasis:#101010] [--cal-brand-subtle:#9CA3AF] [--cal-brand-text:white] [--cal-brand:#111827] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand-text:black] dark:[--cal-brand:white]">
+5 -5
View File
@@ -28,7 +28,7 @@ export default function Authorize() {
const [selectedAccount, setSelectedAccount] = useState<{ value: string; label: string } | null>(); const [selectedAccount, setSelectedAccount] = useState<{ value: string; label: string } | null>();
const scopes = scope ? scope.toString().split(",") : []; const scopes = scope ? scope.toString().split(",") : [];
const { data: client, isLoading: isLoadingGetClient } = trpc.viewer.oAuth.getClient.useQuery( const { data: client, isPending: isPendingGetClient } = trpc.viewer.oAuth.getClient.useQuery(
{ {
clientId: client_id as string, clientId: client_id as string,
}, },
@@ -37,7 +37,7 @@ export default function Authorize() {
} }
); );
const { data, isLoading: isLoadingProfiles } = trpc.viewer.teamsAndUserProfilesQuery.useQuery(); const { data, isPending: isPendingProfiles } = trpc.viewer.teamsAndUserProfilesQuery.useQuery();
const generateAuthCodeMutation = trpc.viewer.oAuth.generateAuthCode.useMutation({ const generateAuthCodeMutation = trpc.viewer.oAuth.generateAuthCode.useMutation({
onSuccess: (data) => { onSuccess: (data) => {
@@ -58,7 +58,7 @@ export default function Authorize() {
if (mappedProfiles.length > 0) { if (mappedProfiles.length > 0) {
setSelectedAccount(mappedProfiles[0]); setSelectedAccount(mappedProfiles[0]);
} }
}, [isLoadingProfiles]); }, [isPendingProfiles]);
useEffect(() => { useEffect(() => {
if (status === "unauthenticated") { if (status === "unauthenticated") {
@@ -69,9 +69,9 @@ export default function Authorize() {
} }
}, [status]); }, [status]);
const isLoading = isLoadingGetClient || isLoadingProfiles || status !== "authenticated"; const isPending = isPendingGetClient || isPendingProfiles || status !== "authenticated";
if (isLoading) { if (isPending) {
return <></>; return <></>;
} }
+13 -62
View File
@@ -1,15 +1,12 @@
import type { GetServerSidePropsContext } from "next"; "use client";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import AdminAppsList from "@calcom/features/apps/AdminAppsList"; import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
import { APP_NAME } from "@calcom/lib/constants"; import { APP_NAME } from "@calcom/lib/constants";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Meta, WizardForm } from "@calcom/ui"; import { Meta, WizardForm } from "@calcom/ui";
@@ -18,7 +15,7 @@ import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser";
import ChooseLicense from "@components/setup/ChooseLicense"; import ChooseLicense from "@components/setup/ChooseLicense";
import EnterpriseLicense from "@components/setup/EnterpriseLicense"; import EnterpriseLicense from "@components/setup/EnterpriseLicense";
import { ssrInit } from "@server/lib/ssr"; import { getServerSideProps } from "@server/lib/setup/getServerSideProps";
function useSetStep() { function useSetStep() {
const router = useRouter(); const router = useRouter();
@@ -44,16 +41,16 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
{ {
title: t("administrator_user"), title: t("administrator_user"),
description: t("lets_create_first_administrator_user"), description: t("lets_create_first_administrator_user"),
content: (setIsLoading) => ( content: (setIsPending) => (
<AdminUser <AdminUser
onSubmit={() => { onSubmit={() => {
setIsLoading(true); setIsPending(true);
}} }}
onSuccess={() => { onSuccess={() => {
setStep(2); setStep(2);
}} }}
onError={() => { onError={() => {
setIsLoading(false); setIsPending(false);
}} }}
userCount={props.userCount} userCount={props.userCount}
/> />
@@ -62,7 +59,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
{ {
title: t("choose_a_license"), title: t("choose_a_license"),
description: t("choose_license_description"), description: t("choose_license_description"),
content: (setIsLoading) => { content: (setIsPending) => {
return ( return (
<ChooseLicense <ChooseLicense
id="wizard-step-2" id="wizard-step-2"
@@ -70,7 +67,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
value={value} value={value}
onChange={setValue} onChange={setValue}
onSubmit={() => { onSubmit={() => {
setIsLoading(true); setIsPending(true);
setStep(3); setStep(3);
}} }}
/> />
@@ -83,14 +80,14 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
steps.push({ steps.push({
title: t("step_enterprise_license"), title: t("step_enterprise_license"),
description: t("step_enterprise_license_description"), description: t("step_enterprise_license_description"),
content: (setIsLoading) => { content: (setIsPending) => {
const currentStep = 3; const currentStep = 3;
return ( return (
<EnterpriseLicense <EnterpriseLicense
id={`wizard-step-${currentStep}`} id={`wizard-step-${currentStep}`}
name={`wizard-step-${currentStep}`} name={`wizard-step-${currentStep}`}
onSubmit={() => { onSubmit={() => {
setIsLoading(true); setIsPending(true);
}} }}
onSuccess={() => { onSuccess={() => {
setStep(currentStep + 1); setStep(currentStep + 1);
@@ -109,7 +106,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
title: t("enable_apps"), title: t("enable_apps"),
description: t("enable_apps_description", { appName: APP_NAME }), description: t("enable_apps_description", { appName: APP_NAME }),
contentClassname: "!pb-0 mb-[-1px]", contentClassname: "!pb-0 mb-[-1px]",
content: (setIsLoading) => { content: (setIsPending) => {
const currentStep = isFreeLicense ? 3 : 4; const currentStep = isFreeLicense ? 3 : 4;
return ( return (
<AdminAppsList <AdminAppsList
@@ -123,7 +120,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
baseURL={`/auth/setup?step=${currentStep}`} baseURL={`/auth/setup?step=${currentStep}`}
useQueryParam={true} useQueryParam={true}
onSubmit={() => { onSubmit={() => {
setIsLoading(true); setIsPending(true);
router.replace("/"); router.replace("/");
}} }}
/> />
@@ -152,50 +149,4 @@ Setup.isThemeSupported = false;
Setup.PageWrapper = PageWrapper; Setup.PageWrapper = PageWrapper;
export default Setup; export default Setup;
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export { getServerSideProps };
const { req } = context;
const ssr = await ssrInit(context);
const userCount = await prisma.user.count();
const session = await getServerSession({ req });
if (session?.user.role && session?.user.role !== UserPermissionRole.ADMIN) {
return {
redirect: {
destination: `/404`,
permanent: false,
},
};
}
const deploymentKey = await prisma.deployment.findUnique({
where: { id: 1 },
select: { licenseKey: true },
});
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) {
await prisma.deployment.upsert({
where: { id: 1 },
update: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
create: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
});
}
const isFreeLicense = (await getDeploymentKey(prisma)) === "";
return {
props: {
trpcState: ssr.dehydrate(),
isFreeLicense,
userCount,
},
};
};
+1 -1
View File
@@ -44,7 +44,7 @@ function VerifyEmailPage() {
<Button <Button
color="minimal" color="minimal"
className="underline" className="underline"
loading={mutation.isLoading} loading={mutation.isPending}
onClick={() => { onClick={() => {
showToast("Send email", "success"); showToast("Send email", "success");
mutation.mutate(); mutation.mutate();
+7 -7
View File
@@ -93,7 +93,7 @@ export default function Availability() {
const fromEventType = searchParams?.get("fromEventType"); const fromEventType = searchParams?.get("fromEventType");
const { timeFormat } = me.data || { timeFormat: null }; const { timeFormat } = me.data || { timeFormat: null };
const [openSidebar, setOpenSidebar] = useState(false); const [openSidebar, setOpenSidebar] = useState(false);
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery( const { data: schedule, isPending } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId }, { scheduleId },
{ {
enabled: !!scheduleId, enabled: !!scheduleId,
@@ -158,7 +158,7 @@ export default function Availability() {
control={form.control} control={form.control}
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<EditableHeading isReady={!isLoading} {...field} data-testid="availablity-title" /> <EditableHeading isReady={!isPending} {...field} data-testid="availablity-title" />
)} )}
/> />
} }
@@ -188,7 +188,7 @@ export default function Availability() {
</Skeleton> </Skeleton>
<Switch <Switch
id="hiddenSwitch" id="hiddenSwitch"
disabled={isLoading || schedule?.isDefault} disabled={isPending || schedule?.isDefault}
checked={form.watch("isDefault")} checked={form.watch("isDefault")}
onCheckedChange={(e) => { onCheckedChange={(e) => {
form.setValue("isDefault", e); form.setValue("isDefault", e);
@@ -210,7 +210,7 @@ export default function Availability() {
/> />
</DialogTrigger> </DialogTrigger>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={deleteMutation.isLoading} isPending={deleteMutation.isPending}
variety="danger" variety="danger"
title={t("delete_schedule")} title={t("delete_schedule")}
confirmBtnText={t("delete")} confirmBtnText={t("delete")}
@@ -249,7 +249,7 @@ export default function Availability() {
/> />
</DialogTrigger> </DialogTrigger>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={deleteMutation.isLoading} isPending={deleteMutation.isPending}
variety="danger" variety="danger"
title={t("delete_schedule")} title={t("delete_schedule")}
confirmBtnText={t("delete")} confirmBtnText={t("delete")}
@@ -284,7 +284,7 @@ export default function Availability() {
</Skeleton> </Skeleton>
<Switch <Switch
id="hiddenSwitch" id="hiddenSwitch"
disabled={isLoading || schedule?.isDefault} disabled={isPending || schedule?.isDefault}
checked={form.watch("isDefault")} checked={form.watch("isDefault")}
onCheckedChange={(e) => { onCheckedChange={(e) => {
form.setValue("isDefault", e); form.setValue("isDefault", e);
@@ -334,7 +334,7 @@ export default function Availability() {
className="ml-4 lg:ml-0" className="ml-4 lg:ml-0"
type="submit" type="submit"
form="availability-form" form="availability-form"
loading={updateMutation.isLoading}> loading={updateMutation.isPending}>
{t("save")} {t("save")}
</Button> </Button>
<Button <Button
+13 -11
View File
@@ -15,7 +15,7 @@ import { trpc } from "@calcom/trpc/react";
import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui"; import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui";
import { Clock } from "@calcom/ui/components/icon"; import { Clock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/availability/SkeletonLoader"; import SkeletonLoader from "@components/availability/SkeletonLoader";
@@ -136,8 +136,17 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
); );
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any function AvailabilityListWithQuery() {
const WithQuery = withQuery(trpc.viewer.availability.list as any); const query = trpc.viewer.availability.list.useQuery();
return (
<QueryCell
query={query}
success={({ data }) => <AvailabilityList {...data} />}
customLoader={<SkeletonLoader />}
/>
);
}
export default function AvailabilityPage() { export default function AvailabilityPage() {
const { t } = useLocale(); const { t } = useLocale();
@@ -179,14 +188,7 @@ export default function AvailabilityPage() {
<NewScheduleButton /> <NewScheduleButton />
</div> </div>
}> }>
{searchParams?.get("type") === "team" ? ( {searchParams?.get("type") === "team" ? <AvailabilitySliderTable /> : <AvailabilityListWithQuery />}
<AvailabilitySliderTable />
) : (
<WithQuery
success={({ data }) => <AvailabilityList {...data} />}
customLoader={<SkeletonLoader />}
/>
)}
</ShellMain> </ShellMain>
</div> </div>
); );
+1 -1
View File
@@ -164,7 +164,7 @@ export default function Bookings() {
{query.status === "error" && ( {query.status === "error" && (
<Alert severity="error" title={t("something_went_wrong")} message={query.error.message} /> <Alert severity="error" title={t("something_went_wrong")} message={query.error.message} />
)} )}
{(query.status === "loading" || query.isPaused) && <SkeletonLoader />} {(query.status === "pending" || query.isPaused) && <SkeletonLoader />}
{query.status === "success" && !isEmpty && ( {query.status === "success" && !isEmpty && (
<> <>
{!!bookingsToday.length && status === "upcoming" && ( {!!bookingsToday.length && status === "upcoming" && (
+1 -1
View File
@@ -68,7 +68,7 @@ function ConnectAndJoin() {
</div> </div>
) : ( ) : (
<Button <Button
loading={mutation.isLoading} loading={mutation.isPending}
tooltip={isUserPartOfOrg ? t("join_meeting") : t("not_part_of_org")} tooltip={isUserPartOfOrg ? t("join_meeting") : t("not_part_of_org")}
disabled={!isUserPartOfOrg} disabled={!isUserPartOfOrg}
onClick={() => { onClick={() => {
+25 -6
View File
@@ -80,6 +80,7 @@ const EventWebhooksTab = dynamic(() =>
const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog")); const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog"));
export type FormValues = { export type FormValues = {
id: number;
title: string; title: string;
eventTitle: string; eventTitle: string;
eventName: string; eventName: string;
@@ -119,6 +120,7 @@ export type FormValues = {
seatsShowAttendees: boolean | null; seatsShowAttendees: boolean | null;
seatsShowAvailabilityCount: boolean | null; seatsShowAvailabilityCount: boolean | null;
seatsPerTimeSlotEnabled: boolean; seatsPerTimeSlotEnabled: boolean;
scheduleName: string;
minimumBookingNotice: number; minimumBookingNotice: number;
minimumBookingNoticeInDurationType: number; minimumBookingNoticeInDurationType: number;
beforeBufferTime: number; beforeBufferTime: number;
@@ -139,6 +141,7 @@ export type FormValues = {
availability?: AvailabilityOption; availability?: AvailabilityOption;
bookerLayouts: BookerLayoutSettings; bookerLayouts: BookerLayoutSettings;
multipleDurationEnabled: boolean; multipleDurationEnabled: boolean;
users: EventTypeSetup["users"];
}; };
export type CustomInputParsed = typeof customInputSchema._output; export type CustomInputParsed = typeof customInputSchema._output;
@@ -180,7 +183,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
const { eventType, locationOptions, team, teamMembers, currentUserMembership, destinationCalendar } = props; const { eventType, locationOptions, team, teamMembers, currentUserMembership, destinationCalendar } = props;
const [animationParentRef] = useAutoAnimate<HTMLDivElement>(); const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
const updateMutation = trpc.viewer.eventTypes.update.useMutation({ const updateMutation = trpc.viewer.eventTypes.update.useMutation({
onSuccess: async () => { onSuccess: async () => {
formMethods.setValue( formMethods.setValue(
@@ -248,6 +250,18 @@ const EventTypePage = (props: EventTypeSetupProps) => {
const defaultValues: any = useMemo(() => { const defaultValues: any = useMemo(() => {
return { return {
title: eventType.title, title: eventType.title,
id: eventType.id,
slug: eventType.slug,
afterEventBuffer: eventType.afterEventBuffer,
beforeEventBuffer: eventType.beforeEventBuffer,
eventName: eventType.eventName || "",
scheduleName: eventType.scheduleName,
periodDays: eventType.periodDays || 30,
requiresBookerEmailVerification: eventType.requiresBookerEmailVerification,
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
seatsShowAttendees: eventType.seatsShowAttendees,
seatsShowAvailabilityCount: eventType.seatsShowAvailabilityCount,
lockTimeZoneToggleOnBookingPage: eventType.lockTimeZoneToggleOnBookingPage,
locations: eventType.locations || [], locations: eventType.locations || [],
recurringEvent: eventType.recurringEvent || null, recurringEvent: eventType.recurringEvent || null,
isInstantEvent: eventType.isInstantEvent, isInstantEvent: eventType.isInstantEvent,
@@ -268,9 +282,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
periodType: eventType.periodType, periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0", periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType, schedulingType: eventType.schedulingType,
requiresConfirmation: eventType.requiresConfirmation,
slotInterval: eventType.slotInterval,
minimumBookingNotice: eventType.minimumBookingNotice, minimumBookingNotice: eventType.minimumBookingNotice,
metadata, metadata,
hosts: eventType.hosts, hosts: eventType.hosts,
successRedirectUrl: eventType.successRedirectUrl || "",
users: eventType.users,
children: eventType.children.map((ch) => ({ children: eventType.children.map((ch) => ({
...ch, ...ch,
created: true, created: true,
@@ -411,7 +429,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
), ),
availability: <EventAvailabilityTab eventType={eventType} isTeamEvent={!!team} />, availability: <EventAvailabilityTab eventType={eventType} isTeamEvent={!!team} />,
team: <EventTeamTab teamMembers={teamMembers} team={team} eventType={eventType} />, team: <EventTeamTab teamMembers={teamMembers} team={team} eventType={eventType} />,
limits: <EventLimitsTab eventType={eventType} />, limits: <EventLimitsTab />,
advanced: <EventAdvancedTab eventType={eventType} team={team} />, advanced: <EventAdvancedTab eventType={eventType} team={team} />,
instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />, instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />,
recurring: <EventRecurringTab eventType={eventType} />, recurring: <EventRecurringTab eventType={eventType} />,
@@ -490,7 +508,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, ...rest } = input; const { availability, users, scheduleName, ...rest } = input;
updateMutation.mutate({ updateMutation.mutate({
...rest, ...rest,
length, length,
@@ -550,9 +568,10 @@ const EventTypePage = (props: EventTypeSetupProps) => {
installedAppsNumber={eventTypeApps?.items.length || 0} installedAppsNumber={eventTypeApps?.items.length || 0}
enabledWorkflowsNumber={eventType.workflows.length} enabledWorkflowsNumber={eventType.workflows.length}
eventType={eventType} eventType={eventType}
activeWebhooksNumber={eventType.webhooks.filter((webhook) => webhook.active).length}
team={team} team={team}
availability={availability} availability={availability}
isUpdateMutationLoading={updateMutation.isLoading} isUpdateMutationLoading={updateMutation.isPending}
formMethods={formMethods} formMethods={formMethods}
// disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"} // disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
disableBorder={true} disableBorder={true}
@@ -618,7 +637,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
throw new Error(t("event_setup_multiple_payment_apps_error")); throw new Error(t("event_setup_multiple_payment_apps_error"));
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, ...rest } = input; const { availability, users, scheduleName, ...rest } = input;
updateMutation.mutate({ updateMutation.mutate({
...rest, ...rest,
length, length,
@@ -647,7 +666,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
{slugExistsChildrenDialogOpen.length ? ( {slugExistsChildrenDialogOpen.length ? (
<ManagedEventTypeDialog <ManagedEventTypeDialog
slugExistsChildrenDialogOpen={slugExistsChildrenDialogOpen} slugExistsChildrenDialogOpen={slugExistsChildrenDialogOpen}
isLoading={formMethods.formState.isSubmitting} isPending={formMethods.formState.isSubmitting}
onOpenChange={() => { onOpenChange={() => {
setSlugExistsChildrenDialogOpen([]); setSlugExistsChildrenDialogOpen([]);
}} }}
+4 -4
View File
@@ -54,6 +54,7 @@ import {
Switch, Switch,
Tooltip, Tooltip,
ArrowButton, ArrowButton,
UserAvatarGroup,
} from "@calcom/ui"; } from "@calcom/ui";
import { import {
Clipboard, Clipboard,
@@ -74,7 +75,6 @@ import useMeQuery from "@lib/hooks/useMeQuery";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/eventtype/SkeletonLoader"; import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"]; type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
type EventTypeGroupProfile = EventTypeGroups[number]["profile"]; type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
@@ -686,7 +686,7 @@ export const EventTypeList = ({
title={t(`delete${isManagedEventPrefix()}_event_type`)} title={t(`delete${isManagedEventPrefix()}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)} confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)} loadingText={t(`confirm_delete_event_type`)}
isLoading={deleteMutation.isLoading} isPending={deleteMutation.isPending}
onConfirm={(e) => { onConfirm={(e) => {
e.preventDefault(); e.preventDefault();
deleteEventTypeHandler(deleteDialogTypeId); deleteEventTypeHandler(deleteDialogTypeId);
@@ -856,7 +856,7 @@ const Main = ({
const searchParams = useCompatSearchParams(); const searchParams = useCompatSearchParams();
const orgBranding = useOrgBranding(); const orgBranding = useOrgBranding();
if (!data || status === "loading") { if (!data || status === "pending") {
return <SkeletonLoader />; return <SkeletonLoader />;
} }
@@ -934,7 +934,7 @@ const EventTypesPage = () => {
// TODO: Maybe useSuspenseQuery to focus on success case only? Remember that it would crash the page when there is an error in query. Also, it won't support skeleton // TODO: Maybe useSuspenseQuery to focus on success case only? Remember that it would crash the page when there is an error in query. Also, it won't support skeleton
const { data, status, error } = trpc.viewer.eventTypes.getByViewer.useQuery(filters && { filters }, { const { data, status, error } = trpc.viewer.eventTypes.getByViewer.useQuery(filters && { filters }, {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
cacheTime: 1 * 60 * 60 * 1000, gcTime: 1 * 60 * 60 * 1000,
staleTime: 1 * 60 * 60 * 1000, staleTime: 1 * 60 * 60 * 1000,
}); });
@@ -36,7 +36,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
const ApiKeysView = () => { const ApiKeysView = () => {
const { t } = useLocale(); const { t } = useLocale();
const { data, isLoading } = trpc.viewer.apiKeys.list.useQuery(); const { data, isPending } = trpc.viewer.apiKeys.list.useQuery();
const [apiKeyModal, setApiKeyModal] = useState(false); const [apiKeyModal, setApiKeyModal] = useState(false);
const [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires?: boolean }) | undefined>( const [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires?: boolean }) | undefined>(
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
); );
}; };
if (isLoading || !data) { if (isPending || !data) {
return ( return (
<SkeletonLoader <SkeletonLoader
title={t("api_keys")} title={t("api_keys")}
@@ -317,7 +317,7 @@ const AppearanceView = ({
<SettingsToggle <SettingsToggle
toggleSwitchAtTheEnd={true} toggleSwitchAtTheEnd={true}
title={t("disable_cal_branding", { appName: APP_NAME })} title={t("disable_cal_branding", { appName: APP_NAME })}
disabled={!hasPaidPlan || mutation?.isLoading} disabled={!hasPaidPlan || mutation?.isPending}
description={t("removes_cal_branding", { appName: APP_NAME })} description={t("removes_cal_branding", { appName: APP_NAME })}
checked={hasPaidPlan ? hideBrandingValue : false} checked={hasPaidPlan ? hideBrandingValue : false}
Badge={<UpgradeTeamsBadge />} Badge={<UpgradeTeamsBadge />}
@@ -332,12 +332,12 @@ const AppearanceView = ({
}; };
const AppearanceViewWrapper = () => { const AppearanceViewWrapper = () => {
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan(); const { isPending: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
const { t } = useLocale(); const { t } = useLocale();
if (isLoading || isTeamPlanStatusLoading || !user) if (isPending || isTeamPlanStatusLoading || !user)
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />; return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
return <AppearanceView user={user} hasPaidPlan={hasPaidPlan} />; return <AppearanceView user={user} hasPaidPlan={hasPaidPlan} />;
@@ -51,8 +51,8 @@ interface GeneralViewProps {
const GeneralQueryView = () => { const GeneralQueryView = () => {
const { t } = useLocale(); const { t } = useLocale();
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
if (isLoading) return <SkeletonLoader title={t("general")} description={t("general_description")} />; if (isPending) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
if (!user) { if (!user) {
throw new Error(t("something_went_wrong")); throw new Error(t("something_went_wrong"));
} }
@@ -233,7 +233,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
toggleSwitchAtTheEnd={true} toggleSwitchAtTheEnd={true}
title={t("dynamic_booking")} title={t("dynamic_booking")}
description={t("allow_dynamic_booking")} description={t("allow_dynamic_booking")}
disabled={mutation.isLoading} disabled={mutation.isPending}
checked={isAllowDynamicBookingChecked} checked={isAllowDynamicBookingChecked}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setIsAllowDynamicBookingChecked(checked); setIsAllowDynamicBookingChecked(checked);
@@ -246,7 +246,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
toggleSwitchAtTheEnd={true} toggleSwitchAtTheEnd={true}
title={t("seo_indexing")} title={t("seo_indexing")}
description={t("allow_seo_indexing")} description={t("allow_seo_indexing")}
disabled={mutation.isLoading} disabled={mutation.isPending}
checked={isAllowSEOIndexingChecked} checked={isAllowSEOIndexingChecked}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setIsAllowSEOIndexingChecked(checked); setIsAllowSEOIndexingChecked(checked);
@@ -259,7 +259,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
toggleSwitchAtTheEnd={true} toggleSwitchAtTheEnd={true}
title={t("monthly_digest_email")} title={t("monthly_digest_email")}
description={t("monthly_digest_email_for_teams")} description={t("monthly_digest_email_for_teams")}
disabled={mutation.isLoading} disabled={mutation.isPending}
checked={isReceiveMonthlyDigestEmailChecked} checked={isReceiveMonthlyDigestEmailChecked}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setIsReceiveMonthlyDigestEmailChecked(checked); setIsReceiveMonthlyDigestEmailChecked(checked);
@@ -155,7 +155,7 @@ const OutOfOfficeSection = () => {
<Button <Button
color="primary" color="primary"
type="submit" type="submit"
disabled={createOutOfOfficeEntry.isLoading} disabled={createOutOfOfficeEntry.isPending}
data-testid="create-entry-ooo-redirect"> data-testid="create-entry-ooo-redirect">
{t("create_entry")} {t("create_entry")}
</Button> </Button>
@@ -168,7 +168,7 @@ const OutOfOfficeSection = () => {
const OutOfOfficeEntriesList = () => { const OutOfOfficeEntriesList = () => {
const { t } = useLocale(); const { t } = useLocale();
const utils = trpc.useContext(); const utils = trpc.useContext();
const { data, isLoading } = trpc.viewer.outOfOfficeEntriesList.useQuery(); const { data, isPending } = trpc.viewer.outOfOfficeEntriesList.useQuery();
const deleteOutOfOfficeEntryMutation = trpc.viewer.outOfOfficeEntryDelete.useMutation({ const deleteOutOfOfficeEntryMutation = trpc.viewer.outOfOfficeEntryDelete.useMutation({
onSuccess: () => { onSuccess: () => {
showToast(t("success_deleted_entry_out_of_office"), "success"); showToast(t("success_deleted_entry_out_of_office"), "success");
@@ -207,7 +207,7 @@ const OutOfOfficeEntriesList = () => {
type="button" type="button"
color="minimal" color="minimal"
variant="icon" variant="icon"
disabled={deleteOutOfOfficeEntryMutation.isLoading} disabled={deleteOutOfOfficeEntryMutation.isPending}
StartIcon={Trash2} StartIcon={Trash2}
onClick={() => { onClick={() => {
deleteOutOfOfficeEntryMutation.mutate({ outOfOfficeUid: item.uuid }); deleteOutOfOfficeEntryMutation.mutate({ outOfOfficeUid: item.uuid });
@@ -216,7 +216,7 @@ const OutOfOfficeEntriesList = () => {
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
{isLoading && ( {isPending && (
<TableRow> <TableRow>
{new Array(6).fill(0).map((_, index) => ( {new Array(6).fill(0).map((_, index) => (
<TableCell key={index}> <TableCell key={index}>
@@ -226,7 +226,7 @@ const OutOfOfficeEntriesList = () => {
</TableRow> </TableRow>
)} )}
{!isLoading && (data === undefined || data.length === 0) && ( {!isPending && (data === undefined || data.length === 0) && (
<TableRow> <TableRow>
<TableCell colSpan={6} className="text-center"> <TableCell colSpan={6} className="text-center">
<p className="text-subtle text-sm">{t("no_redirects_found")}</p> <p className="text-subtle text-sm">{t("no_redirects_found")}</p>
@@ -84,10 +84,10 @@ const ProfileView = () => {
const { t } = useLocale(); const { t } = useLocale();
const utils = trpc.useContext(); const utils = trpc.useContext();
const { update } = useSession(); const { update } = useSession();
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
const { data: avatarData } = trpc.viewer.avatar.useQuery(undefined, { const { data: avatarData } = trpc.viewer.avatar.useQuery(undefined, {
enabled: !isLoading && !user?.avatarUrl, enabled: !isPending && !user?.avatarUrl,
}); });
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({ const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
@@ -222,7 +222,7 @@ const ProfileView = () => {
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"), [ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
}; };
if (isLoading || !user) { if (isPending || !user) {
return ( return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} /> <SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
); );
@@ -246,7 +246,7 @@ const ProfileView = () => {
<ProfileForm <ProfileForm
key={JSON.stringify(defaultValues)} key={JSON.stringify(defaultValues)}
defaultValues={defaultValues} defaultValues={defaultValues}
isLoading={updateProfileMutation.isLoading} isPending={updateProfileMutation.isPending}
isFallbackImg={!user.avatarUrl && !avatarData?.avatar} isFallbackImg={!user.avatarUrl && !avatarData?.avatar}
user={user} user={user}
userOrganization={user.organization} userOrganization={user.organization}
@@ -354,7 +354,7 @@ const ProfileView = () => {
<DialogFooter showDivider> <DialogFooter showDivider>
<Button <Button
color="primary" color="primary"
loading={confirmPasswordMutation.isLoading} loading={confirmPasswordMutation.isPending}
onClick={(e) => onConfirmPassword(e)}> onClick={(e) => onConfirmPassword(e)}>
{t("confirm")} {t("confirm")}
</Button> </Button>
@@ -375,7 +375,7 @@ const ProfileView = () => {
<DialogFooter> <DialogFooter>
<Button <Button
color="primary" color="primary"
loading={updateProfileMutation.isLoading} loading={updateProfileMutation.isPending}
onClick={(e) => onConfirmAuthEmailChange(e)}> onClick={(e) => onConfirmAuthEmailChange(e)}>
{t("confirm")} {t("confirm")}
</Button> </Button>
@@ -391,7 +391,7 @@ const ProfileForm = ({
defaultValues, defaultValues,
onSubmit, onSubmit,
extraField, extraField,
isLoading = false, isPending = false,
isFallbackImg, isFallbackImg,
user, user,
userOrganization, userOrganization,
@@ -399,7 +399,7 @@ const ProfileForm = ({
defaultValues: FormValues; defaultValues: FormValues;
onSubmit: (values: FormValues) => void; onSubmit: (values: FormValues) => void;
extraField?: React.ReactNode; extraField?: React.ReactNode;
isLoading: boolean; isPending: boolean;
isFallbackImg: boolean; isFallbackImg: boolean;
user: RouterOutputs["viewer"]["me"]; user: RouterOutputs["viewer"]["me"];
userOrganization: RouterOutputs["viewer"]["me"]["organization"]; userOrganization: RouterOutputs["viewer"]["me"]["organization"];
@@ -509,7 +509,7 @@ const ProfileForm = ({
</div> </div>
</div> </div>
<SectionBottomActions align="end"> <SectionBottomActions align="end">
<Button loading={isLoading} disabled={isDisabled} color="primary" type="submit"> <Button loading={isPending} disabled={isDisabled} color="primary" type="submit">
{t("update")} {t("update")}
</Button> </Button>
</SectionBottomActions> </SectionBottomActions>
@@ -69,7 +69,7 @@ const ProfileImpersonationView = ({ user }: { user: RouterOutputs["viewer"]["me"
mutation.mutate({ disableImpersonation: !checked }); mutation.mutate({ disableImpersonation: !checked });
}} }}
switchContainerClassName="rounded-t-none border-t-0" switchContainerClassName="rounded-t-none border-t-0"
disabled={mutation.isLoading} disabled={mutation.isPending}
/> />
</div> </div>
</> </>
@@ -77,10 +77,10 @@ const ProfileImpersonationView = ({ user }: { user: RouterOutputs["viewer"]["me"
}; };
const ProfileImpersonationViewWrapper = () => { const ProfileImpersonationViewWrapper = () => {
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
const { t } = useLocale(); const { t } = useLocale();
if (isLoading || !user) if (isPending || !user)
return <SkeletonLoader title={t("impersonation")} description={t("impersonation_description")} />; return <SkeletonLoader title={t("impersonation")} description={t("impersonation_description")} />;
return <ProfileImpersonationView user={user} />; return <ProfileImpersonationView user={user} />;
@@ -215,9 +215,9 @@ const PasswordView = ({ user }: PasswordViewProps) => {
<Button <Button
color="primary" color="primary"
type="submit" type="submit"
loading={passwordMutation.isLoading} loading={passwordMutation.isPending}
onClick={() => formMethods.clearErrors("apiError")} onClick={() => formMethods.clearErrors("apiError")}
disabled={isDisabled || passwordMutation.isLoading || sessionMutation.isLoading}> disabled={isDisabled || passwordMutation.isPending || sessionMutation.isPending}>
{t("update")} {t("update")}
</Button> </Button>
</SectionBottomActions> </SectionBottomActions>
@@ -268,7 +268,7 @@ const PasswordView = ({ user }: PasswordViewProps) => {
<SectionBottomActions align="end"> <SectionBottomActions align="end">
<Button <Button
color="primary" color="primary"
loading={sessionMutation.isLoading} loading={sessionMutation.isPending}
onClick={() => { onClick={() => {
sessionMutation.mutate({ sessionMutation.mutate({
metadata: { ...metadata, sessionTimeout }, metadata: { ...metadata, sessionTimeout },
@@ -277,8 +277,8 @@ const PasswordView = ({ user }: PasswordViewProps) => {
}} }}
disabled={ disabled={
initialSessionTimeout === sessionTimeout || initialSessionTimeout === sessionTimeout ||
passwordMutation.isLoading || passwordMutation.isPending ||
sessionMutation.isLoading sessionMutation.isPending
}> }>
{t("update")} {t("update")}
</Button> </Button>
@@ -293,9 +293,9 @@ const PasswordView = ({ user }: PasswordViewProps) => {
}; };
const PasswordViewWrapper = () => { const PasswordViewWrapper = () => {
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
const { t } = useLocale(); const { t } = useLocale();
if (isLoading || !user) if (isPending || !user)
return <SkeletonLoader title={t("password")} description={t("password_description")} />; return <SkeletonLoader title={t("password")} description={t("password_description")} />;
return <PasswordView user={user} />; return <PasswordView user={user} />;
@@ -37,12 +37,12 @@ const TwoFactorAuthView = () => {
const utils = trpc.useContext(); const utils = trpc.useContext();
const { t } = useLocale(); const { t } = useLocale();
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isPending } = trpc.viewer.me.useQuery();
const [enableModalOpen, setEnableModalOpen] = useState<boolean>(false); const [enableModalOpen, setEnableModalOpen] = useState<boolean>(false);
const [disableModalOpen, setDisableModalOpen] = useState<boolean>(false); const [disableModalOpen, setDisableModalOpen] = useState<boolean>(false);
if (isLoading) if (isPending)
return <SkeletonLoader title={t("2fa")} description={t("set_up_two_factor_authentication")} />; return <SkeletonLoader title={t("2fa")} description={t("set_up_two_factor_authentication")} />;
const isCalProvider = user?.identityProvider === "CAL"; const isCalProvider = user?.identityProvider === "CAL";
+1 -2
View File
@@ -19,7 +19,7 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme"; import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { Avatar, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui"; import { Avatar, Button, HeadSeo, UnpublishedEntity, UserAvatarGroup } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon"; import { ArrowRight } from "@calcom/ui/components/icon";
import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import { useToggleQuery } from "@lib/hooks/useToggleQuery";
@@ -28,7 +28,6 @@ import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";
import Team from "@components/team/screens/Team"; import Team from "@components/team/screens/Team";
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
export { getServerSideProps }; export { getServerSideProps };
+2 -1
View File
@@ -13,7 +13,7 @@ import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { ChevronRight } from "@calcom/ui/components/icon"; import { ChevronRight } from "@calcom/ui/components/icon";
import type { getServerSideProps } from "@lib/video/[uid]/getServerSideProps"; import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";
@@ -247,4 +247,5 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
); );
} }
export { getServerSideProps };
JoinCall.PageWrapper = PageWrapper; JoinCall.PageWrapper = PageWrapper;
@@ -0,0 +1,107 @@
/* eslint-disable playwright/no-conditional-in-test */
import { loginUser } from "../fixtures/regularBookings";
import { test } from "../lib/fixtures";
const resolutions = [
{
width: 1920,
height: 1080,
},
{
width: 1280,
height: 720,
},
{
width: 640,
height: 480,
},
];
test.describe("Booking With All Questions", () => {
test.beforeEach(async ({ page, users, bookingPage }) => {
await loginUser(users);
await page.goto("/event-types");
await bookingPage.goToEventType("30 min");
await bookingPage.goToTab("event_advanced_tab_title");
const allQuestions = [
"phone",
"address",
"checkbox",
"boolean",
"textarea",
"multiemail",
"multiselect",
"number",
"radio",
"select",
"text",
];
for (const question of allQuestions) {
if (
question !== "number" &&
question !== "select" &&
question !== "checkbox" &&
question !== "boolean" &&
question !== "multiselect" &&
question !== "radio"
) {
await bookingPage.addQuestion(
question,
`${question}-test`,
`${question} test`,
true,
`${question} test`
);
} else {
await bookingPage.addQuestion(question, `${question}-test`, `${question} test`, true);
}
await bookingPage.checkField(question);
}
});
for (const resolution of resolutions) {
test(`Booking with ${resolution.width}x${resolution.height} resolution and all questions`, async ({
bookingPage,
}) => {
await bookingPage.setResolution(resolution.width, resolution.height);
await bookingPage.updateEventType();
});
}
});
test.describe("Booking With no questions", () => {
test.beforeEach(async ({ page, users, bookingPage }) => {
await loginUser(users);
await page.goto("/event-types");
await bookingPage.goToEventType("30 min");
await bookingPage.goToTab("event_advanced_tab_title");
});
for (const resolution of resolutions) {
test(`Booking with ${resolution.width}x${resolution.height} resolution and no questions`, async ({
bookingPage,
}) => {
await bookingPage.setResolution(resolution.width, resolution.height);
await bookingPage.updateEventType();
});
}
});
test.describe("Booking page with no questions", () => {
test.beforeEach(async ({ page, users, bookingPage }) => {
await loginUser(users);
await page.goto("/event-types");
await bookingPage.goToEventType("30 min");
await bookingPage.goToTab("event_advanced_tab_title");
});
for (const resolution of resolutions) {
test(`Booking page with ${resolution.width}x${resolution.height} resolution`, async ({ bookingPage }) => {
const eventTypePage = await bookingPage.previewEventType();
await eventTypePage.setViewportSize({ width: resolution.width, height: resolution.height });
await bookingPage.selectTimeSlot(eventTypePage);
await bookingPage.confirmBooking(eventTypePage);
});
}
});
@@ -207,6 +207,9 @@ export function createBookingPageFixture(page: Page) {
goToTab: async (tabName: string) => { goToTab: async (tabName: string) => {
await page.getByTestId(`vertical-tab-${tabName}`).click(); await page.getByTestId(`vertical-tab-${tabName}`).click();
}, },
setResolution: async (width: number, height: number) => {
await page.setViewportSize({ width: width, height: height });
},
addQuestion: async ( addQuestion: async (
questionType: string, questionType: string,
identifier: string, identifier: string,
@@ -325,6 +328,14 @@ export function createBookingPageFixture(page: Page) {
await expect(eventTypePage.getByTestId("cancelled-headline")).toBeVisible(); await expect(eventTypePage.getByTestId("cancelled-headline")).toBeVisible();
}, },
confirmBooking: async (eventTypePage: Page) => {
const confirmButton = "confirm-book-button";
await eventTypePage.getByTestId(confirmButton).click();
const scheduleSuccessfullyPage = eventTypePage.getByText(scheduleSuccessfullyText);
await scheduleSuccessfullyPage.waitFor({ state: "visible" });
await expect(scheduleSuccessfullyPage).toBeVisible();
},
fillAndConfirmBooking: async ({ fillAndConfirmBooking: async ({
eventTypePage, eventTypePage,
placeholderText, placeholderText,
@@ -1066,6 +1066,8 @@
"user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.", "user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.",
"team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.", "team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.",
"cal_signup_description":"Free for individuals. Team plans for collaborative features.", "cal_signup_description":"Free for individuals. Team plans for collaborative features.",
"make_org_private": "Make organization private",
"make_org_private_description": "Your organization members won't be able to see other organization members when this is turned on.",
"make_team_private": "Make team private", "make_team_private": "Make team private",
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.", "make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
"you_cannot_see_team_members": "You cannot see all the team members of a private team.", "you_cannot_see_team_members": "You cannot see all the team members of a private team.",
@@ -302,6 +302,7 @@
"welcome_to_calcom": "ברוך הבא אל {{appName}}", "welcome_to_calcom": "ברוך הבא אל {{appName}}",
"welcome_instructions": "אמור לנו באיזה שם ברצונך שנקרא לך ובאיזה אזור זמן אתה נמצא. ניתן יהיה לערוך את הפרטים האלה מאוחר יותר.", "welcome_instructions": "אמור לנו באיזה שם ברצונך שנקרא לך ובאיזה אזור זמן אתה נמצא. ניתן יהיה לערוך את הפרטים האלה מאוחר יותר.",
"connect_caldav": "חיבור לשרת CalDav (בטא)", "connect_caldav": "חיבור לשרת CalDav (בטא)",
"connect_ics_feed": "חיבור לערוץ ICS",
"connect": "להתחבר", "connect": "להתחבר",
"try_for_free": "לנסות בחינם", "try_for_free": "לנסות בחינם",
"create_booking_link_with_calcom": "צור קישור תזמון משלך עם {{appName}}", "create_booking_link_with_calcom": "צור קישור תזמון משלך עם {{appName}}",
@@ -555,6 +556,7 @@
"enter_number_between_range": "נא להזין מספר בין 1 ל-{{maxOccurences}}", "enter_number_between_range": "נא להזין מספר בין 1 ל-{{maxOccurences}}",
"email_address": "כתובת דוא\"ל", "email_address": "כתובת דוא\"ל",
"enter_valid_email": "יש להזין כתובת דוא״ל תקינה", "enter_valid_email": "יש להזין כתובת דוא״ל תקינה",
"please_schedule_future_call": " נא לתזמן שיחה עתידית אם איננו זמינים תוך {{seconds}} שניות",
"location": "מיקום", "location": "מיקום",
"address": "כתובת", "address": "כתובת",
"enter_address": "יש להזין כתובת", "enter_address": "יש להזין כתובת",
@@ -614,6 +616,7 @@
"new_member": "חבר/ה חדש/ה", "new_member": "חבר/ה חדש/ה",
"invite": "להזמין", "invite": "להזמין",
"add_team_members": "הוספת חברי צוות", "add_team_members": "הוספת חברי צוות",
"add_org_members": "הוספת חברים",
"add_team_members_description": "להזמין עוד אנשים להצטרף לצוות", "add_team_members_description": "להזמין עוד אנשים להצטרף לצוות",
"add_team_member": "הוספת חבר צוות", "add_team_member": "הוספת חבר צוות",
"invite_new_member": "להזמין חבר/ת צוות חדש/ה", "invite_new_member": "להזמין חבר/ת צוות חדש/ה",
@@ -1063,6 +1066,8 @@
"user_impersonation_description": "מצב זה מאפשר לצוות התמיכה שלנו להתחבר באופן זמני לחשבונך כדי שנוכל לפתור במהירות את הבעיות שתדווח/י לנו עליהם.", "user_impersonation_description": "מצב זה מאפשר לצוות התמיכה שלנו להתחבר באופן זמני לחשבונך כדי שנוכל לפתור במהירות את הבעיות שתדווח/י לנו עליהם.",
"team_impersonation_description": "מאפשר לבעלים של הצוות/מנהלים להיכנס זמנית בשמך.", "team_impersonation_description": "מאפשר לבעלים של הצוות/מנהלים להיכנס זמנית בשמך.",
"cal_signup_description": "בחינם למשתמשים פרטיים. התוכנית לצוותים מאפשרת יכולות לשיתופי פעולה.", "cal_signup_description": "בחינם למשתמשים פרטיים. התוכנית לצוותים מאפשרת יכולות לשיתופי פעולה.",
"make_org_private": "הפיכת הארגון לפרטי",
"make_org_private_description": "כשההגדרה הזו מופעלת, חברי הארגון שלך לא יוכלו לראות חברי ארגונים אחרים.",
"make_team_private": "להגדיר את הצוות כפרטי", "make_team_private": "להגדיר את הצוות כפרטי",
"make_team_private_description": "כשההגדרה הזו מופעלת, חברי הצוות שלך לא יוכלו לראות חברי צוות אחרים.", "make_team_private_description": "כשההגדרה הזו מופעלת, חברי הצוות שלך לא יוכלו לראות חברי צוות אחרים.",
"you_cannot_see_team_members": "אין לך אפשרות לראות את כל חברי הצוות של צוות פרטי.", "you_cannot_see_team_members": "אין לך אפשרות לראות את כל חברי הצוות של צוות פרטי.",
@@ -1978,6 +1983,7 @@
"connect_google_workspace": "חיבור Google Workspace", "connect_google_workspace": "חיבור Google Workspace",
"google_workspace_admin_tooltip": "עליך/ייך להיות אדמין/ית ב-Workspace כדי להשתמש בתכונה הזו", "google_workspace_admin_tooltip": "עליך/ייך להיות אדמין/ית ב-Workspace כדי להשתמש בתכונה הזו",
"first_event_type_webhook_description": "צור/צרי את ה-Webhook הראשון שלך עבור סוג האירוע הזה", "first_event_type_webhook_description": "צור/צרי את ה-Webhook הראשון שלך עבור סוג האירוע הזה",
"create_instant_meeting_webhook_description": "אפשר ליצור את ההתליה הראשונה שלך עם ‚נוצרה פגישה מיידית’ כהזנקה לסוג אירוע זה",
"install_app_on": "התקנת האפליקציה ב-", "install_app_on": "התקנת האפליקציה ב-",
"create_for": "צור/צרי עבור", "create_for": "צור/צרי עבור",
"currency": "מטבע", "currency": "מטבע",
@@ -2193,6 +2199,7 @@
"uprade_to_create_instant_bookings": "ניתן לשדג לרישיון התאגידי ולאפשר למשתמשים להצטרף לשיחה מיידית שמשתתפים יכולים לקפוץ ישירות אליה. זה מיועד רק לסוגי אירועים של צוותים", "uprade_to_create_instant_bookings": "ניתן לשדג לרישיון התאגידי ולאפשר למשתמשים להצטרף לשיחה מיידית שמשתתפים יכולים לקפוץ ישירות אליה. זה מיועד רק לסוגי אירועים של צוותים",
"dont_want_to_wait": "לא רוצה להמתין?", "dont_want_to_wait": "לא רוצה להמתין?",
"meeting_started": "הפגישה החלה", "meeting_started": "הפגישה החלה",
"pay_and_book": "נא לשלם כדי להזמין",
"booking_not_found_error": "לא ניתן למצוא הזמנה", "booking_not_found_error": "לא ניתן למצוא הזמנה",
"booking_seats_full_error": "חלונות ההזמנה מלאים", "booking_seats_full_error": "חלונות ההזמנה מלאים",
"missing_payment_credential_error": "פרטי התשלום חסרים", "missing_payment_credential_error": "פרטי התשלום חסרים",
@@ -0,0 +1,56 @@
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { ssrInit } from "@server/lib/ssr";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
const ssr = await ssrInit(context);
const userCount = await prisma.user.count();
const session = await getServerSession({ req });
if (session?.user.role && session?.user.role !== UserPermissionRole.ADMIN) {
return {
redirect: {
destination: `/404`,
permanent: false,
},
};
}
const deploymentKey = await prisma.deployment.findUnique({
where: { id: 1 },
select: { licenseKey: true },
});
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) {
await prisma.deployment.upsert({
where: { id: 1 },
update: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
create: {
licenseKey: process.env.CALCOM_LICENSE_KEY,
agreedLicenseAt: new Date(),
},
});
}
const isFreeLicense = (await getDeploymentKey(prisma)) === "";
return {
props: {
trpcState: ssr.dehydrate(),
isFreeLicense,
userCount,
},
};
}
+2 -2
View File
@@ -4,7 +4,7 @@ import superjson from "superjson";
import { CALCOM_VERSION } from "@calcom/lib/constants"; import { CALCOM_VERSION } from "@calcom/lib/constants";
import prisma, { readonlyPrisma } from "@calcom/prisma"; import prisma, { readonlyPrisma } from "@calcom/prisma";
import { createProxySSGHelpers } from "@calcom/trpc/react/ssg"; import { createServerSideHelpers } from "@calcom/trpc/react/server";
import { appRouter } from "@calcom/trpc/server/routers/_app"; import { appRouter } from "@calcom/trpc/server/routers/_app";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -26,7 +26,7 @@ export async function ssgInit<TParams extends { locale?: string }>(opts: GetStat
const _i18n = await serverSideTranslations(locale, ["common"]); const _i18n = await serverSideTranslations(locale, ["common"]);
const ssg = createProxySSGHelpers({ const ssg = createServerSideHelpers({
router: appRouter, router: appRouter,
transformer: superjson, transformer: superjson,
ctx: { ctx: {
+2 -2
View File
@@ -6,7 +6,7 @@ import { forms } from "@calcom/app-store/routing-forms/trpc/procedures/forms";
import { getLocale } from "@calcom/features/auth/lib/getLocale"; import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { map } from "@calcom/features/flags/server/procedures/map"; import { map } from "@calcom/features/flags/server/procedures/map";
import { CALCOM_VERSION } from "@calcom/lib/constants"; import { CALCOM_VERSION } from "@calcom/lib/constants";
import { createProxySSGHelpers } from "@calcom/trpc/react/ssg"; import { createServerSideHelpers } from "@calcom/trpc/react/server";
import { createContext } from "@calcom/trpc/server/createContext"; import { createContext } from "@calcom/trpc/server/createContext";
import { me } from "@calcom/trpc/server/routers/loggedInViewer/procedures/me"; import { me } from "@calcom/trpc/server/routers/loggedInViewer/procedures/me";
import { teamsAndUserProfilesQuery } from "@calcom/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery"; import { teamsAndUserProfilesQuery } from "@calcom/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery";
@@ -59,7 +59,7 @@ export async function ssrInit(context: GetServerSidePropsContext, options?: { no
const locale = await getLocale(context.req); const locale = await getLocale(context.req);
const i18n = await serverSideTranslations(locale, ["common", "vital"]); const i18n = await serverSideTranslations(locale, ["common", "vital"]);
const ssr = createProxySSGHelpers({ const ssr = createServerSideHelpers({
router: routerSlice, router: routerSlice,
transformer: superjson, transformer: superjson,
ctx: { ...ctx, locale, i18n }, ctx: { ...ctx, locale, i18n },
@@ -71,7 +71,7 @@ export default function OmniInstallAppButton({
return ( return (
<Button <Button
loading={mutation.isLoading} loading={mutation.isPending}
color="secondary" color="secondary"
className="[@media(max-width:260px)]:w-full [@media(max-width:260px)]:justify-center" className="[@media(max-width:260px)]:w-full [@media(max-width:260px)]:justify-center"
StartIcon={Plus} StartIcon={Plus}
+54 -51
View File
@@ -32,64 +32,67 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
AddAppMutationData, AddAppMutationData,
Error, Error,
{ type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean; teamId?: number } | "" { type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean; teamId?: number } | ""
>(async (variables) => { >({
let type: string | null | undefined; ...options,
let isOmniInstall; mutationFn: async (variables) => {
const teamId = variables && variables.teamId ? variables.teamId : undefined; let type: string | null | undefined;
if (variables === "") { let isOmniInstall;
type = _type; const teamId = variables && variables.teamId ? variables.teamId : undefined;
} else { if (variables === "") {
isOmniInstall = variables.isOmniInstall; type = _type;
type = variables.type; } else {
} isOmniInstall = variables.isOmniInstall;
if (type?.endsWith("_other_calendar")) { type = variables.type;
type = type.split("_other_calendar")[0]; }
} if (type?.endsWith("_other_calendar")) {
type = type.split("_other_calendar")[0];
}
if (options?.installGoogleVideo && type !== "google_calendar") if (options?.installGoogleVideo && type !== "google_calendar")
throw new Error("Could not install Google Meet"); throw new Error("Could not install Google Meet");
const state: IntegrationOAuthCallbackState = { const state: IntegrationOAuthCallbackState = {
returnTo: returnTo:
returnTo || returnTo ||
WEBAPP_URL + WEBAPP_URL +
getInstalledAppPath( getInstalledAppPath(
{ variant: variables && variables.variant, slug: variables && variables.slug }, { variant: variables && variables.variant, slug: variables && variables.slug },
location.search location.search
), ),
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }), ...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
...(teamId && { teamId }), ...(teamId && { teamId }),
}; };
const stateStr = encodeURIComponent(JSON.stringify(state)); const stateStr = encodeURIComponent(JSON.stringify(state));
const searchParams = `?state=${stateStr}${teamId ? `&teamId=${teamId}` : ""}`; const searchParams = `?state=${stateStr}${teamId ? `&teamId=${teamId}` : ""}`;
const res = await fetch(`/api/integrations/${type}/add${searchParams}`); const res = await fetch(`/api/integrations/${type}/add${searchParams}`);
if (!res.ok) { if (!res.ok) {
const errorBody = await res.json(); const errorBody = await res.json();
throw new Error(errorBody.message || "Something went wrong"); throw new Error(errorBody.message || "Something went wrong");
} }
const json = await res.json();
const externalUrl = /https?:\/\//.test(json.url) && !json.url.startsWith(window.location.origin);
if (!isOmniInstall) {
gotoUrl(json.url, json.newTab);
return { setupPending: externalUrl || json.url.endsWith("/setup") };
}
// Skip redirection only if it is an OmniInstall and redirect URL isn't of some other origin
// This allows installation of apps like Stripe to still redirect to their authentication pages.
// Check first that the URL is absolute, then check that it is of different origin from the current.
if (externalUrl) {
// TODO: For Omni installation to authenticate and come back to the page where installation was initiated, some changes need to be done in all apps' add callbacks
gotoUrl(json.url, json.newTab);
return { setupPending: externalUrl };
}
const json = await res.json();
const externalUrl = /https?:\/\//.test(json.url) && !json.url.startsWith(window.location.origin);
if (!isOmniInstall) {
gotoUrl(json.url, json.newTab);
return { setupPending: externalUrl || json.url.endsWith("/setup") }; return { setupPending: externalUrl || json.url.endsWith("/setup") };
} },
});
// Skip redirection only if it is an OmniInstall and redirect URL isn't of some other origin
// This allows installation of apps like Stripe to still redirect to their authentication pages.
// Check first that the URL is absolute, then check that it is of different origin from the current.
if (externalUrl) {
// TODO: For Omni installation to authenticate and come back to the page where installation was initiated, some changes need to be done in all apps' add callbacks
gotoUrl(json.url, json.newTab);
return { setupPending: externalUrl };
}
return { setupPending: externalUrl || json.url.endsWith("/setup") };
}, options);
return mutation; return mutation;
} }
@@ -121,7 +121,7 @@ function AlbySetupPage(props: IAlbySetupProps) {
}); });
}, [credentialId, props.clientId, props.clientSecret, saveKeysMutation]); }, [credentialId, props.clientId, props.clientSecret, saveKeysMutation]);
if (integrations.isLoading) { if (integrations.isPending) {
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />; return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
} }
@@ -15,22 +15,26 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
const [selectedProject, setSelectedProject] = useState<undefined | { label: string; value: string }>(); const [selectedProject, setSelectedProject] = useState<undefined | { label: string; value: string }>();
const { data } = trpc.viewer.appBasecamp3.projects.useQuery(); const { data } = trpc.viewer.appBasecamp3.projects.useQuery();
const setProject = trpc.viewer.appBasecamp3.projectMutation.useMutation(); const setProject = trpc.viewer.appBasecamp3.projectMutation.useMutation();
useEffect(() => {
setSelectedProject({ useEffect(
value: data?.projects.currentProject, function refactorMeWithoutEffect() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any setSelectedProject({
label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name, value: data?.projects.currentProject,
}); // eslint-disable-next-line @typescript-eslint/no-explicit-any
setProjects( label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name,
// eslint-disable-next-line @typescript-eslint/no-explicit-any });
data?.projects?.map((project: any) => { setProjects(
return { // eslint-disable-next-line @typescript-eslint/no-explicit-any
value: project.id, data?.projects?.map((project: any) => {
label: project.name, return {
}; value: project.id,
}) label: project.name,
); };
}, [data]); })
);
},
[data]
);
return ( return (
<AppCard <AppCard
+3 -3
View File
@@ -34,7 +34,7 @@ export const InstallAppButtonWithoutPlanCheck = (
onClick: () => { onClick: () => {
mutation.mutate({ type: props.type }); mutation.mutate({ type: props.type });
}, },
loading: mutation.isLoading, loading: mutation.isPending,
})} })}
</> </>
); );
@@ -56,10 +56,10 @@ export const InstallAppButton = (
disableInstall?: boolean; disableInstall?: boolean;
} & InstallAppButtonProps } & InstallAppButtonProps
) => { ) => {
const { isLoading: isUserLoading, data: user } = trpc.viewer.me.useQuery(); const { isPending: isUserLoading, data: user } = trpc.viewer.me.useQuery();
const router = useRouter(); const router = useRouter();
const proProtectionElementRef = useRef<HTMLDivElement | null>(null); const proProtectionElementRef = useRef<HTMLDivElement | null>(null);
const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan(); const { isPending: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
useEffect(() => { useEffect(() => {
const el = proProtectionElementRef.current; const el = proProtectionElementRef.current;
+3 -7
View File
@@ -1,18 +1,14 @@
--- ---
items: items:
- 1.png - 1.jpeg
- 2.png - 2.png
- 3.png - 3.png
- 4.png - 4.png
- 5.png
--- ---
{DESCRIPTION} ### It’s time.
### Cron's most valuable features All of your commitments, now in one place. Meet the beautifully designed, fully integrated calendar for your work and life.
<!-- From "FAQs: What are Cron’s most valuable features?"
https://cronhq.notion.site/Cron-Calendar-5625be54feac4e13a75b10271b65ddb7 -->
- **Availability sharing** — mark holds on the grid, share the slots, and schedule with ease. - **Availability sharing** — mark holds on the grid, share the slots, and schedule with ease.
- **Team overlays** — quickly overlay teammates' calendars. - **Team overlays** — quickly overlay teammates' calendars.
+4 -4
View File
@@ -1,15 +1,15 @@
{ {
"/*": "Don't modify slug - If required, do it using cli edit command", "/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Cron", "name": "Notion Calendar",
"slug": "cron", "slug": "cron",
"type": "cron_other", "type": "cron_calendar",
"logo": "logo.png", "logo": "logo.png",
"url": "https://github.com/miguelnietoa", "url": "https://github.com/miguelnietoa",
"variant": "other", "variant": "calendar",
"categories": ["calendar"], "categories": ["calendar"],
"publisher": "Miguel Nieto", "publisher": "Miguel Nieto",
"email": "support@cal.com", "email": "support@cal.com",
"description": "Cron is the next-generation calendar for professionals and teams.", "description": "All of your commitments, now in one place.",
"isTemplate": false, "isTemplate": false,
"__createdUsingCli": true, "__createdUsingCli": true,
"__template": "link-as-an-app", "__template": "link-as-an-app",
+1 -1
View File
@@ -10,5 +10,5 @@
"devDependencies": { "devDependencies": {
"@calcom/types": "*" "@calcom/types": "*"
}, },
"description": "Cron is the next-generation calendar for professionals and teams." "description": "All of your commitments, now in one place."
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 KiB

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 61 KiB

@@ -11,7 +11,7 @@ export default function InstallAppButton(props: InstallAppButtonProps) {
onClick() { onClick() {
mutation.mutate(""); mutation.mutate("");
}, },
loading: mutation.isLoading, loading: mutation.isPending,
})} })}
</> </>
); );
@@ -9,7 +9,7 @@ export default function InstallAppButton(props: InstallAppButtonProps) {
onClick() { onClick() {
mutation.mutate(""); mutation.mutate("");
}, },
loading: mutation.isLoading, loading: mutation.isPending,
})} })}
</> </>
); );
@@ -23,15 +23,15 @@ export const SearchDialog = (props: ISearchDialog) => {
const [nextOffset, setNextOffset] = useState<number>(0); const [nextOffset, setNextOffset] = useState<number>(0);
const [keyword, setKeyword] = useState<string>(""); const [keyword, setKeyword] = useState<string>("");
const { isOpenDialog, setIsOpenDialog } = props; const { isOpenDialog, setIsOpenDialog } = props;
const [isLoading, setIsLoading] = useState(false); const [isPending, setIsPending] = useState(false);
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const [selectedMode, setSelectedMode] = useState<Mode>(MODE_SEARCH); const [selectedMode, setSelectedMode] = useState<Mode>(MODE_SEARCH);
const searchGiphy = async (keyword: string, offset: number) => { const searchGiphy = async (keyword: string, offset: number) => {
if (isLoading) { if (isPending) {
return; return;
} }
setIsLoading(true); setIsPending(true);
setErrorMessage(""); setErrorMessage("");
const res = await fetch("/api/integrations/giphy/search", { const res = await fetch("/api/integrations/giphy/search", {
method: "POST", method: "POST",
@@ -53,15 +53,15 @@ export const SearchDialog = (props: ISearchDialog) => {
setErrorMessage("No Result found"); setErrorMessage("No Result found");
} }
} }
setIsLoading(false); setIsPending(false);
return null; return null;
}; };
const getGiphyByUrl = async (url: string) => { const getGiphyByUrl = async (url: string) => {
if (isLoading) { if (isPending) {
return; return;
} }
setIsLoading(true); setIsPending(true);
setErrorMessage(""); setErrorMessage("");
const res = await fetch("/api/integrations/giphy/get", { const res = await fetch("/api/integrations/giphy/get", {
method: "POST", method: "POST",
@@ -81,7 +81,7 @@ export const SearchDialog = (props: ISearchDialog) => {
setErrorMessage("No Result found"); setErrorMessage("No Result found");
} }
} }
setIsLoading(false); setIsPending(false);
return null; return null;
}; };
@@ -139,14 +139,14 @@ export const SearchDialog = (props: ISearchDialog) => {
}} }}
/> />
</div> </div>
<Button type="submit" tabIndex={-1} color="secondary" loading={isLoading}> <Button type="submit" tabIndex={-1} color="secondary" loading={isPending}>
{t("search")} {t("search")}
</Button> </Button>
</form> </form>
{gifImage && ( {gifImage && (
<div className="flex flex-col items-center space-x-2 space-y-2 pt-3 rtl:space-x-reverse"> <div className="flex flex-col items-center space-x-2 space-y-2 pt-3 rtl:space-x-reverse">
<div className="bg-subtle flex w-full items-center justify-center"> <div className="bg-subtle flex w-full items-center justify-center">
{isLoading ? ( {isPending ? (
<div className="flex h-[200px] w-full items-center justify-center bg-gray-400 pb-3 pt-3"> <div className="flex h-[200px] w-full items-center justify-center bg-gray-400 pb-3 pt-3">
<svg <svg
className={classNames("mx-4 h-5 w-5 animate-spin", "text-inverted dark:text-emphasis")} className={classNames("mx-4 h-5 w-5 animate-spin", "text-inverted dark:text-emphasis")}
@@ -182,7 +182,7 @@ export const SearchDialog = (props: ISearchDialog) => {
size="sm" size="sm"
color="secondary" color="secondary"
type="button" type="button"
loading={isLoading} loading={isPending}
onClick={() => searchGiphy(keyword, nextOffset)}> onClick={() => searchGiphy(keyword, nextOffset)}>
Shuffle Shuffle
</Button> </Button>
@@ -59,7 +59,7 @@ export default function MakeSetup({ inviteLink }: InferGetServerSidePropsType<ty
setNewApiKeys({ ...newApiKeys, [teamId || ""]: apiKey }); setNewApiKeys({ ...newApiKeys, [teamId || ""]: apiKey });
} }
if (integrations.isLoading) { if (integrations.isPending) {
return <div className="bg-emphasis absolute z-50 flex h-screen w-full items-center" />; return <div className="bg-emphasis absolute z-50 flex h-screen w-full items-center" />;
} }
@@ -27,7 +27,7 @@ export default function PayPalSetup() {
}, },
}); });
if (integrations.isLoading) { if (integrations.isPending) {
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />; return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
} }
@@ -147,7 +147,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) {
</div> </div>
<DialogFooter showDivider className="mt-12"> <DialogFooter showDivider className="mt-12">
<DialogClose /> <DialogClose />
<Button loading={mutation.isLoading} data-testid="add-form" type="submit"> <Button loading={mutation.isPending} data-testid="add-form" type="submit">
{t("continue")} {t("continue")}
</Button> </Button>
</DialogFooter> </DialogFooter>
@@ -234,7 +234,7 @@ function Dialogs({
<RoutingFormEmbedDialog /> <RoutingFormEmbedDialog />
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={deleteMutation.isLoading} isPending={deleteMutation.isPending}
variety="danger" variety="danger"
title={t("delete_form")} title={t("delete_form")}
confirmBtnText={t("delete_form_action")} confirmBtnText={t("delete_form_action")}
@@ -264,12 +264,12 @@ const actionsCtx = createContext({
_delete: { _delete: {
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
onAction: (_arg: { routingForm: RoutingForm | null }) => {}, onAction: (_arg: { routingForm: RoutingForm | null }) => {},
isLoading: false, isPending: false,
}, },
toggle: { toggle: {
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
onAction: (_arg: { routingForm: RoutingForm | null; checked: boolean }) => {}, onAction: (_arg: { routingForm: RoutingForm | null; checked: boolean }) => {},
isLoading: false, isPending: false,
}, },
}); });
@@ -332,7 +332,7 @@ export function FormActionsProvider({ appUrl, children }: { appUrl: string; chil
setDeleteDialogOpen(true); setDeleteDialogOpen(true);
setDeleteDialogFormId(routingForm.id); setDeleteDialogFormId(routingForm.id);
}, },
isLoading: false, isPending: false,
}, },
toggle: { toggle: {
onAction: ({ routingForm, checked }) => { onAction: ({ routingForm, checked }) => {
@@ -344,7 +344,7 @@ export function FormActionsProvider({ appUrl, children }: { appUrl: string; chil
disabled: !checked, disabled: !checked,
}); });
}, },
isLoading: toggleMutation.isLoading, isPending: toggleMutation.isPending,
}, },
}}> }}>
{children} {children}
@@ -446,7 +446,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
}, },
_delete: { _delete: {
onClick: () => _delete.onAction({ routingForm }), onClick: () => _delete.onAction({ routingForm }),
loading: _delete.isLoading, loading: _delete.isPending,
}, },
create: { create: {
onClick: () => openModal({ action: "new", target: "" }), onClick: () => openModal({ action: "new", target: "" }),
@@ -479,7 +479,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
</div> </div>
); );
}, },
loading: toggle.isLoading, loading: toggle.isPending,
}, },
}; };
@@ -65,7 +65,7 @@ const Actions = ({
}: { }: {
form: RoutingFormWithResponseCount; form: RoutingFormWithResponseCount;
mutation: { mutation: {
isLoading: boolean; isPending: boolean;
}; };
}) => { }) => {
const { t } = useLocale(); const { t } = useLocale();
@@ -218,7 +218,7 @@ const Actions = ({
</FormActionsDropdown> </FormActionsDropdown>
</div> </div>
<VerticalDivider /> <VerticalDivider />
<Button data-testid="update-form" loading={mutation.isLoading} type="submit" color="primary"> <Button data-testid="update-form" loading={mutation.isPending} type="submit" color="primary">
{t("save")} {t("save")}
</Button> </Button>
</div> </div>
@@ -518,7 +518,7 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
} }
export default function SingleFormWrapper({ form: _form, ...props }: SingleFormComponentProps) { export default function SingleFormWrapper({ form: _form, ...props }: SingleFormComponentProps) {
const { data: form, isLoading } = trpc.viewer.appRoutingForms.formQuery.useQuery( const { data: form, isPending } = trpc.viewer.appRoutingForms.formQuery.useQuery(
{ id: _form.id }, { id: _form.id },
{ {
initialData: _form, initialData: _form,
@@ -527,7 +527,7 @@ export default function SingleFormWrapper({ form: _form, ...props }: SingleFormC
); );
const { t } = useLocale(); const { t } = useLocale();
if (isLoading) { if (isPending) {
// It shouldn't be possible because we are passing the data from SSR to it as initialData. So, no need for skeleton here // It shouldn't be possible because we are passing the data from SSR to it as initialData. So, no need for skeleton here
return null; return null;
} }
@@ -34,7 +34,7 @@ type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Co
const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: JsonLogicQuery | null }) => { const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: JsonLogicQuery | null }) => {
const { t } = useLocale(); const { t } = useLocale();
const { isLoading, status, data, isFetching, error, isFetchingNextPage, hasNextPage, fetchNextPage } = const { isPending, status, data, isFetching, error, isFetchingNextPage, hasNextPage, fetchNextPage } =
trpc.viewer.appRoutingForms.report.useInfiniteQuery( trpc.viewer.appRoutingForms.report.useInfiniteQuery(
{ {
formId: formId, formId: formId,
@@ -57,7 +57,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
const headers = useRef<string[] | null>(null); const headers = useRef<string[] | null>(null);
if (!isLoading && !data) { if (!isPending && !data) {
return <div>Error loading report {error?.message} </div>; return <div>Error loading report {error?.message} </div>;
} }
headers.current = (data?.pages && data?.pages[0]?.headers) || headers.current; headers.current = (data?.pages && data?.pages[0]?.headers) || headers.current;
@@ -81,7 +81,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
</th> </th>
))} ))}
</tr> </tr>
{!isLoading && {!isPending &&
data?.pages.map((page) => { data?.pages.map((page) => {
return page.responses?.map((responses, rowIndex) => { return page.responses?.map((responses, rowIndex) => {
const isLastRow = page.responses.length - 1 === rowIndex; const isLastRow = page.responses.length - 1 === rowIndex;
@@ -113,7 +113,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
}); });
})} })}
</table> </table>
{isLoading ? <div className="text-default p-2">{t("loading")}</div> : ""} {isPending ? <div className="text-default p-2">{t("loading")}</div> : ""}
{hasNextPage && ( {hasNextPage && (
<Button <Button
type="button" type="button"
@@ -153,7 +153,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
<div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse"> <div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse">
<Button <Button
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent" className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent"
loading={responseMutation.isLoading} loading={responseMutation.isPending}
type="submit" type="submit"
color="primary"> color="primary">
{t("submit")} {t("submit")}
+1 -1
View File
@@ -35,7 +35,7 @@ export interface InstallAppButtonProps {
renderProps: ButtonProps & { renderProps: ButtonProps & {
/** Tells that the default render component should be used */ /** Tells that the default render component should be used */
useDefaultComponent?: boolean; useDefaultComponent?: boolean;
isLoading?: boolean; isPending?: boolean;
} }
) => JSX.Element; ) => JSX.Element;
onChanged?: () => unknown; onChanged?: () => unknown;
@@ -44,7 +44,7 @@ const wipeMyCalAction = async (props: IWipeMyCalAction) => {
export const ConfirmDialog = (props: IConfirmDialogWipe) => { export const ConfirmDialog = (props: IConfirmDialogWipe) => {
const { t } = useLocale(); const { t } = useLocale();
const { isOpenDialog, setIsOpenDialog } = props; const { isOpenDialog, setIsOpenDialog } = props;
const [isLoading, setIsLoading] = useState(false); const [isPending, setIsPending] = useState(false);
const today = dayjs(); const today = dayjs();
const initialDate = today.startOf("day"); const initialDate = today.startOf("day");
const endDate = today.endOf("day"); const endDate = today.endOf("day");
@@ -52,9 +52,9 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
const utils = trpc.useContext(); const utils = trpc.useContext();
const rescheduleApi = useMutation( const rescheduleApi = useMutation({
async () => { mutationFn: async () => {
setIsLoading(true); setIsPending(true);
try { try {
const result = await wipeMyCalAction({ const result = await wipeMyCalAction({
initialDate: initialDate.toISOString(), initialDate: initialDate.toISOString(),
@@ -68,14 +68,12 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
showToast(t("unexpected_error_try_again"), "error"); showToast(t("unexpected_error_try_again"), "error");
// @TODO: notify // @TODO: notify
} }
setIsLoading(false); setIsPending(false);
}, },
{ async onSettled() {
async onSettled() { await utils.viewer.bookings.invalidate();
await utils.viewer.bookings.invalidate(); },
}, });
}
);
return ( return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}> <Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
@@ -104,7 +102,7 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
<Button <Button
color="primary" color="primary"
data-testid="send_request" data-testid="send_request"
disabled={isLoading} disabled={isPending}
onClick={async () => { onClick={async () => {
try { try {
rescheduleApi.mutate(); rescheduleApi.mutate();
@@ -13,7 +13,7 @@ interface IWipeMyCalActionButtonProps {
const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => { const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => {
const { bookingsEmpty, bookingStatus } = props; const { bookingsEmpty, bookingStatus } = props;
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const { isSuccess, isLoading, data } = trpc.viewer.integrations.useQuery({ const { isSuccess, isPending, data } = trpc.viewer.integrations.useQuery({
variant: "other", variant: "other",
onlyInstalled: undefined, onlyInstalled: undefined,
}); });
@@ -27,7 +27,7 @@ const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => {
return ( return (
<> <>
{data && isSuccess && !isLoading && credentialId && ( {data && isSuccess && !isPending && credentialId && (
<div className="mb-4"> <div className="mb-4">
<ConfirmDialog isOpenDialog={openDialog} setIsOpenDialog={setOpenDialog} /> <ConfirmDialog isOpenDialog={openDialog} setIsOpenDialog={setOpenDialog} />
<Button color="primary" onClick={() => setOpenDialog(true)} data-testid="wipe-today-button"> <Button color="primary" onClick={() => setOpenDialog(true)} data-testid="wipe-today-button">
@@ -60,7 +60,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
setNewApiKeys({ ...newApiKeys, [teamId || ""]: apiKey }); setNewApiKeys({ ...newApiKeys, [teamId || ""]: apiKey });
} }
if (integrations.isLoading) { if (integrations.isPending) {
return <div className="bg-emphasis absolute z-50 flex h-screen w-full items-center" />; return <div className="bg-emphasis absolute z-50 flex h-screen w-full items-center" />;
} }
+11 -9
View File
@@ -94,12 +94,12 @@ const _getEventType = async (id: number) => {
type EventType = Awaited<ReturnType<typeof getEventType>>; type EventType = Awaited<ReturnType<typeof getEventType>>;
const getUser = (...args: Parameters<typeof _getUser>): ReturnType<typeof _getUser> => { const getUser = async (...args: Parameters<typeof _getUser>): Promise<ReturnType<typeof _getUser>> => {
return monitorCallbackSync(_getUser, ...args); return monitorCallbackAsync(_getUser, ...args);
}; };
const _getUser = (where: Prisma.UserWhereInput) => const _getUser = async (where: Prisma.UserWhereInput) => {
prisma.user.findFirst({ return await prisma.user.findFirst({
where, where,
select: { select: {
...availabilityUserSelect, ...availabilityUserSelect,
@@ -108,17 +108,18 @@ const _getUser = (where: Prisma.UserWhereInput) =>
}, },
}, },
}); });
};
type User = Awaited<ReturnType<typeof getUser>>; type User = Awaited<ReturnType<typeof getUser>>;
export const getCurrentSeats = ( export const getCurrentSeats = async (
...args: Parameters<typeof _getCurrentSeats> ...args: Parameters<typeof _getCurrentSeats>
): ReturnType<typeof _getCurrentSeats> => { ): Promise<ReturnType<typeof _getCurrentSeats>> => {
return monitorCallbackSync(_getCurrentSeats, ...args); return monitorCallbackAsync(_getCurrentSeats, ...args);
}; };
const _getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) => const _getCurrentSeats = async (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) => {
prisma.booking.findMany({ return await prisma.booking.findMany({
where: { where: {
eventTypeId, eventTypeId,
startTime: { startTime: {
@@ -137,6 +138,7 @@ const _getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) =
}, },
}, },
}); });
};
export type CurrentSeats = Awaited<ReturnType<typeof getCurrentSeats>>; export type CurrentSeats = Awaited<ReturnType<typeof getCurrentSeats>>;
+2 -2
View File
@@ -270,7 +270,7 @@ const AdminAppsListContainer = () => {
const { t } = useLocale(); const { t } = useLocale();
const category = searchParams?.get("category") || AppCategories.calendar; const category = searchParams?.get("category") || AppCategories.calendar;
const { data: apps, isLoading } = trpc.viewer.appsRouter.listLocal.useQuery( const { data: apps, isPending } = trpc.viewer.appsRouter.listLocal.useQuery(
{ category }, { category },
{ enabled: searchParams !== null } { enabled: searchParams !== null }
); );
@@ -291,7 +291,7 @@ const AdminAppsListContainer = () => {
const handleModelOpen = (data: EditModalState) => setModalState({ ...data }); const handleModelOpen = (data: EditModalState) => setModalState({ ...data });
if (isLoading) return <SkeletonLoader />; if (isPending) return <SkeletonLoader />;
if (!apps || apps.length === 0) { if (!apps || apps.length === 0) {
return ( return (
@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
import type { NextApiRequest } from "next"; import type { NextApiRequest } from "next";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { OAuthTokenPayload } from "@calcom/web/pages/api/auth/oauth/token"; import type { OAuthTokenPayload } from "@calcom/types/oauth";
export default async function isAuthorized(req: NextApiRequest, requiredScopes: string[] = []) { export default async function isAuthorized(req: NextApiRequest, requiredScopes: string[] = []) {
const token = req.headers.authorization?.split(" ")[1] || ""; const token = req.headers.authorization?.split(" ")[1] || "";
+7 -5
View File
@@ -1,4 +1,5 @@
import { LazyMotion, m, AnimatePresence } from "framer-motion"; import { LazyMotion, m, AnimatePresence } from "framer-motion";
import { useSession } from "next-auth/react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
@@ -70,6 +71,7 @@ const BookerComponent = ({
shallow shallow
); );
const event = useEvent(); const event = useEvent();
const session = useSession();
const { selectedTimeslot, setSelectedTimeslot } = useSlots(event); const { selectedTimeslot, setSelectedTimeslot } = useSlots(event);
const { const {
@@ -207,7 +209,7 @@ const BookerComponent = ({
} = useCalendars(); } = useCalendars();
useEffect(() => { useEffect(() => {
if (event.isLoading) return setBookerState("loading"); if (event.isPending) return setBookerState("loading");
if (!selectedDate) return setBookerState("selecting_date"); if (!selectedDate) return setBookerState("selecting_date");
if (!selectedTimeslot) return setBookerState("selecting_time"); if (!selectedTimeslot) return setBookerState("selecting_time");
return setBookerState("booking"); return setBookerState("booking");
@@ -362,7 +364,7 @@ const BookerComponent = ({
"bg-default dark:bg-muted sticky top-0 z-10" "bg-default dark:bg-muted sticky top-0 z-10"
)}> )}>
<Header <Header
isMyLink={Boolean(username === event?.data?.owner?.username)} isMyLink={Boolean(username === session?.data?.user.username)}
eventSlug={eventSlug} eventSlug={eventSlug}
enabledLayouts={bookerLayouts.enabledLayouts} enabledLayouts={bookerLayouts.enabledLayouts}
extraDays={layout === BookerLayouts.COLUMN_VIEW ? columnViewExtraDays.current : extraDays} extraDays={layout === BookerLayouts.COLUMN_VIEW ? columnViewExtraDays.current : extraDays}
@@ -399,7 +401,7 @@ const BookerComponent = ({
<BookerSection <BookerSection
area="meta" area="meta"
className="max-w-screen flex w-full flex-col md:w-[var(--booker-meta-width)]"> className="max-w-screen flex w-full flex-col md:w-[var(--booker-meta-width)]">
<EventMeta event={event.data} isLoading={event.isLoading} /> <EventMeta event={event.data} isPending={event.isPending} />
{layout !== BookerLayouts.MONTH_VIEW && {layout !== BookerLayouts.MONTH_VIEW &&
!(layout === "mobile" && bookerState === "booking") && ( !(layout === "mobile" && bookerState === "booking") && (
<div className="mt-auto px-5 py-3 "> <div className="mt-auto px-5 py-3 ">
@@ -434,7 +436,7 @@ const BookerComponent = ({
visible={layout === BookerLayouts.WEEK_VIEW} visible={layout === BookerLayouts.WEEK_VIEW}
className="border-subtle sticky top-0 ml-[-1px] h-full md:border-l" className="border-subtle sticky top-0 ml-[-1px] h-full md:border-l"
{...fadeInLeft}> {...fadeInLeft}>
<LargeCalendar extraDays={extraDays} schedule={schedule.data} isLoading={schedule.isLoading} /> <LargeCalendar extraDays={extraDays} schedule={schedule.data} isLoading={schedule.isPending} />
</BookerSection> </BookerSection>
<BookerSection <BookerSection
@@ -456,7 +458,7 @@ const BookerComponent = ({
extraDays={extraDays} extraDays={extraDays}
limitHeight={layout === BookerLayouts.MONTH_VIEW} limitHeight={layout === BookerLayouts.MONTH_VIEW}
schedule={schedule?.data} schedule={schedule?.data}
isLoading={schedule.isLoading} isLoading={schedule.isPending}
seatsPerTimeSlot={event.data?.seatsPerTimeSlot} seatsPerTimeSlot={event.data?.seatsPerTimeSlot}
showAvailableSeatsCount={event.data?.seatsShowAvailabilityCount} showAvailableSeatsCount={event.data?.seatsShowAvailabilityCount}
/> />
@@ -63,7 +63,7 @@ export const BookEventForm = ({
}, [eventType]); }, [eventType]);
if (eventQuery.isError) return <Alert severity="warning" message={t("error_booking_event")} />; if (eventQuery.isError) return <Alert severity="warning" message={t("error_booking_event")} />;
if (eventQuery.isLoading || !eventQuery.data) return <FormSkeleton />; if (eventQuery.isPending || !eventQuery.data) return <FormSkeleton />;
if (!timeslot) if (!timeslot)
return ( return (
<EmptyScreen <EmptyScreen
@@ -27,7 +27,7 @@ export const DatePicker = ({
return ( return (
<DatePickerComponent <DatePickerComponent
isLoading={schedule.isLoading} isPending={schedule.isPending}
onChange={(date: Dayjs | null) => { onChange={(date: Dayjs | null) => {
setSelectedDate(date === null ? date : date.format("YYYY-MM-DD")); setSelectedDate(date === null ? date : date.format("YYYY-MM-DD"));
}} }}
@@ -24,10 +24,10 @@ const TimezoneSelect = dynamic(
export const EventMeta = ({ export const EventMeta = ({
event, event,
isLoading, isPending,
}: { }: {
event: useEventReturnType["data"]; event: useEventReturnType["data"];
isLoading: useEventReturnType["isLoading"]; isPending: useEventReturnType["isPending"];
}) => { }) => {
const { setTimezone, timeFormat, timezone } = useTimePreferences(); const { setTimezone, timeFormat, timezone } = useTimePreferences();
const selectedDuration = useBookerStore((state) => state.selectedDuration); const selectedDuration = useBookerStore((state) => state.selectedDuration);
@@ -40,7 +40,6 @@ export const EventMeta = ({
shallow shallow
); );
const { i18n, t } = useLocale(); const { i18n, t } = useLocale();
const embedUiConfig = useEmbedUiConfig(); const embedUiConfig = useEmbedUiConfig();
const isEmbed = useIsEmbed(); const isEmbed = useIsEmbed();
const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false; const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false;
@@ -66,12 +65,12 @@ export const EventMeta = ({
return ( return (
<div className="relative z-10 p-6" data-testid="event-meta"> <div className="relative z-10 p-6" data-testid="event-meta">
{isLoading && ( {isPending && (
<m.div {...fadeInUp} initial="visible" layout> <m.div {...fadeInUp} initial="visible" layout>
<EventMetaSkeleton /> <EventMetaSkeleton />
</m.div> </m.div>
)} )}
{!isLoading && !!event && ( {!isPending && !!event && (
<m.div {...fadeInUp} layout transition={{ ...fadeInUp.transition, delay: 0.3 }}> <m.div {...fadeInUp} layout transition={{ ...fadeInUp.transition, delay: 0.3 }}>
<EventMembers <EventMembers
schedulingType={event.schedulingType} schedulingType={event.schedulingType}
@@ -1,8 +1,7 @@
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums"; import { SchedulingType } from "@calcom/prisma/enums";
import { type RouterOutputs } from "@calcom/trpc"; import { type RouterOutputs } from "@calcom/trpc";
import { Button } from "@calcom/ui"; import { Button, UserAvatarGroupWithOrg } from "@calcom/ui";
import { UserAvatarGroupWithOrg } from "@calcom/web/components/ui/avatar/UserAvatarGroupWithOrg";
interface IInstantBookingProps { interface IInstantBookingProps {
onConnectNow: () => void; onConnectNow: () => void;
Loaded 100 of 231 files, more files were not shown because too many files have changed in this diff. Show more