Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0860036607 | ||
|
|
d080a0ef2d | ||
|
|
a6eab74fd1 | ||
|
|
0a2e275908 | ||
|
|
3b05ebc5b5 | ||
|
|
e5b4d2fb80 | ||
|
|
00a2a3d8a8 | ||
|
|
30ba168fc3 | ||
|
|
12eec186b2 | ||
|
|
4417d649ea | ||
|
|
7058da70e6 | ||
|
|
2cb383c337 | ||
|
|
fe5eca8eb3 | ||
|
|
7c6468953a |
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
{}
|
||||
)
|
||||
: availableSlots;
|
||||
: availableSlots.slots;
|
||||
|
||||
return { slots: slotsInProvidedTimeZone };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { type DehydratedState, Hydrate } from "@tanstack/react-query";
|
||||
import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query";
|
||||
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 }) {
|
||||
const { state, children } = props;
|
||||
|
||||
@@ -16,6 +21,6 @@ export function createHydrateClient(opts: { transformer?: DataTransformer }) {
|
||||
return state;
|
||||
}, [state]);
|
||||
|
||||
return <Hydrate state={transformedState}>{children}</Hydrate>;
|
||||
return <HydrationBoundary state={transformedState}>{children}</HydrationBoundary>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ import { trpc } from "app/_trpc/client";
|
||||
import { useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink";
|
||||
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 { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client";
|
||||
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
||||
|
||||
export type Endpoint = (typeof ENDPOINTS)[number];
|
||||
|
||||
@@ -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";
|
||||
|
||||
interface AdditionalCalendarSelectorProps {
|
||||
isLoading?: boolean;
|
||||
isPending?: boolean;
|
||||
}
|
||||
|
||||
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => {
|
||||
const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorProps): JSX.Element | null => {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
|
||||
|
||||
@@ -42,7 +42,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
|
||||
return (
|
||||
<Dropdown modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button StartIcon={Plus} color="secondary" {...(isLoading && { loading: isLoading })}>
|
||||
<Button StartIcon={Plus} color="secondary" {...(isPending && { loading: isPending })}>
|
||||
{t("add")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
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 { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
|
||||
@@ -92,17 +92,19 @@ export const AppPage = ({
|
||||
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
|
||||
const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false);
|
||||
|
||||
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery(
|
||||
{ appType: type },
|
||||
{
|
||||
onSettled(data) {
|
||||
const credentialsCount = data?.credentials.length || 0;
|
||||
setShowDisconnectIntegration(
|
||||
data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0
|
||||
);
|
||||
setExistingCredentials(data?.credentials.map((credential) => credential.id) || []);
|
||||
},
|
||||
}
|
||||
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery({ appType: type });
|
||||
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
const data = appDbQuery.data;
|
||||
|
||||
const credentialsCount = data?.credentials.length || 0;
|
||||
setShowDisconnectIntegration(
|
||||
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, {
|
||||
@@ -190,7 +192,7 @@ export const AppPage = ({
|
||||
)}
|
||||
</header>
|
||||
</div>
|
||||
{!appDbQuery.isLoading ? (
|
||||
{!appDbQuery.isPending ? (
|
||||
isGlobal ||
|
||||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
|
||||
<div className="flex space-x-3">
|
||||
@@ -211,7 +213,7 @@ export const AppPage = ({
|
||||
onClick: () => {
|
||||
mutation.mutate({ type, variant, slug });
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
loading: mutation.isPending,
|
||||
};
|
||||
}
|
||||
return (
|
||||
@@ -250,7 +252,7 @@ export const AppPage = ({
|
||||
onClick: () => {
|
||||
mutation.mutate({ type, variant, slug });
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
loading: mutation.isPending,
|
||||
};
|
||||
}
|
||||
return (
|
||||
@@ -272,7 +274,7 @@ export const AppPage = ({
|
||||
)}
|
||||
|
||||
{dependencies &&
|
||||
(!dependencyData.isLoading ? (
|
||||
(!dependencyData.isPending ? (
|
||||
<div className="mt-6">
|
||||
<AppDependencyComponent appName={name} dependencyData={dependencyData.data} />
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ type Props = {
|
||||
onChanged: () => unknown | Promise<unknown>;
|
||||
fromOnboarding?: boolean;
|
||||
destinationCalendarId?: string;
|
||||
isLoading?: boolean;
|
||||
isPending?: boolean;
|
||||
};
|
||||
|
||||
function CalendarList(props: Props) {
|
||||
@@ -74,7 +74,7 @@ function ConnectedCalendarsList(props: Props) {
|
||||
suspense: true,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
const { fromOnboarding, isLoading } = props;
|
||||
const { fromOnboarding, isPending } = props;
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
@@ -97,7 +97,7 @@ function ConnectedCalendarsList(props: Props) {
|
||||
<div className="flex flex-col xl:flex-row xl:space-x-5">
|
||||
{!!data.connectedCalendars.length && (
|
||||
<div className="flex items-center">
|
||||
<AdditionalCalendarSelector isLoading={isLoading} />
|
||||
<AdditionalCalendarSelector isPending={isPending} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -229,7 +229,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
||||
hidePlaceholder
|
||||
value={data.destinationCalendar?.externalId}
|
||||
onChange={mutation.mutate}
|
||||
isLoading={mutation.isLoading}
|
||||
isPending={mutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,7 +239,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
||||
onChanged={onChanged}
|
||||
fromOnboarding={fromOnboarding}
|
||||
destinationCalendarId={data.destinationCalendar?.externalId}
|
||||
isLoading={mutation.isLoading}
|
||||
isPending={mutation.isPending}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -100,9 +100,9 @@ export const InstallAppButtonChild = ({
|
||||
<DropdownMenuContent
|
||||
className="w-auto"
|
||||
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">
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
@@ -139,7 +139,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
setRejectionDialogIsOpen(true);
|
||||
},
|
||||
icon: Ban,
|
||||
disabled: mutation.isLoading,
|
||||
disabled: mutation.isPending,
|
||||
},
|
||||
// For bookings with payment, only confirm if the booking is paid for
|
||||
...((isPending && !paymentAppData.enabled) ||
|
||||
@@ -153,7 +153,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
bookingConfirm(true);
|
||||
},
|
||||
icon: Check,
|
||||
disabled: mutation.isLoading,
|
||||
disabled: mutation.isPending,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -293,7 +293,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
setViewRecordingsDialogIsOpen(true);
|
||||
},
|
||||
color: showCheckRecordingButton ? "secondary" : "primary",
|
||||
disabled: mutation.isLoading,
|
||||
disabled: mutation.isPending,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -348,7 +348,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
<DialogFooter>
|
||||
<DialogClose />
|
||||
<Button
|
||||
disabled={mutation.isLoading}
|
||||
disabled={mutation.isPending}
|
||||
data-testid="rejection-confirm"
|
||||
onClick={() => {
|
||||
bookingConfirm(false);
|
||||
|
||||
@@ -65,7 +65,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
||||
<DialogClose />
|
||||
<Button
|
||||
data-testid="send_request"
|
||||
disabled={chargeCardMutation.isLoading || chargeError}
|
||||
disabled={chargeCardMutation.isPending || chargeError}
|
||||
onClick={() =>
|
||||
chargeCardMutation.mutate({
|
||||
bookingId,
|
||||
|
||||
@@ -27,7 +27,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
||||
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
|
||||
const [rescheduleReason, setRescheduleReason] = useState("");
|
||||
|
||||
const { mutate: rescheduleApi, isLoading } = trpc.viewer.bookings.requestReschedule.useMutation({
|
||||
const { mutate: rescheduleApi, isPending } = trpc.viewer.bookings.requestReschedule.useMutation({
|
||||
async onSuccess() {
|
||||
showToast(t("reschedule_request_sent"), "success");
|
||||
setIsOpenDialog(false);
|
||||
@@ -65,7 +65,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
||||
<DialogClose />
|
||||
<Button
|
||||
data-testid="send_request"
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
onClick={() => {
|
||||
rescheduleApi({
|
||||
bookingId,
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { EditableSchema } from "@calcom/features/form-builder/schema";
|
||||
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
|
||||
import { classNames } from "@calcom/lib";
|
||||
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 type { Prisma } from "@calcom/prisma/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
import { Copy, Edit, Info } from "@calcom/ui/components/icon";
|
||||
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/web/constants";
|
||||
|
||||
import RequiresConfirmationController from "./RequiresConfirmationController";
|
||||
|
||||
@@ -55,8 +55,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
const { t } = useLocale();
|
||||
|
||||
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
||||
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
|
||||
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
|
||||
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
|
||||
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
|
||||
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
|
||||
|
||||
const bookingFields: Prisma.JsonObject = {};
|
||||
@@ -66,27 +66,31 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
user?.theme === "dark" ||
|
||||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||
|
||||
eventType.bookingFields.forEach(({ name }) => {
|
||||
formMethods.getValues().bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = `${name} input`;
|
||||
});
|
||||
|
||||
const eventNameObject: EventNameObjectType = {
|
||||
attendeeName: t("scheduler"),
|
||||
eventType: eventType.title,
|
||||
eventName: eventType.eventName,
|
||||
host: eventType.users[0]?.name || "Nameless",
|
||||
eventType: formMethods.getValues("title"),
|
||||
eventName: formMethods.getValues("eventName"),
|
||||
host: formMethods.getValues("users")[0]?.name || "Nameless",
|
||||
bookingFields: bookingFields,
|
||||
t,
|
||||
};
|
||||
|
||||
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
|
||||
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
|
||||
const [requiresConfirmation, setRequiresConfirmation] = useState(
|
||||
formMethods.getValues("requiresConfirmation")
|
||||
);
|
||||
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
|
||||
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(() => {
|
||||
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id));
|
||||
}, [eventType.users, hashedUrl, team?.id]);
|
||||
!hashedUrl && setHashedUrl(generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id));
|
||||
}, [formMethods.getValues("users"), hashedUrl, team?.id]);
|
||||
|
||||
const toggleGuests = (enabled: boolean) => {
|
||||
const bookingFields = formMethods.getValues("bookingFields");
|
||||
@@ -108,7 +112,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
};
|
||||
|
||||
const { shouldLockDisableProps } = useLockedFieldsManager(
|
||||
eventType,
|
||||
formMethods.getValues(),
|
||||
t("locked_fields_admin_description"),
|
||||
t("locked_fields_member_description")
|
||||
);
|
||||
@@ -122,7 +126,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
|
||||
const closeEventNameTip = () => setShowEventNameTip(false);
|
||||
|
||||
const setEventName = (value: string) => formMethods.setValue("eventName", value);
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/**
|
||||
@@ -145,12 +148,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
</Link>
|
||||
</div>
|
||||
<Controller
|
||||
control={formMethods.control}
|
||||
name="destinationCalendar"
|
||||
defaultValue={eventType.destinationCalendar || undefined}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<DestinationCalendarSelector
|
||||
destinationCalendar={eventType.destinationCalendar}
|
||||
value={value ? value.externalId : undefined}
|
||||
onChange={onChange}
|
||||
hidePlaceholder
|
||||
@@ -167,7 +167,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
type="text"
|
||||
{...shouldLockDisableProps("eventName")}
|
||||
placeholder={eventNamePlaceholder}
|
||||
defaultValue={eventType.eventName || ""}
|
||||
{...formMethods.register("eventName")}
|
||||
addOnSuffix={
|
||||
<Button
|
||||
@@ -194,7 +193,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
{...shouldLockDisableProps("bookingFields")}
|
||||
dataStore={{
|
||||
options: {
|
||||
locations: getLocationsOptionsForSelect(eventType?.locations ?? [], t),
|
||||
locations: getLocationsOptionsForSelect(formMethods.getValues("locations") ?? [], t),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -203,15 +202,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
<RequiresConfirmationController
|
||||
eventType={eventType}
|
||||
seatsEnabled={seatsEnabled}
|
||||
metadata={eventType.metadata}
|
||||
metadata={formMethods.getValues("metadata")}
|
||||
requiresConfirmation={requiresConfirmation}
|
||||
onRequiresConfirmation={setRequiresConfirmation}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="requiresBookerEmailVerification"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.requiresBookerEmailVerification}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
@@ -228,8 +225,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
|
||||
<Controller
|
||||
name="hideCalendarNotes"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.hideCalendarNotes}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
@@ -246,7 +241,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
|
||||
<Controller
|
||||
name="successRedirectUrl"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
@@ -274,7 +268,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
placeholder={t("external_redirect_url")}
|
||||
required={redirectUrlVisible}
|
||||
type="text"
|
||||
defaultValue={eventType.successRedirectUrl || ""}
|
||||
{...formMethods.register("successRedirectUrl")}
|
||||
/>
|
||||
<div
|
||||
@@ -327,7 +320,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
hint={t("private_link_hint")}
|
||||
defaultValue={placeholderHashedLink}
|
||||
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
|
||||
color="minimal"
|
||||
size="sm"
|
||||
@@ -336,7 +332,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
aria-label="copy link"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(placeholderHashedLink);
|
||||
if (eventType.hashedLink) {
|
||||
if (formMethods.getValues("hashedLink")) {
|
||||
showToast(t("private_link_copied"), "success");
|
||||
} else {
|
||||
showToast(t("enabled_after_update_description"), "warning");
|
||||
@@ -353,8 +349,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
|
||||
<Controller
|
||||
name="seatsPerTimeSlotEnabled"
|
||||
control={formMethods.control}
|
||||
defaultValue={!!eventType.seatsPerTimeSlot}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
@@ -378,7 +372,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
formMethods.setValue("requiresConfirmation", false);
|
||||
setRequiresConfirmation(false);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||
formMethods.setValue("seatsPerTimeSlot", 2);
|
||||
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
|
||||
} else {
|
||||
formMethods.setValue("seatsPerTimeSlot", null);
|
||||
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">
|
||||
<Controller
|
||||
name="seatsPerTimeSlot"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.seatsPerTimeSlot}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div>
|
||||
<TextField
|
||||
@@ -399,7 +391,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
label={t("number_of_seats")}
|
||||
type="number"
|
||||
disabled={seatsLocked.disabled}
|
||||
defaultValue={value || 2}
|
||||
defaultValue={value}
|
||||
min={1}
|
||||
containerClassName="max-w-80"
|
||||
addOnSuffix={<>{t("seats")}</>}
|
||||
@@ -408,21 +400,29 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<CheckboxField
|
||||
description={t("show_attendees")}
|
||||
disabled={seatsLocked.disabled}
|
||||
onChange={(e) => formMethods.setValue("seatsShowAttendees", e.target.checked)}
|
||||
defaultChecked={!!eventType.seatsShowAttendees}
|
||||
<Controller
|
||||
name="seatsShowAttendees"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CheckboxField
|
||||
description={t("show_attendees")}
|
||||
disabled={seatsLocked.disabled}
|
||||
onChange={(e) => onChange(e)}
|
||||
checked={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<CheckboxField
|
||||
description={t("show_available_seats_count")}
|
||||
disabled={seatsLocked.disabled}
|
||||
onChange={(e) =>
|
||||
formMethods.setValue("seatsShowAvailabilityCount", e.target.checked)
|
||||
}
|
||||
defaultChecked={!!eventType.seatsShowAvailabilityCount}
|
||||
<Controller
|
||||
name="seatsShowAvailabilityCount"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CheckboxField
|
||||
description={t("show_available_seats_count")}
|
||||
disabled={seatsLocked.disabled}
|
||||
onChange={(e) => onChange(e)}
|
||||
checked={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,8 +436,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
/>
|
||||
<Controller
|
||||
name="lockTimeZoneToggleOnBookingPage"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.lockTimeZoneToggleOnBookingPage}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
@@ -452,59 +450,47 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
)}
|
||||
/>
|
||||
{allowDisablingAttendeeConfirmationEmails(workflows) && (
|
||||
<>
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.attendee"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||
title={t("disable_attendees_confirmation_emails")}
|
||||
description={t("disable_attendees_confirmation_emails_description")}
|
||||
checked={value || false}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e);
|
||||
onChange(e);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.attendee"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||
title={t("disable_attendees_confirmation_emails")}
|
||||
description={t("disable_attendees_confirmation_emails_description")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => onChange(e)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{allowDisablingHostConfirmationEmails(workflows) && (
|
||||
<>
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.host"
|
||||
control={formMethods.control}
|
||||
defaultValue={!!eventType.seatsPerTimeSlot}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||
title={t("disable_host_confirmation_emails")}
|
||||
description={t("disable_host_confirmation_emails_description")}
|
||||
checked={value || false}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e);
|
||||
onChange(e);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
<Controller
|
||||
name="metadata.disableStandardEmails.confirmation.host"
|
||||
defaultValue={!!formMethods.getValues("seatsPerTimeSlot")}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||
title={t("disable_host_confirmation_emails")}
|
||||
description={t("disable_host_confirmation_emails_description")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => onChange(e)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{showEventNameTip && (
|
||||
<CustomEventTypeModal
|
||||
close={closeEventNameTip}
|
||||
setValue={setEventName}
|
||||
defaultValue={formMethods.getValues("eventName") || eventType.eventName || ""}
|
||||
setValue={(val: string) => formMethods.setValue("eventName", val)}
|
||||
defaultValue={formMethods.getValues("eventName")}
|
||||
placeHolder={eventNamePlaceholder}
|
||||
event={eventNameObject}
|
||||
/>
|
||||
|
||||
@@ -18,21 +18,21 @@ export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
|
||||
|
||||
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
const { t } = useLocale();
|
||||
const { data: eventTypeApps, isLoading } = trpc.viewer.integrations.useQuery({
|
||||
const { data: eventTypeApps, isPending } = trpc.viewer.integrations.useQuery({
|
||||
extendsFeature: "EventType",
|
||||
teamId: eventType.team?.id || eventType.parent?.teamId,
|
||||
});
|
||||
|
||||
const methods = useFormContext<FormValues>();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const installedApps =
|
||||
eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || [];
|
||||
const notInstalledApps =
|
||||
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) => {
|
||||
methods.setValue("metadata", {
|
||||
...methods.getValues("metadata"),
|
||||
formMethods.setValue("metadata", {
|
||||
...formMethods.getValues("metadata"),
|
||||
apps: _allAppsData,
|
||||
});
|
||||
};
|
||||
@@ -47,7 +47,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const eventTypeFormMetadata = methods.getValues("metadata");
|
||||
const eventTypeFormMetadata = formMethods.getValues("metadata");
|
||||
|
||||
const getAppDataSetter = (
|
||||
appId: EventTypeAppsList,
|
||||
@@ -56,8 +56,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
): SetAppData => {
|
||||
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)
|
||||
const allAppsDataFromForm = methods.getValues("metadata")?.apps || {};
|
||||
|
||||
const allAppsDataFromForm = formMethods.getValues("metadata")?.apps || {};
|
||||
const appData = allAppsDataFromForm[appId];
|
||||
setAllAppsData({
|
||||
...allAppsDataFromForm,
|
||||
@@ -138,7 +137,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
message={t("locked_apps_description")}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !installedApps?.length ? (
|
||||
{!isPending && !installedApps?.length ? (
|
||||
<EmptyScreen
|
||||
Icon={Grid}
|
||||
headline={t("empty_installed_apps_headline")}
|
||||
@@ -179,7 +178,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
</div>
|
||||
{!shouldLockDisableProps("apps").disabled && (
|
||||
<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]">
|
||||
{t("available_apps_lower_case")}
|
||||
|
||||
@@ -85,7 +85,7 @@ const EventTypeScheduleDetails = memo(
|
||||
const { watch } = useFormContext<FormValues>();
|
||||
|
||||
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 || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
|
||||
@@ -112,7 +112,7 @@ const EventTypeScheduleDetails = memo(
|
||||
)}>
|
||||
{day}
|
||||
</span>
|
||||
{isLoading ? (
|
||||
{isPending ? (
|
||||
<SkeletonText className="block h-5 w-60" />
|
||||
) : isAvailable ? (
|
||||
<div className="space-y-3 text-right">
|
||||
@@ -142,7 +142,7 @@ const EventTypeScheduleDetails = memo(
|
||||
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
|
||||
<Button
|
||||
href={`/availability/${schedule.id}`}
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
color="minimal"
|
||||
EndIcon={ExternalLink}
|
||||
target="_blank"
|
||||
@@ -169,8 +169,15 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
const watchSchedule = watch("schedule");
|
||||
const [options, setOptions] = useState<AvailabilityOption[]>([]);
|
||||
|
||||
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, {
|
||||
onSuccess: ({ schedules }) => {
|
||||
const { data, isPending } = trpc.viewer.availability.list.useQuery(undefined);
|
||||
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const schedules = data.schedules;
|
||||
|
||||
const options = schedules.map((schedule) => ({
|
||||
value: schedule.id,
|
||||
label: schedule.name,
|
||||
@@ -225,8 +232,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
|
||||
setValue("availability", value);
|
||||
},
|
||||
});
|
||||
|
||||
[data]
|
||||
);
|
||||
const availabilityValue = watch("availability");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -241,8 +248,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
{t("availability")}
|
||||
{shouldLockIndicator("availability")}
|
||||
</label>
|
||||
{isLoading && <SelectSkeletonLoader />}
|
||||
{!isLoading && (
|
||||
{isPending && <SelectSkeletonLoader />}
|
||||
{!isPending && (
|
||||
<Controller
|
||||
name="schedule"
|
||||
render={({ field }) => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
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 React, { useEffect, useState } from "react";
|
||||
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 { 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 formMethods = useFormContext<FormValues>();
|
||||
|
||||
@@ -126,36 +126,20 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
},
|
||||
];
|
||||
|
||||
const 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 watchPeriodType = formMethods.watch("periodType");
|
||||
|
||||
const optionsPeriod = [
|
||||
{ value: 1, label: t("calendar_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 offsetStartValue = useWatch({
|
||||
control: formMethods.control,
|
||||
name: "offsetStart",
|
||||
});
|
||||
const [offsetToggle, setOffsetToggle] = useState(() => offsetStartValue > 0);
|
||||
const [offsetToggle, setOffsetToggle] = useState(formMethods.getValues("offsetStart") > 0);
|
||||
|
||||
// Preview how the offset will affect start times
|
||||
const watchOffsetStartValue = formMethods.watch("offsetStart");
|
||||
const offsetOriginalTime = new Date();
|
||||
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 (
|
||||
<div>
|
||||
@@ -165,8 +149,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
<Label htmlFor="beforeBufferTime">{t("before_event")}</Label>
|
||||
<Controller
|
||||
name="beforeBufferTime"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.beforeEventBuffer || 0}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const beforeBufferOptions = [
|
||||
{
|
||||
@@ -197,8 +179,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
<Label htmlFor="afterBufferTime">{t("after_event")}</Label>
|
||||
<Controller
|
||||
name="afterBufferTime"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.afterEventBuffer || 0}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const afterBufferOptions = [
|
||||
{
|
||||
@@ -235,7 +215,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
<Label htmlFor="slotInterval">{t("slot_interval")}</Label>
|
||||
<Controller
|
||||
name="slotInterval"
|
||||
control={formMethods.control}
|
||||
render={() => {
|
||||
const slotIntervalOptions = [
|
||||
{
|
||||
@@ -254,8 +233,9 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
|
||||
}}
|
||||
defaultValue={
|
||||
slotIntervalOptions.find((option) => option.value === eventType.slotInterval) ||
|
||||
slotIntervalOptions[0]
|
||||
slotIntervalOptions.find(
|
||||
(option) => option.value === formMethods.getValues("slotInterval")
|
||||
) || slotIntervalOptions[0]
|
||||
}
|
||||
options={slotIntervalOptions}
|
||||
/>
|
||||
@@ -267,7 +247,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
</div>
|
||||
<Controller
|
||||
name="bookingLimits"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => {
|
||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||
return (
|
||||
@@ -300,7 +279,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
<Controller
|
||||
name="onlyShowFirstAvailableSlot"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => {
|
||||
const isChecked = value;
|
||||
return (
|
||||
@@ -323,7 +301,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
<Controller
|
||||
name="durationLimits"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => {
|
||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||
return (
|
||||
@@ -361,7 +338,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
<Controller
|
||||
name="periodType"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => {
|
||||
const isChecked = value && value !== "UNLIMITED";
|
||||
|
||||
@@ -380,7 +356,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
<RadioGroup.Root
|
||||
defaultValue={watchPeriodType}
|
||||
value={watchPeriodType}
|
||||
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
|
||||
{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"
|
||||
placeholder="30"
|
||||
{...formMethods.register("periodDays", { valueAsNumber: true })}
|
||||
defaultValue={eventType.periodDays || 30}
|
||||
/>
|
||||
<Select
|
||||
options={optionsPeriod}
|
||||
isSearchable={false}
|
||||
onChange={(opt) => {
|
||||
formMethods.setValue(
|
||||
"periodCountCalendarDays",
|
||||
opt?.value.toString() as "0" | "1"
|
||||
);
|
||||
}}
|
||||
defaultValue={
|
||||
optionsPeriod.find(
|
||||
(opt) => opt.value === (eventType.periodCountCalendarDays ? 1 : 0)
|
||||
) ?? optionsPeriod[0]
|
||||
onChange={(opt) =>
|
||||
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "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>
|
||||
)}
|
||||
@@ -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">
|
||||
<Controller
|
||||
name="periodDates"
|
||||
control={formMethods.control}
|
||||
defaultValue={periodDates}
|
||||
render={() => (
|
||||
<DateRangePicker
|
||||
startDate={formMethods.getValues("periodDates").startDate}
|
||||
@@ -481,7 +455,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
type="number"
|
||||
containerClassName="max-w-80"
|
||||
label={t("offset_start")}
|
||||
{...formMethods.register("offsetStart")}
|
||||
{...formMethods.register("offsetStart", { setValueAs: (value) => Number(value) })}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
hint={t("offset_start_description", {
|
||||
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||
|
||||
@@ -9,9 +9,5 @@ export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupProps, "even
|
||||
|
||||
const requirePayment = paymentAppData.price > 0;
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />
|
||||
</div>
|
||||
);
|
||||
return <RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />;
|
||||
};
|
||||
|
||||
@@ -48,21 +48,19 @@ const getLocationFromType = (
|
||||
}
|
||||
};
|
||||
|
||||
const getLocationInfo = (props: Pick<EventTypeSetupProps, "eventType" | "locationOptions">) => {
|
||||
const getLocationInfo = ({
|
||||
eventType,
|
||||
locationOptions,
|
||||
}: Pick<EventTypeSetupProps, "eventType" | "locationOptions">) => {
|
||||
const locationAvailable =
|
||||
props.eventType.locations &&
|
||||
props.eventType.locations.length > 0 &&
|
||||
props.locationOptions.some((op) =>
|
||||
op.options.find((opt) => opt.value === props.eventType.locations[0].type)
|
||||
);
|
||||
const locationDetails = props.eventType.locations &&
|
||||
props.eventType.locations.length > 0 &&
|
||||
eventType.locations &&
|
||||
eventType.locations.length > 0 &&
|
||||
locationOptions.some((op) => op.options.find((opt) => opt.value === eventType.locations[0].type));
|
||||
const locationDetails = eventType.locations &&
|
||||
eventType.locations.length > 0 &&
|
||||
!locationAvailable && {
|
||||
slug: props.eventType.locations[0].type
|
||||
.replace("integrations:", "")
|
||||
.replace(":", "-")
|
||||
.replace("_video", ""),
|
||||
name: props.eventType.locations[0].type
|
||||
slug: eventType.locations[0].type.replace("integrations:", "").replace(":", "-").replace("_video", ""),
|
||||
name: eventType.locations[0].type
|
||||
.replace("integrations:", "")
|
||||
.replace(":", " ")
|
||||
.replace("_video", "")
|
||||
@@ -72,16 +70,11 @@ const getLocationInfo = (props: Pick<EventTypeSetupProps, "eventType" | "locatio
|
||||
};
|
||||
return { locationAvailable, locationDetails };
|
||||
};
|
||||
interface DescriptionEditorProps {
|
||||
description?: string | null;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
const DescriptionEditor = (props: DescriptionEditorProps) => {
|
||||
const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const [mounted, setIsMounted] = useState(false);
|
||||
const { t } = useLocale();
|
||||
const { description } = props;
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
@@ -89,11 +82,11 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
|
||||
|
||||
return mounted ? (
|
||||
<Editor
|
||||
getText={() => md.render(formMethods.getValues("description") || description || "")}
|
||||
getText={() => md.render(formMethods.getValues("description") || "")}
|
||||
setText={(value: string) => formMethods.setValue("description", turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
placeholder={t("quick_video_meeting")}
|
||||
editable={props.editable}
|
||||
editable={isEditable}
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
@@ -113,7 +106,9 @@ export const EventSetupTab = (
|
||||
const { t } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { eventType, team, destinationCalendar } = props;
|
||||
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
|
||||
const [multipleDuration, setMultipleDuration] = useState(
|
||||
formMethods.getValues("metadata")?.multipleDuration
|
||||
);
|
||||
const orgBranding = useOrgBranding();
|
||||
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
|
||||
|
||||
@@ -143,12 +138,12 @@ export const EventSetupTab = (
|
||||
}>
|
||||
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
|
||||
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 } =
|
||||
useLockedFieldsManager(
|
||||
eventType,
|
||||
formMethods.getValues(),
|
||||
t("locked_fields_admin_description"),
|
||||
t("locked_fields_member_description")
|
||||
);
|
||||
@@ -195,7 +190,6 @@ export const EventSetupTab = (
|
||||
return (
|
||||
<Controller
|
||||
name={`locations.${index}.${eventLocationType.defaultValueVariable}`}
|
||||
control={formMethods.control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
@@ -219,7 +213,6 @@ export const EventSetupTab = (
|
||||
return (
|
||||
<Controller
|
||||
name={`locations.${index}.${eventLocationType.defaultValueVariable}`}
|
||||
control={formMethods.control}
|
||||
defaultValue={defaultValue}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
@@ -339,7 +332,7 @@ export const EventSetupTab = (
|
||||
defaultChecked={defaultLocation?.displayLocationPublicly}
|
||||
description={t("display_location_label")}
|
||||
onChange={(e) => {
|
||||
const fieldValues = formMethods.getValues().locations[index];
|
||||
const fieldValues = formMethods.getValues("locations")[index];
|
||||
updateLocationField(index, {
|
||||
...fieldValues,
|
||||
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.
|
||||
Change it{" "}
|
||||
<Link
|
||||
href={`${CAL_URL}/event-types/${eventType.id}?tabName=advanced`}
|
||||
href={`${CAL_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
|
||||
className="underline">
|
||||
here.
|
||||
</Link>{" "}
|
||||
@@ -463,7 +456,6 @@ export const EventSetupTab = (
|
||||
required
|
||||
label={t("title")}
|
||||
{...shouldLockDisableProps("title")}
|
||||
defaultValue={eventType.title}
|
||||
{...formMethods.register("title")}
|
||||
/>
|
||||
<div>
|
||||
@@ -471,23 +463,19 @@ export const EventSetupTab = (
|
||||
{t("description")}
|
||||
{shouldLockIndicator("description")}
|
||||
</Label>
|
||||
<DescriptionEditor
|
||||
description={eventType?.description}
|
||||
editable={!descriptionLockedProps.disabled}
|
||||
/>
|
||||
<DescriptionEditor isEditable={!descriptionLockedProps.disabled} />
|
||||
</div>
|
||||
<TextField
|
||||
required
|
||||
label={t("URL")}
|
||||
{...shouldLockDisableProps("slug")}
|
||||
defaultValue={eventType.slug}
|
||||
addOnLeading={
|
||||
<>
|
||||
{urlPrefix}/
|
||||
{!isManagedEventType
|
||||
? team
|
||||
? (orgBranding ? "" : "team/") + team.slug
|
||||
: eventType.users[0].username
|
||||
: formMethods.getValues("users")[0].username
|
||||
: t("username_placeholder")}
|
||||
/
|
||||
</>
|
||||
@@ -564,7 +552,7 @@ export const EventSetupTab = (
|
||||
type="number"
|
||||
{...lengthLockedProps}
|
||||
label={t("duration")}
|
||||
defaultValue={eventType.length ?? 15}
|
||||
defaultValue={formMethods.getValues("length") ?? 15}
|
||||
{...formMethods.register("length")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
min={1}
|
||||
@@ -580,6 +568,8 @@ export const EventSetupTab = (
|
||||
onCheckedChange={() => {
|
||||
if (multipleDuration !== undefined) {
|
||||
setMultipleDuration(undefined);
|
||||
setSelectedMultipleDuration([]);
|
||||
setDefaultDuration(null);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||
formMethods.setValue("length", eventType.length);
|
||||
} else {
|
||||
@@ -600,12 +590,7 @@ export const EventSetupTab = (
|
||||
{shouldLockIndicator("locations")}
|
||||
</Skeleton>
|
||||
|
||||
<Controller
|
||||
name="locations"
|
||||
control={formMethods.control}
|
||||
defaultValue={eventType.locations || []}
|
||||
render={() => <Locations />}
|
||||
/>
|
||||
<Controller name="locations" render={() => <Locations />} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,55 +67,66 @@ type Props = {
|
||||
availability?: AvailabilityOption;
|
||||
isUserOrganizationAdmin: boolean;
|
||||
bookerUrl: string;
|
||||
activeWebhooksNumber: number;
|
||||
};
|
||||
|
||||
function getNavigation(props: {
|
||||
type getNavigationProps = {
|
||||
t: TFunction;
|
||||
eventType: Props["eventType"];
|
||||
length: number;
|
||||
id: number;
|
||||
multipleDuration?: EventTypeSetupProps["eventType"]["metadata"]["multipleDuration"];
|
||||
enabledAppsNumber: number;
|
||||
enabledWorkflowsNumber: number;
|
||||
installedAppsNumber: number;
|
||||
availability: AvailabilityOption | undefined;
|
||||
}) {
|
||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
||||
const duration =
|
||||
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
||||
};
|
||||
|
||||
function getNavigation({
|
||||
length,
|
||||
id,
|
||||
multipleDuration,
|
||||
t,
|
||||
enabledAppsNumber,
|
||||
installedAppsNumber,
|
||||
enabledWorkflowsNumber,
|
||||
}: getNavigationProps) {
|
||||
const duration = multipleDuration?.map((duration) => ` ${duration}`) || length;
|
||||
|
||||
return [
|
||||
{
|
||||
name: "event_setup_tab_title",
|
||||
href: `/event-types/${eventType.id}?tabName=setup`,
|
||||
href: `/event-types/${id}?tabName=setup`,
|
||||
icon: LinkIcon,
|
||||
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
|
||||
},
|
||||
{
|
||||
name: "event_limit_tab_title",
|
||||
href: `/event-types/${eventType.id}?tabName=limits`,
|
||||
href: `/event-types/${id}?tabName=limits`,
|
||||
icon: Clock,
|
||||
info: `event_limit_tab_description`,
|
||||
},
|
||||
{
|
||||
name: "event_advanced_tab_title",
|
||||
href: `/event-types/${eventType.id}?tabName=advanced`,
|
||||
href: `/event-types/${id}?tabName=advanced`,
|
||||
icon: Sliders,
|
||||
info: `event_advanced_tab_description`,
|
||||
},
|
||||
{
|
||||
name: "recurring",
|
||||
href: `/event-types/${eventType.id}?tabName=recurring`,
|
||||
href: `/event-types/${id}?tabName=recurring`,
|
||||
icon: Repeat,
|
||||
info: `recurring_event_tab_description`,
|
||||
},
|
||||
{
|
||||
name: "apps",
|
||||
href: `/event-types/${eventType.id}?tabName=apps`,
|
||||
href: `/event-types/${id}?tabName=apps`,
|
||||
icon: Grid,
|
||||
//TODO: Handle proper translation with count handling
|
||||
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
|
||||
},
|
||||
{
|
||||
name: "workflows",
|
||||
href: `/event-types/${eventType.id}?tabName=workflows`,
|
||||
href: `/event-types/${id}?tabName=workflows`,
|
||||
icon: Zap,
|
||||
info: `${enabledWorkflowsNumber} ${t("active")}`,
|
||||
},
|
||||
@@ -136,6 +147,7 @@ function EventTypeSingleLayout({
|
||||
availability,
|
||||
isUserOrganizationAdmin,
|
||||
bookerUrl,
|
||||
activeWebhooksNumber,
|
||||
}: Props) {
|
||||
const utils = trpc.useContext();
|
||||
const { t } = useLocale();
|
||||
@@ -145,7 +157,7 @@ function EventTypeSingleLayout({
|
||||
const hasPermsToDelete =
|
||||
currentUserMembership?.role !== "MEMBER" ||
|
||||
!currentUserMembership ||
|
||||
eventType.schedulingType === SchedulingType.MANAGED ||
|
||||
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
|
||||
isUserOrganizationAdmin;
|
||||
|
||||
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
|
||||
@@ -167,16 +179,23 @@ function EventTypeSingleLayout({
|
||||
});
|
||||
|
||||
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
||||
eventType,
|
||||
formMethods.getValues(),
|
||||
t("locked_fields_admin_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
|
||||
const EventTypeTabs = useMemo(() => {
|
||||
const navigation = getNavigation({
|
||||
t,
|
||||
eventType,
|
||||
length,
|
||||
multipleDuration,
|
||||
id: formMethods.getValues("id"),
|
||||
enabledAppsNumber,
|
||||
installedAppsNumber,
|
||||
enabledWorkflowsNumber,
|
||||
@@ -185,31 +204,29 @@ function EventTypeSingleLayout({
|
||||
|
||||
navigation.splice(1, 0, {
|
||||
name: "availability",
|
||||
href: `/event-types/${eventType.id}?tabName=availability`,
|
||||
href: `/event-types/${formMethods.getValues("id")}?tabName=availability`,
|
||||
icon: Calendar,
|
||||
info:
|
||||
isManagedEventType || isChildrenManagedEventType
|
||||
? eventType.schedule === null
|
||||
? formMethods.getValues("schedule") === null
|
||||
? "members_default_schedule"
|
||||
: isChildrenManagedEventType
|
||||
? `${
|
||||
eventType.scheduleName
|
||||
? `${eventType.scheduleName} - ${t("managed")}`
|
||||
formMethods.getValues("scheduleName")
|
||||
? `${formMethods.getValues("scheduleName")} - ${t("managed")}`
|
||||
: `default_schedule_name`
|
||||
}`
|
||||
: eventType.scheduleName ?? `default_schedule_name`
|
||||
: eventType.scheduleName ?? `default_schedule_name`,
|
||||
: formMethods.getValues("scheduleName") ?? `default_schedule_name`
|
||||
: formMethods.getValues("scheduleName") ?? `default_schedule_name`,
|
||||
});
|
||||
// If there is a team put this navigation item within the tabs
|
||||
if (team) {
|
||||
navigation.splice(2, 0, {
|
||||
name: "assignment",
|
||||
href: `/event-types/${eventType.id}?tabName=team`,
|
||||
href: `/event-types/${formMethods.getValues("id")}?tabName=team`,
|
||||
icon: Users,
|
||||
info: `${t(eventType.schedulingType?.toLowerCase() ?? "")}${
|
||||
isManagedEventType
|
||||
? ` - ${t("number_member", { count: formMethods.watch("children").length || 0 })}`
|
||||
: ""
|
||||
info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
|
||||
isManagedEventType ? ` - ${t("number_member", { count: watchChildrenCount || 0 })}` : ""
|
||||
}`,
|
||||
});
|
||||
}
|
||||
@@ -225,15 +242,14 @@ function EventTypeSingleLayout({
|
||||
}
|
||||
navigation.push({
|
||||
name: "webhooks",
|
||||
href: `/event-types/${eventType.id}?tabName=webhooks`,
|
||||
href: `/event-types/${formMethods.getValues("id")}?tabName=webhooks`,
|
||||
icon: TbWebhook,
|
||||
info: `${eventType.webhooks.filter((webhook) => webhook.active).length} ${t("active")}`,
|
||||
info: `${activeWebhooksNumber} ${t("active")}`,
|
||||
});
|
||||
}
|
||||
return navigation;
|
||||
}, [
|
||||
t,
|
||||
eventType,
|
||||
enabledAppsNumber,
|
||||
installedAppsNumber,
|
||||
enabledWorkflowsNumber,
|
||||
@@ -241,16 +257,23 @@ function EventTypeSingleLayout({
|
||||
isManagedEventType,
|
||||
isChildrenManagedEventType,
|
||||
team,
|
||||
formMethods,
|
||||
length,
|
||||
multipleDuration,
|
||||
formMethods.getValues("id"),
|
||||
watchSchedulingType,
|
||||
watchChildrenCount,
|
||||
activeWebhooksNumber,
|
||||
]);
|
||||
|
||||
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}`;
|
||||
|
||||
const embedLink = `${team ? `team/${team.slug}` : eventType.users[0].username}/${eventType.slug}`;
|
||||
const isManagedEvent = eventType.schedulingType === SchedulingType.MANAGED ? "_managed" : "";
|
||||
|
||||
const embedLink = `${
|
||||
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 (
|
||||
<Shell
|
||||
backPath="/event-types"
|
||||
@@ -258,7 +281,7 @@ function EventTypeSingleLayout({
|
||||
heading={eventType.title}
|
||||
CTA={
|
||||
<div className="flex items-center justify-end">
|
||||
{!eventType.metadata?.managedEventConfig && (
|
||||
{!formMethods.getValues("metadata")?.managedEventConfig && (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -333,7 +356,7 @@ function EventTypeSingleLayout({
|
||||
tooltip={t("embed")}
|
||||
tooltipSide="bottom"
|
||||
tooltipOffset={4}
|
||||
eventId={eventType.id}
|
||||
eventId={formMethods.getValues("id")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -446,14 +469,14 @@ function EventTypeSingleLayout({
|
||||
</Suspense>
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={deleteMutation.isLoading}
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t(`delete${isManagedEvent}_event_type`)}
|
||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||
loadingText={t(`confirm_delete_event_type`)}
|
||||
onConfirm={(e) => {
|
||||
e.preventDefault();
|
||||
deleteMutation.mutate({ id: eventType.id });
|
||||
deleteMutation.mutate({ id: formMethods.getValues("id") });
|
||||
}}>
|
||||
<p className="mt-5">
|
||||
<Trans
|
||||
|
||||
@@ -35,8 +35,9 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
|
||||
const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({
|
||||
async onSuccess() {
|
||||
setEditModalOpen(false);
|
||||
await utils.viewer.webhook.list.invalidate();
|
||||
showToast(t("webhook_updated_successfully"), "success");
|
||||
await utils.viewer.webhook.list.invalidate();
|
||||
await utils.viewer.eventTypes.get.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
showToast(`${error.message}`, "error");
|
||||
@@ -45,9 +46,10 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
|
||||
|
||||
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
|
||||
async onSuccess() {
|
||||
setCreateModalOpen(false);
|
||||
showToast(t("webhook_created_successfully"), "success");
|
||||
await utils.viewer.webhook.list.invalidate();
|
||||
setCreateModalOpen(false);
|
||||
await utils.viewer.eventTypes.get.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
showToast(`${error.message}`, "error");
|
||||
|
||||
@@ -108,7 +108,7 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
|
||||
eventTypeId: eventType.id,
|
||||
eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING],
|
||||
});
|
||||
const { data: installedApps, isLoading } = trpc.viewer.integrations.useQuery({
|
||||
const { data: installedApps, isPending } = trpc.viewer.integrations.useQuery({
|
||||
variant: "other",
|
||||
onlyInstalled: true,
|
||||
});
|
||||
@@ -188,7 +188,7 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{webhooks && !isLoading && (
|
||||
{webhooks && !isPending && (
|
||||
<>
|
||||
<div>
|
||||
{webhooks.length ? (
|
||||
|
||||
@@ -8,18 +8,18 @@ interface ManagedEventDialogProps {
|
||||
slugExistsChildrenDialogOpen: ChildrenEventType[];
|
||||
slug: string;
|
||||
onOpenChange: () => void;
|
||||
isLoading: boolean;
|
||||
isPending: boolean;
|
||||
onConfirm: (e: { preventDefault: () => void }) => void;
|
||||
}
|
||||
|
||||
export default function ManagedEventDialog(props: ManagedEventDialogProps) {
|
||||
const { t } = useLocale();
|
||||
const { slugExistsChildrenDialogOpen, slug, onOpenChange, isLoading, onConfirm } = props;
|
||||
const { slugExistsChildrenDialogOpen, slug, onOpenChange, isPending, onConfirm } = props;
|
||||
|
||||
return (
|
||||
<Dialog open={slugExistsChildrenDialogOpen.length > 0} onOpenChange={onOpenChange}>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={isLoading}
|
||||
isPending={isPending}
|
||||
variety="warning"
|
||||
title={t("managed_event_dialog_title", {
|
||||
slug,
|
||||
|
||||
@@ -19,11 +19,10 @@ export default function RecurringEventController({
|
||||
paymentEnabled,
|
||||
}: RecurringEventControllerProps) {
|
||||
const { t } = useLocale();
|
||||
const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(
|
||||
eventType.recurringEvent
|
||||
);
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
|
||||
const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(
|
||||
formMethods.getValues("recurringEvent")
|
||||
);
|
||||
/* Just yearly-0, monthly-1 and weekly-2 */
|
||||
const recurringEventFreqOptions = Object.entries(Frequency)
|
||||
.filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3)
|
||||
|
||||
@@ -28,7 +28,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
|
||||
color="secondary"
|
||||
disabled={installed}
|
||||
type="button"
|
||||
loading={buttonProps?.isLoading}
|
||||
loading={buttonProps?.isPending}
|
||||
onClick={(event) => {
|
||||
// Save cookie key to return url step
|
||||
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
|
||||
|
||||
@@ -31,7 +31,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
||||
return (
|
||||
<>
|
||||
{/* Already connected calendars */}
|
||||
{!queryConnectedCalendars.isLoading &&
|
||||
{!queryConnectedCalendars.isPending &&
|
||||
firstCalendar &&
|
||||
firstCalendar.integration &&
|
||||
firstCalendar.integration.title &&
|
||||
@@ -76,7 +76,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
||||
</List>
|
||||
)}
|
||||
|
||||
{queryIntegrations.isLoading && <StepConnectionLoader />}
|
||||
{queryIntegrations.isPending && <StepConnectionLoader />}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -13,7 +13,7 @@ interface ConnectedAppStepProps {
|
||||
|
||||
const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
||||
const { nextStep } = props;
|
||||
const { data: queryConnectedVideoApps, isLoading } = trpc.viewer.integrations.useQuery({
|
||||
const { data: queryConnectedVideoApps, isPending } = trpc.viewer.integrations.useQuery({
|
||||
variant: "conferencing",
|
||||
onlyInstalled: false,
|
||||
sortByMostPopular: true,
|
||||
@@ -26,7 +26,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
||||
|
||||
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">
|
||||
{queryConnectedVideoApps?.items &&
|
||||
queryConnectedVideoApps?.items.map((item) => {
|
||||
@@ -48,7 +48,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
||||
</List>
|
||||
)}
|
||||
|
||||
{isLoading && <StepConnectionLoader />}
|
||||
{isPending && <StepConnectionLoader />}
|
||||
<button
|
||||
type="button"
|
||||
data-testid="save-video-button"
|
||||
|
||||
@@ -113,7 +113,7 @@ const UserSettings = (props: IUserSettingsProps) => {
|
||||
<Button
|
||||
type="submit"
|
||||
className="mt-8 flex w-full flex-row justify-center"
|
||||
disabled={mutation.isLoading}>
|
||||
disabled={mutation.isPending}>
|
||||
{t("next_step_text")}
|
||||
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||
</Button>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Check } from "@calcom/ui/components/icon";
|
||||
const StepDone = (props: {
|
||||
currentStep: number;
|
||||
nextStepPath: string;
|
||||
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setIsPending: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useLocale();
|
||||
@@ -18,7 +18,7 @@ const StepDone = (props: {
|
||||
name={`wizard-step-${props.currentStep}`}
|
||||
className="flex justify-center space-y-4"
|
||||
onSubmit={(e) => {
|
||||
props.setIsLoading(true);
|
||||
props.setIsPending(true);
|
||||
e.preventDefault();
|
||||
router.replace(props.nextStepPath);
|
||||
}}>
|
||||
|
||||
@@ -293,7 +293,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
|
||||
<Button
|
||||
type="button"
|
||||
loading={updateUsername.isLoading}
|
||||
loading={updateUsername.isPending}
|
||||
data-testid="go-to-billing"
|
||||
href={paymentLink}>
|
||||
<>
|
||||
@@ -305,7 +305,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
{usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE && (
|
||||
<Button
|
||||
type="button"
|
||||
loading={updateUsername.isLoading}
|
||||
loading={updateUsername.isPending}
|
||||
data-testid="save-username"
|
||||
onClick={() => {
|
||||
saveUsername();
|
||||
|
||||
@@ -177,7 +177,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
|
||||
<DialogFooter className="mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
loading={updateUsernameMutation.isLoading}
|
||||
loading={updateUsernameMutation.isPending}
|
||||
data-testid="save-username"
|
||||
onClick={updateUsername}>
|
||||
{t("save")}
|
||||
|
||||
@@ -2,8 +2,7 @@ import type { InputHTMLAttributes } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
import InfoBadge from "@components/ui/InfoBadge";
|
||||
import { InfoBadge } from "@calcom/ui";
|
||||
|
||||
type Props = InputHTMLAttributes<HTMLInputElement> & {
|
||||
label?: React.ReactNode;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const IS_VISUAL_REGRESSION_TESTING = Boolean(globalThis.window?.Meticulous?.isRunningAsTest);
|
||||
@@ -1,21 +1,15 @@
|
||||
import type {
|
||||
QueryObserverLoadingErrorResult,
|
||||
QueryObserverLoadingResult,
|
||||
QueryObserverPendingResult,
|
||||
QueryObserverRefetchErrorResult,
|
||||
QueryObserverSuccessResult,
|
||||
QueryObserverLoadingErrorResult,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
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 type { UseTRPCQueryOptions } from "@trpc/react-query/shared";
|
||||
|
||||
type ErrorLike = {
|
||||
message: string;
|
||||
};
|
||||
@@ -27,7 +21,7 @@ interface QueryCellOptionsBase<TData, TError extends ErrorLike> {
|
||||
error?: (
|
||||
query: QueryObserverLoadingErrorResult<TData, TError> | QueryObserverRefetchErrorResult<TData, TError>
|
||||
) => JSXElementOrNull;
|
||||
loading?: (query: QueryObserverLoadingResult<TData, TError> | null) => JSXElementOrNull;
|
||||
loading?: (query: QueryObserverPendingResult<TData, TError> | null) => JSXElementOrNull;
|
||||
}
|
||||
|
||||
interface QueryCellOptionsNoEmpty<TData, TError extends ErrorLike>
|
||||
@@ -58,8 +52,11 @@ export function QueryCell<TData, TError extends ErrorLike>(
|
||||
const { isLocaleReady } = useLocale();
|
||||
const StatusLoader = opts.customLoader || <Loader />; // Fixes edge case where this can return null form query cell
|
||||
|
||||
if (query.status === "loading" || !isLocaleReady) {
|
||||
return opts.loading?.(query.status === "loading" ? query : null) ?? StatusLoader;
|
||||
if (!isLocaleReady) {
|
||||
return opts.loading?.(query.status === "pending" ? query : null) ?? StatusLoader;
|
||||
}
|
||||
if (query.status === "pending") {
|
||||
return opts.loading?.(query) ?? StatusLoader;
|
||||
}
|
||||
|
||||
if (query.status === "success") {
|
||||
@@ -81,28 +78,3 @@ export function QueryCell<TData, TError extends ErrorLike>(
|
||||
// impossible state
|
||||
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 };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.7.4",
|
||||
"version": "3.7.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -60,7 +60,7 @@
|
||||
"@sentry/nextjs": "^7.73.0",
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"@tanstack/react-query": "^4.3.9",
|
||||
"@tanstack/react-query": "^5.17.15",
|
||||
"@tremor/react": "^2.0.0",
|
||||
"@types/turndown": "^5.0.1",
|
||||
"@upstash/ratelimit": "^0.4.3",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { OAuthTokenPayload } from "pages/api/auth/oauth/token";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
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) {
|
||||
if (req.method !== "POST") {
|
||||
|
||||
@@ -3,14 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler";
|
||||
|
||||
export type OAuthTokenPayload = {
|
||||
userId?: number | null;
|
||||
teamId?: number | null;
|
||||
token_type: string;
|
||||
scope: string[];
|
||||
clientId: string;
|
||||
};
|
||||
import type { OAuthTokenPayload } from "@calcom/types/oauth";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
|
||||
@@ -5,7 +5,7 @@ import classNames from "classnames";
|
||||
import { signIn } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { FaGoogle } from "react-icons/fa";
|
||||
import { z } from "zod";
|
||||
@@ -157,15 +157,20 @@ inferSSRProps<typeof getServerSideProps> & WithNonceProps<{}>) {
|
||||
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
|
||||
};
|
||||
|
||||
const { data, isLoading } = trpc.viewer.public.ssoConnections.useQuery(undefined, {
|
||||
onError: (err) => {
|
||||
setErrorMessage(err.message);
|
||||
const { data, isPending, error } = trpc.viewer.public.ssoConnections.useQuery();
|
||||
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
if (error) {
|
||||
setErrorMessage(error.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
[error]
|
||||
);
|
||||
|
||||
const displaySSOLogin = HOSTED_CAL_FEATURES
|
||||
? true
|
||||
: isSAMLLoginEnabled && !isLoading && data?.connectionExists;
|
||||
: isSAMLLoginEnabled && !isPending && data?.connectionExists;
|
||||
|
||||
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]">
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Authorize() {
|
||||
const [selectedAccount, setSelectedAccount] = useState<{ value: string; label: string } | null>();
|
||||
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,
|
||||
},
|
||||
@@ -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({
|
||||
onSuccess: (data) => {
|
||||
@@ -58,7 +58,7 @@ export default function Authorize() {
|
||||
if (mappedProfiles.length > 0) {
|
||||
setSelectedAccount(mappedProfiles[0]);
|
||||
}
|
||||
}, [isLoadingProfiles]);
|
||||
}, [isPendingProfiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "unauthenticated") {
|
||||
@@ -69,9 +69,9 @@ export default function Authorize() {
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const isLoading = isLoadingGetClient || isLoadingProfiles || status !== "authenticated";
|
||||
const isPending = isPendingGetClient || isPendingProfiles || status !== "authenticated";
|
||||
|
||||
if (isLoading) {
|
||||
if (isPending) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
"use client";
|
||||
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
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 { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
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 { Meta, WizardForm } from "@calcom/ui";
|
||||
|
||||
@@ -18,7 +15,7 @@ import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser";
|
||||
import ChooseLicense from "@components/setup/ChooseLicense";
|
||||
import EnterpriseLicense from "@components/setup/EnterpriseLicense";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
import { getServerSideProps } from "@server/lib/setup/getServerSideProps";
|
||||
|
||||
function useSetStep() {
|
||||
const router = useRouter();
|
||||
@@ -44,16 +41,16 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
{
|
||||
title: t("administrator_user"),
|
||||
description: t("lets_create_first_administrator_user"),
|
||||
content: (setIsLoading) => (
|
||||
content: (setIsPending) => (
|
||||
<AdminUser
|
||||
onSubmit={() => {
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setStep(2);
|
||||
}}
|
||||
onError={() => {
|
||||
setIsLoading(false);
|
||||
setIsPending(false);
|
||||
}}
|
||||
userCount={props.userCount}
|
||||
/>
|
||||
@@ -62,7 +59,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
{
|
||||
title: t("choose_a_license"),
|
||||
description: t("choose_license_description"),
|
||||
content: (setIsLoading) => {
|
||||
content: (setIsPending) => {
|
||||
return (
|
||||
<ChooseLicense
|
||||
id="wizard-step-2"
|
||||
@@ -70,7 +67,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
onSubmit={() => {
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
setStep(3);
|
||||
}}
|
||||
/>
|
||||
@@ -83,14 +80,14 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
steps.push({
|
||||
title: t("step_enterprise_license"),
|
||||
description: t("step_enterprise_license_description"),
|
||||
content: (setIsLoading) => {
|
||||
content: (setIsPending) => {
|
||||
const currentStep = 3;
|
||||
return (
|
||||
<EnterpriseLicense
|
||||
id={`wizard-step-${currentStep}`}
|
||||
name={`wizard-step-${currentStep}`}
|
||||
onSubmit={() => {
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
setStep(currentStep + 1);
|
||||
@@ -109,7 +106,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
title: t("enable_apps"),
|
||||
description: t("enable_apps_description", { appName: APP_NAME }),
|
||||
contentClassname: "!pb-0 mb-[-1px]",
|
||||
content: (setIsLoading) => {
|
||||
content: (setIsPending) => {
|
||||
const currentStep = isFreeLicense ? 3 : 4;
|
||||
return (
|
||||
<AdminAppsList
|
||||
@@ -123,7 +120,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
||||
baseURL={`/auth/setup?step=${currentStep}`}
|
||||
useQueryParam={true}
|
||||
onSubmit={() => {
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
router.replace("/");
|
||||
}}
|
||||
/>
|
||||
@@ -152,50 +149,4 @@ Setup.isThemeSupported = false;
|
||||
Setup.PageWrapper = PageWrapper;
|
||||
export default Setup;
|
||||
|
||||
export const getServerSideProps = async (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,
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -44,7 +44,7 @@ function VerifyEmailPage() {
|
||||
<Button
|
||||
color="minimal"
|
||||
className="underline"
|
||||
loading={mutation.isLoading}
|
||||
loading={mutation.isPending}
|
||||
onClick={() => {
|
||||
showToast("Send email", "success");
|
||||
mutation.mutate();
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function Availability() {
|
||||
const fromEventType = searchParams?.get("fromEventType");
|
||||
const { timeFormat } = me.data || { timeFormat: null };
|
||||
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 },
|
||||
{
|
||||
enabled: !!scheduleId,
|
||||
@@ -158,7 +158,7 @@ export default function Availability() {
|
||||
control={form.control}
|
||||
name="name"
|
||||
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>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
disabled={isLoading || schedule?.isDefault}
|
||||
disabled={isPending || schedule?.isDefault}
|
||||
checked={form.watch("isDefault")}
|
||||
onCheckedChange={(e) => {
|
||||
form.setValue("isDefault", e);
|
||||
@@ -210,7 +210,7 @@ export default function Availability() {
|
||||
/>
|
||||
</DialogTrigger>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={deleteMutation.isLoading}
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t("delete_schedule")}
|
||||
confirmBtnText={t("delete")}
|
||||
@@ -249,7 +249,7 @@ export default function Availability() {
|
||||
/>
|
||||
</DialogTrigger>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={deleteMutation.isLoading}
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t("delete_schedule")}
|
||||
confirmBtnText={t("delete")}
|
||||
@@ -284,7 +284,7 @@ export default function Availability() {
|
||||
</Skeleton>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
disabled={isLoading || schedule?.isDefault}
|
||||
disabled={isPending || schedule?.isDefault}
|
||||
checked={form.watch("isDefault")}
|
||||
onCheckedChange={(e) => {
|
||||
form.setValue("isDefault", e);
|
||||
@@ -334,7 +334,7 @@ export default function Availability() {
|
||||
className="ml-4 lg:ml-0"
|
||||
type="submit"
|
||||
form="availability-form"
|
||||
loading={updateMutation.isLoading}>
|
||||
loading={updateMutation.isPending}>
|
||||
{t("save")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -15,7 +15,7 @@ import { trpc } from "@calcom/trpc/react";
|
||||
import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui";
|
||||
import { Clock } from "@calcom/ui/components/icon";
|
||||
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
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
|
||||
const WithQuery = withQuery(trpc.viewer.availability.list as any);
|
||||
function AvailabilityListWithQuery() {
|
||||
const query = trpc.viewer.availability.list.useQuery();
|
||||
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => <AvailabilityList {...data} />}
|
||||
customLoader={<SkeletonLoader />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AvailabilityPage() {
|
||||
const { t } = useLocale();
|
||||
@@ -179,14 +188,7 @@ export default function AvailabilityPage() {
|
||||
<NewScheduleButton />
|
||||
</div>
|
||||
}>
|
||||
{searchParams?.get("type") === "team" ? (
|
||||
<AvailabilitySliderTable />
|
||||
) : (
|
||||
<WithQuery
|
||||
success={({ data }) => <AvailabilityList {...data} />}
|
||||
customLoader={<SkeletonLoader />}
|
||||
/>
|
||||
)}
|
||||
{searchParams?.get("type") === "team" ? <AvailabilitySliderTable /> : <AvailabilityListWithQuery />}
|
||||
</ShellMain>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -164,7 +164,7 @@ export default function Bookings() {
|
||||
{query.status === "error" && (
|
||||
<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 && (
|
||||
<>
|
||||
{!!bookingsToday.length && status === "upcoming" && (
|
||||
|
||||
@@ -68,7 +68,7 @@ function ConnectAndJoin() {
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
loading={mutation.isLoading}
|
||||
loading={mutation.isPending}
|
||||
tooltip={isUserPartOfOrg ? t("join_meeting") : t("not_part_of_org")}
|
||||
disabled={!isUserPartOfOrg}
|
||||
onClick={() => {
|
||||
|
||||
@@ -80,6 +80,7 @@ const EventWebhooksTab = dynamic(() =>
|
||||
const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog"));
|
||||
|
||||
export type FormValues = {
|
||||
id: number;
|
||||
title: string;
|
||||
eventTitle: string;
|
||||
eventName: string;
|
||||
@@ -119,6 +120,7 @@ export type FormValues = {
|
||||
seatsShowAttendees: boolean | null;
|
||||
seatsShowAvailabilityCount: boolean | null;
|
||||
seatsPerTimeSlotEnabled: boolean;
|
||||
scheduleName: string;
|
||||
minimumBookingNotice: number;
|
||||
minimumBookingNoticeInDurationType: number;
|
||||
beforeBufferTime: number;
|
||||
@@ -139,6 +141,7 @@ export type FormValues = {
|
||||
availability?: AvailabilityOption;
|
||||
bookerLayouts: BookerLayoutSettings;
|
||||
multipleDurationEnabled: boolean;
|
||||
users: EventTypeSetup["users"];
|
||||
};
|
||||
|
||||
export type CustomInputParsed = typeof customInputSchema._output;
|
||||
@@ -180,7 +183,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
|
||||
const { eventType, locationOptions, team, teamMembers, currentUserMembership, destinationCalendar } = props;
|
||||
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
formMethods.setValue(
|
||||
@@ -248,6 +250,18 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
const defaultValues: any = useMemo(() => {
|
||||
return {
|
||||
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 || [],
|
||||
recurringEvent: eventType.recurringEvent || null,
|
||||
isInstantEvent: eventType.isInstantEvent,
|
||||
@@ -268,9 +282,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
periodType: eventType.periodType,
|
||||
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
|
||||
schedulingType: eventType.schedulingType,
|
||||
requiresConfirmation: eventType.requiresConfirmation,
|
||||
slotInterval: eventType.slotInterval,
|
||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||
metadata,
|
||||
hosts: eventType.hosts,
|
||||
successRedirectUrl: eventType.successRedirectUrl || "",
|
||||
users: eventType.users,
|
||||
children: eventType.children.map((ch) => ({
|
||||
...ch,
|
||||
created: true,
|
||||
@@ -411,7 +429,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
),
|
||||
availability: <EventAvailabilityTab eventType={eventType} isTeamEvent={!!team} />,
|
||||
team: <EventTeamTab teamMembers={teamMembers} team={team} eventType={eventType} />,
|
||||
limits: <EventLimitsTab eventType={eventType} />,
|
||||
limits: <EventLimitsTab />,
|
||||
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
|
||||
instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />,
|
||||
recurring: <EventRecurringTab eventType={eventType} />,
|
||||
@@ -490,7 +508,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { availability, ...rest } = input;
|
||||
const { availability, users, scheduleName, ...rest } = input;
|
||||
updateMutation.mutate({
|
||||
...rest,
|
||||
length,
|
||||
@@ -550,9 +568,10 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
installedAppsNumber={eventTypeApps?.items.length || 0}
|
||||
enabledWorkflowsNumber={eventType.workflows.length}
|
||||
eventType={eventType}
|
||||
activeWebhooksNumber={eventType.webhooks.filter((webhook) => webhook.active).length}
|
||||
team={team}
|
||||
availability={availability}
|
||||
isUpdateMutationLoading={updateMutation.isLoading}
|
||||
isUpdateMutationLoading={updateMutation.isPending}
|
||||
formMethods={formMethods}
|
||||
// disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
|
||||
disableBorder={true}
|
||||
@@ -618,7 +637,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
throw new Error(t("event_setup_multiple_payment_apps_error"));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { availability, ...rest } = input;
|
||||
const { availability, users, scheduleName, ...rest } = input;
|
||||
updateMutation.mutate({
|
||||
...rest,
|
||||
length,
|
||||
@@ -647,7 +666,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
{slugExistsChildrenDialogOpen.length ? (
|
||||
<ManagedEventTypeDialog
|
||||
slugExistsChildrenDialogOpen={slugExistsChildrenDialogOpen}
|
||||
isLoading={formMethods.formState.isSubmitting}
|
||||
isPending={formMethods.formState.isSubmitting}
|
||||
onOpenChange={() => {
|
||||
setSlugExistsChildrenDialogOpen([]);
|
||||
}}
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
Switch,
|
||||
Tooltip,
|
||||
ArrowButton,
|
||||
UserAvatarGroup,
|
||||
} from "@calcom/ui";
|
||||
import {
|
||||
Clipboard,
|
||||
@@ -74,7 +75,6 @@ import useMeQuery from "@lib/hooks/useMeQuery";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
||||
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
|
||||
|
||||
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
|
||||
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
|
||||
@@ -686,7 +686,7 @@ export const EventTypeList = ({
|
||||
title={t(`delete${isManagedEventPrefix()}_event_type`)}
|
||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||
loadingText={t(`confirm_delete_event_type`)}
|
||||
isLoading={deleteMutation.isLoading}
|
||||
isPending={deleteMutation.isPending}
|
||||
onConfirm={(e) => {
|
||||
e.preventDefault();
|
||||
deleteEventTypeHandler(deleteDialogTypeId);
|
||||
@@ -856,7 +856,7 @@ const Main = ({
|
||||
const searchParams = useCompatSearchParams();
|
||||
const orgBranding = useOrgBranding();
|
||||
|
||||
if (!data || status === "loading") {
|
||||
if (!data || status === "pending") {
|
||||
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
|
||||
const { data, status, error } = trpc.viewer.eventTypes.getByViewer.useQuery(filters && { filters }, {
|
||||
refetchOnWindowFocus: false,
|
||||
cacheTime: 1 * 60 * 60 * 1000,
|
||||
gcTime: 1 * 60 * 60 * 1000,
|
||||
staleTime: 1 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
const ApiKeysView = () => {
|
||||
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 [apiKeyToEdit, setApiKeyToEdit] = useState<(TApiKeys & { neverExpires?: boolean }) | undefined>(
|
||||
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading || !data) {
|
||||
if (isPending || !data) {
|
||||
return (
|
||||
<SkeletonLoader
|
||||
title={t("api_keys")}
|
||||
|
||||
@@ -317,7 +317,7 @@ const AppearanceView = ({
|
||||
<SettingsToggle
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("disable_cal_branding", { appName: APP_NAME })}
|
||||
disabled={!hasPaidPlan || mutation?.isLoading}
|
||||
disabled={!hasPaidPlan || mutation?.isPending}
|
||||
description={t("removes_cal_branding", { appName: APP_NAME })}
|
||||
checked={hasPaidPlan ? hideBrandingValue : false}
|
||||
Badge={<UpgradeTeamsBadge />}
|
||||
@@ -332,12 +332,12 @@ const AppearanceView = ({
|
||||
};
|
||||
|
||||
const AppearanceViewWrapper = () => {
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
|
||||
const { data: user, isPending } = trpc.viewer.me.useQuery();
|
||||
const { isPending: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
|
||||
|
||||
const { t } = useLocale();
|
||||
|
||||
if (isLoading || isTeamPlanStatusLoading || !user)
|
||||
if (isPending || isTeamPlanStatusLoading || !user)
|
||||
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
||||
|
||||
return <AppearanceView user={user} hasPaidPlan={hasPaidPlan} />;
|
||||
|
||||
@@ -51,8 +51,8 @@ interface GeneralViewProps {
|
||||
const GeneralQueryView = () => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
if (isLoading) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
|
||||
const { data: user, isPending } = trpc.viewer.me.useQuery();
|
||||
if (isPending) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
|
||||
if (!user) {
|
||||
throw new Error(t("something_went_wrong"));
|
||||
}
|
||||
@@ -233,7 +233,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("dynamic_booking")}
|
||||
description={t("allow_dynamic_booking")}
|
||||
disabled={mutation.isLoading}
|
||||
disabled={mutation.isPending}
|
||||
checked={isAllowDynamicBookingChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsAllowDynamicBookingChecked(checked);
|
||||
@@ -246,7 +246,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("seo_indexing")}
|
||||
description={t("allow_seo_indexing")}
|
||||
disabled={mutation.isLoading}
|
||||
disabled={mutation.isPending}
|
||||
checked={isAllowSEOIndexingChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsAllowSEOIndexingChecked(checked);
|
||||
@@ -259,7 +259,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("monthly_digest_email")}
|
||||
description={t("monthly_digest_email_for_teams")}
|
||||
disabled={mutation.isLoading}
|
||||
disabled={mutation.isPending}
|
||||
checked={isReceiveMonthlyDigestEmailChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsReceiveMonthlyDigestEmailChecked(checked);
|
||||
|
||||
@@ -155,7 +155,7 @@ const OutOfOfficeSection = () => {
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={createOutOfOfficeEntry.isLoading}
|
||||
disabled={createOutOfOfficeEntry.isPending}
|
||||
data-testid="create-entry-ooo-redirect">
|
||||
{t("create_entry")}
|
||||
</Button>
|
||||
@@ -168,7 +168,7 @@ const OutOfOfficeSection = () => {
|
||||
const OutOfOfficeEntriesList = () => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const { data, isLoading } = trpc.viewer.outOfOfficeEntriesList.useQuery();
|
||||
const { data, isPending } = trpc.viewer.outOfOfficeEntriesList.useQuery();
|
||||
const deleteOutOfOfficeEntryMutation = trpc.viewer.outOfOfficeEntryDelete.useMutation({
|
||||
onSuccess: () => {
|
||||
showToast(t("success_deleted_entry_out_of_office"), "success");
|
||||
@@ -207,7 +207,7 @@ const OutOfOfficeEntriesList = () => {
|
||||
type="button"
|
||||
color="minimal"
|
||||
variant="icon"
|
||||
disabled={deleteOutOfOfficeEntryMutation.isLoading}
|
||||
disabled={deleteOutOfOfficeEntryMutation.isPending}
|
||||
StartIcon={Trash2}
|
||||
onClick={() => {
|
||||
deleteOutOfOfficeEntryMutation.mutate({ outOfOfficeUid: item.uuid });
|
||||
@@ -216,7 +216,7 @@ const OutOfOfficeEntriesList = () => {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{isLoading && (
|
||||
{isPending && (
|
||||
<TableRow>
|
||||
{new Array(6).fill(0).map((_, index) => (
|
||||
<TableCell key={index}>
|
||||
@@ -226,7 +226,7 @@ const OutOfOfficeEntriesList = () => {
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
{!isLoading && (data === undefined || data.length === 0) && (
|
||||
{!isPending && (data === undefined || data.length === 0) && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center">
|
||||
<p className="text-subtle text-sm">{t("no_redirects_found")}</p>
|
||||
|
||||
@@ -84,10 +84,10 @@ const ProfileView = () => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
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, {
|
||||
enabled: !isLoading && !user?.avatarUrl,
|
||||
enabled: !isPending && !user?.avatarUrl,
|
||||
});
|
||||
|
||||
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
|
||||
@@ -222,7 +222,7 @@ const ProfileView = () => {
|
||||
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
|
||||
};
|
||||
|
||||
if (isLoading || !user) {
|
||||
if (isPending || !user) {
|
||||
return (
|
||||
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
|
||||
);
|
||||
@@ -246,7 +246,7 @@ const ProfileView = () => {
|
||||
<ProfileForm
|
||||
key={JSON.stringify(defaultValues)}
|
||||
defaultValues={defaultValues}
|
||||
isLoading={updateProfileMutation.isLoading}
|
||||
isPending={updateProfileMutation.isPending}
|
||||
isFallbackImg={!user.avatarUrl && !avatarData?.avatar}
|
||||
user={user}
|
||||
userOrganization={user.organization}
|
||||
@@ -354,7 +354,7 @@ const ProfileView = () => {
|
||||
<DialogFooter showDivider>
|
||||
<Button
|
||||
color="primary"
|
||||
loading={confirmPasswordMutation.isLoading}
|
||||
loading={confirmPasswordMutation.isPending}
|
||||
onClick={(e) => onConfirmPassword(e)}>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
@@ -375,7 +375,7 @@ const ProfileView = () => {
|
||||
<DialogFooter>
|
||||
<Button
|
||||
color="primary"
|
||||
loading={updateProfileMutation.isLoading}
|
||||
loading={updateProfileMutation.isPending}
|
||||
onClick={(e) => onConfirmAuthEmailChange(e)}>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
@@ -391,7 +391,7 @@ const ProfileForm = ({
|
||||
defaultValues,
|
||||
onSubmit,
|
||||
extraField,
|
||||
isLoading = false,
|
||||
isPending = false,
|
||||
isFallbackImg,
|
||||
user,
|
||||
userOrganization,
|
||||
@@ -399,7 +399,7 @@ const ProfileForm = ({
|
||||
defaultValues: FormValues;
|
||||
onSubmit: (values: FormValues) => void;
|
||||
extraField?: React.ReactNode;
|
||||
isLoading: boolean;
|
||||
isPending: boolean;
|
||||
isFallbackImg: boolean;
|
||||
user: RouterOutputs["viewer"]["me"];
|
||||
userOrganization: RouterOutputs["viewer"]["me"]["organization"];
|
||||
@@ -509,7 +509,7 @@ const ProfileForm = ({
|
||||
</div>
|
||||
</div>
|
||||
<SectionBottomActions align="end">
|
||||
<Button loading={isLoading} disabled={isDisabled} color="primary" type="submit">
|
||||
<Button loading={isPending} disabled={isDisabled} color="primary" type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
|
||||
@@ -69,7 +69,7 @@ const ProfileImpersonationView = ({ user }: { user: RouterOutputs["viewer"]["me"
|
||||
mutation.mutate({ disableImpersonation: !checked });
|
||||
}}
|
||||
switchContainerClassName="rounded-t-none border-t-0"
|
||||
disabled={mutation.isLoading}
|
||||
disabled={mutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
@@ -77,10 +77,10 @@ const ProfileImpersonationView = ({ user }: { user: RouterOutputs["viewer"]["me"
|
||||
};
|
||||
|
||||
const ProfileImpersonationViewWrapper = () => {
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { data: user, isPending } = trpc.viewer.me.useQuery();
|
||||
const { t } = useLocale();
|
||||
|
||||
if (isLoading || !user)
|
||||
if (isPending || !user)
|
||||
return <SkeletonLoader title={t("impersonation")} description={t("impersonation_description")} />;
|
||||
|
||||
return <ProfileImpersonationView user={user} />;
|
||||
|
||||
@@ -215,9 +215,9 @@ const PasswordView = ({ user }: PasswordViewProps) => {
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
loading={passwordMutation.isLoading}
|
||||
loading={passwordMutation.isPending}
|
||||
onClick={() => formMethods.clearErrors("apiError")}
|
||||
disabled={isDisabled || passwordMutation.isLoading || sessionMutation.isLoading}>
|
||||
disabled={isDisabled || passwordMutation.isPending || sessionMutation.isPending}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
@@ -268,7 +268,7 @@ const PasswordView = ({ user }: PasswordViewProps) => {
|
||||
<SectionBottomActions align="end">
|
||||
<Button
|
||||
color="primary"
|
||||
loading={sessionMutation.isLoading}
|
||||
loading={sessionMutation.isPending}
|
||||
onClick={() => {
|
||||
sessionMutation.mutate({
|
||||
metadata: { ...metadata, sessionTimeout },
|
||||
@@ -277,8 +277,8 @@ const PasswordView = ({ user }: PasswordViewProps) => {
|
||||
}}
|
||||
disabled={
|
||||
initialSessionTimeout === sessionTimeout ||
|
||||
passwordMutation.isLoading ||
|
||||
sessionMutation.isLoading
|
||||
passwordMutation.isPending ||
|
||||
sessionMutation.isPending
|
||||
}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
@@ -293,9 +293,9 @@ const PasswordView = ({ user }: PasswordViewProps) => {
|
||||
};
|
||||
|
||||
const PasswordViewWrapper = () => {
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const { data: user, isPending } = trpc.viewer.me.useQuery();
|
||||
const { t } = useLocale();
|
||||
if (isLoading || !user)
|
||||
if (isPending || !user)
|
||||
return <SkeletonLoader title={t("password")} description={t("password_description")} />;
|
||||
|
||||
return <PasswordView user={user} />;
|
||||
|
||||
@@ -37,12 +37,12 @@ const TwoFactorAuthView = () => {
|
||||
const utils = trpc.useContext();
|
||||
|
||||
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 [disableModalOpen, setDisableModalOpen] = useState<boolean>(false);
|
||||
|
||||
if (isLoading)
|
||||
if (isPending)
|
||||
return <SkeletonLoader title={t("2fa")} description={t("set_up_two_factor_authentication")} />;
|
||||
|
||||
const isCalProvider = user?.identityProvider === "CAL";
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
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 { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||
@@ -28,7 +28,6 @@ import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import Team from "@components/team/screens/Team";
|
||||
import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup";
|
||||
|
||||
export { getServerSideProps };
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
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";
|
||||
|
||||
|
||||
@@ -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.",
|
||||
"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.",
|
||||
"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_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.",
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
"welcome_to_calcom": "ברוך הבא אל {{appName}}",
|
||||
"welcome_instructions": "אמור לנו באיזה שם ברצונך שנקרא לך ובאיזה אזור זמן אתה נמצא. ניתן יהיה לערוך את הפרטים האלה מאוחר יותר.",
|
||||
"connect_caldav": "חיבור לשרת CalDav (בטא)",
|
||||
"connect_ics_feed": "חיבור לערוץ ICS",
|
||||
"connect": "להתחבר",
|
||||
"try_for_free": "לנסות בחינם",
|
||||
"create_booking_link_with_calcom": "צור קישור תזמון משלך עם {{appName}}",
|
||||
@@ -555,6 +556,7 @@
|
||||
"enter_number_between_range": "נא להזין מספר בין 1 ל-{{maxOccurences}}",
|
||||
"email_address": "כתובת דוא\"ל",
|
||||
"enter_valid_email": "יש להזין כתובת דוא״ל תקינה",
|
||||
"please_schedule_future_call": " נא לתזמן שיחה עתידית אם איננו זמינים תוך {{seconds}} שניות",
|
||||
"location": "מיקום",
|
||||
"address": "כתובת",
|
||||
"enter_address": "יש להזין כתובת",
|
||||
@@ -614,6 +616,7 @@
|
||||
"new_member": "חבר/ה חדש/ה",
|
||||
"invite": "להזמין",
|
||||
"add_team_members": "הוספת חברי צוות",
|
||||
"add_org_members": "הוספת חברים",
|
||||
"add_team_members_description": "להזמין עוד אנשים להצטרף לצוות",
|
||||
"add_team_member": "הוספת חבר צוות",
|
||||
"invite_new_member": "להזמין חבר/ת צוות חדש/ה",
|
||||
@@ -1978,6 +1981,7 @@
|
||||
"connect_google_workspace": "חיבור Google Workspace",
|
||||
"google_workspace_admin_tooltip": "עליך/ייך להיות אדמין/ית ב-Workspace כדי להשתמש בתכונה הזו",
|
||||
"first_event_type_webhook_description": "צור/צרי את ה-Webhook הראשון שלך עבור סוג האירוע הזה",
|
||||
"create_instant_meeting_webhook_description": "אפשר ליצור את ההתליה הראשונה שלך עם ‚נוצרה פגישה מיידית’ כהזנקה לסוג אירוע זה",
|
||||
"install_app_on": "התקנת האפליקציה ב-",
|
||||
"create_for": "צור/צרי עבור",
|
||||
"currency": "מטבע",
|
||||
@@ -2193,6 +2197,7 @@
|
||||
"uprade_to_create_instant_bookings": "ניתן לשדג לרישיון התאגידי ולאפשר למשתמשים להצטרף לשיחה מיידית שמשתתפים יכולים לקפוץ ישירות אליה. זה מיועד רק לסוגי אירועים של צוותים",
|
||||
"dont_want_to_wait": "לא רוצה להמתין?",
|
||||
"meeting_started": "הפגישה החלה",
|
||||
"pay_and_book": "נא לשלם כדי להזמין",
|
||||
"booking_not_found_error": "לא ניתן למצוא הזמנה",
|
||||
"booking_seats_full_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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import superjson from "superjson";
|
||||
|
||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
||||
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";
|
||||
|
||||
// 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 ssg = createProxySSGHelpers({
|
||||
const ssg = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
transformer: superjson,
|
||||
ctx: {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { forms } from "@calcom/app-store/routing-forms/trpc/procedures/forms";
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { map } from "@calcom/features/flags/server/procedures/map";
|
||||
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 { me } from "@calcom/trpc/server/routers/loggedInViewer/procedures/me";
|
||||
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 i18n = await serverSideTranslations(locale, ["common", "vital"]);
|
||||
|
||||
const ssr = createProxySSGHelpers({
|
||||
const ssr = createServerSideHelpers({
|
||||
router: routerSlice,
|
||||
transformer: superjson,
|
||||
ctx: { ...ctx, locale, i18n },
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function OmniInstallAppButton({
|
||||
|
||||
return (
|
||||
<Button
|
||||
loading={mutation.isLoading}
|
||||
loading={mutation.isPending}
|
||||
color="secondary"
|
||||
className="[@media(max-width:260px)]:w-full [@media(max-width:260px)]:justify-center"
|
||||
StartIcon={Plus}
|
||||
|
||||
@@ -32,64 +32,67 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
|
||||
AddAppMutationData,
|
||||
Error,
|
||||
{ type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean; teamId?: number } | ""
|
||||
>(async (variables) => {
|
||||
let type: string | null | undefined;
|
||||
let isOmniInstall;
|
||||
const teamId = variables && variables.teamId ? variables.teamId : undefined;
|
||||
if (variables === "") {
|
||||
type = _type;
|
||||
} else {
|
||||
isOmniInstall = variables.isOmniInstall;
|
||||
type = variables.type;
|
||||
}
|
||||
if (type?.endsWith("_other_calendar")) {
|
||||
type = type.split("_other_calendar")[0];
|
||||
}
|
||||
>({
|
||||
...options,
|
||||
mutationFn: async (variables) => {
|
||||
let type: string | null | undefined;
|
||||
let isOmniInstall;
|
||||
const teamId = variables && variables.teamId ? variables.teamId : undefined;
|
||||
if (variables === "") {
|
||||
type = _type;
|
||||
} else {
|
||||
isOmniInstall = variables.isOmniInstall;
|
||||
type = variables.type;
|
||||
}
|
||||
if (type?.endsWith("_other_calendar")) {
|
||||
type = type.split("_other_calendar")[0];
|
||||
}
|
||||
|
||||
if (options?.installGoogleVideo && type !== "google_calendar")
|
||||
throw new Error("Could not install Google Meet");
|
||||
if (options?.installGoogleVideo && type !== "google_calendar")
|
||||
throw new Error("Could not install Google Meet");
|
||||
|
||||
const state: IntegrationOAuthCallbackState = {
|
||||
returnTo:
|
||||
returnTo ||
|
||||
WEBAPP_URL +
|
||||
getInstalledAppPath(
|
||||
{ variant: variables && variables.variant, slug: variables && variables.slug },
|
||||
location.search
|
||||
),
|
||||
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
|
||||
...(teamId && { teamId }),
|
||||
};
|
||||
const state: IntegrationOAuthCallbackState = {
|
||||
returnTo:
|
||||
returnTo ||
|
||||
WEBAPP_URL +
|
||||
getInstalledAppPath(
|
||||
{ variant: variables && variables.variant, slug: variables && variables.slug },
|
||||
location.search
|
||||
),
|
||||
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
|
||||
...(teamId && { teamId }),
|
||||
};
|
||||
|
||||
const stateStr = encodeURIComponent(JSON.stringify(state));
|
||||
const searchParams = `?state=${stateStr}${teamId ? `&teamId=${teamId}` : ""}`;
|
||||
const stateStr = encodeURIComponent(JSON.stringify(state));
|
||||
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) {
|
||||
const errorBody = await res.json();
|
||||
throw new Error(errorBody.message || "Something went wrong");
|
||||
}
|
||||
if (!res.ok) {
|
||||
const errorBody = await res.json();
|
||||
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") };
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ function AlbySetupPage(props: IAlbySetupProps) {
|
||||
});
|
||||
}, [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" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,22 +15,26 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
const [selectedProject, setSelectedProject] = useState<undefined | { label: string; value: string }>();
|
||||
const { data } = trpc.viewer.appBasecamp3.projects.useQuery();
|
||||
const setProject = trpc.viewer.appBasecamp3.projectMutation.useMutation();
|
||||
useEffect(() => {
|
||||
setSelectedProject({
|
||||
value: data?.projects.currentProject,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name,
|
||||
});
|
||||
setProjects(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?.projects?.map((project: any) => {
|
||||
return {
|
||||
value: project.id,
|
||||
label: project.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
setSelectedProject({
|
||||
value: data?.projects.currentProject,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name,
|
||||
});
|
||||
setProjects(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?.projects?.map((project: any) => {
|
||||
return {
|
||||
value: project.id,
|
||||
label: project.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppCard
|
||||
|
||||
@@ -34,7 +34,7 @@ export const InstallAppButtonWithoutPlanCheck = (
|
||||
onClick: () => {
|
||||
mutation.mutate({ type: props.type });
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
loading: mutation.isPending,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
@@ -56,10 +56,10 @@ export const InstallAppButton = (
|
||||
disableInstall?: boolean;
|
||||
} & InstallAppButtonProps
|
||||
) => {
|
||||
const { isLoading: isUserLoading, data: user } = trpc.viewer.me.useQuery();
|
||||
const { isPending: isUserLoading, data: user } = trpc.viewer.me.useQuery();
|
||||
const router = useRouter();
|
||||
const proProtectionElementRef = useRef<HTMLDivElement | null>(null);
|
||||
const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
const { isPending: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
useEffect(() => {
|
||||
const el = proProtectionElementRef.current;
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
---
|
||||
items:
|
||||
- 1.png
|
||||
- 1.jpeg
|
||||
- 2.png
|
||||
- 3.png
|
||||
- 4.png
|
||||
- 5.png
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
||||
### It’s time.
|
||||
|
||||
### Cron's most valuable features
|
||||
|
||||
<!-- From "FAQs: What are Cron’s most valuable features?"
|
||||
https://cronhq.notion.site/Cron-Calendar-5625be54feac4e13a75b10271b65ddb7 -->
|
||||
All of your commitments, now in one place. Meet the beautifully designed, fully integrated calendar for your work and life.
|
||||
|
||||
- **Availability sharing** — mark holds on the grid, share the slots, and schedule with ease.
|
||||
- **Team overlays** — quickly overlay teammates' calendars.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Cron",
|
||||
"name": "Notion Calendar",
|
||||
"slug": "cron",
|
||||
"type": "cron_other",
|
||||
"type": "cron_calendar",
|
||||
"logo": "logo.png",
|
||||
"url": "https://github.com/miguelnietoa",
|
||||
"variant": "other",
|
||||
"variant": "calendar",
|
||||
"categories": ["calendar"],
|
||||
"publisher": "Miguel Nieto",
|
||||
"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,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "link-as-an-app",
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Cron is the next-generation calendar for professionals and teams."
|
||||
"description": "All of your commitments, now in one place."
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 487 KiB After Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 1000 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 61 KiB |
@@ -11,7 +11,7 @@ export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||
onClick() {
|
||||
mutation.mutate("");
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
loading: mutation.isPending,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||
onClick() {
|
||||
mutation.mutate("");
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
loading: mutation.isPending,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,15 +23,15 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
const [nextOffset, setNextOffset] = useState<number>(0);
|
||||
const [keyword, setKeyword] = useState<string>("");
|
||||
const { isOpenDialog, setIsOpenDialog } = props;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [selectedMode, setSelectedMode] = useState<Mode>(MODE_SEARCH);
|
||||
|
||||
const searchGiphy = async (keyword: string, offset: number) => {
|
||||
if (isLoading) {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
setErrorMessage("");
|
||||
const res = await fetch("/api/integrations/giphy/search", {
|
||||
method: "POST",
|
||||
@@ -53,15 +53,15 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
setErrorMessage("No Result found");
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
setIsPending(false);
|
||||
return null;
|
||||
};
|
||||
|
||||
const getGiphyByUrl = async (url: string) => {
|
||||
if (isLoading) {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setIsPending(true);
|
||||
setErrorMessage("");
|
||||
const res = await fetch("/api/integrations/giphy/get", {
|
||||
method: "POST",
|
||||
@@ -81,7 +81,7 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
setErrorMessage("No Result found");
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
setIsPending(false);
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -139,14 +139,14 @@ export const SearchDialog = (props: ISearchDialog) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" tabIndex={-1} color="secondary" loading={isLoading}>
|
||||
<Button type="submit" tabIndex={-1} color="secondary" loading={isPending}>
|
||||
{t("search")}
|
||||
</Button>
|
||||
</form>
|
||||
{gifImage && (
|
||||
<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">
|
||||
{isLoading ? (
|
||||
{isPending ? (
|
||||
<div className="flex h-[200px] w-full items-center justify-center bg-gray-400 pb-3 pt-3">
|
||||
<svg
|
||||
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"
|
||||
color="secondary"
|
||||
type="button"
|
||||
loading={isLoading}
|
||||
loading={isPending}
|
||||
onClick={() => searchGiphy(keyword, nextOffset)}>
|
||||
Shuffle
|
||||
</Button>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function MakeSetup({ inviteLink }: InferGetServerSidePropsType<ty
|
||||
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" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ function NewFormDialog({ appUrl }: { appUrl: string }) {
|
||||
</div>
|
||||
<DialogFooter showDivider className="mt-12">
|
||||
<DialogClose />
|
||||
<Button loading={mutation.isLoading} data-testid="add-form" type="submit">
|
||||
<Button loading={mutation.isPending} data-testid="add-form" type="submit">
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -234,7 +234,7 @@ function Dialogs({
|
||||
<RoutingFormEmbedDialog />
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={deleteMutation.isLoading}
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t("delete_form")}
|
||||
confirmBtnText={t("delete_form_action")}
|
||||
@@ -264,12 +264,12 @@ const actionsCtx = createContext({
|
||||
_delete: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||
onAction: (_arg: { routingForm: RoutingForm | null }) => {},
|
||||
isLoading: false,
|
||||
isPending: false,
|
||||
},
|
||||
toggle: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||
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);
|
||||
setDeleteDialogFormId(routingForm.id);
|
||||
},
|
||||
isLoading: false,
|
||||
isPending: false,
|
||||
},
|
||||
toggle: {
|
||||
onAction: ({ routingForm, checked }) => {
|
||||
@@ -344,7 +344,7 @@ export function FormActionsProvider({ appUrl, children }: { appUrl: string; chil
|
||||
disabled: !checked,
|
||||
});
|
||||
},
|
||||
isLoading: toggleMutation.isLoading,
|
||||
isPending: toggleMutation.isPending,
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
@@ -446,7 +446,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
|
||||
},
|
||||
_delete: {
|
||||
onClick: () => _delete.onAction({ routingForm }),
|
||||
loading: _delete.isLoading,
|
||||
loading: _delete.isPending,
|
||||
},
|
||||
create: {
|
||||
onClick: () => openModal({ action: "new", target: "" }),
|
||||
@@ -479,7 +479,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
|
||||
</div>
|
||||
);
|
||||
},
|
||||
loading: toggle.isLoading,
|
||||
loading: toggle.isPending,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ const Actions = ({
|
||||
}: {
|
||||
form: RoutingFormWithResponseCount;
|
||||
mutation: {
|
||||
isLoading: boolean;
|
||||
isPending: boolean;
|
||||
};
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
@@ -218,7 +218,7 @@ const Actions = ({
|
||||
</FormActionsDropdown>
|
||||
</div>
|
||||
<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")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -518,7 +518,7 @@ function SingleForm({ form, appUrl, Page }: 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 },
|
||||
{
|
||||
initialData: _form,
|
||||
@@ -527,7 +527,7 @@ export default function SingleFormWrapper({ form: _form, ...props }: SingleFormC
|
||||
);
|
||||
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
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Co
|
||||
const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: JsonLogicQuery | null }) => {
|
||||
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(
|
||||
{
|
||||
formId: formId,
|
||||
@@ -57,7 +57,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
|
||||
|
||||
const headers = useRef<string[] | null>(null);
|
||||
|
||||
if (!isLoading && !data) {
|
||||
if (!isPending && !data) {
|
||||
return <div>Error loading report {error?.message} </div>;
|
||||
}
|
||||
headers.current = (data?.pages && data?.pages[0]?.headers) || headers.current;
|
||||
@@ -81,7 +81,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
{!isLoading &&
|
||||
{!isPending &&
|
||||
data?.pages.map((page) => {
|
||||
return page.responses?.map((responses, rowIndex) => {
|
||||
const isLastRow = page.responses.length - 1 === rowIndex;
|
||||
@@ -113,7 +113,7 @@ const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: Js
|
||||
});
|
||||
})}
|
||||
</table>
|
||||
{isLoading ? <div className="text-default p-2">{t("loading")}</div> : ""}
|
||||
{isPending ? <div className="text-default p-2">{t("loading")}</div> : ""}
|
||||
{hasNextPage && (
|
||||
<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">
|
||||
<Button
|
||||
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent"
|
||||
loading={responseMutation.isLoading}
|
||||
loading={responseMutation.isPending}
|
||||
type="submit"
|
||||
color="primary">
|
||||
{t("submit")}
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface InstallAppButtonProps {
|
||||
renderProps: ButtonProps & {
|
||||
/** Tells that the default render component should be used */
|
||||
useDefaultComponent?: boolean;
|
||||
isLoading?: boolean;
|
||||
isPending?: boolean;
|
||||
}
|
||||
) => JSX.Element;
|
||||
onChanged?: () => unknown;
|
||||
|
||||
@@ -44,7 +44,7 @@ const wipeMyCalAction = async (props: IWipeMyCalAction) => {
|
||||
export const ConfirmDialog = (props: IConfirmDialogWipe) => {
|
||||
const { t } = useLocale();
|
||||
const { isOpenDialog, setIsOpenDialog } = props;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const today = dayjs();
|
||||
const initialDate = today.startOf("day");
|
||||
const endDate = today.endOf("day");
|
||||
@@ -52,9 +52,9 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
|
||||
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const rescheduleApi = useMutation(
|
||||
async () => {
|
||||
setIsLoading(true);
|
||||
const rescheduleApi = useMutation({
|
||||
mutationFn: async () => {
|
||||
setIsPending(true);
|
||||
try {
|
||||
const result = await wipeMyCalAction({
|
||||
initialDate: initialDate.toISOString(),
|
||||
@@ -68,14 +68,12 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
|
||||
showToast(t("unexpected_error_try_again"), "error");
|
||||
// @TODO: notify
|
||||
}
|
||||
setIsLoading(false);
|
||||
setIsPending(false);
|
||||
},
|
||||
{
|
||||
async onSettled() {
|
||||
await utils.viewer.bookings.invalidate();
|
||||
},
|
||||
}
|
||||
);
|
||||
async onSettled() {
|
||||
await utils.viewer.bookings.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
||||
@@ -104,7 +102,7 @@ export const ConfirmDialog = (props: IConfirmDialogWipe) => {
|
||||
<Button
|
||||
color="primary"
|
||||
data-testid="send_request"
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
onClick={async () => {
|
||||
try {
|
||||
rescheduleApi.mutate();
|
||||
|
||||
@@ -13,7 +13,7 @@ interface IWipeMyCalActionButtonProps {
|
||||
const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => {
|
||||
const { bookingsEmpty, bookingStatus } = props;
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const { isSuccess, isLoading, data } = trpc.viewer.integrations.useQuery({
|
||||
const { isSuccess, isPending, data } = trpc.viewer.integrations.useQuery({
|
||||
variant: "other",
|
||||
onlyInstalled: undefined,
|
||||
});
|
||||
@@ -27,7 +27,7 @@ const WipeMyCalActionButton = (props: IWipeMyCalActionButtonProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{data && isSuccess && !isLoading && credentialId && (
|
||||
{data && isSuccess && !isPending && credentialId && (
|
||||
<div className="mb-4">
|
||||
<ConfirmDialog isOpenDialog={openDialog} setIsOpenDialog={setOpenDialog} />
|
||||
<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 });
|
||||
}
|
||||
|
||||
if (integrations.isLoading) {
|
||||
if (integrations.isPending) {
|
||||
return <div className="bg-emphasis absolute z-50 flex h-screen w-full items-center" />;
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ const AdminAppsListContainer = () => {
|
||||
const { t } = useLocale();
|
||||
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 },
|
||||
{ enabled: searchParams !== null }
|
||||
);
|
||||
@@ -291,7 +291,7 @@ const AdminAppsListContainer = () => {
|
||||
|
||||
const handleModelOpen = (data: EditModalState) => setModalState({ ...data });
|
||||
|
||||
if (isLoading) return <SkeletonLoader />;
|
||||
if (isPending) return <SkeletonLoader />;
|
||||
|
||||
if (!apps || apps.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
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[] = []) {
|
||||
const token = req.headers.authorization?.split(" ")[1] || "";
|
||||
|
||||
@@ -207,7 +207,7 @@ const BookerComponent = ({
|
||||
} = useCalendars();
|
||||
|
||||
useEffect(() => {
|
||||
if (event.isLoading) return setBookerState("loading");
|
||||
if (event.isPending) return setBookerState("loading");
|
||||
if (!selectedDate) return setBookerState("selecting_date");
|
||||
if (!selectedTimeslot) return setBookerState("selecting_time");
|
||||
return setBookerState("booking");
|
||||
@@ -399,7 +399,7 @@ const BookerComponent = ({
|
||||
<BookerSection
|
||||
area="meta"
|
||||
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 === "mobile" && bookerState === "booking") && (
|
||||
<div className="mt-auto px-5 py-3 ">
|
||||
@@ -434,7 +434,7 @@ const BookerComponent = ({
|
||||
visible={layout === BookerLayouts.WEEK_VIEW}
|
||||
className="border-subtle sticky top-0 ml-[-1px] h-full md:border-l"
|
||||
{...fadeInLeft}>
|
||||
<LargeCalendar extraDays={extraDays} schedule={schedule.data} isLoading={schedule.isLoading} />
|
||||
<LargeCalendar extraDays={extraDays} schedule={schedule.data} isLoading={schedule.isPending} />
|
||||
</BookerSection>
|
||||
|
||||
<BookerSection
|
||||
@@ -456,7 +456,7 @@ const BookerComponent = ({
|
||||
extraDays={extraDays}
|
||||
limitHeight={layout === BookerLayouts.MONTH_VIEW}
|
||||
schedule={schedule?.data}
|
||||
isLoading={schedule.isLoading}
|
||||
isLoading={schedule.isPending}
|
||||
seatsPerTimeSlot={event.data?.seatsPerTimeSlot}
|
||||
showAvailableSeatsCount={event.data?.seatsShowAvailabilityCount}
|
||||
/>
|
||||
|
||||
@@ -63,7 +63,7 @@ export const BookEventForm = ({
|
||||
}, [eventType]);
|
||||
|
||||
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)
|
||||
return (
|
||||
<EmptyScreen
|
||||
|
||||
@@ -27,7 +27,7 @@ export const DatePicker = ({
|
||||
|
||||
return (
|
||||
<DatePickerComponent
|
||||
isLoading={schedule.isLoading}
|
||||
isPending={schedule.isPending}
|
||||
onChange={(date: Dayjs | null) => {
|
||||
setSelectedDate(date === null ? date : date.format("YYYY-MM-DD"));
|
||||
}}
|
||||
|
||||
@@ -24,10 +24,10 @@ const TimezoneSelect = dynamic(
|
||||
|
||||
export const EventMeta = ({
|
||||
event,
|
||||
isLoading,
|
||||
isPending,
|
||||
}: {
|
||||
event: useEventReturnType["data"];
|
||||
isLoading: useEventReturnType["isLoading"];
|
||||
isPending: useEventReturnType["isPending"];
|
||||
}) => {
|
||||
const { setTimezone, timeFormat, timezone } = useTimePreferences();
|
||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
||||
@@ -40,7 +40,6 @@ export const EventMeta = ({
|
||||
shallow
|
||||
);
|
||||
const { i18n, t } = useLocale();
|
||||
|
||||
const embedUiConfig = useEmbedUiConfig();
|
||||
const isEmbed = useIsEmbed();
|
||||
const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false;
|
||||
@@ -66,12 +65,12 @@ export const EventMeta = ({
|
||||
|
||||
return (
|
||||
<div className="relative z-10 p-6" data-testid="event-meta">
|
||||
{isLoading && (
|
||||
{isPending && (
|
||||
<m.div {...fadeInUp} initial="visible" layout>
|
||||
<EventMetaSkeleton />
|
||||
</m.div>
|
||||
)}
|
||||
{!isLoading && !!event && (
|
||||
{!isPending && !!event && (
|
||||
<m.div {...fadeInUp} layout transition={{ ...fadeInUp.transition, delay: 0.3 }}>
|
||||
<EventMembers
|
||||
schedulingType={event.schedulingType}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { type RouterOutputs } from "@calcom/trpc";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { UserAvatarGroupWithOrg } from "@calcom/web/components/ui/avatar/UserAvatarGroupWithOrg";
|
||||
import { Button, UserAvatarGroupWithOrg } from "@calcom/ui";
|
||||
|
||||
interface IInstantBookingProps {
|
||||
onConnectNow: () => void;
|
||||
|
||||
@@ -74,7 +74,7 @@ export const LargeCalendar = ({
|
||||
return (
|
||||
<div className="h-full [--calendar-dates-sticky-offset:66px]">
|
||||
<Calendar
|
||||
isLoading={isLoading}
|
||||
isPending={isLoading}
|
||||
availableTimeslots={availableSlots}
|
||||
startHour={0}
|
||||
endHour={23}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
|
||||
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -74,27 +74,33 @@ export const useBookings = ({ event, hashedLink, bookingForm }: IUseBookings) =>
|
||||
enabled: !!bookingId && !hasInstantMeetingTokenExpired,
|
||||
refetchInterval: 2000,
|
||||
refetchIntervalInBackground: true,
|
||||
onSuccess: (data) => {
|
||||
try {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
|
||||
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
||||
data.booking?.metadata || {}
|
||||
)?.videoCallUrl;
|
||||
|
||||
if (locationVideoCallUrl) {
|
||||
router.push(locationVideoCallUrl);
|
||||
} else {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
const data = _instantBooking.data;
|
||||
if (!data) return;
|
||||
try {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
|
||||
const createBookingMutation = useMutation(createBooking, {
|
||||
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
||||
data.booking?.metadata || {}
|
||||
)?.videoCallUrl;
|
||||
|
||||
if (locationVideoCallUrl) {
|
||||
router.push(locationVideoCallUrl);
|
||||
} else {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(t("something_went_wrong_on_our_end"), "error");
|
||||
}
|
||||
},
|
||||
[_instantBooking.data]
|
||||
);
|
||||
|
||||
const createBookingMutation = useMutation({
|
||||
mutationFn: createBooking,
|
||||
onSuccess: (responseData) => {
|
||||
const { uid, paymentUid } = responseData;
|
||||
const fullName = getFullName(bookingForm.getValues("responses.name"));
|
||||
@@ -141,7 +147,8 @@ export const useBookings = ({ event, hashedLink, bookingForm }: IUseBookings) =>
|
||||
},
|
||||
});
|
||||
|
||||
const createInstantBookingMutation = useMutation(createInstantBooking, {
|
||||
const createInstantBookingMutation = useMutation({
|
||||
mutationFn: createInstantBooking,
|
||||
onSuccess: (responseData) => {
|
||||
updateQueryParam("bookingId", responseData.bookingId);
|
||||
setExpiryTime(responseData.expires);
|
||||
@@ -153,7 +160,8 @@ export const useBookings = ({ event, hashedLink, bookingForm }: IUseBookings) =>
|
||||
},
|
||||
});
|
||||
|
||||
const createRecurringBookingMutation = useMutation(createRecurringBooking, {
|
||||
const createRecurringBookingMutation = useMutation({
|
||||
mutationFn: createRecurringBooking,
|
||||
onSuccess: async (responseData) => {
|
||||
const booking = responseData[0] || {};
|
||||
const { uid } = booking;
|
||||
@@ -253,10 +261,10 @@ export const useBookings = ({ event, hashedLink, bookingForm }: IUseBookings) =>
|
||||
|
||||
// A redirect is triggered on mutation success, so keep the loading state while it is happening.
|
||||
const loadingStates = {
|
||||
creatingBooking: createBookingMutation.isLoading || createBookingMutation.isSuccess,
|
||||
creatingBooking: createBookingMutation.isPending || createBookingMutation.isSuccess,
|
||||
creatingRecurringBooking:
|
||||
createRecurringBookingMutation.isLoading || createRecurringBookingMutation.isSuccess,
|
||||
creatingInstantBooking: createInstantBookingMutation.isLoading,
|
||||
createRecurringBookingMutation.isPending || createRecurringBookingMutation.isSuccess,
|
||||
creatingInstantBooking: createInstantBookingMutation.isPending,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
@@ -30,7 +31,7 @@ export const useCalendars = () => {
|
||||
shallow
|
||||
);
|
||||
|
||||
const { data: overlayBusyDates } = trpc.viewer.availability.calendarOverlay.useQuery(
|
||||
const { data: overlayBusyDates, isError } = trpc.viewer.availability.calendarOverlay.useQuery(
|
||||
{
|
||||
loggedInUsersTz: timezone || "Europe/London",
|
||||
dateFrom: selectedDate,
|
||||
@@ -42,13 +43,18 @@ export const useCalendars = () => {
|
||||
},
|
||||
{
|
||||
enabled: !!session && set.size > 0 && switchEnabled,
|
||||
onError: () => {
|
||||
clearSet();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { data, isLoading } = trpc.viewer.connectedCalendars.useQuery(undefined, {
|
||||
useEffect(
|
||||
function refactorMeWithoutEffect() {
|
||||
if (!isError) return;
|
||||
clearSet();
|
||||
},
|
||||
[isError]
|
||||
);
|
||||
|
||||
const { data, isPending } = trpc.viewer.connectedCalendars.useQuery(undefined, {
|
||||
enabled: !!calendarSettingsOverlay || Boolean(searchParams?.get("overlayCalendar")),
|
||||
});
|
||||
|
||||
@@ -56,7 +62,7 @@ export const useCalendars = () => {
|
||||
overlayBusyDates,
|
||||
isOverlayCalendarEnabled: switchEnabled,
|
||||
connectedCalendars: data?.connectedCalendars || [],
|
||||
loadingConnectedCalendar: isLoading,
|
||||
loadingConnectedCalendar: isPending,
|
||||
onToggleCalendar: () => {
|
||||
utils.viewer.availability.calendarOverlay.reset();
|
||||
},
|
||||
|
||||