Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18f637d94c | ||
|
|
e5eb121ab7 | ||
|
|
6612926aa5 | ||
|
|
93dc1247f1 | ||
|
|
24607873c0 | ||
|
|
8b37d91108 | ||
|
|
22b0644700 | ||
|
|
404a7c1550 | ||
|
|
19a2cd5ae4 | ||
|
|
0798e2940a | ||
|
|
b1bf94aa58 | ||
|
|
0a921c2929 | ||
|
|
67c318e527 | ||
|
|
41b8d2ab1a | ||
|
|
57124b48fe | ||
|
|
b76a2a4019 | ||
|
|
a28b9cacd2 | ||
|
|
abd90f6af8 | ||
|
|
0bdc45a1a5 | ||
|
|
65d9704f2b | ||
|
|
d8ba783369 | ||
|
|
782127a993 | ||
|
|
fbc3f7b51f | ||
|
|
619ccf4065 | ||
|
|
40d8f34e8d | ||
|
|
9c1e1d7312 | ||
|
|
710a1a7d38 | ||
|
|
f25605ef4d | ||
|
|
684575d0a4 | ||
|
|
bc6267e99b | ||
|
|
9901c91e9b | ||
|
|
dfe03efc37 | ||
|
|
0eabb56f07 | ||
|
|
cdd066c653 | ||
|
|
026f22fbaf | ||
|
|
5950c5a756 | ||
|
|
cd9d16be3e | ||
|
|
993f92acba | ||
|
|
6f110d21fd | ||
|
|
0df6777814 | ||
|
|
5f14cd31d1 | ||
|
|
070ec326aa | ||
|
|
4ca79af13f | ||
|
|
a2e70f9aad | ||
|
|
f186d2e41d | ||
|
|
aaeea250bc | ||
|
|
96af17d8d7 | ||
|
|
dfaa6d28e4 | ||
|
|
6a2de993bb | ||
|
|
b69d885ee6 |
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||
organization-name: calcom
|
||||
ignore-labels: "app-store, ai, authentication, automated-testing, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
||||
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
||||
apply-labels-from-issue:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -62,18 +62,18 @@ jobs:
|
||||
|
||||
- name: Get comment body
|
||||
id: get-comment-body
|
||||
if: success()
|
||||
if: success() && github.event.number
|
||||
run: |
|
||||
cd apps/web
|
||||
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
|
||||
body="${body//'%'/'%25'}"
|
||||
body="${body//$'\n'/'%0A'}"
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo "{name}={$body}" >> $GITHUB_OUTPUT
|
||||
echo "{body}=${body}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
if: success()
|
||||
if: success() && github.event.number
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
|
||||
@@ -28,7 +28,9 @@ const schemaBookingEditParams = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const schemaBookingEditBodyParams = schemaBookingBaseBodyParams.merge(schemaBookingEditParams);
|
||||
export const schemaBookingEditBodyParams = schemaBookingBaseBodyParams
|
||||
.merge(schemaBookingEditParams)
|
||||
.omit({ uid: true });
|
||||
|
||||
export const schemaBookingReadPublic = Booking.extend({
|
||||
attendees: z
|
||||
|
||||
@@ -33,7 +33,7 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
||||
* type: boolean
|
||||
* description: Delete all remaining bookings
|
||||
* - in: query
|
||||
* name: reason
|
||||
* name: cancellationReason
|
||||
* required: false
|
||||
* schema:
|
||||
* type: string
|
||||
|
||||
@@ -58,6 +58,7 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, body, userId } = req;
|
||||
const data = schemaTeamUpdateBodyParams.parse(body);
|
||||
const { teamId } = schemaQueryTeamId.parse(req.query);
|
||||
|
||||
/** Only OWNERS and ADMINS can edit teams */
|
||||
const _team = await prisma.team.findFirst({
|
||||
include: { members: true },
|
||||
@@ -65,6 +66,18 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
});
|
||||
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
|
||||
|
||||
const slugAlreadyExists = await prisma.team.findFirst({
|
||||
where: {
|
||||
slug: {
|
||||
mode: "insensitive",
|
||||
equals: data.slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (slugAlreadyExists && data.slug !== _team.slug)
|
||||
throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
|
||||
|
||||
// Check if parentId is related to this user
|
||||
if (data.parentId && data.parentId === teamId) {
|
||||
throw new HttpError({
|
||||
|
||||
@@ -69,7 +69,12 @@ import { schemaWebhookEditBodyParams, schemaWebhookReadPublic } from "~/lib/vali
|
||||
export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, query, userId, isAdmin } = req;
|
||||
const { id } = schemaQueryIdAsString.parse(query);
|
||||
const { eventTypeId, userId: bodyUserId, ...data } = schemaWebhookEditBodyParams.parse(req.body);
|
||||
const {
|
||||
eventTypeId,
|
||||
userId: bodyUserId,
|
||||
eventTriggers,
|
||||
...data
|
||||
} = schemaWebhookEditBodyParams.parse(req.body);
|
||||
const args: Prisma.WebhookUpdateArgs = { where: { id }, data };
|
||||
|
||||
if (eventTypeId) {
|
||||
@@ -87,6 +92,11 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
const result = await prisma.webhook.update(args);
|
||||
return { webhook: schemaWebhookReadPublic.parse(result) };
|
||||
}
|
||||
|
||||
@@ -66,7 +66,12 @@ import { schemaWebhookCreateBodyParams, schemaWebhookReadPublic } from "~/lib/va
|
||||
*/
|
||||
async function postHandler(req: NextApiRequest) {
|
||||
const { userId, isAdmin, prisma } = req;
|
||||
const { eventTypeId, userId: bodyUserId, ...body } = schemaWebhookCreateBodyParams.parse(req.body);
|
||||
const {
|
||||
eventTypeId,
|
||||
userId: bodyUserId,
|
||||
eventTriggers,
|
||||
...body
|
||||
} = schemaWebhookCreateBodyParams.parse(req.body);
|
||||
const args: Prisma.WebhookCreateArgs = { data: { id: uuidv4(), ...body } };
|
||||
|
||||
// If no event type, we assume is for the current user. If admin we run more checks below...
|
||||
@@ -87,6 +92,11 @@ async function postHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
const data = await prisma.webhook.create(args);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
export const withAppDir =
|
||||
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
||||
async (context: GetServerSidePropsContext): Promise<T> => {
|
||||
const ssrResponse = await getServerSideProps(context);
|
||||
|
||||
if ("redirect" in ssrResponse) {
|
||||
redirect(ssrResponse.redirect.destination);
|
||||
}
|
||||
if ("notFound" in ssrResponse) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
...ssrResponse.props,
|
||||
// includes dehydratedState required for future page trpcPropvider
|
||||
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { isNotFoundError } from "next/dist/client/components/not-found";
|
||||
import { getURLFromRedirectError, isRedirectError } from "next/dist/client/components/redirect";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
export type EmbedProps = {
|
||||
isEmbed?: boolean;
|
||||
};
|
||||
|
||||
export default function withEmbedSsrAppDir<T extends Record<string, any>>(
|
||||
getData: (context: GetServerSidePropsContext) => Promise<T>
|
||||
) {
|
||||
return async (context: GetServerSidePropsContext): Promise<T> => {
|
||||
const { embed, layout } = context.query;
|
||||
|
||||
try {
|
||||
const props = await getData(context);
|
||||
|
||||
return {
|
||||
...props,
|
||||
isEmbed: true,
|
||||
};
|
||||
} catch (e) {
|
||||
if (isRedirectError(e)) {
|
||||
const destinationUrl = getURLFromRedirectError(e);
|
||||
let urlPrefix = "";
|
||||
|
||||
// Get the URL parsed from URL so that we can reliably read pathname and searchParams from it.
|
||||
const destinationUrlObj = new URL(destinationUrl, WEBAPP_URL);
|
||||
|
||||
// If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain.
|
||||
if (destinationUrl.search(/^(http:|https:).*/) !== -1) {
|
||||
urlPrefix = destinationUrlObj.origin;
|
||||
} else {
|
||||
// Don't use any prefix for relative URLs to ensure we stay on the same domain
|
||||
urlPrefix = "";
|
||||
}
|
||||
|
||||
const destinationQueryStr = destinationUrlObj.searchParams.toString();
|
||||
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
|
||||
const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${
|
||||
destinationQueryStr ? `${destinationQueryStr}&` : ""
|
||||
}layout=${layout}&embed=${embed}`;
|
||||
|
||||
redirect(newDestinationUrl);
|
||||
}
|
||||
|
||||
if (isNotFoundError(e)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import LegacyPage from "@pages/apps/categories/index";
|
||||
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 type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
@@ -17,11 +16,9 @@ export const generateMetadata = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(ctx);
|
||||
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
let appStore;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AppsPage from "@pages/apps";
|
||||
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";
|
||||
@@ -10,8 +11,6 @@ import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import type { AppCategories } from "@calcom/prisma/enums";
|
||||
|
||||
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
@@ -21,11 +20,9 @@ export const generateMetadata = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(ctx);
|
||||
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
let appStore, userAdminTeams: UserAdminTeams;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import OldPage from "@pages/booking/[uid]";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getData } from "../page";
|
||||
|
||||
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 });
|
||||
@@ -0,0 +1,204 @@
|
||||
import OldPage from "@pages/booking/[uid]";
|
||||
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 { 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 { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } from "@lib/booking";
|
||||
|
||||
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 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,
|
||||
};
|
||||
};
|
||||
|
||||
// @ts-expect-error Argument of type '{ req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
||||
@@ -1,13 +1,12 @@
|
||||
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 type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||
@@ -26,7 +25,7 @@ export const generateStaticParams = async () => {
|
||||
return validStatuses.map((status) => ({ status }));
|
||||
};
|
||||
|
||||
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const parsedParams = querySchema.safeParse(ctx.params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import LegacyPage, { type PageProps } from "@pages/d/[link]/[slug]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
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 { 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
|
||||
);
|
||||
|
||||
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
||||
const rescheduleUid = booking?.uid;
|
||||
const { trpc } = await import("@calcom/trpc");
|
||||
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||
{ username: user ?? "", eventSlug: slug ?? "", isTeamEvent, org: entity.orgSlug ?? null },
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
const profileName = event?.profile?.name ?? "";
|
||||
const title = event?.title ?? "";
|
||||
return await _generateMetadata(
|
||||
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout })<"L">;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import EnterprisePage from "@components/EnterprisePage";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("create_your_org"),
|
||||
(t) => t("create_your_org_description")
|
||||
);
|
||||
|
||||
export default EnterprisePage;
|
||||
@@ -1,25 +1,19 @@
|
||||
import LegacyPage from "@pages/getting-started/[[...step]]";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||
const req = { headers: headers(), cookies: cookies() };
|
||||
|
||||
//@ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest
|
||||
const session = await getServerSession({ req });
|
||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||
const session = await getServerSession({ req: ctx.req });
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return redirect("/auth/login");
|
||||
}
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
const ssr = await ssrInit(ctx);
|
||||
await ssr.viewer.me.prefetch();
|
||||
|
||||
@@ -54,7 +48,7 @@ const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||
|
||||
return {
|
||||
dehydratedState: ssr.dehydrate(),
|
||||
hasPendingInvites: user.teams.find((team: any) => team.accepted === false) ?? false,
|
||||
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
|
||||
requiresLicense: false,
|
||||
themeBasis: null,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import LegacyPage from "@pages/insights/index";
|
||||
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";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
() => "Insights",
|
||||
(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 {};
|
||||
}
|
||||
|
||||
export default WithLayout({ getLayout, getData, Page: LegacyPage });
|
||||
@@ -0,0 +1,13 @@
|
||||
import LegacyPage from "@pages/maintenance";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => `${t("under_maintenance")} | ${APP_NAME}`,
|
||||
(t) => t("under_maintenance_description", { appName: APP_NAME })
|
||||
);
|
||||
|
||||
export default WithLayout({ getLayout: null, Page: LegacyPage })<"P">;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Page from "@pages/more";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export default WithLayout({ getLayout: null, Page })<"P">;
|
||||
@@ -0,0 +1,167 @@
|
||||
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 { 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(
|
||||
// the title does not contain the eventName as in the legacy page
|
||||
(t) => `${t("payment")} | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: PaymentPage });
|
||||
@@ -0,0 +1,21 @@
|
||||
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
}>;
|
||||
|
||||
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);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,30 @@
|
||||
import OldPage, { getServerSideProps as _getServerSideProps } from "@pages/reschedule/[uid]";
|
||||
import { withAppDir } from "app/AppDirSSRHOC";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
() => "",
|
||||
() => ""
|
||||
);
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
}>;
|
||||
|
||||
const getData = withAppDir(_getServerSideProps);
|
||||
|
||||
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 getData(legacyCtx);
|
||||
|
||||
return <OldPage />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/my-account/appearance";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("appearance"),
|
||||
(t) => t("appearance_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/my-account/calendars";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("calendars"),
|
||||
(t) => t("calendars_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/my-account/conferencing";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("conferencing"),
|
||||
(t) => t("conferencing_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/my-account/general";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("general"),
|
||||
(t) => t("general_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/my-account/profile";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("profile"),
|
||||
(t) => t("profile_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,11 @@
|
||||
import LegacyPage, { WrappedAboutOrganizationPage } from "@pages/settings/organizations/[id]/about";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("about_your_organization"),
|
||||
(t) => t("about_your_organization_description")
|
||||
);
|
||||
|
||||
export default WithLayout({ Page: LegacyPage, getLayout: WrappedAboutOrganizationPage });
|
||||
@@ -0,0 +1,11 @@
|
||||
import LegacyPage, { WrapperAddNewTeamsPage } from "@pages/settings/organizations/[id]/add-teams";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("create_your_teams"),
|
||||
(t) => t("create_your_teams_description")
|
||||
);
|
||||
|
||||
export default WithLayout({ Page: LegacyPage, getLayout: WrapperAddNewTeamsPage });
|
||||
@@ -0,0 +1,35 @@
|
||||
import LegacyPage, {
|
||||
buildWrappedOnboardTeamMembersPage,
|
||||
} from "@pages/settings/organizations/[id]/onboard-admins";
|
||||
import { type Params } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
}>;
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("invite_organization_admins"),
|
||||
(t) => t("invite_organization_admins_description")
|
||||
);
|
||||
|
||||
const Page = ({ params }: PageProps) => {
|
||||
const h = headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
getLayout={(page: React.ReactElement) => buildWrappedOnboardTeamMembersPage(params.id, page)}
|
||||
requiresLicense={false}
|
||||
nonce={nonce}
|
||||
themeBasis={null}>
|
||||
<LegacyPage />
|
||||
</PageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,11 @@
|
||||
import LegacyPage, { WrappedSetPasswordPage } from "@pages/settings/organizations/[id]/set-password";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("set_a_password"),
|
||||
(t) => t("set_a_password_description")
|
||||
);
|
||||
|
||||
export default WithLayout({ Page: LegacyPage, getLayout: WrappedSetPasswordPage });
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/appearance";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("appearance"),
|
||||
(t) => t("appearance_org_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/billing/index";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("billing"),
|
||||
(t) => t("manage_billing_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/general";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("general"),
|
||||
(t) => t("general_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/members";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("organization_members"),
|
||||
(t) => t("organization_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,34 @@
|
||||
import LegacyPage, { WrappedCreateNewOrganizationPage } from "@pages/settings/organizations/new/index";
|
||||
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";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("set_up_your_organization"),
|
||||
(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,
|
||||
Page: LegacyPage,
|
||||
getData: getPageProps,
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/profile";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("profile"),
|
||||
(t) => t("profile_org_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/teams/pages/team-appearance-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("booking_appearance"),
|
||||
(t) => t("appearance_team_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/other-team-members-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("team_members"),
|
||||
(t) => t("members_team_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/other-team-profile-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("profile"),
|
||||
(t) => t("profile_team_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/organizations/pages/settings/other-team-listing-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("org_admin_other_teams"),
|
||||
(t) => t("org_admin_other_teams_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/security/impersonation";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("impersonation"),
|
||||
(t) => t("impersonation_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/security/password";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("password"),
|
||||
(t) => t("password_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,11 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
import Page from "@calcom/features/ee/sso/page/user-sso-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("sso_configuration"),
|
||||
(t) => t("sso_configuration_description")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||
|
||||
export default WithLayout({ getLayout });
|
||||
@@ -0,0 +1,10 @@
|
||||
import Page from "@pages/settings/security/two-factor-auth";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("two_factor_auth"),
|
||||
(t) => t("add_an_extra_layer_of_security")
|
||||
);
|
||||
|
||||
export default Page;
|
||||
@@ -1,13 +1,12 @@
|
||||
import OldPage from "@pages/teams/index";
|
||||
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 type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
@@ -16,14 +15,12 @@ export const generateMetadata = async () =>
|
||||
(t) => t("create_manage_teams_collaborative")
|
||||
);
|
||||
|
||||
async function getData(context: ReturnType<typeof buildLegacyCtx>) {
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
async function getData(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
await ssr.viewer.me.prefetch();
|
||||
|
||||
const session = await getServerSession({
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | (IncomingMessage & { cookies: Partial<{ [key: string]: string; }>; })'.
|
||||
req: context.req,
|
||||
});
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@ import OldPage from "@pages/video/[uid]";
|
||||
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 type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
@@ -20,8 +19,7 @@ export const generateMetadata = async () =>
|
||||
|
||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||
|
||||
async function getData(context: ReturnType<typeof buildLegacyCtx>) {
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
async function getData(context: GetServerSidePropsContext) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
@@ -79,12 +77,11 @@ async function getData(context: ReturnType<typeof buildLegacyCtx>) {
|
||||
endTime: booking.endTime.toString(),
|
||||
});
|
||||
|
||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | (IncomingMessage & { cookies: Partial<{ [key: string]: string; }>; })'.
|
||||
const session = await getServerSession({ req: context.req });
|
||||
|
||||
// set meetingPassword to null for guests
|
||||
if (session?.user.id !== bookingObj.user?.id) {
|
||||
bookingObj.references.forEach((bookRef: any) => {
|
||||
bookingObj.references.forEach((bookRef) => {
|
||||
bookRef.meetingPassword = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,5 +49,4 @@ async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolve
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error getData arg
|
||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
||||
|
||||
@@ -47,5 +47,4 @@ async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolve
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error getData arg
|
||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import LegacyPage from "@pages/video/no-meeting-found";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
@@ -12,8 +11,7 @@ export const generateMetadata = async () =>
|
||||
(t) => t("no_meeting_found")
|
||||
);
|
||||
|
||||
const getData = async (context: ReturnType<typeof buildLegacyCtx>) => {
|
||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||
const getData = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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
|
||||
);
|
||||
return await _generateMetadata(
|
||||
() => workflow ?? "Untitled",
|
||||
() => ""
|
||||
);
|
||||
};
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
export const generateStaticParams = () => [];
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getProps, Page: LegacyPage })<"P">;
|
||||
export const dynamic = "force-static";
|
||||
// generate segments on demand
|
||||
export const dynamicParams = true;
|
||||
export const revalidate = 10;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import LegacyPage from "@calcom/features/ee/workflows/pages/index";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("workflows"),
|
||||
(t) => t("workflows_to_automate_notifications")
|
||||
);
|
||||
|
||||
export default WithLayout({ getLayout, Page: LegacyPage })<"P">;
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LayoutProps, PageProps } from "app/_types";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
@@ -7,15 +8,17 @@ import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
type WithLayoutParams<T extends Record<string, any>> = {
|
||||
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
|
||||
Page?: (props: T) => React.ReactElement;
|
||||
getData?: (arg: ReturnType<typeof buildLegacyCtx>) => Promise<T>;
|
||||
Page?: (props: T) => React.ReactElement | null;
|
||||
getData?: (arg: GetServerSidePropsContext) => Promise<T>;
|
||||
};
|
||||
|
||||
export function WithLayout<T extends Record<string, any>>({ getLayout, getData, Page }: WithLayoutParams<T>) {
|
||||
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 T);
|
||||
const props = getData
|
||||
? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext)
|
||||
: ({} as T);
|
||||
|
||||
const children = "children" in p ? p.children : null;
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import NotFoundPage from "@pages/404";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import type { GetStaticPropsContext } from "next";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
const getData = async (context: GetStaticPropsContext) => {
|
||||
const ssg = await ssgInit(context);
|
||||
|
||||
return {
|
||||
dehydratedState: ssg.dehydrate(),
|
||||
};
|
||||
};
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: NotFoundPage });
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import { UpgradeTip } from "@calcom/features/tips";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, ButtonGroup } from "@calcom/ui";
|
||||
import { BarChart, CreditCard, Globe, Lock, Paintbrush, Users } from "@calcom/ui/components/icon";
|
||||
|
||||
export default function EnterprisePage() {
|
||||
const { t } = useLocale();
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <Globe className="h-5 w-5 text-red-500" />,
|
||||
title: t("branded_subdomain"),
|
||||
description: t("branded_subdomain_description"),
|
||||
},
|
||||
{
|
||||
icon: <BarChart className="h-5 w-5 text-blue-500" />,
|
||||
title: t("org_insights"),
|
||||
description: t("org_insights_description"),
|
||||
},
|
||||
{
|
||||
icon: <Paintbrush className="h-5 w-5 text-pink-500" />,
|
||||
title: t("extensive_whitelabeling"),
|
||||
description: t("extensive_whitelabeling_description"),
|
||||
},
|
||||
{
|
||||
icon: <Users className="h-5 w-5 text-orange-500" />,
|
||||
title: t("unlimited_teams"),
|
||||
description: t("unlimited_teams_description"),
|
||||
},
|
||||
{
|
||||
icon: <CreditCard className="h-5 w-5 text-green-500" />,
|
||||
title: t("unified_billing"),
|
||||
description: t("unified_billing_description"),
|
||||
},
|
||||
{
|
||||
icon: <Lock className="h-5 w-5 text-purple-500" />,
|
||||
title: t("advanced_managed_events"),
|
||||
description: t("advanced_managed_events_description"),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<ShellMain heading="Enterprise" subtitle={t("enterprise_description")}>
|
||||
<UpgradeTip
|
||||
plan="enterprise"
|
||||
title={t("create_your_org")}
|
||||
description={t("create_your_org_description")}
|
||||
features={features}
|
||||
background="/tips/enterprise"
|
||||
buttons={
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
<Button color="primary" href="https://i.cal.com/sales/enterprise?duration=25" target="_blank">
|
||||
{t("contact_sales")}
|
||||
</Button>
|
||||
<Button color="minimal" href="https://cal.com/enterprise" target="_blank">
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
}>
|
||||
<>Create Org</>
|
||||
</UpgradeTip>
|
||||
</ShellMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -116,7 +116,7 @@ function getNavigation(props: {
|
||||
{
|
||||
name: "workflows",
|
||||
href: `/event-types/${eventType.id}?tabName=workflows`,
|
||||
icon: PhoneCall,
|
||||
icon: Zap,
|
||||
info: `${enabledWorkflowsNumber} ${t("active")}`,
|
||||
},
|
||||
];
|
||||
@@ -219,7 +219,7 @@ function EventTypeSingleLayout({
|
||||
navigation.push({
|
||||
name: "instant_tab_title",
|
||||
href: `/event-types/${eventType.id}?tabName=instant`,
|
||||
icon: Zap,
|
||||
icon: PhoneCall,
|
||||
info: `instant_event_tab_description`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,8 +62,12 @@ const CustomI18nextProvider = (props: { children: React.ReactElement; i18n?: SSR
|
||||
// @TODO
|
||||
|
||||
const session = useSession();
|
||||
|
||||
// window.document.documentElement.lang can be empty in some cases, for instance when we rendering GlobalError (not-found) page.
|
||||
const locale =
|
||||
session?.data?.user.locale ?? typeof window !== "undefined" ? window.document.documentElement.lang : "en";
|
||||
session?.data?.user.locale ?? typeof window !== "undefined"
|
||||
? window.document.documentElement.lang || "en"
|
||||
: "en";
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const getEventTypesFromDB = async (id: number) => {
|
||||
const userSelect = {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
};
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true,
|
||||
eventName: true,
|
||||
recurringEvent: true,
|
||||
requiresConfirmation: true,
|
||||
userId: true,
|
||||
successRedirectUrl: true,
|
||||
customInputs: true,
|
||||
locations: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
bookingFields: true,
|
||||
disableGuests: true,
|
||||
timeZone: true,
|
||||
owner: {
|
||||
select: userSelect,
|
||||
},
|
||||
users: {
|
||||
select: userSelect,
|
||||
},
|
||||
hosts: {
|
||||
select: {
|
||||
user: {
|
||||
select: userSelect,
|
||||
},
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
},
|
||||
workflows: {
|
||||
select: {
|
||||
workflow: {
|
||||
select: {
|
||||
id: true,
|
||||
steps: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: true,
|
||||
seatsPerTimeSlot: true,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
periodStartDate: true,
|
||||
periodEndDate: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
const metadata = EventTypeMetaDataSchema.parse(eventType.metadata);
|
||||
|
||||
return {
|
||||
isDynamic: false,
|
||||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields(eventType),
|
||||
metadata,
|
||||
};
|
||||
};
|
||||
|
||||
export const handleSeatsEventTypeOnBooking = async (
|
||||
eventType: {
|
||||
seatsPerTimeSlot?: number | null;
|
||||
seatsShowAttendees: boolean | null;
|
||||
seatsShowAvailabilityCount: boolean | null;
|
||||
[x: string | number | symbol]: unknown;
|
||||
},
|
||||
bookingInfo: Partial<
|
||||
Prisma.BookingGetPayload<{
|
||||
include: {
|
||||
attendees: { select: { name: true; email: true } };
|
||||
seatsReferences: { select: { referenceUid: true } };
|
||||
user: {
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
email: true;
|
||||
username: true;
|
||||
timeZone: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>
|
||||
>,
|
||||
seatReferenceUid?: string,
|
||||
userId?: number
|
||||
) => {
|
||||
if (eventType?.seatsPerTimeSlot !== null) {
|
||||
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
|
||||
delete bookingInfo.description;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// @TODO: If handling teams, we need to do more check ups for this.
|
||||
if (bookingInfo?.user?.id === userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventType.seatsShowAttendees) {
|
||||
const seatAttendee = await prisma.bookingSeat.findFirst({
|
||||
where: {
|
||||
referenceUid: seatReferenceUid,
|
||||
},
|
||||
include: {
|
||||
attendee: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (seatAttendee) {
|
||||
const attendee = bookingInfo?.attendees?.find((a) => {
|
||||
return a.email === seatAttendee.attendee?.email;
|
||||
});
|
||||
bookingInfo["attendees"] = attendee ? [attendee] : [];
|
||||
} else {
|
||||
bookingInfo["attendees"] = [];
|
||||
}
|
||||
}
|
||||
return bookingInfo;
|
||||
};
|
||||
|
||||
export async function getRecurringBookings(recurringEventId: string | null) {
|
||||
if (!recurringEventId) return null;
|
||||
const recurringBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
recurringEventId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
},
|
||||
select: {
|
||||
startTime: true,
|
||||
},
|
||||
});
|
||||
return recurringBookings.map((obj) => obj.startTime.toString());
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
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 { 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 async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
// this is needed to prevent bundling of lib/booking to the client bundle
|
||||
// usually functions that are used in getServerSideProps are tree shaken from client bundle
|
||||
// but not in case when they are exported. So we have to dynamically load them, or to copy paste them to the /future/page.
|
||||
|
||||
const { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } = await import(
|
||||
"@lib/booking"
|
||||
);
|
||||
|
||||
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) return { notFound: true } as const;
|
||||
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) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const eventTypeRaw = !bookingInfoRaw.eventTypeId
|
||||
? getDefaultEvent(eventTypeSlug || "")
|
||||
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
|
||||
if (!eventTypeRaw) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
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)
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
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 {
|
||||
props: {
|
||||
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),
|
||||
trpcState: ssr.dehydrate(),
|
||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
||||
bookingInfo,
|
||||
paymentStatus: payment,
|
||||
...(tz && { tz }),
|
||||
userTimeFormat,
|
||||
requiresLoginToUpdate,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
import prismaMock from "../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "./getTemporaryOrgRedirect";
|
||||
|
||||
const mockData = {
|
||||
redirects: [] as {
|
||||
toUrl: string;
|
||||
from: string;
|
||||
redirectType: RedirectType;
|
||||
}[],
|
||||
};
|
||||
|
||||
function mockARedirectInDB({
|
||||
toUrl,
|
||||
slug,
|
||||
@@ -15,28 +23,38 @@ function mockARedirectInDB({
|
||||
slug: string;
|
||||
redirectType: RedirectType;
|
||||
}) {
|
||||
mockData.redirects.push({
|
||||
toUrl,
|
||||
from: slug,
|
||||
redirectType,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
prismaMock.tempOrgRedirect.findUnique.mockImplementation(({ where }) => {
|
||||
prismaMock.tempOrgRedirect.findMany.mockImplementation(({ where }) => {
|
||||
return new Promise((resolve) => {
|
||||
if (
|
||||
where.from_type_fromOrgId.type === redirectType &&
|
||||
where.from_type_fromOrgId.from === slug &&
|
||||
where.from_type_fromOrgId.fromOrgId === 0
|
||||
) {
|
||||
resolve({ toUrl });
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
const tempOrgRedirects: typeof mockData.redirects = [];
|
||||
where.from.in.forEach((whereSlug: string) => {
|
||||
const matchingRedirect = mockData.redirects.find((redirect) => {
|
||||
return where.type === redirect.redirectType && whereSlug === redirect.from && where.fromOrgId === 0;
|
||||
});
|
||||
if (matchingRedirect) {
|
||||
tempOrgRedirects.push(matchingRedirect);
|
||||
}
|
||||
});
|
||||
resolve(tempOrgRedirects);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockData.redirects = [];
|
||||
});
|
||||
|
||||
describe("getTemporaryOrgRedirect", () => {
|
||||
it("should generate event-type URL without existing query params", async () => {
|
||||
mockARedirectInDB({ slug: "slug", toUrl: "https://calcom.cal.com", redirectType: RedirectType.User });
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "slug",
|
||||
slugs: "slug",
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {},
|
||||
@@ -54,7 +72,7 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
mockARedirectInDB({ slug: "slug", toUrl: "https://calcom.cal.com", redirectType: RedirectType.User });
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "slug",
|
||||
slugs: "slug",
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {
|
||||
@@ -74,7 +92,7 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
mockARedirectInDB({ slug: "slug", toUrl: "https://calcom.cal.com", redirectType: RedirectType.User });
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "slug",
|
||||
slugs: "slug",
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {
|
||||
@@ -98,7 +116,7 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "seeded-team",
|
||||
slugs: "seeded-team",
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {
|
||||
@@ -122,7 +140,7 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "seeded-team",
|
||||
slugs: "seeded-team",
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {
|
||||
@@ -146,7 +164,7 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: "seeded-team",
|
||||
slugs: "seeded-team",
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {},
|
||||
@@ -159,4 +177,157 @@ describe("getTemporaryOrgRedirect", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate Dynamic Group Booking Profile Url", async () => {
|
||||
mockARedirectInDB({
|
||||
slug: "first",
|
||||
toUrl: "https://calcom.cal.com/first-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
mockARedirectInDB({
|
||||
slug: "second",
|
||||
toUrl: "https://calcom.cal.com/second-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: ["first", "second"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {},
|
||||
});
|
||||
|
||||
expect(redirect).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/first-in-org1+second-in-org1",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate Dynamic Group Booking Profile Url - same order", async () => {
|
||||
mockARedirectInDB({
|
||||
slug: "second",
|
||||
toUrl: "https://calcom.cal.com/second-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
mockARedirectInDB({
|
||||
slug: "first",
|
||||
toUrl: "https://calcom.cal.com/first-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: ["first", "second"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {},
|
||||
});
|
||||
|
||||
expect(redirect).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/first-in-org1+second-in-org1",
|
||||
},
|
||||
});
|
||||
|
||||
const redirect1 = await getTemporaryOrgRedirect({
|
||||
slugs: ["second", "first"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {},
|
||||
});
|
||||
|
||||
expect(redirect1).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/second-in-org1+first-in-org1",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate Dynamic Group Booking Profile Url with query params", async () => {
|
||||
mockARedirectInDB({
|
||||
slug: "first",
|
||||
toUrl: "https://calcom.cal.com/first-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
mockARedirectInDB({
|
||||
slug: "second",
|
||||
toUrl: "https://calcom.cal.com/second-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: ["first", "second"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: {
|
||||
abc: "1",
|
||||
},
|
||||
});
|
||||
|
||||
expect(redirect).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/first-in-org1+second-in-org1?abc=1",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate Dynamic Group Booking EventType Url", async () => {
|
||||
mockARedirectInDB({
|
||||
slug: "first",
|
||||
toUrl: "https://calcom.cal.com/first-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
mockARedirectInDB({
|
||||
slug: "second",
|
||||
toUrl: "https://calcom.cal.com/second-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: ["first", "second"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {},
|
||||
});
|
||||
|
||||
expect(redirect).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/first-in-org1+second-in-org1/30min",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate Dynamic Group Booking EventType Url with query params", async () => {
|
||||
mockARedirectInDB({
|
||||
slug: "first",
|
||||
toUrl: "https://calcom.cal.com/first-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
mockARedirectInDB({
|
||||
slug: "second",
|
||||
toUrl: "https://calcom.cal.com/second-in-org1",
|
||||
redirectType: RedirectType.Team,
|
||||
});
|
||||
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: ["first", "second"],
|
||||
redirectType: RedirectType.Team,
|
||||
eventTypeSlug: "30min",
|
||||
currentQuery: {
|
||||
abc: "1",
|
||||
},
|
||||
});
|
||||
|
||||
expect(redirect).toEqual({
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "https://calcom.cal.com/first-in-org1+second-in-org1/30min?abc=1",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,45 +7,65 @@ import type { RedirectType } from "@calcom/prisma/client";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["lib", "getTemporaryOrgRedirect"] });
|
||||
export const getTemporaryOrgRedirect = async ({
|
||||
slug,
|
||||
slugs,
|
||||
redirectType,
|
||||
eventTypeSlug,
|
||||
currentQuery,
|
||||
}: {
|
||||
slug: string;
|
||||
slugs: string[] | string;
|
||||
redirectType: RedirectType;
|
||||
eventTypeSlug: string | null;
|
||||
currentQuery: ParsedUrlQuery;
|
||||
}) => {
|
||||
const prisma = (await import("@calcom/prisma")).default;
|
||||
slugs = slugs instanceof Array ? slugs : [slugs];
|
||||
log.debug(
|
||||
`Looking for redirect for`,
|
||||
safeStringify({
|
||||
slug,
|
||||
slugs,
|
||||
redirectType,
|
||||
eventTypeSlug,
|
||||
})
|
||||
);
|
||||
const redirect = await prisma.tempOrgRedirect.findUnique({
|
||||
|
||||
const redirects = await prisma.tempOrgRedirect.findMany({
|
||||
where: {
|
||||
from_type_fromOrgId: {
|
||||
type: redirectType,
|
||||
from: slug,
|
||||
fromOrgId: 0,
|
||||
type: redirectType,
|
||||
from: {
|
||||
in: slugs,
|
||||
},
|
||||
fromOrgId: 0,
|
||||
},
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
log.debug(`Redirecting ${slug} to ${redirect.toUrl}`);
|
||||
const newDestinationWithoutQuery = eventTypeSlug ? `${redirect.toUrl}/${eventTypeSlug}` : redirect.toUrl;
|
||||
const currentQueryString = stringify(currentQuery);
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: `${newDestinationWithoutQuery}${currentQueryString ? `?${currentQueryString}` : ""}`,
|
||||
},
|
||||
} as const;
|
||||
const currentQueryString = stringify(currentQuery);
|
||||
if (!redirects.length) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
// Use the first redirect origin as the new origin as we aren't supposed to handle different org usernames in a group
|
||||
const newOrigin = new URL(redirects[0].toUrl).origin;
|
||||
const query = currentQueryString ? `?${currentQueryString}` : "";
|
||||
// Use the same order as in input slugs - It is important from Dynamic Group perspective as the first user's settings are used for various things
|
||||
const newSlugs = slugs.map((slug) => {
|
||||
const redirect = redirects.find((redirect) => redirect.from === slug);
|
||||
if (!redirect) {
|
||||
return slug;
|
||||
}
|
||||
const newSlug = new URL(redirect.toUrl).pathname.slice(1);
|
||||
return newSlug;
|
||||
});
|
||||
|
||||
const newSlug = newSlugs.join("+");
|
||||
const newPath = newSlug ? `/${newSlug}` : "";
|
||||
|
||||
const newDestination = `${newOrigin}${newPath}${eventTypeSlug ? `/${eventTypeSlug}` : ""}${query}`;
|
||||
log.debug(`Suggesting redirect from ${slugs} to ${newDestination}`);
|
||||
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: newDestination,
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.6.3",
|
||||
"version": "3.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -39,7 +39,7 @@
|
||||
"@calcom/tsconfig": "*",
|
||||
"@calcom/ui": "*",
|
||||
"@daily-co/daily-js": "^0.37.0",
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@formkit/auto-animate": "1.0.0-beta.5",
|
||||
"@glidejs/glide": "^3.5.2",
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { GetStaticPropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
+76
-31
@@ -2,6 +2,8 @@ import type { DehydratedState } from "@tanstack/react-query";
|
||||
import classNames from "classnames";
|
||||
import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { encode } from "querystring";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import type { z } from "zod";
|
||||
|
||||
@@ -11,10 +13,12 @@ import {
|
||||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
|
||||
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
@@ -40,6 +44,7 @@ import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect";
|
||||
|
||||
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [user] = users; //To be used when we only have a single user, not dynamic group
|
||||
useTheme(profile.theme);
|
||||
@@ -59,6 +64,8 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
||||
...query
|
||||
} = useRouterQuery();
|
||||
|
||||
const isRedirect = searchParams?.get("redirected") === "true" || false;
|
||||
const fromUserNameRedirected = searchParams?.get("username") || "";
|
||||
/*
|
||||
const telemetry = useTelemetry();
|
||||
useEffect(() => {
|
||||
@@ -77,6 +84,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
||||
}
|
||||
|
||||
const isEventListEmpty = eventTypes.length === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
@@ -100,6 +108,25 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
{isRedirect && (
|
||||
<div className="mb-8 rounded-md bg-blue-100 p-4 dark:border dark:bg-transparent dark:bg-transparent">
|
||||
<h2 className="text-default mb-2 text-sm font-semibold dark:text-white">
|
||||
{t("user_redirect_title", {
|
||||
username: fromUserNameRedirected,
|
||||
})}{" "}
|
||||
🏝️
|
||||
</h2>
|
||||
<p className="text-default text-sm">
|
||||
{t("user_redirect_description", {
|
||||
profile: {
|
||||
username: user.username,
|
||||
},
|
||||
username: fromUserNameRedirected,
|
||||
})}{" "}
|
||||
😄
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-8 text-center">
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
@@ -290,6 +317,31 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
const usernameList = getUsernameList(context.query.user as string);
|
||||
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||
const dataFetchStart = Date.now();
|
||||
let outOfOffice = false;
|
||||
const isDynamicGroup = usernameList.length > 1;
|
||||
if (usernameList.length === 1) {
|
||||
const result = await handleUserRedirection({ username: usernameList[0] });
|
||||
if (result && result.outOfOffice) {
|
||||
outOfOffice = true;
|
||||
}
|
||||
if (result && result.redirect?.destination) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: usernameList,
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: context.query,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
const usersWithoutAvatar = await prisma.user.findMany({
|
||||
where: {
|
||||
username: {
|
||||
@@ -323,21 +375,6 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
},
|
||||
});
|
||||
|
||||
const isDynamicGroup = usersWithoutAvatar.length > 1;
|
||||
if (isDynamicGroup) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: `/${usernameList.join("+")}/dynamic`,
|
||||
},
|
||||
} as {
|
||||
redirect: {
|
||||
permanent: false;
|
||||
destination: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const users = usersWithoutAvatar.map((user) => ({
|
||||
...user,
|
||||
organization: {
|
||||
@@ -347,17 +384,20 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
avatar: `/${user.username}/avatar.png`,
|
||||
}));
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: usernameList[0],
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: null,
|
||||
currentQuery: context.query,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
if (isDynamicGroup) {
|
||||
const destinationUrl = `/${usernameList.join("+")}/dynamic`;
|
||||
logger.debug(`Dynamic group detected, redirecting to ${destinationUrl}`);
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: destinationUrl,
|
||||
},
|
||||
} as {
|
||||
redirect: {
|
||||
permanent: false;
|
||||
destination: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
||||
@@ -374,9 +414,9 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
name: user.name || user.username || "",
|
||||
image: user.avatar,
|
||||
theme: user.theme,
|
||||
brandColor: user.brandColor,
|
||||
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||
avatarUrl: user.avatarUrl,
|
||||
darkBrandColor: user.darkBrandColor,
|
||||
darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||
allowSEOIndexing: user.allowSEOIndexing ?? true,
|
||||
username: user.username,
|
||||
organization: {
|
||||
@@ -400,11 +440,16 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
}));
|
||||
|
||||
// if profile only has one public event-type, redirect to it
|
||||
if (eventTypes.length === 1 && context.query.redirect !== "false") {
|
||||
if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) {
|
||||
// Redirect but don't change the URL
|
||||
const urlDestination = `/${user.username}/${eventTypes[0].slug}`;
|
||||
const { query } = context;
|
||||
const urlQuery = new URLSearchParams(encode(query));
|
||||
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: `/${user.username}/${eventTypes[0].slug}`,
|
||||
destination: `${urlDestination}?${urlQuery}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -421,7 +466,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
username: user.username,
|
||||
bio: user.bio,
|
||||
avatarUrl: user.avatarUrl,
|
||||
away: user.away,
|
||||
away: usernameList.length === 1 ? outOfOffice : user.away,
|
||||
verified: user.verified,
|
||||
})),
|
||||
entity: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
||||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { handleTypeRedirection } from "@calcom/features/booking-redirect/handle-type";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
|
||||
@@ -89,6 +90,19 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
const { ssrInit } = await import("@server/lib/ssr");
|
||||
const ssr = await ssrInit(context);
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
if (!org) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slugs: usernames,
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: slug,
|
||||
currentQuery: context.query,
|
||||
});
|
||||
|
||||
if (redirect) {
|
||||
return redirect;
|
||||
}
|
||||
}
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
@@ -111,7 +125,6 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
@@ -164,12 +177,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
const username = usernames[0];
|
||||
const { rescheduleUid, bookingUid } = context.query;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||
|
||||
let outOfOffice = false;
|
||||
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
||||
|
||||
if (!isOrgContext) {
|
||||
const redirect = await getTemporaryOrgRedirect({
|
||||
slug: usernames[0],
|
||||
slugs: usernames,
|
||||
redirectType: RedirectType.User,
|
||||
eventTypeSlug: slug,
|
||||
currentQuery: context.query,
|
||||
@@ -188,7 +200,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
organization: userOrgQuery(context.req, context.params?.orgSlug),
|
||||
},
|
||||
select: {
|
||||
away: true,
|
||||
id: true,
|
||||
hideBranding: true,
|
||||
allowSEOIndexing: true,
|
||||
},
|
||||
@@ -199,6 +211,18 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
// If user is found, quickly verify bookingRedirects
|
||||
const result = await handleTypeRedirection({
|
||||
userId: user.id,
|
||||
username,
|
||||
slug,
|
||||
});
|
||||
if (result && result.outOfOffice) {
|
||||
outOfOffice = true;
|
||||
}
|
||||
if (result && result.redirect?.destination) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
@@ -230,7 +254,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
length: eventData.length,
|
||||
metadata: eventData.metadata,
|
||||
},
|
||||
away: user?.away,
|
||||
away: outOfOffice,
|
||||
user: username,
|
||||
slug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
|
||||
@@ -55,6 +55,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const payload: OAuthTokenPayload = {
|
||||
userId: decodedRefreshToken.userId,
|
||||
teamId: decodedRefreshToken.teamId,
|
||||
scope: decodedRefreshToken.scope,
|
||||
token_type: "Access Token",
|
||||
clientId: client_id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useCallback } from "react";
|
||||
|
||||
@@ -104,24 +105,32 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="border-subtle bg-default mb-16 overflow-hidden rounded-md border">
|
||||
<ul className="divide-subtle divide-y" data-testid="schedules" ref={animationParentRef}>
|
||||
{schedules.map((schedule) => (
|
||||
<ScheduleListItem
|
||||
displayOptions={{
|
||||
hour12: meQuery.data?.timeFormat ? meQuery.data.timeFormat === 12 : undefined,
|
||||
timeZone: meQuery.data?.timeZone,
|
||||
}}
|
||||
key={schedule.id}
|
||||
schedule={schedule}
|
||||
isDeletable={schedules.length !== 1}
|
||||
updateDefault={updateMutation.mutate}
|
||||
deleteFunction={deleteMutation.mutate}
|
||||
duplicateFunction={duplicateMutation.mutate}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<>
|
||||
<div className="border-subtle bg-default overflow-hidden rounded-md border">
|
||||
<ul className="divide-subtle divide-y" data-testid="schedules" ref={animationParentRef}>
|
||||
{schedules.map((schedule) => (
|
||||
<ScheduleListItem
|
||||
displayOptions={{
|
||||
hour12: meQuery.data?.timeFormat ? meQuery.data.timeFormat === 12 : undefined,
|
||||
timeZone: meQuery.data?.timeZone,
|
||||
}}
|
||||
key={schedule.id}
|
||||
schedule={schedule}
|
||||
isDeletable={schedules.length !== 1}
|
||||
updateDefault={updateMutation.mutate}
|
||||
deleteFunction={deleteMutation.mutate}
|
||||
duplicateFunction={duplicateMutation.mutate}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="text-default mb-16 mt-4 hidden text-center text-sm md:block">
|
||||
{t("temporarily_out_of_office")}{" "}
|
||||
<Link href="settings/my-account/out-of-office" className="underline">
|
||||
{t("add_a_redirect")}
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import classNames from "classnames";
|
||||
import { createEvent } from "ics";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
@@ -22,35 +23,28 @@ import {
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { Price } from "@calcom/features/bookings/components/event-meta/Price";
|
||||
import { SMS_REMINDER_NUMBER_FIELD, SystemField } from "@calcom/features/bookings/lib/SystemField";
|
||||
import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import {
|
||||
formatToLocalizedDate,
|
||||
formatToLocalizedTime,
|
||||
formatToLocalizedTimezone,
|
||||
} from "@calcom/lib/date-fns";
|
||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
||||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema, customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Alert, Badge, Button, EmailInput, HeadSeo, useCalcomTheme } from "@calcom/ui";
|
||||
import { AlertCircle, Calendar, Check, ChevronLeft, ExternalLink, X } from "@calcom/ui/components/icon";
|
||||
|
||||
import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
|
||||
import { timeZone } from "@lib/clock";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
@@ -58,23 +52,7 @@ import PageWrapper from "@components/PageWrapper";
|
||||
import CancelBooking from "@components/booking/CancelBooking";
|
||||
import EventReservationSchema from "@components/schemas/EventReservationSchema";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const useBrandColors = ({
|
||||
brandColor,
|
||||
darkBrandColor,
|
||||
}: {
|
||||
brandColor?: string | null;
|
||||
darkBrandColor?: string | null;
|
||||
}) => {
|
||||
const brandTheme = useGetBrandingColours({
|
||||
lightVal: brandColor,
|
||||
darkVal: darkBrandColor,
|
||||
});
|
||||
useCalcomTheme(brandTheme);
|
||||
};
|
||||
|
||||
type SuccessProps = inferSSRProps<typeof getServerSideProps>;
|
||||
export { getServerSideProps };
|
||||
|
||||
const stringToBoolean = z
|
||||
.string()
|
||||
@@ -94,6 +72,22 @@ const querySchema = z.object({
|
||||
seatReferenceUid: z.string().optional(),
|
||||
});
|
||||
|
||||
const useBrandColors = ({
|
||||
brandColor,
|
||||
darkBrandColor,
|
||||
}: {
|
||||
brandColor?: string | null;
|
||||
darkBrandColor?: string | null;
|
||||
}) => {
|
||||
const brandTheme = useGetBrandingColours({
|
||||
lightVal: brandColor,
|
||||
darkVal: darkBrandColor,
|
||||
});
|
||||
useCalcomTheme(brandTheme);
|
||||
};
|
||||
|
||||
type SuccessProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Success(props: SuccessProps) {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
@@ -925,329 +919,3 @@ export function RecurringBookings({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getEventTypesFromDB = async (id: number) => {
|
||||
const userSelect = {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
hideBranding: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
email: true,
|
||||
timeZone: true,
|
||||
};
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true,
|
||||
eventName: true,
|
||||
recurringEvent: true,
|
||||
requiresConfirmation: true,
|
||||
userId: true,
|
||||
successRedirectUrl: true,
|
||||
customInputs: true,
|
||||
locations: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
bookingFields: true,
|
||||
disableGuests: true,
|
||||
timeZone: true,
|
||||
owner: {
|
||||
select: userSelect,
|
||||
},
|
||||
users: {
|
||||
select: userSelect,
|
||||
},
|
||||
hosts: {
|
||||
select: {
|
||||
user: {
|
||||
select: userSelect,
|
||||
},
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
},
|
||||
workflows: {
|
||||
select: {
|
||||
workflow: {
|
||||
select: {
|
||||
id: true,
|
||||
steps: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: true,
|
||||
seatsPerTimeSlot: true,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
periodStartDate: true,
|
||||
periodEndDate: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
const metadata = EventTypeMetaDataSchema.parse(eventType.metadata);
|
||||
|
||||
return {
|
||||
isDynamic: false,
|
||||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields(eventType),
|
||||
metadata,
|
||||
};
|
||||
};
|
||||
|
||||
const handleSeatsEventTypeOnBooking = async (
|
||||
eventType: {
|
||||
seatsPerTimeSlot?: number | null;
|
||||
seatsShowAttendees: boolean | null;
|
||||
seatsShowAvailabilityCount: boolean | null;
|
||||
[x: string | number | symbol]: unknown;
|
||||
},
|
||||
bookingInfo: Partial<
|
||||
Prisma.BookingGetPayload<{
|
||||
include: {
|
||||
attendees: { select: { name: true; email: true } };
|
||||
seatsReferences: { select: { referenceUid: true } };
|
||||
user: {
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
email: true;
|
||||
username: true;
|
||||
timeZone: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>
|
||||
>,
|
||||
seatReferenceUid?: string,
|
||||
userId?: number
|
||||
) => {
|
||||
if (eventType?.seatsPerTimeSlot !== null) {
|
||||
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
|
||||
delete bookingInfo.description;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// @TODO: If handling teams, we need to do more check ups for this.
|
||||
if (bookingInfo?.user?.id === userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventType.seatsShowAttendees) {
|
||||
const seatAttendee = await prisma.bookingSeat.findFirst({
|
||||
where: {
|
||||
referenceUid: seatReferenceUid,
|
||||
},
|
||||
include: {
|
||||
attendee: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (seatAttendee) {
|
||||
const attendee = bookingInfo?.attendees?.find((a) => {
|
||||
return a.email === seatAttendee.attendee?.email;
|
||||
});
|
||||
bookingInfo["attendees"] = attendee ? [attendee] : [];
|
||||
} else {
|
||||
bookingInfo["attendees"] = [];
|
||||
}
|
||||
}
|
||||
return bookingInfo;
|
||||
};
|
||||
|
||||
export async function getServerSideProps(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) return { notFound: true } as const;
|
||||
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) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const eventTypeRaw = !bookingInfoRaw.eventTypeId
|
||||
? getDefaultEvent(eventTypeSlug || "")
|
||||
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
|
||||
if (!eventTypeRaw) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
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)
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
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 {
|
||||
props: {
|
||||
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),
|
||||
trpcState: ssr.dehydrate(),
|
||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
||||
bookingInfo,
|
||||
paymentStatus: payment,
|
||||
...(tz && { tz }),
|
||||
userTimeFormat,
|
||||
requiresLoginToUpdate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getRecurringBookings(recurringEventId: string | null) {
|
||||
if (!recurringEventId) return null;
|
||||
const recurringBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
recurringEventId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
},
|
||||
select: {
|
||||
startTime: true,
|
||||
},
|
||||
});
|
||||
return recurringBookings.map((obj) => obj.startTime.toString());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
"use client";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[uid]";
|
||||
import { getServerSideProps as _getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
export { default } from "../[uid]";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -16,7 +18,7 @@ import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
export type PageProps = Omit<inferSSRProps<typeof getServerSideProps>, "trpcState"> & EmbedProps;
|
||||
|
||||
export default function Type({
|
||||
slug,
|
||||
|
||||
@@ -1,74 +1,17 @@
|
||||
import { getLayout } from "@calcom/features/MainLayout";
|
||||
import { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import { UpgradeTip } from "@calcom/features/tips";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, ButtonGroup } from "@calcom/ui";
|
||||
import { BarChart, CreditCard, Globe, Lock, Paintbrush, Users } from "@calcom/ui/components/icon";
|
||||
"use client";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayout";
|
||||
|
||||
import EnterprisePage from "@components/EnterprisePage";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
export default function EnterprisePage() {
|
||||
const { t } = useLocale();
|
||||
const ProxifiedEnterprisePage = new Proxy<{
|
||||
(): JSX.Element;
|
||||
PageWrapper?: typeof PageWrapper;
|
||||
getLayout?: typeof getLayout;
|
||||
}>(EnterprisePage, {});
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <Globe className="h-5 w-5 text-red-500" />,
|
||||
title: t("branded_subdomain"),
|
||||
description: t("branded_subdomain_description"),
|
||||
},
|
||||
{
|
||||
icon: <BarChart className="h-5 w-5 text-blue-500" />,
|
||||
title: t("org_insights"),
|
||||
description: t("org_insights_description"),
|
||||
},
|
||||
{
|
||||
icon: <Paintbrush className="h-5 w-5 text-pink-500" />,
|
||||
title: t("extensive_whitelabeling"),
|
||||
description: t("extensive_whitelabeling_description"),
|
||||
},
|
||||
{
|
||||
icon: <Users className="h-5 w-5 text-orange-500" />,
|
||||
title: t("unlimited_teams"),
|
||||
description: t("unlimited_teams_description"),
|
||||
},
|
||||
{
|
||||
icon: <CreditCard className="h-5 w-5 text-green-500" />,
|
||||
title: t("unified_billing"),
|
||||
description: t("unified_billing_description"),
|
||||
},
|
||||
{
|
||||
icon: <Lock className="h-5 w-5 text-purple-500" />,
|
||||
title: t("advanced_managed_events"),
|
||||
description: t("advanced_managed_events_description"),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<ShellMain heading="Enterprise" subtitle={t("enterprise_description")}>
|
||||
<UpgradeTip
|
||||
plan="enterprise"
|
||||
title={t("create_your_org")}
|
||||
description={t("create_your_org_description")}
|
||||
features={features}
|
||||
background="/tips/enterprise"
|
||||
buttons={
|
||||
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
|
||||
<ButtonGroup>
|
||||
<Button color="primary" href="https://i.cal.com/sales/enterprise?duration=25" target="_blank">
|
||||
{t("contact_sales")}
|
||||
</Button>
|
||||
<Button color="minimal" href="https://cal.com/enterprise" target="_blank">
|
||||
{t("learn_more")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
}>
|
||||
<>Create Org</>
|
||||
</UpgradeTip>
|
||||
</ShellMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ProxifiedEnterprisePage.PageWrapper = PageWrapper;
|
||||
ProxifiedEnterprisePage.getLayout = getLayout;
|
||||
|
||||
EnterprisePage.PageWrapper = PageWrapper;
|
||||
EnterprisePage.getLayout = getLayout;
|
||||
export default ProxifiedEnterprisePage;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayout";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Head from "next/head";
|
||||
|
||||
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// page can be a server component
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { URLSearchParams } from "url";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[uid]";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import type { z } from "zod";
|
||||
@@ -98,10 +100,15 @@ const AppearanceView = ({
|
||||
reset: resetBookerLayoutThemeReset,
|
||||
} = bookerLayoutFormMethods;
|
||||
|
||||
const DEFAULT_BRAND_COLOURS = {
|
||||
light: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||
dark: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||
};
|
||||
|
||||
const brandColorsFormMethods = useForm({
|
||||
defaultValues: {
|
||||
brandColor: user.brandColor || DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: user.darkBrandColor || DEFAULT_DARK_BRAND_COLOR,
|
||||
brandColor: DEFAULT_BRAND_COLOURS.light,
|
||||
darkBrandColor: DEFAULT_BRAND_COLOURS.dark,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -231,12 +238,12 @@ const AppearanceView = ({
|
||||
<Controller
|
||||
name="brandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={user.brandColor}
|
||||
defaultValue={DEFAULT_BRAND_COLOURS.light}
|
||||
render={() => (
|
||||
<div>
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.brandColor}
|
||||
defaultValue={DEFAULT_BRAND_COLOURS.light}
|
||||
resetDefaultValue={DEFAULT_LIGHT_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
@@ -260,12 +267,12 @@ const AppearanceView = ({
|
||||
<Controller
|
||||
name="darkBrandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={user.darkBrandColor}
|
||||
defaultValue={DEFAULT_BRAND_COLOURS.dark}
|
||||
render={() => (
|
||||
<div className="mt-6 sm:mt-0">
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.darkBrandColor}
|
||||
defaultValue={DEFAULT_BRAND_COLOURS.dark}
|
||||
resetDefaultValue={DEFAULT_DARK_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useReducer } from "react";
|
||||
|
||||
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user