Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bab2af500 | ||
|
|
9b7c6585d8 | ||
|
|
58930c4c5e | ||
|
|
9412226a76 | ||
|
|
df12581879 | ||
|
|
0ec1638ca7 | ||
|
|
2516e18ebb | ||
|
|
d2f8076c70 | ||
|
|
cbfb11015e | ||
|
|
2d90d11e9e | ||
|
|
7ae0d6b7cc | ||
|
|
e0dc2ba03a |
@@ -74,8 +74,14 @@ jobs:
|
|||||||
uses: ./.github/workflows/e2e-embed-react.yml
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e-atoms:
|
||||||
|
name: Tests
|
||||||
|
needs: [lint, build, build-atoms, build-api-v2]
|
||||||
|
uses: ./.github/workflows/e2e-atoms.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
required:
|
required:
|
||||||
needs: [lint, type-check, unit-test, integration-test, build, build-api-v1, build-api-v2, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
needs: [lint, type-check, unit-test, integration-test, build, build-api-v1, build-api-v2, e2e, e2e-embed, e2e-embed-react, e2e-app-store, e2e-atoms]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-2vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
name: Atoms E2E Tests
|
name: Atoms E2E Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_call:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'packages/platform/atoms/**'
|
|
||||||
- 'packages/platform/examples/base/**'
|
|
||||||
- 'apps/api/v2/**'
|
|
||||||
- '.github/workflows/atoms-e2e.yml'
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -25,7 +17,7 @@ env:
|
|||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
atoms-e2e:
|
e2e-atoms:
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
name: Atoms E2E Tests
|
name: Atoms E2E Tests
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
@@ -45,13 +37,13 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: atoms-e2e-test-results
|
name: e2e-atoms-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Upload Playwright Report
|
- name: Upload Playwright Report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: atoms-e2e-playwright-report
|
name: e2e-atoms-playwright-report
|
||||||
path: playwright-report
|
path: playwright-report
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
@@ -195,6 +195,13 @@ jobs:
|
|||||||
uses: ./.github/workflows/e2e-embed-react.yml
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e-atoms:
|
||||||
|
name: Tests
|
||||||
|
needs: [changes, check-label, build, build-atoms, build-api-v2]
|
||||||
|
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
|
uses: ./.github/workflows/e2e-atoms.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze Build
|
name: Analyze Build
|
||||||
needs: [build]
|
needs: [build]
|
||||||
@@ -204,7 +211,7 @@ jobs:
|
|||||||
merge-reports:
|
merge-reports:
|
||||||
name: Merge reports
|
name: Merge reports
|
||||||
if: ${{ !cancelled() && needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ !cancelled() && needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
needs: [changes, check-label, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
needs: [changes, check-label, e2e, e2e-embed, e2e-embed-react, e2e-app-store, e2e-atoms]
|
||||||
uses: ./.github/workflows/merge-reports.yml
|
uses: ./.github/workflows/merge-reports.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
@@ -249,6 +256,7 @@ jobs:
|
|||||||
e2e-embed,
|
e2e-embed,
|
||||||
e2e-embed-react,
|
e2e-embed-react,
|
||||||
e2e-app-store,
|
e2e-app-store,
|
||||||
|
e2e-atoms,
|
||||||
]
|
]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-2vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
@@ -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.320",
|
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.321",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module";
|
|||||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||||
import { KyselyModule } from "@/modules/kysely/kysely.module";
|
import { KyselyModule } from "@/modules/kysely/kysely.module";
|
||||||
|
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||||
@@ -53,6 +54,7 @@ import { Module } from "@nestjs/common";
|
|||||||
StripeModule,
|
StripeModule,
|
||||||
TeamsModule,
|
TeamsModule,
|
||||||
TeamsEventTypesModule,
|
TeamsEventTypesModule,
|
||||||
|
MembershipsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
TokensRepository,
|
TokensRepository,
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ import { CalVideoService } from "@/ee/bookings/2024-08-13/services/cal-video.ser
|
|||||||
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
|
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
|
||||||
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
|
import { API_KEY_OR_ACCESS_TOKEN_HEADER } from "@/lib/docs/headers";
|
||||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||||
|
import {
|
||||||
|
AuthOptionalUser,
|
||||||
|
GetOptionalUser,
|
||||||
|
} from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
||||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
|
||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
|
import { OptionalApiAuthGuard } from "@/modules/auth/guards/optional-api-auth/optional-api-auth.guard";
|
||||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||||
import { UsersService } from "@/modules/users/services/users.service";
|
import { UsersService } from "@/modules/users/services/users.service";
|
||||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||||
@@ -96,6 +100,7 @@ export class BookingsController_2024_08_13 {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post("/")
|
@Post("/")
|
||||||
|
@UseGuards(OptionalApiAuthGuard)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Create a booking",
|
summary: "Create a booking",
|
||||||
description: `
|
description: `
|
||||||
@@ -138,9 +143,10 @@ export class BookingsController_2024_08_13 {
|
|||||||
async createBooking(
|
async createBooking(
|
||||||
@Body(new CreateBookingInputPipe())
|
@Body(new CreateBookingInputPipe())
|
||||||
body: CreateBookingInput,
|
body: CreateBookingInput,
|
||||||
@Req() request: Request
|
@Req() request: Request,
|
||||||
|
@GetOptionalUser() user: AuthOptionalUser
|
||||||
): Promise<CreateBookingOutput_2024_08_13> {
|
): Promise<CreateBookingOutput_2024_08_13> {
|
||||||
const booking = await this.bookingsService.createBooking(request, body);
|
const booking = await this.bookingsService.createBooking(request, body, user);
|
||||||
|
|
||||||
if (Array.isArray(booking)) {
|
if (Array.isArray(booking)) {
|
||||||
await this.bookingsService.billBookings(booking);
|
await this.bookingsService.billBookings(booking);
|
||||||
|
|||||||
+73
-1
@@ -3,6 +3,7 @@ import { AppModule } from "@/app.module";
|
|||||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||||
import { Locales } from "@/lib/enums/locales";
|
import { Locales } from "@/lib/enums/locales";
|
||||||
|
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||||
import {
|
import {
|
||||||
CreateManagedUserData,
|
CreateManagedUserData,
|
||||||
CreateManagedUserOutput,
|
CreateManagedUserOutput,
|
||||||
@@ -80,7 +81,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await Test.createTestingModule({
|
const moduleRef = await Test.createTestingModule({
|
||||||
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
providers: [PrismaExceptionFilter, HttpExceptionFilter],
|
||||||
imports: [AppModule, UsersModule],
|
imports: [AppModule, UsersModule, MembershipsModule],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
@@ -772,6 +773,77 @@ describe("Managed user bookings 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("event type booking requires authentication", () => {
|
||||||
|
let eventTypeRequiringAuthenticationId: number;
|
||||||
|
|
||||||
|
let body: CreateBookingInput_2024_08_13;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const eventTypeRequiringAuthentication = await eventTypesRepositoryFixture.create(
|
||||||
|
{
|
||||||
|
title: `event-type-requiring-authentication-${randomString()}`,
|
||||||
|
slug: `event-type-requiring-authentication-${randomString()}`,
|
||||||
|
length: 60,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
bookingRequiresAuthentication: true,
|
||||||
|
},
|
||||||
|
secondManagedUser.user.id
|
||||||
|
);
|
||||||
|
eventTypeRequiringAuthenticationId = eventTypeRequiringAuthentication.id;
|
||||||
|
|
||||||
|
body = {
|
||||||
|
start: new Date(Date.UTC(2030, 0, 9, 15, 0, 0)).toISOString(),
|
||||||
|
eventTypeId: eventTypeRequiringAuthenticationId,
|
||||||
|
attendee: {
|
||||||
|
email: "external@example.com",
|
||||||
|
name: "External Attendee",
|
||||||
|
timeZone: "Europe/Rome",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can't be booked without credentials", async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings`)
|
||||||
|
.send(body)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.expect(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can't be booked with managed user credentials who is not admin and not event type owner", async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings`)
|
||||||
|
.send(body)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${firstManagedUser.accessToken}`)
|
||||||
|
.expect(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be booked with managed user credentials who is event type owner", async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings`)
|
||||||
|
.send(body)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${secondManagedUser.accessToken}`)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const bookingId = response.body.data.id;
|
||||||
|
await bookingsRepositoryFixture.deleteById(bookingId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be booked with managed user credentials who is admin", async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.post(`/v2/bookings`)
|
||||||
|
.send(body)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.set("Authorization", `Bearer ${orgAdminManagedUser.accessToken}`)
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const bookingId = response.body.data.id;
|
||||||
|
await bookingsRepositoryFixture.deleteById(bookingId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
await userRepositoryFixture.delete(firstManagedUser.user.id);
|
||||||
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
await userRepositoryFixture.delete(secondManagedUser.user.id);
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/servi
|
|||||||
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
|
import { PlatformBookingsService } from "@/ee/bookings/shared/platform-bookings.service";
|
||||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||||
import { getPagination } from "@/lib/pagination/pagination";
|
import { getPagination } from "@/lib/pagination/pagination";
|
||||||
|
import { AuthOptionalUser } from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
||||||
import { BillingService } from "@/modules/billing/services/billing.service";
|
import { BillingService } from "@/modules/billing/services/billing.service";
|
||||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||||
import { KyselyReadService } from "@/modules/kysely/kysely-read.service";
|
import { KyselyReadService } from "@/modules/kysely/kysely-read.service";
|
||||||
|
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||||
|
import { MembershipsService } from "@/modules/memberships/services/memberships.service";
|
||||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||||
@@ -18,7 +21,14 @@ 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 { ConflictException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
import {
|
||||||
|
ConflictException,
|
||||||
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
NotFoundException,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from "@nestjs/common";
|
||||||
import { BadRequestException } from "@nestjs/common";
|
import { BadRequestException } from "@nestjs/common";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
@@ -95,10 +105,12 @@ export class BookingsService_2024_08_13 {
|
|||||||
private readonly organizationsRepository: OrganizationsRepository,
|
private readonly organizationsRepository: OrganizationsRepository,
|
||||||
private readonly teamsRepository: TeamsRepository,
|
private readonly teamsRepository: TeamsRepository,
|
||||||
private readonly teamsEventTypesRepository: TeamsEventTypesRepository,
|
private readonly teamsEventTypesRepository: TeamsEventTypesRepository,
|
||||||
|
private readonly membershipsRepository: MembershipsRepository,
|
||||||
|
private readonly membershipsService: MembershipsService,
|
||||||
private readonly errorsBookingsService: ErrorsBookingsService_2024_08_13
|
private readonly errorsBookingsService: ErrorsBookingsService_2024_08_13
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createBooking(request: Request, body: CreateBookingInput) {
|
async createBooking(request: Request, body: CreateBookingInput, authUser: AuthOptionalUser) {
|
||||||
let bookingTeamEventType = false;
|
let bookingTeamEventType = false;
|
||||||
try {
|
try {
|
||||||
const eventType = await this.getBookedEventType(body);
|
const eventType = await this.getBookedEventType(body);
|
||||||
@@ -108,6 +120,7 @@ export class BookingsService_2024_08_13 {
|
|||||||
if (!eventType) {
|
if (!eventType) {
|
||||||
this.errorsBookingsService.handleEventTypeToBeBookedNotFound(body);
|
this.errorsBookingsService.handleEventTypeToBeBookedNotFound(body);
|
||||||
}
|
}
|
||||||
|
await this.checkBookingRequiresAuthenticationSetting(eventType, authUser);
|
||||||
|
|
||||||
if (eventType.schedulingType === "MANAGED") {
|
if (eventType.schedulingType === "MANAGED") {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -142,6 +155,56 @@ export class BookingsService_2024_08_13 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkBookingRequiresAuthenticationSetting(
|
||||||
|
eventType: EventTypeWithOwnerAndTeam,
|
||||||
|
authUser: AuthOptionalUser
|
||||||
|
) {
|
||||||
|
if (!eventType.bookingRequiresAuthentication) return true;
|
||||||
|
if (!authUser) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
"checkBookingRequiresAuthentication - request must be authenticated by passing credentials belonging to event type owner, host or team or org admin or owner."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authUserId = authUser.id;
|
||||||
|
const authUserRole = authUser.role;
|
||||||
|
const eventTypeId = eventType.id;
|
||||||
|
const teamId = eventType.teamId;
|
||||||
|
const bookingUserId = eventType.owner?.id || null;
|
||||||
|
|
||||||
|
if (authUserRole === "ADMIN") return;
|
||||||
|
|
||||||
|
if (bookingUserId === authUserId) return;
|
||||||
|
|
||||||
|
if (eventTypeId) {
|
||||||
|
const [isUserHost, isUserAssigned] = await Promise.all([
|
||||||
|
this.eventTypesRepository.isUserHostOfEventType(authUserId, eventTypeId),
|
||||||
|
this.eventTypesRepository.isUserAssignedToEventType(authUserId, eventTypeId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isUserHost || isUserAssigned) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId) {
|
||||||
|
const membership = await this.membershipsRepository.getUserAdminOrOwnerTeamMembership(
|
||||||
|
authUserId,
|
||||||
|
teamId
|
||||||
|
);
|
||||||
|
if (membership) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
bookingUserId &&
|
||||||
|
(await this.membershipsService.isUserOrgAdminOrOwnerOfAnotherUser(authUserId, bookingUserId))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ForbiddenException(
|
||||||
|
"checkBookingRequiresAuthentication - user is not authorized to access this event type. User has to be either event type owner, host, team admin or owner or org admin or owner."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getBookedEventType(body: CreateBookingInput) {
|
async getBookedEventType(body: CreateBookingInput) {
|
||||||
if (body.eventTypeId) {
|
if (body.eventTypeId) {
|
||||||
return await this.eventTypesRepository.getEventTypeByIdWithOwnerAndTeam(body.eventTypeId);
|
return await this.eventTypesRepository.getEventTypeByIdWithOwnerAndTeam(body.eventTypeId);
|
||||||
|
|||||||
+5
-1
@@ -498,6 +498,7 @@ describe("Event types Endpoints", () => {
|
|||||||
lightThemeHex: "#fafafa",
|
lightThemeHex: "#fafafa",
|
||||||
},
|
},
|
||||||
customName: `{Event type title} between {Organiser} and {Scheduler}`,
|
customName: `{Event type title} between {Organiser} and {Scheduler}`,
|
||||||
|
bookingRequiresAuthentication: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
@@ -570,6 +571,7 @@ describe("Event types Endpoints", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||||
|
expect(createdEventType.bookingRequiresAuthentication).toEqual(true);
|
||||||
eventType = responseBody.data;
|
eventType = responseBody.data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1026,6 +1028,7 @@ describe("Event types Endpoints", () => {
|
|||||||
lightThemeHex: "#fafafa",
|
lightThemeHex: "#fafafa",
|
||||||
},
|
},
|
||||||
customName: `{Event type title} betweennnnnnnnnnn {Organiser} and {Scheduler}`,
|
customName: `{Event type title} betweennnnnnnnnnn {Organiser} and {Scheduler}`,
|
||||||
|
bookingRequiresAuthentication: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
@@ -1120,6 +1123,8 @@ describe("Event types Endpoints", () => {
|
|||||||
eventType.color = updatedEventType.color;
|
eventType.color = updatedEventType.color;
|
||||||
eventType.bookingFields = updatedEventType.bookingFields;
|
eventType.bookingFields = updatedEventType.bookingFields;
|
||||||
eventType.calVideoSettings = updatedEventType.calVideoSettings;
|
eventType.calVideoSettings = updatedEventType.calVideoSettings;
|
||||||
|
|
||||||
|
expect(updatedEventType.bookingRequiresAuthentication).toEqual(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2028,7 +2033,6 @@ describe("Event types Endpoints", () => {
|
|||||||
const username = name;
|
const username = name;
|
||||||
let user: User;
|
let user: User;
|
||||||
let legacyEventTypeId1: number;
|
let legacyEventTypeId1: number;
|
||||||
let legacyEventTypeId2: number;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
|
|||||||
@@ -132,4 +132,26 @@ export class EventTypesRepository_2024_06_14 {
|
|||||||
async deleteEventType(eventTypeId: number) {
|
async deleteEventType(eventTypeId: number) {
|
||||||
return this.dbWrite.prisma.eventType.delete({ where: { id: eventTypeId } });
|
return this.dbWrite.prisma.eventType.delete({ where: { id: eventTypeId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isUserHostOfEventType(userId: number, eventTypeId: number) {
|
||||||
|
const eventType = await this.dbRead.prisma.eventType.findFirst({
|
||||||
|
where: {
|
||||||
|
id: eventTypeId,
|
||||||
|
hosts: { some: { userId: userId } },
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
return !!eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
async isUserAssignedToEventType(userId: number, eventTypeId: number) {
|
||||||
|
const eventType = await this.dbRead.prisma.eventType.findFirst({
|
||||||
|
where: {
|
||||||
|
id: eventTypeId,
|
||||||
|
users: { some: { id: userId } },
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
return !!eventType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -90,6 +90,7 @@ type Input = Pick<
|
|||||||
| "hideOrganizerEmail"
|
| "hideOrganizerEmail"
|
||||||
| "calVideoSettings"
|
| "calVideoSettings"
|
||||||
| "hidden"
|
| "hidden"
|
||||||
|
| "bookingRequiresAuthentication"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -129,6 +130,7 @@ export class OutputEventTypesService_2024_06_14 {
|
|||||||
hideOrganizerEmail,
|
hideOrganizerEmail,
|
||||||
calVideoSettings,
|
calVideoSettings,
|
||||||
hidden,
|
hidden,
|
||||||
|
bookingRequiresAuthentication,
|
||||||
} = databaseEventType;
|
} = databaseEventType;
|
||||||
|
|
||||||
const locations = this.transformLocations(databaseEventType.locations);
|
const locations = this.transformLocations(databaseEventType.locations);
|
||||||
@@ -206,6 +208,7 @@ export class OutputEventTypesService_2024_06_14 {
|
|||||||
hideOrganizerEmail,
|
hideOrganizerEmail,
|
||||||
calVideoSettings,
|
calVideoSettings,
|
||||||
hidden,
|
hidden,
|
||||||
|
bookingRequiresAuthentication,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -2,6 +2,8 @@ import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.st
|
|||||||
import { ExecutionContext } from "@nestjs/common";
|
import { ExecutionContext } from "@nestjs/common";
|
||||||
import { createParamDecorator } from "@nestjs/common";
|
import { createParamDecorator } from "@nestjs/common";
|
||||||
|
|
||||||
|
export type AuthOptionalUser = ApiAuthGuardUser | null;
|
||||||
|
|
||||||
export const GetOptionalUser = createParamDecorator<
|
export const GetOptionalUser = createParamDecorator<
|
||||||
keyof ApiAuthGuardUser | (keyof ApiAuthGuardUser)[],
|
keyof ApiAuthGuardUser | (keyof ApiAuthGuardUser)[],
|
||||||
ExecutionContext
|
ExecutionContext
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/client";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MembershipsRepository {
|
export class MembershipsRepository {
|
||||||
@@ -90,6 +90,26 @@ export class MembershipsRepository {
|
|||||||
return !!adminMembership;
|
return !!adminMembership;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getOrgIdsWhereUserIsAdminOrOwner(userId: number) {
|
||||||
|
const userOrgAdminMemberships = await this.dbRead.prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
accepted: true,
|
||||||
|
role: {
|
||||||
|
in: ["OWNER", "ADMIN"],
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
teamId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return userOrgAdminMemberships.map((m) => m.teamId);
|
||||||
|
}
|
||||||
|
|
||||||
async createMembership(teamId: number, userId: number, role: MembershipRole, accepted: boolean) {
|
async createMembership(teamId: number, userId: number, role: MembershipRole, accepted: boolean) {
|
||||||
const membership = await this.dbRead.prisma.membership.create({
|
const membership = await this.dbRead.prisma.membership.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -103,4 +123,46 @@ export class MembershipsRepository {
|
|||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserAdminOrOwnerTeamMembership(userId: number, teamId: number) {
|
||||||
|
return this.dbRead.prisma.membership.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
teamId: teamId,
|
||||||
|
accepted: true,
|
||||||
|
role: {
|
||||||
|
in: ["OWNER", "ADMIN"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserMembershipInOneOfOrgs(userId: number, orgIds: number[]) {
|
||||||
|
return this.dbRead.prisma.membership.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
accepted: true,
|
||||||
|
teamId: {
|
||||||
|
in: orgIds,
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserMembershipInOneOfOrgsTeams(userId: number, orgIds: number[]) {
|
||||||
|
return this.dbRead.prisma.membership.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
accepted: true,
|
||||||
|
team: {
|
||||||
|
parentId: {
|
||||||
|
in: orgIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,28 @@ export class MembershipsService {
|
|||||||
"teamId"
|
"teamId"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isUserOrgAdminOrOwnerOfAnotherUser(userId: number, anotherUserId: number) {
|
||||||
|
const orgIdsWhereUserIsAdminOrOwner = await this.membershipsRepository.getOrgIdsWhereUserIsAdminOrOwner(
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (orgIdsWhereUserIsAdminOrOwner.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anotherUserOrgMembership = await this.membershipsRepository.getUserMembershipInOneOfOrgs(
|
||||||
|
anotherUserId,
|
||||||
|
orgIdsWhereUserIsAdminOrOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
if (anotherUserOrgMembership) return true;
|
||||||
|
|
||||||
|
const anotherUserOrgTeamMembership = await this.membershipsRepository.getUserMembershipInOneOfOrgsTeams(
|
||||||
|
anotherUserId,
|
||||||
|
orgIdsWhereUserIsAdminOrOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
return !!anotherUserOrgTeamMembership;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type Input = Pick<
|
|||||||
| "team"
|
| "team"
|
||||||
| "calVideoSettings"
|
| "calVideoSettings"
|
||||||
| "hidden"
|
| "hidden"
|
||||||
|
| "bookingRequiresAuthentication"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
+5
@@ -43,6 +43,8 @@ export class OrganizationsTeamsSchedulesController {
|
|||||||
@DocsTags("Orgs / Teams / Schedules")
|
@DocsTags("Orgs / Teams / Schedules")
|
||||||
@ApiOperation({ summary: "Get all team member schedules" })
|
@ApiOperation({ summary: "Get all team member schedules" })
|
||||||
async getTeamSchedules(
|
async getTeamSchedules(
|
||||||
|
// note(Lauirs): putting orgId so swagger is generacted correctly
|
||||||
|
@Param("orgId", ParseIntPipe) _orgId: number,
|
||||||
@Param("teamId", ParseIntPipe) teamId: number,
|
@Param("teamId", ParseIntPipe) teamId: number,
|
||||||
@Query() queryParams: SkipTakePagination
|
@Query() queryParams: SkipTakePagination
|
||||||
): Promise<GetSchedulesOutput_2024_06_11> {
|
): Promise<GetSchedulesOutput_2024_06_11> {
|
||||||
@@ -65,6 +67,9 @@ export class OrganizationsTeamsSchedulesController {
|
|||||||
summary: "Get schedules of a team member",
|
summary: "Get schedules of a team member",
|
||||||
})
|
})
|
||||||
async getUserSchedules(
|
async getUserSchedules(
|
||||||
|
// note(Lauirs): putting orgId and teamId so swagger is generacted correctly
|
||||||
|
@Param("orgId", ParseIntPipe) _orgId: number,
|
||||||
|
@Param("teamId", ParseIntPipe) _teamId: number,
|
||||||
@Param("userId", ParseIntPipe) userId: number
|
@Param("userId", ParseIntPipe) userId: number
|
||||||
): Promise<GetSchedulesOutput_2024_06_11> {
|
): Promise<GetSchedulesOutput_2024_06_11> {
|
||||||
const schedules = await this.schedulesService.getUserSchedules(userId);
|
const schedules = await this.schedulesService.getUserSchedules(userId);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { VERSION_2024_09_04 } from "@/lib/api-versions";
|
import { VERSION_2024_09_04 } from "@/lib/api-versions";
|
||||||
import { OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER, OPTIONAL_X_CAL_CLIENT_ID_HEADER } from "@/lib/docs/headers";
|
import { OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER, OPTIONAL_X_CAL_CLIENT_ID_HEADER } from "@/lib/docs/headers";
|
||||||
import { GetOptionalUser } from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
import {
|
||||||
|
AuthOptionalUser,
|
||||||
|
GetOptionalUser,
|
||||||
|
} from "@/modules/auth/decorators/get-optional-user/get-optional-user.decorator";
|
||||||
import { OptionalApiAuthGuard } from "@/modules/auth/guards/optional-api-auth/optional-api-auth.guard";
|
import { OptionalApiAuthGuard } from "@/modules/auth/guards/optional-api-auth/optional-api-auth.guard";
|
||||||
import { GetReservedSlotOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-reserved-slot.output";
|
import { GetReservedSlotOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-reserved-slot.output";
|
||||||
import { GetSlotsOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-slots.output";
|
import { GetSlotsOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-slots.output";
|
||||||
@@ -26,7 +29,6 @@ import {
|
|||||||
ApiResponse as DocsResponse,
|
ApiResponse as DocsResponse,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { User } from "@prisma/client";
|
|
||||||
import { plainToClass } from "class-transformer";
|
import { plainToClass } from "class-transformer";
|
||||||
|
|
||||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
@@ -261,7 +263,7 @@ export class SlotsController_2024_09_04 {
|
|||||||
@ApiHeader(OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER)
|
@ApiHeader(OPTIONAL_API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||||
async reserveSlot(
|
async reserveSlot(
|
||||||
@Body() body: ReserveSlotInput_2024_09_04,
|
@Body() body: ReserveSlotInput_2024_09_04,
|
||||||
@GetOptionalUser() user: User
|
@GetOptionalUser() user: AuthOptionalUser
|
||||||
): Promise<ReserveSlotOutputResponse_2024_09_04> {
|
): Promise<ReserveSlotOutputResponse_2024_09_04> {
|
||||||
const reservedSlot = await this.slotsService.reserveSlot(body, user?.id);
|
const reservedSlot = await this.slotsService.reserveSlot(body, user?.id);
|
||||||
|
|
||||||
|
|||||||
@@ -5547,6 +5547,97 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/schedules": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "OrganizationsTeamsSchedulesController_getTeamSchedules",
|
||||||
|
"summary": "Get all team member schedules",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orgId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Maximum number of items to return",
|
||||||
|
"example": 25,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 250,
|
||||||
|
"default": 250,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items to skip",
|
||||||
|
"example": 0,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 0,
|
||||||
|
"default": 0,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetSchedulesOutput_2024_06_11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Orgs / Teams / Schedules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/stripe/connect": {
|
"/v2/organizations/{orgId}/teams/{teamId}/stripe/connect": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "OrganizationsStripeController_getTeamStripeConnectUrl",
|
"operationId": "OrganizationsStripeController_getTeamStripeConnectUrl",
|
||||||
@@ -5720,6 +5811,22 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "orgId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "userId",
|
"name": "userId",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -13215,6 +13322,71 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/teams/{teamId}/schedules": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "TeamsSchedulesController_getTeamSchedules",
|
||||||
|
"summary": "Get all team member schedules",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Maximum number of items to return",
|
||||||
|
"example": 25,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 250,
|
||||||
|
"default": 250,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items to skip",
|
||||||
|
"example": 0,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 0,
|
||||||
|
"default": 0,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetSchedulesOutput_2024_06_11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Teams / Schedules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/teams/{teamId}/verified-resources/emails/verification-code/request": {
|
"/v2/teams/{teamId}/verified-resources/emails/verification-code/request": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "TeamsVerifiedResourcesController_requestEmailVerificationCode",
|
"operationId": "TeamsVerifiedResourcesController_requestEmailVerificationCode",
|
||||||
@@ -16504,6 +16676,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
||||||
@@ -18166,6 +18346,13 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"ownerId": {
|
"ownerId": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"example": 10
|
"example": 10
|
||||||
@@ -18195,6 +18382,8 @@
|
|||||||
"successRedirectUrl",
|
"successRedirectUrl",
|
||||||
"isInstantEvent",
|
"isInstantEvent",
|
||||||
"scheduleId",
|
"scheduleId",
|
||||||
|
"hidden",
|
||||||
|
"bookingRequiresAuthentication",
|
||||||
"ownerId",
|
"ownerId",
|
||||||
"users"
|
"users"
|
||||||
]
|
]
|
||||||
@@ -18515,6 +18704,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
||||||
@@ -20343,6 +20540,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"schedulingType": {
|
"schedulingType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -20777,6 +20982,13 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"teamId": {
|
"teamId": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -21223,6 +21435,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"hosts": {
|
"hosts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -22589,6 +22809,11 @@
|
|||||||
},
|
},
|
||||||
"unit": {
|
"unit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"enum": [
|
||||||
|
"hour",
|
||||||
|
"minute",
|
||||||
|
"day"
|
||||||
|
],
|
||||||
"description": "Unit for the offset time",
|
"description": "Unit for the offset time",
|
||||||
"example": "hour"
|
"example": "hour"
|
||||||
}
|
}
|
||||||
@@ -22612,6 +22837,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "beforeEvent",
|
"default": "beforeEvent",
|
||||||
|
"enum": [
|
||||||
|
"beforeEvent"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "beforeEvent"
|
"example": "beforeEvent"
|
||||||
}
|
}
|
||||||
@@ -22635,6 +22863,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterEvent",
|
"default": "afterEvent",
|
||||||
|
"enum": [
|
||||||
|
"afterEvent"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterEvent"
|
"example": "afterEvent"
|
||||||
}
|
}
|
||||||
@@ -22650,6 +22881,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "eventCancelled",
|
"default": "eventCancelled",
|
||||||
|
"enum": [
|
||||||
|
"eventCancelled"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22663,6 +22897,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "newEvent",
|
"default": "newEvent",
|
||||||
|
"enum": [
|
||||||
|
"newEvent"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22676,6 +22913,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "rescheduleEvent",
|
"default": "rescheduleEvent",
|
||||||
|
"enum": [
|
||||||
|
"rescheduleEvent"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22697,6 +22937,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterGuestsCalVideoNoShow",
|
"default": "afterGuestsCalVideoNoShow",
|
||||||
|
"enum": [
|
||||||
|
"afterGuestsCalVideoNoShow"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterGuestsCalVideoNoShow"
|
"example": "afterGuestsCalVideoNoShow"
|
||||||
}
|
}
|
||||||
@@ -22720,6 +22963,9 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterHostsCalVideoNoShow",
|
"default": "afterHostsCalVideoNoShow",
|
||||||
|
"enum": [
|
||||||
|
"afterHostsCalVideoNoShow"
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterHostsCalVideoNoShow"
|
"example": "afterHostsCalVideoNoShow"
|
||||||
}
|
}
|
||||||
@@ -23314,6 +23560,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"enum": [
|
||||||
|
[
|
||||||
|
"beforeEvent",
|
||||||
|
"eventCancelled",
|
||||||
|
"newEvent",
|
||||||
|
"afterEvent",
|
||||||
|
"rescheduleEvent",
|
||||||
|
"afterHostsCalVideoNoShow",
|
||||||
|
"afterGuestsCalVideoNoShow"
|
||||||
|
]
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
|
||||||
|
|
||||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
|
||||||
import { Button } from "@calcom/ui/components/button";
|
|
||||||
|
|
||||||
export const generateMetadata = () =>
|
|
||||||
_generateMetadata(
|
|
||||||
(t) => `${t("access_denied")} | ${APP_NAME}`,
|
|
||||||
() => "",
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
"/403"
|
|
||||||
);
|
|
||||||
|
|
||||||
async function Error403() {
|
|
||||||
const t = await getTranslate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-subtle flex h-screen">
|
|
||||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
|
||||||
<h1 className="font-cal text-emphasis text-6xl">403</h1>
|
|
||||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">{t("dont_have_access_this_page")}</h2>
|
|
||||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
|
|
||||||
{t("you_need_admin_or_owner_privileges_to_access")}
|
|
||||||
</p>
|
|
||||||
<Button href={WEBAPP_URL}>{t("go_back_home")}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Error403;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
||||||
import { Button } from "@calcom/ui/components/button";
|
|
||||||
import { showToast } from "@calcom/ui/components/toast";
|
|
||||||
|
|
||||||
export default function CopyButton({ error }: { error: string }) {
|
|
||||||
const { t } = useLocale();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
className="mt-2 border-0 font-sans font-normal hover:bg-gray-300"
|
|
||||||
StartIcon="copy"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(error);
|
|
||||||
showToast("Link copied!", "success");
|
|
||||||
}}>
|
|
||||||
{t("copy")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { _generateMetadata, getTranslate } from "app/_utils";
|
|
||||||
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
|
||||||
import { Button } from "@calcom/ui/components/button";
|
|
||||||
|
|
||||||
import CopyButton from "./copy-button";
|
|
||||||
|
|
||||||
export const generateMetadata = () =>
|
|
||||||
_generateMetadata(
|
|
||||||
(t) => `${t("something_unexpected_occurred")} | ${APP_NAME}`,
|
|
||||||
() => "",
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
"/500"
|
|
||||||
);
|
|
||||||
|
|
||||||
async function Error500({ searchParams }: { searchParams: { error?: string } }) {
|
|
||||||
const t = await getTranslate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-subtle flex h-screen">
|
|
||||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
|
||||||
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
|
||||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">{t("500_error_message")}</h2>
|
|
||||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">{t("something_went_wrong_on_our_end")}</p>
|
|
||||||
{searchParams?.error && (
|
|
||||||
<div className="mb-8 flex flex-col">
|
|
||||||
<p className="text-default mb-4 max-w-2xl text-sm">
|
|
||||||
{t("please_provide_following_text_to_suppport")}:
|
|
||||||
</p>
|
|
||||||
<pre className="bg-emphasis text-emphasis w-full max-w-2xl whitespace-normal break-words rounded-md p-4">
|
|
||||||
{searchParams.error}
|
|
||||||
<br />
|
|
||||||
<CopyButton error={searchParams.error} />
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button href="mailto:support@cal.com">{t("contact_support")}</Button>
|
|
||||||
<Button color="secondary" href="javascript:history.back()" className="ml-2">
|
|
||||||
{t("go_back")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Error500;
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { TrpcProvider } from "app/_trpc/trpc-provider";
|
import { TrpcProvider } from "app/_trpc/trpc-provider";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import CacheProvider from "react-inlinesvg/provider";
|
import CacheProvider from "react-inlinesvg/provider";
|
||||||
|
|
||||||
import { WebPushProvider } from "@calcom/features/notifications/WebPushContext";
|
import { WebPushProvider } from "@calcom/features/notifications/WebPushContext";
|
||||||
@@ -17,11 +18,13 @@ type ProvidersProps = {
|
|||||||
};
|
};
|
||||||
export function Providers({ isEmbed, children, nonce }: ProvidersProps) {
|
export function Providers({ isEmbed, children, nonce }: ProvidersProps) {
|
||||||
const isBookingPage = useIsBookingPage();
|
const isBookingPage = useIsBookingPage();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isOnboardingPage = pathname?.startsWith("/getting-started");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<TrpcProvider>
|
<TrpcProvider>
|
||||||
{!isBookingPage ? <PlainChat nonce={nonce} /> : null}
|
{!isBookingPage && !isOnboardingPage ? <PlainChat nonce={nonce} /> : null}
|
||||||
{!isEmbed && !isBookingPage && <NotificationSoundHandler />}
|
{!isEmbed && !isBookingPage && <NotificationSoundHandler />}
|
||||||
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
|
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
|
||||||
<CacheProvider>
|
<CacheProvider>
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export const ReassignDialog = ({
|
|||||||
placeholder={t("search")}
|
placeholder={t("search")}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="scroll-bar flex h-[150px] flex-col gap-0.5 overflow-y-scroll rounded-md border p-1">
|
<div className="scroll-bar mt-2 flex h-[150px] flex-col gap-0.5 overflow-y-scroll rounded-md border p-1">
|
||||||
{teamMemberOptions.map((member) => (
|
{teamMemberOptions.map((member) => (
|
||||||
<label
|
<label
|
||||||
key={member.value}
|
key={member.value}
|
||||||
|
|||||||
@@ -539,18 +539,11 @@ const NewRoutingManager = ({
|
|||||||
url: chosenEventUrls.eventBookingAbsoluteUrl,
|
url: chosenEventUrls.eventBookingAbsoluteUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("SETTING NEW VALUE", {
|
|
||||||
type: "reschedule_to_different_event_new_tab",
|
|
||||||
reschedulerWindow,
|
|
||||||
newBooking: null,
|
|
||||||
});
|
|
||||||
reroutingState.setValue({
|
reroutingState.setValue({
|
||||||
type: "reschedule_to_different_event_new_tab",
|
type: "reschedule_to_different_event_new_tab",
|
||||||
reschedulerWindow,
|
reschedulerWindow,
|
||||||
newBooking: null,
|
newBooking: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("VALUE NOW", JSON.stringify(reroutingState));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reroutingPreview() {
|
function reroutingPreview() {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
|
|||||||
{t("this_app_requires_connected_account", {
|
{t("this_app_requires_connected_account", {
|
||||||
appName: title,
|
appName: title,
|
||||||
dependencyName: dependency.name,
|
dependencyName: dependency.name,
|
||||||
|
interpolation: { escapeValue: false },
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
|||||||
loading={isPageLoading}
|
loading={isPageLoading}
|
||||||
onClick={() => nextStep()}
|
onClick={() => nextStep()}
|
||||||
disabled={disabledNextButton}>
|
disabled={disabledNextButton}>
|
||||||
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
|
{firstCalendar ? `${t("connect_your_video")}` : `${t("connect_calendar_first")}`}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
|||||||
disabled={!hasAnyInstalledVideoApps}
|
disabled={!hasAnyInstalledVideoApps}
|
||||||
loading={isPageLoading}
|
loading={isPageLoading}
|
||||||
onClick={() => nextStep()}>
|
onClick={() => nextStep()}>
|
||||||
{t("next_step_text")}
|
{t("set_availability")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
|
|||||||
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
|
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
|
||||||
loading={availabilityForm.formState.isSubmitting}
|
loading={availabilityForm.formState.isSubmitting}
|
||||||
disabled={availabilityForm.formState.isSubmitting}>
|
disabled={availabilityForm.formState.isSubmitting}>
|
||||||
{t("next_step_text")}
|
{t("complete_profile")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const UserProfile = ({ user }: UserProfileProps) => {
|
|||||||
EndIcon="arrow-right"
|
EndIcon="arrow-right"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="mt-8 w-full items-center justify-center">
|
className="mt-8 w-full items-center justify-center">
|
||||||
{t("finish")}
|
{t("finish_and_start")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ const UserSettings = (props: IUserSettingsProps) => {
|
|||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
|
placeholder="John Doe"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
/>
|
/>
|
||||||
@@ -121,7 +122,7 @@ const UserSettings = (props: IUserSettingsProps) => {
|
|||||||
className="mt-8 flex w-full flex-row justify-center"
|
className="mt-8 flex w-full flex-row justify-center"
|
||||||
loading={mutation.isPending}
|
loading={mutation.isPending}
|
||||||
disabled={mutation.isPending}>
|
disabled={mutation.isPending}>
|
||||||
{t("next_step_text")}
|
{t("connect_your_calendar")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export function NotificationSoundHandler() {
|
|||||||
const response = await fetch("/ring.mp3");
|
const response = await fetch("/ring.mp3");
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
audioBufferRef.current = await audioContextRef.current.decodeAudioData(arrayBuffer);
|
audioBufferRef.current = await audioContextRef.current.decodeAudioData(arrayBuffer);
|
||||||
console.log("Audio file loaded and decoded");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -62,7 +61,6 @@ export function NotificationSoundHandler() {
|
|||||||
|
|
||||||
source.loop = true;
|
source.loop = true;
|
||||||
source.start(0);
|
source.start(0);
|
||||||
console.log("Sound started playing");
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (sourceRef.current === source) {
|
if (sourceRef.current === source) {
|
||||||
@@ -101,7 +99,7 @@ export function NotificationSoundHandler() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!("serviceWorker" in navigator)) {
|
if (!("serviceWorker" in navigator)) {
|
||||||
console.log("ServiceWorker not available");
|
console.warn("ServiceWorker not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
|
|||||||
<TextField
|
<TextField
|
||||||
ref={usernameRef}
|
ref={usernameRef}
|
||||||
name="username"
|
name="username"
|
||||||
|
placeholder="john"
|
||||||
value={inputUsernameValue}
|
value={inputUsernameValue}
|
||||||
autoComplete="none"
|
autoComplete="none"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import { APP_NAME } from "@calcom/lib/constants";
|
|||||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions";
|
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions";
|
||||||
import { Icon } from "@calcom/ui/components/icon";
|
|
||||||
import { Avatar } from "@calcom/ui/components/avatar";
|
import { Avatar } from "@calcom/ui/components/avatar";
|
||||||
import { Button } from "@calcom/ui/components/button";
|
import { Button } from "@calcom/ui/components/button";
|
||||||
|
import { Icon } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { hasPermission } from "../../../../../packages/platform/utils/permissions";
|
import { hasPermission } from "../../../../../packages/platform/utils/permissions";
|
||||||
|
|
||||||
@@ -32,8 +32,6 @@ export default function Authorize() {
|
|||||||
permissions: 7,
|
permissions: 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("These are the search params:", queryString);
|
|
||||||
|
|
||||||
const permissions = Object.values(PERMISSIONS_GROUPED_MAP).map((value) => {
|
const permissions = Object.values(PERMISSIONS_GROUPED_MAP).map((value) => {
|
||||||
let permissionsMessage = "";
|
let permissionsMessage = "";
|
||||||
const hasReadPermission = hasPermission(client.permissions, value.read);
|
const hasReadPermission = hasPermission(client.permissions, value.read);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const getStepsAndHeadersForUser = (t: TFunction) => {
|
|||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
title: t("welcome_to_cal_header", { appName: APP_NAME }),
|
title: t("welcome_to_cal_header", { appName: APP_NAME }),
|
||||||
subtitle: [t("we_just_need_basic_info"), t("edit_form_later_subtitle")],
|
subtitle: [t("we_just_need_basic_info")],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("connect_your_calendar"),
|
title: t("connect_your_calendar"),
|
||||||
@@ -57,8 +57,9 @@ const getStepsAndHeadersForUser = (t: TFunction) => {
|
|||||||
{
|
{
|
||||||
title: t("set_availability"),
|
title: t("set_availability"),
|
||||||
subtitle: [
|
subtitle: [
|
||||||
t("set_availability_getting_started_subtitle_1"),
|
`${t("set_availability_getting_started_subtitle_1")} ${t(
|
||||||
t("set_availability_getting_started_subtitle_2"),
|
"set_availability_getting_started_subtitle_2"
|
||||||
|
)}`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ export default function Signup({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
|
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||||
localStorage.setItem("onBoardingRedirect", redirectUrl);
|
localStorage.setItem("onBoardingRedirect", redirectUrl);
|
||||||
}
|
}
|
||||||
}, [redirectUrl]);
|
}, [redirectUrl]);
|
||||||
@@ -216,7 +217,6 @@ export default function Signup({
|
|||||||
if (err.checkoutSessionId) {
|
if (err.checkoutSessionId) {
|
||||||
const stripe = await getStripe();
|
const stripe = await getStripe();
|
||||||
if (stripe) {
|
if (stripe) {
|
||||||
console.log("Redirecting to stripe checkout");
|
|
||||||
const { error } = await stripe.redirectToCheckout({
|
const { error } = await stripe.redirectToCheckout({
|
||||||
sessionId: err.checkoutSessionId,
|
sessionId: err.checkoutSessionId,
|
||||||
});
|
});
|
||||||
@@ -470,6 +470,7 @@ export default function Signup({
|
|||||||
showToast("error", t("username_required"));
|
showToast("error", t("username_required"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||||
localStorage.setItem("username", username);
|
localStorage.setItem("username", username);
|
||||||
const sp = new URLSearchParams();
|
const sp = new URLSearchParams();
|
||||||
// @NOTE: don't remove username query param as it's required right now for stripe payment page
|
// @NOTE: don't remove username query param as it's required right now for stripe payment page
|
||||||
@@ -539,6 +540,7 @@ export default function Signup({
|
|||||||
if (prepopulateFormValues?.username) {
|
if (prepopulateFormValues?.username) {
|
||||||
// If username is present we save it in query params to check for premium
|
// If username is present we save it in query params to check for premium
|
||||||
searchQueryParams.set("username", prepopulateFormValues.username);
|
searchQueryParams.set("username", prepopulateFormValues.username);
|
||||||
|
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
|
||||||
localStorage.setItem("username", prepopulateFormValues.username);
|
localStorage.setItem("username", prepopulateFormValues.username);
|
||||||
}
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|||||||
+11
-1
@@ -196,6 +196,8 @@ const nextConfig = (phase) => {
|
|||||||
experimental: {
|
experimental: {
|
||||||
// externalize server-side node_modules with size > 1mb, to improve dev mode performance/RAM usage
|
// externalize server-side node_modules with size > 1mb, to improve dev mode performance/RAM usage
|
||||||
optimizePackageImports: ["@calcom/ui"],
|
optimizePackageImports: ["@calcom/ui"],
|
||||||
|
webpackMemoryOptimizations: true,
|
||||||
|
webpackBuildWorker: true,
|
||||||
},
|
},
|
||||||
productionBrowserSourceMaps: true,
|
productionBrowserSourceMaps: true,
|
||||||
/* We already do type check on GH actions */
|
/* We already do type check on GH actions */
|
||||||
@@ -231,7 +233,15 @@ const nextConfig = (phase) => {
|
|||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
webpack: (config, { webpack, buildId, isServer }) => {
|
webpack: (config, { webpack, buildId, isServer, dev }) => {
|
||||||
|
if (!dev) {
|
||||||
|
if (config.cache) {
|
||||||
|
config.cache = Object.freeze({
|
||||||
|
type: "memory",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isServer) {
|
if (isServer) {
|
||||||
// Module not found fix @see https://github.com/boxyhq/jackson/issues/1535#issuecomment-1704381612
|
// Module not found fix @see https://github.com/boxyhq/jackson/issues/1535#issuecomment-1704381612
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.6.4",
|
"version": "5.6.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
import { createTeamEventType } from "./fixtures/users";
|
||||||
|
import { test } from "./lib/fixtures";
|
||||||
|
|
||||||
|
test.describe("Duplicate API Calls Prevention", () => {
|
||||||
|
test.afterEach(({ users }) => users.deleteAll());
|
||||||
|
|
||||||
|
test("should detect when schedule endpoints are called multiple times for individual user events", async ({
|
||||||
|
page,
|
||||||
|
users,
|
||||||
|
}) => {
|
||||||
|
const user = await users.create();
|
||||||
|
const eventType = user.eventTypes.find((e) => e.slug === "30-min");
|
||||||
|
|
||||||
|
const trpcCalls: string[] = [];
|
||||||
|
const apiV2Calls: string[] = [];
|
||||||
|
|
||||||
|
// Intercept tRPC getSchedule calls - pattern matches trpc.viewer.slots.getSchedule.useQuery()
|
||||||
|
await page.route("**/api/trpc/viewer.slots.getSchedule**", async (route) => {
|
||||||
|
trpcCalls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route("**/api/v2/slots/available**", async (route) => {
|
||||||
|
apiV2Calls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(`/${user.username}/${eventType?.slug}`);
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
|
||||||
|
const totalCalls = trpcCalls.length + apiV2Calls.length;
|
||||||
|
|
||||||
|
if (totalCalls === 0) {
|
||||||
|
console.log("No API calls detected - environment may have issues preventing page load");
|
||||||
|
expect(totalCalls).toBeGreaterThanOrEqual(0);
|
||||||
|
} else {
|
||||||
|
expect(totalCalls).toBeLessThanOrEqual(1);
|
||||||
|
expect(trpcCalls.length).toBeLessThanOrEqual(1);
|
||||||
|
expect(apiV2Calls.length).toBeLessThanOrEqual(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should detect when schedule endpoints are called multiple times for team events", async ({
|
||||||
|
page,
|
||||||
|
users,
|
||||||
|
}) => {
|
||||||
|
const teamOwner = await users.create(
|
||||||
|
{ name: "Team Owner" },
|
||||||
|
{
|
||||||
|
hasTeam: true,
|
||||||
|
teammates: [{ name: "teammate-1" }],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { team } = await teamOwner.getFirstTeamMembership();
|
||||||
|
const teamEvent = await createTeamEventType(
|
||||||
|
{ id: teamOwner.id },
|
||||||
|
{ id: team.id },
|
||||||
|
{ teamEventSlug: "team-event-test", teamEventTitle: "Team Event Test" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const trpcCalls: string[] = [];
|
||||||
|
const apiV2Calls: string[] = [];
|
||||||
|
|
||||||
|
await page.route("**/api/trpc/viewer.slots.getSchedule**", async (route) => {
|
||||||
|
trpcCalls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route("**/api/v2/slots/available**", async (route) => {
|
||||||
|
apiV2Calls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(`/team/${team.slug}/${teamEvent.slug}`);
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
|
||||||
|
const totalCalls = trpcCalls.length + apiV2Calls.length;
|
||||||
|
|
||||||
|
if (totalCalls === 0) {
|
||||||
|
console.log("No API calls detected - environment may have issues preventing page load");
|
||||||
|
expect(totalCalls).toBeGreaterThanOrEqual(0);
|
||||||
|
} else {
|
||||||
|
expect(totalCalls).toBeLessThanOrEqual(1);
|
||||||
|
expect(trpcCalls.length).toBeLessThanOrEqual(1);
|
||||||
|
expect(apiV2Calls.length).toBeLessThanOrEqual(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should detect when schedule endpoints are called multiple times for organization team events", async ({
|
||||||
|
page,
|
||||||
|
users,
|
||||||
|
}) => {
|
||||||
|
const orgOwner = await users.create(
|
||||||
|
{ name: "Org Owner" },
|
||||||
|
{
|
||||||
|
hasTeam: true,
|
||||||
|
teammates: [{ name: "teammate-1" }],
|
||||||
|
isOrg: true,
|
||||||
|
isOrgVerified: true,
|
||||||
|
hasSubteam: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { team } = await orgOwner.getFirstTeamMembership();
|
||||||
|
const { team: org } = await orgOwner.getOrgMembership();
|
||||||
|
const teamEvent = await createTeamEventType(
|
||||||
|
{ id: orgOwner.id },
|
||||||
|
{ id: team.id },
|
||||||
|
{ teamEventSlug: "org-team-event", teamEventTitle: "Org Team Event" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const trpcCalls: string[] = [];
|
||||||
|
const apiV2Calls: string[] = [];
|
||||||
|
|
||||||
|
await page.route("**/api/trpc/viewer.slots.getSchedule**", async (route) => {
|
||||||
|
trpcCalls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route("**/api/v2/slots/available**", async (route) => {
|
||||||
|
apiV2Calls.push(route.request().url());
|
||||||
|
await route.continue();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(`/org/${org.slug}/${team.slug}/${teamEvent.slug}`);
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
|
||||||
|
const totalCalls = trpcCalls.length + apiV2Calls.length;
|
||||||
|
|
||||||
|
if (totalCalls === 0) {
|
||||||
|
console.log("No API calls detected - environment may have issues preventing page load");
|
||||||
|
expect(totalCalls).toBeGreaterThanOrEqual(0);
|
||||||
|
} else {
|
||||||
|
expect(totalCalls).toBeLessThanOrEqual(1);
|
||||||
|
expect(trpcCalls.length).toBeLessThanOrEqual(1);
|
||||||
|
expect(apiV2Calls.length).toBeLessThanOrEqual(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -109,10 +109,7 @@ test("multiple duration selection updates event length correctly", async ({ page
|
|||||||
await page.waitForSelector('[data-testid="event-types"]');
|
await page.waitForSelector('[data-testid="event-types"]');
|
||||||
await page.click(`text=Multiple duration`);
|
await page.click(`text=Multiple duration`);
|
||||||
await page.waitForSelector('[data-testid="event-title"]');
|
await page.waitForSelector('[data-testid="event-title"]');
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||||
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
await page.fill('[name="eventName"]', "{Event duration} event btwn {Organiser} {Scheduler}");
|
||||||
await page.locator('[data-testid="update-eventtype"]').click();
|
await page.locator('[data-testid="update-eventtype"]').click();
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ test.describe("Event Types tests", () => {
|
|||||||
|
|
||||||
// fix the race condition
|
// fix the race condition
|
||||||
await page.waitForSelector('[data-testid="event-title"]');
|
await page.waitForSelector('[data-testid="event-title"]');
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.click("[data-testid=vertical-tab-recurring]");
|
await page.click("[data-testid=vertical-tab-recurring]");
|
||||||
await expect(page.locator("[data-testid=recurring-event-collapsible]")).toBeHidden();
|
await expect(page.locator("[data-testid=recurring-event-collapsible]")).toBeHidden();
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export function createAppsFixture(page: Page) {
|
|||||||
},
|
},
|
||||||
verifyAppsInfoNew: async (app: string, eventTypeId: number) => {
|
verifyAppsInfoNew: async (app: string, eventTypeId: number) => {
|
||||||
await page.goto(`event-types/${eventTypeId}?tabName=apps`);
|
await page.goto(`event-types/${eventTypeId}?tabName=apps`);
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toContainText("Event Setup"); // fix the race condition
|
await expect(page.getByTestId("vertical-tab-basics")).toContainText("Basics"); // fix the race condition
|
||||||
await expect(page.locator(`[data-testid='${app}-app-switch'][data-state="checked"]`)).toBeVisible();
|
await expect(page.locator(`[data-testid='${app}-app-switch'][data-state="checked"]`)).toBeVisible();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ test.describe("private links creation and usage", () => {
|
|||||||
|
|
||||||
// Ensure that private URL is enabled after modifying the event type.
|
// Ensure that private URL is enabled after modifying the event type.
|
||||||
// Additionally, if the slug is changed, ensure that the private URL is updated accordingly.
|
// Additionally, if the slug is changed, ensure that the private URL is updated accordingly.
|
||||||
await page.getByTestId("vertical-tab-event_setup_tab_title").click();
|
await page.getByTestId("vertical-tab-basics").click();
|
||||||
await page.locator("[data-testid=event-title]").first().fill("somethingrandom");
|
await page.locator("[data-testid=event-title]").first().fill("somethingrandom");
|
||||||
await page.locator("[data-testid=event-slug]").first().fill("somethingrandom");
|
await page.locator("[data-testid=event-slug]").first().fill("somethingrandom");
|
||||||
await expect(page.locator('[data-testid="event-slug"]').first()).toHaveValue("somethingrandom");
|
await expect(page.locator('[data-testid="event-slug"]').first()).toHaveValue("somethingrandom");
|
||||||
|
|||||||
@@ -222,10 +222,7 @@ export const createNewSeatedEventType = async (page: Page, args: { eventTitle: s
|
|||||||
const eventTitle = args.eventTitle;
|
const eventTitle = args.eventTitle;
|
||||||
await createNewUserEventType(page, { eventTitle });
|
await createNewUserEventType(page, { eventTitle });
|
||||||
await page.waitForSelector('[data-testid="event-title"]');
|
await page.waitForSelector('[data-testid="event-title"]');
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
await page.locator('[data-testid="vertical-tab-event_advanced_tab_title"]').click();
|
await page.locator('[data-testid="vertical-tab-event_advanced_tab_title"]').click();
|
||||||
await page.locator('[data-testid="offer-seats-toggle"]').click();
|
await page.locator('[data-testid="offer-seats-toggle"]').click();
|
||||||
await page.locator('[data-testid="update-eventtype"]').click();
|
await page.locator('[data-testid="update-eventtype"]').click();
|
||||||
|
|||||||
@@ -70,10 +70,7 @@ test.describe("Manage Booking Questions", () => {
|
|||||||
const firstEventTypeElement = $eventTypes.first();
|
const firstEventTypeElement = $eventTypes.first();
|
||||||
|
|
||||||
await firstEventTypeElement.click();
|
await firstEventTypeElement.click();
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,10 +114,7 @@ test.describe("Manage Booking Questions", () => {
|
|||||||
const firstEventTypeElement = $eventTypes.first();
|
const firstEventTypeElement = $eventTypes.first();
|
||||||
|
|
||||||
await firstEventTypeElement.click();
|
await firstEventTypeElement.click();
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step("Open the 'Name' field dialog", async () => {
|
await test.step("Open the 'Name' field dialog", async () => {
|
||||||
@@ -642,8 +636,8 @@ async function toggleQuestionRequireStatusAndSave({
|
|||||||
await page.locator(`[data-testid="field-${name}"]`).locator('[data-testid="edit-field-action"]').click();
|
await page.locator(`[data-testid="field-${name}"]`).locator('[data-testid="edit-field-action"]').click();
|
||||||
await page
|
await page
|
||||||
.locator('[data-testid="edit-field-dialog"]')
|
.locator('[data-testid="edit-field-dialog"]')
|
||||||
.locator('[data-testid="field-required"] button')
|
.locator('[data-testid="field-required"]')
|
||||||
.locator(`text=${required ? "Yes" : "No"}`)
|
.first()
|
||||||
.click();
|
.click();
|
||||||
await page.locator('[data-testid="field-add-save"]').click();
|
await page.locator('[data-testid="field-add-save"]').click();
|
||||||
await saveEventType(page);
|
await saveEventType(page);
|
||||||
@@ -754,11 +748,8 @@ test.describe("Text area min and max characters text", () => {
|
|||||||
await page.waitForSelector('[data-testid="event-types"]');
|
await page.waitForSelector('[data-testid="event-types"]');
|
||||||
await createNewUserEventType(page, { eventTitle });
|
await createNewUserEventType(page, { eventTitle });
|
||||||
await page.waitForSelector('[data-testid="event-title"]');
|
await page.waitForSelector('[data-testid="event-title"]');
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toContainText("Event Setup"); //fix the race condition
|
await expect(page.getByTestId("vertical-tab-basics")).toContainText("Basics"); //fix the race condition
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page");
|
||||||
"aria-current",
|
|
||||||
"page"
|
|
||||||
);
|
|
||||||
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
await page.getByTestId("vertical-tab-event_advanced_tab_title").click();
|
||||||
const insertQuestion = async (questionName: string) => {
|
const insertQuestion = async (questionName: string) => {
|
||||||
const element = page.locator('[data-testid="add-field"]');
|
const element = page.locator('[data-testid="add-field"]');
|
||||||
|
|||||||
@@ -79,11 +79,8 @@ test.describe("Managed Event Types", () => {
|
|||||||
const { adminUser, managedEvent } = await setupManagedEvent({ users });
|
const { adminUser, managedEvent } = await setupManagedEvent({ users });
|
||||||
await adminUser.apiLogin();
|
await adminUser.apiLogin();
|
||||||
await page.goto(`/event-types/${managedEvent.id}?tabName=setup`);
|
await page.goto(`/event-types/${managedEvent.id}?tabName=setup`);
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toHaveAttribute(
|
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page"); // fix the race condition
|
||||||
"aria-current",
|
await expect(page.getByTestId("vertical-tab-basics")).toContainText("Basics"); //fix the race condition
|
||||||
"page"
|
|
||||||
); // fix the race condition
|
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toContainText("Event Setup"); //fix the race condition
|
|
||||||
await page.locator("#location-select").click();
|
await page.locator("#location-select").click();
|
||||||
const optionText = await getByKey(page, "organizer_default_conferencing_app");
|
const optionText = await getByKey(page, "organizer_default_conferencing_app");
|
||||||
await expect(optionText).toBeVisible();
|
await expect(optionText).toBeVisible();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ test.describe("Onboarding", () => {
|
|||||||
// tests whether the user makes it to /getting-started
|
// tests whether the user makes it to /getting-started
|
||||||
// after login with completedOnboarding false
|
// after login with completedOnboarding false
|
||||||
await page.waitForURL("/getting-started");
|
await page.waitForURL("/getting-started");
|
||||||
await expect(page.locator('text="Next Step"')).toBeVisible(); // Fix race condition
|
await expect(page.locator('text="Connect your calendar"')).toBeVisible(); // Fix race condition
|
||||||
|
|
||||||
await test.step("step 1 - User Settings", async () => {
|
await test.step("step 1 - User Settings", async () => {
|
||||||
// Check required fields
|
// Check required fields
|
||||||
|
|||||||
@@ -780,8 +780,8 @@ const markPhoneNumberAsRequiredAndEmailAsOptional = async (page: Page, eventId:
|
|||||||
|
|
||||||
// Make email as not required
|
// Make email as not required
|
||||||
await page.locator('[data-testid="field-email"] [data-testid="edit-field-action"]').click();
|
await page.locator('[data-testid="field-email"] [data-testid="edit-field-action"]').click();
|
||||||
const emailRequiredFiled = await page.locator('[data-testid="field-required"]');
|
const emailRequiredFiled = await page.locator('[data-testid="field-required"]').first();
|
||||||
await emailRequiredFiled.locator("> :nth-child(2)").click();
|
await emailRequiredFiled.click();
|
||||||
await page.getByTestId("field-add-save").click();
|
await page.getByTestId("field-add-save").click();
|
||||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
@@ -790,12 +790,12 @@ const markPhoneNumberAsRequiredAndEmailAsOptional = async (page: Page, eventId:
|
|||||||
|
|
||||||
const markPhoneNumberAsRequiredField = async (page: Page, eventId: number) => {
|
const markPhoneNumberAsRequiredField = async (page: Page, eventId: number) => {
|
||||||
await page.goto(`/event-types/${eventId}?tabName=advanced`);
|
await page.goto(`/event-types/${eventId}?tabName=advanced`);
|
||||||
await expect(page.getByTestId("vertical-tab-event_setup_tab_title")).toContainText("Event Setup"); // fix the race condition
|
await expect(page.getByTestId("vertical-tab-basics")).toContainText("Basics"); // fix the race condition
|
||||||
|
|
||||||
await page.locator('[data-testid="field-attendeePhoneNumber"] [data-testid="toggle-field"]').click();
|
await page.locator('[data-testid="field-attendeePhoneNumber"] [data-testid="toggle-field"]').click();
|
||||||
await page.locator('[data-testid="field-attendeePhoneNumber"] [data-testid="edit-field-action"]').click();
|
await page.locator('[data-testid="field-attendeePhoneNumber"] [data-testid="edit-field-action"]').click();
|
||||||
const phoneRequiredFiled = await page.locator('[data-testid="field-required"]');
|
const phoneRequiredFiled = await page.locator('[data-testid="field-required"]').first();
|
||||||
await phoneRequiredFiled.locator("> :nth-child(1)").click();
|
await phoneRequiredFiled.click();
|
||||||
await page.getByTestId("field-add-save").click();
|
await page.getByTestId("field-add-save").click();
|
||||||
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
|
||||||
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
action: () => page.locator("[data-testid=update-eventtype]").click(),
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "يمكنك ربط رزنامتك من <1>هنا</1>.",
|
"connect_your_calendar_and_link": "يمكنك ربط رزنامتك من <1>هنا</1>.",
|
||||||
"default_calendar_selected": "الرزنامة الافتراضية",
|
"default_calendar_selected": "الرزنامة الافتراضية",
|
||||||
"hide_from_profile": "إخفاء من الملف الشخصي",
|
"hide_from_profile": "إخفاء من الملف الشخصي",
|
||||||
"event_setup_tab_title": "إعداد الحدث",
|
"basics": "إعداد الحدث",
|
||||||
"availability_not_found_in_schedule_error": "لا يوجد مواعيد متاحة",
|
"availability_not_found_in_schedule_error": "لا يوجد مواعيد متاحة",
|
||||||
"event_limit_tab_title": "الحدود",
|
"event_limit_tab_title": "الحدود",
|
||||||
"event_limit_tab_description": "ما تواتر مرات الحجز",
|
"event_limit_tab_description": "ما تواتر مرات الحجز",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Təqviminizi <1>buradan</1> əlaqələndirə bilərsiniz.",
|
"connect_your_calendar_and_link": "Təqviminizi <1>buradan</1> əlaqələndirə bilərsiniz.",
|
||||||
"default_calendar_selected": "Varsayılan təqvim seçildi",
|
"default_calendar_selected": "Varsayılan təqvim seçildi",
|
||||||
"hide_from_profile": "Profildən gizlət",
|
"hide_from_profile": "Profildən gizlət",
|
||||||
"event_setup_tab_title": "Tədbir Quraşdırması",
|
"basics": "Tədbir Quraşdırması",
|
||||||
"availability_not_found_in_schedule_error": "Cədvəldə mövcudluq tapılmadı",
|
"availability_not_found_in_schedule_error": "Cədvəldə mövcudluq tapılmadı",
|
||||||
"event_limit_tab_title": "Limitlər",
|
"event_limit_tab_title": "Limitlər",
|
||||||
"event_limit_tab_description": "Nə qədər tez-tez rezervasiya edilə bilərsiniz",
|
"event_limit_tab_description": "Nə qədər tez-tez rezervasiya edilə bilərsiniz",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Можете да свържете календара си от <1>тук</1>.",
|
"connect_your_calendar_and_link": "Можете да свържете календара си от <1>тук</1>.",
|
||||||
"default_calendar_selected": "Календар по подразбиране",
|
"default_calendar_selected": "Календар по подразбиране",
|
||||||
"hide_from_profile": "Скрий от профила",
|
"hide_from_profile": "Скрий от профила",
|
||||||
"event_setup_tab_title": "Настройка на събитие",
|
"basics": "Настройка на събитие",
|
||||||
"availability_not_found_in_schedule_error": "Не е намерена свободна заетост в графика",
|
"availability_not_found_in_schedule_error": "Не е намерена свободна заетост в графика",
|
||||||
"event_limit_tab_title": "Лимити",
|
"event_limit_tab_title": "Лимити",
|
||||||
"event_limit_tab_description": "Колко често можете да бъдете резервиран",
|
"event_limit_tab_description": "Колко често можете да бъдете резервиран",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "আপনি আপনার ক্যালেন্ডার থেকে সংযুক্ত করতে পারেন <1>এখানে</1>।",
|
"connect_your_calendar_and_link": "আপনি আপনার ক্যালেন্ডার থেকে সংযুক্ত করতে পারেন <1>এখানে</1>।",
|
||||||
"default_calendar_selected": "ডিফল্ট ক্যালেন্ডার",
|
"default_calendar_selected": "ডিফল্ট ক্যালেন্ডার",
|
||||||
"hide_from_profile": "প্রোফাইল থেকে লুকান",
|
"hide_from_profile": "প্রোফাইল থেকে লুকান",
|
||||||
"event_setup_tab_title": "ইভেন্ট সেটআপ",
|
"basics": "ইভেন্ট সেটআপ",
|
||||||
"availability_not_found_in_schedule_error": "তফসিলের মধ্যে কোনও প্রাপ্যতা পাওয়া যায় না",
|
"availability_not_found_in_schedule_error": "তফসিলের মধ্যে কোনও প্রাপ্যতা পাওয়া যায় না",
|
||||||
"event_limit_tab_title": "সীমা",
|
"event_limit_tab_title": "সীমা",
|
||||||
"event_limit_tab_description": "আপনি কতবার বুক করা যায়",
|
"event_limit_tab_description": "আপনি কতবার বুক করা যায়",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Pots connectar el teu calendari des d'<1>aquí</1>.",
|
"connect_your_calendar_and_link": "Pots connectar el teu calendari des d'<1>aquí</1>.",
|
||||||
"default_calendar_selected": "Calendari predeterminat",
|
"default_calendar_selected": "Calendari predeterminat",
|
||||||
"hide_from_profile": "Amaga del perfil",
|
"hide_from_profile": "Amaga del perfil",
|
||||||
"event_setup_tab_title": "Configuració de l'esdeveniment",
|
"basics": "Configuració de l'esdeveniment",
|
||||||
"availability_not_found_in_schedule_error": "No s'ha trobat disponibilitat a l'agenda",
|
"availability_not_found_in_schedule_error": "No s'ha trobat disponibilitat a l'agenda",
|
||||||
"event_limit_tab_title": "Límits",
|
"event_limit_tab_title": "Límits",
|
||||||
"event_limit_tab_description": "Amb quina freqüència et poden reservar",
|
"event_limit_tab_description": "Amb quina freqüència et poden reservar",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Svůj kalendář můžete připojit <1>zde</1>.",
|
"connect_your_calendar_and_link": "Svůj kalendář můžete připojit <1>zde</1>.",
|
||||||
"default_calendar_selected": "Výchozí kalendář",
|
"default_calendar_selected": "Výchozí kalendář",
|
||||||
"hide_from_profile": "Nezobrazovat v profilu",
|
"hide_from_profile": "Nezobrazovat v profilu",
|
||||||
"event_setup_tab_title": "Nastavení události",
|
"basics": "Nastavení události",
|
||||||
"availability_not_found_in_schedule_error": "V rozvrhu nebyla nalezena žádná dostupnost",
|
"availability_not_found_in_schedule_error": "V rozvrhu nebyla nalezena žádná dostupnost",
|
||||||
"event_limit_tab_title": "Omezení",
|
"event_limit_tab_title": "Omezení",
|
||||||
"event_limit_tab_description": "Jak často lze u vás provést rezervaci",
|
"event_limit_tab_description": "Jak často lze u vás provést rezervaci",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Du kan forbinde din kalender fra <1>her</1>.",
|
"connect_your_calendar_and_link": "Du kan forbinde din kalender fra <1>her</1>.",
|
||||||
"default_calendar_selected": "Standard kalender",
|
"default_calendar_selected": "Standard kalender",
|
||||||
"hide_from_profile": "Skjul fra profil",
|
"hide_from_profile": "Skjul fra profil",
|
||||||
"event_setup_tab_title": "Opsætning af Begivenheder",
|
"basics": "Opsætning af Begivenheder",
|
||||||
"availability_not_found_in_schedule_error": "Ingen tilgængelighed fundet i tidsplanen",
|
"availability_not_found_in_schedule_error": "Ingen tilgængelighed fundet i tidsplanen",
|
||||||
"event_limit_tab_title": "Grænser",
|
"event_limit_tab_title": "Grænser",
|
||||||
"event_limit_tab_description": "Hvor ofte du kan bookes",
|
"event_limit_tab_description": "Hvor ofte du kan bookes",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Sie können Ihren Kalender von <1>hier</1> verbinden.",
|
"connect_your_calendar_and_link": "Sie können Ihren Kalender von <1>hier</1> verbinden.",
|
||||||
"default_calendar_selected": "Standardkalender",
|
"default_calendar_selected": "Standardkalender",
|
||||||
"hide_from_profile": "Im Profil ausblenden",
|
"hide_from_profile": "Im Profil ausblenden",
|
||||||
"event_setup_tab_title": "Termin-Einrichtung",
|
"basics": "Termin-Einrichtung",
|
||||||
"availability_not_found_in_schedule_error": "Keine Verfügbarkeit im Zeitplan gefunden",
|
"availability_not_found_in_schedule_error": "Keine Verfügbarkeit im Zeitplan gefunden",
|
||||||
"event_limit_tab_title": "Limits",
|
"event_limit_tab_title": "Limits",
|
||||||
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
|
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Μπορείτε να συνδέσετε το ημερολόγιό σας από <1>εδώ</1>.",
|
"connect_your_calendar_and_link": "Μπορείτε να συνδέσετε το ημερολόγιό σας από <1>εδώ</1>.",
|
||||||
"default_calendar_selected": "Προεπιλεγμένο ημερολόγιο",
|
"default_calendar_selected": "Προεπιλεγμένο ημερολόγιο",
|
||||||
"hide_from_profile": "Απόκρυψη από το προφίλ",
|
"hide_from_profile": "Απόκρυψη από το προφίλ",
|
||||||
"event_setup_tab_title": "Ρύθμιση εκδήλωσης",
|
"basics": "Ρύθμιση εκδήλωσης",
|
||||||
"availability_not_found_in_schedule_error": "Δεν βρέθηκε διαθεσιμότητα στο πρόγραμμα",
|
"availability_not_found_in_schedule_error": "Δεν βρέθηκε διαθεσιμότητα στο πρόγραμμα",
|
||||||
"event_limit_tab_title": "Όρια",
|
"event_limit_tab_title": "Όρια",
|
||||||
"event_limit_tab_description": "Πόσο συχνά μπορείτε να κάνετε κράτηση",
|
"event_limit_tab_description": "Πόσο συχνά μπορείτε να κάνετε κράτηση",
|
||||||
|
|||||||
@@ -278,6 +278,7 @@
|
|||||||
"number": "Number",
|
"number": "Number",
|
||||||
"checkbox": "Checkbox",
|
"checkbox": "Checkbox",
|
||||||
"is_required": "Is required",
|
"is_required": "Is required",
|
||||||
|
"make_field_required": "Make this field required",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"optional": "Optional",
|
"optional": "Optional",
|
||||||
"input_type": "Input type",
|
"input_type": "Input type",
|
||||||
@@ -354,7 +355,7 @@
|
|||||||
"check_email_reset_password": "Check your email. We sent you a link to reset your password.",
|
"check_email_reset_password": "Check your email. We sent you a link to reset your password.",
|
||||||
"finish": "Finish",
|
"finish": "Finish",
|
||||||
"organization_general_description": "Manage settings for your team language and timezone",
|
"organization_general_description": "Manage settings for your team language and timezone",
|
||||||
"few_sentences_about_yourself": "A few sentences about yourself. This will appear on your personal url page.",
|
"few_sentences_about_yourself": "A few sentences about yourself. This will appear on your scheduling link.",
|
||||||
"nearly_there": "Nearly there!",
|
"nearly_there": "Nearly there!",
|
||||||
"nearly_there_instructions": "Last thing, a brief description about you and a photo really helps you get bookings and let people know who they're booking with.",
|
"nearly_there_instructions": "Last thing, a brief description about you and a photo really helps you get bookings and let people know who they're booking with.",
|
||||||
"set_availability_instructions": "Define ranges of time when you are available on a recurring basis. You can create more of these later and assign them to different calendars.",
|
"set_availability_instructions": "Define ranges of time when you are available on a recurring basis. You can create more of these later and assign them to different calendars.",
|
||||||
@@ -367,7 +368,7 @@
|
|||||||
"continue_with": "Continue with {{appName}}",
|
"continue_with": "Continue with {{appName}}",
|
||||||
"connect_your_calendar": "Connect your calendar",
|
"connect_your_calendar": "Connect your calendar",
|
||||||
"connect_your_video_app": "Connect your video apps",
|
"connect_your_video_app": "Connect your video apps",
|
||||||
"connect_your_video_app_instructions": "Connect your video apps to use them on your event types.",
|
"connect_your_video_app_instructions": "Connect your preferred video conferencing app to use it for your meetings.",
|
||||||
"connect_your_calendar_instructions": "Connect your calendar to automatically check for busy times and new events as they're scheduled.",
|
"connect_your_calendar_instructions": "Connect your calendar to automatically check for busy times and new events as they're scheduled.",
|
||||||
"set_up_later": "Set up later",
|
"set_up_later": "Set up later",
|
||||||
"current_time": "Current time",
|
"current_time": "Current time",
|
||||||
@@ -931,7 +932,7 @@
|
|||||||
"add_attendees": "Add attendees",
|
"add_attendees": "Add attendees",
|
||||||
"show_advanced_settings": "Show advanced settings",
|
"show_advanced_settings": "Show advanced settings",
|
||||||
"event_name": "Event Name",
|
"event_name": "Event Name",
|
||||||
"event_name_in_calendar": "With event name",
|
"event_name_in_calendar": "Calendar event name",
|
||||||
"event_name_tooltip": "The name that will appear in calendars",
|
"event_name_tooltip": "The name that will appear in calendars",
|
||||||
"meeting_with_user": "{Event type title} between {Organiser} & {Scheduler}",
|
"meeting_with_user": "{Event type title} between {Organiser} & {Scheduler}",
|
||||||
"additional_inputs": "Additional Inputs",
|
"additional_inputs": "Additional Inputs",
|
||||||
@@ -947,7 +948,7 @@
|
|||||||
"add_input": "Add an Input",
|
"add_input": "Add an Input",
|
||||||
"disable_notes": "Hide notes in calendar",
|
"disable_notes": "Hide notes in calendar",
|
||||||
"disable_notes_description": "For privacy reasons, additional inputs and notes will be hidden in the calendar entry. They will still be sent to your email.",
|
"disable_notes_description": "For privacy reasons, additional inputs and notes will be hidden in the calendar entry. They will still be sent to your email.",
|
||||||
"requires_confirmation_description": "The booking needs to be manually confirmed before it is pushed to the integrations and a confirmation mail is sent.",
|
"requires_confirmation_description": "The booking needs to be manually confirmed before it is pushed to your calendar and a confirmation is sent.",
|
||||||
"requires_confirmation_will_block_slot_description": "Unconfirmed bookings still block calendar slots.",
|
"requires_confirmation_will_block_slot_description": "Unconfirmed bookings still block calendar slots.",
|
||||||
"recurring_event": "Recurring Event",
|
"recurring_event": "Recurring Event",
|
||||||
"recurring_event_description": "People can subscribe for recurring events",
|
"recurring_event_description": "People can subscribe for recurring events",
|
||||||
@@ -998,7 +999,7 @@
|
|||||||
"quick_chat": "Quick Chat",
|
"quick_chat": "Quick Chat",
|
||||||
"add_new_team_event_type": "Add a new team event type",
|
"add_new_team_event_type": "Add a new team event type",
|
||||||
"add_new_event_type": "Add a new event type",
|
"add_new_event_type": "Add a new event type",
|
||||||
"new_event_type_to_book_description": "Create a new event type for people to book times with.",
|
"new_event_type_to_book_description": "Set up event types to offer different types of meetings.",
|
||||||
"length": "Length",
|
"length": "Length",
|
||||||
"minimum_booking_notice": "Minimum Notice",
|
"minimum_booking_notice": "Minimum Notice",
|
||||||
"offset_toggle": "Offset start times",
|
"offset_toggle": "Offset start times",
|
||||||
@@ -1023,6 +1024,7 @@
|
|||||||
"category_apps": "{{category}} apps",
|
"category_apps": "{{category}} apps",
|
||||||
"app_store": "App Store",
|
"app_store": "App Store",
|
||||||
"app_store_description": "Connecting people, technology and the workplace.",
|
"app_store_description": "Connecting people, technology and the workplace.",
|
||||||
|
"connect_your_video": "Connect your video conferencing app",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"event_type_moved_successfully": "Event type has been moved successfully",
|
"event_type_moved_successfully": "Event type has been moved successfully",
|
||||||
"next_step_text": "Next Step",
|
"next_step_text": "Next Step",
|
||||||
@@ -1238,7 +1240,7 @@
|
|||||||
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
|
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
|
||||||
"you_cannot_see_team_members": "You cannot see all the team members of a private team.",
|
"you_cannot_see_team_members": "You cannot see all the team members of a private team.",
|
||||||
"you_cannot_see_teams_of_org": "You cannot see teams of a private organization.",
|
"you_cannot_see_teams_of_org": "You cannot see teams of a private organization.",
|
||||||
"allow_booker_to_select_duration": "Allow booker to select duration",
|
"allow_booker_to_select_duration": "Allow booker to select from multiple durations",
|
||||||
"impersonate_user_tip": "All uses of this feature is audited.",
|
"impersonate_user_tip": "All uses of this feature is audited.",
|
||||||
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
|
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
|
||||||
"impersonating_stop_instructions": "Click here to stop",
|
"impersonating_stop_instructions": "Click here to stop",
|
||||||
@@ -1494,7 +1496,7 @@
|
|||||||
"connect_your_calendar_and_link": "You can connect your calendar from <1>here</1>.",
|
"connect_your_calendar_and_link": "You can connect your calendar from <1>here</1>.",
|
||||||
"default_calendar_selected": "Default calendar",
|
"default_calendar_selected": "Default calendar",
|
||||||
"hide_from_profile": "Hide from profile",
|
"hide_from_profile": "Hide from profile",
|
||||||
"event_setup_tab_title": "Event Setup",
|
"basics": "Basics",
|
||||||
"availability_not_found_in_schedule_error": "No availability found in schedule",
|
"availability_not_found_in_schedule_error": "No availability found in schedule",
|
||||||
"event_limit_tab_title": "Limits",
|
"event_limit_tab_title": "Limits",
|
||||||
"event_limit_tab_description": "How often you can be booked",
|
"event_limit_tab_description": "How often you can be booked",
|
||||||
@@ -1524,7 +1526,8 @@
|
|||||||
"settings_updated_successfully": "Settings updated successfully",
|
"settings_updated_successfully": "Settings updated successfully",
|
||||||
"error_updating_settings": "Error updating settings",
|
"error_updating_settings": "Error updating settings",
|
||||||
"personal_cal_url": "My personal {{appName}} URL",
|
"personal_cal_url": "My personal {{appName}} URL",
|
||||||
"bio_hint": "A few sentences about yourself. this will appear on your personal url page.",
|
"bio_hint": "A few sentences about yourself. this will appear on your scheduling link.",
|
||||||
|
"finish_and_start": "Finish setup and get started",
|
||||||
"user_has_no_bio": "This user has not added a bio yet.",
|
"user_has_no_bio": "This user has not added a bio yet.",
|
||||||
"bio": "Bio",
|
"bio": "Bio",
|
||||||
"delete_account_modal_title": "Delete Account",
|
"delete_account_modal_title": "Delete Account",
|
||||||
@@ -1580,8 +1583,10 @@
|
|||||||
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
|
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
|
||||||
"skip": "Skip",
|
"skip": "Skip",
|
||||||
"do_this_later": "Do this later",
|
"do_this_later": "Do this later",
|
||||||
"set_availability_getting_started_subtitle_1": "Define ranges of time when you are available",
|
"set_availability_getting_started_subtitle_1": "Define ranges of time when you are available.",
|
||||||
"set_availability_getting_started_subtitle_2": "You can customise all of this later in the availability page.",
|
"set_availability_getting_started_subtitle_2": "Customize it later in the availability page.",
|
||||||
|
"connect_calendar_first": "Connect a calendar first",
|
||||||
|
"complete_profile": "Complete your profile",
|
||||||
"connect_calendars_from_app_store": "You can add more calendars from the app store",
|
"connect_calendars_from_app_store": "You can add more calendars from the app store",
|
||||||
"connect_conference_apps": "Connect conference apps",
|
"connect_conference_apps": "Connect conference apps",
|
||||||
"connect_calendar_apps": "Connect calendar apps",
|
"connect_calendar_apps": "Connect calendar apps",
|
||||||
@@ -1602,7 +1607,9 @@
|
|||||||
"event_duration_info": "The event duration",
|
"event_duration_info": "The event duration",
|
||||||
"event_time_info": "The event start time",
|
"event_time_info": "The event start time",
|
||||||
"event_type_not_found": "EventType not Found",
|
"event_type_not_found": "EventType not Found",
|
||||||
|
"location_variable": "Location",
|
||||||
"location_info": "The location of the event",
|
"location_info": "The location of the event",
|
||||||
|
"additional_notes_variable": "Additional notes",
|
||||||
"additional_notes_info": "The additional notes of booking",
|
"additional_notes_info": "The additional notes of booking",
|
||||||
"attendee_name_info": "The person booking's name",
|
"attendee_name_info": "The person booking's name",
|
||||||
"organizer_name_info": "Organizer's name",
|
"organizer_name_info": "Organizer's name",
|
||||||
@@ -2115,6 +2122,7 @@
|
|||||||
"can_you_try_again": "Can you try again with a different time?",
|
"can_you_try_again": "Can you try again with a different time?",
|
||||||
"verify": "Verify",
|
"verify": "Verify",
|
||||||
"timezone_info": "The timezone of the person receiving",
|
"timezone_info": "The timezone of the person receiving",
|
||||||
|
"timezone_variable": "Timezone",
|
||||||
"event_end_time_variable": "Event end time",
|
"event_end_time_variable": "Event end time",
|
||||||
"event_end_time_info": "The event end time",
|
"event_end_time_info": "The event end time",
|
||||||
"cancel_url_variable": "Cancel URL",
|
"cancel_url_variable": "Cancel URL",
|
||||||
@@ -2730,7 +2738,9 @@
|
|||||||
"review": "Review",
|
"review": "Review",
|
||||||
"reviewed": "Reviewed",
|
"reviewed": "Reviewed",
|
||||||
"unreviewed": "Unreviewed",
|
"unreviewed": "Unreviewed",
|
||||||
|
"rating_url_variable": "Rating url",
|
||||||
"rating_url_info": "The URL for Rating Feedback Form",
|
"rating_url_info": "The URL for Rating Feedback Form",
|
||||||
|
"no_show_url_variable": "No Show url",
|
||||||
"no_show_url_info": "The URL for No Show Feedback",
|
"no_show_url_info": "The URL for No Show Feedback",
|
||||||
"no_support_needed": "No Support Needed?",
|
"no_support_needed": "No Support Needed?",
|
||||||
"hide_support": "Hide Support",
|
"hide_support": "Hide Support",
|
||||||
@@ -2920,8 +2930,10 @@
|
|||||||
"row": "row",
|
"row": "row",
|
||||||
"rows": "rows",
|
"rows": "rows",
|
||||||
"attendee_timezone_variable": "Attendee Timezone",
|
"attendee_timezone_variable": "Attendee Timezone",
|
||||||
|
"event_start_time_in_attendee_timezone_variable": "Event Start Time In Attendee Timezone",
|
||||||
"event_start_time_in_attendee_timezone_info": "Event Start Time In Attendee Timezone",
|
"event_start_time_in_attendee_timezone_info": "Event Start Time In Attendee Timezone",
|
||||||
"event_end_time_in_attendee_timezone_info": "Event End Time In Attendee Timezone",
|
"event_end_time_in_attendee_timezone_info": "Event End Time In Attendee Timezone",
|
||||||
|
"event_end_time_in_attendee_timezone_variable": "Event End Time In Attendee Timezone",
|
||||||
"salesforce_create_event_on_contact": "Create event on contact, if it exists. Else fallback to lead",
|
"salesforce_create_event_on_contact": "Create event on contact, if it exists. Else fallback to lead",
|
||||||
"salesforce_owner_name_to_change": "Owner name to change",
|
"salesforce_owner_name_to_change": "Owner name to change",
|
||||||
"no_filter_set": "No filter set",
|
"no_filter_set": "No filter set",
|
||||||
@@ -2985,8 +2997,8 @@
|
|||||||
"edit_team_member_weights": "Edit Weights",
|
"edit_team_member_weights": "Edit Weights",
|
||||||
"weights_updated_successfully": "Weights updated successfully",
|
"weights_updated_successfully": "Weights updated successfully",
|
||||||
"require_confirmation_for_free_email": "Only require confirmation for free email providers (Ex. @gmail.com, @outlook.com)",
|
"require_confirmation_for_free_email": "Only require confirmation for free email providers (Ex. @gmail.com, @outlook.com)",
|
||||||
"exclude_emails_that_contain": "Exclude emails that contain ...",
|
"exclude_emails_that_contain": "Exclude emails that contain",
|
||||||
"require_emails_that_contain": "Require emails that contain ...",
|
"require_emails_that_contain": "Require emails that contain",
|
||||||
"exclude_emails_match_found_error_message": "Please enter a valid work email address",
|
"exclude_emails_match_found_error_message": "Please enter a valid work email address",
|
||||||
"require_emails_no_match_found_error_message": "You are not allowed to book with this email domain. Please contact the owner of this link.",
|
"require_emails_no_match_found_error_message": "You are not allowed to book with this email domain. Please contact the owner of this link.",
|
||||||
"search_columns": "Search columns",
|
"search_columns": "Search columns",
|
||||||
@@ -3121,7 +3133,7 @@
|
|||||||
"inactive_team_plan_description": "Your team plan is inactive. Check your subcription or reach out to customer support",
|
"inactive_team_plan_description": "Your team plan is inactive. Check your subcription or reach out to customer support",
|
||||||
"limited_access_trial_mode": "Limited access during trial. Feature available after trial ends.",
|
"limited_access_trial_mode": "Limited access during trial. Feature available after trial ends.",
|
||||||
"referral_program": "Referral Program",
|
"referral_program": "Referral Program",
|
||||||
"referral_text": "Earn 20% referral",
|
"referral_text": "Refer and earn",
|
||||||
"dub_disabled_error_message": "You need a Dub API Key and Program Id to use this page.",
|
"dub_disabled_error_message": "You need a Dub API Key and Program Id to use this page.",
|
||||||
"uid": "UID",
|
"uid": "UID",
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
@@ -3472,6 +3484,8 @@
|
|||||||
"webhook_metadata": "Metadata",
|
"webhook_metadata": "Metadata",
|
||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
"booking_status": "Booking status",
|
"booking_status": "Booking status",
|
||||||
|
"location_custom_label_input_label": "Custom label on booking page",
|
||||||
|
"meeting_link": "Meeting link",
|
||||||
"my_bookings": "My Bookings",
|
"my_bookings": "My Bookings",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Puedes conectar tu calendario desde <1>aquí</1>.",
|
"connect_your_calendar_and_link": "Puedes conectar tu calendario desde <1>aquí</1>.",
|
||||||
"default_calendar_selected": "Calendario predeterminado",
|
"default_calendar_selected": "Calendario predeterminado",
|
||||||
"hide_from_profile": "Ocultar del perfil",
|
"hide_from_profile": "Ocultar del perfil",
|
||||||
"event_setup_tab_title": "Configuración del evento",
|
"basics": "Configuración del evento",
|
||||||
"availability_not_found_in_schedule_error": "No se encontró disponibilidad en el horario",
|
"availability_not_found_in_schedule_error": "No se encontró disponibilidad en el horario",
|
||||||
"event_limit_tab_title": "Límites",
|
"event_limit_tab_title": "Límites",
|
||||||
"event_limit_tab_description": "Con qué frecuencia puedes ser reservado",
|
"event_limit_tab_description": "Con qué frecuencia puedes ser reservado",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Puede conectar su calendario desde <1>aquí</1>.",
|
"connect_your_calendar_and_link": "Puede conectar su calendario desde <1>aquí</1>.",
|
||||||
"default_calendar_selected": "Calendario predeterminado",
|
"default_calendar_selected": "Calendario predeterminado",
|
||||||
"hide_from_profile": "Ocultar del perfil",
|
"hide_from_profile": "Ocultar del perfil",
|
||||||
"event_setup_tab_title": "Configuración del evento",
|
"basics": "Configuración del evento",
|
||||||
"availability_not_found_in_schedule_error": "No se encontró disponibilidad en el horario",
|
"availability_not_found_in_schedule_error": "No se encontró disponibilidad en el horario",
|
||||||
"event_limit_tab_title": "Límites",
|
"event_limit_tab_title": "Límites",
|
||||||
"event_limit_tab_description": "Con qué frecuencia se puede reservar",
|
"event_limit_tab_description": "Con qué frecuencia se puede reservar",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Saate oma kalendri ühendada <1>siit</1>.",
|
"connect_your_calendar_and_link": "Saate oma kalendri ühendada <1>siit</1>.",
|
||||||
"default_calendar_selected": "Vaikekalender",
|
"default_calendar_selected": "Vaikekalender",
|
||||||
"hide_from_profile": "Peida profiilist",
|
"hide_from_profile": "Peida profiilist",
|
||||||
"event_setup_tab_title": "Sündmuse seadistamine",
|
"basics": "Sündmuse seadistamine",
|
||||||
"availability_not_found_in_schedule_error": "Ajakavas ei leitud saadavust",
|
"availability_not_found_in_schedule_error": "Ajakavas ei leitud saadavust",
|
||||||
"event_limit_tab_title": "Piirangud",
|
"event_limit_tab_title": "Piirangud",
|
||||||
"event_limit_tab_description": "Kui sageli saate broneerida",
|
"event_limit_tab_description": "Kui sageli saate broneerida",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Zure egutegia <1>hemendik</1> konekta dezakezu.",
|
"connect_your_calendar_and_link": "Zure egutegia <1>hemendik</1> konekta dezakezu.",
|
||||||
"default_calendar_selected": "Egutegi lehenetsia",
|
"default_calendar_selected": "Egutegi lehenetsia",
|
||||||
"hide_from_profile": "Ezkutatu profiletik",
|
"hide_from_profile": "Ezkutatu profiletik",
|
||||||
"event_setup_tab_title": "Gertaeraren konfigurazioa",
|
"basics": "Gertaeraren konfigurazioa",
|
||||||
"availability_not_found_in_schedule_error": "Ez da eskuragarritasunik aurkitu ordutegian",
|
"availability_not_found_in_schedule_error": "Ez da eskuragarritasunik aurkitu ordutegian",
|
||||||
"event_limit_tab_title": "Mugak",
|
"event_limit_tab_title": "Mugak",
|
||||||
"event_limit_tab_description": "Zenbatetan erreserbatu zaitzaketen",
|
"event_limit_tab_description": "Zenbatetan erreserbatu zaitzaketen",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Voit yhdistää kalenterisi <1>täältä</1>.",
|
"connect_your_calendar_and_link": "Voit yhdistää kalenterisi <1>täältä</1>.",
|
||||||
"default_calendar_selected": "Oletuskalenteri valittu",
|
"default_calendar_selected": "Oletuskalenteri valittu",
|
||||||
"hide_from_profile": "Piilota profiilista",
|
"hide_from_profile": "Piilota profiilista",
|
||||||
"event_setup_tab_title": "Tapahtuman asetukset",
|
"basics": "Tapahtuman asetukset",
|
||||||
"availability_not_found_in_schedule_error": "Aikataulusta ei löytynyt vapaita aikoja",
|
"availability_not_found_in_schedule_error": "Aikataulusta ei löytynyt vapaita aikoja",
|
||||||
"event_limit_tab_title": "Rajoitukset",
|
"event_limit_tab_title": "Rajoitukset",
|
||||||
"event_limit_tab_description": "Kuinka usein sinut voidaan varata",
|
"event_limit_tab_description": "Kuinka usein sinut voidaan varata",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Vous pouvez connecter votre calendrier depuis <1>ici</1>.",
|
"connect_your_calendar_and_link": "Vous pouvez connecter votre calendrier depuis <1>ici</1>.",
|
||||||
"default_calendar_selected": "Calendrier par défaut",
|
"default_calendar_selected": "Calendrier par défaut",
|
||||||
"hide_from_profile": "Masquer du profil",
|
"hide_from_profile": "Masquer du profil",
|
||||||
"event_setup_tab_title": "Configuration",
|
"basics": "Configuration",
|
||||||
"availability_not_found_in_schedule_error": "Aucune disponibilité trouvée dans le planning",
|
"availability_not_found_in_schedule_error": "Aucune disponibilité trouvée dans le planning",
|
||||||
"event_limit_tab_title": "Limites",
|
"event_limit_tab_title": "Limites",
|
||||||
"event_limit_tab_description": "Fréquence de réservation",
|
"event_limit_tab_description": "Fréquence de réservation",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "ניתן לחבר את לוח השנה שלך מ<1>כאן</1>.",
|
"connect_your_calendar_and_link": "ניתן לחבר את לוח השנה שלך מ<1>כאן</1>.",
|
||||||
"default_calendar_selected": "לוח השנה המוגדר כברירת מחדל",
|
"default_calendar_selected": "לוח השנה המוגדר כברירת מחדל",
|
||||||
"hide_from_profile": "הסתר מהפרופיל",
|
"hide_from_profile": "הסתר מהפרופיל",
|
||||||
"event_setup_tab_title": "הגדרת ארוע",
|
"basics": "הגדרת ארוע",
|
||||||
"availability_not_found_in_schedule_error": "לא נמצא חלון זמינות בלוח הזמנים",
|
"availability_not_found_in_schedule_error": "לא נמצא חלון זמינות בלוח הזמנים",
|
||||||
"event_limit_tab_title": "מגבלות",
|
"event_limit_tab_title": "מגבלות",
|
||||||
"event_limit_tab_description": "באיזו תדירות ניתן להזמין אותך",
|
"event_limit_tab_description": "באיזו תדירות ניתן להזמין אותך",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Naptárát <1>innen</1> csatlakoztathatja.",
|
"connect_your_calendar_and_link": "Naptárát <1>innen</1> csatlakoztathatja.",
|
||||||
"default_calendar_selected": "Alapértelmezett naptár",
|
"default_calendar_selected": "Alapértelmezett naptár",
|
||||||
"hide_from_profile": "Elrejtés a profilból",
|
"hide_from_profile": "Elrejtés a profilból",
|
||||||
"event_setup_tab_title": "Esemény beállítása",
|
"basics": "Esemény beállítása",
|
||||||
"availability_not_found_in_schedule_error": "Az ütemezésben nem található elérhetőség",
|
"availability_not_found_in_schedule_error": "Az ütemezésben nem található elérhetőség",
|
||||||
"event_limit_tab_title": "Korlátok",
|
"event_limit_tab_title": "Korlátok",
|
||||||
"event_limit_tab_description": "Milyen gyakran vagy foglalható",
|
"event_limit_tab_description": "Milyen gyakran vagy foglalható",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Puoi collegare il tuo calendario <1>qui</1>.",
|
"connect_your_calendar_and_link": "Puoi collegare il tuo calendario <1>qui</1>.",
|
||||||
"default_calendar_selected": "Calendario predefinito",
|
"default_calendar_selected": "Calendario predefinito",
|
||||||
"hide_from_profile": "Nascondi nel profilo",
|
"hide_from_profile": "Nascondi nel profilo",
|
||||||
"event_setup_tab_title": "Impostazione evento",
|
"basics": "Impostazione evento",
|
||||||
"availability_not_found_in_schedule_error": "Nessuna disponibilità trovata nel programma",
|
"availability_not_found_in_schedule_error": "Nessuna disponibilità trovata nel programma",
|
||||||
"event_limit_tab_title": "Limiti",
|
"event_limit_tab_title": "Limiti",
|
||||||
"event_limit_tab_description": "Con quale frequenza è possibile fare una prenotazione",
|
"event_limit_tab_description": "Con quale frequenza è possibile fare una prenotazione",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "<1>こちら</1>からカレンダーを接続できます。",
|
"connect_your_calendar_and_link": "<1>こちら</1>からカレンダーを接続できます。",
|
||||||
"default_calendar_selected": "デフォルトのカレンダー",
|
"default_calendar_selected": "デフォルトのカレンダー",
|
||||||
"hide_from_profile": "プロファイルから隠す",
|
"hide_from_profile": "プロファイルから隠す",
|
||||||
"event_setup_tab_title": "イベント設定",
|
"basics": "イベント設定",
|
||||||
"availability_not_found_in_schedule_error": "スケジュールに利用可能な時間が見つかりません",
|
"availability_not_found_in_schedule_error": "スケジュールに利用可能な時間が見つかりません",
|
||||||
"event_limit_tab_title": "上限",
|
"event_limit_tab_title": "上限",
|
||||||
"event_limit_tab_description": "予約できる頻度",
|
"event_limit_tab_description": "予約できる頻度",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "អ្នកអាចភ្ជាប់ប្រតិទិនរបស់អ្នកពី <1>ទីនេះ</1>។",
|
"connect_your_calendar_and_link": "អ្នកអាចភ្ជាប់ប្រតិទិនរបស់អ្នកពី <1>ទីនេះ</1>។",
|
||||||
"default_calendar_selected": "ប្រតិទិនលំនាំដើម",
|
"default_calendar_selected": "ប្រតិទិនលំនាំដើម",
|
||||||
"hide_from_profile": "លាក់ពីប្រវត្តិរូប",
|
"hide_from_profile": "លាក់ពីប្រវត្តិរូប",
|
||||||
"event_setup_tab_title": "ការកំណត់ព្រឹត្តិការណ៍",
|
"basics": "ការកំណត់ព្រឹត្តិការណ៍",
|
||||||
"availability_not_found_in_schedule_error": "មិនមានភាពទំនេរដែលបានរកឃើញនៅក្នុងកាលវិភាគ",
|
"availability_not_found_in_schedule_error": "មិនមានភាពទំនេរដែលបានរកឃើញនៅក្នុងកាលវិភាគ",
|
||||||
"event_limit_tab_title": "ដែនកំណត់",
|
"event_limit_tab_title": "ដែនកំណត់",
|
||||||
"event_limit_tab_description": "ភាពញឹកញាប់ដែលអ្នកអាចត្រូវបានកក់",
|
"event_limit_tab_description": "ភាពញឹកញាប់ដែលអ្នកអាចត្រូវបានកក់",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "캘린더는 <1>여기</1>에서 연결할 수 있습니다.",
|
"connect_your_calendar_and_link": "캘린더는 <1>여기</1>에서 연결할 수 있습니다.",
|
||||||
"default_calendar_selected": "기본 캘린더",
|
"default_calendar_selected": "기본 캘린더",
|
||||||
"hide_from_profile": "프로필에서 숨기기",
|
"hide_from_profile": "프로필에서 숨기기",
|
||||||
"event_setup_tab_title": "이벤트 설정",
|
"basics": "이벤트 설정",
|
||||||
"availability_not_found_in_schedule_error": "일정에서 사용 가능 시간을 찾을 수 없습니다",
|
"availability_not_found_in_schedule_error": "일정에서 사용 가능 시간을 찾을 수 없습니다",
|
||||||
"event_limit_tab_title": "한도",
|
"event_limit_tab_title": "한도",
|
||||||
"event_limit_tab_description": "예약 가능 횟수",
|
"event_limit_tab_description": "예약 가능 횟수",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "U kunt uw agenda koppelen vanaf <1>hier</1>.",
|
"connect_your_calendar_and_link": "U kunt uw agenda koppelen vanaf <1>hier</1>.",
|
||||||
"default_calendar_selected": "Standaardagenda",
|
"default_calendar_selected": "Standaardagenda",
|
||||||
"hide_from_profile": "In profiel verbergen",
|
"hide_from_profile": "In profiel verbergen",
|
||||||
"event_setup_tab_title": "Configuratie gebeurtenis",
|
"basics": "Configuratie gebeurtenis",
|
||||||
"availability_not_found_in_schedule_error": "Geen beschikbaarheid gevonden in het schema",
|
"availability_not_found_in_schedule_error": "Geen beschikbaarheid gevonden in het schema",
|
||||||
"event_limit_tab_title": "Grenswaarden",
|
"event_limit_tab_title": "Grenswaarden",
|
||||||
"event_limit_tab_description": "Hoe vaak u geboekt kunt worden",
|
"event_limit_tab_description": "Hoe vaak u geboekt kunt worden",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Du kan koble til kalenderen din <1>her</1>.",
|
"connect_your_calendar_and_link": "Du kan koble til kalenderen din <1>her</1>.",
|
||||||
"default_calendar_selected": "Standard kalender",
|
"default_calendar_selected": "Standard kalender",
|
||||||
"hide_from_profile": "Skjul fra profilen",
|
"hide_from_profile": "Skjul fra profilen",
|
||||||
"event_setup_tab_title": "Hendelsesoppsett",
|
"basics": "Hendelsesoppsett",
|
||||||
"availability_not_found_in_schedule_error": "Ingen tilgjengelighet funnet i timeplanen",
|
"availability_not_found_in_schedule_error": "Ingen tilgjengelighet funnet i timeplanen",
|
||||||
"event_limit_tab_title": "Grenser",
|
"event_limit_tab_title": "Grenser",
|
||||||
"event_limit_tab_description": "Hvor ofte du kan bookes",
|
"event_limit_tab_description": "Hvor ofte du kan bookes",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Możesz podłączyć swój kalendarz <1>tutaj</1>.",
|
"connect_your_calendar_and_link": "Możesz podłączyć swój kalendarz <1>tutaj</1>.",
|
||||||
"default_calendar_selected": "Domyślny kalendarz",
|
"default_calendar_selected": "Domyślny kalendarz",
|
||||||
"hide_from_profile": "Ukryj w profilu",
|
"hide_from_profile": "Ukryj w profilu",
|
||||||
"event_setup_tab_title": "Konfiguracja wydarzenia",
|
"basics": "Konfiguracja wydarzenia",
|
||||||
"availability_not_found_in_schedule_error": "Brak dostępności w harmonogramie",
|
"availability_not_found_in_schedule_error": "Brak dostępności w harmonogramie",
|
||||||
"event_limit_tab_title": "Limity",
|
"event_limit_tab_title": "Limity",
|
||||||
"event_limit_tab_description": "Jak często można rezerwować Twoje usługi",
|
"event_limit_tab_description": "Jak często można rezerwować Twoje usługi",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Você pode conectar seu calendário por <1>aqui</1>.",
|
"connect_your_calendar_and_link": "Você pode conectar seu calendário por <1>aqui</1>.",
|
||||||
"default_calendar_selected": "Calendário padrão",
|
"default_calendar_selected": "Calendário padrão",
|
||||||
"hide_from_profile": "Ocultar do perfil",
|
"hide_from_profile": "Ocultar do perfil",
|
||||||
"event_setup_tab_title": "Configuração de eventos",
|
"basics": "Configuração de eventos",
|
||||||
"availability_not_found_in_schedule_error": "Nenhuma disponibilidade encontrada na programação",
|
"availability_not_found_in_schedule_error": "Nenhuma disponibilidade encontrada na programação",
|
||||||
"event_limit_tab_title": "Limites",
|
"event_limit_tab_title": "Limites",
|
||||||
"event_limit_tab_description": "Com que frequência você pode receber reserva",
|
"event_limit_tab_description": "Com que frequência você pode receber reserva",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Pode associar ao seu calendário a partir de <1>aqui</1>.",
|
"connect_your_calendar_and_link": "Pode associar ao seu calendário a partir de <1>aqui</1>.",
|
||||||
"default_calendar_selected": "Calendário predefinido",
|
"default_calendar_selected": "Calendário predefinido",
|
||||||
"hide_from_profile": "Ocultar do perfil",
|
"hide_from_profile": "Ocultar do perfil",
|
||||||
"event_setup_tab_title": "Configuração do evento",
|
"basics": "Configuração do evento",
|
||||||
"availability_not_found_in_schedule_error": "Não foi encontrada nenhuma disponibilidade na agenda",
|
"availability_not_found_in_schedule_error": "Não foi encontrada nenhuma disponibilidade na agenda",
|
||||||
"event_limit_tab_title": "Limites",
|
"event_limit_tab_title": "Limites",
|
||||||
"event_limit_tab_description": "Com que frequência pode ser reservado",
|
"event_limit_tab_description": "Com que frequência pode ser reservado",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Vă puteți conecta calendarul de <1>aici</1>.",
|
"connect_your_calendar_and_link": "Vă puteți conecta calendarul de <1>aici</1>.",
|
||||||
"default_calendar_selected": "Calendar implicit",
|
"default_calendar_selected": "Calendar implicit",
|
||||||
"hide_from_profile": "Ascundeți din profil",
|
"hide_from_profile": "Ascundeți din profil",
|
||||||
"event_setup_tab_title": "Configurare eveniment",
|
"basics": "Configurare eveniment",
|
||||||
"availability_not_found_in_schedule_error": "Nu s-a găsit disponibilitate în program",
|
"availability_not_found_in_schedule_error": "Nu s-a găsit disponibilitate în program",
|
||||||
"event_limit_tab_title": "Limite",
|
"event_limit_tab_title": "Limite",
|
||||||
"event_limit_tab_description": "Cât de des puteți fi rezervat",
|
"event_limit_tab_description": "Cât de des puteți fi rezervat",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Можно подключить календарь <1>отсюда</1>.",
|
"connect_your_calendar_and_link": "Можно подключить календарь <1>отсюда</1>.",
|
||||||
"default_calendar_selected": "Календарь по умолчанию",
|
"default_calendar_selected": "Календарь по умолчанию",
|
||||||
"hide_from_profile": "Скрыть в профиле",
|
"hide_from_profile": "Скрыть в профиле",
|
||||||
"event_setup_tab_title": "Настройка события",
|
"basics": "Настройка события",
|
||||||
"availability_not_found_in_schedule_error": "Доступность не найдена в расписании",
|
"availability_not_found_in_schedule_error": "Доступность не найдена в расписании",
|
||||||
"event_limit_tab_title": "Лимиты",
|
"event_limit_tab_title": "Лимиты",
|
||||||
"event_limit_tab_description": "Как часто можно забронировать для вас событие",
|
"event_limit_tab_description": "Как часто можно забронировать для вас событие",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Môžete pripojiť svoj kalendár z <1>tu</1>.",
|
"connect_your_calendar_and_link": "Môžete pripojiť svoj kalendár z <1>tu</1>.",
|
||||||
"default_calendar_selected": "Predvolený kalendár",
|
"default_calendar_selected": "Predvolený kalendár",
|
||||||
"hide_from_profile": "Skryť z profilu",
|
"hide_from_profile": "Skryť z profilu",
|
||||||
"event_setup_tab_title": "Nastavenie udalosti",
|
"basics": "Nastavenie udalosti",
|
||||||
"availability_not_found_in_schedule_error": "V rozvrhu nebola nájdená dostupnosť",
|
"availability_not_found_in_schedule_error": "V rozvrhu nebola nájdená dostupnosť",
|
||||||
"event_limit_tab_title": "Limity",
|
"event_limit_tab_title": "Limity",
|
||||||
"event_limit_tab_description": "Ako často môžete byť rezervovaní",
|
"event_limit_tab_description": "Ako často môžete byť rezervovaní",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Ne možete da povežete svoj kalendar <1>odavde</1>.",
|
"connect_your_calendar_and_link": "Ne možete da povežete svoj kalendar <1>odavde</1>.",
|
||||||
"default_calendar_selected": "Podrazumevani kalendar",
|
"default_calendar_selected": "Podrazumevani kalendar",
|
||||||
"hide_from_profile": "Sakrij sa profila",
|
"hide_from_profile": "Sakrij sa profila",
|
||||||
"event_setup_tab_title": "Podešavanje događaja",
|
"basics": "Podešavanje događaja",
|
||||||
"availability_not_found_in_schedule_error": "Nema dostupnosti u rasporedu",
|
"availability_not_found_in_schedule_error": "Nema dostupnosti u rasporedu",
|
||||||
"event_limit_tab_title": "Granice",
|
"event_limit_tab_title": "Granice",
|
||||||
"event_limit_tab_description": "Koliko često možete da budete rezervisani",
|
"event_limit_tab_description": "Koliko često možete da budete rezervisani",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Du kan ansluta din kalender från <1>här</1>.",
|
"connect_your_calendar_and_link": "Du kan ansluta din kalender från <1>här</1>.",
|
||||||
"default_calendar_selected": "Förvald kalender",
|
"default_calendar_selected": "Förvald kalender",
|
||||||
"hide_from_profile": "Dölj från profil",
|
"hide_from_profile": "Dölj från profil",
|
||||||
"event_setup_tab_title": "Konfiguration av händelse",
|
"basics": "Konfiguration av händelse",
|
||||||
"availability_not_found_in_schedule_error": "Ingen tillgänglighet hittades i schemat",
|
"availability_not_found_in_schedule_error": "Ingen tillgänglighet hittades i schemat",
|
||||||
"event_limit_tab_title": "Gränser",
|
"event_limit_tab_title": "Gränser",
|
||||||
"event_limit_tab_description": "Hur ofta du kan bokas",
|
"event_limit_tab_description": "Hur ofta du kan bokas",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Takviminizi <1>buradan</1> bağlayabilirsiniz.",
|
"connect_your_calendar_and_link": "Takviminizi <1>buradan</1> bağlayabilirsiniz.",
|
||||||
"default_calendar_selected": "Varsayılan takvim",
|
"default_calendar_selected": "Varsayılan takvim",
|
||||||
"hide_from_profile": "Profilde gizle",
|
"hide_from_profile": "Profilde gizle",
|
||||||
"event_setup_tab_title": "Etkinlik Kurulumu",
|
"basics": "Etkinlik Kurulumu",
|
||||||
"availability_not_found_in_schedule_error": "Programda uygunluk bulunamadı",
|
"availability_not_found_in_schedule_error": "Programda uygunluk bulunamadı",
|
||||||
"event_limit_tab_title": "Limitler",
|
"event_limit_tab_title": "Limitler",
|
||||||
"event_limit_tab_description": "Ne sıklıkla rezervasyon yapabilirsiniz",
|
"event_limit_tab_description": "Ne sıklıkla rezervasyon yapabilirsiniz",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Підключити календар можна <1>звідси</1>.",
|
"connect_your_calendar_and_link": "Підключити календар можна <1>звідси</1>.",
|
||||||
"default_calendar_selected": "Календар за замовчуванням",
|
"default_calendar_selected": "Календар за замовчуванням",
|
||||||
"hide_from_profile": "Не показувати в профілі",
|
"hide_from_profile": "Не показувати в профілі",
|
||||||
"event_setup_tab_title": "Налаштування заходу",
|
"basics": "Налаштування заходу",
|
||||||
"availability_not_found_in_schedule_error": "Доступність у розкладі не знайдена",
|
"availability_not_found_in_schedule_error": "Доступність у розкладі не знайдена",
|
||||||
"event_limit_tab_title": "Ліміти",
|
"event_limit_tab_title": "Ліміти",
|
||||||
"event_limit_tab_description": "Як часто ваш час можуть бронювати",
|
"event_limit_tab_description": "Як часто ваш час можуть бронювати",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "Bạn có thể kết nối lịch của mình từ <1>đây</1>.",
|
"connect_your_calendar_and_link": "Bạn có thể kết nối lịch của mình từ <1>đây</1>.",
|
||||||
"default_calendar_selected": "Lịch mặc định",
|
"default_calendar_selected": "Lịch mặc định",
|
||||||
"hide_from_profile": "Ẩn khỏi hồ sơ",
|
"hide_from_profile": "Ẩn khỏi hồ sơ",
|
||||||
"event_setup_tab_title": "Thiết lập sự kiện",
|
"basics": "Thiết lập sự kiện",
|
||||||
"availability_not_found_in_schedule_error": "Không tìm thấy thời gian khả dụng trong lịch trình",
|
"availability_not_found_in_schedule_error": "Không tìm thấy thời gian khả dụng trong lịch trình",
|
||||||
"event_limit_tab_title": "Giới hạn",
|
"event_limit_tab_title": "Giới hạn",
|
||||||
"event_limit_tab_description": "Bạn có thể được đặt lịch bao lần",
|
"event_limit_tab_description": "Bạn có thể được đặt lịch bao lần",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "您可以从<1>这里</1>连接您的日历。",
|
"connect_your_calendar_and_link": "您可以从<1>这里</1>连接您的日历。",
|
||||||
"default_calendar_selected": "默认日历",
|
"default_calendar_selected": "默认日历",
|
||||||
"hide_from_profile": "从个人资料中隐藏",
|
"hide_from_profile": "从个人资料中隐藏",
|
||||||
"event_setup_tab_title": "活动设置",
|
"basics": "活动设置",
|
||||||
"availability_not_found_in_schedule_error": "时间表中没有可供安排的时间",
|
"availability_not_found_in_schedule_error": "时间表中没有可供安排的时间",
|
||||||
"event_limit_tab_title": "限制",
|
"event_limit_tab_title": "限制",
|
||||||
"event_limit_tab_description": "您多久可被预约一次",
|
"event_limit_tab_description": "您多久可被预约一次",
|
||||||
|
|||||||
@@ -1489,7 +1489,7 @@
|
|||||||
"connect_your_calendar_and_link": "您可以從<1>這裡</1>連結行事曆。",
|
"connect_your_calendar_and_link": "您可以從<1>這裡</1>連結行事曆。",
|
||||||
"default_calendar_selected": "預設行事曆",
|
"default_calendar_selected": "預設行事曆",
|
||||||
"hide_from_profile": "從個人資料隱藏",
|
"hide_from_profile": "從個人資料隱藏",
|
||||||
"event_setup_tab_title": "活動設定",
|
"basics": "活動設定",
|
||||||
"availability_not_found_in_schedule_error": "在日程中找不到可用時間",
|
"availability_not_found_in_schedule_error": "在日程中找不到可用時間",
|
||||||
"event_limit_tab_title": "限制",
|
"event_limit_tab_title": "限制",
|
||||||
"event_limit_tab_description": "您的可預約頻率",
|
"event_limit_tab_description": "您的可預約頻率",
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ describe("pagesAndRewritePaths", () => {
|
|||||||
"upgrade",
|
"upgrade",
|
||||||
"video",
|
"video",
|
||||||
"workflows",
|
"workflows",
|
||||||
"403",
|
|
||||||
"500",
|
|
||||||
"bookings",
|
"bookings",
|
||||||
"event-types",
|
"event-types",
|
||||||
"icons",
|
"icons",
|
||||||
|
|||||||
@@ -5315,6 +5315,95 @@
|
|||||||
"tags": ["Orgs / Teams / Routing forms / Responses"]
|
"tags": ["Orgs / Teams / Routing forms / Responses"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/organizations/{orgId}/teams/{teamId}/schedules": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "OrganizationsTeamsSchedulesController_getTeamSchedules",
|
||||||
|
"summary": "Get all team member schedules",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For non-platform customers - value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-secret-key",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client secret key",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cal-client-id",
|
||||||
|
"in": "header",
|
||||||
|
"description": "For platform customers - OAuth client ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orgId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Maximum number of items to return",
|
||||||
|
"example": 25,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 250,
|
||||||
|
"default": 250,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items to skip",
|
||||||
|
"example": 0,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 0,
|
||||||
|
"default": 0,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetSchedulesOutput_2024_06_11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": ["Orgs / Teams / Schedules"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/organizations/{orgId}/teams/{teamId}/stripe/connect": {
|
"/v2/organizations/{orgId}/teams/{teamId}/stripe/connect": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "OrganizationsStripeController_getTeamStripeConnectUrl",
|
"operationId": "OrganizationsStripeController_getTeamStripeConnectUrl",
|
||||||
@@ -5482,6 +5571,22 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "orgId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "userId",
|
"name": "userId",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -12629,6 +12734,69 @@
|
|||||||
"tags": ["Teams / Memberships"]
|
"tags": ["Teams / Memberships"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/teams/{teamId}/schedules": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "TeamsSchedulesController_getTeamSchedules",
|
||||||
|
"summary": "Get all team member schedules",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"in": "header",
|
||||||
|
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "teamId",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Maximum number of items to return",
|
||||||
|
"example": 25,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 250,
|
||||||
|
"default": 250,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items to skip",
|
||||||
|
"example": 0,
|
||||||
|
"schema": {
|
||||||
|
"minimum": 0,
|
||||||
|
"default": 0,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetSchedulesOutput_2024_06_11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": ["Teams / Schedules"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/teams/{teamId}/verified-resources/emails/verification-code/request": {
|
"/v2/teams/{teamId}/verified-resources/emails/verification-code/request": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "TeamsVerifiedResourcesController_requestEmailVerificationCode",
|
"operationId": "TeamsVerifiedResourcesController_requestEmailVerificationCode",
|
||||||
@@ -15566,6 +15734,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
||||||
@@ -17045,6 +17221,13 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"ownerId": {
|
"ownerId": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"example": 10
|
"example": 10
|
||||||
@@ -17074,6 +17257,8 @@
|
|||||||
"successRedirectUrl",
|
"successRedirectUrl",
|
||||||
"isInstantEvent",
|
"isInstantEvent",
|
||||||
"scheduleId",
|
"scheduleId",
|
||||||
|
"hidden",
|
||||||
|
"bookingRequiresAuthentication",
|
||||||
"ownerId",
|
"ownerId",
|
||||||
"users"
|
"users"
|
||||||
]
|
]
|
||||||
@@ -17372,6 +17557,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"locations": {
|
"locations": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
"description": "Locations where the event will take place. If not provided, cal video link will be used as the location.",
|
||||||
@@ -18935,6 +19128,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"schedulingType": {
|
"schedulingType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["collective", "roundRobin", "managed"],
|
"enum": ["collective", "roundRobin", "managed"],
|
||||||
@@ -19338,6 +19539,13 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"teamId": {
|
"teamId": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
@@ -19747,6 +19955,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"bookingRequiresAuthentication": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users can book this event type."
|
||||||
|
},
|
||||||
"hosts": {
|
"hosts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -20844,6 +21060,7 @@
|
|||||||
},
|
},
|
||||||
"unit": {
|
"unit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"enum": ["hour", "minute", "day"],
|
||||||
"description": "Unit for the offset time",
|
"description": "Unit for the offset time",
|
||||||
"example": "hour"
|
"example": "hour"
|
||||||
}
|
}
|
||||||
@@ -20864,6 +21081,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "beforeEvent",
|
"default": "beforeEvent",
|
||||||
|
"enum": ["beforeEvent"],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "beforeEvent"
|
"example": "beforeEvent"
|
||||||
}
|
}
|
||||||
@@ -20884,6 +21102,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterEvent",
|
"default": "afterEvent",
|
||||||
|
"enum": ["afterEvent"],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterEvent"
|
"example": "afterEvent"
|
||||||
}
|
}
|
||||||
@@ -20896,6 +21115,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "eventCancelled",
|
"default": "eventCancelled",
|
||||||
|
"enum": ["eventCancelled"],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20907,6 +21127,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "newEvent",
|
"default": "newEvent",
|
||||||
|
"enum": ["newEvent"],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20918,6 +21139,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "rescheduleEvent",
|
"default": "rescheduleEvent",
|
||||||
|
"enum": ["rescheduleEvent"],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20937,6 +21159,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterGuestsCalVideoNoShow",
|
"default": "afterGuestsCalVideoNoShow",
|
||||||
|
"enum": ["afterGuestsCalVideoNoShow"],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterGuestsCalVideoNoShow"
|
"example": "afterGuestsCalVideoNoShow"
|
||||||
}
|
}
|
||||||
@@ -20957,6 +21180,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "afterHostsCalVideoNoShow",
|
"default": "afterHostsCalVideoNoShow",
|
||||||
|
"enum": ["afterHostsCalVideoNoShow"],
|
||||||
"description": "Trigger type for the workflow",
|
"description": "Trigger type for the workflow",
|
||||||
"example": "afterHostsCalVideoNoShow"
|
"example": "afterHostsCalVideoNoShow"
|
||||||
}
|
}
|
||||||
@@ -21428,6 +21652,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"enum": [
|
||||||
|
[
|
||||||
|
"beforeEvent",
|
||||||
|
"eventCancelled",
|
||||||
|
"newEvent",
|
||||||
|
"afterEvent",
|
||||||
|
"rescheduleEvent",
|
||||||
|
"afterHostsCalVideoNoShow",
|
||||||
|
"afterGuestsCalVideoNoShow"
|
||||||
|
]
|
||||||
|
],
|
||||||
"description": "Trigger type for the workflow"
|
"description": "Trigger type for the workflow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type DefaultEventLocationType = {
|
|||||||
messageForOrganizer: string;
|
messageForOrganizer: string;
|
||||||
category: "in person" | "conferencing" | "other" | "phone";
|
category: "in person" | "conferencing" | "other" | "phone";
|
||||||
linkType: "static";
|
linkType: "static";
|
||||||
|
supportsCustomLabel?: boolean;
|
||||||
|
|
||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
urlRegExp?: string;
|
urlRegExp?: string;
|
||||||
@@ -42,6 +43,7 @@ export type DefaultEventLocationType = {
|
|||||||
| {
|
| {
|
||||||
organizerInputType: "phone" | "text" | null;
|
organizerInputType: "phone" | "text" | null;
|
||||||
organizerInputPlaceholder?: string | null;
|
organizerInputPlaceholder?: string | null;
|
||||||
|
organizerInputLabel?: string | null;
|
||||||
attendeeInputType?: null;
|
attendeeInputType?: null;
|
||||||
attendeeInputPlaceholder?: null;
|
attendeeInputPlaceholder?: null;
|
||||||
}
|
}
|
||||||
@@ -56,7 +58,7 @@ export type DefaultEventLocationType = {
|
|||||||
export type EventLocationTypeFromApp = Ensure<
|
export type EventLocationTypeFromApp = Ensure<
|
||||||
EventLocationTypeFromAppMeta,
|
EventLocationTypeFromAppMeta,
|
||||||
"defaultValueVariable" | "variable"
|
"defaultValueVariable" | "variable"
|
||||||
>;
|
> & { supportsCustomLabel?: boolean; organizerInputLabel?: string };
|
||||||
|
|
||||||
export type EventLocationType = DefaultEventLocationType | EventLocationTypeFromApp;
|
export type EventLocationType = DefaultEventLocationType | EventLocationTypeFromApp;
|
||||||
|
|
||||||
@@ -147,18 +149,21 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
|||||||
category: "conferencing",
|
category: "conferencing",
|
||||||
messageForOrganizer: "",
|
messageForOrganizer: "",
|
||||||
linkType: "static",
|
linkType: "static",
|
||||||
|
supportsCustomLabel: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: true,
|
default: true,
|
||||||
type: DefaultEventLocationTypeEnum.Link,
|
type: DefaultEventLocationTypeEnum.Link,
|
||||||
label: "link_meeting",
|
label: "link_meeting",
|
||||||
organizerInputType: "text",
|
organizerInputType: "text",
|
||||||
|
organizerInputLabel: "meeting_link",
|
||||||
variable: "locationLink",
|
variable: "locationLink",
|
||||||
messageForOrganizer: "Provide a Meeting Link",
|
messageForOrganizer: "Provide a Meeting Link",
|
||||||
defaultValueVariable: "link",
|
defaultValueVariable: "link",
|
||||||
iconUrl: "/link.svg",
|
iconUrl: "/link.svg",
|
||||||
category: "other",
|
category: "other",
|
||||||
linkType: "static",
|
linkType: "static",
|
||||||
|
supportsCustomLabel: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: true,
|
default: true,
|
||||||
@@ -182,6 +187,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
|||||||
label: "organizer_phone_number",
|
label: "organizer_phone_number",
|
||||||
messageForOrganizer: "Provide your phone number",
|
messageForOrganizer: "Provide your phone number",
|
||||||
organizerInputType: "phone",
|
organizerInputType: "phone",
|
||||||
|
organizerInputLabel: "phone_number",
|
||||||
variable: "locationPhoneNumber",
|
variable: "locationPhoneNumber",
|
||||||
defaultValueVariable: "hostPhoneNumber",
|
defaultValueVariable: "hostPhoneNumber",
|
||||||
iconUrl: "/phone.svg",
|
iconUrl: "/phone.svg",
|
||||||
@@ -206,6 +212,7 @@ export type LocationObject = {
|
|||||||
address?: string;
|
address?: string;
|
||||||
displayLocationPublicly?: boolean;
|
displayLocationPublicly?: boolean;
|
||||||
credentialId?: number;
|
credentialId?: number;
|
||||||
|
customLabel?: string;
|
||||||
} & Partial<
|
} & Partial<
|
||||||
Record<
|
Record<
|
||||||
"address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "hostDefault" | "phone" | "somewhereElse",
|
"address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "hostDefault" | "phone" | "somewhereElse",
|
||||||
@@ -286,7 +293,7 @@ export const guessEventLocationType = (locationTypeOrValue: string | undefined |
|
|||||||
|
|
||||||
export const LocationType = { ...DefaultEventLocationTypeEnum, ...AppStoreLocationType };
|
export const LocationType = { ...DefaultEventLocationTypeEnum, ...AppStoreLocationType };
|
||||||
|
|
||||||
type PrivacyFilteredLocationObject = Optional<LocationObject, "address" | "link">;
|
type PrivacyFilteredLocationObject = Optional<LocationObject, "address" | "link" | "customLabel">;
|
||||||
|
|
||||||
export const privacyFilteredLocations = (locations: LocationObject[]): PrivacyFilteredLocationObject[] => {
|
export const privacyFilteredLocations = (locations: LocationObject[]): PrivacyFilteredLocationObject[] => {
|
||||||
const locationsAfterPrivacyFilter = locations.map((location) => {
|
const locationsAfterPrivacyFilter = locations.map((location) => {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export async function getLocationGroupedOptions(
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
credentialId?: number;
|
credentialId?: number;
|
||||||
|
supportsCustomLabel?: boolean;
|
||||||
}[]
|
}[]
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@ export async function getLocationGroupedOptions(
|
|||||||
label: l.label,
|
label: l.label,
|
||||||
value: l.type,
|
value: l.type,
|
||||||
icon: l.iconUrl,
|
icon: l.iconUrl,
|
||||||
|
supportsCustomLabel: l.supportsCustomLabel,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
@@ -153,6 +155,7 @@ export async function getLocationGroupedOptions(
|
|||||||
label: l.label,
|
label: l.label,
|
||||||
value: l.type,
|
value: l.type,
|
||||||
icon: l.iconUrl,
|
icon: l.iconUrl,
|
||||||
|
supportsCustomLabel: l.supportsCustomLabel,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export function Segment({
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
if (isPending) return <span>Loading...</span>;
|
if (isPending) return <span>Loading...</span>;
|
||||||
if (!attributes) {
|
if (!attributes) {
|
||||||
console.log("Error fetching attributes");
|
console.error("Error fetching attributes");
|
||||||
return <span>{t("something_went_wrong")}</span>;
|
return <span>{t("something_went_wrong")}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/l
|
|||||||
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import invertLogoOnDark from "@calcom/lib/invertLogoOnDark";
|
import invertLogoOnDark from "@calcom/lib/invertLogoOnDark";
|
||||||
|
import classNames from "@calcom/ui/classNames";
|
||||||
import { Icon } from "@calcom/ui/components/icon";
|
import { Icon } from "@calcom/ui/components/icon";
|
||||||
import { Tooltip } from "@calcom/ui/components/tooltip";
|
import { Tooltip } from "@calcom/ui/components/tooltip";
|
||||||
import classNames from "@calcom/ui/classNames";
|
|
||||||
|
|
||||||
const excludeNullValues = (value: unknown) => !!value;
|
const excludeNullValues = (value: unknown) => !!value;
|
||||||
|
|
||||||
@@ -47,7 +47,8 @@ function RenderLocationTooltip({ locations }: { locations: LocationObject[] }) {
|
|||||||
if (!eventLocationType) {
|
if (!eventLocationType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
|
const translatedLocation =
|
||||||
|
location.customLabel || getTranslatedLocation(location, eventLocationType, t);
|
||||||
return (
|
return (
|
||||||
<div key={`${location.type}-${index}`} className="font-sm flex flex-row items-center">
|
<div key={`${location.type}-${index}`} className="font-sm flex flex-row items-center">
|
||||||
<RenderIcon eventLocationType={eventLocationType} isTooltip />
|
<RenderIcon eventLocationType={eventLocationType} isTooltip />
|
||||||
@@ -74,11 +75,8 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje
|
|||||||
// It's possible that the location app got uninstalled
|
// It's possible that the location app got uninstalled
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (eventLocationType.variable === "hostDefault") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
|
const locationName = location?.customLabel || getTranslatedLocation(location, eventLocationType, t);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
||||||
@@ -87,8 +85,8 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje
|
|||||||
) : (
|
) : (
|
||||||
<RenderIcon eventLocationType={eventLocationType} isTooltip={false} />
|
<RenderIcon eventLocationType={eventLocationType} isTooltip={false} />
|
||||||
)}
|
)}
|
||||||
<Tooltip content={translatedLocation}>
|
<Tooltip content={locationName}>
|
||||||
<p className="line-clamp-1">{translatedLocation}</p>
|
<p className="line-clamp-1">{locationName}</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function getLocationsOptionsForSelect(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const type = eventLocation.type;
|
const type = eventLocation.type;
|
||||||
const translatedLocation = getTranslatedLocation(location, eventLocation, t);
|
const translatedLocation = location.customLabel || getTranslatedLocation(location, eventLocation, t);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// XYZ: is considered a namespace in i18next https://www.i18next.com/principles/namespaces and thus it gets cleaned up.
|
// XYZ: is considered a namespace in i18next https://www.i18next.com/principles/namespaces and thus it gets cleaned up.
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ export default function ApiKeyDialogForm({
|
|||||||
form={form}
|
form={form}
|
||||||
handleSubmit={async (event) => {
|
handleSubmit={async (event) => {
|
||||||
if (defaultValues) {
|
if (defaultValues) {
|
||||||
console.log("Name changed");
|
|
||||||
await updateApiKeyMutation.mutate({ id: defaultValues.id, note: event.note });
|
await updateApiKeyMutation.mutate({ id: defaultValues.id, note: event.note });
|
||||||
} else {
|
} else {
|
||||||
const apiKey = await utils.client.viewer.apiKeys.create.mutate(event);
|
const apiKey = await utils.client.viewer.apiKeys.create.mutate(event);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const ApiKeyListItem = ({
|
|||||||
showToast(t("api_key_deleted"), "success");
|
showToast(t("api_key_deleted"), "success");
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
console.log(err);
|
console.error(err);
|
||||||
showToast(t("something_went_wrong"), "error");
|
showToast(t("something_went_wrong"), "error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user