Merge branch 'main' into feat/add-owner-during-org-creation
This commit is contained in:
commit
465cc89805
|
@ -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) {
|
||||
|
@ -88,7 +93,8 @@ export async function patchHandler(req: NextApiRequest) {
|
|||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
args.data.eventTriggers = [...new Set(args.data.eventTriggers)];
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
const result = await prisma.webhook.update(args);
|
||||
|
|
|
@ -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...
|
||||
|
@ -88,7 +93,8 @@ async function postHandler(req: NextApiRequest) {
|
|||
}
|
||||
|
||||
if (args.data.eventTriggers) {
|
||||
args.data.eventTriggers = [...new Set(args.data.eventTriggers)];
|
||||
const eventTriggersSet = new Set(eventTriggers);
|
||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||
}
|
||||
|
||||
const data = await prisma.webhook.create(args);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 });
|
|
@ -0,0 +1,4 @@
|
|||
import Page from "@pages/more";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
export default WithLayout({ getLayout: null, Page })<"P">;
|
|
@ -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`,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,6 +1,6 @@
|
|||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.6.3",
|
||||
"version": "3.6.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
|
|
@ -18,6 +18,7 @@ import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/or
|
|||
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";
|
||||
|
@ -412,9 +413,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: {
|
||||
|
|
|
@ -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,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 Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
|
|
|
@ -100,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,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -233,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 {
|
||||
|
@ -262,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 {
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"a_refund_failed": "Возврат не удался",
|
||||
"awaiting_payment_subject": "Ожидание оплаты: {{title}} на {{date}}",
|
||||
"meeting_awaiting_payment": "Ваша встреча ожидает оплаты",
|
||||
"dark_theme_contrast_error": "Темный цвет темы не проходит проверку на контрастность. Мы рекомендуем вам изменить этот цвет, чтобы ваши кнопки были более заметны.",
|
||||
"help": "Помощь",
|
||||
"price": "Цена",
|
||||
"paid": "Оплачено",
|
||||
|
|
|
@ -10,6 +10,7 @@ import dayjs from "@calcom/dayjs";
|
|||
import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
|
||||
|
@ -144,8 +145,8 @@ const BookerComponent = ({
|
|||
: bookerLayouts.defaultLayout;
|
||||
|
||||
useBrandColors({
|
||||
brandColor: event.data?.profile.brandColor,
|
||||
darkBrandColor: event.data?.profile.darkBrandColor,
|
||||
brandColor: event.data?.profile.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: event.data?.profile.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||
theme: event.data?.profile.theme,
|
||||
});
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ export const Day = ({
|
|||
<span
|
||||
className={classNames(
|
||||
"bg-brand-default absolute left-1/2 top-1/2 flex h-[5px] w-[5px] -translate-x-1/2 translate-y-[8px] items-center justify-center rounded-full align-middle sm:translate-y-[12px]",
|
||||
active && "invert"
|
||||
active && "bg-brand-accent"
|
||||
)}>
|
||||
<span className="sr-only">{t("today")}</span>
|
||||
</span>
|
||||
|
|
|
@ -26,7 +26,6 @@ const BrandColorsForm = ({
|
|||
const brandColorsFormMethods = useFormContext();
|
||||
const {
|
||||
formState: { isSubmitting: isBrandColorsFormSubmitting, isDirty: isBrandColorsFormDirty },
|
||||
handleSubmit,
|
||||
} = brandColorsFormMethods;
|
||||
|
||||
const [isCustomBrandColorChecked, setIsCustomBrandColorChecked] = useState(
|
||||
|
|
|
@ -180,8 +180,8 @@ const OrgAppearanceView = ({
|
|||
}}>
|
||||
<BrandColorsForm
|
||||
onSubmit={onBrandColorsFormSubmit}
|
||||
brandColor={currentOrg?.brandColor}
|
||||
darkBrandColor={currentOrg?.darkBrandColor}
|
||||
brandColor={currentOrg?.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR}
|
||||
darkBrandColor={currentOrg?.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
|
|
|
@ -61,7 +61,10 @@ const ProfileView = ({ team }: ProfileViewProps) => {
|
|||
await utils.viewer.teams.get.invalidate();
|
||||
if (res) {
|
||||
resetTheme({ theme: res.theme });
|
||||
resetBrandColors({ brandColor: res.brandColor, darkBrandColor: res.darkBrandColor });
|
||||
resetBrandColors({
|
||||
brandColor: res.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: res.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||
});
|
||||
}
|
||||
|
||||
showToast(t("your_team_updated_successfully"), "success");
|
||||
|
@ -139,8 +142,8 @@ const ProfileView = ({ team }: ProfileViewProps) => {
|
|||
}}>
|
||||
<BrandColorsForm
|
||||
onSubmit={onBrandColorsFormSubmit}
|
||||
brandColor={team?.brandColor}
|
||||
darkBrandColor={team?.darkBrandColor}
|
||||
brandColor={team?.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR}
|
||||
darkBrandColor={team?.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { useBrandColors } from "@calcom/embed-core/embed-iframe";
|
||||
|
||||
const BRAND_COLOR = "#292929";
|
||||
const DARK_BRAND_COLOR = "#fafafa";
|
||||
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "./constants";
|
||||
|
||||
const BRAND_COLOR = DEFAULT_LIGHT_BRAND_COLOR;
|
||||
const DARK_BRAND_COLOR = DEFAULT_DARK_BRAND_COLOR;
|
||||
|
||||
type Rgb = {
|
||||
r: number;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
ALTER TABLE "Team" ALTER COLUMN "brandColor" DROP NOT NULL,
|
||||
ALTER COLUMN "brandColor" DROP DEFAULT,
|
||||
ALTER COLUMN "darkBrandColor" DROP NOT NULL,
|
||||
ALTER COLUMN "darkBrandColor" DROP DEFAULT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ALTER COLUMN "brandColor" DROP NOT NULL,
|
||||
ALTER COLUMN "brandColor" DROP DEFAULT,
|
||||
ALTER COLUMN "darkBrandColor" DROP NOT NULL,
|
||||
ALTER COLUMN "darkBrandColor" DROP DEFAULT;
|
||||
|
||||
UPDATE "Team" SET "brandColor" = NULL WHERE "brandColor" = '#292929';
|
||||
UPDATE "Team" SET "darkBrandColor" = NULL WHERE "darkBrandColor" = '#fafafa';
|
||||
|
||||
UPDATE "users" SET "brandColor" = NULL WHERE "brandColor" = '#292929';
|
||||
UPDATE "users" SET "darkBrandColor" = NULL WHERE "darkBrandColor" = '#fafafa';
|
||||
|
|
@ -224,8 +224,8 @@ model User {
|
|||
availability Availability[]
|
||||
invitedTo Int?
|
||||
webhooks Webhook[]
|
||||
brandColor String @default("#292929")
|
||||
darkBrandColor String @default("#fafafa")
|
||||
brandColor String?
|
||||
darkBrandColor String?
|
||||
// the location where the events will end up
|
||||
destinationCalendar DestinationCalendar?
|
||||
away Boolean @default(false)
|
||||
|
@ -299,8 +299,8 @@ model Team {
|
|||
/// @zod.custom(imports.teamMetadataSchema)
|
||||
metadata Json?
|
||||
theme String?
|
||||
brandColor String @default("#292929")
|
||||
darkBrandColor String @default("#fafafa")
|
||||
brandColor String?
|
||||
darkBrandColor String?
|
||||
verifiedNumbers VerifiedNumber[]
|
||||
parentId Int?
|
||||
parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade)
|
||||
|
|
Loading…
Reference in New Issue
Block a user