Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7b723c914 | ||
|
|
aad3988d60 | ||
|
|
c8172ab790 | ||
|
|
657f9dfd41 | ||
|
|
c48b209c04 | ||
|
|
b9a460d8b4 | ||
|
|
a7727b5ca6 | ||
|
|
9b661b75f1 | ||
|
|
487cc0751d | ||
|
|
f7f36978e1 | ||
|
|
c1dbb7cffd | ||
|
|
e722e75a6f | ||
|
|
455572b5f4 | ||
|
|
2fe4462f62 | ||
|
|
f9dc788007 | ||
|
|
6e293d2985 | ||
|
|
00334c5610 | ||
|
|
2f9c8cb441 | ||
|
|
39173900b1 | ||
|
|
bd61e01a5d | ||
|
|
aa0b3a2778 | ||
|
|
170feeae5e | ||
|
|
a67b57b216 | ||
|
|
821a16648f | ||
|
|
727f7df2c2 | ||
|
|
be5f7f83b5 | ||
|
|
02a49e2b4b | ||
|
|
22f92f1ee3 | ||
|
|
c7a797c0e0 | ||
|
|
447436dfff | ||
|
|
c415b2d7f5 | ||
|
|
c10c87c98e | ||
|
|
85f96f492d | ||
|
|
b1350d0764 | ||
|
|
bac2d1f6f0 | ||
|
|
fec504496a | ||
|
|
890fc92148 | ||
|
|
557bd8bf25 | ||
|
|
d8027fe248 | ||
|
|
116c765c36 |
@@ -365,7 +365,6 @@ APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
||||
APP_ROUTER_APPS_ENABLED=0
|
||||
APP_ROUTER_TEAM_ENABLED=0
|
||||
APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGIN_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGOUT_ENABLED=0
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/prisma"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
@@ -29,7 +29,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.78",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.79",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
} from "@calcom/platform-constants";
|
||||
|
||||
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
||||
@@ -45,6 +46,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
|
||||
allowedHeaders: [
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
CAL_API_VERSION_HEADER,
|
||||
"Accept",
|
||||
"Authorization",
|
||||
|
||||
@@ -37,7 +37,7 @@ import { Request } from "express";
|
||||
import { NextApiRequest } from "next/types";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
|
||||
import { X_CAL_CLIENT_ID, X_CAL_PLATFORM_EMBED } from "@calcom/platform-constants";
|
||||
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
|
||||
import {
|
||||
handleNewRecurringBooking,
|
||||
@@ -161,14 +161,15 @@ export class BookingsController_2024_04_15 {
|
||||
async createBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() body: CreateBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<Partial<BookingResponse>>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
const { orgSlug, locationUrl } = body;
|
||||
req.headers["x-cal-force-slug"] = orgSlug;
|
||||
try {
|
||||
const booking = await handleNewBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl)
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed)
|
||||
);
|
||||
if (booking.userId && booking.uid && booking.startTime) {
|
||||
void (await this.billingService.increaseUsageByUserId(booking.userId, {
|
||||
@@ -192,13 +193,16 @@ export class BookingsController_2024_04_15 {
|
||||
@Req() req: BookingRequest,
|
||||
@Param("bookingId") bookingId: string,
|
||||
@Body() _: CancelBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
if (bookingId) {
|
||||
try {
|
||||
req.body.id = parseInt(bookingId);
|
||||
const res = await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
|
||||
const res = await handleCancelBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
if (!res.onlyRemovedAttendee) {
|
||||
void (await this.billingService.cancelUsageByBookingUid(res.bookingUid));
|
||||
}
|
||||
@@ -246,7 +250,8 @@ export class BookingsController_2024_04_15 {
|
||||
async createRecurringBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() _: CreateRecurringBookingInput_2024_04_15[],
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<BookingResponse[]>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
try {
|
||||
@@ -258,7 +263,7 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
|
||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId)
|
||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
|
||||
createdBookings.forEach(async (booking) => {
|
||||
@@ -284,13 +289,14 @@ export class BookingsController_2024_04_15 {
|
||||
async createInstantBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() _: CreateBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
req.userId = (await this.getOwnerId(req)) ?? -1;
|
||||
try {
|
||||
const instantMeeting = await handleInstantMeeting(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId)
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
|
||||
if (instantMeeting.userId && instantMeeting.bookingUid) {
|
||||
@@ -332,8 +338,14 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
}
|
||||
|
||||
private async getOAuthClientsParams(clientId: string): Promise<OAuthRequestParams> {
|
||||
private async getOAuthClientsParams(clientId: string, isEmbed = false): Promise<OAuthRequestParams> {
|
||||
const res = { ...DEFAULT_PLATFORM_PARAMS };
|
||||
|
||||
if (isEmbed) {
|
||||
// embed should ignore oauth client settings and enable emails by default
|
||||
return { ...res, arePlatformEmailsEnabled: true };
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await this.oAuthClientRepository.getOAuthClient(clientId);
|
||||
// fetch oAuthClient from db and use data stored in db to set these values
|
||||
@@ -354,13 +366,14 @@ export class BookingsController_2024_04_15 {
|
||||
private async createNextApiBookingRequest(
|
||||
req: BookingRequest,
|
||||
oAuthClientId?: string,
|
||||
platformBookingLocation?: string
|
||||
platformBookingLocation?: string,
|
||||
isEmbed?: string
|
||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||
const requestId = req.get("X-Request-Id");
|
||||
const clone = { ...req };
|
||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||
const oAuthParams = oAuthClientId
|
||||
? await this.getOAuthClientsParams(oAuthClientId)
|
||||
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||
: DEFAULT_PLATFORM_PARAMS;
|
||||
this.logger.log(`createNextApiBookingRequest_2024_04_15`, {
|
||||
requestId,
|
||||
@@ -377,12 +390,13 @@ export class BookingsController_2024_04_15 {
|
||||
private async createNextApiRecurringBookingRequest(
|
||||
req: BookingRequest,
|
||||
oAuthClientId?: string,
|
||||
platformBookingLocation?: string
|
||||
platformBookingLocation?: string,
|
||||
isEmbed?: string
|
||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||
const clone = { ...req };
|
||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||
const oAuthParams = oAuthClientId
|
||||
? await this.getOAuthClientsParams(oAuthClientId)
|
||||
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||
: DEFAULT_PLATFORM_PARAMS;
|
||||
const requestId = req.get("X-Request-Id");
|
||||
this.logger.log(`createNextApiRecurringBookingRequest_2024_04_15`, {
|
||||
@@ -424,4 +438,8 @@ export class BookingsController_2024_04_15 {
|
||||
|
||||
throw new InternalServerErrorException(errMsg);
|
||||
}
|
||||
|
||||
private transformToBoolean(v?: string): boolean {
|
||||
return v && typeof v === "string" ? v.toLowerCase() === "true" : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { MarkAbsentBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/out
|
||||
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
||||
import { VERSION_2024_08_13_VALUE } from "@/lib/api-versions";
|
||||
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
@ApiHeader({
|
||||
name: "cal-api-version",
|
||||
description: `Must be set to \`2024-08-13\``,
|
||||
example: VERSION_2024_08_13,
|
||||
required: true,
|
||||
})
|
||||
export class BookingsController_2024_08_13 {
|
||||
|
||||
@@ -952,7 +952,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(2);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
const firstAttendee = firstBooking.attendees.find(
|
||||
(attendee) => attendee.name === nameAttendeeOne
|
||||
);
|
||||
expect(firstAttendee).toEqual({
|
||||
name: nameAttendeeOne,
|
||||
email: emailAttendeeOne,
|
||||
timeZone: body.attendee.timeZone,
|
||||
@@ -965,7 +968,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
},
|
||||
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
|
||||
});
|
||||
expect(firstBooking.attendees[1]).toEqual({
|
||||
const secondAttendee = firstBooking.attendees.find(
|
||||
(attendee) => attendee.name === nameAttendeeTwo
|
||||
);
|
||||
expect(secondAttendee).toEqual({
|
||||
name: nameAttendeeTwo,
|
||||
email: emailAttendeeTwo,
|
||||
timeZone: body.attendee.timeZone,
|
||||
|
||||
+164
-84
@@ -33,11 +33,18 @@ import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
NameFieldInput_2024_06_14,
|
||||
NameDefaultFieldInput_2024_06_14,
|
||||
NotesDefaultFieldInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
const orderBySlug = (a: { slug: string }, b: { slug: string }) => {
|
||||
if (a.slug < b.slug) return -1;
|
||||
if (a.slug > b.slug) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
describe("Event types Endpoints", () => {
|
||||
describe("Not authenticated", () => {
|
||||
let app: INestApplication;
|
||||
@@ -93,6 +100,68 @@ describe("Event types Endpoints", () => {
|
||||
let orgUserEventType1: EventType;
|
||||
let orgUserEventType2: EventType;
|
||||
let orgUserEventType3: EventType;
|
||||
|
||||
const defaultResponseBookingFieldName = {
|
||||
isDefault: true,
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldEmail = {
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldLocation = {
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldTitle = {
|
||||
isDefault: true,
|
||||
type: "text",
|
||||
slug: "title",
|
||||
required: true,
|
||||
hidden: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldNotes = {
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldGuests = {
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
slug: "guests",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldRescheduleReason = {
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "rescheduleReason",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
@@ -253,7 +322,7 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
@@ -379,30 +448,25 @@ describe("Event types Endpoints", () => {
|
||||
);
|
||||
expect(createdEventType.color).toEqual(body.color);
|
||||
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
// note(Lauris): location booking field is added if multiple locations are passed
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "location",
|
||||
type: "radioInput",
|
||||
disableOnPrefill: false,
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "rescheduleReason",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
{ ...defaultResponseBookingFieldNotes },
|
||||
];
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -432,7 +496,9 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.bookingFields.sort(orderBySlug)).toEqual(
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
@@ -455,7 +521,6 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and orgSlug`, async () => {
|
||||
console.log(organization);
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${orgUser.username}&orgSlug=${organization.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
@@ -636,7 +701,6 @@ describe("Event types Endpoints", () => {
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
console.log("createdEventType: ", createdEventType);
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
@@ -779,13 +843,20 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
|
||||
const notesBookingField: NotesDefaultFieldInput_2024_06_14 = {
|
||||
slug: "notes",
|
||||
label: "lemme take some notes",
|
||||
placeholder: "write your notes here",
|
||||
required: true,
|
||||
};
|
||||
|
||||
const newTitle = "Coding class in Italian!";
|
||||
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
@@ -804,6 +875,7 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
notesBookingField,
|
||||
],
|
||||
bookingLimitsCount: {
|
||||
day: 4,
|
||||
@@ -866,21 +938,25 @@ describe("Event types Endpoints", () => {
|
||||
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(updatedEventType.locations).toEqual(eventType.locations);
|
||||
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "rescheduleReason",
|
||||
type: "textarea",
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
{ ...defaultResponseBookingFieldNotes, ...notesBookingField },
|
||||
];
|
||||
|
||||
expect(updatedEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -997,7 +1073,9 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.bookingFields.sort(orderBySlug)).toEqual(
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
@@ -1091,14 +1169,6 @@ describe("Event types Endpoints", () => {
|
||||
placeholder: "",
|
||||
},
|
||||
{ isDefault: true, required: true, slug: "title", type: "text", disableOnPrefill: false, hidden: true },
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
@@ -1116,6 +1186,14 @@ describe("Event types Endpoints", () => {
|
||||
hidden: false,
|
||||
},
|
||||
{ isDefault: true, type: "phone", slug: "attendeePhoneNumber", required: false, hidden: true },
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -1239,15 +1317,6 @@ describe("Event types Endpoints", () => {
|
||||
defaultLabel: "what_is_this_meeting_about",
|
||||
defaultPlaceholder: "",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
{
|
||||
name: "guests",
|
||||
type: "multiemail",
|
||||
@@ -1283,6 +1352,15 @@ describe("Event types Endpoints", () => {
|
||||
required: false,
|
||||
defaultLabel: "phone_number",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
],
|
||||
};
|
||||
const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id);
|
||||
@@ -1369,15 +1447,6 @@ describe("Event types Endpoints", () => {
|
||||
defaultLabel: "what_is_this_meeting_about",
|
||||
defaultPlaceholder: "",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
{
|
||||
name: "guests",
|
||||
type: "multiemail",
|
||||
@@ -1413,6 +1482,15 @@ describe("Event types Endpoints", () => {
|
||||
required: false,
|
||||
defaultLabel: "phone_number",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
],
|
||||
};
|
||||
const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id);
|
||||
@@ -1426,19 +1504,21 @@ describe("Event types Endpoints", () => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const fetchedEventType = responseBody.data;
|
||||
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
...expectedReturnSystemFields,
|
||||
{
|
||||
isDefault: false,
|
||||
type: userDefinedBookingField.type,
|
||||
slug: userDefinedBookingField.name,
|
||||
label: userDefinedBookingField.label,
|
||||
required: userDefinedBookingField.required,
|
||||
placeholder: userDefinedBookingField.placeholder,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
expect(fetchedEventType.bookingFields.sort(orderBySlug)).toEqual(
|
||||
[
|
||||
{
|
||||
isDefault: false,
|
||||
type: userDefinedBookingField.type,
|
||||
slug: userDefinedBookingField.name,
|
||||
label: userDefinedBookingField.label,
|
||||
required: userDefinedBookingField.required,
|
||||
placeholder: userDefinedBookingField.placeholder,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
...expectedReturnSystemFields,
|
||||
].sort(orderBySlug)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1541,14 +1621,6 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
@@ -1565,6 +1637,14 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+38
-16
@@ -5,16 +5,19 @@ import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, BadRequestException } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
transformBookingFieldsApiRequestToInternal,
|
||||
transformBookingFieldsApiToInternal,
|
||||
transformLocationsApiToInternal,
|
||||
transformIntervalLimitsApiToInternal,
|
||||
transformFutureBookingLimitsApiToInternal,
|
||||
transformRecurrenceApiToInternal,
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
systemAfterFieldTitle,
|
||||
systemAfterFieldNotes,
|
||||
systemAfterFieldGuests,
|
||||
systemAfterFieldRescheduleReason,
|
||||
EventTypeMetaDataSchema,
|
||||
systemBeforeFieldLocation,
|
||||
transformBookerLayoutsApiToInternal,
|
||||
transformConfirmationPolicyApiToInternal,
|
||||
transformEventColorsApiToInternal,
|
||||
@@ -217,27 +220,46 @@ export class InputEventTypesService_2024_06_14 {
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
|
||||
hasMultipleLocations: boolean
|
||||
) {
|
||||
const customFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
? transformBookingFieldsApiRequestToInternal(inputBookingFields)
|
||||
const internalFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
? transformBookingFieldsApiToInternal(inputBookingFields)
|
||||
: [];
|
||||
const customFieldsWithoutNameEmail = customFields.filter(
|
||||
(field) => field.type !== "name" && field.type !== "email"
|
||||
const systemCustomFields = internalFields.filter((field) => !this.isUserCustomField(field));
|
||||
const userCustomFields = internalFields.filter((field) => this.isUserCustomField(field));
|
||||
|
||||
const systemCustomNameField = systemCustomFields?.find((field) => field.type === "name");
|
||||
const systemCustomEmailField = systemCustomFields?.find((field) => field.type === "email");
|
||||
const systemCustomTitleField = systemCustomFields?.find((field) => field.name === "title");
|
||||
const systemCustomNotesField = systemCustomFields?.find((field) => field.name === "notes");
|
||||
const systemCustomGuestsField = systemCustomFields?.find((field) => field.name === "guests");
|
||||
const systemCustomRescheduleReasonField = systemCustomFields?.find(
|
||||
(field) => field.name === "rescheduleReason"
|
||||
);
|
||||
const customNameField = customFields?.find((field) => field.type === "name");
|
||||
const customEmailField = customFields?.find((field) => field.type === "email");
|
||||
|
||||
const defaultFieldsBefore: (SystemField | CustomField)[] = [
|
||||
customNameField || systemBeforeFieldName,
|
||||
customEmailField || systemBeforeFieldEmail,
|
||||
systemCustomNameField || systemBeforeFieldName,
|
||||
systemCustomEmailField || systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
];
|
||||
// note(Lauris): if event type has multiple locations then a radio button booking field has to be displayed to allow booker to pick location
|
||||
if (hasMultipleLocations) {
|
||||
defaultFieldsBefore.push(systemBeforeFieldLocation);
|
||||
}
|
||||
|
||||
const defaultFieldsAfter = [systemAfterFieldRescheduleReason];
|
||||
const defaultFieldsAfter = [
|
||||
systemCustomTitleField || systemAfterFieldTitle,
|
||||
systemCustomNotesField || systemAfterFieldNotes,
|
||||
systemCustomGuestsField || systemAfterFieldGuests,
|
||||
systemCustomRescheduleReasonField || systemAfterFieldRescheduleReason,
|
||||
];
|
||||
|
||||
return [...defaultFieldsBefore, ...customFieldsWithoutNameEmail, ...defaultFieldsAfter];
|
||||
return [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
|
||||
}
|
||||
|
||||
isUserCustomField(field: SystemField | CustomField): field is CustomField {
|
||||
return (
|
||||
field.type !== "name" &&
|
||||
field.type !== "email" &&
|
||||
field.name !== "title" &&
|
||||
field.name !== "notes" &&
|
||||
field.name !== "guests" &&
|
||||
field.name !== "rescheduleReason"
|
||||
);
|
||||
}
|
||||
|
||||
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
|
||||
|
||||
+13
-1
@@ -242,7 +242,19 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
}
|
||||
}
|
||||
|
||||
return [...transformBookingFieldsInternalToApi(knownBookingFields), ...unknownBookingFields];
|
||||
const fields = [...transformBookingFieldsInternalToApi(knownBookingFields), ...unknownBookingFields];
|
||||
|
||||
// ensure additional notes are always at the end
|
||||
return fields.sort((a, b) => {
|
||||
if (!a?.slug || !b?.slug) return 0;
|
||||
if (a.slug === "notes" && b.slug !== "notes") {
|
||||
return 1;
|
||||
}
|
||||
if (b.slug === "notes" && a.slug !== "notes") {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
getDefaultBookingFields(isOrgTeamEvent: boolean) {
|
||||
|
||||
@@ -41,6 +41,18 @@ describe("isOriginAllowed", () => {
|
||||
const origin = "https://sub.mydomain.com/path/to/resource";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle patterns with wildcard for routes correctly", () => {
|
||||
const allowedOrigins = ["https://domain.com/*"];
|
||||
const origin = "https://domain.com/dashboard";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle patterns with wildcard for root route correctly", () => {
|
||||
const allowedOrigins = ["https://domain.com*"];
|
||||
const origin = "https://domain.com";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("is not allowed", () => {
|
||||
|
||||
@@ -179,7 +179,7 @@ export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth")
|
||||
|
||||
if (origin && !isOriginAllowed(origin, client.redirectUris)) {
|
||||
throw new UnauthorizedException(
|
||||
`Invalid request origin - please open https://app.cal.com/settings/platform and add the origin '${origin}' to the 'Redirect uris' of your OAuth client.`
|
||||
`Invalid request origin - please open https://app.cal.com/settings/platform and add the origin '${origin}' to the 'Redirect uris' of your OAuth client with ID '${client.id}'`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+16
-34
@@ -677,27 +677,9 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const fetchedEventType = responseBody.data;
|
||||
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
{
|
||||
isDefault: true,
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{ isDefault: true, type: "phone", slug: "attendeePhoneNumber", required: false, hidden: true },
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
@@ -708,33 +690,33 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "text",
|
||||
slug: "title",
|
||||
required: true,
|
||||
slug: "title",
|
||||
type: "text",
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
slug: "guests",
|
||||
required: false,
|
||||
type: "multiemail",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "rescheduleReason",
|
||||
required: false,
|
||||
slug: "rescheduleReason",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
|
||||
@@ -7741,7 +7741,7 @@
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"NameFieldInput_2024_06_14": {
|
||||
"NameDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -7766,7 +7766,7 @@
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
"EmailFieldInput_2024_06_14": {
|
||||
"EmailDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -7791,6 +7791,126 @@
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
"TitleDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"example": "title",
|
||||
"description": "only allowed value for type is `title`"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&title=journey`, the title field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug"
|
||||
]
|
||||
},
|
||||
"NotesDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"example": "notes",
|
||||
"description": "only allowed value for type is `notes`"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `¬es=journey`, the notes field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug"
|
||||
]
|
||||
},
|
||||
"GuestsDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"example": "guests",
|
||||
"description": "only allowed value for type is `guests`"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&guests=bob@cal.com`, the guests field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug"
|
||||
]
|
||||
},
|
||||
"RescheduleReasonDefaultFieldInput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"example": "rescheduleReason",
|
||||
"description": "only allowed value for type is `rescheduleReason`"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"description": "If true show under event type settings but don't show this booking field in the Booker. If false show in both."
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&rescheduleReason=travel`, the rescheduleReason field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug"
|
||||
]
|
||||
},
|
||||
"BookerLayouts_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7921,10 +8041,22 @@
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NameFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/NameDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EmailFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/EmailDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/TitleDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/NotesDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/GuestsDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RescheduleReasonDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PhoneFieldInput_2024_06_14"
|
||||
@@ -8475,20 +8607,19 @@
|
||||
"RescheduleReasonDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
"description": "This property is always true because it's a default field",
|
||||
"example": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"title",
|
||||
"notes",
|
||||
"guests"
|
||||
],
|
||||
"example": "rescheduleReason",
|
||||
"description": "only allowed value for type is `rescheduleReason`",
|
||||
"default": "rescheduleReason"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "textarea"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -8505,33 +8636,39 @@
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&rescheduleReason=busy`, the reschedule reason field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required",
|
||||
"hidden",
|
||||
"disableOnPrefill"
|
||||
]
|
||||
},
|
||||
"TitleDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
"description": "This property is always true because it's a default field",
|
||||
"example": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"default": "title"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "text"
|
||||
"default": "textarea"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug",
|
||||
"isDefault",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"TitleDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"title",
|
||||
"notes",
|
||||
"guests"
|
||||
],
|
||||
"example": "title",
|
||||
"description": "only allowed value for type is `title`",
|
||||
"default": "title"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
@@ -8549,33 +8686,39 @@
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&title=masterclass`, the title field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required",
|
||||
"hidden",
|
||||
"disableOnPrefill"
|
||||
]
|
||||
},
|
||||
"NotesDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
"description": "This property is always true because it's a default field",
|
||||
"example": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"default": "notes"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "textarea"
|
||||
"default": "text"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug",
|
||||
"isDefault",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"NotesDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"title",
|
||||
"notes",
|
||||
"guests"
|
||||
],
|
||||
"example": "notes",
|
||||
"description": "only allowed value for type is `notes`",
|
||||
"default": "notes"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
@@ -8593,33 +8736,39 @@
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `¬es=hello`, the notes field will be prefilled with this value and disabled."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required",
|
||||
"hidden",
|
||||
"disableOnPrefill"
|
||||
]
|
||||
},
|
||||
"GuestsDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
"description": "This property is always true because it's a default field",
|
||||
"example": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"default": "guests"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "multiemail"
|
||||
"default": "textarea"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"slug",
|
||||
"isDefault",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"GuestsDefaultFieldOutput_2024_06_14": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"email",
|
||||
"title",
|
||||
"notes",
|
||||
"guests"
|
||||
],
|
||||
"example": "guests",
|
||||
"description": "only allowed value for type is `guests`",
|
||||
"default": "guests"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
@@ -8637,15 +8786,22 @@
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if URL contains query parameter `&guests=lauris@cal.com`, the guests field will be prefilled with this value and disabled."
|
||||
},
|
||||
"isDefault": {
|
||||
"type": "object",
|
||||
"default": true,
|
||||
"description": "This property is always true because it's a default field",
|
||||
"example": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "multiemail"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isDefault",
|
||||
"slug",
|
||||
"type",
|
||||
"required",
|
||||
"hidden",
|
||||
"disableOnPrefill"
|
||||
"isDefault",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"AddressFieldOutput_2024_06_14": {
|
||||
@@ -9776,10 +9932,22 @@
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NameFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/NameDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EmailFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/EmailDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/TitleDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/NotesDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/GuestsDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RescheduleReasonDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PhoneFieldInput_2024_06_14"
|
||||
@@ -11353,10 +11521,22 @@
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NameFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/NameDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EmailFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/EmailDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/TitleDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/NotesDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/GuestsDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RescheduleReasonDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PhoneFieldInput_2024_06_14"
|
||||
@@ -12178,10 +12358,22 @@
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NameFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/NameDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EmailFieldInput_2024_06_14"
|
||||
"$ref": "#/components/schemas/EmailDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/TitleDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/NotesDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/GuestsDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RescheduleReasonDefaultFieldInput_2024_06_14"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PhoneFieldInput_2024_06_14"
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @calcom/web
|
||||
|
||||
## 4.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @calcom/embed-core@1.5.2
|
||||
- @calcom/embed-react@1.5.2
|
||||
- @calcom/embed-snippet@1.3.2
|
||||
|
||||
## 4.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -20,7 +20,6 @@ const ROUTES: [URLPattern, boolean][] = [
|
||||
["/auth/error", process.env.APP_ROUTER_AUTH_ERROR_ENABLED === "1"] as const,
|
||||
["/auth/platform/:path*", process.env.APP_ROUTER_AUTH_PLATFORM_ENABLED === "1"] as const,
|
||||
["/auth/oauth2/:path*", process.env.APP_ROUTER_AUTH_OAUTH2_ENABLED === "1"] as const,
|
||||
["/team", process.env.APP_ROUTER_TEAM_ENABLED === "1"] as const,
|
||||
].map(([pathname, enabled]) => [
|
||||
new URLPattern({
|
||||
pathname,
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import Head from "next/head";
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
export const generateMetadata = () =>
|
||||
_generateMetadata(
|
||||
(t) => `${t("access_denied")} | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
|
||||
export default function AccessDenied() {
|
||||
const { t } = useLocale();
|
||||
async function Error403() {
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<div className="bg-subtle flex h-screen">
|
||||
<Head>
|
||||
<title>{`${t("access_denied")} | ${APP_NAME}`}</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="font-cal text-emphasis text-6xl">403</h1>
|
||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">{t("dont_have_access_this_page")}</h2>
|
||||
@@ -27,4 +27,4 @@ export default function AccessDenied() {
|
||||
);
|
||||
}
|
||||
|
||||
AccessDenied.PageWrapper = PageWrapper;
|
||||
export default WithLayout({ ServerPage: Error403 });
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, showToast } from "@calcom/ui";
|
||||
|
||||
export default function CopyButton({ error }: { error: string }) {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="secondary"
|
||||
className="mt-2 border-0 font-sans font-normal hover:bg-gray-300"
|
||||
StartIcon="copy"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(error);
|
||||
showToast("Link copied!", "success");
|
||||
}}>
|
||||
{t("copy")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
import CopyButton from "./copy-button";
|
||||
|
||||
export const generateMetadata = () =>
|
||||
_generateMetadata(
|
||||
(t) => `${t("something_unexpected_occurred")} | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
|
||||
async function Error500({ searchParams }: { searchParams: { error?: string } }) {
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<div className="bg-subtle flex h-screen">
|
||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">{t("500_error_message")}</h2>
|
||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">{t("something_went_wrong_on_our_end")}</p>
|
||||
{searchParams?.error && (
|
||||
<div className="mb-8 flex flex-col">
|
||||
<p className="text-default mb-4 max-w-2xl text-sm">
|
||||
{t("please_provide_following_text_to_suppport")}:
|
||||
</p>
|
||||
<pre className="bg-emphasis text-emphasis w-full max-w-2xl whitespace-normal break-words rounded-md p-4">
|
||||
{searchParams.error}
|
||||
<br />
|
||||
<CopyButton error={searchParams.error} />
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
<Button href="mailto:support@cal.com">{t("contact_support")}</Button>
|
||||
<Button color="secondary" href="javascript:history.back()" className="ml-2">
|
||||
{t("go_back")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WithLayout({
|
||||
ServerPage: Error500,
|
||||
});
|
||||
@@ -13,6 +13,9 @@ export function createMockNextJsRequest(...args: Parameters<typeof createMocks>)
|
||||
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: vi.fn(),
|
||||
notFound: vi.fn(),
|
||||
@@ -86,7 +89,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "namespace1",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith("/reschedule/embed?layout=week_view&embed=namespace1");
|
||||
});
|
||||
@@ -105,7 +108,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "namespace1",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith(
|
||||
"/reschedule/embed?redirectParam=1&layout=week_view&embed=namespace1"
|
||||
@@ -126,7 +129,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith("/reschedule/embed?redirectParam=1&layout=week_view&embed=");
|
||||
});
|
||||
@@ -147,7 +150,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "namespace1",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith(
|
||||
"https://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
|
||||
@@ -168,7 +171,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "namespace1",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith(
|
||||
"http://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
|
||||
@@ -189,7 +192,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "namespace1",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(redirect).toHaveBeenCalledWith(
|
||||
"/calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
|
||||
@@ -239,7 +242,7 @@ describe("withEmbedSsrAppDir", () => {
|
||||
embed: "",
|
||||
},
|
||||
})
|
||||
).catch(() => {});
|
||||
).catch(noop);
|
||||
|
||||
expect(notFound).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
@@ -18,7 +18,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
const { booking, user: username, slug: eventSlug } = props;
|
||||
const { booking, user: username, slug: eventSlug, isSEOIndexable, eventData, isBrandingHidden } = props;
|
||||
const rescheduleUid = booking?.uid;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug);
|
||||
|
||||
@@ -31,12 +31,21 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
});
|
||||
|
||||
const profileName = event?.profile?.name ?? "";
|
||||
const profileImage = event?.profile.image;
|
||||
const title = event?.title ?? "";
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||
);
|
||||
const metadata = await generateEventBookingPageMetadata({
|
||||
event: {
|
||||
hidden: event?.hidden ?? false,
|
||||
title,
|
||||
users: event?.users ?? [],
|
||||
},
|
||||
hideBranding: isBrandingHidden,
|
||||
orgSlug: eventData?.entity.orgSlug ?? null,
|
||||
isSEOIndexable: !!isSEOIndexable,
|
||||
profile: { name: profileName, image: profileImage ?? "" },
|
||||
isReschedule: !!rescheduleUid,
|
||||
});
|
||||
return metadata;
|
||||
};
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { generateUserProfilePageMetadata } from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
import type { PageProps as LegacyPageProps } from "~/users/views/users-public-view";
|
||||
import LegacyPage from "~/users/views/users-public-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props;
|
||||
|
||||
const isOrg = !!profile?.organization;
|
||||
const allowSEOIndexing = !!(
|
||||
(!isOrg && profile.allowSEOIndexing) ||
|
||||
(isOrg && isOrgSEOIndexable && profile.allowSEOIndexing)
|
||||
);
|
||||
return await generateUserProfilePageMetadata({
|
||||
profile: {
|
||||
name: profile.name,
|
||||
image: profile.image ?? "",
|
||||
username: profile.username ?? "",
|
||||
markdownStrippedBio: markdownStrippedBio,
|
||||
},
|
||||
event: null,
|
||||
hideBranding: false,
|
||||
orgSlug: entity.orgSlug ?? null,
|
||||
isSEOIndexable: allowSEOIndexing,
|
||||
});
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
export default WithLayout({ getData, Page: LegacyPage })<"P">;
|
||||
+21
-2
@@ -3,7 +3,8 @@ import i18next from "i18next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import { constructGenericImage } from "@calcom/lib/OgImages";
|
||||
import type { MeetingImageProps } from "@calcom/lib/OgImages";
|
||||
import { constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
|
||||
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants";
|
||||
import { buildCanonical } from "@calcom/lib/next-seo.config";
|
||||
import { truncateOnWord } from "@calcom/lib/text";
|
||||
@@ -33,7 +34,7 @@ const createI18nInstance = async (locale: string, ns: string) => {
|
||||
return _i18n;
|
||||
};
|
||||
|
||||
const getTranslationWithCache = async (locale: string, ns: string = "common") => {
|
||||
const getTranslationWithCache = async (locale: string, ns = "common") => {
|
||||
const localeWithFallback = locale ?? "en";
|
||||
const i18n = await createI18nInstance(localeWithFallback, ns);
|
||||
return i18n.getFixedT(localeWithFallback, ns);
|
||||
@@ -103,3 +104,21 @@ export const _generateMetadata = async (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const generateMeetingMetadata = async (
|
||||
meeting: MeetingImageProps,
|
||||
getTitle: (t: TFunction<string, undefined>) => string,
|
||||
getDescription: (t: TFunction<string, undefined>) => string,
|
||||
hideBranding?: boolean,
|
||||
origin?: string
|
||||
) => {
|
||||
const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin);
|
||||
const image = SEO_IMG_OGIMG + constructMeetingImage(meeting);
|
||||
return {
|
||||
...metadata,
|
||||
openGraph: {
|
||||
...metadata.openGraph,
|
||||
images: [image],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ const inputSchema = z.object({
|
||||
cardKeys: z.array(z.string()),
|
||||
});
|
||||
|
||||
export async function handler(request: Request) {
|
||||
async function handler(request: Request) {
|
||||
const headersList = headers();
|
||||
const requestBody = await request.json();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageProps } from "app/_types";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
@@ -17,7 +17,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const pageProps = await getData(legacyCtx);
|
||||
|
||||
const { booking, user: username, slug: eventSlug, isTeamEvent } = pageProps;
|
||||
const { booking, user: username, slug: eventSlug, isTeamEvent, isBrandingHidden } = pageProps;
|
||||
const rescheduleUid = booking?.uid;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req);
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
@@ -34,7 +34,8 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
const title = event?.title ?? "";
|
||||
return await _generateMetadata(
|
||||
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`,
|
||||
isBrandingHidden
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
import type { PageProps as LegacyPageProps } from "~/users/views/users-public-view";
|
||||
import LegacyPage from "~/users/views/users-public-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
const { profile, markdownStrippedBio } = props;
|
||||
return await _generateMetadata(
|
||||
() => profile.name,
|
||||
() => markdownStrippedBio
|
||||
);
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
|
||||
@@ -1,10 +1,9 @@
|
||||
import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||
import Page from "@pages/org/[orgSlug]/[user]/[type]/embed";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { Page, type OrgTypePageProps } from "app/org/[orgSlug]/[user]/[type]/page";
|
||||
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
|
||||
const getEmbedData = withEmbedSsrAppDir<OrgTypePageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, ServerPage: Page });
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import Page, { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page });
|
||||
@@ -1,8 +1,9 @@
|
||||
import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||
import Page from "@pages/org/[orgSlug]/[user]/embed";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { Page, type OrgPageProps } from "app/org/[orgSlug]/[user]/page";
|
||||
|
||||
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps";
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||
const getEmbedData = withEmbedSsrAppDir<OrgPageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, ServerPage: Page });
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import Page, { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page });
|
||||
@@ -1,6 +1,4 @@
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
import type { PageProps } from "~/team/team-view";
|
||||
import TeamPage from "~/team/team-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => props.team.name || t("nameless_team"),
|
||||
(t) => props.team.name || t("nameless_team")
|
||||
);
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: TeamPage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -1,45 +0,0 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventRepository } from "@calcom/lib/server/repository/event";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||
|
||||
import type { PageProps } from "~/team/type-view";
|
||||
import TypePage from "~/team/type-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
const { user: username, slug: eventSlug, booking } = props;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug);
|
||||
|
||||
const event = await EventRepository.getPublicEvent({
|
||||
username,
|
||||
eventSlug,
|
||||
isTeamEvent: true,
|
||||
org: isValidOrgDomain ? currentOrgDomain : null,
|
||||
fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true",
|
||||
});
|
||||
|
||||
const profileName = event?.profile?.name ?? "";
|
||||
const title = event?.title ?? "";
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
|
||||
);
|
||||
};
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: TypePage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
import type { PageProps } from "~/team/team-view";
|
||||
import TeamPage from "~/team/team-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => props.team.name || t("nameless_team"),
|
||||
(t) => props.team.name || t("nameless_team")
|
||||
);
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
|
||||
export default WithLayout({
|
||||
Page: TeamPage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -1,6 +1,4 @@
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
|
||||
import { generateMeetingMetadata } from "./_utils";
|
||||
|
||||
type MetadataPayload = {
|
||||
title: (t: TFunction) => string;
|
||||
description: (t: TFunction) => string;
|
||||
meeting: {
|
||||
title: string;
|
||||
profile: { name: string; image: string };
|
||||
users?: { name: string; username: string }[];
|
||||
};
|
||||
robots: {
|
||||
index: boolean;
|
||||
follow: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type UserProfilePageProfile = {
|
||||
name: string | null;
|
||||
username: string | null;
|
||||
image: string | null;
|
||||
markdownStrippedBio: string | null | undefined;
|
||||
};
|
||||
|
||||
type TeamProfilePageProfile = {
|
||||
teamName: string | null;
|
||||
image: string | null;
|
||||
markdownStrippedBio: string | null | undefined;
|
||||
};
|
||||
|
||||
type EventBookingPageProfile = { name: string; image: string };
|
||||
type BaseMetadataArgs = {
|
||||
hideBranding: boolean;
|
||||
orgSlug: string | null;
|
||||
isSEOIndexable: boolean;
|
||||
isReschedule?: boolean;
|
||||
};
|
||||
|
||||
type TeamProfilePageArgs = BaseMetadataArgs & {
|
||||
profile: TeamProfilePageProfile;
|
||||
event: null;
|
||||
};
|
||||
|
||||
type UserProfilePageArgs = BaseMetadataArgs & {
|
||||
profile: UserProfilePageProfile;
|
||||
event: null;
|
||||
};
|
||||
|
||||
type EventBookingPageArgs = BaseMetadataArgs & {
|
||||
profile: EventBookingPageProfile;
|
||||
event: {
|
||||
title: string;
|
||||
hidden: boolean;
|
||||
users: { name: string | null; username: string | null }[];
|
||||
};
|
||||
};
|
||||
|
||||
export const generateEventBookingPageMetadata = async (props: EventBookingPageArgs) => {
|
||||
const { profile, isReschedule, hideBranding, orgSlug, isSEOIndexable, event } = props;
|
||||
const fullOrigin = getOrgFullOrigin(orgSlug);
|
||||
|
||||
const payload: MetadataPayload = {
|
||||
title: (t) => `${isReschedule ? t("reschedule") : ""} ${event.title} | ${profile.name}`,
|
||||
description: (t) => `${isReschedule ? t("reschedule") : ""} ${event.title}`,
|
||||
meeting: {
|
||||
title: event.title,
|
||||
profile: { name: profile.name, image: profile.image },
|
||||
users: event.users.map((user) => ({
|
||||
name: `${user.name}`,
|
||||
username: `${user.username}`,
|
||||
})),
|
||||
},
|
||||
robots: {
|
||||
index: !(event.hidden || !isSEOIndexable),
|
||||
follow: !(event.hidden || !isSEOIndexable),
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...(await generateMeetingMetadata(
|
||||
payload.meeting,
|
||||
payload.title,
|
||||
payload.description,
|
||||
hideBranding,
|
||||
fullOrigin
|
||||
)),
|
||||
robots: payload.robots,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateUserProfilePageMetadata = async (props: UserProfilePageArgs) => {
|
||||
const { profile, hideBranding, orgSlug, isSEOIndexable } = props;
|
||||
const fullOrigin = getOrgFullOrigin(orgSlug);
|
||||
|
||||
const payload: MetadataPayload = {
|
||||
title: () => profile.name ?? "",
|
||||
description: () => profile.markdownStrippedBio ?? "",
|
||||
meeting: {
|
||||
title: profile.markdownStrippedBio ?? "",
|
||||
profile: { name: profile.name ?? "", image: profile.image ?? "" },
|
||||
users: [{ name: profile.name ?? "", username: profile.username ?? "" }],
|
||||
},
|
||||
robots: {
|
||||
index: isSEOIndexable,
|
||||
follow: isSEOIndexable,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...(await generateMeetingMetadata(
|
||||
payload.meeting,
|
||||
payload.title,
|
||||
payload.description,
|
||||
hideBranding,
|
||||
fullOrigin
|
||||
)),
|
||||
robots: payload.robots,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateTeamProfilePageMetadata = async (props: TeamProfilePageArgs) => {
|
||||
const { profile, hideBranding, orgSlug, isSEOIndexable } = props;
|
||||
const fullOrigin = getOrgFullOrigin(orgSlug);
|
||||
|
||||
const payload: MetadataPayload = {
|
||||
title: (t) => profile.teamName || t("nameless_team"),
|
||||
description: () => profile.markdownStrippedBio ?? "",
|
||||
meeting: {
|
||||
title: profile.markdownStrippedBio ?? "",
|
||||
profile: { name: `${profile.teamName}`, image: profile.image ?? "" },
|
||||
},
|
||||
robots: {
|
||||
index: isSEOIndexable,
|
||||
follow: isSEOIndexable,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...(await generateMeetingMetadata(
|
||||
payload.meeting,
|
||||
payload.title,
|
||||
payload.description,
|
||||
hideBranding,
|
||||
fullOrigin
|
||||
)),
|
||||
robots: payload.robots,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@calcom/ui";
|
||||
import type { IconName } from "@calcom/ui";
|
||||
|
||||
export const IconGrid = (props: {
|
||||
title: string;
|
||||
icons: IconName[];
|
||||
rootClassName?: string;
|
||||
iconClassName?: string;
|
||||
}) => (
|
||||
<div className={props.rootClassName}>
|
||||
<h2 className="font-cal mt-6 text-lg font-medium">{props.title}</h2>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-6">
|
||||
{props.icons.map((icon) => {
|
||||
return (
|
||||
<div key={icon} className="flex items-center gap-1">
|
||||
<Icon name={icon} className={props.iconClassName} />
|
||||
<div>{icon}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
||||
import { Inter } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
|
||||
import { type IconName, IconSprites } from "@calcom/ui";
|
||||
|
||||
import { lucideIconList } from "../../../../packages/ui/components/icon/icon-list.mjs";
|
||||
import { IconGrid } from "./IconGrid";
|
||||
|
||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||
const calFont = localFont({
|
||||
src: "../../fonts/CalSans-SemiBold.woff2",
|
||||
variable: "--font-cal",
|
||||
preload: true,
|
||||
display: "swap",
|
||||
weight: "600",
|
||||
});
|
||||
export const generateMetadata = async () => {
|
||||
return await _generateMetadata(
|
||||
(t) => t("icon_showcase"),
|
||||
() => ""
|
||||
);
|
||||
};
|
||||
export default async function IconsPage() {
|
||||
const icons = Array.from(lucideIconList).sort() as IconName[];
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<div className={`${interFont.variable} ${calFont.variable}`}>
|
||||
<div className="bg-subtle flex h-screen">
|
||||
<IconSprites />
|
||||
<div className="bg-default m-auto min-w-full rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="text-emphasis font-cal text-2xl font-medium">{t("icons_showcase")}</h1>
|
||||
<IconGrid title="Regular Icons" icons={icons} />
|
||||
<IconGrid
|
||||
title="Filled Icons"
|
||||
icons={icons}
|
||||
rootClassName="bg-darkgray-100 text-gray-50"
|
||||
iconClassName="fill-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const dynamic = "force-static";
|
||||
@@ -26,7 +26,10 @@ export const generateMetadata = () => prepareRootMetadata();
|
||||
const getInitialProps = async (url: string) => {
|
||||
const { pathname, searchParams } = new URL(url);
|
||||
|
||||
const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null;
|
||||
const isEmbedSnippetGeneratorPath = pathname.startsWith("/event-types");
|
||||
const isEmbed =
|
||||
(pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null) &&
|
||||
!isEmbedSnippetGeneratorPath;
|
||||
const embedColorScheme = searchParams?.get("ui.color-scheme");
|
||||
|
||||
const req = { headers: headers(), cookies: cookies() };
|
||||
@@ -76,6 +79,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<script
|
||||
nonce={nonce}
|
||||
id="headScript"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.calNewLocale = "${locale}";
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
import type { PageProps as TeamTypePageProps } from "~/team/type-view";
|
||||
import TeamTypePage from "~/team/type-view";
|
||||
import UserTypePage from "~/users/views/users-type-public-view";
|
||||
import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view";
|
||||
|
||||
export type OrgTypePageProps = UserTypePageProps | TeamTypePageProps;
|
||||
const getData = withAppDirSsr<OrgTypePageProps>(getServerSideProps);
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
const { booking, isSEOIndexable, eventData, isBrandingHidden } = props;
|
||||
const rescheduleUid = booking?.uid;
|
||||
|
||||
const profileName = eventData?.profile?.name ?? "";
|
||||
const profileImage = eventData?.profile.image ?? "";
|
||||
|
||||
return await generateEventBookingPageMetadata({
|
||||
profile: { name: profileName, image: profileImage },
|
||||
event: {
|
||||
title: eventData?.title ?? "",
|
||||
hidden: eventData?.hidden ?? false,
|
||||
users: [
|
||||
...(eventData?.users || []).map((user) => ({
|
||||
name: `${user.name}`,
|
||||
username: `${user.username}`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
hideBranding: isBrandingHidden,
|
||||
orgSlug: eventData?.entity.orgSlug ?? null,
|
||||
isSEOIndexable: !!isSEOIndexable,
|
||||
isReschedule: !!rescheduleUid,
|
||||
});
|
||||
};
|
||||
|
||||
export const Page = async (props: OrgTypePageProps) => {
|
||||
if ((props as TeamTypePageProps)?.teamId) {
|
||||
return <TeamTypePage {...(props as TeamTypePageProps)} />;
|
||||
}
|
||||
return <UserTypePage {...(props as UserTypePageProps)} />;
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, ServerPage: Page, isBookingPage: true });
|
||||
@@ -0,0 +1,64 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import {
|
||||
generateTeamProfilePageMetadata,
|
||||
generateUserProfilePageMetadata,
|
||||
} from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps";
|
||||
|
||||
import type { PageProps as TeamPageProps } from "~/team/team-view";
|
||||
import TeamPage from "~/team/team-view";
|
||||
import UserPage from "~/users/views/users-public-view";
|
||||
import type { PageProps as UserPageProps } from "~/users/views/users-public-view";
|
||||
|
||||
export type OrgPageProps = UserPageProps | TeamPageProps;
|
||||
const getData = withAppDirSsr<OrgPageProps>(getServerSideProps);
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
|
||||
if ((props as TeamPageProps)?.team) {
|
||||
const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = props as TeamPageProps;
|
||||
return await generateTeamProfilePageMetadata({
|
||||
profile: {
|
||||
teamName: team.name,
|
||||
image: getOrgOrTeamAvatar(team),
|
||||
markdownStrippedBio: markdownStrippedBio,
|
||||
},
|
||||
event: null,
|
||||
hideBranding: false,
|
||||
orgSlug: currentOrgDomain ?? null,
|
||||
isSEOIndexable: !!isSEOIndexable,
|
||||
});
|
||||
} else {
|
||||
const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props as UserPageProps;
|
||||
|
||||
const isOrg = !!profile?.organization;
|
||||
const allowSEOIndexing =
|
||||
(!isOrg && profile.allowSEOIndexing) || (isOrg && isOrgSEOIndexable && profile.allowSEOIndexing);
|
||||
|
||||
return await generateUserProfilePageMetadata({
|
||||
profile: { name: profile.name, username: profile.username, image: profile.image, markdownStrippedBio },
|
||||
hideBranding: false,
|
||||
orgSlug: entity.orgSlug ?? null,
|
||||
isSEOIndexable: !!allowSEOIndexing,
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const Page = async (props: OrgPageProps) => {
|
||||
if ((props as TeamPageProps)?.team) {
|
||||
return <TeamPage {...(props as TeamPageProps)} />;
|
||||
}
|
||||
return <UserPage {...(props as UserPageProps)} />;
|
||||
};
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, ServerPage: Page, isBookingPage: true });
|
||||
+4
-4
@@ -4,7 +4,6 @@ import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventRepository } from "@calcom/lib/server/repository/event";
|
||||
|
||||
@@ -16,7 +15,7 @@ import Page from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-mee
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const { slug: eventSlug, user: username } = await getData(context);
|
||||
const { slug: eventSlug, user: username, isBrandingHidden } = await getData(context);
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||
|
||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||
@@ -34,9 +33,10 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
|
||||
|
||||
return await _generateMetadata(
|
||||
() => `${title} | ${profileName}`,
|
||||
() => `${title}`
|
||||
() => `${title}`,
|
||||
isBrandingHidden
|
||||
);
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
export default WithLayout({ getLayout, getData, Page })<"P">;
|
||||
export default WithLayout({ getData, Page, isBookingPage: true })<"P">;
|
||||
@@ -0,0 +1 @@
|
||||
export { default, generateMetadata } from "app/team/[slug]/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default, generateMetadata } from "app/team/[slug]/[type]/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default, generateMetadata } from "app/team/[slug]/page";
|
||||
+22
-9
@@ -1,6 +1,6 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
@@ -15,7 +15,7 @@ import LegacyPage, { type PageProps as LegacyPageProps } from "~/team/type-view"
|
||||
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
const props = await getData(legacyCtx);
|
||||
const { user: username, slug: eventSlug, booking } = props;
|
||||
const { user: username, slug: eventSlug, booking, isSEOIndexable, eventData, isBrandingHidden } = props;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug);
|
||||
|
||||
const event = await EventRepository.getPublicEvent({
|
||||
@@ -26,13 +26,26 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
|
||||
fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true",
|
||||
});
|
||||
|
||||
const profileName = event?.profile?.name ?? "";
|
||||
const title = event?.title ?? "";
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
|
||||
);
|
||||
return await generateEventBookingPageMetadata({
|
||||
profile: {
|
||||
name: event?.profile?.name ?? "",
|
||||
image: event?.profile.image ?? "",
|
||||
},
|
||||
event: {
|
||||
title: event?.title ?? "",
|
||||
hidden: event?.hidden ?? false,
|
||||
users: [
|
||||
...(event?.users || []).map((user) => ({
|
||||
name: `${user.name}`,
|
||||
username: `${user.username}`,
|
||||
})),
|
||||
],
|
||||
},
|
||||
hideBranding: isBrandingHidden,
|
||||
orgSlug: eventData?.entity.orgSlug ?? null,
|
||||
isSEOIndexable,
|
||||
isReschedule: !!booking,
|
||||
});
|
||||
};
|
||||
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import type { PageProps as _PageProps } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { generateTeamProfilePageMetadata } from "app/generateBookingPageMetadata";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
@@ -11,12 +13,21 @@ import type { PageProps } from "~/team/team-view";
|
||||
import LegacyPage from "~/team/team-view";
|
||||
|
||||
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
|
||||
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||
|
||||
return await _generateMetadata(
|
||||
(t) => props.team.name || t("nameless_team"),
|
||||
(t) => props.team.name || t("nameless_team")
|
||||
const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = await getData(
|
||||
buildLegacyCtx(headers(), cookies(), params, searchParams)
|
||||
);
|
||||
|
||||
return await generateTeamProfilePageMetadata({
|
||||
profile: {
|
||||
teamName: team.name,
|
||||
image: getOrgOrTeamAvatar(team),
|
||||
markdownStrippedBio: markdownStrippedBio,
|
||||
},
|
||||
event: null,
|
||||
hideBranding: false,
|
||||
orgSlug: currentOrgDomain ?? null,
|
||||
isSEOIndexable: !!isSEOIndexable,
|
||||
});
|
||||
};
|
||||
|
||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||
@@ -25,4 +36,5 @@ export default WithLayout({
|
||||
Page: LegacyPage,
|
||||
getData,
|
||||
getLayout: null,
|
||||
isBookingPage: true,
|
||||
})<"P">;
|
||||
@@ -11,7 +11,7 @@ import "@calcom/dayjs/locales";
|
||||
import ViewRecordingsDialog from "@calcom/features/ee/video/ViewRecordingsDialog";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { formatTime } from "@calcom/lib/date-fns";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { getPaymentAppData } from "@calcom/lib/getPaymentAppData";
|
||||
import { useCopy } from "@calcom/lib/hooks/useCopy";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useGetTheme } from "@calcom/lib/hooks/useTheme";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -12,7 +13,6 @@ import { RedirectType } from "@calcom/prisma/enums";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
|
||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getServerSideProps as GSSUserPage } from "@pages/[user]";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
@@ -6,6 +5,8 @@ import prisma from "@calcom/prisma";
|
||||
|
||||
import { getServerSideProps as GSSTeamPage } from "@lib/team/[slug]/getServerSideProps";
|
||||
|
||||
import { getServerSideProps as GSSUserPage } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -55,7 +55,6 @@ const getTheLastArrayElement = (value: ReadonlyArray<string> | string | undefine
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const slug = getTheLastArrayElement(context.query.slug) ?? getTheLastArrayElement(context.query.orgSlug);
|
||||
|
||||
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(
|
||||
context.req,
|
||||
context.params?.orgSlug ?? context.query?.orgSlug
|
||||
|
||||
@@ -153,6 +153,8 @@ export const config = {
|
||||
// Next.js Doesn't support spread operator in config matcher, so, we must list all paths explicitly here.
|
||||
// https://github.com/vercel/next.js/discussions/42458
|
||||
matcher: [
|
||||
"/403",
|
||||
"/500",
|
||||
"/d/:path*",
|
||||
"/more/:path*",
|
||||
"/maintenance/:path*",
|
||||
@@ -193,7 +195,10 @@ export const config = {
|
||||
"/settings/:path*",
|
||||
"/reschedule/:path*",
|
||||
"/availability/:path*",
|
||||
"/booking/:path*",
|
||||
"/org/:path*",
|
||||
"/team/:path*",
|
||||
"/:user/:type/",
|
||||
"/:user/",
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -827,7 +827,8 @@ export default function Success(props: PageProps) {
|
||||
)}`
|
||||
: ""
|
||||
}`}
|
||||
className="text-default border-subtle h-10 w-10 rounded-sm border px-3 py-2 ltr:mr-2 rtl:ml-2">
|
||||
className="text-default border-subtle h-10 w-10 rounded-sm border px-3 py-2 ltr:mr-2 rtl:ml-2"
|
||||
target="_blank">
|
||||
<svg
|
||||
className="-mt-1.5 inline-block h-4 w-4"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Booker } from "@calcom/atoms/monorepo";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
|
||||
import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||
|
||||
@@ -20,13 +19,6 @@ export default function Type({
|
||||
}: PageProps) {
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
entity={entity}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
-10
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Booker } from "@calcom/atoms/monorepo";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
|
||||
import type { getServerSideProps } from "@lib/org/[orgSlug]/instant-meeting/team/[slug]/[type]/getServerSideProps";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -13,15 +12,6 @@ export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
function Type({ slug, user, booking, isEmbed, isBrandingHidden, entity, eventTypeId, duration }: PageProps) {
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={undefined}
|
||||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent
|
||||
entity={entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { usePathname } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
|
||||
import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -20,7 +19,7 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { Avatar, Button, HeadSeo, UnpublishedEntity, UserAvatarGroup } from "@calcom/ui";
|
||||
import { Avatar, Button, UnpublishedEntity, UserAvatarGroup } from "@calcom/ui";
|
||||
|
||||
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||
import type { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
@@ -164,105 +163,87 @@ function TeamPage({
|
||||
const profileImageSrc = getOrgOrTeamAvatar(team);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
origin={getOrgFullOrigin(currentOrgDomain)}
|
||||
title={teamName}
|
||||
description={teamName}
|
||||
meeting={{
|
||||
title: markdownStrippedBio,
|
||||
profile: {
|
||||
name: `${team.name}`,
|
||||
image: profileImageSrc,
|
||||
},
|
||||
}}
|
||||
nextSeoProps={{
|
||||
nofollow: !isSEOIndexable,
|
||||
noindex: !isSEOIndexable,
|
||||
}}
|
||||
/>
|
||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
|
||||
<div className="mx-auto mb-8 max-w-3xl text-center">
|
||||
<div className="relative">
|
||||
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
|
||||
</div>
|
||||
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
|
||||
{team.parent && `${team.parent.name} `}
|
||||
{teamName}
|
||||
</p>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: team.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
|
||||
<div className="mx-auto mb-8 max-w-3xl text-center">
|
||||
<div className="relative">
|
||||
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
|
||||
</div>
|
||||
{team.isOrganization ? (
|
||||
!teamOrOrgIsPrivate ? (
|
||||
<SubTeams />
|
||||
) : (
|
||||
<div className="w-full text-center">
|
||||
<h2 className="text-emphasis font-semibold">{t("you_cannot_see_teams_of_org")}</h2>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
|
||||
{team.parent && `${team.parent.name} `}
|
||||
{teamName}
|
||||
</p>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
{(showMembers.isOn || !team.eventTypes?.length) &&
|
||||
(teamOrOrgIsPrivate ? (
|
||||
<div className="w-full text-center">
|
||||
<h2 data-testid="you-cannot-see-team-members" className="text-emphasis font-semibold">
|
||||
{t("you_cannot_see_team_members")}
|
||||
</h2>
|
||||
</div>
|
||||
) : (
|
||||
<Team members={team.members} teamName={team.name} />
|
||||
))}
|
||||
{!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
|
||||
<div className="mx-auto max-w-3xl ">
|
||||
<EventTypes eventTypes={team.eventTypes} />
|
||||
|
||||
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */}
|
||||
{!team.hideBookATeamMember && !teamOrOrgIsPrivate && (
|
||||
<div>
|
||||
<div className="relative mt-12">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="border-subtle w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="dark:bg-darkgray-50 bg-subtle text-subtle px-2 text-sm">
|
||||
{t("or")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="dark:text-inverted mt-8 flex justify-center text-center">
|
||||
<Button
|
||||
color="minimal"
|
||||
EndIcon="arrow-right"
|
||||
data-testid="book-a-team-member-btn"
|
||||
className="dark:hover:bg-darkgray-200"
|
||||
href={{
|
||||
pathname: `${isValidOrgDomain ? "" : "/team"}/${team.slug}`,
|
||||
query: {
|
||||
...queryParamsToForward,
|
||||
members: "1",
|
||||
},
|
||||
}}
|
||||
shallow={true}>
|
||||
{t("book_a_team_member")}
|
||||
</Button>
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: team.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
</div>
|
||||
{team.isOrganization ? (
|
||||
!teamOrOrgIsPrivate ? (
|
||||
<SubTeams />
|
||||
) : (
|
||||
<div className="w-full text-center">
|
||||
<h2 className="text-emphasis font-semibold">{t("you_cannot_see_teams_of_org")}</h2>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{(showMembers.isOn || !team.eventTypes?.length) &&
|
||||
(teamOrOrgIsPrivate ? (
|
||||
<div className="w-full text-center">
|
||||
<h2 data-testid="you-cannot-see-team-members" className="text-emphasis font-semibold">
|
||||
{t("you_cannot_see_team_members")}
|
||||
</h2>
|
||||
</div>
|
||||
) : (
|
||||
<Team members={team.members} teamName={team.name} />
|
||||
))}
|
||||
{!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
|
||||
<div className="mx-auto max-w-3xl ">
|
||||
<EventTypes eventTypes={team.eventTypes} />
|
||||
|
||||
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */}
|
||||
{!team.hideBookATeamMember && !teamOrOrgIsPrivate && (
|
||||
<div>
|
||||
<div className="relative mt-12">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="border-subtle w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="dark:bg-darkgray-50 bg-subtle text-subtle px-2 text-sm">
|
||||
{t("or")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="dark:text-inverted mt-8 flex justify-center text-center">
|
||||
<Button
|
||||
color="minimal"
|
||||
EndIcon="arrow-right"
|
||||
data-testid="book-a-team-member-btn"
|
||||
className="dark:hover:bg-darkgray-200"
|
||||
href={{
|
||||
pathname: `${isValidOrgDomain ? "" : "/team"}/${team.slug}`,
|
||||
query: {
|
||||
...queryParamsToForward,
|
||||
members: "1",
|
||||
},
|
||||
}}
|
||||
shallow={true}>
|
||||
{t("book_a_team_member")}
|
||||
</Button>
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
import { Booker } from "@calcom/atoms/monorepo";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
|
||||
import type { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
|
||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
|
||||
@@ -34,33 +33,11 @@ function Type({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isSEOIndexable,
|
||||
}: PageProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const { profile, users, hidden, title } = eventData;
|
||||
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent
|
||||
eventData={
|
||||
profile && users && title && hidden !== undefined
|
||||
? {
|
||||
profile,
|
||||
users,
|
||||
title,
|
||||
hidden,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
entity={eventData.entity}
|
||||
bookingData={booking}
|
||||
isSEOIndexable={isSEOIndexable}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, it, vi } from "vitest";
|
||||
|
||||
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
||||
import UserPage from "./users-public-view";
|
||||
|
||||
@@ -71,33 +70,5 @@ describe("UserPage Component", () => {
|
||||
});
|
||||
|
||||
render(<UserPage {...mockData.props} />);
|
||||
|
||||
const expectedDescription = mockData.props.markdownStrippedBio;
|
||||
const expectedTitle = expectedDescription;
|
||||
expect(HeadSeo).toHaveBeenCalledWith(
|
||||
{
|
||||
origin: `${mockData.props.entity.orgSlug}.cal.local`,
|
||||
title: `${mockData.props.profile.name}`,
|
||||
description: expectedDescription,
|
||||
meeting: {
|
||||
profile: {
|
||||
name: mockData.props.profile.name,
|
||||
image: mockData.props.users[0].avatarUrl,
|
||||
},
|
||||
title: expectedTitle,
|
||||
users: [
|
||||
{
|
||||
name: mockData.props.users[0].name,
|
||||
username: mockData.props.users[0].username,
|
||||
},
|
||||
],
|
||||
},
|
||||
nextSeoProps: {
|
||||
nofollow: !mockData.props.profile.allowSEOIndexing,
|
||||
noindex: !mockData.props.profile.allowSEOIndexing,
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { getOrgFullOrigin } 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 { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { HeadSeo, Icon, UnpublishedEntity, UserAvatar } from "@calcom/ui";
|
||||
import { Icon, UnpublishedEntity, UserAvatar } from "@calcom/ui";
|
||||
|
||||
import type { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
@@ -60,112 +59,89 @@ export function UserPage(props: PageProps) {
|
||||
const isEventListEmpty = eventTypes.length === 0;
|
||||
const isOrg = !!user?.profile?.organization;
|
||||
|
||||
const allowSEOIndexing = isOrg
|
||||
? isOrgSEOIndexable
|
||||
? profile.allowSEOIndexing
|
||||
: false
|
||||
: profile.allowSEOIndexing;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
origin={getOrgFullOrigin(entity.orgSlug ?? null)}
|
||||
title={profile.name}
|
||||
description={markdownStrippedBio}
|
||||
meeting={{
|
||||
title: markdownStrippedBio,
|
||||
profile: { name: `${profile.name}`, image: user.avatarUrl || null },
|
||||
users: [{ username: `${user.username}`, name: `${user.name}` }],
|
||||
}}
|
||||
nextSeoProps={{
|
||||
noindex: !allowSEOIndexing,
|
||||
nofollow: !allowSEOIndexing,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
|
||||
<main
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md" : "",
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
user={{
|
||||
avatarUrl: user.avatarUrl,
|
||||
profile: user.profile,
|
||||
name: profile.name,
|
||||
username: profile.username,
|
||||
}}
|
||||
/>
|
||||
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
|
||||
{profile.name}
|
||||
{!isOrg && user.verified && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
{isOrg && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: props.safeBio }}
|
||||
/>
|
||||
</>
|
||||
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
|
||||
<main
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md" : "",
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
user={{
|
||||
avatarUrl: user.avatarUrl,
|
||||
profile: user.profile,
|
||||
name: profile.name,
|
||||
username: profile.username,
|
||||
}}
|
||||
/>
|
||||
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
|
||||
{profile.name}
|
||||
{!isOrg && user.verified && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isOrg && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: props.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
|
||||
data-testid="event-types">
|
||||
{eventTypes.map((type) => (
|
||||
<Link
|
||||
key={type.id}
|
||||
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||
prefetch={false}
|
||||
href={{
|
||||
pathname: `/${user.profile.username}/${type.slug}`,
|
||||
query,
|
||||
}}
|
||||
passHref
|
||||
onClick={async () => {
|
||||
sdkActionManager?.fire("eventTypeSelected", {
|
||||
eventType: type,
|
||||
});
|
||||
}}
|
||||
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0"
|
||||
data-testid="event-type-link">
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||
<div className="block w-full p-5">
|
||||
<div className="flex flex-wrap items-center">
|
||||
<h2 className="text-default pr-2 text-sm font-semibold">{type.title}</h2>
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
|
||||
<div
|
||||
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
|
||||
data-testid="event-types">
|
||||
{eventTypes.map((type) => (
|
||||
<Link
|
||||
key={type.id}
|
||||
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||
prefetch={false}
|
||||
href={{
|
||||
pathname: `/${user.profile.username}/${type.slug}`,
|
||||
query,
|
||||
}}
|
||||
passHref
|
||||
onClick={async () => {
|
||||
sdkActionManager?.fire("eventTypeSelected", {
|
||||
eventType: type,
|
||||
});
|
||||
}}
|
||||
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0"
|
||||
data-testid="event-type-link">
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||
<div className="block w-full p-5">
|
||||
<div className="flex flex-wrap items-center">
|
||||
<h2 className="text-default pr-2 text-sm font-semibold">{type.title}</h2>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
|
||||
</main>
|
||||
<Toaster position="bottom-right" />
|
||||
</div>
|
||||
</>
|
||||
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
|
||||
</main>
|
||||
<Toaster position="bottom-right" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
UserPage.isBookingPage = true;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
import { Booker } from "@calcom/atoms/monorepo";
|
||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
|
||||
import type { getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps";
|
||||
|
||||
@@ -23,40 +22,11 @@ export const getMultipleDurationValue = (
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
function Type({
|
||||
slug,
|
||||
user,
|
||||
isEmbed,
|
||||
booking,
|
||||
isBrandingHidden,
|
||||
isSEOIndexable,
|
||||
rescheduleUid,
|
||||
eventData,
|
||||
orgBannerUrl,
|
||||
}: PageProps) {
|
||||
function Type({ slug, user, isEmbed, booking, isBrandingHidden, eventData, orgBannerUrl }: PageProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const { profile, users, hidden, title } = eventData;
|
||||
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={rescheduleUid ?? undefined}
|
||||
hideBranding={isBrandingHidden}
|
||||
isSEOIndexable={isSEOIndexable ?? true}
|
||||
eventData={
|
||||
profile && users && title && hidden !== undefined
|
||||
? {
|
||||
profile,
|
||||
users,
|
||||
title,
|
||||
hidden,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
entity={eventData.entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
@@ -395,6 +395,11 @@ const nextConfig = {
|
||||
value: "cross-origin",
|
||||
};
|
||||
|
||||
const ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = {
|
||||
key: "Access-Control-Allow-Origin",
|
||||
value: "*",
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
source: "/auth/:path*",
|
||||
@@ -464,7 +469,7 @@ const nextConfig = {
|
||||
},
|
||||
{
|
||||
source: "/icons/sprite.svg",
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER],
|
||||
headers: [CORP_CROSS_ORIGIN_HEADER, ACCESS_CONTROL_ALLOW_ORIGIN_HEADER],
|
||||
},
|
||||
],
|
||||
...(isOrganizationsEnabled
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "4.8.11",
|
||||
"version": "4.8.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import Head from "next/head";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, showToast } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
export default function Error500() {
|
||||
const searchParams = useCompatSearchParams();
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<div className="bg-subtle flex h-screen">
|
||||
<Head>
|
||||
<title>Something unexpected occurred | {APP_NAME}</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">It's not you, it's us.</h2>
|
||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">{t("something_went_wrong_on_our_end")}</p>
|
||||
{searchParams?.get("error") && (
|
||||
<div className="mb-8 flex flex-col">
|
||||
<p className="text-default mb-4 max-w-2xl text-sm">
|
||||
{t("please_provide_following_text_to_suppport")}:
|
||||
</p>
|
||||
<pre className="bg-emphasis text-emphasis w-full max-w-2xl whitespace-normal break-words rounded-md p-4">
|
||||
{searchParams?.get("error")}
|
||||
<br />
|
||||
<Button
|
||||
color="secondary"
|
||||
className="mt-2 border-0 font-sans font-normal hover:bg-gray-300"
|
||||
StartIcon="copy"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(searchParams?.get("error") as string);
|
||||
showToast("Link copied!", "success");
|
||||
}}>
|
||||
{t("copy")}
|
||||
</Button>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
<Button href="mailto:support@cal.com">{t("contact_support")}</Button>
|
||||
<Button color="secondary" href="javascript:history.back()" className="ml-2">
|
||||
{t("go_back")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Error500.PageWrapper = PageWrapper;
|
||||
@@ -1,7 +1,16 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps";
|
||||
|
||||
import type { PageProps } from "~/users/views/users-type-public-view";
|
||||
import TypePage from "~/users/views/users-type-public-view";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
|
||||
export { default } from "./index";
|
||||
const Type = (props: PageProps) => <TypePage {...props} />;
|
||||
|
||||
Type.PageWrapper = PageWrapper;
|
||||
|
||||
export default Type;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { PageProps } from "~/users/views/users-type-public-view";
|
||||
import TypePage from "~/users/views/users-type-public-view";
|
||||
|
||||
export { getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps";
|
||||
|
||||
const Type = (props: PageProps) => <TypePage {...props} />;
|
||||
|
||||
Type.PageWrapper = PageWrapper;
|
||||
|
||||
export default Type;
|
||||
@@ -1,6 +1,19 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
import type { PageProps as TeamPageProps } from "~/team/team-view";
|
||||
import TeamPage from "~/team/team-view";
|
||||
import UserPage from "~/users/views/users-public-view";
|
||||
import type { PageProps as UserPageProps } from "~/users/views/users-public-view";
|
||||
|
||||
export { default } from "./index";
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps";
|
||||
|
||||
export type PageProps = UserPageProps | TeamPageProps;
|
||||
|
||||
function Page(props: PageProps) {
|
||||
if ((props as TeamPageProps)?.team) return <TeamPage {...(props as TeamPageProps)} />;
|
||||
return <UserPage {...(props as UserPageProps)} />;
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import User, { type PageProps } from "~/users/views/users-public-view";
|
||||
|
||||
export { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
const UserPage = (props: PageProps) => <User {...props} />;
|
||||
|
||||
UserPage.PageWrapper = PageWrapper;
|
||||
|
||||
export default UserPage;
|
||||
@@ -1,77 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import type { InferGetStaticPropsType } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import Head from "next/head";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import type { IconName } from "@calcom/ui";
|
||||
import { Icon, IconSprites } from "@calcom/ui";
|
||||
|
||||
import { lucideIconList } from "../../../packages/ui/components/icon/icon-list.mjs";
|
||||
|
||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||
const calFont = localFont({
|
||||
src: "../fonts/CalSans-SemiBold.woff2",
|
||||
variable: "--font-cal",
|
||||
preload: true,
|
||||
display: "swap",
|
||||
weight: "600",
|
||||
});
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
return {
|
||||
props: {
|
||||
icons: Array.from(lucideIconList).sort() as IconName[],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const IconGrid = (props: {
|
||||
title: string;
|
||||
icons: IconName[];
|
||||
rootClassName?: string;
|
||||
iconClassName?: string;
|
||||
}) => (
|
||||
<div className={props.rootClassName}>
|
||||
<h2 className="font-cal mt-6 text-lg font-medium">{props.title}</h2>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-6">
|
||||
{props.icons.map((icon) => {
|
||||
return (
|
||||
<div key={icon} className="flex items-center gap-1">
|
||||
<Icon name={icon} className={props.iconClassName} />
|
||||
<div>{icon}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function IconsPage(props: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
return (
|
||||
<div className="bg-subtle flex h-screen">
|
||||
<Head>
|
||||
<title>Icon showcase | {APP_NAME}</title>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
:root {
|
||||
--font-cal: ${calFont.style.fontFamily};
|
||||
--font-inter: ${interFont.style.fontFamily};
|
||||
}
|
||||
`}</style>
|
||||
<IconSprites />
|
||||
<div className="bg-default m-auto min-w-full rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="text-emphasis font-cal text-2xl font-medium">Icons showcase</h1>
|
||||
<IconGrid title="Regular Icons" icons={props.icons} />
|
||||
<IconGrid
|
||||
title="Filled Icons"
|
||||
icons={props.icons}
|
||||
rootClassName="bg-darkgray-100 text-gray-50"
|
||||
iconClassName="fill-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,20 @@
|
||||
import { getServerSideProps as _getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from ".";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
export { default, type PageProps } from ".";
|
||||
import type { PageProps as TeamTypePageProps } from "~/team/type-view";
|
||||
import TeamTypePage from "~/team/type-view";
|
||||
import UserTypePage from "~/users/views/users-type-public-view";
|
||||
import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view";
|
||||
|
||||
export type PageProps = UserTypePageProps | TeamTypePageProps;
|
||||
|
||||
export default function Page(props: PageProps) {
|
||||
if ((props as TeamTypePageProps)?.teamId) return <TeamTypePage {...(props as TeamTypePageProps)} />;
|
||||
return <UserTypePage {...(props as UserTypePageProps)} />;
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { PageProps as TeamTypePageProps } from "~/team/type-view";
|
||||
import TeamTypePage from "~/team/type-view";
|
||||
import UserTypePage from "~/users/views/users-type-public-view";
|
||||
import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view";
|
||||
|
||||
export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||
|
||||
export type PageProps = UserTypePageProps | TeamTypePageProps;
|
||||
|
||||
export default function Page(props: PageProps) {
|
||||
if ((props as TeamTypePageProps)?.teamId) return <TeamTypePage {...(props as TeamTypePageProps)} />;
|
||||
return <UserTypePage {...(props as UserTypePageProps)} />;
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
@@ -1,9 +1 @@
|
||||
"use client";
|
||||
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[user]";
|
||||
|
||||
export { default } from "../[user]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
export { default, getServerSideProps } from "@pages/[user]/embed";
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { PageProps as TeamPageProps } from "~/team/team-view";
|
||||
import TeamPage from "~/team/team-view";
|
||||
import UserPage from "~/users/views/users-public-view";
|
||||
import type { PageProps as UserPageProps } from "~/users/views/users-public-view";
|
||||
|
||||
export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps";
|
||||
|
||||
export type PageProps = UserPageProps | TeamPageProps;
|
||||
|
||||
function Page(props: PageProps) {
|
||||
if ((props as TeamPageProps)?.team) return <TeamPage {...(props as TeamPageProps)} />;
|
||||
return <UserPage {...(props as UserPageProps)} />;
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
@@ -1,7 +1,14 @@
|
||||
import { getServerSideProps as _getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "./index";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
export { default } from "./index";
|
||||
import TeamPage, { type PageProps } from "~/team/team-view";
|
||||
|
||||
const Page = (props: PageProps) => <TeamPage {...props} />;
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { getServerSideProps, default } from "@pages/team/[slug]";
|
||||
@@ -1,10 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { PageProps } from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-meeting-view";
|
||||
import Type from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-meeting-view";
|
||||
|
||||
const Page = (props: PageProps) => <Type {...props} />;
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export { getServerSideProps } from "@lib/org/[orgSlug]/instant-meeting/team/[slug]/[type]/getServerSideProps";
|
||||
export default Page;
|
||||
@@ -1 +0,0 @@
|
||||
export { getServerSideProps, default } from "@pages/team/[slug]/[type]";
|
||||
@@ -1 +0,0 @@
|
||||
export { getServerSideProps, default } from "@pages/team/[slug]";
|
||||
@@ -1,12 +0,0 @@
|
||||
import { getServerSideProps } from "@lib/signup/getServerSideProps";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { SignupProps } from "~/signup-view";
|
||||
import Signup from "~/signup-view";
|
||||
|
||||
const Page = (props: SignupProps) => <Signup {...props} />;
|
||||
|
||||
export { getServerSideProps };
|
||||
Page.PageWrapper = PageWrapper;
|
||||
export default Page;
|
||||
@@ -1,10 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import TeamPage, { type PageProps } from "~/team/team-view";
|
||||
|
||||
const Page = (props: PageProps) => <TeamPage {...props} />;
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
export { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||
@@ -1,10 +0,0 @@
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import TypePage, { type PageProps } from "~/team/type-view";
|
||||
|
||||
const Page = (props: PageProps) => <TypePage {...props} />;
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
export { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
selectSecondAvailableTimeSlotNextMonth,
|
||||
} from "./lib/testUtils";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
test("dynamic booking", async ({ page, users }) => {
|
||||
|
||||
@@ -66,7 +66,7 @@ async function verifyRobotsMetaTag({ page, orgSlug, urls, expectedContent }: Ver
|
||||
await doOnOrgDomain({ orgSlug, page }, async ({ page, goToUrlWithErrorHandling }) => {
|
||||
for (const relativeUrl of urls) {
|
||||
const { url } = await goToUrlWithErrorHandling(relativeUrl);
|
||||
const metaTag = await page.locator('head > meta[name="robots"]');
|
||||
const metaTag = await page.locator('head > meta[name="robots"]').first();
|
||||
const metaTagValue = await metaTag.getAttribute("content");
|
||||
expect(metaTagValue).toEqual(expectedContent);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ test.describe("Organization Settings", () => {
|
||||
`/${orgMember.username}`,
|
||||
`/${orgMember.username}/${userEvent.slug}`,
|
||||
],
|
||||
expectedContent: "noindex,nofollow",
|
||||
expectedContent: "noindex, nofollow",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -130,7 +130,7 @@ test.describe("Organization Settings", () => {
|
||||
`/${orgMember.username}`,
|
||||
`/${orgMember.username}/${userEvent.slug}`,
|
||||
],
|
||||
expectedContent: "index,follow",
|
||||
expectedContent: "index, follow",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -148,7 +148,7 @@ test.describe("Organization Settings", () => {
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
urls: [`/${orgMember.username}/${userEvent.slug}`],
|
||||
expectedContent: "noindex,nofollow",
|
||||
expectedContent: "noindex, nofollow",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "وقت نهاية الحدث",
|
||||
"cancel_url_variable": "رابط الإلغاء",
|
||||
"cancel_url_info": "رابط إلغاء الحجز",
|
||||
"cancel_reason_variable": "سبب الإلغاء",
|
||||
"cancel_reason_info": "سبب إلغاء الحجز",
|
||||
"reschedule_url_variable": "رابط إعادة الجدولة",
|
||||
"reschedule_url_info": "رابط لإعادة جدولة الحجز",
|
||||
"reschedule_reason_variable": "سبب إعادة الجدولة",
|
||||
"reschedule_reason_info": "سبب إعادة جدولة الحجز",
|
||||
"invalid_event_name_variables": "هناك متغيّر غير صالح في اسم الحدث",
|
||||
"select_all": "تحديد الكل",
|
||||
"default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الأحداث الحالية",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "منذ بداية الشهر",
|
||||
"year_to_date": "منذ بداية السنة",
|
||||
"custom_range": "نطاق مخصص",
|
||||
"refund_policy": "سياسة الاسترداد",
|
||||
"always": "دائماً",
|
||||
"never": "أبداً",
|
||||
"payment_option": "خيار الدفع",
|
||||
"if_cancelled": "في حالة الإلغاء",
|
||||
"before": "قبل",
|
||||
"show_all_columns": "عرض جميع الأعمدة",
|
||||
"toggle_columns": "تبديل الأعمدة",
|
||||
"no_columns_found": "لم يتم العثور على أعمدة",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "المعايرة",
|
||||
"shortfall": "العجز",
|
||||
"routing_form": "نموذج التوجيه",
|
||||
"go_back_and_save": "العودة والحفظ",
|
||||
"leave_without_saving": "المغادرة دون حفظ",
|
||||
"leave_without_saving_description": "هل أنت متأكد من أنك تريد المغادرة دون حفظ التغييرات في نموذج التوجيه الخاص بك؟",
|
||||
"something_unexpected_occurred": "حدث خطأ غير متوقع",
|
||||
"500_error_message": "الخطأ من جانبنا، وليس من جانبك.",
|
||||
"dry_run_mode_active": "أنت تقوم بحجز تجريبي.",
|
||||
"icon_showcase": "معرض الأيقونات",
|
||||
"icons_showcase": "معرض الأيقونات",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ أضف السلاسل الجديدة أعلاه هنا ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Tədbirin bitmə vaxtı",
|
||||
"cancel_url_variable": "Ləğv URL",
|
||||
"cancel_url_info": "Rezervasiyanı ləğv etmək üçün URL",
|
||||
"cancel_reason_variable": "Ləğv etmə səbəbi",
|
||||
"cancel_reason_info": "Rezervasiyanın ləğv edilməsi səbəbi",
|
||||
"reschedule_url_variable": "Yenidən planlaşdırma URL",
|
||||
"reschedule_url_info": "Rezervasiyanı yenidən planlaşdırmaq üçün URL",
|
||||
"reschedule_reason_variable": "Yenidən planlaşdırma səbəbi",
|
||||
"reschedule_reason_info": "Rezervasiyanın yenidən planlaşdırılması səbəbi",
|
||||
"invalid_event_name_variables": "{{item}} tədbir adında etibarsız dəyişəndir",
|
||||
"select_all": "Hamısını seç",
|
||||
"default_conferencing_bulk_title": "Mövcud tədbir növlərini toplu yeniləmə",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "bu ayın tarixinə qədər",
|
||||
"year_to_date": "bu ilin tarixinə qədər",
|
||||
"custom_range": "xüsusi aralıq",
|
||||
"refund_policy": "Geri ödəmə siyasəti",
|
||||
"always": "Həmişə",
|
||||
"never": "Heç vaxt",
|
||||
"payment_option": "Ödəniş seçimi",
|
||||
"if_cancelled": "Ləğv edilərsə",
|
||||
"before": "əvvəl",
|
||||
"show_all_columns": "Bütün sütunları göstər",
|
||||
"toggle_columns": "Sütunları dəyiş",
|
||||
"no_columns_found": "Sütun tapılmadı",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrasiya",
|
||||
"shortfall": "Çatışmazlıq",
|
||||
"routing_form": "Marşrutlama forması",
|
||||
"go_back_and_save": "Geri qayıt və yadda saxla",
|
||||
"leave_without_saving": "Yadda saxlamadan çıx",
|
||||
"leave_without_saving_description": "Marşrutlaşdırma formanızdakı dəyişiklikləri yadda saxlamadan çıxmaq istədiyinizə əminsiniz?",
|
||||
"something_unexpected_occurred": "Gözlənilməz bir xəta baş verdi",
|
||||
"500_error_message": "Problem sizdə deyil, bizdədir.",
|
||||
"dry_run_mode_active": "Siz test rezervasiyası edirsiniz.",
|
||||
"icon_showcase": "İkon nümayişi",
|
||||
"icons_showcase": "İkonların nümayişi",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Yeni sətirləri bura əlavə edin ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Крайният час на събитието",
|
||||
"cancel_url_variable": "URL за отказ",
|
||||
"cancel_url_info": "URL адресът за отказване на резервацията",
|
||||
"cancel_reason_variable": "Причина за отказ",
|
||||
"cancel_reason_info": "Причината за отказване на резервацията",
|
||||
"reschedule_url_variable": "URL за препланиране",
|
||||
"reschedule_url_info": "URL адресът за препланиране на резервацията",
|
||||
"reschedule_reason_variable": "Причина за препланиране",
|
||||
"reschedule_reason_info": "Причината за препланиране на резервацията",
|
||||
"invalid_event_name_variables": "{{item}} е невалидна променлива в името на събитието",
|
||||
"select_all": "Избери всички",
|
||||
"default_conferencing_bulk_title": "Масова актуализация на съществуващите типове събития",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "от началото на месеца",
|
||||
"year_to_date": "от началото на годината",
|
||||
"custom_range": "персонализиран период",
|
||||
"refund_policy": "Политика за възстановяване",
|
||||
"always": "Винаги",
|
||||
"never": "Никога",
|
||||
"payment_option": "Опция за плащане",
|
||||
"if_cancelled": "При отказ",
|
||||
"before": "преди",
|
||||
"show_all_columns": "Покажи всички колони",
|
||||
"toggle_columns": "Превключи колоните",
|
||||
"no_columns_found": "Няма намерени колони",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Калибриране",
|
||||
"shortfall": "Недостиг",
|
||||
"routing_form": "Форма за маршрутизиране",
|
||||
"go_back_and_save": "Върни се и запази",
|
||||
"leave_without_saving": "Излез без да запазиш",
|
||||
"leave_without_saving_description": "Сигурни ли сте, че искате да излезете без да запазите промените във вашата форма за маршрутизиране?",
|
||||
"something_unexpected_occurred": "Възникна неочаквана грешка",
|
||||
"500_error_message": "Не сте вие, ние сме.",
|
||||
"dry_run_mode_active": "Правите тестова резервация.",
|
||||
"icon_showcase": "Галерия с икони",
|
||||
"icons_showcase": "Галерия с икони",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Добавете новите си низове над този ред ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "L'hora de finalització de l'esdeveniment",
|
||||
"cancel_url_variable": "URL de cancel·lació",
|
||||
"cancel_url_info": "L'URL per cancel·lar la reserva",
|
||||
"cancel_reason_variable": "Motiu de cancel·lació",
|
||||
"cancel_reason_info": "El motiu per cancel·lar la reserva",
|
||||
"reschedule_url_variable": "URL de reprogramació",
|
||||
"reschedule_url_info": "L'URL per reprogramar la reserva",
|
||||
"reschedule_reason_variable": "Motiu de reprogramació",
|
||||
"reschedule_reason_info": "El motiu per reprogramar la reserva",
|
||||
"invalid_event_name_variables": "{{item}} és una variable no vàlida en el nom del teu esdeveniment",
|
||||
"select_all": "Selecciona-ho tot",
|
||||
"default_conferencing_bulk_title": "Actualització massiva dels tipus d'esdeveniments existents",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "mes fins avui",
|
||||
"year_to_date": "any fins avui",
|
||||
"custom_range": "rang personalitzat",
|
||||
"refund_policy": "Política de reemborsament",
|
||||
"always": "Sempre",
|
||||
"never": "Mai",
|
||||
"payment_option": "Opció de pagament",
|
||||
"if_cancelled": "Si es cancel·la",
|
||||
"before": "abans",
|
||||
"show_all_columns": "Mostra totes les columnes",
|
||||
"toggle_columns": "Commuta columnes",
|
||||
"no_columns_found": "No s'han trobat columnes",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Calibratge",
|
||||
"shortfall": "Dèficit",
|
||||
"routing_form": "Formulari d'encaminament",
|
||||
"go_back_and_save": "Torna enrere i desa",
|
||||
"leave_without_saving": "Surt sense desar",
|
||||
"leave_without_saving_description": "Estàs segur que vols sortir sense desar els canvis al formulari de rutes?",
|
||||
"something_unexpected_occurred": "S'ha produït un error inesperat",
|
||||
"500_error_message": "No ets tu, som nosaltres.",
|
||||
"dry_run_mode_active": "Estàs fent una reserva de prova.",
|
||||
"icon_showcase": "Mostra d'icones",
|
||||
"icons_showcase": "Mostra d'icones",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Afegiu les vostres noves cadenes a dalt ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Čas ukončení události",
|
||||
"cancel_url_variable": "Adresa URL pro zrušení",
|
||||
"cancel_url_info": "Adresa URL pro zrušení rezervace",
|
||||
"cancel_reason_variable": "Důvod zrušení",
|
||||
"cancel_reason_info": "Důvod pro zrušení rezervace",
|
||||
"reschedule_url_variable": "Adresa URL pro změnu termínu",
|
||||
"reschedule_url_info": "Adresa URL pro změnu termínu rezervace",
|
||||
"reschedule_reason_variable": "Důvod přeplánování",
|
||||
"reschedule_reason_info": "Důvod pro přeplánování rezervace",
|
||||
"invalid_event_name_variables": "V názvu události je neplatná proměnná",
|
||||
"select_all": "Vybrat vše",
|
||||
"default_conferencing_bulk_title": "Hromadná aktualizace stávajících typů událostí",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "od začátku měsíce",
|
||||
"year_to_date": "od začátku roku",
|
||||
"custom_range": "vlastní rozsah",
|
||||
"refund_policy": "Pravidla pro vrácení peněz",
|
||||
"always": "Vždy",
|
||||
"never": "Nikdy",
|
||||
"payment_option": "Možnost platby",
|
||||
"if_cancelled": "Při zrušení",
|
||||
"before": "před",
|
||||
"show_all_columns": "Zobrazit všechny sloupce",
|
||||
"toggle_columns": "Přepnout sloupce",
|
||||
"no_columns_found": "Žádné sloupce nebyly nalezeny",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrace",
|
||||
"shortfall": "Nedostatek",
|
||||
"routing_form": "Směrovací formulář",
|
||||
"go_back_and_save": "Vrátit se a uložit",
|
||||
"leave_without_saving": "Odejít bez uložení",
|
||||
"leave_without_saving_description": "Opravdu chcete odejít bez uložení změn ve vašem směrovacím formuláři?",
|
||||
"something_unexpected_occurred": "Došlo k neočekávané chybě",
|
||||
"500_error_message": "Není to vámi, je to námi.",
|
||||
"dry_run_mode_active": "Provádíte testovací rezervaci.",
|
||||
"icon_showcase": "Ukázka ikon",
|
||||
"icons_showcase": "Ukázka ikon",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Přidejte své nové řetězce nahoru ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Sluttidspunkt for begivenheden",
|
||||
"cancel_url_variable": "Annuller URL",
|
||||
"cancel_url_info": "URL'en til at annullere bookingen",
|
||||
"cancel_reason_variable": "Aflysningsårsag",
|
||||
"cancel_reason_info": "Årsagen til at aflyse bookingen",
|
||||
"reschedule_url_variable": "Omlæg URL",
|
||||
"reschedule_url_info": "URL'en til at omlægge bookingen",
|
||||
"reschedule_reason_variable": "Omlægningsårsag",
|
||||
"reschedule_reason_info": "Årsagen til at omlægge bookingen",
|
||||
"invalid_event_name_variables": "{{item}} er en ugyldig variabel i dit begivenhedsnavn",
|
||||
"select_all": "Vælg alle",
|
||||
"default_conferencing_bulk_title": "Masseopdater eksisterende begivenhedstyper",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "måned til dato",
|
||||
"year_to_date": "år til dato",
|
||||
"custom_range": "tilpasset interval",
|
||||
"refund_policy": "Refundpolitik",
|
||||
"always": "Altid",
|
||||
"never": "Aldrig",
|
||||
"payment_option": "Betalingsmulighed",
|
||||
"if_cancelled": "Hvis annulleret",
|
||||
"before": "før",
|
||||
"show_all_columns": "Vis alle kolonner",
|
||||
"toggle_columns": "Skift kolonner",
|
||||
"no_columns_found": "Ingen kolonner fundet",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrering",
|
||||
"shortfall": "Underskud",
|
||||
"routing_form": "Routing-formular",
|
||||
"go_back_and_save": "Gå tilbage og gem",
|
||||
"leave_without_saving": "Forlad uden at gemme",
|
||||
"leave_without_saving_description": "Er du sikker på, at du vil forlade siden uden at gemme ændringerne i din rutningsformular?",
|
||||
"something_unexpected_occurred": "Der opstod en uventet fejl",
|
||||
"500_error_message": "Det er ikke dig, det er os.",
|
||||
"dry_run_mode_active": "Du foretager en testbooking.",
|
||||
"icon_showcase": "Ikonoversigt",
|
||||
"icons_showcase": "Ikonoversigt",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Tilføj dine nye strenge ovenfor her ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Der Endzeitpunkt des Termins",
|
||||
"cancel_url_variable": "Absage-URL",
|
||||
"cancel_url_info": "Die URL, um die Buchung abzusagen",
|
||||
"cancel_reason_variable": "Stornierungsgrund",
|
||||
"cancel_reason_info": "Der Grund für die Stornierung der Buchung",
|
||||
"reschedule_url_variable": "Umplanungs-URL",
|
||||
"reschedule_url_info": "Die URL, um die Buchung umzuplanen",
|
||||
"reschedule_reason_variable": "Umbuchungsgrund",
|
||||
"reschedule_reason_info": "Der Grund für die Umbuchung des Termins",
|
||||
"invalid_event_name_variables": "Es gibt eine ungültige Variable in Ihrem Terminnamen",
|
||||
"select_all": "Alle auswählen",
|
||||
"default_conferencing_bulk_title": "Massenaktualisierung vorhandener Termintypen",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "aktueller Monat",
|
||||
"year_to_date": "aktuelles Jahr",
|
||||
"custom_range": "benutzerdefinierter Zeitraum",
|
||||
"refund_policy": "Rückerstattungsrichtlinie",
|
||||
"always": "Immer",
|
||||
"never": "Nie",
|
||||
"payment_option": "Zahlungsoption",
|
||||
"if_cancelled": "Bei Stornierung",
|
||||
"before": "vor",
|
||||
"show_all_columns": "Alle Spalten anzeigen",
|
||||
"toggle_columns": "Spalten ein-/ausblenden",
|
||||
"no_columns_found": "Keine Spalten gefunden",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrierung",
|
||||
"shortfall": "Fehlmenge",
|
||||
"routing_form": "Routing-Formular",
|
||||
"go_back_and_save": "Zurück und speichern",
|
||||
"leave_without_saving": "Ohne speichern verlassen",
|
||||
"leave_without_saving_description": "Sind Sie sicher, dass Sie die Änderungen an Ihrem Routing-Formular nicht speichern möchten?",
|
||||
"something_unexpected_occurred": "Ein unerwarteter Fehler ist aufgetreten",
|
||||
"500_error_message": "Es liegt nicht an Ihnen, sondern an uns.",
|
||||
"dry_run_mode_active": "Sie führen eine Testbuchung durch.",
|
||||
"icon_showcase": "Icon-Übersicht",
|
||||
"icons_showcase": "Icons-Übersicht",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Η ώρα λήξης του συμβάντος",
|
||||
"cancel_url_variable": "URL ακύρωσης",
|
||||
"cancel_url_info": "Το URL για την ακύρωση της κράτησης",
|
||||
"cancel_reason_variable": "Αιτία ακύρωσης",
|
||||
"cancel_reason_info": "Ο λόγος για την ακύρωση της κράτησης",
|
||||
"reschedule_url_variable": "URL επαναπρογραμματισμού",
|
||||
"reschedule_url_info": "Το URL για τον επαναπρογραμματισμό της κράτησης",
|
||||
"reschedule_reason_variable": "Αιτία αλλαγής",
|
||||
"reschedule_reason_info": "Ο λόγος για την αλλαγή της κράτησης",
|
||||
"invalid_event_name_variables": "Το {{item}} είναι μια μη έγκυρη μεταβλητή στο όνομα του συμβάντος σας",
|
||||
"select_all": "Επιλογή όλων",
|
||||
"default_conferencing_bulk_title": "Μαζική ενημέρωση υπαρχόντων τύπων συμβάντων",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "από την αρχή του μήνα",
|
||||
"year_to_date": "από την αρχή του έτους",
|
||||
"custom_range": "προσαρμοσμένο εύρος",
|
||||
"refund_policy": "Πολιτική επιστροφής χρημάτων",
|
||||
"always": "Πάντα",
|
||||
"never": "Ποτέ",
|
||||
"payment_option": "Επιλογή πληρωμής",
|
||||
"if_cancelled": "Εάν ακυρωθεί",
|
||||
"before": "πριν",
|
||||
"show_all_columns": "Εμφάνιση όλων των στηλών",
|
||||
"toggle_columns": "Εναλλαγή στηλών",
|
||||
"no_columns_found": "Δεν βρέθηκαν στήλες",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Βαθμονόμηση",
|
||||
"shortfall": "Έλλειμμα",
|
||||
"routing_form": "Φόρμα δρομολόγησης",
|
||||
"go_back_and_save": "Επιστροφή και αποθήκευση",
|
||||
"leave_without_saving": "Έξοδος χωρίς αποθήκευση",
|
||||
"leave_without_saving_description": "Είστε βέβαιοι ότι θέλετε να αποχωρήσετε χωρίς να αποθηκεύσετε τις αλλαγές στη φόρμα δρομολόγησης;",
|
||||
"something_unexpected_occurred": "Συνέβη κάτι απρόσμενο",
|
||||
"500_error_message": "Δεν φταίτε εσείς, εμείς φταίμε.",
|
||||
"dry_run_mode_active": "Κάνετε μια δοκιμαστική κράτηση.",
|
||||
"icon_showcase": "Παρουσίαση εικονιδίων",
|
||||
"icons_showcase": "Παρουσίαση εικονιδίων",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Προσθέστε τις νέες συμβολοσειρές σας πάνω από εδώ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "The event end time",
|
||||
"cancel_url_variable": "Cancel URL",
|
||||
"cancel_url_info": "The URL to cancel the booking",
|
||||
"cancel_reason_variable": "Cancellation Reason",
|
||||
"cancel_reason_info": "The reason for canceling the booking",
|
||||
"reschedule_url_variable": "Reschedule URL",
|
||||
"reschedule_url_info": "The URL to reschedule the booking",
|
||||
"reschedule_reason_variable": "Reschedule Reason",
|
||||
"reschedule_reason_info": "The reason for rescheduling the booking",
|
||||
"invalid_event_name_variables": "{{item}} is an invalid variable in your event name",
|
||||
"select_all": "Select All",
|
||||
"default_conferencing_bulk_title": "Bulk update existing event types",
|
||||
@@ -2528,26 +2532,22 @@
|
||||
"schedule_timezone_change": "Schedule timezone change",
|
||||
"date": "Date",
|
||||
"overlaps_with_existing_schedule": "This overlaps with an existing schedule. Please select a different date.",
|
||||
|
||||
"org_admin_no_slots|subject": "No availability found for {{name}}",
|
||||
"org_admin_no_slots|heading": "No availability found for {{name}}",
|
||||
"org_admin_no_slots|content": "Hello Organization Admins,<br /><br />Please note: It has been brought to our attention that {{username}} has not had any availability when a user has visited {{username}}/{{slug}}<br /><br />There’s a few reasons why this could be happening<br />The user does not have any calendars connected<br />Their schedules attached to this event are not enabled<br /> <br />We recommend checking their availability to resolve this.",
|
||||
"org_admin_no_slots|cta": "Open users availability",
|
||||
"organization_no_slots_notification_switch_title": "Get notifications when your team has no availability",
|
||||
"organization_no_slots_notification_switch_description": "Admins will get email notifications when a user tries to book a team member and is faced with 'No availability'. We trigger this email after two occurrences and remind you every 7 days per user. ",
|
||||
|
||||
"email_team_invite|subject|added_to_org": "{{user}} added you to the organization {{team}} on {{appName}}",
|
||||
"email_team_invite|subject|invited_to_org": "{{user}} invited you to join the organization {{team}} on {{appName}}",
|
||||
"email_team_invite|subject|added_to_subteam": "{{user}} added you to the team {{team}} of organization {{parentTeamName}} on {{appName}}",
|
||||
"email_team_invite|subject|invited_to_subteam": "{{user}} invited you to join the team {{team}} of organization {{parentTeamName}} on {{appName}}",
|
||||
"email_team_invite|subject|invited_to_regular_team": "{{user}} invited you to join the team {{team}} on {{appName}}",
|
||||
|
||||
"email_team_invite|heading|added_to_org": "You’ve been added to a {{appName}} organization",
|
||||
"email_team_invite|heading|invited_to_org": "You’ve been invited to a {{appName}} organization",
|
||||
"email_team_invite|heading|added_to_subteam": "You’ve been added to a team of {{parentTeamName}} organization",
|
||||
"email_team_invite|heading|invited_to_subteam": "You’ve been invited to a team of {{parentTeamName}} organization",
|
||||
"email_team_invite|heading|invited_to_regular_team": "You’ve been invited to join a {{appName}} team",
|
||||
|
||||
"email_team_invite|content|added_to_org": "{{invitedBy}} has added you to the <strong>{{teamName}}</strong> organization.",
|
||||
"email_team_invite|content|invited_to_org": "{{invitedBy}} has invited you to join the {{teamName}} organization.",
|
||||
"email_team_invite|content|added_to_subteam": "{{invitedBy}} has added you to the team <strong>{{teamName}}</strong> in their organization {{parentTeamName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.",
|
||||
@@ -2741,6 +2741,12 @@
|
||||
"month_to_date": "month to date",
|
||||
"year_to_date": "year to date",
|
||||
"custom_range": "custom range",
|
||||
"refund_policy": "Refund Policy",
|
||||
"always": "Always",
|
||||
"never": "Never",
|
||||
"payment_option": "Payment Option",
|
||||
"if_cancelled": "If cancelled",
|
||||
"before": "before",
|
||||
"show_all_columns": "Show all columns",
|
||||
"toggle_columns": "Toggle columns",
|
||||
"no_columns_found": "No columns found",
|
||||
@@ -2840,12 +2846,12 @@
|
||||
"routing_form_insights_booking_status": "Booking Status",
|
||||
"routing_form_insights_booking_at": "Booking At",
|
||||
"routing_form_insights_submitted_at": "Submitted At",
|
||||
"total_bookings_per_period":"Total Bookings per period",
|
||||
"routed_to_per_period":"Routed to per period",
|
||||
"total_bookings_per_period": "Total Bookings per period",
|
||||
"routed_to_per_period": "Routed to per period",
|
||||
"per_day": "Per day",
|
||||
"per_week": "Per week",
|
||||
"per_month": "Per month",
|
||||
"routing_form_insights_assignment_reason":"Assignment Reason",
|
||||
"routing_form_insights_assignment_reason": "Assignment Reason",
|
||||
"access_denied": "Access Denied",
|
||||
"salesforce_route_to_owner": "Contact owner will be the Round Robin host if available",
|
||||
"salesforce_do_not_route_to_owner": "Contact owner will not be forced (can still be host if it matches the attributes and Round Robin criteria)",
|
||||
@@ -2868,7 +2874,7 @@
|
||||
"rr_distribution_method_availability_description": "Allows bookers to book meetings whenever a host is available. Use this to maximize the number of potential meetings booked and when the even distribution of meetings across hosts is less important.",
|
||||
"rr_distribution_method_balanced_title": "Load balancing",
|
||||
"rr_distribution_method_balanced_description": "We will monitor how many bookings have been made with each host and compare this with others, disabling some hosts that are too far ahead so bookings are evenly distributed.",
|
||||
"require_confirmation_for_free_email": "Only require confirmation for free email providers (Ex. @gmail.com, @outlook.com)",
|
||||
"require_confirmation_for_free_email": "Only require confirmation for free email providers (Ex. @gmail.com, @outlook.com)",
|
||||
"exclude_emails_that_contain": "Exclude emails that contain ...",
|
||||
"require_emails_that_contain": "Require emails that contain ...",
|
||||
"exclude_emails_match_found_error_message": "Please enter a valid work email address",
|
||||
@@ -2890,8 +2896,8 @@
|
||||
"lock_attribute_for_assignment_description": "Locking would only allow assignments from Directory Sync",
|
||||
"attribute_edited_successfully": "Attribute edited successfully",
|
||||
"new_group_option": "New group option",
|
||||
"attribute_weight_enabled":"Weights enabled",
|
||||
"attribute_weight_enabled_description":"By enabling weights, it would be possible to assign higher priority to certain attributes per user. The higher the weight, the higher the priority.",
|
||||
"attribute_weight_enabled": "Weights enabled",
|
||||
"attribute_weight_enabled_description": "By enabling weights, it would be possible to assign higher priority to certain attributes per user. The higher the weight, the higher the priority.",
|
||||
"routed": "Routed",
|
||||
"reassigned": "Reassigned",
|
||||
"rerouted": "Rerouted",
|
||||
@@ -2903,5 +2909,13 @@
|
||||
"calibration": "Calibration",
|
||||
"shortfall": "Shortfall",
|
||||
"routing_form": "Routing Form",
|
||||
"go_back_and_save": "Go back and save",
|
||||
"leave_without_saving": "Leave without saving",
|
||||
"leave_without_saving_description": "Are you sure you want to leave without saving changes to your routing form?",
|
||||
"something_unexpected_occurred": "Something unexpected occurred",
|
||||
"500_error_message": "It's not you, it's us.",
|
||||
"dry_run_mode_active": "You are doing a test booking.",
|
||||
"icon_showcase": "Icon showcase",
|
||||
"icons_showcase": "Icons showcase",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
}
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "La hora de finalización del evento",
|
||||
"cancel_url_variable": "URL de cancelación",
|
||||
"cancel_url_info": "La URL para cancelar la reserva",
|
||||
"cancel_reason_variable": "Motivo de cancelación",
|
||||
"cancel_reason_info": "El motivo por el cual se cancela la reserva",
|
||||
"reschedule_url_variable": "URL de reprogramación",
|
||||
"reschedule_url_info": "La URL para reprogramar la reserva",
|
||||
"reschedule_reason_variable": "Motivo de reprogramación",
|
||||
"reschedule_reason_info": "El motivo por el cual se reprograma la reserva",
|
||||
"invalid_event_name_variables": "{{item}} es una variable inválida en el nombre de tu evento",
|
||||
"select_all": "Seleccionar todo",
|
||||
"default_conferencing_bulk_title": "Actualizar en masa los tipos de eventos existentes",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "mes hasta la fecha",
|
||||
"year_to_date": "año hasta la fecha",
|
||||
"custom_range": "rango personalizado",
|
||||
"refund_policy": "Política de reembolso",
|
||||
"always": "Siempre",
|
||||
"never": "Nunca",
|
||||
"payment_option": "Opción de pago",
|
||||
"if_cancelled": "Si se cancela",
|
||||
"before": "antes",
|
||||
"show_all_columns": "Mostrar todas las columnas",
|
||||
"toggle_columns": "Alternar columnas",
|
||||
"no_columns_found": "No se encontraron columnas",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Calibración",
|
||||
"shortfall": "Déficit",
|
||||
"routing_form": "Formulario de enrutamiento",
|
||||
"go_back_and_save": "Volver y guardar",
|
||||
"leave_without_saving": "Salir sin guardar",
|
||||
"leave_without_saving_description": "¿Estás seguro de que quieres salir sin guardar los cambios en tu formulario de enrutamiento?",
|
||||
"something_unexpected_occurred": "Ocurrió algo inesperado",
|
||||
"500_error_message": "No eres tú, somos nosotros.",
|
||||
"dry_run_mode_active": "Estás haciendo una reserva de prueba.",
|
||||
"icon_showcase": "Muestra de íconos",
|
||||
"icons_showcase": "Muestra de íconos",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agrega tus nuevas cadenas arriba de esta línea ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "La hora de finalización del evento",
|
||||
"cancel_url_variable": "URL de cancelación",
|
||||
"cancel_url_info": "La URL para cancelar la reserva",
|
||||
"cancel_reason_variable": "Motivo de cancelación",
|
||||
"cancel_reason_info": "El motivo de la cancelación de la reserva",
|
||||
"reschedule_url_variable": "URL de reprogramación",
|
||||
"reschedule_url_info": "La URL para reprogramar la reserva",
|
||||
"reschedule_reason_variable": "Motivo de reprogramación",
|
||||
"reschedule_reason_info": "El motivo de la reprogramación de la reserva",
|
||||
"invalid_event_name_variables": "Hay una variable no válida en el nombre de su evento",
|
||||
"select_all": "Seleccionar todo",
|
||||
"default_conferencing_bulk_title": "Actualización masiva de los tipos de eventos existentes",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "mes hasta la fecha",
|
||||
"year_to_date": "año hasta la fecha",
|
||||
"custom_range": "rango personalizado",
|
||||
"refund_policy": "Política de reembolso",
|
||||
"always": "Siempre",
|
||||
"never": "Nunca",
|
||||
"payment_option": "Opción de pago",
|
||||
"if_cancelled": "Si se cancela",
|
||||
"before": "antes",
|
||||
"show_all_columns": "Mostrar todas las columnas",
|
||||
"toggle_columns": "Alternar columnas",
|
||||
"no_columns_found": "No se encontraron columnas",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Calibración",
|
||||
"shortfall": "Déficit",
|
||||
"routing_form": "Formulario de enrutamiento",
|
||||
"go_back_and_save": "Volver y guardar",
|
||||
"leave_without_saving": "Salir sin guardar",
|
||||
"leave_without_saving_description": "¿Estás seguro de que quieres salir sin guardar los cambios en tu formulario de enrutamiento?",
|
||||
"something_unexpected_occurred": "Algo inesperado ha ocurrido",
|
||||
"500_error_message": "No eres tú, somos nosotros.",
|
||||
"dry_run_mode_active": "Estás haciendo una reserva de prueba.",
|
||||
"icon_showcase": "Muestra de iconos",
|
||||
"icons_showcase": "Muestra de iconos",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agregue sus nuevas cadenas arriba ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Ürituse lõpuaeg",
|
||||
"cancel_url_variable": "Tühista URL",
|
||||
"cancel_url_info": "URL broneeringu tühistamiseks",
|
||||
"cancel_reason_variable": "Tühistamise põhjus",
|
||||
"cancel_reason_info": "Broneeringu tühistamise põhjus",
|
||||
"reschedule_url_variable": "Ajasta URL ümber",
|
||||
"reschedule_url_info": "URL broneeringu ümberajatamiseks",
|
||||
"reschedule_reason_variable": "Ümbertõstmise põhjus",
|
||||
"reschedule_reason_info": "Broneeringu ümbertõstmise põhjus",
|
||||
"invalid_event_name_variables": "Teie sündmuse nimes on kehtetu muutuja",
|
||||
"select_all": "Vali kõik",
|
||||
"default_conferencing_bulk_title": "Olemasolevate sündmuste tüüpide hulgivärskendus",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "kuu algusest tänaseni",
|
||||
"year_to_date": "aasta algusest tänaseni",
|
||||
"custom_range": "kohandatud vahemik",
|
||||
"refund_policy": "Tagastuspoliitika",
|
||||
"always": "Alati",
|
||||
"never": "Mitte kunagi",
|
||||
"payment_option": "Makseviis",
|
||||
"if_cancelled": "Kui tühistatud",
|
||||
"before": "enne",
|
||||
"show_all_columns": "Näita kõiki veerge",
|
||||
"toggle_columns": "Lülita veerud sisse/välja",
|
||||
"no_columns_found": "Veerge ei leitud",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibreerimine",
|
||||
"shortfall": "Puudujääk",
|
||||
"routing_form": "Marsruutimise vorm",
|
||||
"go_back_and_save": "Mine tagasi ja salvesta",
|
||||
"leave_without_saving": "Lahku salvestamata",
|
||||
"leave_without_saving_description": "Kas oled kindel, et soovid lahkuda marsruutimisvormi muudatusi salvestamata?",
|
||||
"something_unexpected_occurred": "Midagi ootamatut juhtus",
|
||||
"500_error_message": "Viga ei ole sinus, vaid meis.",
|
||||
"dry_run_mode_active": "Teed proovibroneeringut.",
|
||||
"icon_showcase": "Ikoonide ülevaade",
|
||||
"icons_showcase": "Ikoonide ülevaade",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Gertaeraren amaiera ordua",
|
||||
"cancel_url_variable": "Bertan behera uzteko URLa",
|
||||
"cancel_url_info": "Erreserba bertan behera uzteko URLa",
|
||||
"cancel_reason_variable": "Bertan behera uzteko arrazoia",
|
||||
"cancel_reason_info": "Erreserba bertan behera uzteko arrazoia",
|
||||
"reschedule_url_variable": "Berriz programatzeko URLa",
|
||||
"reschedule_url_info": "Erreserba berriz programatzeko URLa",
|
||||
"reschedule_reason_variable": "Birprogramatzeko arrazoia",
|
||||
"reschedule_reason_info": "Erreserba birprogramatzeko arrazoia",
|
||||
"invalid_event_name_variables": "{{item}} aldagai baliogabea da zure gertaeraren izenean",
|
||||
"select_all": "Hautatu guztiak",
|
||||
"default_conferencing_bulk_title": "Eguneratu masiboki existitzen diren gertaera motak",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "hilabetea gaur arte",
|
||||
"year_to_date": "urtea gaur arte",
|
||||
"custom_range": "pertsonalizatutako tartea",
|
||||
"refund_policy": "Itzulketa politika",
|
||||
"always": "Beti",
|
||||
"never": "Inoiz ez",
|
||||
"payment_option": "Ordainketa aukera",
|
||||
"if_cancelled": "Bertan behera utziz gero",
|
||||
"before": "aurretik",
|
||||
"show_all_columns": "Erakutsi zutabe guztiak",
|
||||
"toggle_columns": "Aldatu zutabeak",
|
||||
"no_columns_found": "Ez da zutaberik aurkitu",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrazioa",
|
||||
"shortfall": "Gabezia",
|
||||
"routing_form": "Bideratze-formularioa",
|
||||
"go_back_and_save": "Itzuli eta gorde",
|
||||
"leave_without_saving": "Irten gorde gabe",
|
||||
"leave_without_saving_description": "Ziur zaude bideratzeko inprimakian egindako aldaketak gorde gabe irten nahi duzula?",
|
||||
"something_unexpected_occurred": "Ustekabeko zerbait gertatu da",
|
||||
"500_error_message": "Ez zara zu, gu gara",
|
||||
"dry_run_mode_active": "Proba-erreserba bat egiten ari zara.",
|
||||
"icon_showcase": "Ikonoen erakusleihoa",
|
||||
"icons_showcase": "Ikonoen erakusleihoa",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Gehitu zure kate berriak honen gainean ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -2031,8 +2031,12 @@
|
||||
"event_end_time_info": "Tapahtuman päättymisaika",
|
||||
"cancel_url_variable": "Peruutus-URL",
|
||||
"cancel_url_info": "URL-osoite varauksen peruuttamiseen",
|
||||
"cancel_reason_variable": "Peruutuksen syy",
|
||||
"cancel_reason_info": "Varauksen peruuttamisen syy",
|
||||
"reschedule_url_variable": "Uudelleenajoitus-URL",
|
||||
"reschedule_url_info": "URL-osoite varauksen uudelleenajoittamiseen",
|
||||
"reschedule_reason_variable": "Uudelleenvarauksen syy",
|
||||
"reschedule_reason_info": "Varauksen uudelleenajoituksen syy",
|
||||
"invalid_event_name_variables": "{{item}} on virheellinen muuttuja tapahtuman nimessä",
|
||||
"select_all": "Valitse kaikki",
|
||||
"default_conferencing_bulk_title": "Päivitä olemassa olevat tapahtumatyypit massana",
|
||||
@@ -2737,6 +2741,12 @@
|
||||
"month_to_date": "kuukauden alusta tähän päivään",
|
||||
"year_to_date": "vuoden alusta tähän päivään",
|
||||
"custom_range": "mukautettu aikaväli",
|
||||
"refund_policy": "Palautuskäytäntö",
|
||||
"always": "Aina",
|
||||
"never": "Ei koskaan",
|
||||
"payment_option": "Maksutapa",
|
||||
"if_cancelled": "Jos peruutettu",
|
||||
"before": "ennen",
|
||||
"show_all_columns": "Näytä kaikki sarakkeet",
|
||||
"toggle_columns": "Näytä/piilota sarakkeet",
|
||||
"no_columns_found": "Sarakkeita ei löytynyt",
|
||||
@@ -2899,5 +2909,13 @@
|
||||
"calibration": "Kalibrointi",
|
||||
"shortfall": "Vajaus",
|
||||
"routing_form": "Reitityskaavake",
|
||||
"go_back_and_save": "Palaa takaisin ja tallenna",
|
||||
"leave_without_saving": "Poistu tallentamatta",
|
||||
"leave_without_saving_description": "Haluatko varmasti poistua tallentamatta muutoksia reitityslomaakkeeseesi?",
|
||||
"something_unexpected_occurred": "Jotain odottamatonta tapahtui",
|
||||
"500_error_message": "Vika ei ole sinussa, vaan meissä.",
|
||||
"dry_run_mode_active": "Teet testikäyntivarausta.",
|
||||
"icon_showcase": "Kuvakegalleria",
|
||||
"icons_showcase": "Kuvakegalleria",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Lisää uudet merkkijonot tämän yläpuolelle ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user