Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0860036607 | ||
|
|
d080a0ef2d | ||
|
|
a6eab74fd1 | ||
|
|
0a2e275908 | ||
|
|
3b05ebc5b5 | ||
|
|
e5b4d2fb80 | ||
|
|
00a2a3d8a8 | ||
|
|
30ba168fc3 | ||
|
|
12eec186b2 | ||
|
|
4417d649ea | ||
|
|
7058da70e6 | ||
|
|
2cb383c337 | ||
|
|
fe5eca8eb3 | ||
|
|
7c6468953a | ||
|
|
28a0553d18 | ||
|
|
ffcb04fa99 | ||
|
|
5ca216b922 | ||
|
|
0abff9f562 | ||
|
|
942dc38b58 | ||
|
|
827d8995a1 | ||
|
|
850e222f0e | ||
|
|
ede2357203 | ||
|
|
559ab4192f | ||
|
|
3c8c6d0574 | ||
|
|
2ae2c15083 | ||
|
|
313bb89789 | ||
|
|
95e037c664 | ||
|
|
da026d909d | ||
|
|
c6b50bfd14 | ||
|
|
30b433397a | ||
|
|
ff1aefe539 | ||
|
|
5685d343b1 | ||
|
|
ae3f30fabf | ||
|
|
c11e76bac9 | ||
|
|
eed34559b5 | ||
|
|
bf958c82da | ||
|
|
728a386bf0 | ||
|
|
41efb5ea04 | ||
|
|
b268540d35 | ||
|
|
bdd1d3a99d | ||
|
|
dd7673b1f9 | ||
|
|
29f37a7e98 | ||
|
|
3983351dac | ||
|
|
01322431fc | ||
|
|
dcd06b0663 | ||
|
|
26e50d24b0 | ||
|
|
74803dc122 | ||
|
|
de65b85862 | ||
|
|
0ee56be1d8 | ||
|
|
de6f7dcfc0 | ||
|
|
956f2c7a2b | ||
|
|
14de0a1d62 | ||
|
|
51cca7481f | ||
|
|
3fe0280dee | ||
|
|
876fc188d4 | ||
|
|
d1810d9f14 | ||
|
|
a25cd43ddf | ||
|
|
833a91375e | ||
|
|
8124627cc5 | ||
|
|
749747ad7e | ||
|
|
967f70a5ec | ||
|
|
51c9b94fb5 | ||
|
|
1976dd15c3 | ||
|
|
03671e2a58 | ||
|
|
5e5f223908 | ||
|
|
9076a7eb42 | ||
|
|
a9e50d76ca | ||
|
|
bd4722554d | ||
|
|
105d373b8e |
@@ -138,6 +138,11 @@ FORMBRICKS_HOST_URL=https://app.formbricks.com
|
||||
FORMBRICKS_ENVIRONMENT_ID=
|
||||
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
||||
|
||||
# AvatarAPI
|
||||
# Used to pre-fill avatar during signup
|
||||
AVATARAPI_USERNAME=
|
||||
AVATARAPI_PASSWORD=
|
||||
|
||||
# Twilio
|
||||
# Used to send SMS reminders in workflows
|
||||
TWILIO_SID=
|
||||
|
||||
@@ -28,6 +28,10 @@ jobs:
|
||||
shard: [1, 2]
|
||||
|
||||
steps:
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -23,6 +23,10 @@ jobs:
|
||||
shard: [1, 2]
|
||||
|
||||
steps:
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -28,6 +28,10 @@ jobs:
|
||||
shard: [1, 2]
|
||||
|
||||
steps:
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -26,6 +26,10 @@ jobs:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -12,14 +12,6 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
login:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
changes:
|
||||
name: Detect changes
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const membershipEditBodySchema = Membership.omit({
|
||||
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
||||
teamId: true,
|
||||
userId: true,
|
||||
id: true,
|
||||
})
|
||||
.partial({
|
||||
accepted: true,
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@sentry/nextjs": "^7.73.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "^13.4.6",
|
||||
"next": "^13.5.4",
|
||||
"next-api-middleware": "^1.0.1",
|
||||
"next-axiom": "^0.17.0",
|
||||
"next-swagger-doc": "^0.3.6",
|
||||
|
||||
@@ -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": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { usernameList, ...rest } = req.query;
|
||||
let slugs = usernameList;
|
||||
if (!Array.isArray(usernameList)) {
|
||||
slugs = usernameList ? [usernameList] : [];
|
||||
slugs = usernameList ? [usernameList] : undefined;
|
||||
}
|
||||
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
|
||||
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
|
||||
@@ -38,9 +38,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
{}
|
||||
)
|
||||
: availableSlots;
|
||||
: availableSlots.slots;
|
||||
|
||||
return slotsInProvidedTimeZone;
|
||||
return { slots: slotsInProvidedTimeZone };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (cause) {
|
||||
if (cause instanceof TRPCError) {
|
||||
|
||||
@@ -92,7 +92,7 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
if (eventTriggers) {
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ async function postHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
if (eventTriggers) {
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@storybook/blocks": "^7.6.3",
|
||||
"@storybook/nextjs": "^7.6.3",
|
||||
"@storybook/preview-api": "^7.6.3",
|
||||
"next": "^13.4.6",
|
||||
"next": "^13.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"storybook-addon-rtl-direction": "^0.0.19"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.6.0",
|
||||
"isarray": "2.0.5",
|
||||
"next": "^13.4.6",
|
||||
"next": "^13.5.4",
|
||||
"openapi-snippet": "^0.13.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { GetStaticProps, GetStaticPropsContext } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
export const withAppDirSsg =
|
||||
<T extends Record<string, any>>(getStaticProps: GetStaticProps<T>) =>
|
||||
async (context: GetStaticPropsContext) => {
|
||||
const ssgResponse = await getStaticProps(context);
|
||||
|
||||
if ("redirect" in ssgResponse) {
|
||||
redirect(ssgResponse.redirect.destination);
|
||||
}
|
||||
|
||||
if ("notFound" in ssgResponse) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const props = await Promise.resolve(ssgResponse.props);
|
||||
|
||||
return {
|
||||
...ssgResponse.props,
|
||||
// includes dehydratedState required for future page trpcPropvider
|
||||
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
export const withAppDir =
|
||||
export const withAppDirSsr =
|
||||
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
||||
async (context: GetServerSidePropsContext): Promise<T> => {
|
||||
async (context: GetServerSidePropsContext) => {
|
||||
const ssrResponse = await getServerSideProps(context);
|
||||
|
||||
if ("redirect" in ssrResponse) {
|
||||
@@ -13,9 +13,11 @@ export const withAppDir =
|
||||
notFound();
|
||||
}
|
||||
|
||||
const props = await Promise.resolve(ssrResponse.props);
|
||||
|
||||
return {
|
||||
...ssrResponse.props,
|
||||
...props,
|
||||
// includes dehydratedState required for future page trpcPropvider
|
||||
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
|
||||
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||
};
|
||||
};
|
||||
@@ -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,197 @@
|
||||
import LegacyPage, { getLayout } from "@pages/apps/[slug]/[...pages]";
|
||||
import type { PageProps, SearchParams } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
import RoutingFormsRoutingConfig, {
|
||||
serverSidePropsConfig,
|
||||
} from "@calcom/app-store/routing-forms/pages/app-routing.config";
|
||||
import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import type { AppProps } from "@lib/app-providers";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
type AppPageType = {
|
||||
getServerSideProps: AppGetServerSideProps;
|
||||
// A component than can accept any properties
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default: ((props: any) => JSX.Element) &
|
||||
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
|
||||
};
|
||||
|
||||
type Found = {
|
||||
notFound: false;
|
||||
Component: AppPageType["default"];
|
||||
getServerSideProps: AppPageType["getServerSideProps"];
|
||||
};
|
||||
|
||||
const AppsRouting = {
|
||||
"routing-forms": RoutingFormsRoutingConfig,
|
||||
typeform: TypeformRoutingConfig,
|
||||
};
|
||||
|
||||
const paramsSchema = z.object({
|
||||
slug: z.string(),
|
||||
pages: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}) => {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const mainPage = p.data.pages[0];
|
||||
|
||||
if (mainPage === "forms") {
|
||||
return await _generateMetadata(
|
||||
() => `Forms | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
}
|
||||
|
||||
const legacyContext = buildLegacyCtx(
|
||||
headers(),
|
||||
cookies(),
|
||||
params,
|
||||
searchParams
|
||||
) as unknown as GetServerSidePropsContext;
|
||||
const { form } = await getPageProps(legacyContext);
|
||||
|
||||
return await _generateMetadata(
|
||||
() => `${form.name} | ${APP_NAME}`,
|
||||
() => form.description
|
||||
);
|
||||
};
|
||||
|
||||
function getRoute(appName: string, pages: string[]) {
|
||||
const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as Record<string, AppPageType>;
|
||||
|
||||
if (!routingConfig) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const mainPage = pages[0];
|
||||
const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as AppPageType);
|
||||
|
||||
const getServerSidePropsHandler = serverSidePropsConfig[mainPage];
|
||||
|
||||
if (!appPage) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
notFound: false,
|
||||
Component: appPage.default,
|
||||
...appPage,
|
||||
getServerSideProps: getServerSidePropsHandler,
|
||||
} as Found;
|
||||
}
|
||||
|
||||
const getPageProps = async ({ params, query, req }: GetServerSidePropsContext) => {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { slug: appName, pages } = p.data;
|
||||
|
||||
const route = getRoute(appName, pages);
|
||||
|
||||
if (route.notFound) {
|
||||
return route;
|
||||
}
|
||||
|
||||
if (route.getServerSideProps) {
|
||||
// TODO: Document somewhere that right now it is just a convention that filename should have appPages in it's name.
|
||||
// appPages is actually hardcoded here and no matter the fileName the same variable would be used.
|
||||
// We can write some validation logic later on that ensures that [...appPages].tsx file exists
|
||||
params!.appPages = pages.slice(1);
|
||||
|
||||
const ctx = { req, params, query };
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
const user = session?.user;
|
||||
const app = await getAppWithMetadata({ slug: appName });
|
||||
|
||||
if (!app) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const result = await route.getServerSideProps(
|
||||
{
|
||||
...ctx,
|
||||
params: {
|
||||
...ctx.params,
|
||||
appPages: pages.slice(1),
|
||||
},
|
||||
} as GetServerSidePropsContext<{
|
||||
slug: string;
|
||||
pages: string[];
|
||||
appPages: string[];
|
||||
}>,
|
||||
prisma,
|
||||
user,
|
||||
ssrInit
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
|
||||
if (result.notFound) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if (result.redirect) {
|
||||
redirect(result.redirect.destination);
|
||||
}
|
||||
|
||||
return {
|
||||
appName,
|
||||
appUrl: app.simplePath || `/apps/${appName}`,
|
||||
...result.props,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
appName,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
const h = headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
const legacyContext = buildLegacyCtx(
|
||||
h,
|
||||
cookies(),
|
||||
params,
|
||||
searchParams
|
||||
) as unknown as GetServerSidePropsContext;
|
||||
const props = await getPageProps(legacyContext);
|
||||
return (
|
||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||
<LegacyPage {...props} />
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export default WithLayout({ getLayout: null })<"L">;
|
||||
@@ -1,40 +1,33 @@
|
||||
import AppPage from "@pages/apps/[slug]/index";
|
||||
import Page from "@pages/apps/[slug]/index";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import fs from "fs";
|
||||
import matter from "gray-matter";
|
||||
import { notFound } from "next/navigation";
|
||||
import path from "path";
|
||||
import { z } from "zod";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetStaticPropsType } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
||||
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const sourceSchema = z.object({
|
||||
content: z.string(),
|
||||
data: z.object({
|
||||
description: z.string().optional(),
|
||||
items: z
|
||||
.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
iframe: z.object({ src: z.string() }),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const { data } = await getPageProps({ params });
|
||||
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Record<string, string | string[]>;
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const res = await getData(legacyContext);
|
||||
|
||||
return await _generateMetadata(
|
||||
() => `${data.name} | ${APP_NAME}`,
|
||||
() => data.description
|
||||
() => `${res?.data.name} | ${APP_NAME}`,
|
||||
() => res?.data.description ?? ""
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,74 +46,6 @@ export const generateStaticParams = async () => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
if (typeof params?.slug !== "string") {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const appMeta = await getAppWithMetadata({
|
||||
slug: params?.slug,
|
||||
});
|
||||
|
||||
const appFromDb = await prisma.app.findUnique({
|
||||
where: { slug: params.slug.toLowerCase() },
|
||||
});
|
||||
|
||||
const isAppAvailableInFileSystem = appMeta;
|
||||
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
|
||||
|
||||
if (!IS_PRODUCTION && isAppDisabled) {
|
||||
return {
|
||||
isAppDisabled: true as const,
|
||||
data: {
|
||||
...appMeta,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!appFromDb || !appMeta || isAppDisabled) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const isTemplate = appMeta.isTemplate;
|
||||
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
|
||||
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
|
||||
const postFilePath = path.join(README_PATH);
|
||||
let source = "";
|
||||
|
||||
try {
|
||||
source = fs.readFileSync(postFilePath).toString();
|
||||
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
|
||||
} catch (error) {
|
||||
/* If the app doesn't have a README we fallback to the package description */
|
||||
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
||||
source = appMeta.description;
|
||||
}
|
||||
|
||||
const result = matter(source);
|
||||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
||||
if (data.items) {
|
||||
data.items = data.items.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
return getAppAssetFullPath(item, {
|
||||
dirName: appMeta.dirName,
|
||||
isTemplate: appMeta.isTemplate,
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
return {
|
||||
isAppDisabled: false as const,
|
||||
source: { content, data },
|
||||
data: appMeta,
|
||||
};
|
||||
};
|
||||
|
||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
||||
const pageProps = await getPageProps({ params });
|
||||
|
||||
return <AppPage {...pageProps} />;
|
||||
}
|
||||
export default WithLayout({ getLayout: null, Page, getData });
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import SetupPage from "@pages/apps/[slug]/setup";
|
||||
import Page from "@pages/apps/[slug]/setup";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetServerSidePropsType } from "next";
|
||||
|
||||
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
@@ -14,23 +14,8 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
||||
);
|
||||
};
|
||||
|
||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const req = { headers: headers(), cookies: cookies() };
|
||||
type T = InferGetServerSidePropsType<typeof getServerSideProps>;
|
||||
|
||||
const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);
|
||||
const getData = withAppDirSsr<T>(getServerSideProps);
|
||||
|
||||
if (!result || "notFound" in result) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if ("redirect" in result) {
|
||||
redirect(result.redirect.destination);
|
||||
}
|
||||
|
||||
return result.props;
|
||||
};
|
||||
|
||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
||||
const pageProps = await getPageProps({ params });
|
||||
return <SetupPage {...pageProps} />;
|
||||
}
|
||||
export default WithLayout({ getLayout: null, Page, getData });
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import CategoryPage from "@pages/apps/categories/[category]";
|
||||
import CategoryPage, { type PageProps } from "@pages/apps/categories/[category]";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { notFound } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
() => `${APP_NAME} | ${APP_NAME}`,
|
||||
@@ -34,38 +34,7 @@ export const generateStaticParams = async () => {
|
||||
return paths.map((category) => ({ category }));
|
||||
};
|
||||
|
||||
const querySchema = z.object({
|
||||
category: z.nativeEnum(AppCategories),
|
||||
});
|
||||
const getData = withAppDirSsg<PageProps>(getStaticProps);
|
||||
|
||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const p = querySchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const appQuery = await prisma.app.findMany({
|
||||
where: {
|
||||
categories: {
|
||||
has: p.data.category,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
});
|
||||
|
||||
const dbAppsSlugs = appQuery.map((category) => category.slug);
|
||||
|
||||
const appStore = await getAppRegistry();
|
||||
|
||||
const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug));
|
||||
return {
|
||||
apps,
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-expect-error getData arg
|
||||
export default WithLayout({ getData: getPageProps, Page: CategoryPage })<"P">;
|
||||
export default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import LegacyPage from "@pages/apps/categories/index";
|
||||
import Page from "@pages/apps/categories/index";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
import { getServerSideProps } from "@lib/apps/categories/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
@@ -16,29 +14,4 @@ export const generateMetadata = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(ctx);
|
||||
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
let appStore;
|
||||
if (session?.user?.id) {
|
||||
appStore = await getAppRegistryWithCredentials(session.user.id);
|
||||
} else {
|
||||
appStore = await getAppRegistry();
|
||||
}
|
||||
|
||||
const categories = appStore.reduce((c, app) => {
|
||||
for (const category of app.categories) {
|
||||
c[category] = c[category] ? c[category] + 1 : 1;
|
||||
}
|
||||
return c;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
return {
|
||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
};
|
||||
};
|
||||
|
||||
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
||||
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), Page, getLayout: null })<"P">;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export default WithLayout({ getLayout: null })<"L">;
|
||||
@@ -1,14 +1,11 @@
|
||||
import LegacyPage from "@pages/apps/installed/[category]";
|
||||
import Page from "@pages/apps/installed/[category]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
const querySchema = z.object({
|
||||
category: z.nativeEnum(AppCategories),
|
||||
});
|
||||
import { getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
@@ -17,20 +14,6 @@ export const generateMetadata = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const p = querySchema.safeParse(params);
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
category: p.data.category,
|
||||
};
|
||||
};
|
||||
|
||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
||||
await getPageProps({ params });
|
||||
|
||||
return <LegacyPage />;
|
||||
}
|
||||
export default WithLayout({ getLayout: null, getData, Page });
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import AppsPage from "@pages/apps";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import type { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
import { getServerSideProps } from "@lib/apps/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
@@ -20,44 +15,4 @@ export const generateMetadata = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(ctx);
|
||||
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
let appStore, userAdminTeams: UserAdminTeams;
|
||||
if (session?.user?.id) {
|
||||
userAdminTeams = await getUserAdminTeams({ userId: session.user.id, getUserInfo: true });
|
||||
appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeams);
|
||||
} else {
|
||||
appStore = await getAppRegistry();
|
||||
userAdminTeams = [];
|
||||
}
|
||||
|
||||
const categoryQuery = appStore.map(({ categories }) => ({
|
||||
categories: categories || [],
|
||||
}));
|
||||
|
||||
const categories = categoryQuery.reduce((c, app) => {
|
||||
for (const category of app.categories) {
|
||||
c[category] = c[category] ? c[category] + 1 : 1;
|
||||
}
|
||||
return c;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
return {
|
||||
categories: Object.entries(categories)
|
||||
.map(([name, count]): { name: AppCategories; count: number } => ({
|
||||
name: name as AppCategories,
|
||||
count,
|
||||
}))
|
||||
.sort(function (a, b) {
|
||||
return b.count - a.count;
|
||||
}),
|
||||
appStore,
|
||||
userAdminTeams,
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
};
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout, getData, Page: AppsPage });
|
||||
export default WithLayout({ getLayout, getData: withAppDirSsr(getServerSideProps), Page: AppsPage });
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import SetNewUserPassword from "@pages/auth/forgot-password/[id]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/[id]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("reset_password"),
|
||||
(t) => t("change_your_password")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: SetNewUserPassword,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,19 @@
|
||||
import ForgotPassword from "@pages/auth/forgot-password";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/forgot-password/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("reset_password"),
|
||||
(t) => t("change_your_password")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: ForgotPassword,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,22 @@
|
||||
import Login from "@pages/auth/login";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetServerSidePropsType } from "next";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/login/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => `${t("login")} | ${APP_NAME}`,
|
||||
(t) => t("login")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: Login,
|
||||
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Logout from "@pages/auth/logout";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/logout/getServerSideProps";
|
||||
|
||||
export default WithLayout({ getLayout: null, Page: Logout, getData: withAppDirSsr(getServerSideProps) })<"P">;
|
||||
@@ -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">;
|
||||
@@ -0,0 +1,13 @@
|
||||
import signin from "@pages/auth/signin";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetServerSidePropsType } from "next";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/signin/getServerSideProps";
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: signin,
|
||||
// @ts-expect-error TODO: fix this
|
||||
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,12 @@
|
||||
import Provider from "@pages/auth/sso/[provider]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetServerSidePropsType } from "next";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps";
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: Provider,
|
||||
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,11 @@
|
||||
import DirectSSOLogin from "@pages/auth/sso/direct";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps";
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: DirectSSOLogin,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -0,0 +1,15 @@
|
||||
import VerifyEmailPage from "@pages/auth/verify-email";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("check_your_email"),
|
||||
(t) => t("check_your_email")
|
||||
);
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: VerifyEmailPage,
|
||||
})<"P">;
|
||||
@@ -0,0 +1,11 @@
|
||||
import VerifyPage from "@pages/auth/verify";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/auth/verify/getServerSideProps";
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: null,
|
||||
Page: VerifyPage,
|
||||
getData: withAppDirSsr(getServerSideProps),
|
||||
})<"P">;
|
||||
@@ -1,10 +1,11 @@
|
||||
import OldPage from "@pages/booking/[uid]";
|
||||
import { type PageProps, getServerSideProps } from "@pages/booking/[uid]";
|
||||
import OldPage from "@pages/booking/[uid]/embed";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getData } from "../page";
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||
|
||||
// @ts-expect-error Type '(context: GetServerSidePropsContext) => Promise<any>' is not assignable to type '(arg: {
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
|
||||
|
||||
@@ -1,204 +1,36 @@
|
||||
import OldPage from "@pages/booking/[uid]";
|
||||
import OldPage, { type PageProps } from "@pages/booking/[uid]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { Params, SearchParams } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
import { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } from "@lib/booking";
|
||||
import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const stringToBoolean = z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((val) => val === "true");
|
||||
|
||||
const querySchema = z.object({
|
||||
uid: z.string(),
|
||||
email: z.string().optional(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
cancel: stringToBoolean,
|
||||
allRemainingBookings: stringToBoolean,
|
||||
changes: stringToBoolean,
|
||||
reschedule: stringToBoolean,
|
||||
isSuccessBookingPage: stringToBoolean,
|
||||
formerTime: z.string().optional(),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
});
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
() => "",
|
||||
() => ""
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}) => {
|
||||
const { bookingInfo, eventType, recurringBookings } = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||
);
|
||||
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
|
||||
|
||||
export const getData = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
const session = await getServerSession(context);
|
||||
let tz: string | null = null;
|
||||
let userTimeFormat: number | null = null;
|
||||
let requiresLoginToUpdate = false;
|
||||
if (session) {
|
||||
const user = await ssr.viewer.me.fetch();
|
||||
tz = user.timeZone;
|
||||
userTimeFormat = user.timeFormat;
|
||||
}
|
||||
|
||||
const parsedQuery = querySchema.safeParse(context.query);
|
||||
|
||||
if (!parsedQuery.success) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
|
||||
|
||||
const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid);
|
||||
const bookingInfoRaw = await prisma.booking.findFirst({
|
||||
where: {
|
||||
uid: maybeUid,
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
id: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
smsReminderNumber: true,
|
||||
recurringEventId: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
location: true,
|
||||
status: true,
|
||||
metadata: true,
|
||||
cancellationReason: true,
|
||||
responses: true,
|
||||
rejectionReason: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
username: true,
|
||||
timeZone: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
},
|
||||
},
|
||||
eventTypeId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
eventName: true,
|
||||
slug: true,
|
||||
timeZone: true,
|
||||
},
|
||||
},
|
||||
seatsReferences: {
|
||||
select: {
|
||||
referenceUid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!bookingInfoRaw) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const eventTypeRaw = !bookingInfoRaw.eventTypeId
|
||||
? getDefaultEvent(eventTypeSlug || "")
|
||||
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
|
||||
if (!eventTypeRaw) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) {
|
||||
requiresLoginToUpdate = true;
|
||||
}
|
||||
|
||||
const bookingInfo = getBookingWithResponses(bookingInfoRaw);
|
||||
// @NOTE: had to do this because Server side cant return [Object objects]
|
||||
// probably fixable with json.stringify -> json.parse
|
||||
bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date;
|
||||
bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date;
|
||||
|
||||
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
|
||||
? eventTypeRaw.hosts.map((host) => host.user)
|
||||
: eventTypeRaw.users;
|
||||
|
||||
if (!eventTypeRaw.users.length) {
|
||||
if (!eventTypeRaw.owner) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
eventTypeRaw.users.push({
|
||||
...eventTypeRaw.owner,
|
||||
});
|
||||
}
|
||||
|
||||
const eventType = {
|
||||
...eventTypeRaw,
|
||||
periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null,
|
||||
periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata),
|
||||
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
|
||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
||||
};
|
||||
|
||||
const profile = {
|
||||
name: eventType.team?.name || eventType.users[0]?.name || null,
|
||||
email: eventType.team ? null : eventType.users[0].email || null,
|
||||
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
||||
brandColor: eventType.team ? null : eventType.users[0].brandColor || null,
|
||||
darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null,
|
||||
slug: eventType.team?.slug || eventType.users[0]?.username || null,
|
||||
};
|
||||
|
||||
if (bookingInfo !== null && eventType.seatsPerTimeSlot) {
|
||||
await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id);
|
||||
}
|
||||
|
||||
const payment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
bookingId: bookingInfo.id,
|
||||
},
|
||||
select: {
|
||||
success: true,
|
||||
refunded: true,
|
||||
currency: true,
|
||||
amount: true,
|
||||
paymentOption: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
||||
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
|
||||
profile,
|
||||
eventType,
|
||||
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
||||
bookingInfo,
|
||||
paymentStatus: payment,
|
||||
...(tz && { tz }),
|
||||
userTimeFormat,
|
||||
requiresLoginToUpdate,
|
||||
};
|
||||
return await _generateMetadata(
|
||||
(t) =>
|
||||
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
|
||||
(t) =>
|
||||
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`)
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-expect-error Argument of type '{ req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||
|
||||
const querySchema = z.object({
|
||||
status: z.enum(validStatuses),
|
||||
});
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => `${APP_NAME} | ${t("bookings")}`,
|
||||
() => ""
|
||||
);
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
return validStatuses.map((status) => ({ status }));
|
||||
};
|
||||
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const parsedParams = querySchema.safeParse(ctx.params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const ssg = await ssgInit(ctx);
|
||||
|
||||
return {
|
||||
status: parsedParams.data.status,
|
||||
dehydratedState: ssg.dehydrate(),
|
||||
};
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout, getData })<"L">;
|
||||
|
||||
export const dynamic = "force-static";
|
||||
@@ -1 +1,29 @@
|
||||
export { default } from "@pages/bookings/[status]";
|
||||
import Page from "@pages/bookings/[status]";
|
||||
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { InferGetStaticPropsType } from "next";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { getStaticProps } from "@lib/bookings/[status]/getStaticProps";
|
||||
|
||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||
|
||||
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => `${APP_NAME} | ${t("bookings")}`,
|
||||
() => ""
|
||||
);
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
return validStatuses.map((status) => ({ status }));
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout, getData, Page })<"P">;
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import LegacyPage, { type PageProps } from "@pages/d/[link]/[slug]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import LegacyPage from "@pages/d/[link]/[slug]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { Params, SearchParams } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const pageProps = await getPageProps(
|
||||
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
||||
);
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}) => {
|
||||
const pageProps = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
||||
const rescheduleUid = booking?.uid;
|
||||
@@ -38,95 +32,5 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
||||
);
|
||||
};
|
||||
|
||||
async function getPageProps(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession({ req: context.req });
|
||||
const { link, slug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid, duration: queryDuration } = context.query;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
|
||||
const hashedLink = await prisma.hashedLink.findUnique({
|
||||
where: {
|
||||
link,
|
||||
},
|
||||
select: {
|
||||
eventTypeId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
users: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const username = hashedLink?.eventType.users[0]?.username;
|
||||
|
||||
if (!hashedLink || !username) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
organization: isValidOrgDomain
|
||||
? {
|
||||
slug: currentOrgDomain,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
select: {
|
||||
away: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
}
|
||||
|
||||
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent, org });
|
||||
|
||||
if (!eventData) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
entity: eventData.entity,
|
||||
duration: getMultipleDurationValue(eventData.metadata?.multipleDuration, queryDuration, eventData.length),
|
||||
booking,
|
||||
away: user?.away,
|
||||
user: username,
|
||||
slug,
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
isBrandingHidden: user?.hideBranding,
|
||||
// Sending the team event from the server, because this template file
|
||||
// is reused for both team and user events.
|
||||
isTeamEvent,
|
||||
hashedLink: link,
|
||||
};
|
||||
}
|
||||
|
||||
const paramsSchema = z.object({ link: z.string(), slug: z.string().transform((s) => slugify(s)) });
|
||||
|
||||
// @ts-expect-error arg
|
||||
const getData = withAppDir<PageProps>(getPageProps);
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout })<"L">;
|
||||
@@ -1,4 +1,7 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
import EnterprisePage from "@components/EnterprisePage";
|
||||
|
||||
@@ -8,4 +11,4 @@ export const generateMetadata = async () =>
|
||||
(t) => t("create_your_org_description")
|
||||
);
|
||||
|
||||
export default EnterprisePage;
|
||||
export default WithLayout({ getLayout, Page: EnterprisePage })<"P">;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout })<"L">;
|
||||
@@ -1,5 +1,8 @@
|
||||
import EventTypes from "@pages/event-types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -7,4 +10,4 @@ export const generateMetadata = async () =>
|
||||
(t) => t("event_types_page_subtitle")
|
||||
);
|
||||
|
||||
export default EventTypes;
|
||||
export default WithLayout({ getLayout, Page: EventTypes })<"P">;
|
||||
|
||||
@@ -1,57 +1,7 @@
|
||||
import LegacyPage from "@pages/getting-started/[[...step]]";
|
||||
import Page from "@pages/getting-started/[[...step]]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { getServerSideProps } from "@lib/getting-started/[[...step]]/getServerSideProps";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return redirect("/auth/login");
|
||||
}
|
||||
const ssr = await ssrInit(ctx);
|
||||
await ssr.viewer.me.prefetch();
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
completedOnboarding: true,
|
||||
teams: {
|
||||
select: {
|
||||
accepted: true,
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
logo: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User from session not found");
|
||||
}
|
||||
|
||||
if (user.completedOnboarding) {
|
||||
redirect("/event-types");
|
||||
}
|
||||
|
||||
return {
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
|
||||
requiresLicense: false,
|
||||
themeBasis: null,
|
||||
};
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: LegacyPage });
|
||||
export default WithLayout({ getLayout: null, getData: withAppDirSsr(getServerSideProps), Page });
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import LegacyPage from "@pages/insights/index";
|
||||
import LegacyPage, { getServerSideProps } from "@pages/insights/index";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
|
||||
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -12,15 +13,6 @@ export const generateMetadata = async () =>
|
||||
(t) => t("insights_subtitle")
|
||||
);
|
||||
|
||||
async function getData() {
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
|
||||
if (flags.insights === false) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout, getData, Page: LegacyPage });
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||
import Page from "@pages/org/[orgSlug]/[user]/[type]/embed";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
const getData = withAppDir<PageProps>(getServerSideProps);
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Page, { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
const getData = withAppDir<PageProps>(getServerSideProps);
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||
import Page from "@pages/org/[orgSlug]/[user]/embed";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
const getData = withAppDir<PageProps>(getServerSideProps);
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Page, { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
const getData = withAppDir<PageProps>(getServerSideProps);
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { redirect, notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage";
|
||||
import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment";
|
||||
import { getServerSideProps, type PaymentPageProps } from "@calcom/features/ee/payments/pages/payment";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -21,147 +13,6 @@ export const generateMetadata = async () =>
|
||||
() => ""
|
||||
);
|
||||
|
||||
const querySchema = z.object({
|
||||
uid: z.string(),
|
||||
});
|
||||
|
||||
async function getData(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession({ req: context.req });
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return redirect("/auth/login");
|
||||
}
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
await ssr.viewer.me.prefetch();
|
||||
|
||||
const { uid } = querySchema.parse(context.params);
|
||||
const rawPayment = await prisma.payment.findFirst({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
select: {
|
||||
data: true,
|
||||
success: true,
|
||||
uid: true,
|
||||
refunded: true,
|
||||
bookingId: true,
|
||||
appId: true,
|
||||
amount: true,
|
||||
currency: true,
|
||||
paymentOption: true,
|
||||
booking: {
|
||||
select: {
|
||||
id: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
title: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
attendees: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
eventTypeId: true,
|
||||
location: true,
|
||||
status: true,
|
||||
rejectionReason: true,
|
||||
cancellationReason: true,
|
||||
eventType: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true,
|
||||
eventName: true,
|
||||
requiresConfirmation: true,
|
||||
userId: true,
|
||||
metadata: true,
|
||||
users: {
|
||||
select: {
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
name: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
},
|
||||
price: true,
|
||||
currency: true,
|
||||
successRedirectUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!rawPayment) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { data, booking: _booking, ...restPayment } = rawPayment;
|
||||
|
||||
const payment = {
|
||||
...restPayment,
|
||||
data: data as Record<string, unknown>,
|
||||
};
|
||||
|
||||
if (!_booking) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { startTime, endTime, eventType, ...restBooking } = _booking;
|
||||
const booking = {
|
||||
...restBooking,
|
||||
startTime: startTime.toString(),
|
||||
endTime: endTime.toString(),
|
||||
};
|
||||
|
||||
if (!eventType) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
if (eventType.users.length === 0 && !!!eventType.team) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const [user] = eventType?.users.length
|
||||
? eventType.users
|
||||
: [{ name: null, theme: null, hideBranding: null, username: null }];
|
||||
const profile = {
|
||||
name: eventType.team?.name || user?.name || null,
|
||||
theme: (!eventType.team?.name && user?.theme) || null,
|
||||
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
|
||||
};
|
||||
|
||||
if (
|
||||
([BookingStatus.CANCELLED, BookingStatus.REJECTED] as BookingStatus[]).includes(
|
||||
booking.status as BookingStatus
|
||||
)
|
||||
) {
|
||||
return redirect(`/booking/${booking.uid}`);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
eventType: {
|
||||
...eventType,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
||||
},
|
||||
booking,
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
payment,
|
||||
clientSecret: getClientSecretFromPayment(payment),
|
||||
profile,
|
||||
};
|
||||
}
|
||||
const getData = withAppDirSsr<PaymentPageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: PaymentPage });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { SearchParams } from "app/_types";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
@@ -8,12 +9,12 @@ import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}>;
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }'
|
||||
await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx);
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
await withAppDirSsr(withEmbedSsr(getServerSideProps))(legacyCtx);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import OldPage, { getServerSideProps as _getServerSideProps } from "@pages/reschedule/[uid]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { SearchParams } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { headers, cookies } from "next/headers";
|
||||
@@ -14,17 +15,17 @@ export const generateMetadata = async () =>
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}>;
|
||||
|
||||
const getData = withAppDir(_getServerSideProps);
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }'
|
||||
await getData(legacyCtx);
|
||||
|
||||
return <OldPage />;
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
const getPageProps = () => {
|
||||
return redirect(`/apps/routing-forms/forms`);
|
||||
};
|
||||
const Page = () => {
|
||||
getPageProps();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { type SearchParams } from "app/_types";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
pages: z.array(z.string()),
|
||||
})
|
||||
.catch({
|
||||
pages: [],
|
||||
});
|
||||
|
||||
const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||
const { pages } = paramsSchema.parse(context.params);
|
||||
|
||||
return redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`);
|
||||
};
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}>;
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
await getPageProps(legacyCtx as unknown as GetServerSidePropsContext);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -1,36 +1,32 @@
|
||||
import { getServerCaller } from "app/_trpc/serverClient";
|
||||
import { type Params } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { z } from "zod";
|
||||
|
||||
import Page from "@calcom/features/ee/users/pages/users-edit-view";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
const userIdSchema = z.object({ id: z.coerce.number() });
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Params }) => {
|
||||
const input = userIdSchema.safeParse(params);
|
||||
|
||||
let title = "";
|
||||
if (!input.success) {
|
||||
title = "Editing user";
|
||||
} else {
|
||||
const req = {
|
||||
headers: headers(),
|
||||
cookies: cookies(),
|
||||
};
|
||||
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest'
|
||||
const data = await getServerCaller({ req, prisma }).viewer.users.get({ userId: input.data.id });
|
||||
const { user } = data;
|
||||
title = `Editing user: ${user.username}`;
|
||||
return await _generateMetadata(
|
||||
() => "",
|
||||
() => "Here you can edit a current user."
|
||||
);
|
||||
}
|
||||
|
||||
const userId = input.data.id;
|
||||
const { trpc } = await import("@calcom/trpc");
|
||||
const [data] = trpc.viewer.users.get.useSuspenseQuery({ userId });
|
||||
const { user } = data;
|
||||
const title = `Editing user: ${user.username}`;
|
||||
|
||||
return await _generateMetadata(
|
||||
() => title,
|
||||
() => "Here you can edit a current user."
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export default WithLayout({ getLayout, Page })<"P">;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import Page from "@calcom/features/ee/users/pages/users-add-view";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -8,4 +10,4 @@ export const generateMetadata = async () =>
|
||||
() => "Here you can add a new user."
|
||||
);
|
||||
|
||||
export default Page;
|
||||
export default WithLayout({ getLayout, Page })<"P">;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout })<"L">;
|
||||
@@ -1,6 +1,8 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import Page from "@calcom/features/ee/users/pages/users-listing-view";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -8,4 +10,4 @@ export const generateMetadata = async () =>
|
||||
() => "A list of all the users in your account including their name, title, email and role."
|
||||
);
|
||||
|
||||
export default Page;
|
||||
export default WithLayout({ getLayout, Page })<"P">;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import LegacyPage, {
|
||||
buildWrappedOnboardTeamMembersPage,
|
||||
} from "@pages/settings/organizations/[id]/onboard-admins";
|
||||
} from "@pages/settings/organizations/[id]/onboard-members";
|
||||
import { type Params } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import LegacyPage, { WrappedCreateNewOrganizationPage } from "@pages/settings/organizations/new/index";
|
||||
import LegacyPage, { getServerSideProps, LayoutWrapperAppDir } from "@pages/settings/organizations/new/index";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -12,23 +11,8 @@ export const generateMetadata = async () =>
|
||||
(t) => t("organizations_description")
|
||||
);
|
||||
|
||||
const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
// Check if organizations are enabled
|
||||
if (flags["organizations"] !== true) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const querySlug = context.query.slug as string;
|
||||
|
||||
return {
|
||||
querySlug: querySlug ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
export default WithLayout({
|
||||
getLayout: WrappedCreateNewOrganizationPage,
|
||||
getLayout: LayoutWrapperAppDir,
|
||||
Page: LegacyPage,
|
||||
getData: getPageProps,
|
||||
getData: withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import LegacyPage, { type SignupProps } from "@pages/signup";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/signup/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("sign_up"),
|
||||
(t) => t("sign_up")
|
||||
);
|
||||
|
||||
const getData = withAppDirSsr<SignupProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: LegacyPage,
|
||||
getLayout: null,
|
||||
getData,
|
||||
})<"P">;
|
||||
@@ -0,0 +1,10 @@
|
||||
import LegacyPage, { getServerSideProps } from "@pages/team/[slug]/[type]/embed";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||
|
||||
// @ts-expect-error Type is missing the following properties from type: entity, duration, booking, away, and 7 more.
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||
@@ -0,0 +1,41 @@
|
||||
import LegacyPage, { type PageProps, getServerSideProps } from "@pages/team/[slug]/[type]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Record<string, string | string[]>;
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
|
||||
const { entity, user, slug, booking } = props;
|
||||
const { trpc } = await import("@calcom/trpc");
|
||||
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
const profileName = event?.profile?.name ?? "";
|
||||
const title = event?.title ?? "";
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
|
||||
);
|
||||
};
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: LegacyPage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -0,0 +1,9 @@
|
||||
import LegacyPage, { type PageProps, getServerSideProps } from "@pages/team/[slug]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||
@@ -0,0 +1,35 @@
|
||||
import LegacyPage, { getServerSideProps, type PageProps } from "@pages/team/[slug]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Record<string, string | string[]>;
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const props = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||
);
|
||||
const teamName = props.team.name || "Nameless Team";
|
||||
|
||||
return await _generateMetadata(
|
||||
() => teamName,
|
||||
() => teamName
|
||||
);
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: LegacyPage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -1,13 +1,11 @@
|
||||
import OldPage from "@pages/teams/index";
|
||||
import Page from "@pages/teams/index";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
import { getServerSideProps } from "@lib/teams/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -15,23 +13,4 @@ export const generateMetadata = async () =>
|
||||
(t) => t("create_manage_teams_collaborative")
|
||||
);
|
||||
|
||||
async function getData(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
await ssr.viewer.me.prefetch();
|
||||
|
||||
const session = await getServerSession({
|
||||
req: context.req,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
const token = Array.isArray(context.query.token) ? context.query.token[0] : context.query.token;
|
||||
|
||||
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
|
||||
return redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
|
||||
}
|
||||
|
||||
return { dehydratedState: ssr.dehydrate() };
|
||||
}
|
||||
|
||||
export default WithLayout({ getData, getLayout, Page: OldPage })<"P">;
|
||||
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout, Page })<"P">;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import OldPage from "@pages/video/[uid]";
|
||||
import Page, { type JoinCallPageProps } from "@pages/video/[uid]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -17,86 +13,6 @@ export const generateMetadata = async () =>
|
||||
(t) => t("quick_video_meeting")
|
||||
);
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
const getData = withAppDirSsr<JoinCallPageProps>(getServerSideProps);
|
||||
|
||||
async function getData(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: context.query.uid as string,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
description: true,
|
||||
isRecorded: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
timeZone: true,
|
||||
name: true,
|
||||
email: true,
|
||||
organization: {
|
||||
select: {
|
||||
calVideoLogo: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
references: {
|
||||
select: {
|
||||
uid: true,
|
||||
type: true,
|
||||
meetingUrl: true,
|
||||
meetingPassword: true,
|
||||
},
|
||||
where: {
|
||||
type: "daily_video",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking || booking.references.length === 0 || !booking.references[0].meetingUrl) {
|
||||
return redirect("/video/no-meeting-found");
|
||||
}
|
||||
|
||||
//daily.co calls have a 60 minute exit buffer when a user enters a call when it's not available it will trigger the modals
|
||||
const now = new Date();
|
||||
const exitDate = new Date(now.getTime() - 60 * 60 * 1000);
|
||||
|
||||
//find out if the meeting is in the past
|
||||
const isPast = booking?.endTime <= exitDate;
|
||||
if (isPast) {
|
||||
return redirect(`/video/meeting-ended/${booking?.uid}`);
|
||||
}
|
||||
|
||||
const bookingObj = Object.assign({}, booking, {
|
||||
startTime: booking.startTime.toString(),
|
||||
endTime: booking.endTime.toString(),
|
||||
});
|
||||
|
||||
const session = await getServerSession({ req: context.req });
|
||||
|
||||
// set meetingPassword to null for guests
|
||||
if (session?.user.id !== bookingObj.user?.id) {
|
||||
bookingObj.references.forEach((bookRef) => {
|
||||
bookRef.meetingPassword = null;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
meetingUrl: bookingObj.references[0].meetingUrl ?? "",
|
||||
...(typeof bookingObj.references[0].meetingPassword === "string" && {
|
||||
meetingPassword: bookingObj.references[0].meetingPassword,
|
||||
}),
|
||||
booking: {
|
||||
...bookingObj,
|
||||
...(bookingObj.description && { description: md.render(bookingObj.description) }),
|
||||
},
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
};
|
||||
}
|
||||
|
||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import OldPage from "@pages/video/meeting-ended/[uid]";
|
||||
import Page from "@pages/video/meeting-ended/[uid]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
@@ -12,41 +11,6 @@ export const generateMetadata = async () =>
|
||||
() => "Meeting Unavailable"
|
||||
);
|
||||
|
||||
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
user: {
|
||||
select: {
|
||||
credentials: true,
|
||||
},
|
||||
},
|
||||
references: {
|
||||
select: {
|
||||
uid: true,
|
||||
type: true,
|
||||
meetingUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
if (!booking) {
|
||||
return redirect("/video/no-meeting-found");
|
||||
}
|
||||
|
||||
const bookingObj = Object.assign({}, booking, {
|
||||
startTime: booking.startTime.toString(),
|
||||
endTime: booking.endTime.toString(),
|
||||
});
|
||||
|
||||
return {
|
||||
booking: bookingObj,
|
||||
};
|
||||
}
|
||||
|
||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import OldPage from "@pages/video/meeting-not-started/[uid]";
|
||||
import { type Params } from "app/_types";
|
||||
import Page from "@pages/video/meeting-not-started/[uid]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
}>;
|
||||
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
|
||||
|
||||
export const generateMetadata = async ({ params }: PageProps) => {
|
||||
const booking = await prisma.booking.findUnique({
|
||||
@@ -25,26 +22,6 @@ export const generateMetadata = async ({ params }: PageProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
||||
},
|
||||
select: bookingMinimalSelect,
|
||||
});
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
if (!booking) {
|
||||
return redirect("/video/no-meeting-found");
|
||||
}
|
||||
|
||||
const bookingObj = Object.assign({}, booking, {
|
||||
startTime: booking.startTime.toString(),
|
||||
endTime: booking.endTime.toString(),
|
||||
});
|
||||
|
||||
return {
|
||||
booking: bookingObj,
|
||||
};
|
||||
}
|
||||
|
||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import LegacyPage from "@pages/video/no-meeting-found";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
@@ -11,6 +11,7 @@ export const generateMetadata = async () =>
|
||||
(t) => t("no_meeting_found")
|
||||
);
|
||||
|
||||
// ssr was added by Intuita, legacy page does not have it
|
||||
const getData = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import LegacyPage, { getStaticProps } from "@pages/workflows/[workflow]";
|
||||
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { notFound } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
import LegacyPage from "@calcom/features/ee/workflows/pages/workflow";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
const querySchema = z.object({
|
||||
workflow: z.string(),
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||
const { workflow } = await getProps(
|
||||
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Record<string, string | string[]>;
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const { workflow } = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||
);
|
||||
return await _generateMetadata(
|
||||
() => workflow ?? "Untitled",
|
||||
@@ -23,19 +23,12 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
||||
);
|
||||
};
|
||||
|
||||
async function getProps(context: GetServerSidePropsContext) {
|
||||
const safeParams = querySchema.safeParse(context.params);
|
||||
|
||||
console.log("Built workflow page:", safeParams);
|
||||
if (!safeParams.success) {
|
||||
return notFound();
|
||||
}
|
||||
return { workflow: safeParams.data.workflow };
|
||||
}
|
||||
const getData = withAppDirSsg(getStaticProps);
|
||||
|
||||
export const generateStaticParams = () => [];
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getProps, Page: LegacyPage })<"P">;
|
||||
// @ts-expect-error TODO: fix this
|
||||
export default WithLayout({ getLayout: null, getData, Page: LegacyPage })<"P">;
|
||||
export const dynamic = "force-static";
|
||||
// generate segments on demand
|
||||
export const dynamicParams = true;
|
||||
|
||||
@@ -9,7 +9,7 @@ import PageWrapper from "@components/PageWrapperAppDir";
|
||||
type WithLayoutParams<T extends Record<string, any>> = {
|
||||
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
|
||||
Page?: (props: T) => React.ReactElement | null;
|
||||
getData?: (arg: GetServerSidePropsContext) => Promise<T>;
|
||||
getData?: (arg: GetServerSidePropsContext) => Promise<T | undefined>;
|
||||
isBookingPage?: boolean;
|
||||
};
|
||||
|
||||
@@ -22,9 +22,11 @@ export function WithLayout<T extends Record<string, any>>({
|
||||
return async <P extends "P" | "L">(p: P extends "P" ? PageProps : LayoutProps) => {
|
||||
const h = headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
const props = getData
|
||||
? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext)
|
||||
: ({} as T);
|
||||
let props = {} as T;
|
||||
|
||||
if ("searchParams" in p && getData) {
|
||||
props = (await getData(buildLegacyCtx(h, cookies(), p.params, p.searchParams))) ?? ({} as T);
|
||||
}
|
||||
|
||||
const children = "children" in p ? p.children : null;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import NotFoundPage from "@pages/404";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetStaticPropsContext } from "next";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
import { getTranslations } from "@server/lib/getTranslations";
|
||||
|
||||
const getData = async (context: GetStaticPropsContext) => {
|
||||
const ssg = await ssgInit(context);
|
||||
const i18n = await getTranslations(context);
|
||||
|
||||
return {
|
||||
dehydratedState: ssg.dehydrate(),
|
||||
i18n,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -89,8 +89,7 @@ function PageWrapper(props: AppProps) {
|
||||
</LicenseRequired>
|
||||
) : (
|
||||
<Component {...pageProps} err={err} />
|
||||
),
|
||||
router
|
||||
)
|
||||
)}
|
||||
</AppProviders>
|
||||
);
|
||||
|
||||
@@ -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" }),
|
||||
@@ -528,7 +502,10 @@ const IntervalLimitItem = ({
|
||||
onIntervalSelect,
|
||||
}: IntervalLimitItemProps) => {
|
||||
return (
|
||||
<div className="mb-4 flex max-h-9 items-center space-x-2 text-sm rtl:space-x-reverse" key={limitKey}>
|
||||
<div
|
||||
data-testid="add-limit"
|
||||
className="mb-4 flex max-h-9 items-center space-x-2 text-sm rtl:space-x-reverse"
|
||||
key={limitKey}>
|
||||
<TextField
|
||||
required
|
||||
type="number"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Webhook } from "@prisma/client";
|
||||
import { useSession } from "next-auth/react";
|
||||
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
@@ -5,10 +6,16 @@ import { useFormContext } from "react-hook-form";
|
||||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||
import { WebhookForm } from "@calcom/features/webhooks/components";
|
||||
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
|
||||
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
|
||||
import { subscriberUrlReserved } from "@calcom/features/webhooks/lib/subscriberUrlReserved";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Alert, Button, EmptyScreen, SettingsToggle } from "@calcom/ui";
|
||||
import { PhoneCall } from "@calcom/ui/components/icon";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, EmptyScreen, SettingsToggle, Dialog, DialogContent, showToast } from "@calcom/ui";
|
||||
import { PhoneCall, Plus, Lock, Webhook as TbWebhook } from "@calcom/ui/components/icon";
|
||||
|
||||
type InstantEventControllerProps = {
|
||||
eventType: EventTypeSetup;
|
||||
@@ -82,11 +89,7 @@ export default function InstantEventController({
|
||||
}
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
{instantEventState && (
|
||||
<div data-testid="instant-event-collapsible" className="flex flex-col gap-2 text-sm">
|
||||
<p>{t("warning_payment_instant_meeting_event")}</p>
|
||||
</div>
|
||||
)}
|
||||
{instantEventState && <InstantMeetingWebhooks eventType={eventType} />}
|
||||
</div>
|
||||
</SettingsToggle>
|
||||
</>
|
||||
@@ -97,3 +100,201 @@ export default function InstantEventController({
|
||||
</LicenseRequired>
|
||||
);
|
||||
}
|
||||
|
||||
const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const { data: webhooks } = trpc.viewer.webhook.list.useQuery({
|
||||
eventTypeId: eventType.id,
|
||||
eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING],
|
||||
});
|
||||
const { data: installedApps, isPending } = trpc.viewer.integrations.useQuery({
|
||||
variant: "other",
|
||||
onlyInstalled: true,
|
||||
});
|
||||
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [webhookToEdit, setWebhookToEdit] = useState<Webhook>();
|
||||
|
||||
const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({
|
||||
async onSuccess() {
|
||||
setEditModalOpen(false);
|
||||
await utils.viewer.webhook.list.invalidate();
|
||||
showToast(t("webhook_updated_successfully"), "success");
|
||||
},
|
||||
onError(error) {
|
||||
showToast(`${error.message}`, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
|
||||
async onSuccess() {
|
||||
showToast(t("webhook_created_successfully"), "success");
|
||||
await utils.viewer.webhook.list.invalidate();
|
||||
setCreateModalOpen(false);
|
||||
},
|
||||
onError(error) {
|
||||
showToast(`${error.message}`, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const onCreateWebhook = async (values: WebhookFormSubmitData) => {
|
||||
if (
|
||||
subscriberUrlReserved({
|
||||
subscriberUrl: values.subscriberUrl,
|
||||
id: values.id,
|
||||
webhooks,
|
||||
eventTypeId: eventType.id,
|
||||
})
|
||||
) {
|
||||
showToast(t("webhook_subscriber_url_reserved"), "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!values.payloadTemplate) {
|
||||
values.payloadTemplate = null;
|
||||
}
|
||||
|
||||
createWebhookMutation.mutate({
|
||||
subscriberUrl: values.subscriberUrl,
|
||||
eventTriggers: values.eventTriggers,
|
||||
active: values.active,
|
||||
payloadTemplate: values.payloadTemplate,
|
||||
secret: values.secret,
|
||||
eventTypeId: eventType.id,
|
||||
});
|
||||
};
|
||||
|
||||
const NewWebhookButton = () => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid="new_webhook"
|
||||
StartIcon={Plus}
|
||||
onClick={() => setCreateModalOpen(true)}>
|
||||
{t("new_webhook")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager(
|
||||
eventType,
|
||||
t("locked_fields_admin_description"),
|
||||
t("locked_fields_member_description")
|
||||
);
|
||||
const webhookLockedStatus = shouldLockDisableProps("webhooks");
|
||||
|
||||
return (
|
||||
<div>
|
||||
{webhooks && !isPending && (
|
||||
<>
|
||||
<div>
|
||||
{webhooks.length ? (
|
||||
<>
|
||||
<div className="border-subtle my-2 rounded-md border">
|
||||
{webhooks.map((webhook, index) => {
|
||||
return (
|
||||
<WebhookListItem
|
||||
key={webhook.id}
|
||||
webhook={webhook}
|
||||
lastItem={webhooks.length === index + 1}
|
||||
canEditWebhook={!webhookLockedStatus.disabled}
|
||||
onEditWebhook={() => {
|
||||
setEditModalOpen(true);
|
||||
setWebhookToEdit(webhook);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="text-default text-sm font-normal">
|
||||
{t("warning_payment_instant_meeting_event")}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-default mb-4 text-sm font-normal">
|
||||
{t("warning_payment_instant_meeting_event")}
|
||||
</p>
|
||||
<EmptyScreen
|
||||
Icon={TbWebhook}
|
||||
headline={t("create_your_first_webhook")}
|
||||
description={t("create_instant_meeting_webhook_description")}
|
||||
buttonRaw={
|
||||
isChildrenManagedEventType && !isManagedEventType ? (
|
||||
<Button StartIcon={Lock} color="secondary" disabled>
|
||||
{t("locked_by_admin")}
|
||||
</Button>
|
||||
) : (
|
||||
<NewWebhookButton />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* New webhook dialog */}
|
||||
<Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}>
|
||||
<DialogContent
|
||||
enableOverflow
|
||||
title={t("create_webhook")}
|
||||
description={t("create_webhook_team_event_type")}>
|
||||
<WebhookForm
|
||||
noRoutingFormTriggers={true}
|
||||
onSubmit={onCreateWebhook}
|
||||
onCancel={() => setCreateModalOpen(false)}
|
||||
apps={installedApps?.items.map((app) => app.slug)}
|
||||
selectOnlyInstantMeetingOption={true}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Edit webhook dialog */}
|
||||
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
||||
<DialogContent enableOverflow title={t("edit_webhook")}>
|
||||
<WebhookForm
|
||||
noRoutingFormTriggers={true}
|
||||
webhook={webhookToEdit}
|
||||
apps={installedApps?.items.map((app) => app.slug)}
|
||||
onCancel={() => setEditModalOpen(false)}
|
||||
onSubmit={(values: WebhookFormSubmitData) => {
|
||||
if (
|
||||
subscriberUrlReserved({
|
||||
subscriberUrl: values.subscriberUrl,
|
||||
id: webhookToEdit?.id,
|
||||
webhooks,
|
||||
eventTypeId: eventType.id,
|
||||
})
|
||||
) {
|
||||
showToast(t("webhook_subscriber_url_reserved"), "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.changeSecret) {
|
||||
values.secret = values.newSecret.length ? values.newSecret : null;
|
||||
}
|
||||
|
||||
if (!values.payloadTemplate) {
|
||||
values.payloadTemplate = null;
|
||||
}
|
||||
|
||||
editWebhookMutation.mutate({
|
||||
id: webhookToEdit?.id || "",
|
||||
subscriberUrl: values.subscriberUrl,
|
||||
eventTriggers: values.eventTriggers,
|
||||
active: values.active,
|
||||
payloadTemplate: values.payloadTemplate,
|
||||
secret: values.secret,
|
||||
eventTypeId: webhookToEdit?.eventTypeId || undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user