Compare commits

...
25 Commits
Author SHA1 Message Date
Omar López 9dab915ddd chore: upgrade dependencies (#20740)
Post release / release (push) Canceled after 0s
2025-04-21 15:21:10 -03:00
GitHub Actions 8fca8f3910 chore: release v5.2.3 2025-04-21 18:20:38 +00:00
Omar López 1638465607 chore: remove swagger app and its dependencies (#20798) 2025-04-21 18:13:19 +00:00
Keith Williams ab7eb7f4b1 chore: Add layout.tsx to Foundation team CODEOWNERS (#20802) 2025-04-21 23:35:10 +05:30
Benny Joo 0592badd56 fix: semi-colon in the bottom of layout UI (#20799) 2025-04-21 15:46:23 +00:00
Morgan 7e82b8fa86 fix: sms workflow api v2 (#20795)
* fix: sms workflow api v2

* fixup! fix: sms workflow api v2
2025-04-21 14:41:59 +00:00
7dfb71ca30 fix: Location not changing when rescheduling (#20404)
* fix: location not chnging

* update

* update

* Update EventManager.ts

* update

* update

* Update reschedule.test.ts

---------

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2025-04-21 14:45:59 +01:00
d99ecde0e2 fix: improved 'use-identity-login' error message for users trying to signInn using different provider (#20582)
* improved 'use-identity-login' error message for users trying to signin using different provider

* passing original identity provider as query parameter

* Update packages/features/auth/lib/next-auth-options.ts

* passed different error names for different types of error

---------

Co-authored-by: romit <romitgabani1@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
2025-04-21 13:38:19 +00:00
Omar López d81b905ad7 refactor: replace timeZone string validation with timeZoneSchema (#20719)
* refactor: replace timeZone string validation with timeZoneSchema - Replace all instances of timeZone: z.string() with timeZoneSchema - Preserve modifiers (.optional(), .default(), .nullish()) where present - Fix VideoApiAdapter to use organizer's timezone value - Remove redundant isSupportedTimeZone import from booking.ts - Add timeZoneSchema import where needed

* Update index.ts

* fix: conflicts

Signed-off-by: Omar López <zomars@me.com>

* Update yarn.lock

* Discard changes to yarn.lock

* Update booking.ts

* Discard changes to packages/platform/types/me.ts

* Discard changes to packages/platform/types/me/outputs/me.ts

* Discard changes to packages/platform/types/schedules/schedules-2024-04-15/schedules.ts

* Discard changes to packages/features/ee/cal-ai-phone/zod-utils.ts

---------

Signed-off-by: Omar López <zomars@me.com>
2025-04-21 14:37:07 +01:00
Hariom Balhara 46c6582328 fix: Wrong text EmbedTheme -> Theme (#20790) 2025-04-21 10:16:09 +00:00
Hariom Balhara 396811b954 fix: Org onboarding handle error due to platform team being moved to regular org (#20786)
* Add better error logs and handle platform team

* Add better error logs and handle platform team
2025-04-21 14:58:55 +05:30
Lauris Skraucis f6c7d8d97f fix: v2 Sentry errors (#20788)
* fix: Only Team Event Types are supported for Instant Meeting

* refactor: Cannot read properties of undefined (reading 'timeZone')

* fix: Cannot read properties of undefined (reading 'map')

* refactor: rearrange function order

* refactor: booking_seats_full_error

* fix: invalid_round_robin_host

* refactor: booking_time_out_of_bounds_error

* refactor: Attempting to book a meeting in the past.

* fix: obj.phoneNumber.trim is not a function

* fix: Cannot use 'in' operator to search for 'disabled' in 1
2025-04-21 12:12:46 +03:00
Ronit Raj 930165b6e8 Update rateLimit.ts (#20785) 2025-04-21 05:33:00 +00:00
Hariom Balhara a77bd7bbe0 fx 404 for embed (#20120) 2025-04-20 16:16:48 +00:00
51212cdacf fix: header overlaps in month view on short viewport (#20749)
* fix: header overlaps in month view on short viewport

* refactor: Merge classes into same condition

* fix: Remove booker margin top if isEmbed

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
Co-authored-by: amrit <iamamrit27@gmail.com>
2025-04-20 08:03:12 +05:30
Lauris Skraucis 3731b1ef3d fix: calendar connect atoms (#20766) 2025-04-18 11:29:11 +00:00
amrit f631c4aa79 feat: make skeleton for booking embed accurate (#20765)
* fix: trigger lingo.dev by removing duplicate value

* feat: make skeleton for booking embed accurate
2025-04-18 10:28:37 +00:00
Lauris Skraucis 95cd76e732 fix: only managed users assignable to managed team event (#20754)
* fix: only managed users assignable to managed team event

* chore: bump platform libraries
2025-04-18 12:21:02 +05:30
Joe Au-Yeung 7e525779b7 Remove trailing } from utm param (#20763) 2025-04-18 11:13:26 +05:30
Anik Dhabal Babu f176ebf1da fix: offer seats broken (#20758) 2025-04-18 05:32:31 +00:00
Keith Williams 5058e0e750 chore: Remove verbose logging (#20732) 2025-04-18 04:20:19 +00:00
Joe Au-Yeung dc409e1c5a chore: Add consent message when SMS workflows are related (#20741)
* Add consent message

* Add consent message if using SMS workflow

* Skip flakey tests

* Revert "Skip flakey tests"

This reverts commit 3bd6f3fd71469df4dbd67b69b8e17011657df9d8.
2025-04-18 09:23:15 +05:30
Joe Au-Yeung 89b5d1ebc3 Write UTM params to Salesforce (#20759) 2025-04-18 03:24:57 +00:00
Joe Au-Yeung 0d24664a72 Skip flakey tests (#20761) 2025-04-18 03:01:35 +00:00
69efc26f36 perf: slim down middleware matcher (#20735)
* wip

* wip

* refactor

* add pathname

* fix

* fix: manually construct pathname for metadata functions

Co-Authored-By: benny@cal.com <benny@cal.com>

* fix: add pathname parameter to all metadata functions

Co-Authored-By: benny@cal.com <benny@cal.com>

* fix: add pathname parameter to remaining metadata functions

Co-Authored-By: benny@cal.com <benny@cal.com>

* fix

* fix

* fix type checks

* Refactor

* fix

* fix

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: benny@cal.com <benny@cal.com>
2025-04-17 19:55:01 -07:00
188 changed files with 2100 additions and 2844 deletions
+1
View File
@@ -1,5 +1,6 @@
/**/package.json @calcom/Foundation /**/package.json @calcom/Foundation
/apps/api/**/* @calcom/Platform @calcom/Foundation /apps/api/**/* @calcom/Platform @calcom/Foundation
/apps/web/**/layout.tsx @calcom/Foundation
/deploy/**/* @calcom/Foundation /deploy/**/* @calcom/Foundation
/infra/**/* @calcom/Foundation /infra/**/* @calcom/Foundation
/packages/app-store/applecalendar/**/* @calcom/Foundation /packages/app-store/applecalendar/**/* @calcom/Foundation
+2
View File
@@ -0,0 +1,2 @@
undecided:
- "@calcom/prisma"
+1 -1
View File
@@ -38,7 +38,7 @@
"@axiomhq/winston": "^1.2.0", "@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*", "@calcom/platform-constants": "*",
"@calcom/platform-enums": "*", "@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.173", "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.174",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2", "@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*", "@calcom/platform-types": "*",
"@calcom/platform-utils": "*", "@calcom/platform-utils": "*",
@@ -58,14 +58,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
let user: User; let user: User;
let seatedEventTypeId: number; let seatedEventTypeId: number;
let recurringSeatedEventTypeId: number;
const maxRecurrenceCount = 3; const maxRecurrenceCount = 3;
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`; const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13; let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`; const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
const nameAttendeeOne = `Attendee One ${randomString()}`; const nameAttendeeOne = `Attendee One ${randomString()}`;
@@ -126,22 +123,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
); );
seatedEventTypeId = seatedEvent.id; seatedEventTypeId = seatedEvent.id;
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
{
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
slug: recurringSeatedEventSlug,
length: 60,
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
seatsPerTimeSlot: 5,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
},
user.id
);
recurringSeatedEventTypeId = recurringSeatedEvent.id;
app = moduleRef.createNestApplication(); app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication); bootstrap(app as NestExpressApplication);
@@ -321,127 +302,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
}); });
}); });
it("should book a recurring event type with seats", async () => {
const recurrenceCount = 2;
const body: CreateRecurringBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(),
eventTypeId: recurringSeatedEventTypeId,
attendee: {
name: "Mr Proper",
email: "mr_proper@gmail.com",
timeZone: "Europe/Rome",
language: "it",
},
location: googleMeetUrl,
bookingFieldsResponses: {
codingLanguage: "TypeScript",
},
recurrenceCount,
metadata: {
userId: "300",
},
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsCreateRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsCreateRecurringSeatedBooking(responseBody.data)) {
const data: CreateRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
expect(data.length).toEqual(recurrenceCount);
const firstBooking = data[0];
expect(firstBooking.seatUid).toBeDefined();
const seatUid = firstBooking.seatUid;
expect(firstBooking.id).toBeDefined();
expect(firstBooking.uid).toBeDefined();
expect(firstBooking.hosts[0].id).toEqual(user.id);
expect(firstBooking.status).toEqual("accepted");
expect(firstBooking.start).toEqual(body.start);
expect(firstBooking.end).toEqual(
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(firstBooking.duration).toEqual(60);
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(firstBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(firstBooking.attendees.length).toEqual(1);
expect(firstBooking.attendees[0]).toEqual({
name: body.attendee.name,
email: body.attendee.email,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(firstBooking.location).toEqual(body.location);
expect(firstBooking.absentHost).toEqual(false);
const secondBooking = data[1];
expect(secondBooking.seatUid).toBeDefined();
const secondSeatUid = secondBooking.seatUid;
expect(secondBooking.id).toBeDefined();
expect(secondBooking.uid).toBeDefined();
expect(secondBooking.hosts[0].id).toEqual(user.id);
expect(secondBooking.status).toEqual("accepted");
const expectedStart = DateTime.fromISO(body.start, { zone: "utc" }).plus({ weeks: 1 }).toISO();
expect(secondBooking.start).toEqual(expectedStart);
expect(secondBooking.end).toEqual(
DateTime.fromISO(expectedStart!, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(secondBooking.duration).toEqual(60);
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(secondBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(secondBooking.attendees.length).toEqual(1);
expect(secondBooking.attendees[0]).toEqual({
name: body.attendee.name,
email: body.attendee.email,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: secondSeatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(secondBooking.location).toEqual(body.location);
expect(secondBooking.absentHost).toEqual(false);
createdRecurringSeatedBooking = data;
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
it("should get a booking for an event type with seats", async () => { it("should get a booking for an event type with seats", async () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.get(`/v2/bookings/${createdSeatedBooking.uid}`) .get(`/v2/bookings/${createdSeatedBooking.uid}`)
@@ -469,62 +329,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
}); });
}); });
it("should get a booking for a recurring event type with seats", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${createdRecurringSeatedBooking[1].recurringBookingUid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsGetRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsGetRecurringSeatedBooking(responseBody.data)) {
const data: GetRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
const expected = structuredClone(createdRecurringSeatedBooking);
for (const booking of expected) {
// note(Lauris): seatUid in get response resides only in each attendee object
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete booking.seatUid;
}
expect(data).toEqual(expected);
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
it("should get a specific recurrence of a booking for a recurring event type with seats", async () => {
return request(app.getHttpServer())
.get(`/v2/bookings/${createdRecurringSeatedBooking[0].uid}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: GetBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsGetSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsGetSeatedBooking(responseBody.data)) {
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
const expected = structuredClone(createdRecurringSeatedBooking[0]);
// note(Lauris): seatUid in get response resides only in each attendee object
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete expected.seatUid;
expect(data).toEqual(expected);
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
it("should get all seated bookings", async () => { it("should get all seated bookings", async () => {
return request(app.getHttpServer()) return request(app.getHttpServer())
.get("/v2/bookings?sortCreated=asc") .get("/v2/bookings?sortCreated=asc")
@@ -534,7 +338,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
const responseBody: GetBookingsOutput_2024_08_13 = response.body; const responseBody: GetBookingsOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS); expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined(); expect(responseBody.data).toBeDefined();
expect(responseBody.data.length).toEqual(3); expect(responseBody.data.length).toEqual(1);
const seatedBooking = responseBody.data[0]; const seatedBooking = responseBody.data[0];
const seatedBookingExpected = structuredClone(createdSeatedBooking); const seatedBookingExpected = structuredClone(createdSeatedBooking);
@@ -543,16 +347,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
// @ts-ignore // @ts-ignore
delete seatedBookingExpected.seatUid; delete seatedBookingExpected.seatUid;
expect(seatedBooking).toEqual(seatedBookingExpected); expect(seatedBooking).toEqual(seatedBookingExpected);
const recurringSeatedBookings = [responseBody.data[1], responseBody.data[2]];
const recurringSeatedBookingsExpected = structuredClone(createdRecurringSeatedBooking);
for (const booking of recurringSeatedBookingsExpected) {
// note(Lauris): seatUid in get response resides only in each attendee object
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete booking.seatUid;
}
expect(recurringSeatedBookings).toEqual(recurringSeatedBookingsExpected);
}); });
}); });
@@ -682,424 +476,4 @@ describe("Bookings Endpoints 2024-08-13", () => {
await app.close(); await app.close();
}); });
}); });
describe("Recurring seated bookings creation", () => {
let app: INestApplication;
let organization: Team;
let userRepositoryFixture: UserRepositoryFixture;
let bookingsRepositoryFixture: BookingsRepositoryFixture;
let schedulesService: SchedulesService_2024_04_15;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let oAuthClient: PlatformOAuthClient;
let teamRepositoryFixture: TeamRepositoryFixture;
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
let user: User;
let seatedEventTypeId: number;
let recurringSeatedEventTypeId: number;
const maxRecurrenceCount = 3;
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
const nameAttendeeOne = `Attendee One ${randomString()}`;
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
organization = await teamRepositoryFixture.create({
name: `seated-bookings-organization-${randomString()}`,
});
oAuthClient = await createOAuthClient(organization.id);
user = await userRepositoryFixture.create({
email: userEmail,
platformOAuthClients: {
connect: {
id: oAuthClient.id,
},
},
});
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
timeZone: "Europe/Rome",
isDefault: true,
};
await schedulesService.createUserSchedule(user.id, userSchedule);
const seatedEvent = await eventTypesRepositoryFixture.create(
{
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
slug: seatedEventSlug,
length: 60,
seatsPerTimeSlot: 3,
},
user.id
);
seatedEventTypeId = seatedEvent.id;
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
{
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
slug: recurringSeatedEventSlug,
length: 60,
seatsPerTimeSlot: 3,
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
},
user.id
);
recurringSeatedEventTypeId = recurringSeatedEvent.id;
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["http://localhost:5555"],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should book a recurring event type with seats for the first time", async () => {
const recurrenceCount = 2;
const body: CreateRecurringBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(),
eventTypeId: recurringSeatedEventTypeId,
attendee: {
name: nameAttendeeOne,
email: emailAttendeeOne,
timeZone: "Europe/Rome",
language: "it",
},
location: googleMeetUrl,
bookingFieldsResponses: {
codingLanguage: "TypeScript",
},
metadata: {
userId: "1000",
},
recurrenceCount,
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsCreateRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsCreateRecurringSeatedBooking(responseBody.data)) {
const data: CreateRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
expect(data.length).toEqual(recurrenceCount);
const firstBooking = data[0];
expect(firstBooking.seatUid).toBeDefined();
const seatUid = firstBooking.seatUid;
expect(firstBooking.id).toBeDefined();
expect(firstBooking.uid).toBeDefined();
expect(firstBooking.hosts[0].id).toEqual(user.id);
expect(firstBooking.status).toEqual("accepted");
expect(firstBooking.start).toEqual(body.start);
expect(firstBooking.end).toEqual(
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(firstBooking.duration).toEqual(60);
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(firstBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(firstBooking.attendees.length).toEqual(1);
expect(firstBooking.attendees[0]).toEqual({
name: body.attendee.name,
email: body.attendee.email,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(firstBooking.location).toEqual(body.location);
expect(firstBooking.absentHost).toEqual(false);
const secondBooking = data[1];
expect(secondBooking.seatUid).toBeDefined();
const secondSeatUid = secondBooking.seatUid;
expect(secondBooking.id).toBeDefined();
expect(secondBooking.uid).toBeDefined();
expect(secondBooking.hosts[0].id).toEqual(user.id);
expect(secondBooking.status).toEqual("accepted");
const expectedStart = DateTime.fromISO(body.start, { zone: "utc" }).plus({ weeks: 1 }).toISO();
expect(secondBooking.start).toEqual(expectedStart);
expect(secondBooking.end).toEqual(
DateTime.fromISO(expectedStart!, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(secondBooking.duration).toEqual(60);
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(secondBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(secondBooking.attendees.length).toEqual(1);
expect(secondBooking.attendees[0]).toEqual({
name: body.attendee.name,
email: body.attendee.email,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: secondSeatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(secondBooking.location).toEqual(body.location);
expect(secondBooking.absentHost).toEqual(false);
createdRecurringSeatedBooking = data;
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
it("should book a recurring event type with seats for the second time", async () => {
const recurrenceCount = 2;
const body: CreateRecurringBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(),
eventTypeId: recurringSeatedEventTypeId,
attendee: {
name: nameAttendeeTwo,
email: emailAttendeeTwo,
timeZone: "Europe/Rome",
language: "it",
},
location: googleMeetUrl,
bookingFieldsResponses: {
codingLanguage: "TypeScript",
},
metadata: {
userId: "2000",
},
recurrenceCount,
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsCreateRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsCreateRecurringSeatedBooking(responseBody.data)) {
const data: CreateRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
expect(data.length).toEqual(recurrenceCount);
const firstBooking = data[0];
expect(firstBooking.seatUid).toBeDefined();
const seatUid = firstBooking.seatUid;
expect(firstBooking.id).toBeDefined();
expect(firstBooking.uid).toBeDefined();
expect(firstBooking.hosts[0].id).toEqual(user.id);
expect(firstBooking.status).toEqual("accepted");
expect(firstBooking.start).toEqual(body.start);
expect(firstBooking.end).toEqual(
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(firstBooking.duration).toEqual(60);
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(firstBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(firstBooking.attendees.length).toEqual(2);
const firstAttendee = firstBooking.attendees.find(
(attendee) => attendee.name === nameAttendeeOne
);
expect(firstAttendee).toEqual({
name: nameAttendeeOne,
email: emailAttendeeOne,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: createdRecurringSeatedBooking[0].seatUid,
bookingFieldsResponses: {
name: nameAttendeeOne,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
});
const secondAttendee = firstBooking.attendees.find(
(attendee) => attendee.name === nameAttendeeTwo
);
expect(secondAttendee).toEqual({
name: nameAttendeeTwo,
email: emailAttendeeTwo,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid,
bookingFieldsResponses: {
name: nameAttendeeTwo,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(firstBooking.location).toEqual(body.location);
expect(firstBooking.absentHost).toEqual(false);
const secondBooking = data[1];
expect(secondBooking.seatUid).toBeDefined();
const secondSeatUid = secondBooking.seatUid;
expect(secondBooking.id).toBeDefined();
expect(secondBooking.uid).toBeDefined();
expect(secondBooking.hosts[0].id).toEqual(user.id);
expect(secondBooking.status).toEqual("accepted");
const expectedStart = DateTime.fromISO(body.start, { zone: "utc" }).plus({ weeks: 1 }).toISO();
expect(secondBooking.start).toEqual(expectedStart);
expect(secondBooking.end).toEqual(
DateTime.fromISO(expectedStart!, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(secondBooking.duration).toEqual(60);
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(secondBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedEventSlug,
});
expect(secondBooking.attendees.length).toEqual(2);
expect(secondBooking.attendees[0]).toEqual({
name: nameAttendeeOne,
email: emailAttendeeOne,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: createdRecurringSeatedBooking[1].seatUid,
bookingFieldsResponses: {
name: nameAttendeeOne,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
});
expect(secondBooking.attendees[1]).toEqual({
name: nameAttendeeTwo,
email: emailAttendeeTwo,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: secondSeatUid,
bookingFieldsResponses: {
name: nameAttendeeTwo,
...body.bookingFieldsResponses,
location: {
optionValue: googleMeetUrl,
value: "link",
},
},
metadata: body.metadata,
});
expect(secondBooking.location).toEqual(body.location);
expect(secondBooking.absentHost).toEqual(false);
createdRecurringSeatedBooking = data;
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
function responseDataIsCreateRecurringSeatedBooking(
data: any
): data is CreateRecurringSeatedBookingOutput_2024_08_13[] {
return Array.isArray(data);
}
function responseDataIsGetSeatedBooking(data: any): data is GetSeatedBookingOutput_2024_08_13 {
return data?.attendees?.every((attendee: any) => attendee?.hasOwnProperty("seatUid"));
}
function responseDataIsGetRecurringSeatedBooking(
data: any
): data is GetRecurringSeatedBookingOutput_2024_08_13[] {
return Array.isArray(data);
}
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
await userRepositoryFixture.deleteByEmail(user.email);
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
await app.close();
});
});
}); });
@@ -15,7 +15,7 @@ import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-eve
import { TeamsRepository } from "@/modules/teams/teams/teams.repository"; import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
import { UsersService } from "@/modules/users/services/users.service"; import { UsersService } from "@/modules/users/services/users.service";
import { UsersRepository, UserWithProfile } from "@/modules/users/users.repository"; import { UsersRepository, UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, Logger, NotFoundException } from "@nestjs/common"; import { ConflictException, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { BadRequestException } from "@nestjs/common"; import { BadRequestException } from "@nestjs/common";
import { Request } from "express"; import { Request } from "express";
import { z } from "zod"; import { z } from "zod";
@@ -141,6 +141,12 @@ export class BookingsService_2024_08_13 {
if (error instanceof Error) { if (error instanceof Error) {
if (error.message === "no_available_users_found_error") { if (error.message === "no_available_users_found_error") {
throw new BadRequestException("User either already has booking at this time or is not available"); throw new BadRequestException("User either already has booking at this time or is not available");
} else if (error.message === "booking_time_out_of_bounds_error") {
throw new BadRequestException(
`The event type can't be booked at selected time ${body.start}. This could be because it's too soon (violating the minimum booking notice) or too far in the future (outside the event's scheduling window). Try fetching available slots first using the GET /v2/slots endpoint and then make a booking with "start" time equal to one of the available slots: https://cal.com/docs/api-reference/v2/slots`
);
} else if (error.message === "Attempting to book a meeting in the past.") {
throw new BadRequestException("Attempting to book a meeting in the past.");
} }
} }
throw error; throw error;
@@ -223,6 +229,12 @@ export class BookingsService_2024_08_13 {
body: CreateInstantBookingInput_2024_08_13, body: CreateInstantBookingInput_2024_08_13,
eventType: EventTypeWithOwnerAndTeam eventType: EventTypeWithOwnerAndTeam
) { ) {
if (!eventType.team?.id) {
throw new BadRequestException(
"Instant bookings are only supported for team event types, not individual user event types."
);
}
const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType); const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType);
const booking = await handleInstantMeeting(bookingRequest); const booking = await handleInstantMeeting(bookingRequest);
@@ -313,29 +325,37 @@ export class BookingsService_2024_08_13 {
eventType: EventTypeWithOwnerAndTeam eventType: EventTypeWithOwnerAndTeam
) { ) {
const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType); const bookingRequest = await this.inputService.createBookingRequest(request, body, eventType);
const booking = await handleNewBooking({ try {
bookingData: bookingRequest.body, const booking = await handleNewBooking({
userId: bookingRequest.userId, bookingData: bookingRequest.body,
hostname: bookingRequest.headers?.host || "", userId: bookingRequest.userId,
platformClientId: bookingRequest.platformClientId, hostname: bookingRequest.headers?.host || "",
platformRescheduleUrl: bookingRequest.platformRescheduleUrl, platformClientId: bookingRequest.platformClientId,
platformCancelUrl: bookingRequest.platformCancelUrl, platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
platformBookingUrl: bookingRequest.platformBookingUrl, platformCancelUrl: bookingRequest.platformCancelUrl,
platformBookingLocation: bookingRequest.platformBookingLocation, platformBookingUrl: bookingRequest.platformBookingUrl,
}); platformBookingLocation: bookingRequest.platformBookingLocation,
});
if (!booking.uid) { if (!booking.uid) {
throw new Error("Booking missing uid"); throw new Error("Booking missing uid");
}
const databaseBooking =
await this.bookingsRepository.getByUidWithAttendeesWithBookingSeatAndUserAndEvent(booking.uid);
if (!databaseBooking) {
throw new Error(`Booking with uid=${booking.uid} was not found in the database`);
}
return this.outputService.getOutputCreateSeatedBooking(databaseBooking, booking.seatReferenceUid || "");
} catch (error) {
if (error instanceof Error) {
if (error.message === "booking_seats_full_error") {
throw new ConflictException("No more seats left at this seated booking.");
}
}
throw error;
} }
const databaseBooking = await this.bookingsRepository.getByUidWithAttendeesWithBookingSeatAndUserAndEvent(
booking.uid
);
if (!databaseBooking) {
throw new Error(`Booking with uid=${booking.uid} was not found in the database`);
}
return this.outputService.getOutputCreateSeatedBooking(databaseBooking, booking.seatReferenceUid || "");
} }
async getBooking(uid: string) { async getBooking(uid: string) {
@@ -697,17 +717,28 @@ export class BookingsService_2024_08_13 {
const profile = this.usersService.getUserMainProfile(user); const profile = this.usersService.getUserMainProfile(user);
const reassigned = await roundRobinManualReassignment({ try {
bookingId: booking.id, const reassigned = await roundRobinManualReassignment({
newUserId, bookingId: booking.id,
orgId: profile?.organizationId || null, newUserId,
reassignReason: body.reason, orgId: profile?.organizationId || null,
reassignedById, reassignReason: body.reason,
emailsEnabled, reassignedById,
platformClientParams, emailsEnabled,
}); platformClientParams,
});
return this.outputService.getOutputReassignedBooking(reassigned); return this.outputService.getOutputReassignedBooking(reassigned);
} catch (error) {
if (error instanceof Error) {
if (error.message === "invalid_round_robin_host") {
throw new BadRequestException(
`User with id=${newUserId} is not a valid Round Robin host - the user to which you reassign this booking must be one of the booking hosts. Fetch the booking using following endpoint and select id of one of the hosts: https://cal.com/docs/api-reference/v2/bookings/get-a-booking`
);
}
}
throw error;
}
} }
async confirmBooking(bookingUid: string, requestUser: UserWithProfile) { async confirmBooking(bookingUid: string, requestUser: UserWithProfile) {
@@ -150,7 +150,7 @@ export class CalendarsController {
@Headers("Authorization") authorization: string, @Headers("Authorization") authorization: string,
@Param("calendar") calendar: string, @Param("calendar") calendar: string,
@Query("redir") redir?: string | null, @Query("redir") redir?: string | null,
@Query("isDryRun", ParseBoolPipe) isDryRun?: boolean @Query("isDryRun", new ParseBoolPipe({ optional: true })) isDryRun?: boolean
): Promise<ApiResponse<{ authUrl: string }>> { ): Promise<ApiResponse<{ authUrl: string }>> {
switch (calendar) { switch (calendar) {
case OFFICE_365_CALENDAR: case OFFICE_365_CALENDAR:
+4
View File
@@ -38,6 +38,10 @@ export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environme
const value = process.env[key] as Environment[K] | undefined; const value = process.env[key] as Environment[K] | undefined;
if (value === undefined) { if (value === undefined) {
// handle fallback falsy cases that should still be used as value
if (fallback === false || fallback === "" || fallback === 0) {
return fallback;
}
if (fallback) { if (fallback) {
return fallback; return fallback;
} }
@@ -81,6 +81,10 @@ export class EventTypesAtomService {
this.eventTypeService.checkUserOwnsEventType(user.id, eventType.eventType); this.eventTypeService.checkUserOwnsEventType(user.id, eventType.eventType);
} }
// note (Lauris): don't show platform owner as one of the people that can be assigned to managed team event type
const onlyManagedTeamMembers = eventType.teamMembers.filter((user) => user.isPlatformManaged);
eventType.teamMembers = onlyManagedTeamMembers;
return eventType; return eventType;
} }
+55 -54
View File
@@ -13337,6 +13337,7 @@
}, },
"enabledLayouts": { "enabledLayouts": {
"type": "array", "type": "array",
"description": "Array of valid layouts - month, week or column",
"items": { "items": {
"type": "string", "type": "string",
"enum": [ "enum": [
@@ -21103,10 +21104,20 @@
"description": "The start time of the booking in ISO 8601 format in UTC timezone.", "description": "The start time of the booking in ISO 8601 format in UTC timezone.",
"example": "2024-08-13T09:00:00Z" "example": "2024-08-13T09:00:00Z"
}, },
"lengthInMinutes": { "attendee": {
"type": "number", "description": "The attendee's details.",
"example": 30, "allOf": [
"description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking." {
"$ref": "#/components/schemas/Attendee"
}
]
},
"bookingFieldsResponses": {
"type": "object",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values for custom booking fields added by you.",
"example": {
"customField": "customValue"
}
}, },
"eventTypeId": { "eventTypeId": {
"type": "number", "type": "number",
@@ -21133,14 +21144,6 @@
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.", "description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp" "example": "acme-corp"
}, },
"attendee": {
"description": "The attendee's details.",
"allOf": [
{
"$ref": "#/components/schemas/Attendee"
}
]
},
"guests": { "guests": {
"description": "An optional list of guest emails attending the event.", "description": "An optional list of guest emails attending the event.",
"example": [ "example": [
@@ -21194,12 +21197,10 @@
"key": "value" "key": "value"
} }
}, },
"bookingFieldsResponses": { "lengthInMinutes": {
"type": "object", "type": "number",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values.", "example": 30,
"example": { "description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking."
"customField": "customValue"
}
} }
}, },
"required": [ "required": [
@@ -21215,10 +21216,20 @@
"description": "The start time of the booking in ISO 8601 format in UTC timezone.", "description": "The start time of the booking in ISO 8601 format in UTC timezone.",
"example": "2024-08-13T09:00:00Z" "example": "2024-08-13T09:00:00Z"
}, },
"lengthInMinutes": { "attendee": {
"type": "number", "description": "The attendee's details.",
"example": 30, "allOf": [
"description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking." {
"$ref": "#/components/schemas/Attendee"
}
]
},
"bookingFieldsResponses": {
"type": "object",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values for custom booking fields added by you.",
"example": {
"customField": "customValue"
}
}, },
"eventTypeId": { "eventTypeId": {
"type": "number", "type": "number",
@@ -21245,14 +21256,6 @@
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.", "description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp" "example": "acme-corp"
}, },
"attendee": {
"description": "The attendee's details.",
"allOf": [
{
"$ref": "#/components/schemas/Attendee"
}
]
},
"guests": { "guests": {
"description": "An optional list of guest emails attending the event.", "description": "An optional list of guest emails attending the event.",
"example": [ "example": [
@@ -21306,12 +21309,10 @@
"key": "value" "key": "value"
} }
}, },
"bookingFieldsResponses": { "lengthInMinutes": {
"type": "object", "type": "number",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values.", "example": 30,
"example": { "description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking."
"customField": "customValue"
}
}, },
"instant": { "instant": {
"type": "boolean", "type": "boolean",
@@ -21333,10 +21334,20 @@
"description": "The start time of the booking in ISO 8601 format in UTC timezone.", "description": "The start time of the booking in ISO 8601 format in UTC timezone.",
"example": "2024-08-13T09:00:00Z" "example": "2024-08-13T09:00:00Z"
}, },
"lengthInMinutes": { "attendee": {
"type": "number", "description": "The attendee's details.",
"example": 30, "allOf": [
"description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking." {
"$ref": "#/components/schemas/Attendee"
}
]
},
"bookingFieldsResponses": {
"type": "object",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values for custom booking fields added by you.",
"example": {
"customField": "customValue"
}
}, },
"eventTypeId": { "eventTypeId": {
"type": "number", "type": "number",
@@ -21363,14 +21374,6 @@
"description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.", "description": "The organization slug. Optional, only used when booking with eventTypeSlug + username or eventTypeSlug + teamSlug.",
"example": "acme-corp" "example": "acme-corp"
}, },
"attendee": {
"description": "The attendee's details.",
"allOf": [
{
"$ref": "#/components/schemas/Attendee"
}
]
},
"guests": { "guests": {
"description": "An optional list of guest emails attending the event.", "description": "An optional list of guest emails attending the event.",
"example": [ "example": [
@@ -21424,12 +21427,10 @@
"key": "value" "key": "value"
} }
}, },
"bookingFieldsResponses": { "lengthInMinutes": {
"type": "object", "type": "number",
"description": "Booking field responses consisting of an object with booking field slug as keys and user response as values.", "example": 30,
"example": { "description": "If it is an event type that has multiple possible lengths that attendee can pick from, you can pass the desired booking length here.\n If not provided then event type default length will be used for the booking."
"customField": "customValue"
}
}, },
"recurrenceCount": { "recurrenceCount": {
"type": "number", "type": "number",
-1
View File
@@ -1 +0,0 @@
NEXT_PUBLIC_SWAGGER_DOCS_URL=http://localhost:3002/api/docs
-34
View File
@@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
-34
View File
@@ -1,34 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
-353
View File
@@ -1,353 +0,0 @@
import * as OpenAPISnippet from "openapi-snippet";
export const requestSnippets = {
generators: {
curl_bash: {
title: "cURL (bash)",
syntax: "bash",
},
curl_powershell: {
title: "cURL (PowerShell)",
syntax: "powershell",
},
curl_cmd: {
title: "cURL (CMD)",
syntax: "bash",
},
node: {
title: "Node",
syntax: "node",
},
},
defaultExpanded: true,
languages: ["node", "curl_bash"],
};
// Since swagger-ui-react was not configured to change the request snippets some workarounds required
// configuration will be added programatically
// Custom Plugin
export const SnippedGenerator = {
statePlugins: {
// extend some internals to gain information about current path, method and spec in the generator function metioned later
spec: {
wrapSelectors: {
requestFor: (ori) => (state, path, method) => {
return ori(path, method)
?.set("spec", state.get("json", {}))
?.setIn(["oasPathMethod", "path"], path)
?.setIn(["oasPathMethod", "method"], method);
},
mutatedRequestFor: (ori) => (state, path, method) => {
return ori(path, method)
?.set("spec", state.get("json", {}))
?.setIn(["oasPathMethod", "path"], path)
?.setIn(["oasPathMethod", "method"], method);
},
},
},
// extend the request snippets core plugin
requestSnippets: {
wrapSelectors: {
// add additional snippet generators here
getSnippetGenerators:
(ori, system) =>
(state, ...args) =>
ori(state, ...args)
// add node native snippet generator
// .set(
// // key
// "node_native",
// // config and generator function
// system.Im.fromJS({
// title: "NodeJs Native",
// syntax: "javascript",
// hostname: "test",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["node_native"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// // Since I don't know why hostname was undefinedundefined, I harcoded it here
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
.set(
// key
"node_fetch",
// config and generator function
system.Im.fromJS({
title: "NodeJS",
syntax: "javascript",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["node_fetch"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"shell_httpie",
// config and generator function
system.Im.fromJS({
title: "HTTPie",
syntax: "bash",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["shell_httpie"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"php_curl",
// config and generator function
system.Im.fromJS({
title: "PHP",
syntax: "php",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["php_curl"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"java_okhttp",
// config and generator function
system.Im.fromJS({
title: "Java",
syntax: "java",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
console.log(spec, oasPathMethod, path, method);
// run OpenAPISnippet for target node
const targets = ["java_okhttp"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
// .set(
// // key
// "java",
// // config and generator function
// system.Im.fromJS({
// title: "Java (Unirest)",
// syntax: "java",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["java"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
// .set(
// // key
// "c_libcurl",
// // config and generator function
// system.Im.fromJS({
// title: "C (libcurl) ",
// syntax: "bash",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["c_libcurl"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
.set(
// key
"go_native",
// config and generator function
system.Im.fromJS({
title: "Go",
syntax: "bash",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["go_native"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"ruby",
// config and generator function
system.Im.fromJS({
title: "Ruby",
syntax: "ruby",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["ruby"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"python",
// config and generator function
system.Im.fromJS({
title: "Python",
syntax: "python",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["python"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
),
},
},
},
};
-5
View File
@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
-29
View File
@@ -1,29 +0,0 @@
{
"name": "@calcom/swagger",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "next build",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"dev": "PORT=4200 next dev",
"lint": "eslint . --ignore-path .gitignore",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
"lint:report": "eslint . --format json --output-file ../../lint-results/swagger.json",
"start": "PORT=4200 next start"
},
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^13.5.6",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"swagger-ui-react": "4.11.1"
},
"devDependencies": {
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@types/swagger-ui-react": "^4.18.3",
"typescript": "^4.9.4"
}
}
-11
View File
@@ -1,11 +0,0 @@
import "highlight.js/styles/default.css";
import { type AppProps } from "next/app";
import "swagger-ui-react/swagger-ui.css";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
-23
View File
@@ -1,23 +0,0 @@
import dynamic from "next/dynamic";
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
ssr: false,
});
export default function APIDocs() {
return (
<SwaggerUIDynamic
url={process.env.NEXT_PUBLIC_SWAGGER_DOCS_URL || "https://api.cal.com/docs"}
persistAuthorization={true}
supportedSubmitMethods={["get", "post", "delete", "put", "options", "patch"]}
requestSnippetsEnabled={true}
requestSnippets={requestSnippets}
plugins={[SnippedGenerator]}
tryItOutEnabled={true}
docExpansion="list"
filter={true}
/>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-103
View File
@@ -1,103 +0,0 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
@media (max-width: 768px) {
.swagger-ui .opblock-tag {
font-size: 90% !important;
}
.swagger-ui .opblock .opblock-summary {
display: grid;
flex-direction: column;
}
.opblock-summary-path {
flex-shrink: 0;
max-width: 100% !important;
padding: 10px 5px !important;
}
.opblock-summary-description {
font-size: 16px !important;
padding: 0px 5px;
}
.swagger-ui .scheme-container .schemes {
align-items: center;
display: flex;
flex-direction: column;
}
.swagger-ui .info .title {
color: #3b4151;
font-family: sans-serif;
font-size: 22px;
}
.swagger-ui .scheme-container {
padding: 14px 0;
}
.swagger-ui .info {
margin: 10px 0;
}
.swagger-ui .auth-wrapper {
margin: 10px 0;
}
.swagger-ui .authorization__btn {
display: none;
}
.swagger-ui .opblock {
margin: 0 0 5px;
}
button.opblock-summary-control > svg {
display: none;
}
.swagger-ui .filter .operation-filter-input {
border: 2px solid #d8dde7;
margin: 5px 5px;
padding: 5px;
width: 100vw;
}
.swagger-ui .wrapper {
padding: 0 4px;
width: 100%;
}
.swagger-ui .info .title small {
top: 5px;
}
.swagger-ui a.nostyle,
.swagger-ui a.nostyle:visited {
width: 100%;
}
div.request-snippets > div.curl-command > div:nth-child(1) {
overscroll-behavior: contain;
overflow-x: scroll;
}
.swagger-ui .opblock-body pre.microlight {
font-size: 9px;
}
.swagger-ui table tbody tr td {
padding: 0px 0 0;
vertical-align: none;
}
td.response-col_description > div > div > p {
font-size: 12px;
}
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > tbody > tr {
display: flex;
width: 100vw;
flex-direction: column;
font-size: 60%;
}
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > thead > tr {
display: none;
}
}
-17
View File
@@ -1,17 +0,0 @@
{
"extends": "@calcom/tsconfig/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/*"]
}
},
"include": [
"next-env.d.ts",
"../../packages/types/*.d.ts",
"../../packages/types/next-auth.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["**/node_modules/**"]
}
@@ -20,6 +20,7 @@ const getData = withAppDirSsr<OrgPageProps>(getServerSideProps);
export const generateMetadata = async ({ params, searchParams }: PageProps) => { export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams); const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx); const props = await getData(legacyCtx);
const decodedParams = decodeParams(await params);
if ((props as TeamPageProps)?.team) { if ((props as TeamPageProps)?.team) {
const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = props as TeamPageProps; const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = props as TeamPageProps;
@@ -30,7 +31,6 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
image: getOrgOrTeamAvatar(team), image: getOrgOrTeamAvatar(team),
}, },
}; };
const decodedParams = decodeParams(await params);
return { return {
...(await generateMeetingMetadata( ...(await generateMeetingMetadata(
meeting, meeting,
@@ -67,7 +67,8 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
() => profile.name, () => profile.name,
() => markdownStrippedBio, () => markdownStrippedBio,
false, false,
getOrgFullOrigin(entity.orgSlug ?? null) getOrgFullOrigin(entity.orgSlug ?? null),
`/${decodedParams.user}`
)), )),
robots: { robots: {
index: allowSEOIndexing, index: allowSEOIndexing,
@@ -12,7 +12,10 @@ import { ShellMainAppDir } from "../ShellMainAppDir";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("availability"), (t) => t("availability"),
(t) => t("configure_availability") (t) => t("configure_availability"),
undefined,
undefined,
"/availability"
); );
}; };
@@ -11,10 +11,13 @@ const querySchema = z.object({
status: z.enum(validStatuses), status: z.enum(validStatuses),
}); });
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ status: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("bookings"), (t) => t("bookings"),
(t) => t("bookings_description") (t) => t("bookings_description"),
undefined,
undefined,
`/bookings/${(await params).status}`
); );
const Page = async ({ params }: PageProps) => { const Page = async ({ params }: PageProps) => {
@@ -16,7 +16,10 @@ import EventTypes, { EventTypesCTA } from "~/event-types/views/event-types-listi
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("event_types_page_title"), (t) => t("event_types_page_title"),
(t) => t("event_types_page_subtitle") (t) => t("event_types_page_subtitle"),
undefined,
undefined,
"/event-types"
); );
const Page = async ({ params, searchParams }: PageProps) => { const Page = async ({ params, searchParams }: PageProps) => {
@@ -13,7 +13,10 @@ import TeamsView, { TeamsCTA } from "~/teams/teams-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("teams"), (t) => t("teams"),
(t) => t("create_manage_teams_collaborative") (t) => t("create_manage_teams_collaborative"),
undefined,
undefined,
"/teams"
); );
const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => { const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
+4 -1
View File
@@ -6,7 +6,10 @@ import { Button } from "@calcom/ui/components/button";
export const generateMetadata = () => export const generateMetadata = () =>
_generateMetadata( _generateMetadata(
(t) => `${t("access_denied")} | ${APP_NAME}`, (t) => `${t("access_denied")} | ${APP_NAME}`,
() => "" () => "",
undefined,
undefined,
"/403"
); );
async function Error403() { async function Error403() {
+4 -1
View File
@@ -8,7 +8,10 @@ import CopyButton from "./copy-button";
export const generateMetadata = () => export const generateMetadata = () =>
_generateMetadata( _generateMetadata(
(t) => `${t("something_unexpected_occurred")} | ${APP_NAME}`, (t) => `${t("something_unexpected_occurred")} | ${APP_NAME}`,
() => "" () => "",
undefined,
undefined,
"/500"
); );
async function Error500({ searchParams }: { searchParams: { error?: string } }) { async function Error500({ searchParams }: { searchParams: { error?: string } }) {
@@ -28,7 +28,10 @@ export const generateMetadata = async ({ params }: _PageProps) => {
return await generateAppMetadata( return await generateAppMetadata(
{ slug: logo, name, description }, { slug: logo, name, description },
() => name, () => name,
() => description () => description,
undefined,
undefined,
`/apps/${p.data.slug}`
); );
}; };
@@ -13,7 +13,10 @@ export const generateMetadata = async ({ params: _params }: ServerPageProps) =>
const params = await _params; const params = await _params;
const metadata = await _generateMetadata( const metadata = await _generateMetadata(
() => `${params.slug}`, () => `${params.slug}`,
() => "" () => "",
undefined,
undefined,
`/apps/${params.slug}/setup`
); );
return { return {
...metadata, ...metadata,
@@ -3,7 +3,10 @@ import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("app_store"), (t) => t("app_store"),
(t) => t("app_store_description") (t) => t("app_store_description"),
undefined,
undefined,
"/apps/categories"
); );
}; };
@@ -13,9 +13,14 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams); const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const { appMetadata } = await getData(legacyCtx); const { appMetadata } = await getData(legacyCtx);
const stepParam = (await params).step;
const step = stepParam && Array.isArray(stepParam) ? stepParam.join("/") : "";
return await _generateMetadata( return await _generateMetadata(
(t) => `${t("install")} ${appMetadata?.name ?? ""}`, (t) => `${t("install")} ${appMetadata?.name ?? ""}`,
() => "" () => "",
undefined,
undefined,
`/apps/installation${step ? `/${step}` : ""}`
); );
}; };
@@ -11,10 +11,13 @@ const querySchema = z.object({
category: z.nativeEnum(AppCategories), category: z.nativeEnum(AppCategories),
}); });
export const generateMetadata = async () => { export const generateMetadata = async ({ params }: { params: Promise<{ category: string }> }) => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("installed_apps"), (t) => t("installed_apps"),
(t) => t("manage_your_connected_apps") (t) => t("manage_your_connected_apps"),
undefined,
undefined,
`/apps/installed/${(await params).category}`
); );
}; };
@@ -11,7 +11,10 @@ import AppsPage from "~/apps/apps-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("app_store"), (t) => t("app_store"),
(t) => t("app_store_description") (t) => t("app_store_description"),
undefined,
undefined,
"/apps"
); );
}; };
@@ -25,7 +25,10 @@ export const generateMetadata = async ({ params }: { params: Promise<{ pages: st
return await _generateMetadata( return await _generateMetadata(
// TODO: Need to show the actual form name instead of "Form" // TODO: Need to show the actual form name instead of "Form"
(t) => (mainPage === "routing-link" ? `Form | Cal.com Forms` : `${t("routing_forms")} | Cal.com Forms`), (t) => (mainPage === "routing-link" ? `Form | Cal.com Forms` : `${t("routing_forms")} | Cal.com Forms`),
() => "" () => "",
undefined,
undefined,
`/apps/routing-forms/${(await params).pages.join("/")}`
); );
}; };
@@ -3,6 +3,7 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import Link from "next/link"; import Link from "next/link";
import { z } from "zod"; import { z } from "zod";
import { IdentityProvider } from "@calcom/prisma/client";
import { Button } from "@calcom/ui/components/button"; import { Button } from "@calcom/ui/components/button";
import { Icon } from "@calcom/ui/components/icon"; import { Icon } from "@calcom/ui/components/icon";
@@ -11,18 +12,47 @@ import AuthContainer from "@components/ui/AuthContainer";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("error"), (t) => t("error"),
() => "" () => "",
undefined,
undefined,
"/auth/error"
); );
}; };
const querySchema = z.object({ const querySchema = z.object({
error: z.string().optional(), error: z.string().optional(),
provider: z.string().optional(),
}); });
const ServerPage = async ({ searchParams }: PageProps) => { const ServerPage = async ({ searchParams }: PageProps) => {
const t = await getTranslate(); const t = await getTranslate();
const { error } = querySchema.parse({ error: (await searchParams)?.error || undefined });
const errorMsg = t("error_during_login") + (error ? ` Error code: ${error}` : ""); const { error, provider } = querySchema.parse({
error: (await searchParams)?.error || undefined,
provider: (await searchParams)?.provider || undefined,
});
// generate error message based on different errors
const getErrorMessage = (error: string | undefined, provider: string | undefined) => {
if (error === "user-creation-error") {
return t("user_creation_error");
} else if (error === "wrong-provider") {
// showing user their original identity provider by which account is managed.
const providerName =
provider === IdentityProvider.GOOGLE
? "Google"
: provider === IdentityProvider.CAL
? "Email and Password"
: provider === IdentityProvider.SAML
? "SAML (like Okta)"
: "your original login method";
return t("account_managed_by_identity_provider_error", { provider: providerName });
}
return t("error_during_login") + (error ? ` Error code: ${error}` : "");
};
const errorMsg = getErrorMessage(error, provider);
return ( return (
<AuthContainer> <AuthContainer>
<div> <div>
@@ -10,10 +10,13 @@ import { getServerSideProps } from "@server/lib/auth/forgot-password/[id]/getSer
import type { PageProps as ClientPageProps } from "~/auth/forgot-password/[id]/forgot-password-single-view"; import type { PageProps as ClientPageProps } from "~/auth/forgot-password/[id]/forgot-password-single-view";
import SetNewUserPassword from "~/auth/forgot-password/[id]/forgot-password-single-view"; import SetNewUserPassword from "~/auth/forgot-password/[id]/forgot-password-single-view";
export const generateMetadata = async () => { export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("reset_password"), (t) => t("reset_password"),
(t) => t("change_your_password") (t) => t("change_your_password"),
undefined,
undefined,
`/auth/forgot-password/${(await params).id}`
); );
}; };
@@ -13,7 +13,10 @@ import ForgotPassword from "~/auth/forgot-password/forgot-password-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("forgot_password"), (t) => t("forgot_password"),
(t) => t("request_password_reset") (t) => t("request_password_reset"),
undefined,
undefined,
"/auth/forgot-password"
); );
}; };
@@ -13,7 +13,10 @@ import Login from "~/auth/login-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("login"), (t) => t("login"),
(t) => t("login") (t) => t("login"),
undefined,
undefined,
"/auth/login"
); );
}; };
@@ -9,7 +9,10 @@ import Logout from "~/auth/logout-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("logged_out"), (t) => t("logged_out"),
(t) => t("youve_been_logged_out") (t) => t("youve_been_logged_out"),
undefined,
undefined,
"/auth/logout"
); );
}; };
@@ -5,7 +5,10 @@ import Page from "~/auth/oauth2/authorize-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("authorize"), (t) => t("authorize"),
() => "" () => "",
undefined,
undefined,
"/auth/oauth2/authorize"
); );
}; };
@@ -5,7 +5,10 @@ import Page from "~/auth/platform/authorize-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("authorize"), (t) => t("authorize"),
() => "" () => "",
undefined,
undefined,
"/auth/platform/authorize"
); );
}; };
@@ -13,7 +13,10 @@ import type { PageProps as ClientPageProps } from "~/auth/setup-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("setup"), (t) => t("setup"),
(t) => t("setup_description") (t) => t("setup_description"),
undefined,
undefined,
"/auth/setup"
); );
}; };
@@ -13,7 +13,10 @@ import VerifyEmailChange from "~/auth/verify-email-change-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("verify_email_change"), (t) => t("verify_email_change"),
() => "" () => "",
undefined,
undefined,
"/auth/verify-email-change"
); );
}; };
@@ -5,7 +5,10 @@ import VerifyEmailPage from "~/auth/verify-email-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("verify_email_button"), (t) => t("verify_email_button"),
() => "" () => "",
undefined,
undefined,
"/auth/verify-email"
); );
}; };
@@ -26,7 +26,10 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
return await _generateMetadata( return await _generateMetadata(
() => () =>
hasPaymentFailed ? "Your payment failed" : sessionId ? "Payment successful!" : `Verify your email`, hasPaymentFailed ? "Your payment failed" : sessionId ? "Payment successful!" : `Verify your email`,
() => "" () => "",
undefined,
undefined,
"/auth/verify"
); );
}; };
@@ -38,7 +38,10 @@ export const generateMetadata = async ({ params }: PageProps) => {
return await _generateMetadata( return await _generateMetadata(
(t) => (schedule.name ? `${schedule.name} | ${t("availability")}` : t("availability")), (t) => (schedule.name ? `${schedule.name} | ${t("availability")}` : t("availability")),
() => "" () => "",
undefined,
undefined,
`/availability/${parsed.data.schedule}`
); );
}; };
@@ -5,7 +5,10 @@ import Troubleshoot from "~/availability/troubleshoot/troubleshoot-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("troubleshoot"), (t) => t("troubleshoot"),
(t) => t("troubleshoot_availability") (t) => t("troubleshoot_availability"),
undefined,
undefined,
"/availability/troubleshoot"
); );
}; };
@@ -26,7 +26,8 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
(t) => (t) =>
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`), t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
false, false,
getOrgFullOrigin(orgSlug) getOrgFullOrigin(orgSlug),
`/booking/${(await params).uid}`
); );
}; };
@@ -5,7 +5,10 @@ import BookingDryRunSuccessView from "~/bookings/views/booking-dry-run-success-v
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("booking_dry_run_successful"), (t) => t("booking_dry_run_successful"),
(t) => t("booking_dry_run_successful_description") (t) => t("booking_dry_run_successful_description"),
undefined,
undefined,
"/booking/dry-run-successful"
); );
}; };
@@ -7,7 +7,10 @@ import LegacyPage from "~/connect-and-join/connect-and-join-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("connect_and_join"), (t) => t("connect_and_join"),
() => "" () => "",
undefined,
undefined,
"/connect-and-join"
); );
}; };
@@ -10,7 +10,8 @@ import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps";
import Type from "~/d/[link]/d-type-view"; import Type from "~/d/[link]/d-type-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => { export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams); const _params = await params;
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), _params, await searchParams);
const pageProps = await getData(legacyCtx); const pageProps = await getData(legacyCtx);
const { booking, eventData, isBrandingHidden } = pageProps; const { booking, eventData, isBrandingHidden } = pageProps;
@@ -21,7 +22,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
return await _generateMetadata( return await _generateMetadata(
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`, (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`,
isBrandingHidden isBrandingHidden,
undefined,
`/d/${_params.link}/${_params.slug}`
); );
}; };
@@ -5,7 +5,10 @@ import EnterprisePage from "@components/EnterprisePage";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("create_your_org"), (t) => t("create_your_org"),
(t) => t("create_your_org_description") (t) => t("create_your_org_description"),
undefined,
undefined,
"/enterprise"
); );
const ServerPageWrapper = async () => { const ServerPageWrapper = async () => {
@@ -24,7 +24,10 @@ export const generateMetadata = async ({ params }: _PageProps) => {
if (!parsed.success) { if (!parsed.success) {
return await _generateMetadata( return await _generateMetadata(
(t) => `${t("event_type")}`, (t) => `${t("event_type")}`,
() => "" () => "",
undefined,
undefined,
"/event-types"
); );
} }
@@ -34,7 +37,10 @@ export const generateMetadata = async ({ params }: _PageProps) => {
return await _generateMetadata( return await _generateMetadata(
(t) => (data?.title ? `${data.title} | ${t("event_type")}` : `${t("event_type")}`), (t) => (data?.title ? `${data.title} | ${t("event_type")}` : `${t("event_type")}`),
() => "" () => "",
undefined,
undefined,
`/event-types/${parsed.data.type}`
); );
}; };
@@ -11,11 +11,15 @@ import { getServerSideProps } from "@lib/getting-started/[[...step]]/getServerSi
import type { PageProps as ClientPageProps } from "~/getting-started/[[...step]]/onboarding-view"; import type { PageProps as ClientPageProps } from "~/getting-started/[[...step]]/onboarding-view";
import Page from "~/getting-started/[[...step]]/onboarding-view"; import Page from "~/getting-started/[[...step]]/onboarding-view";
export const generateMetadata = async () => { export const generateMetadata = async ({ params }: ServerPageProps) => {
const stepParam = (await params).step;
const step = stepParam && Array.isArray(stepParam) ? stepParam.join("/") : "";
return await _generateMetadata( return await _generateMetadata(
(t) => `${APP_NAME} - ${t("getting_started")}`, (t) => `${APP_NAME} - ${t("getting_started")}`,
() => "", () => "",
true true,
undefined,
`/getting-started${step ? `/${step}` : ""}`
); );
}; };
@@ -8,7 +8,10 @@ import InsightsPage from "~/insights/insights-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("insights"), (t) => t("insights"),
(t) => t("insights_subtitle") (t) => t("insights_subtitle"),
undefined,
undefined,
"/insights"
); );
export default async function Page() { export default async function Page() {
@@ -8,7 +8,10 @@ import InsightsVirtualQueuesPage from "~/insights/insights-virtual-queues-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("insights"), (t) => t("insights"),
(t) => t("insights_subtitle") (t) => t("insights_subtitle"),
undefined,
undefined,
"/insights/router-position"
); );
export default async function Page() { export default async function Page() {
@@ -8,7 +8,10 @@ import InsightsRoutingPage from "~/insights/insights-routing-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("insights"), (t) => t("insights"),
(t) => t("insights_subtitle") (t) => t("insights_subtitle"),
undefined,
undefined,
"/insights/routing"
); );
export default async function Page() { export default async function Page() {
@@ -7,7 +7,10 @@ import LegacyPage from "~/maintenance/maintenance-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("under_maintenance"), (t) => t("under_maintenance"),
(t) => t("under_maintenance_description", { appName: APP_NAME }) (t) => t("under_maintenance_description", { appName: APP_NAME }),
undefined,
undefined,
"/maintenance"
); );
}; };
@@ -5,7 +5,10 @@ import Page from "~/more/more-page-view";
export const generateMetadata = async () => { export const generateMetadata = async () => {
return await _generateMetadata( return await _generateMetadata(
(t) => t("more"), (t) => t("more"),
() => "" () => "",
undefined,
undefined,
"/more"
); );
}; };
@@ -16,7 +16,10 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const eventName = props.booking.title; const eventName = props.booking.title;
return await _generateMetadata( return await _generateMetadata(
(t) => `${t("payment")} | ${eventName} | ${APP_NAME}`, (t) => `${t("payment")} | ${eventName} | ${APP_NAME}`,
() => "" () => "",
undefined,
undefined,
`/payment/${(await params).uid}`
); );
}; };
@@ -4,10 +4,13 @@ import { getTranslate } from "app/_utils";
import AdminAppsList from "@calcom/features/apps/AdminAppsList"; import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ category: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("apps"), (t) => t("apps"),
(t) => t("admin_apps_description") (t) => t("admin_apps_description"),
undefined,
undefined,
`/settings/admin/apps/${(await params).category}`
); );
const Page = async () => { const Page = async () => {
@@ -4,7 +4,10 @@ import { redirect } from "next/navigation";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("apps"), (t) => t("apps"),
(t) => t("admin_apps_description") (t) => t("admin_apps_description"),
undefined,
undefined,
"/settings/admin/apps"
); );
const Page = () => { const Page = () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("feature_flags"), (t) => t("feature_flags"),
(t) => t("admin_flags_description") (t) => t("admin_flags_description"),
undefined,
undefined,
"/settings/admin/flags"
); );
const Page = async () => { const Page = async () => {
@@ -8,7 +8,10 @@ import ImpersonationView from "~/settings/admin/impersonation-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("admin"), (t) => t("admin"),
(t) => t("impersonation") (t) => t("impersonation"),
undefined,
undefined,
"/settings/admin/impersonation"
); );
const Page = async () => { const Page = async () => {
@@ -7,7 +7,10 @@ import LockedSMSView from "~/settings/admin/locked-sms-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("lockedSMS"), (t) => t("lockedSMS"),
(t) => t("admin_lockedSMS_description") (t) => t("admin_lockedSMS_description"),
undefined,
undefined,
"/settings/admin/lockedSMS"
); );
const Page = async () => { const Page = async () => {
@@ -7,7 +7,10 @@ import LegacyPage from "~/settings/admin/oauth-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("oAuth"), (t) => t("oAuth"),
(t) => t("admin_oAuth_description") (t) => t("admin_oAuth_description"),
undefined,
undefined,
"/settings/admin/oAuth"
); );
const Page = async () => { const Page = async () => {
@@ -15,7 +15,10 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
if (!input.success) { if (!input.success) {
return await _generateMetadata( return await _generateMetadata(
(t) => t("editing_org"), (t) => t("editing_org"),
(t) => t("admin_orgs_edit_description") (t) => t("admin_orgs_edit_description"),
undefined,
undefined,
"/settings/admin/organizations/edit"
); );
} }
@@ -23,7 +26,10 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
return await _generateMetadata( return await _generateMetadata(
(t) => `${t("editing_org")}: ${org.name}`, (t) => `${t("editing_org")}: ${org.name}`,
(t) => t("admin_orgs_edit_description") (t) => t("admin_orgs_edit_description"),
undefined,
undefined,
`/settings/admin/organizations/${input.data.id}/edit`
); );
}; };
@@ -8,7 +8,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("organizations"), (t) => t("organizations"),
(t) => t("orgs_page_description") (t) => t("orgs_page_description"),
undefined,
undefined,
"/settings/admin/organizations"
); );
const Page = async () => { const Page = async () => {
@@ -3,7 +3,10 @@ import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("admin"), (t) => t("admin"),
() => "" () => "",
undefined,
undefined,
"/settings/admin"
); );
const Page = () => <h1>Admin index</h1>; const Page = () => <h1>Admin index</h1>;
@@ -15,7 +15,10 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
if (!input.success) { if (!input.success) {
return await _generateMetadata( return await _generateMetadata(
(t) => t("editing_user"), (t) => t("editing_user"),
(t) => t("admin_users_edit_description") (t) => t("admin_users_edit_description"),
undefined,
undefined,
"/settings/admin/users/edit"
); );
} }
@@ -23,7 +26,10 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
return await _generateMetadata( return await _generateMetadata(
(t) => `${t("editing_user")}: ${user.username}`, (t) => `${t("editing_user")}: ${user.username}`,
(t) => t("admin_users_edit_description") (t) => t("admin_users_edit_description"),
undefined,
undefined,
`/settings/admin/users/${input.data.id}/edit`
); );
}; };
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("add_new_user"), (t) => t("add_new_user"),
(t) => t("admin_users_add_description") (t) => t("admin_users_add_description"),
undefined,
undefined,
"/settings/admin/users/add"
); );
const Page = async () => { const Page = async () => {
@@ -7,7 +7,10 @@ import { Button } from "@calcom/ui/components/button";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("users"), (t) => t("users"),
(t) => t("admin_users_description") (t) => t("admin_users_description"),
undefined,
undefined,
"/settings/admin/users"
); );
const Page = async () => { const Page = async () => {
@@ -7,7 +7,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("workspace_platforms"), (t) => t("workspace_platforms"),
(t) => t("workspace_platforms_description") (t) => t("workspace_platforms_description"),
undefined,
undefined,
"/settings/admin/workspace-platforms"
); );
const Page = async () => { const Page = async () => {
@@ -8,7 +8,10 @@ import BillingView from "~/settings/billing/billing-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("billing"), (t) => t("billing"),
(t) => t("manage_billing_description") (t) => t("manage_billing_description"),
undefined,
undefined,
"/settings/billing"
); );
const Page = async () => { const Page = async () => {
@@ -8,7 +8,10 @@ import ApiKeysView, { NewApiKeyButton } from "~/settings/developer/api-keys-view
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("api_keys"), (t) => t("api_keys"),
(t) => t("create_first_api_key_description", { appName: APP_NAME }) (t) => t("create_first_api_key_description", { appName: APP_NAME }),
undefined,
undefined,
"/settings/developer/api-keys"
); );
const Page = async () => { const Page = async () => {
@@ -14,7 +14,10 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("webhooks"), (t) => t("webhooks"),
(t) => t("add_webhook_description", { appName: APP_NAME }) (t) => t("add_webhook_description", { appName: APP_NAME }),
undefined,
undefined,
"/settings/developer/webhooks"
); );
const WebhooksViewServerWrapper = async () => { const WebhooksViewServerWrapper = async () => {
@@ -6,10 +6,13 @@ import { EditWebhookView } from "@calcom/features/webhooks/pages/webhook-edit-vi
import { APP_NAME } from "@calcom/lib/constants"; import { APP_NAME } from "@calcom/lib/constants";
import { WebhookRepository } from "@calcom/lib/server/repository/webhook"; import { WebhookRepository } from "@calcom/lib/server/repository/webhook";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("webhooks"), (t) => t("webhooks"),
(t) => t("add_webhook_description", { appName: APP_NAME }) (t) => t("add_webhook_description", { appName: APP_NAME }),
undefined,
undefined,
`/settings/developer/webhooks/${(await params).id}`
); );
const Page = async ({ params: _params }: PageProps) => { const Page = async ({ params: _params }: PageProps) => {
@@ -9,7 +9,10 @@ import { webhookRouter } from "@calcom/trpc/server/routers/viewer/webhook/_route
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("webhooks"), (t) => t("webhooks"),
(t) => t("add_webhook_description", { appName: APP_NAME }) (t) => t("add_webhook_description", { appName: APP_NAME }),
undefined,
undefined,
"/settings/developer/webhooks/new"
); );
const Page = async () => { const Page = async () => {
@@ -32,7 +32,7 @@ export default async function SettingsLayoutAppDir(props: SettingsLayoutAppDirPr
return ( return (
<> <>
<SettingsLayoutAppDirClient {...props} currentOrg={currentOrg} otherTeams={otherTeams} />; <SettingsLayoutAppDirClient {...props} currentOrg={currentOrg} otherTeams={otherTeams} />
</> </>
); );
} }
@@ -8,7 +8,10 @@ import AppearancePage from "~/settings/my-account/appearance-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("appearance"), (t) => t("appearance"),
(t) => t("appearance_description") (t) => t("appearance_description"),
undefined,
undefined,
"/settings/my-account/appearance"
); );
const Page = async () => { const Page = async () => {
@@ -9,7 +9,10 @@ import { CalendarListContainer } from "@components/apps/CalendarListContainer";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("calendars"), (t) => t("calendars"),
(t) => t("calendars_description") (t) => t("calendars_description"),
undefined,
undefined,
"/settings/my-account/calendars"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import { ConferencingAppsViewWebWrapper } from "@calcom/atoms/connect/conferenci
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("conferencing"), (t) => t("conferencing"),
(t) => t("conferencing_description") (t) => t("conferencing_description"),
undefined,
undefined,
"/settings/my-account/conferencing"
); );
const Page = async () => { const Page = async () => {
@@ -9,7 +9,10 @@ import GeneralQueryView from "~/settings/my-account/general-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("general"), (t) => t("general"),
(t) => t("general_description") (t) => t("general_description"),
undefined,
undefined,
"/settings/my-account/general"
); );
const Page = async () => { const Page = async () => {
@@ -9,7 +9,10 @@ import { OutOfOfficeToggleGroup } from "@calcom/features/settings/outOfOffice/Ou
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("out_of_office"), (t) => t("out_of_office"),
(t) => t("out_of_office_description") (t) => t("out_of_office_description"),
undefined,
undefined,
"/settings/my-account/out-of-office"
); );
const Page = async () => { const Page = async () => {
@@ -9,7 +9,10 @@ import ProfileView from "~/settings/my-account/profile-view";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("profile"), (t) => t("profile"),
(t) => t("profile_description") (t) => t("profile_description"),
undefined,
undefined,
"/settings/my-account/profile"
); );
const Page = async () => { const Page = async () => {
@@ -8,7 +8,10 @@ import PushNotificationsView from "~/settings/my-account/push-notifications-view
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("push_notifications"), (t) => t("push_notifications"),
(t) => t("push_notifications_description") (t) => t("push_notifications_description"),
undefined,
undefined,
"/settings/my-account/push-notifications"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => `${t("admin")} ${t("api_reference")}`, (t) => `${t("admin")} ${t("api_reference")}`,
(t) => t("leverage_our_api") (t) => t("leverage_our_api"),
undefined,
undefined,
"/settings/organizations/admin-api"
); );
const Page = async () => { const Page = async () => {
@@ -2,10 +2,13 @@ import { _generateMetadata } from "app/_utils";
import OrgAttributesEditPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-edit-view"; import OrgAttributesEditPage from "@calcom/ee/organizations/pages/settings/attributes/attributes-edit-view";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("attribute"), (t) => t("attribute"),
(t) => t("edit_attribute_description") (t) => t("edit_attribute_description"),
undefined,
undefined,
`/settings/organizations/attributes/${(await params).id}/edit`
); );
const OrgAttributesEditPageWrapper = async () => { const OrgAttributesEditPageWrapper = async () => {
@@ -5,7 +5,10 @@ import OrgAttributesCreatePage from "@calcom/ee/organizations/pages/settings/att
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("attribute"), (t) => t("attribute"),
(t) => t("create_attribute_description") (t) => t("create_attribute_description"),
undefined,
undefined,
"/settings/organizations/attributes/create"
); );
const OrgAttributesCreatePageWrapper = async () => { const OrgAttributesCreatePageWrapper = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("attributes"), (t) => t("attributes"),
(t) => t("attribute_meta_description") (t) => t("attribute_meta_description"),
undefined,
undefined,
"/settings/organizations/attributes"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("delegation_credential"), (t) => t("delegation_credential"),
(t) => t("delegation_credential_description") (t) => t("delegation_credential_description"),
undefined,
undefined,
"/settings/organizations/delegation-credential"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("directory_sync"), (t) => t("directory_sync"),
(t) => t("directory_sync_description") (t) => t("directory_sync_description"),
undefined,
undefined,
"/settings/organizations/dsync"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("general"), (t) => t("general"),
(t) => t("general_description") (t) => t("general_description"),
undefined,
undefined,
"/settings/organizations/general"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("privacy"), (t) => t("privacy"),
(t) => t("privacy_organization_description") (t) => t("privacy_organization_description"),
undefined,
undefined,
"/settings/organizations/privacy"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("profile"), (t) => t("profile"),
(t) => t("profile_org_description") (t) => t("profile_org_description"),
undefined,
undefined,
"/settings/organizations/profile"
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("sso_configuration"), (t) => t("sso_configuration"),
(t) => t("sso_configuration_description_orgs") (t) => t("sso_configuration_description_orgs"),
undefined,
undefined,
"/settings/organizations/sso"
); );
const Page = async () => { const Page = async () => {
@@ -3,10 +3,13 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import LegacyPage from "@calcom/features/ee/teams/pages/team-appearance-view"; import LegacyPage from "@calcom/features/ee/teams/pages/team-appearance-view";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("booking_appearance"), (t) => t("booking_appearance"),
(t) => t("appearance_team_description") (t) => t("appearance_team_description"),
undefined,
undefined,
`/settings/organizations/teams/other/${(await params).id}/appearance`
); );
const Page = async () => { const Page = async () => {
@@ -5,10 +5,13 @@ import LegacyPage, {
} from "@calcom/features/ee/organizations/pages/settings/other-team-members-view"; } from "@calcom/features/ee/organizations/pages/settings/other-team-members-view";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("team_members"), (t) => t("team_members"),
(t) => t("members_team_description") (t) => t("members_team_description"),
undefined,
undefined,
`/settings/organizations/teams/other/${(await params).id}/members`
); );
const Page = async () => { const Page = async () => {
@@ -3,10 +3,13 @@ import { _generateMetadata, getTranslate } from "app/_utils";
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/other-team-profile-view"; import LegacyPage from "@calcom/features/ee/organizations/pages/settings/other-team-profile-view";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async ({ params }: { params: Promise<{ id: string }> }) =>
await _generateMetadata( await _generateMetadata(
(t) => t("profile"), (t) => t("profile"),
(t) => t("profile_team_description") (t) => t("profile_team_description"),
undefined,
undefined,
`/settings/organizations/teams/other/${(await params).id}/profile`
); );
const Page = async () => { const Page = async () => {
@@ -6,7 +6,10 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
export const generateMetadata = async () => export const generateMetadata = async () =>
await _generateMetadata( await _generateMetadata(
(t) => t("org_admin_other_teams"), (t) => t("org_admin_other_teams"),
(t) => t("org_admin_other_teams_description") (t) => t("org_admin_other_teams_description"),
undefined,
undefined,
"/settings/organizations/teams/other"
); );
const Page = async () => { const Page = async () => {

Some files were not shown because too many files have changed in this diff Show More