Compare commits

...
52 Commits
Author SHA1 Message Date
Keith Williams 656fcafcde chore: v4.6.17 (#17493)
release.yml / release (push) Canceled after 0s
2024-11-05 14:05:23 -03:00
Nizzy 6d7080d9fc posthog vars (#17494) 2024-11-05 16:37:00 +00:00
Rajiv Sahal f943debda2 feat: docs for event type atom (#17466)
* init docs for event types atom

* images for event type atom

* update images for destination and selected calendar atoms

* update event types docs
2024-11-05 13:32:45 -03:00
Benny Joo 803fc488c5 feat: Add feature to download routing form reports (#17400)
* remove unused code

* add i18n for some strings in widget

* improve UI and add download button to routing forms reporting page

* add more margin

* add downloadAsCsv util

* integrate download button to routing forms report

* refactor

* address comment

* escape comma multiselect items

* fix

* preserve new lines and wrapping

* refactor

* ensure fetching all rows
2024-11-05 11:21:13 -05:00
Rajiv Sahal 423b284278 feat: docs for payment form atom (#17360)
* init docs for stripe payment form

* update payment form page

* fixup
2024-11-05 15:48:51 +00:00
sean-brydon f1111c9d42 show who a RR was reassigned to on auto (#17454) 2024-11-05 12:44:49 -03:00
Keith Williams ed6d88e30f perf: Allow bypassing 3rd party calendar busy times (#17492)
* perf: Allow bypassing 3rd party calendar busy times

* Fix typing issues

* Fixed API v1 build

* Another fix for API v1
2024-11-05 12:31:33 -03:00
Morgan e675ad0c6c fix: assign/remove team members to team event-types (#17483)
* fix: assign/remove team members to team event-types

* fixup! fix: assign/remove team members to team event-types

* fixup! fixup! fix: assign/remove team members to team event-types

* fixup! fixup! fixup! fix: assign/remove team members to team event-types
2024-11-05 17:19:43 +02:00
Udit Takkar a01d6aa932 feat: instant meeting modal improvements (#17489)
* feat: instant meeting modal improvements

* fix: type error
2024-11-05 09:02:30 -05:00
Lauris Skraucis f868b86e6c fix: creating recurring seated bookings for same timeslot (#17482)
* fix: creating multiple seated bookings for same timeslot

* add test
2024-11-05 15:38:21 +02:00
Peer Richelsen db0a7f3797 chore: Update build-an-app.mdx (#17486) 2024-11-05 13:38:07 +00:00
Morgan 5646229cd4 fix: cancelling invalid booking seat apiv2 (#17484) 2024-11-05 12:30:38 +01:00
Anik Dhabal Babu f39e817d86 fix: Update the common schedule toggle check logic (#17439) 2024-11-05 05:10:12 +00:00
Benny Joo 42c4c134fa chore: add missing i18n to metadata in settings/platform pages (#17476) 2024-11-05 02:37:12 +00:00
Benny Joo ffc4b69edd fix: api/routing-forms/responses (#17473)
* add i18n

* fix api/routing-forms/responses
2024-11-04 13:19:58 -06:00
Benny Joo 7263f3b00b chore: settings/platform, settings/license-key - remove pages router and use app router (#17384)
* remove pages router and use app router

* remove pages router and use app router for platform

* add billing page and plans page

* fix metadata for settings/platform/billing

* fix metadata for settings/platform/oauth-clients
2024-11-04 15:56:43 -03:00
ddfbe25903 feat: adding posthog libs for node + js (#16862)
* adding posthog libs for node + js

* updating lock

* move to init posthog client - pageview component

* adding identify in shell

* removing test event captures

* add signout tracking to dropdown as it was missed in merge

---------

Co-authored-by: sean <sean@brydon.io>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2024-11-04 11:46:25 -05:00
Udit Takkar dd944bace1 chore: add webhook in payload (#17468)
* chore: add webhook in payload

* chore

* chore: add more fields

* add host email

* chore: change name
2024-11-04 16:12:34 +00:00
Keith Williams e4353af839 chore: v4.6.16 (#17465)
release.yml / release (push) Canceled after 0s
2024-11-04 10:57:04 -03:00
Keith Williams ff9936fdeb perf: Remove extra spans (#17464) 2024-11-04 13:50:49 +00:00
Benny Joo e74718ac66 fix: ensure fetching all members before exporting csv (#17461) 2024-11-04 11:35:37 +00:00
Udit Takkar a78f10223f chore: remove no option text (#17457) 2024-11-04 09:47:36 +00:00
Benny Joo c850af7409 feat: allow users to generate csv tables for org members (#17458)
* add downloadAsCsv util

* add sanitizeValue util

* write logic

* refactor

* add tests
2024-11-04 09:47:10 +00:00
Udit Takkar 06dccd7a73 fix: filter by team url (#17456) 2024-11-04 09:45:14 +00:00
Rajiv Sahal 3b59185f75 fix typo (#17451) 2024-11-04 08:39:42 +00:00
Morgan c907bcc646 fix: docs mdx syntax errors (#17450)
* fix: docs mdx syntax errors

* fixup! fix: docs mdx syntax errors
2024-11-04 08:20:33 +00:00
Keith Williams 6c5b12db57 perf: Add more specific spans to getBusyTimes (#17437)
* perf: Add more specific spans to getBusyTimes

* fix types
2024-11-03 12:21:35 -03:00
Benny Joo bcc1a8e279 fix: create a wrapper for google calendar fetching (#17440)
* fix: create a wrapper for google cal fetching

* improve log
2024-11-01 20:22:57 -03:00
Benny Joo a277b8515f fix (#17441) 2024-11-01 20:51:02 +00:00
Benny Joo 42a02cbb60 fix: update profile photo logic in google cal integration (#17438) 2024-11-01 16:21:42 -04:00
sean-brydon 0c60ad1c82 fix: no availability email frequency fix (#17430)
* pass teamID to only send to relevant team admins not org admins

* a lot more tests aroundthis

* improve test cases

* change reddis cache key to take into account teamID

* add start/endtime to template

* fix text in email

* add endTime to tests
2024-11-01 14:54:34 -04:00
Keith Williams 1add8e73ee Revert "perf: Cache app store imports (#17425)" (#17428)
This reverts commit b2d395de52.
2024-10-31 19:26:55 +00:00
Alex van Andel ff15ec77c3 chore: Release v4.6.15 (#17422)
release.yml / release (push) Canceled after 0s
2024-10-31 18:20:08 +00:00
Keith Williams b2d395de52 perf: Cache app store imports (#17425) 2024-10-31 18:19:07 +00:00
Joe Au-Yeung 9dd35c7ab3 feat: Salesforce only get active users (#17426)
* Add isActive boolean when querying owners

* Add conditional
2024-10-31 18:18:35 +00:00
Calcom BotandReplexica bd30bf335b feat: update translations (#17421)
Co-authored-by: Replexica <support@replexica.com>
2024-10-31 17:56:22 +00:00
Hariom BalharaandCarinaWolli ab65ce73c2 feat: Support Attribute Logic fallback (#17290)
* Add fallback

* Support attribute query fallback

* Refactor

* Add tests and cleanup SingleFofrm

* small text fixes

* With fallback in picture, we dont throw error in preview now instead we capture errors and show them gracefully

* Get attribute logic preview working without saving Fixes CAL-4582

* Abstract useRoutes out

* Update e2e

* Dont define Page component again and again

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-10-31 12:53:34 -04:00
Joe Au-Yeung 06f83e6d0d feat: Send Attendee No Show Data To Salesforce (#17423) 2024-10-31 16:39:58 +00:00
Udit Takkarandsean ebd5ca6ceb fix: people filter in /bookings and create OOO modal (#17396)
* fix: people filter in /bookings

* refactor: lazy loading people filters and OOO

* chore: type errr

* chore: type errr

* fallback to username if no name is set

---------

Co-authored-by: sean <sean@brydon.io>
2024-10-31 14:57:07 +00:00
Benny Joo bcaf067ed6 fix: routing-forms reporting - "in" operator logic is broken + overall refactor (#17398)
* fix: routing-forms reporting - "in" operator logic is broken

* fix

* refactor to make it more readable

* add tests
2024-10-31 14:09:10 +00:00
Morgan 120cfed8a1 fix: booker platform atom multi duration selector (#17419) 2024-10-31 13:14:58 +00:00
sean-brydonandPeer Richelsen e728ed74ce fix: add reassignedBy and reassign reason to both status (#17418)
* add reassignedBy and reassign reason to both status

* fix w-full issue in dialog

* fix types

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-10-31 12:32:05 +00:00
Keith Williams 64551a506a perf: Allow configuring Sentry:debug (#17420) 2024-10-31 12:15:07 +00:00
fee3b73bda feat: payment card atom (#17203)
* init stripe payment card atom

* cleanup

* update platform libraries

* fix import paths

* add function to get user payment info

* fixup

* resolve merge conflicts

* fixup fixup

* resolve merge conflicts

* fixup

* update payments page

* add props for event payment success and failure

* update props and fixup

* bump platform libraries

* fixup! bump platform libraries

* fixup! fixup! bump platform libraries

* resolve merge conflicts

* fix: add isActive to paymentsTab

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-10-31 11:21:13 +02:00
Carina WollendorferandCarinaWolli a5806cb100 fix: buffers in handleNewBooking (#17415)
* send missing buffer params

* add tests

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-10-31 08:48:36 +00:00
Anik Dhabal Babu f649f8b78d fix: huddle01 is not generating meeting link (#17417)
* fix: huddle01 not generating link

* update
2024-10-31 04:59:34 +00:00
Keith Williams ced7f282a5 perf: Reduce spans in highPerf temporarily (#17413)
* perf: Reduce spans in highPerf temporarily

* Just completely removed them
2024-10-30 16:15:51 +00:00
Keith Williams dfa4e10c83 perf: Add ability to configure Sentry maxSpans (#17411) 2024-10-30 13:54:22 +00:00
c3f03c8000 fix: stripe connect atom for teams (#17294)
* add teamId

* add team app credentials method

* add handler to get all user admin teams

* update stripe logic to handle teams

* enable teamId for stripe connect

* update stripe module

* implement PR feedback

* update stripe module

* add method to get credential by team id

* handle check for teamId

* abstract team and individual stripe connection checks into two separate parts

* hide badge for platform user

* update typing

* add logic to handle team stripe integration

* bump platform libraries

* bump platform libraries

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-10-30 15:28:26 +02:00
Benny Joo aaa5531a3a fix: main google calendar not being connected (#17404) 2024-10-30 11:58:44 +00:00
Morgan 2784564427 Revert "chore: ensure idempotency cancel booking api v2" (#17407)
This reverts commit 85246f0754.
2024-10-30 11:37:17 +00:00
Keith Williams 4b6b9badf5 perf: Sentry perf tracing (#17202)
* chore: Add Sentry perf tracing

* Just to build

* perf: Add Sentry perf tracing

* Removed the extra integrations tracing config

* Got spans working

* Now only tracing individual endpoints

* Removed check for booking event type

* Fixed types

* Added back for spans

* Consolidate sentry imports

* Added more traces

* Moved the start of the span
2024-10-30 00:35:25 +00:00
220 changed files with 6272 additions and 1885 deletions
+12 -3
View File
@@ -16,7 +16,7 @@
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
#
# To enable enterprise-only features please add your environment variable to the .env file then make your way to /auth/setup to select your license and follow instructions.
# To enable enterprise-only features please add your environment variable to the .env file then make your way to /auth/setup to select your license and follow instructions.
CALCOM_LICENSE_KEY=
# Signature token for the Cal.com License API (used for self-hosted integrations)
# We will give you a token when we provide you with a license key this ensure you and only you can communicate with the Cal.com License API for your license key
@@ -102,6 +102,11 @@ NEXT_PUBLIC_INTERCOM_APP_ID=
# Secret to enable Intercom Identity Verification
INTERCOM_SECRET=
# Posthog Config
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=
# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=
@@ -137,6 +142,9 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME=
# Sentry
# Used for capturing exceptions and logging messages
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_DEBUG=
SENTRY_MAX_SPANS=
SENTRY_TRACES_SAMPLE_RATE=
# Formbricks Experience Management Integration
NEXT_PUBLIC_FORMBRICKS_HOST_URL=https://app.formbricks.com
@@ -334,8 +342,6 @@ AB_TEST_BUCKET_PROBABILITY=50
# whether we redirect to the future/event-types from event-types or not
APP_ROUTER_EVENT_TYPES_ENABLED=0
APP_ROUTER_SETTINGS_DEVELOPER_ENABLED=0
APP_ROUTER_SETTINGS_MY_ACCOUNT_ENABLED=0
APP_ROUTER_SETTINGS_PLATFORM_ENABLED=0
APP_ROUTER_SETTINGS_ORG_ENABLED=0
APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0
APP_ROUTER_APPS_SLUG_ENABLED=0
@@ -384,6 +390,9 @@ UNKEY_ROOT_KEY=
# https://retellai.com
RETELL_AI_KEY=
# Used for the huddle01 integration
HUDDLE01_API_TOKEN=
# Used to disallow emails as being added as guests on bookings
BLACKLISTED_GUEST_EMAILS=
@@ -199,6 +199,7 @@ async function handler(req: NextApiRequest) {
eventTypeId,
userId,
returnDateOverrides: true,
bypassBusyCalendarTimes: false,
});
const team = await prisma.team.findUnique({
where: { id: teamId },
@@ -235,6 +236,7 @@ async function handler(req: NextApiRequest) {
dateTo,
eventTypeId,
returnDateOverrides: true,
bypassBusyCalendarTimes: false,
}),
};
});
+1 -1
View File
@@ -28,7 +28,7 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.54",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.56",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -7,6 +7,7 @@ import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_20
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
import { BillingModule } from "@/modules/billing/billing.module";
import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module";
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
@@ -27,6 +28,7 @@ import { Module } from "@nestjs/common";
OutputBookingsService_2024_08_13,
BookingsRepository_2024_08_13,
EventTypesRepository_2024_06_14,
BookingSeatRepository,
ApiKeyRepository,
],
controllers: [BookingsController_2024_08_13],
@@ -196,15 +196,6 @@ export class BookingsController_2024_08_13 {
@Body(new CancelBookingInputPipe())
body: CancelBookingInput
): Promise<CancelBookingOutput_2024_08_13> {
// TODO: implement Idempotency middleware
const alreadyCancelledBooking = await this.bookingsService.getBooking(bookingUid);
if (!Array.isArray(alreadyCancelledBooking) && alreadyCancelledBooking.status === "cancelled") {
return {
status: SUCCESS_STATUS,
data: alreadyCancelledBooking,
};
}
const cancelledBooking = await this.bookingsService.cancelBooking(request, bookingUid, body);
return {
@@ -614,45 +614,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
it("should return already cancelled seated booking (idempotency)", async () => {
const body: CancelSeatedBookingInput_2024_08_13 = {
seatUid: createdSeatedBooking.seatUid,
};
return request(app.getHttpServer())
.post(`/v2/bookings/${createdSeatedBooking.uid}/cancel`)
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(200)
.then(async (response) => {
const responseBody: RescheduleBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsGetSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsGetSeatedBooking(responseBody.data)) {
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
expect(data.id).toBeDefined();
expect(data.uid).toBeDefined();
expect(data.hosts[0].id).toEqual(user.id);
expect(data.status).toEqual("cancelled");
expect(data.start).toEqual(createdSeatedBooking.start);
expect(data.end).toEqual(createdSeatedBooking.end);
expect(data.duration).toEqual(60);
expect(data.eventTypeId).toEqual(seatedEventTypeId);
expect(data.eventType).toEqual({
id: seatedEventTypeId,
slug: seatedTventTypeSlug,
});
expect(data.attendees.length).toEqual(0);
expect(data.location).toBeDefined();
expect(data.absentHost).toEqual(false);
} else {
throw new Error("Invalid response data - expected booking but received array response");
}
});
});
function responseDataIsCreateSeatedBooking(data: any): data is CreateSeatedBookingOutput_2024_08_13 {
return data.hasOwnProperty("seatUid");
}
@@ -681,4 +642,374 @@ describe("Bookings Endpoints 2024-08-13", () => {
await app.close();
});
});
describe("Recurring seated bookings creation", () => {
let app: INestApplication;
let organization: Team;
let userRepositoryFixture: UserRepositoryFixture;
let bookingsRepositoryFixture: BookingsRepositoryFixture;
let schedulesService: SchedulesService_2024_04_15;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let oAuthClient: PlatformOAuthClient;
let teamRepositoryFixture: TeamRepositoryFixture;
const userEmail = "seated-bookings-controller-e2e@api.com";
let user: User;
const seatedTventTypeSlug = "peer-coding-seated";
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
let seatedEventTypeId: number;
let recurringSeatedEventTypeId: number;
const maxRecurrenceCount = 3;
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
const emailAttendeeOne = "mr_proper_first@gmail.com";
const nameAttendeeOne = "Mr Proper First";
const emailAttendeeTwo = "mr_proper_second@gmail.com";
const nameAttendeeTwo = "Mr Proper Second";
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
oAuthClient = await createOAuthClient(organization.id);
user = await userRepositoryFixture.create({
email: userEmail,
});
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: "working time",
timeZone: "Europe/Rome",
isDefault: true,
};
await schedulesService.createUserSchedule(user.id, userSchedule);
const seatedEvent = await eventTypesRepositoryFixture.create(
{
title: "peer coding",
slug: seatedTventTypeSlug,
length: 60,
seatsPerTimeSlot: 5,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
},
user.id
);
seatedEventTypeId = seatedEvent.id;
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
{
title: "peer coding recurring",
slug: recurringSeatedTventTypeSlug,
length: 60,
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
seatsPerTimeSlot: 5,
seatsShowAttendees: true,
seatsShowAvailabilityCount: true,
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
},
user.id
);
recurringSeatedEventTypeId = recurringSeatedEvent.id;
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["http://localhost:5555"],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should book a recurring event type with seats for the first time", async () => {
const recurrenceCount = 2;
const body: CreateRecurringBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(),
eventTypeId: recurringSeatedEventTypeId,
attendee: {
name: nameAttendeeOne,
email: emailAttendeeOne,
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
codingLanguage: "TypeScript",
},
recurrenceCount,
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsCreateRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsCreateRecurringSeatedBooking(responseBody.data)) {
const data: CreateRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
expect(data.length).toEqual(recurrenceCount);
const firstBooking = data[0];
expect(firstBooking.seatUid).toBeDefined();
const seatUid = firstBooking.seatUid;
expect(firstBooking.id).toBeDefined();
expect(firstBooking.uid).toBeDefined();
expect(firstBooking.hosts[0].id).toEqual(user.id);
expect(firstBooking.status).toEqual("accepted");
expect(firstBooking.start).toEqual(body.start);
expect(firstBooking.end).toEqual(
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(firstBooking.duration).toEqual(60);
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(firstBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedTventTypeSlug,
});
expect(firstBooking.attendees.length).toEqual(1);
expect(firstBooking.attendees[0]).toEqual({
name: body.attendee.name,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
},
});
expect(firstBooking.location).toEqual(body.location);
expect(firstBooking.absentHost).toEqual(false);
const secondBooking = data[1];
expect(secondBooking.seatUid).toBeDefined();
const secondSeatUid = secondBooking.seatUid;
expect(secondBooking.id).toBeDefined();
expect(secondBooking.uid).toBeDefined();
expect(secondBooking.hosts[0].id).toEqual(user.id);
expect(secondBooking.status).toEqual("accepted");
const expectedStart = DateTime.fromISO(body.start, { zone: "utc" }).plus({ weeks: 1 }).toISO();
expect(secondBooking.start).toEqual(expectedStart);
expect(secondBooking.end).toEqual(
DateTime.fromISO(expectedStart!, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(secondBooking.duration).toEqual(60);
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(secondBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedTventTypeSlug,
});
expect(secondBooking.attendees.length).toEqual(1);
expect(secondBooking.attendees[0]).toEqual({
name: body.attendee.name,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: secondSeatUid,
bookingFieldsResponses: {
name: body.attendee.name,
...body.bookingFieldsResponses,
},
});
expect(secondBooking.location).toEqual(body.location);
expect(secondBooking.absentHost).toEqual(false);
createdRecurringSeatedBooking = data;
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
it("should book a recurring event type with seats for the second time", async () => {
const recurrenceCount = 2;
const body: CreateRecurringBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 11, 0, 0)).toISOString(),
eventTypeId: recurringSeatedEventTypeId,
attendee: {
name: nameAttendeeTwo,
email: emailAttendeeTwo,
timeZone: "Europe/Rome",
language: "it",
},
location: "https://meet.google.com/abc-def-ghi",
bookingFieldsResponses: {
codingLanguage: "TypeScript",
},
recurrenceCount,
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsCreateRecurringSeatedBooking(responseBody.data)).toBe(true);
if (responseDataIsCreateRecurringSeatedBooking(responseBody.data)) {
const data: CreateRecurringSeatedBookingOutput_2024_08_13[] = responseBody.data;
expect(data.length).toEqual(recurrenceCount);
const firstBooking = data[0];
expect(firstBooking.seatUid).toBeDefined();
const seatUid = firstBooking.seatUid;
expect(firstBooking.id).toBeDefined();
expect(firstBooking.uid).toBeDefined();
expect(firstBooking.hosts[0].id).toEqual(user.id);
expect(firstBooking.status).toEqual("accepted");
expect(firstBooking.start).toEqual(body.start);
expect(firstBooking.end).toEqual(
DateTime.fromISO(body.start, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(firstBooking.duration).toEqual(60);
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(firstBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedTventTypeSlug,
});
expect(firstBooking.attendees.length).toEqual(2);
expect(firstBooking.attendees[0]).toEqual({
name: nameAttendeeOne,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: createdRecurringSeatedBooking[0].seatUid,
bookingFieldsResponses: {
name: nameAttendeeOne,
...body.bookingFieldsResponses,
},
});
expect(firstBooking.attendees[1]).toEqual({
name: nameAttendeeTwo,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid,
bookingFieldsResponses: {
name: nameAttendeeTwo,
...body.bookingFieldsResponses,
},
});
expect(firstBooking.location).toEqual(body.location);
expect(firstBooking.absentHost).toEqual(false);
const secondBooking = data[1];
expect(secondBooking.seatUid).toBeDefined();
const secondSeatUid = secondBooking.seatUid;
expect(secondBooking.id).toBeDefined();
expect(secondBooking.uid).toBeDefined();
expect(secondBooking.hosts[0].id).toEqual(user.id);
expect(secondBooking.status).toEqual("accepted");
const expectedStart = DateTime.fromISO(body.start, { zone: "utc" }).plus({ weeks: 1 }).toISO();
expect(secondBooking.start).toEqual(expectedStart);
expect(secondBooking.end).toEqual(
DateTime.fromISO(expectedStart!, { zone: "utc" }).plus({ hours: 1 }).toISO()
);
expect(secondBooking.duration).toEqual(60);
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
expect(secondBooking.eventType).toEqual({
id: recurringSeatedEventTypeId,
slug: recurringSeatedTventTypeSlug,
});
expect(secondBooking.attendees.length).toEqual(2);
expect(secondBooking.attendees[0]).toEqual({
name: nameAttendeeOne,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: createdRecurringSeatedBooking[1].seatUid,
bookingFieldsResponses: {
name: nameAttendeeOne,
...body.bookingFieldsResponses,
},
});
expect(secondBooking.attendees[1]).toEqual({
name: nameAttendeeTwo,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
seatUid: secondSeatUid,
bookingFieldsResponses: {
name: nameAttendeeTwo,
...body.bookingFieldsResponses,
},
});
expect(secondBooking.location).toEqual(body.location);
expect(secondBooking.absentHost).toEqual(false);
createdRecurringSeatedBooking = data;
} else {
throw new Error(
"Invalid response data - expected recurring booking but received non array response"
);
}
});
});
function responseDataIsCreateRecurringSeatedBooking(
data: any
): data is CreateRecurringSeatedBookingOutput_2024_08_13[] {
return Array.isArray(data);
}
function responseDataIsGetSeatedBooking(data: any): data is GetSeatedBookingOutput_2024_08_13 {
return data?.attendees?.every((attendee: any) => attendee?.hasOwnProperty("seatUid"));
}
function responseDataIsGetRecurringSeatedBooking(
data: any
): data is GetRecurringSeatedBookingOutput_2024_08_13[] {
return Array.isArray(data);
}
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
await userRepositoryFixture.deleteByEmail(user.email);
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
await app.close();
});
});
});
@@ -3,6 +3,7 @@ import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/servic
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { BillingService } from "@/modules/billing/services/billing.service";
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import { BadRequestException } from "@nestjs/common";
@@ -46,6 +47,7 @@ export class BookingsService_2024_08_13 {
private readonly inputService: InputBookingsService_2024_08_13,
private readonly outputService: OutputBookingsService_2024_08_13,
private readonly bookingsRepository: BookingsRepository_2024_08_13,
private readonly bookingSeatRepository: BookingSeatRepository,
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
private readonly prismaReadService: PrismaReadService,
private readonly billingService: BillingService
@@ -107,7 +109,7 @@ export class BookingsService_2024_08_13 {
const bookingRequest = await this.inputService.createRecurringBookingRequest(request, body);
const bookings = await handleNewRecurringBooking(bookingRequest);
return this.outputService.getOutputCreateRecurringSeatedBookings(
bookings.map((booking) => ({ id: booking.id || 0, seatUid: booking.seatReferenceUid || "" }))
bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" }))
);
}
@@ -282,6 +284,20 @@ export class BookingsService_2024_08_13 {
}
async cancelBooking(request: Request, bookingUid: string, body: CancelBookingInput) {
if (this.inputService.isCancelSeatedBody(body)) {
const seat = await this.bookingSeatRepository.getByReferenceUid(body.seatUid);
if (!seat) {
throw new BadRequestException(
"Invalid seatUid: this seat does not exist or has already been cancelled."
);
}
if (seat && bookingUid !== seat.booking.uid) {
throw new BadRequestException("Invalid seatUid: this seat does not belong to this booking.");
}
}
const bookingRequest = await this.inputService.createCancelBookingRequest(request, bookingUid, body);
await handleCancelBooking(bookingRequest);
return this.getBooking(bookingUid);
@@ -230,14 +230,14 @@ export class OutputBookingsService_2024_08_13 {
return transformed.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
}
async getOutputCreateRecurringSeatedBookings(bookings: { id: number; seatUid: string }[]) {
async getOutputCreateRecurringSeatedBookings(bookings: { uid: string; seatUid: string }[]) {
const transformed = [];
for (const booking of bookings) {
const databaseBooking =
await this.bookingsRepository.getByIdWithAttendeesWithBookingSeatAndUserAndEvent(booking.id);
await this.bookingsRepository.getByUidWithAttendeesWithBookingSeatAndUserAndEvent(booking.uid);
if (!databaseBooking) {
throw new Error(`Booking with id=${booking.id} was not found in the database`);
throw new Error(`Booking with uid=${booking.uid} was not found in the database`);
}
transformed.push(this.getOutputCreateRecurringSeatedBooking(databaseBooking, booking.seatUid));
}
@@ -21,4 +21,15 @@ export class AppsRepository {
},
});
}
async createTeamAppCredential(type: string, key: Prisma.InputJsonValue, teamId: number, appId: string) {
return this.dbWrite.prisma.credential.create({
data: {
type: type,
key: key,
teamId: teamId,
appId: appId,
},
});
}
}
@@ -1,5 +1,6 @@
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { AtomsController } from "@/modules/atoms/controllers/atoms.controller";
import { EventTypesAtomService } from "@/modules/atoms/services/event-types-atom.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
@@ -17,6 +18,7 @@ import { Module } from "@nestjs/common";
MembershipsRepository,
CredentialsRepository,
UsersRepository,
AtomsRepository,
UsersService,
SchedulesRepository_2024_06_11,
],
@@ -0,0 +1,17 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { paymentDataSelect } from "@calcom/platform-libraries";
@Injectable()
export class AtomsRepository {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async getRawPayment(uid: string) {
return await this.dbRead.prisma.payment.findFirst({
where: { uid },
select: paymentDataSelect,
});
}
}
@@ -72,6 +72,18 @@ export class AtomsController {
};
}
@Get("payment/:uid")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
async getUserPaymentInfoById(@Param("uid") uid: string): Promise<ApiResponse<unknown>> {
const data = await this.eventTypesService.getUserPaymentInfo(uid);
return {
status: SUCCESS_STATUS,
data,
};
}
@Patch("event-types/:eventTypeId")
@Version(VERSION_NEUTRAL)
@UseGuards(ApiAuthGuard)
@@ -1,4 +1,5 @@
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { OrganizationsTeamsRepository } from "@/modules/organizations/repositories/organizations-teams.repository";
@@ -17,6 +18,7 @@ import {
getEnabledAppsFromCredentials,
getAppFromSlug,
MembershipRole,
EventTypeMetaDataSchema,
} from "@calcom/platform-libraries";
import type {
App,
@@ -27,6 +29,7 @@ import type {
TDependencyData,
CredentialPayload,
} from "@calcom/platform-libraries";
import { getClientSecretFromPayment } from "@calcom/platform-libraries";
import { PrismaClient } from "@calcom/prisma/client";
type EnabledAppType = App & {
@@ -40,6 +43,7 @@ export class EventTypesAtomService {
constructor(
private readonly membershipsRepository: MembershipsRepository,
private readonly credentialsRepository: CredentialsRepository,
private readonly atomsRepository: AtomsRepository,
private readonly organizationsTeamsRepository: OrganizationsTeamsRepository,
private readonly usersService: UsersService,
private readonly dbWrite: PrismaWriteService,
@@ -274,4 +278,43 @@ export class EventTypesAtomService {
);
return apps[0];
}
async getUserPaymentInfo(uid: string) {
const rawPayment = await this.atomsRepository.getRawPayment(uid);
if (!rawPayment) throw new NotFoundException(`Payment with uid ${uid} not found`);
const { data, booking: _booking, ...restPayment } = rawPayment;
const payment = {
...restPayment,
data: data as Record<string, unknown>,
};
if (!_booking) throw new NotFoundException(`Booking with uid ${uid} not found`);
const { startTime, endTime, eventType, ...restBooking } = _booking;
const booking = {
...restBooking,
startTime: startTime.toString(),
endTime: endTime.toString(),
};
if (!eventType) throw new NotFoundException(`Event type with uid ${uid} not found`);
if (eventType.users.length === 0 && !!!eventType.team)
throw new NotFoundException(`No users found or no team present for event type with uid ${uid}`);
const [user] = eventType?.users.length
? eventType.users
: [{ name: null, theme: null, hideBranding: null, username: null }];
const profile = {
name: eventType.team?.name || user?.name || null,
theme: (!eventType.team?.name && user?.theme) || null,
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
};
return {
user,
eventType: {
...eventType,
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
},
booking,
payment,
clientSecret: getClientSecretFromPayment(payment),
profile,
};
}
}
@@ -12,6 +12,9 @@ export class BookingSeatRepository {
where: {
referenceUid,
},
include: {
booking: { select: { uid: true } },
},
});
}
}
@@ -37,6 +37,10 @@ export class CredentialsRepository {
return this.dbWrite.prisma.credential.findFirst({ where: { type, userId } });
}
getByTypeAndTeamId(type: string, teamId: number) {
return this.dbWrite.prisma.credential.findFirst({ where: { type, teamId } });
}
getAllUserCredentialsByTypeAndId(type: string, userId: number) {
return this.dbRead.prisma.credential.findMany({ where: { type, userId } });
}
@@ -510,7 +510,7 @@ describe("OAuth Client Users Endpoints", () => {
expect(oAuthClient1).toBeDefined();
});
it(`should create managed user and update team event-types of OAuthClient marked as assignAllTeamMembers: true`, async () => {
it(`should create managed user and update team event-types`, async () => {
const requestBody: CreateManagedUserInput = {
email: userEmail,
timeZone: userTimeZone,
@@ -532,48 +532,13 @@ describe("OAuth Client Users Endpoints", () => {
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
await userHasCorrectEventTypes(responseBody.data.user.id);
await teamHasCorrectEventTypes(team1.id);
expect(responseBody.data.user.name).toEqual(requestBody.name);
});
async function userHasCorrectEventTypes(userId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
expect(eventTypes?.length).toEqual(5);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
// note(Lauris): default event types
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutes.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutes.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.thirtyMinutesVideo.slug)
).toBeTruthy();
expect(
eventTypes?.find((eventType) => eventType.slug === DEFAULT_EVENT_TYPES.sixtyMinutesVideo.slug)
).toBeTruthy();
}
async function teamHasCorrectEventTypes(teamId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
expect(eventTypes?.length).toEqual(2);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType1.slug)).toBeTruthy();
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
expect(collective).toBeTruthy();
expect(collective?.hosts).toBeDefined();
expect(collective?.hosts?.length).toEqual(1);
expect(collective?.hosts[0].userId).toEqual(postResponseData.user.id);
}
afterAll(async () => {
@@ -83,8 +83,6 @@ export class OAuthClientUsersService {
user.defaultScheduleId = defaultSchedule.id;
}
await this.organizationsTeamsService.addUserToPlatformTeamEvents(user.id, organizationId, oAuthClientId);
return {
user,
tokens: {
@@ -9,8 +9,9 @@ import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { User } from "@prisma/client";
import { EventType, User } from "@prisma/client";
import * as request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
@@ -26,6 +27,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
describe("User Authentication - User is Org Admin", () => {
let app: INestApplication;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
@@ -36,6 +38,8 @@ describe("Organizations Teams Memberships Endpoints", () => {
let org: Team;
let orgTeam: Team;
let nonOrgTeam: Team;
let teamEventType: EventType;
let managedEventType: EventType;
let membership: Membership;
let membership2: Membership;
let membershipCreatedViaApi: OrgTeamMembershipOutputDto;
@@ -66,6 +70,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
user = await userRepositoryFixture.create({
email: userEmail,
@@ -97,6 +102,32 @@ describe("Organizations Teams Memberships Endpoints", () => {
parent: { connect: { id: org.id } },
});
teamEventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: orgTeam.id },
},
title: "Collective Event Type",
slug: "collective-event-type",
length: 30,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
managedEventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "MANAGED",
team: {
connect: { id: orgTeam.id },
},
title: "Managed Event Type",
slug: "managed-event-type",
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
});
nonOrgTeam = await teamsRepositoryFixture.create({
name: "Non Org Team",
isOrganization: false,
@@ -264,7 +295,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
.expect(404);
});
it("should create the membership of the org's team", async () => {
it("should have created the membership of the org's team and assigned team wide events", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.send({
@@ -281,9 +312,22 @@ describe("Organizations Teams Memberships Endpoints", () => {
expect(membershipCreatedViaApi.role).toEqual("MEMBER");
expect(membershipCreatedViaApi.userId).toEqual(userToInviteViaApi.id);
expect(membershipCreatedViaApi.user.email).toEqual(userToInviteViaApi.email);
userHasCorrectEventTypes(membershipCreatedViaApi.userId);
});
});
async function userHasCorrectEventTypes(userId: number) {
const managedEventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
const teamEventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(orgTeam.id);
expect(managedEventTypes?.length).toEqual(1);
expect(teamEventTypes?.length).toEqual(2);
const collectiveEvenType = teamEventTypes?.find((eventType) => eventType.slug === teamEventType.slug);
expect(collectiveEvenType).toBeTruthy();
const userHost = collectiveEvenType?.hosts.find((host) => host.userId === userId);
expect(userHost).toBeTruthy();
expect(managedEventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
}
it("should fail to create the membership of the org's team for a non org user", async () => {
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
@@ -15,6 +15,7 @@ import {
OrgTeamMembershipsOutputResponseDto,
OrgTeamMembershipOutputResponseDto,
} from "@/modules/organizations/outputs/organization-teams-memberships.output";
import { OrganizationsEventTypesService } from "@/modules/organizations/services/event-types/organizations-event-types.service";
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/services/organizations-teams-memberships.service";
import {
Controller,
@@ -35,6 +36,7 @@ import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries";
import { SkipTakePagination } from "@calcom/platform-types";
@Controller({
@@ -46,6 +48,7 @@ import { SkipTakePagination } from "@calcom/platform-types";
export class OrganizationsTeamsMembershipsController {
constructor(
private organizationsTeamsMembershipsService: OrganizationsTeamsMembershipsService,
private oganizationsEventTypesService: OrganizationsEventTypesService,
private readonly organizationsRepository: OrganizationsRepository
) {}
@@ -112,6 +115,9 @@ export class OrganizationsTeamsMembershipsController {
teamId,
membershipId
);
await this.oganizationsEventTypesService.deleteUserTeamEventTypesAndHosts(membership.userId, teamId);
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
@@ -129,15 +135,25 @@ export class OrganizationsTeamsMembershipsController {
@Param("membershipId", ParseIntPipe) membershipId: number,
@Body() data: UpdateOrgTeamMembershipDto
): Promise<OrgTeamMembershipOutputResponseDto> {
const membership = await this.organizationsTeamsMembershipsService.updateOrgTeamMembership(
const currentMembership = await this.organizationsTeamsMembershipsService.getOrgTeamMembership(
orgId,
teamId,
membershipId
);
const updatedMembership = await this.organizationsTeamsMembershipsService.updateOrgTeamMembership(
orgId,
teamId,
membershipId,
data
);
if (!currentMembership.accepted && updatedMembership.accepted) {
await updateNewTeamMemberEventTypes(updatedMembership.userId, teamId);
}
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
data: plainToClass(OrgTeamMembershipOutputDto, updatedMembership, { strategy: "excludeAll" }),
};
}
@@ -158,6 +174,9 @@ export class OrganizationsTeamsMembershipsController {
}
const membership = await this.organizationsTeamsMembershipsService.createOrgTeamMembership(teamId, data);
if (membership.accepted) {
await updateNewTeamMemberEventTypes(user.id, teamId);
}
return {
status: SUCCESS_STATUS,
data: plainToClass(OrgTeamMembershipOutputDto, membership, { strategy: "excludeAll" }),
@@ -469,34 +469,13 @@ describe("Organizations Users Endpoints", () => {
const userData = body.data;
expect(body.status).toBe(SUCCESS_STATUS);
userHasCorrectEventTypes(userData.id);
createdUser = userData;
teamHasCorrectEventTypes(team.id);
});
async function userHasCorrectEventTypes(userId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllUserEventTypes(userId);
expect(eventTypes?.length).toEqual(1);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
}
async function teamHasCorrectEventTypes(teamId: number) {
const eventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(teamId);
expect(eventTypes?.length).toEqual(2);
// note(Lauris): managed event-types with assignAllTeamMembers: true
expect(eventTypes?.find((eventType) => eventType.slug === managedEventType.slug)).toBeTruthy();
// note(Lauris): check if managed user added to collective event-type hosts given that it has assignAllTeamMembers: true
const collective = eventTypes?.find((eventType) => eventType.schedulingType === "COLLECTIVE");
expect(collective).toBeTruthy();
expect(collective?.hosts).toBeDefined();
expect(collective?.hosts?.length).toEqual(1);
expect(collective?.hosts[0].userId).toEqual(createdUser.id);
}
afterAll(async () => {
@@ -70,4 +70,26 @@ export class OrganizationsEventTypesRepository {
include: { children: true },
});
}
async deleteUserManagedTeamEventTypes(userId: number, teamId: number) {
return this.dbWrite.prisma.eventType.deleteMany({
where: {
parent: {
teamId,
},
userId,
},
});
}
async removeUserFromTeamEventTypesHosts(userId: number, teamId: number) {
return this.dbWrite.prisma.host.deleteMany({
where: {
userId,
eventType: {
teamId,
},
},
});
}
}
@@ -6,13 +6,15 @@ import { DatabaseTeamEventType } from "@/modules/organizations/services/event-ty
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UsersService } from "@/modules/users/services/users.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { Injectable, NotFoundException, Logger } from "@nestjs/common";
import { createEventType, updateEventType } from "@calcom/platform-libraries";
import { InputTeamEventTransformed_2024_06_14 } from "@calcom/platform-types";
@Injectable()
export class OrganizationsEventTypesService {
private readonly logger = new Logger("OrganizationsEventTypesService");
constructor(
private readonly eventTypesService: EventTypesService_2024_06_14,
private readonly dbWrite: PrismaWriteService,
@@ -145,4 +147,17 @@ export class OrganizationsEventTypesService {
return this.eventTypesRepository.deleteEventType(eventTypeId);
}
async deleteUserTeamEventTypesAndHosts(userId: number, teamId: number) {
try {
await this.organizationEventTypesRepository.deleteUserManagedTeamEventTypes(userId, teamId);
await this.organizationEventTypesRepository.removeUserFromTeamEventTypesHosts(userId, teamId);
} catch (err) {
this.logger.error("Could not remove user from all team event-types.", {
error: err,
userId,
teamId,
});
}
}
}
@@ -5,8 +5,6 @@ import { OrganizationsTeamsRepository } from "@/modules/organizations/repositori
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import { updateNewTeamMemberEventTypes } from "@calcom/platform-libraries";
@Injectable()
export class OrganizationsTeamsService {
constructor(
@@ -69,23 +67,4 @@ export class OrganizationsTeamsService {
}
return team;
}
async addUserToTeamEvents(userId: number, organizationId: number) {
const orgTeams = await this.organizationsTeamRepository.findOrgTeams(organizationId);
for (const team of orgTeams) {
await updateNewTeamMemberEventTypes(userId, team.id);
}
}
async addUserToPlatformTeamEvents(userId: number, organizationId: number, oAuthClientId: string) {
const oAuthClientTeams = await this.organizationsTeamRepository.getPlatformOrgTeams(
organizationId,
oAuthClientId
);
for (const team of oAuthClientTeams) {
await updateNewTeamMemberEventTypes(userId, team.id);
}
}
}
@@ -79,8 +79,6 @@ export class OrganizationsUsersService {
updateUserBody
);
await this.organizationsTeamsService.addUserToTeamEvents(user.id, org.id);
// Need to send email to new user to create password
await this.emailService.sendSignupToOrganizationEmail({
usernameOrEmail,
@@ -21,6 +21,7 @@ import {
Req,
BadRequestException,
Headers,
Param,
} from "@nestjs/common";
import { ApiTags as DocsTags, ApiOperation } from "@nestjs/swagger";
import { plainToClass } from "class-transformer";
@@ -46,7 +47,8 @@ export class StripeController {
@Headers("Authorization") authorization: string,
@GetUser() user: UserWithProfile,
@Query("redir") redir?: string | null,
@Query("errorRedir") errorRedir?: string | null
@Query("errorRedir") errorRedir?: string | null,
@Query("teamId") teamId?: string | null
): Promise<StripConnectOutputResponseDto> {
const origin = req.headers.origin;
const accessToken = authorization.replace("Bearer ", "");
@@ -56,6 +58,7 @@ export class StripeController {
fromApp: false,
returnTo: !!redir ? redir : origin,
accessToken,
teamId: Number(teamId) ?? null,
};
const stripeRedirectUrl = await this.stripeService.getStripeRedirectUrl(
@@ -99,6 +102,16 @@ export class StripeController {
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Check stripe connection" })
async check(@GetUser() user: UserWithProfile): Promise<StripCredentialsCheckOutputResponseDto> {
return await this.stripeService.checkIfStripeAccountConnected(user.id);
return await this.stripeService.checkIfIndividualStripeAccountConnected(user.id);
}
@Get("/check/:teamId")
@UseGuards(ApiAuthGuard)
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: "Check team stripe connection" })
async checkTeamStripeConnection(
@Param("teamId") teamId: string
): Promise<StripCredentialsCheckOutputResponseDto> {
return await this.stripeService.checkIfTeamStripeAccountConnected(Number(teamId));
}
}
@@ -1,5 +1,6 @@
import { AppsRepository } from "@/modules/apps/apps.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { StripeController } from "@/modules/stripe/controllers/stripe.controller";
import { StripeService } from "@/modules/stripe/stripe.service";
@@ -10,7 +11,7 @@ import { ConfigModule } from "@nestjs/config";
@Module({
imports: [ConfigModule, PrismaModule],
exports: [StripeService],
providers: [StripeService, AppsRepository, CredentialsRepository, TokensRepository],
providers: [StripeService, AppsRepository, CredentialsRepository, TokensRepository, MembershipsRepository],
controllers: [StripeController],
})
export class StripeModule {}
@@ -1,13 +1,14 @@
import { AppConfig } from "@/config/type";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { getReturnToValueFromQueryState } from "@/modules/stripe/utils/getReturnToValueFromQueryState";
import { stripeInstance } from "@/modules/stripe/utils/newStripeInstance";
import { StripeData } from "@/modules/stripe/utils/stripeDataSchemas";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { Injectable, NotFoundException, BadRequestException, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import type { Prisma } from "@prisma/client";
import type { Prisma, Credential } from "@prisma/client";
import Stripe from "stripe";
import { z } from "zod";
@@ -17,6 +18,14 @@ import { stripeKeysResponseSchema } from "./utils/stripeDataSchemas";
import stringify = require("qs-stringify");
type IntegrationOAuthCallbackState = {
accessToken: string;
returnTo: string;
onErrorReturnTo: string;
fromApp: boolean;
teamId?: number | null;
};
@Injectable()
export class StripeService {
public stripe: Stripe;
@@ -27,7 +36,8 @@ export class StripeService {
private readonly config: ConfigService,
private readonly appsRepository: AppsRepository,
private readonly credentialRepository: CredentialsRepository,
private readonly tokensRepository: TokensRepository
private readonly tokensRepository: TokensRepository,
private readonly membershipRepository: MembershipsRepository
) {
this.stripe = new Stripe(configService.get("stripe.apiKey", { infer: true }) ?? "", {
apiVersion: "2020-08-27",
@@ -76,6 +86,7 @@ export class StripeService {
async saveStripeAccount(state: string, code: string, accessToken: string): Promise<{ url: string }> {
const userId = await this.tokensRepository.getAccessTokenOwnerId(accessToken);
const oAuthCallbackState: IntegrationOAuthCallbackState = JSON.parse(state);
if (!userId) {
throw new UnauthorizedException("Invalid Access token.");
@@ -92,6 +103,19 @@ export class StripeService {
data["default_currency"] = account.default_currency;
}
if (oAuthCallbackState.teamId) {
await this.checkIfUserHasAdminAccessToTeam(oAuthCallbackState.teamId, userId);
await this.appsRepository.createTeamAppCredential(
"stripe_payment",
data as unknown as Prisma.InputJsonObject,
oAuthCallbackState.teamId,
"stripe"
);
return { url: getReturnToValueFromQueryState(state) };
}
await this.appsRepository.createAppCredential(
"stripe_payment",
data as unknown as Prisma.InputJsonObject,
@@ -102,18 +126,39 @@ export class StripeService {
return { url: getReturnToValueFromQueryState(state) };
}
async checkIfStripeAccountConnected(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
async checkIfIndividualStripeAccountConnected(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
const stripeCredentials = await this.credentialRepository.getByTypeAndUserId("stripe_payment", userId);
if (!stripeCredentials) {
return await this.validateStripeCredentials(stripeCredentials);
}
async checkIfTeamStripeAccountConnected(teamId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
const stripeCredentials = await this.credentialRepository.getByTypeAndTeamId("stripe_payment", teamId);
return await this.validateStripeCredentials(stripeCredentials);
}
async checkIfUserHasAdminAccessToTeam(teamId: number, userId: number) {
const teamMembership = await this.membershipRepository.findMembershipByTeamId(teamId, userId);
const hasAdminAccessToTeam = teamMembership?.role === "ADMIN" || teamMembership?.role === "OWNER";
if (!hasAdminAccessToTeam) {
throw new BadRequestException("You must be team owner or admin to do this");
}
}
async validateStripeCredentials(
credentials?: Credential | null
): Promise<{ status: typeof SUCCESS_STATUS }> {
if (!credentials) {
throw new NotFoundException("Credentials for stripe not found.");
}
if (stripeCredentials.invalid) {
if (credentials.invalid) {
throw new BadRequestException("Invalid stripe credentials.");
}
const stripeKey = JSON.stringify(stripeCredentials.key);
const stripeKey = JSON.stringify(credentials.key);
const stripeKeyObject = JSON.parse(stripeKey);
const stripeAccount = await stripeInstance.accounts.retrieve(stripeKeyObject?.stripe_user_id);
-2
View File
@@ -8,8 +8,6 @@ import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME }
const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
["/settings/developer/:path*", process.env.APP_ROUTER_SETTINGS_DEVELOPER_ENABLED === "1"] as const,
["/settings/my-account/:path*", process.env.APP_ROUTER_SETTINGS_MY_ACCOUNT_ENABLED === "1"] as const,
["/settings/platform/:path*", process.env.APP_ROUTER_SETTINGS_PLATFORM_ENABLED === "1"] as const,
["/settings/organizations/:path*", process.env.APP_ROUTER_SETTINGS_ORG_ENABLED === "1"] as const,
["/apps/installed/:category", process.env.APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED === "1"] as const,
["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_ENABLED === "1"] as const,
@@ -5,7 +5,7 @@ import EditView from "~/settings/platform/oauth-clients/[clientId]/edit/edit-vie
export const generateMetadata = async () =>
await _generateMetadata(
() => "OAuth client updation form",
(t) => t("oAuth_client_updation_form"),
() => ""
);
@@ -5,7 +5,7 @@ import EditWebhooksView from "~/settings/platform/oauth-clients/[clientId]/edit/
export const generateMetadata = async () =>
await _generateMetadata(
() => "OAuth client updation form",
(t) => t("oAuth_client_updation_form"),
() => ""
);
@@ -0,0 +1,15 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import PlatformBillingUpgrade from "~/settings/platform/billing/billing-view";
export const generateMetadata = async () => {
return await _generateMetadata(
(t) => t("platform_billing"),
(t) => t("manage_billing_description")
);
};
export default WithLayout({
Page: PlatformBillingUpgrade,
});
@@ -0,0 +1,21 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
import { type inferSSRProps } from "@lib/types/inferSSRProps";
import LegacyPage, { LayoutWrapper } from "~/settings/platform/new/create-new-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("set_up_your_platform_organization"),
(t) => t("platform_organization_description")
);
export default WithLayout({
getLayout: LayoutWrapper,
Page: LegacyPage,
getData: withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps),
requiresLicense: true,
});
@@ -0,0 +1,15 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import EditView from "~/settings/platform/oauth-clients/[clientId]/edit/edit-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("oAuth_client_updation_form"),
() => ""
);
export default WithLayout({
getLayout: null,
Page: EditView,
});
@@ -0,0 +1,15 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import EditWebhooksView from "~/settings/platform/oauth-clients/[clientId]/edit/edit-webhooks-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("webhook_update_form"),
() => ""
);
export default WithLayout({
getLayout: null,
Page: EditWebhooksView,
});
@@ -0,0 +1,16 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import CreateNewView from "~/settings/platform/oauth-clients/create-new-view";
export const generateMetadata = async () => {
return await _generateMetadata(
(t) => t("oAuth_client_creation_form"),
() => ""
);
};
export default WithLayout({
getLayout: null,
Page: CreateNewView,
});
+16
View File
@@ -0,0 +1,16 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import PlatformView from "~/settings/platform/platform-view";
export const generateMetadata = async () => {
return await _generateMetadata(
(t) => t("platform"),
(t) => t("platform_description")
);
};
export default WithLayout({
getLayout: null,
Page: PlatformView,
});
@@ -0,0 +1,15 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import PlatformPlansView from "~/settings/platform/plans/platform-plans-view";
export const generateMetadata = async () => {
return await _generateMetadata(
(t) => `${t("platform")} ${t("plans")}`,
() => ""
);
};
export default WithLayout({
Page: PlatformPlansView,
});
+32 -16
View File
@@ -106,10 +106,10 @@ export const ReassignDialog = ({ isOpenDialog, setIsOpenDialog, teamId, bookingI
});
const roundRobinReassignMutation = trpc.viewer.teams.roundRobinReassign.useMutation({
onSuccess: async () => {
onSuccess: async (data) => {
await utils.viewer.bookings.get.invalidate();
setIsOpenDialog(false);
showToast(t("booking_reassigned"), "success");
showToast(t("booking_reassigned_to_host", { host: data?.reassignedTo.name }), "success");
},
onError: async (error) => {
if (error.message.includes(ErrorCode.NoAvailableUsersFound)) {
@@ -135,7 +135,13 @@ export const ReassignDialog = ({ isOpenDialog, setIsOpenDialog, teamId, bookingI
},
});
const [showConfirmation, setShowConfirmation] = useState(false);
const [confirmationModal, setConfirmationModal] = useState<{
show: boolean;
membersStatus: "unavailable" | "available" | null;
}>({
show: false,
membersStatus: null,
});
const handleSubmit = (values: FormValues) => {
if (values.reassignType === "round_robin") {
@@ -143,13 +149,10 @@ export const ReassignDialog = ({ isOpenDialog, setIsOpenDialog, teamId, bookingI
} else {
if (values.teamMemberId) {
const selectedMember = teamMemberOptions?.find((member) => member.value === values.teamMemberId);
if (selectedMember && selectedMember.status === "unavailable") {
setShowConfirmation(true);
} else {
roundRobinManualReassignMutation.mutate({
bookingId,
teamMemberId: values.teamMemberId,
reassignReason: values.reassignReason,
if (selectedMember) {
setConfirmationModal({
show: true,
membersStatus: selectedMember.status as "available" | "unavailable",
});
}
}
@@ -263,10 +266,16 @@ export const ReassignDialog = ({ isOpenDialog, setIsOpenDialog, teamId, bookingI
</DialogContent>
</Dialog>
<Dialog open={showConfirmation} onOpenChange={setShowConfirmation}>
<Dialog
open={confirmationModal?.show}
onOpenChange={(open) => setConfirmationModal({ ...confirmationModal, show: open })}>
<ConfirmationDialogContent
variety="warning"
title={t("confirm_reassign_unavailable")}
variety={confirmationModal?.membersStatus === "unavailable" ? "warning" : "success"}
title={
confirmationModal?.membersStatus === "unavailable"
? t("confirm_reassign_unavailable")
: t("confirm_reassign_available")
}
confirmBtnText={t("yes_reassign")}
cancelBtnText={t("cancel")}
onConfirm={() => {
@@ -279,14 +288,21 @@ export const ReassignDialog = ({ isOpenDialog, setIsOpenDialog, teamId, bookingI
teamMemberId,
reassignReason: form.getValues("reassignReason"),
});
setShowConfirmation(false);
setConfirmationModal({
show: false,
membersStatus: null,
});
}}>
<p className="mb-4">{t("reassign_unavailable_team_member_description")}</p>
<p className="mb-4">
{confirmationModal?.membersStatus === "unavailable"
? t("reassign_unavailable_team_member_description")
: t("reassign_available_team_member_description")}
</p>
<TextAreaField
name="reassignReason"
label={t("reassign_reason")}
onChange={(e) => form.setValue("reassignReason", e.target.value)}
required
required={confirmationModal?.membersStatus === "unavailable"}
/>
</ConfirmationDialogContent>
</Dialog>
+1 -1
View File
@@ -798,7 +798,7 @@ const RerouteDialogContentAndFooterWithFormResponse = ({
findTeamMembersMatchingAttributeLogicMutation.mutate({
formId: form.id,
response: currentResponse,
routeId: route.id,
route,
});
}
+4 -1
View File
@@ -14,6 +14,7 @@ import type { ReactNode } from "react";
import { useEffect } from "react";
import CacheProvider from "react-inlinesvg/provider";
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
@@ -299,7 +300,9 @@ const AppProviders = (props: PageWrapperProps) => {
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
<DynamicIntercomProvider>
<DynamicPostHogProvider>{RemainingProviders}</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);
};
+15 -1
View File
@@ -7,11 +7,13 @@ import type { SSRConfig } from "next-i18next";
import { appWithTranslation } from "next-i18next";
import { ThemeProvider } from "next-themes";
import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app";
import dynamic from "next/dynamic";
import type { ParsedUrlQuery } from "querystring";
import type { PropsWithChildren, ReactNode } from "react";
import { useEffect } from "react";
import CacheProvider from "react-inlinesvg/provider";
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
@@ -56,6 +58,13 @@ export type AppProps = Omit<
err?: Error;
};
const PostHogPageView = dynamic(
() => import("@calcom/features/ee/event-tracking/lib/posthog/web/PostHogPageView"),
{
ssr: false,
}
);
type AppPropsWithChildren = AppProps & {
children: ReactNode;
};
@@ -319,7 +328,12 @@ const AppProviders = (props: AppPropsWithChildren) => {
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
<DynamicIntercomProvider>
<DynamicPostHogProvider>
<PostHogPageView />
{RemainingProviders}
</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);
};
@@ -55,11 +55,11 @@ export default function PlatformBillingUpgrade() {
return (
<div>
<Shell
heading="Platform billing"
title="Platform billing"
heading={t("platform_billing")}
title={t("platform_billing")}
hideHeadingOnMobile
withoutMain={false}
subtitle="Manage all things billing"
subtitle={t("manage_billing_description")}
isPlatformUser={true}>
<>
<div className="border-subtle space-y-6 rounded-lg border px-6 py-8 text-sm sm:space-y-8">
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Shell from "@calcom/features/shell/Shell";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants";
import { showToast } from "@calcom/ui";
@@ -30,6 +31,7 @@ import {
} from "../../../../../../../../packages/platform/utils/permissions";
export default function EditOAuthClient() {
const { t } = useLocale();
const router = useRouter();
const params = useParams<{ clientId: string }>();
const clientId = params?.clientId || "";
@@ -78,15 +80,15 @@ export default function EditOAuthClient() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell title="OAuth client updation form" isPlatformUser={true}>
<Shell title={t("oAuth_client_updation_form")} isPlatformUser={true}>
<div className="m-2 md:mx-14 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold leading-5 tracking-wide">
OAuth client updation form
{t("oAuth_client_updation_form")}
</h1>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">
This is the form to edit an existing OAuth client
{t("oAuth_client_updation_form_description")}
</p>
</div>
</div>
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Shell from "@calcom/features/shell/Shell";
import { WebhookForm } from "@calcom/features/webhooks/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { showToast } from "@calcom/ui";
@@ -18,6 +19,7 @@ import NoPlatformPlan from "@components/settings/platform/dashboard/NoPlatformPl
import { useGetUserAttributes } from "@components/settings/platform/hooks/useGetUserAttributes";
export default function EditOAuthClientWebhooks() {
const { t } = useLocale();
const router = useRouter();
const params = useParams<{ clientId: string }>();
const clientId = params?.clientId || "";
@@ -40,14 +42,16 @@ export default function EditOAuthClientWebhooks() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell title="OAuth client updation form" isPlatformUser={true}>
<Shell title={t("webhook_update_form")} isPlatformUser={true}>
<div className="m-2 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold leading-5 tracking-wide">
Webhook update form
{t("webhook_update_form")}
</h1>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">Add a webhook to your OAuthClient.</p>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">
{t("webhook_update_form_description")}
</p>
</div>
</div>
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Shell from "@calcom/features/shell/Shell";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions";
import { showToast } from "@calcom/ui";
@@ -18,6 +19,7 @@ import { OAuthClientForm } from "@components/settings/platform/oauth-clients/oau
export default function CreateOAuthClient() {
const searchParams = useCompatSearchParams();
const router = useRouter();
const { t } = useLocale();
const clientId = searchParams?.get("clientId") || "";
const { isUserLoading, isPlatformUser, isPaidUser } = useGetUserAttributes();
@@ -62,15 +64,15 @@ export default function CreateOAuthClient() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell title={`OAuth client ${!!clientId ? "updation" : "creation"} form`} isPlatformUser={true}>
<Shell title={t("oAuth_client_creation_form")} isPlatformUser={true}>
<div className="m-2 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold leading-5 tracking-wide">
OAuth client creation form
{t("oAuth_client_creation_form")}
</h1>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">
This is the form to create a new OAuth client
{t("oAuth_client_creation_form_description")}
</p>
</div>
</div>
+3 -3
View File
@@ -624,7 +624,7 @@ export default function Signup({
</div>
</div>
</div>
<div className="border-subtle lg:bg-subtle mx-auto mt-24 w-full max-w-2xl flex-col justify-between rounded-l-2xl pl-4 dark:bg-none lg:mt-0 lg:flex lg:max-w-full lg:border lg:py-12 lg:pl-12">
<div className="border-subtle lg:bg-subtle mx-auto mt-24 w-full max-w-2xl flex-col justify-between rounded-l-2xl pl-4 lg:mt-0 lg:flex lg:max-w-full lg:border lg:py-12 lg:pl-12 dark:bg-none">
{IS_CALCOM && (
<>
<div className="-mt-4 mb-6 mr-12 grid w-full grid-cols-3 gap-5 pr-4 sm:gap-3 lg:grid-cols-4">
@@ -675,7 +675,7 @@ export default function Signup({
</div>
</>
)}
<div className="border-default hidden rounded-bl-2xl rounded-br-none rounded-tl-2xl border border-r-0 border-dashed bg-black/[3%] dark:bg-white/5 lg:block lg:py-[6px] lg:pl-[6px]">
<div className="border-default hidden rounded-bl-2xl rounded-br-none rounded-tl-2xl border border-r-0 border-dashed bg-black/[3%] lg:block lg:py-[6px] lg:pl-[6px] dark:bg-white/5">
<img className="block dark:hidden" src="/mock-event-type-list.svg" alt="Cal.com Booking Page" />
<img
className="hidden dark:block"
@@ -686,7 +686,7 @@ export default function Signup({
<div className="mr-12 mt-8 hidden h-full w-full grid-cols-3 gap-4 overflow-hidden lg:grid">
{FEATURES.map((feature) => (
<>
<div className="max-w-52 mb-8 flex flex-col leading-none sm:mb-0">
<div className="mb-8 flex max-w-52 flex-col leading-none sm:mb-0">
<div className="text-emphasis items-center">
<Icon name={feature.icon} className="mb-1 h-4 w-4" />
<span className="text-sm font-medium">{t(feature.title)}</span>
+1 -1
View File
@@ -663,7 +663,7 @@ const nextConfig = {
if (!!process.env.NEXT_PUBLIC_SENTRY_DSN) {
plugins.push((nextConfig) =>
withSentryConfig(nextConfig, {
autoInstrumentServerFunctions: true,
autoInstrumentServerFunctions: false,
hideSourceMaps: true,
// disable source map generation for the server code
disableServerWebpackPlugin: !!process.env.SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN,
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "4.6.14",
"version": "4.6.17",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -109,6 +109,8 @@
"nodemailer": "^6.7.8",
"nuqs": "^1.20.0",
"otplib": "^12.0.1",
"posthog-js": "^1.164.1",
"posthog-node": "^4.2.0",
"qrcode": "^1.5.1",
"raw-body": "^2.5.1",
"react": "^18.2.0",
+3 -1
View File
@@ -1,4 +1,6 @@
import { wrapApiHandlerWithSentry } from "@sentry/nextjs";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { highPerfRouter } from "@calcom/trpc/server/routers/viewer/highPerf/_router";
export default createNextApiHandler(highPerfRouter);
export default wrapApiHandlerWithSentry(createNextApiHandler(highPerfRouter), "/api/trpc/highPerf/[trpc]");
@@ -1,27 +0,0 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui";
import { getServerSideProps } from "@lib/settings/license-key/new/getServerSideProps";
import PageWrapper from "@components/PageWrapper";
import CreateANewLicenseKeyForm, { LayoutWrapper } from "~/settings/license-key/new/new-view";
const CreateNewLicenseKeyPage = () => {
const { t } = useLocale();
return (
<>
<Meta title={t("set_up_your_organization")} description={t("organizations_description")} />
<CreateANewLicenseKeyForm />
</>
);
};
CreateNewLicenseKeyPage.getLayout = LayoutWrapper;
CreateNewLicenseKeyPage.PageWrapper = PageWrapper;
export default CreateNewLicenseKeyPage;
export { getServerSideProps };
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import PlatformBillingUpgrade from "~/settings/platform/billing/billing-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(PlatformBillingUpgrade, {});
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import PlatformView from "~/settings/platform/platform-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(PlatformView, {});
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1,64 +0,0 @@
import { useRouter } from "next/navigation";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import { getServerSideProps } from "@lib/settings/organizations/new/getServerSideProps";
import PageWrapper from "@components/PageWrapper";
import usePlatformMe from "@components/settings/platform/hooks/usePlatformMe";
import CreateNewOrganizationPage, { LayoutWrapper } from "~/settings/platform/new/create-new-view";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
<div className="mr-3">
<SkeletonText className="h-4 w-28" />
<SkeletonText className="mt-3 h-11 w-full" />
<SkeletonText className="mt-2 h-11 w-full" />
</div>
</SkeletonContainer>
);
};
const Page = () => {
const { t } = useLocale();
const { isFetching, data: platformMe } = usePlatformMe();
const router = useRouter();
if (isFetching) {
return <SkeletonLoader />;
}
if (platformMe?.organization?.id) {
// if user has a platform org redirect to platform dashboard
if (platformMe?.organization?.isPlatform) {
router.push("/settings/platform");
// user has a regular org redirect to organization settings
} else {
router.push("/settings/organizations/profile");
}
// display loader while redirection is happening
return <SkeletonLoader />;
}
return (
<LicenseRequired>
<Meta
title={t("set_up_your_platform_organization")}
description={t("platform_organization_description")}
/>
<CreateNewOrganizationPage />
</LicenseRequired>
);
};
Page.getLayout = LayoutWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
export { getServerSideProps };
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import EditView from "~/settings/platform/oauth-clients/[clientId]/edit/edit-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(EditView, {});
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import EditWebhooksView from "~/settings/platform/oauth-clients/[clientId]/edit/edit-webhooks-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(EditWebhooksView, {});
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import CreateOAuthClientView from "~/settings/platform/oauth-clients/create-new-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(CreateOAuthClientView, {});
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1,12 +0,0 @@
import PageWrapper from "@components/PageWrapper";
import PlatformPlansView from "~/settings/platform/plans/platform-plans-view";
const Page = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(PlatformPlansView, {});
Page.PageWrapper = PageWrapper;
export default Page;
+2 -6
View File
@@ -79,9 +79,7 @@ test.describe("Out of office", () => {
await page.getByTestId("profile-redirect-switch").click();
await page.getByTestId("team_username_select").click();
await page.locator("#react-select-3-option-0").click();
await page.getByTestId(`team_username_select_${userTo.id}`).click();
// send request
await saveAndWaitForResponse(page);
@@ -157,9 +155,7 @@ test.describe("Out of office", () => {
await page.getByTestId("notes_input").click();
await page.getByTestId("notes_input").fill("Changed notes");
await page.getByTestId("team_username_select").click();
await page.locator("#react-select-3-option-1").click();
await page.getByTestId(`team_username_select_${userToSecond.id}`).click();
// send request
await saveAndWaitForResponse(page);
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "أنت غير مخول لإجراء هذا التغيير على الحجز",
"matching_members": "الأعضاء المطابقون",
"no_matching_members": "لا يوجد أعضاء مطابقون. سيتم الرجوع إلى استخدام أعضاء الفريق المعينين لنوع الحدث.",
"no_matching_members_will_fallback_to_all_assigned_members": "لا يوجد أعضاء مطابقين. سيتم الرجوع إلى استخدام أعضاء الفريق المعينين لنوع الحدث. فكر في إضافة بديل أو تصحيح منطق استخدام الأعضاء البديلين",
"hide_calendar_event_details": "إخفاء تفاصيل الأحداث في التقويمات المشتركة",
"description_hide_calendar_event_details": "عند مشاركة التقويم، تكون الأحداث مرئية للقراء ولكن تفاصيلها تكون مخفية عن أولئك الذين ليس لديهم صلاحية الكتابة.",
"last_number_of_days": "آخر {{count}} يوم",
@@ -2703,8 +2704,21 @@
"add_new_field": "إضافة حقل جديد",
"you_dont_have_access_to_reroute_this_booking": "ليس لديك صلاحية لإعادة توجيه هذا الحجز",
"form_response_not_found": "لم يتم العثور على استجابة النموذج",
"using_fallback_members": "استخدام الأعضاء البديلين",
"chosen_route": "المسار المختار",
"attribute_logic_matched": "تطابق منطق السمات",
"attribute_logic_fallback_matched": "تطابق البديل لمنطق السمات",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "جميع الأعضاء المعينين لنوع حدث الفريق. فكر في إضافة بعض قواعد السمات",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "جميع الأعضاء المعينين لنوع حدث الفريق. فكر في إضافة بعض قواعد السمات للبديل",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "جميع الأعضاء المعينين لنوع حدث الفريق. فكر في تعديل البديل للحصول على تطابق",
"warning": "تحذير",
"fallback_attribute_logic_description": "البديل: إذا لم يتطابق أي من أعضاء الفريق، استخدم أولئك الذين يتطابقون مع المعايير التالية (يتطابق مع جميع أعضاء الفريق المعينين للحدث افتراضيًا)",
"fallback_attribute_logic_warning": "تحذير البديل",
"fallback_not_needed": "غير مطلوب",
"confirm_reassign_unavailable": "المضيف غير متاح",
"confirm_reassign_available": "المضيف متاح",
"reassign_unavailable_team_member_description": "هل أنت متأكد أنك تريد إعادة تعيين هذا الحجز لمضيف غير متاح؟",
"reassign_available_team_member_description": "هل أنت متأكد أنك تريد إعادة تعيين هذا الحجز؟",
"yes_reassign": "نعم، أعد التعيين",
"reassign_reason": "سبب إعادة التعيين",
"reassigned_by": "تمت إعادة التعيين بواسطة",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Bu dəyişikliyi etmək üçün səlahiyyətiniz yoxdur",
"matching_members": "Uyğun üzvlər",
"no_matching_members": "Uyğun üzvlər yoxdur. Bu halda tədbir növünə təyin edilmiş komanda üzvləri istifadə olunacaq.",
"no_matching_members_will_fallback_to_all_assigned_members": "Uyğun üzvlər tapılmadı. Tədbir növünə təyin edilmiş komanda üzvlərindən istifadə ediləcək. Ehtiyat variant əlavə etməyi və ya using_fallback_members məntiqini düzəltməyi nəzərdən keçirin",
"hide_calendar_event_details": "Təqvim hadisə detallarını gizlət",
"description_hide_calendar_event_details": "Təqvim paylaşıldıqda, hadisələr oxucular üçün görünür, lakin yazma icazəsi olmayanlar üçün detallar gizlədilir.",
"last_number_of_days": "son {{count}} gün",
@@ -2703,8 +2704,21 @@
"add_new_field": "Yeni sahə əlavə et",
"you_dont_have_access_to_reroute_this_booking": "Bu rezervasiyanı yönləndirmək üçün icazəniz yoxdur",
"form_response_not_found": "Form cavabı tapılmadı",
"using_fallback_members": "Ehtiyat üzvlərdən istifadə edilir",
"chosen_route": "Seçilmiş marşrut",
"attribute_logic_matched": "Atribut məntiqi uyğun gəldi",
"attribute_logic_fallback_matched": "Atribut məntiqi ehtiyat variantı uyğun gəldi",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Komanda tədbir növünə təyin edilmiş bütün üzvlər. Bəzi atribut qaydaları əlavə etməyi nəzərdən keçirin.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Komanda tədbir növünə təyin edilmiş bütün üzvlər. Ehtiyat varianta bəzi atribut qaydaları əlavə etməyi nəzərdən keçirin.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Komanda tədbir növünə təyin edilmiş bütün üzvlər. Uyğunluq əldə etmək üçün ehtiyat variantı tənzimləməyi nəzərdən keçirin.",
"warning": "Xəbərdarlıq",
"fallback_attribute_logic_description": "Ehtiyat variant: Heç bir komanda üzvü uyğun gəlmirsə, aşağıdakı meyarlara uyğun olanlardan istifadə edin (standart olaraq tədbirin bütün təyin edilmiş komanda üzvlərinə uyğun gəlir)",
"fallback_attribute_logic_warning": "Ehtiyat variant xəbərdarlığı",
"fallback_not_needed": "Lazım deyil",
"confirm_reassign_unavailable": "Ev sahibi mövcud deyil",
"confirm_reassign_available": "Ev sahibi mövcuddur",
"reassign_unavailable_team_member_description": "Bu rezervasiyanı mövcud olmayan ev sahibinə yenidən təyin etmək istədiyinizə əminsiniz?",
"reassign_available_team_member_description": "Bu rezervasiyanı yenidən təyin etmək istədiyinizə əminsiniz?",
"yes_reassign": "Bəli, yenidən təyin et",
"reassign_reason": "Yenidən təyin etmə səbəbi",
"reassigned_by": "Yenidən təyin edən",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Nemáte oprávnění provést tuto změnu v rezervaci",
"matching_members": "Odpovídající členové",
"no_matching_members": "Žádní odpovídající členové. Bude použito záložní řešení s členy týmu přiřazenými k typu události.",
"no_matching_members_will_fallback_to_all_assigned_members": "Žádní odpovídající členové. Použijí se všichni členové týmu přiřazení k typu události. Zvažte přidání záložního plánu nebo opravu logiky using_fallback_members",
"hide_calendar_event_details": "Skrýt podrobnosti událostí v kalendáři na sdílených kalendářích",
"description_hide_calendar_event_details": "Když je kalendář sdílen, události jsou viditelné pro čtenáře, ale jejich podrobnosti jsou skryty pro ty, kteří nemají přístup k úpravám.",
"last_number_of_days": "posledních {{count}} dní",
@@ -2703,8 +2704,21 @@
"add_new_field": "Přidat nové pole",
"you_dont_have_access_to_reroute_this_booking": "Nemáte přístup k přesměrování této rezervace",
"form_response_not_found": "Odpověď formuláře nebyla nalezena",
"using_fallback_members": "Používají se záložní členové",
"chosen_route": "Zvolená cesta",
"attribute_logic_matched": "Logika atributů odpovídá",
"attribute_logic_fallback_matched": "Záložní logika atributů odpovídá",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Všichni přiřazení členové týmového typu události. Zvažte přidání některých pravidel atributů.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Všichni přiřazení členové týmového typu události. Zvažte přidání některých pravidel atributů pro záložní plán.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Všichni přiřazení členové týmového typu události. Zvažte úpravu záložního plánu, aby došlo ke shodě.",
"warning": "Varování",
"fallback_attribute_logic_description": "Záložní plán: Pokud žádní členové týmu neodpovídají, použijte ty, kteří splňují následující kritéria (ve výchozím nastavení odpovídá všem přiřazeným členům týmu události)",
"fallback_attribute_logic_warning": "Varování záložního plánu",
"fallback_not_needed": "Není potřeba",
"confirm_reassign_unavailable": "Hostitel není k dispozici",
"confirm_reassign_available": "Hostitel je k dispozici",
"reassign_unavailable_team_member_description": "Opravdu chcete přeřadit tuto rezervaci nedostupnému hostiteli?",
"reassign_available_team_member_description": "Opravdu chcete tuto rezervaci přeřadit?",
"yes_reassign": "Ano, přeřadit",
"reassign_reason": "Důvod přeřazení",
"reassigned_by": "Přeřazeno kým",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Du har ikke tilladelse til at foretage denne ændring i bookingen",
"matching_members": "Matchende medlemmer",
"no_matching_members": "Ingen matchende medlemmer. Det vil falde tilbage til at bruge de teammedlemmer, der er tildelt til begivenhedstypen.",
"no_matching_members_will_fallback_to_all_assigned_members": "Ingen matchende medlemmer. Der vil falde tilbage til at bruge de teammedlemmer, der er tildelt begivenhedstypen. Overvej at tilføje en fallback eller korrigere logikken for using_fallback_members",
"hide_calendar_event_details": "Skjul kalenderbegivenhedsdetaljer på delte kalendere",
"description_hide_calendar_event_details": "Når en kalender deles, er begivenheder synlige for læsere, men deres detaljer er skjult for dem uden skriveadgang.",
"last_number_of_days": "sidste {{count}} dage",
@@ -2703,8 +2704,21 @@
"add_new_field": "Tilføj nyt felt",
"you_dont_have_access_to_reroute_this_booking": "Du har ikke adgang til at omdirigere denne booking",
"form_response_not_found": "Formularbesvarelse ikke fundet",
"using_fallback_members": "Bruger fallback-medlemmer",
"chosen_route": "Valgt rute",
"attribute_logic_matched": "Attributlogik matchet",
"attribute_logic_fallback_matched": "Attributlogik fallback matchet",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Alle tildelte medlemmer af team-begivenhedstypen. Overvej at tilføje nogle attributregler.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Alle tildelte medlemmer af team-begivenhedstypen. Overvej at tilføje nogle attributregler til fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Alle tildelte medlemmer af team-begivenhedstypen. Overvej at justere fallback for at få et match.",
"warning": "Advarsel",
"fallback_attribute_logic_description": "Fallback: Hvis ingen teammedlemmer matcher, brug dem der matcher følgende kriterier (matcher som standard alle tildelte teammedlemmer til begivenheden)",
"fallback_attribute_logic_warning": "Fallback-advarsel",
"fallback_not_needed": "Ikke nødvendig",
"confirm_reassign_unavailable": "Vært ikke tilgængelig",
"confirm_reassign_available": "Vært tilgængelig",
"reassign_unavailable_team_member_description": "Er du sikker på, at du vil omfordele denne booking til en utilgængelig vært?",
"reassign_available_team_member_description": "Er du sikker på, at du vil omfordele denne booking?",
"yes_reassign": "Ja, omfordel",
"reassign_reason": "Årsag til omfordeling",
"reassigned_by": "Omfordelt af",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Sie sind nicht berechtigt, diese Änderung an der Buchung vorzunehmen",
"matching_members": "Passende Mitglieder",
"no_matching_members": "Keine passenden Mitglieder gefunden. Es wird auf die dem Ereignistyp zugewiesenen Teammitglieder zurückgegriffen.",
"no_matching_members_will_fallback_to_all_assigned_members": "Keine passenden Mitglieder gefunden. Es wird auf alle dem Ereignistyp zugewiesenen Teammitglieder zurückgegriffen. Erwägen Sie, eine Fallback-Option hinzuzufügen oder die Logik für using_fallback_members zu korrigieren",
"hide_calendar_event_details": "Kalenderereignisdetails in geteilten Kalendern verbergen",
"description_hide_calendar_event_details": "Wenn ein Kalender geteilt wird, sind Ereignisse für Leser sichtbar, aber ihre Details bleiben für Personen ohne Schreibzugriff verborgen.",
"last_number_of_days": "letzte {{count}} Tage",
@@ -2703,8 +2704,21 @@
"add_new_field": "Neues Feld hinzufügen",
"you_dont_have_access_to_reroute_this_booking": "Sie haben keine Berechtigung, diese Buchung umzuleiten",
"form_response_not_found": "Formularantwort nicht gefunden",
"using_fallback_members": "Verwendung von Fallback-Mitgliedern",
"chosen_route": "Gewählte Route",
"attribute_logic_matched": "Attributlogik zutreffend",
"attribute_logic_fallback_matched": "Attributlogik-Fallback zutreffend",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Alle zugewiesenen Mitglieder des Team-Ereignistyps. Erwägen Sie, einige Attributregeln hinzuzufügen.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Alle zugewiesenen Mitglieder des Team-Ereignistyps. Erwägen Sie, einige Attributregeln für den Fallback hinzuzufügen.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Alle zugewiesenen Mitglieder des Team-Ereignistyps. Erwägen Sie, den Fallback anzupassen, um eine Übereinstimmung zu erzielen.",
"warning": "Warnung",
"fallback_attribute_logic_description": "Fallback: Wenn keine Teammitglieder übereinstimmen, verwenden Sie diejenigen, die den folgenden Kriterien entsprechen (standardmäßig alle zugewiesenen Teammitglieder des Ereignisses)",
"fallback_attribute_logic_warning": "Fallback-Warnung",
"fallback_not_needed": "Nicht erforderlich",
"confirm_reassign_unavailable": "Gastgeber nicht verfügbar",
"confirm_reassign_available": "Gastgeber verfügbar",
"reassign_unavailable_team_member_description": "Sind Sie sicher, dass Sie diese Buchung einem nicht verfügbaren Gastgeber neu zuweisen möchten?",
"reassign_available_team_member_description": "Sind Sie sicher, dass Sie diese Buchung neu zuweisen möchten?",
"yes_reassign": "Ja, neu zuweisen",
"reassign_reason": "Grund für die Neuzuweisung",
"reassigned_by": "Neu zugewiesen von",
+26 -1
View File
@@ -198,6 +198,8 @@
"webhook_disabled": "Webhook Disabled",
"webhook_response": "Webhook response",
"webhook_test": "Webhook test",
"webhook_update_form": "Webhook update form",
"webhook_update_form_description": "Add a webhook to your OAuthClient.",
"manage_your_webhook": "Manage your webhook",
"webhook_created_successfully": "Webhook created successfully!",
"webhook_updated_successfully": "Webhook updated successfully!",
@@ -1630,6 +1632,7 @@
"format": "Format",
"uppercase_for_letters": "Use uppercase for all letters",
"replace_whitespaces_underscores": "Replace whitespaces with underscores",
"platform_billing": "Platform billing",
"manage_billing": "Manage billing",
"manage_billing_description": "Manage all things billing",
"billing_freeplan_title": "You're currently on the FREE plan",
@@ -2036,7 +2039,9 @@
"looking_for_more_analytics": "Looking for more analytics?",
"looking_for_more_insights": "Looking for more Insights?",
"filters": "Filters",
"add_filter": "Add filter",
"add_filter": "Add filter",
"add_rule": "Add rule",
"add_rule_group": "Add rule group",
"remove_filters": "Clear all filters",
"email_verified": "Email Verified",
"select_user": "Select User",
@@ -2139,6 +2144,7 @@
"set_up_your_organization": "Set up your organization",
"set_up_your_platform_organization": "Set up your platform",
"organizations_description": "Organizations are shared environments where teams can create shared event types, apps, workflows and more.",
"plans": "Plans",
"platform": "Platform",
"platform_description": "Manage everything related to platform.",
"platform_organization_description": "Cal.com Platform lets you integrate scheduling effortlessly into your app using platform apis and atoms.",
@@ -2276,6 +2282,10 @@
"seat_options_doesnt_multiple_durations": "Seat option doesn't support multiple durations",
"include_calendar_event": "Include calendar event",
"oAuth": "OAuth",
"oAuth_client_creation_form": "OAuth client creation form",
"oAuth_client_creation_form_description": "This is the form to create a new OAuth client",
"oAuth_client_updation_form": "OAuth client updation form",
"oAuth_client_updation_form_description": "This is the form to edit an existing OAuth client",
"recently_added": "Recently added",
"connect_all_calendars": "Connect all your calendars",
"connect_all_calendars_description": "{{appName}} reads availability from all your existing calendars.",
@@ -2660,6 +2670,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking",
"matching_members": "Matching members",
"no_matching_members": "No matching members. It will fallback to using the team members assigned to the event type.",
"no_matching_members_will_fallback_to_all_assigned_members": "No matching members. It will fallback to using the team members assigned to the event type. Consider adding a fallback or correcting the logic of using_fallback_members",
"hide_calendar_event_details": "Hide calendar event details on shared calendars",
"description_hide_calendar_event_details": "When a calendar is shared, events are visible to readers but their details are hidden from those without write access.",
"last_number_of_days": "last {{count}} days",
@@ -2707,8 +2718,21 @@
"add_new_field": "Add new field",
"you_dont_have_access_to_reroute_this_booking": "You don't have access to reroute this booking",
"form_response_not_found": "Form response not found",
"using_fallback_members": "Using fallback members",
"chosen_route": "Chosen Route",
"attribute_logic_matched": "Attribute logic matched",
"attribute_logic_fallback_matched": "Attribute logic fallback matched",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "All assigned members of the team event type. Consider adding some attribute rules.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "All assigned members of the team event type. Consider adding some attribute rules to fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "All assigned members of the team event type. Consider tweaking fallback to have a match.",
"warning": "Warning",
"fallback_attribute_logic_description": "Fallback: If no Team Members match, use those that match the following criteria (matches all assigned team members of the event by default)",
"fallback_attribute_logic_warning": "Fallback warning",
"fallback_not_needed": "Not needed",
"confirm_reassign_unavailable": "Host unavailable",
"confirm_reassign_available": "Host available",
"reassign_unavailable_team_member_description": "Are you sure you want to reassign this booking to an unavailable host?",
"reassign_available_team_member_description": "Are you sure you want to reassign this booking?",
"yes_reassign": "Yes, reassign",
"reassign_reason": "Reason for reassigning",
"reassigned_by": "Reassigned by",
@@ -2722,5 +2746,6 @@
"event_end_time_in_attendee_timezone_info": "Event End Time In Attendee Timezone",
"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",
"booking_reassigned_to_host": "Booking reassigned to {{host}}",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "No tienes autorización para realizar este cambio en la reserva",
"matching_members": "Miembros coincidentes",
"no_matching_members": "No hay miembros coincidentes. Se utilizarán los miembros del equipo asignados al tipo de evento como alternativa.",
"no_matching_members_will_fallback_to_all_assigned_members": "No hay miembros que coincidan. Se utilizarán los miembros del equipo asignados al tipo de evento como respaldo. Considera agregar un respaldo o corregir la lógica de using_fallback_members",
"hide_calendar_event_details": "Ocultar detalles de eventos del calendario en calendarios compartidos",
"description_hide_calendar_event_details": "Cuando se comparte un calendario, los eventos son visibles para los lectores, pero sus detalles están ocultos para aquellos sin acceso de escritura.",
"last_number_of_days": "últimos {{count}} días",
@@ -2703,8 +2704,21 @@
"add_new_field": "Agregar nuevo campo",
"you_dont_have_access_to_reroute_this_booking": "No tienes acceso para redirigir esta reserva",
"form_response_not_found": "Respuesta del formulario no encontrada",
"using_fallback_members": "Usando miembros de respaldo",
"chosen_route": "Ruta elegida",
"attribute_logic_matched": "Lógica de atributos coincidente",
"attribute_logic_fallback_matched": "Lógica de atributos de respaldo coincidente",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Todos los miembros asignados al tipo de evento del equipo. Considera agregar algunas reglas de atributos.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Todos los miembros asignados al tipo de evento del equipo. Considera agregar algunas reglas de atributos como respaldo.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Todos los miembros asignados al tipo de evento del equipo. Considera ajustar el respaldo para obtener una coincidencia.",
"warning": "Advertencia",
"fallback_attribute_logic_description": "Respaldo: Si ningún miembro del equipo coincide, usa aquellos que cumplan con los siguientes criterios (por defecto, coincide con todos los miembros del equipo asignados al evento)",
"fallback_attribute_logic_warning": "Advertencia de respaldo",
"fallback_not_needed": "No es necesario",
"confirm_reassign_unavailable": "Anfitrión no disponible",
"confirm_reassign_available": "Anfitrión disponible",
"reassign_unavailable_team_member_description": "¿Estás seguro de que quieres reasignar esta reserva a un anfitrión no disponible?",
"reassign_available_team_member_description": "¿Estás seguro de que quieres reasignar esta reserva?",
"yes_reassign": "Sí, reasignar",
"reassign_reason": "Motivo de la reasignación",
"reassigned_by": "Reasignado por",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "No tienes autorización para realizar este cambio en la reserva",
"matching_members": "Miembros coincidentes",
"no_matching_members": "No hay miembros coincidentes. Se utilizarán los miembros del equipo asignados al tipo de evento como alternativa.",
"no_matching_members_will_fallback_to_all_assigned_members": "No hay miembros coincidentes. Se utilizarán los miembros del equipo asignados al tipo de evento como alternativa. Considera añadir una opción de respaldo o corregir la lógica de using_fallback_members",
"hide_calendar_event_details": "Ocultar detalles de eventos del calendario en calendarios compartidos",
"description_hide_calendar_event_details": "Cuando se comparte un calendario, los eventos son visibles para los lectores, pero sus detalles están ocultos para aquellos sin acceso de escritura.",
"last_number_of_days": "últimos {{count}} días",
@@ -2703,8 +2704,21 @@
"add_new_field": "Añadir nuevo campo",
"you_dont_have_access_to_reroute_this_booking": "No tienes acceso para redirigir esta reserva",
"form_response_not_found": "Respuesta del formulario no encontrada",
"using_fallback_members": "Usando miembros de respaldo",
"chosen_route": "Ruta elegida",
"attribute_logic_matched": "Lógica de atributos coincidente",
"attribute_logic_fallback_matched": "Lógica de atributos de respaldo coincidente",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Todos los miembros asignados al tipo de evento del equipo. Considera añadir algunas reglas de atributos.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Todos los miembros asignados al tipo de evento del equipo. Considera añadir algunas reglas de atributos como respaldo.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Todos los miembros asignados al tipo de evento del equipo. Considera ajustar el respaldo para obtener una coincidencia.",
"warning": "Advertencia",
"fallback_attribute_logic_description": "Respaldo: Si ningún miembro del equipo coincide, usa aquellos que cumplan los siguientes criterios (por defecto, coincide con todos los miembros del equipo asignados al evento)",
"fallback_attribute_logic_warning": "Advertencia de lógica de atributos de respaldo",
"fallback_not_needed": "No es necesario",
"confirm_reassign_unavailable": "Anfitrión no disponible",
"confirm_reassign_available": "Anfitrión disponible",
"reassign_unavailable_team_member_description": "¿Estás seguro de que quieres reasignar esta reserva a un anfitrión no disponible?",
"reassign_available_team_member_description": "¿Estás seguro de que quieres reasignar esta reserva?",
"yes_reassign": "Sí, reasignar",
"reassign_reason": "Motivo de la reasignación",
"reassigned_by": "Reasignado por",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Teil puuduvad õigused selle broneeringu muutmiseks",
"matching_members": "Sobivad liikmed",
"no_matching_members": "Sobivaid liikmeid ei leitud. Kasutatakse sündmuse tüübile määratud meeskonnaliikmeid.",
"no_matching_members_will_fallback_to_all_assigned_members": "Sobivaid liikmeid ei leitud. Kasutatakse sündmuse tüübile määratud meeskonnaliikmeid. Kaaluge varuvariant lisada või parandada using_fallback_members loogikat",
"hide_calendar_event_details": "Peida kalendrisündmuste üksikasjad jagatud kalendrites",
"description_hide_calendar_event_details": "Kui kalender on jagatud, on sündmused lugejatele nähtavad, kuid nende üksikasjad on peidetud nende eest, kellel puudub kirjutamisõigus.",
"last_number_of_days": "viimased {{count}} päeva",
@@ -2703,8 +2704,21 @@
"add_new_field": "Lisa uus väli",
"you_dont_have_access_to_reroute_this_booking": "Sul pole õigust seda broneeringut ümber suunata",
"form_response_not_found": "Vormi vastust ei leitud",
"using_fallback_members": "Kasutatakse varuliikmeid",
"chosen_route": "Valitud tee",
"attribute_logic_matched": "Atribuudi loogika sobis",
"attribute_logic_fallback_matched": "Atribuudi loogika varuvariant sobis",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Kõik meeskonna sündmuse tüübile määratud liikmed. Kaaluge mõne atribuudi reegli lisamist.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Kõik meeskonna sündmuse tüübile määratud liikmed. Kaaluge mõne atribuudi reegli lisamist varuvariandile.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Kõik meeskonna sündmuse tüübile määratud liikmed. Kaaluge varuvariandi kohandamist, et leida sobivus.",
"warning": "Hoiatus",
"fallback_attribute_logic_description": "Varuvariant: Kui ükski meeskonnaliige ei sobi, kasuta neid, kes vastavad järgmistele kriteeriumitele (vaikimisi sobivad kõik sündmusele määratud meeskonnaliikmed)",
"fallback_attribute_logic_warning": "Varuvariandi hoiatus",
"fallback_not_needed": "Pole vajalik",
"confirm_reassign_unavailable": "Võõrustaja pole saadaval",
"confirm_reassign_available": "Võõrustaja on saadaval",
"reassign_unavailable_team_member_description": "Kas oled kindel, et soovid selle broneeringu määrata mittesaadaolevale võõrustajale?",
"reassign_available_team_member_description": "Kas olete kindel, et soovite selle broneeringu ümber määrata?",
"yes_reassign": "Jah, määra ümber",
"reassign_reason": "Ümbermääramise põhjus",
"reassigned_by": "Ümber määranud",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Vous n'êtes pas autorisé à apporter cette modification à la réservation",
"matching_members": "Membres correspondants",
"no_matching_members": "Aucun membre correspondant. Par défaut, les membres de l'équipe assignés au type d'événement seront utilisés.",
"no_matching_members_will_fallback_to_all_assigned_members": "Aucun membre correspondant. Le système utilisera par défaut les membres de l'équipe assignés au type d'événement. Envisagez d'ajouter une solution de repli ou de corriger la logique d'utilisation des membres de repli",
"hide_calendar_event_details": "Masquer les détails des événements du calendrier sur les calendriers partagés",
"description_hide_calendar_event_details": "Lorsqu'un calendrier est partagé, les événements sont visibles pour les lecteurs, mais leurs détails sont cachés pour ceux qui n'ont pas d'accès en écriture.",
"last_number_of_days": "{{count}} derniers jours",
@@ -2703,8 +2704,21 @@
"add_new_field": "Ajouter un nouveau champ",
"you_dont_have_access_to_reroute_this_booking": "Vous n'avez pas accès pour rediriger cette réservation",
"form_response_not_found": "Réponse du formulaire non trouvée",
"using_fallback_members": "Utilisation des membres de repli",
"chosen_route": "Itinéraire choisi",
"attribute_logic_matched": "Logique d'attribut correspondante",
"attribute_logic_fallback_matched": "Logique de repli d'attribut correspondante",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Tous les membres assignés au type d'événement d'équipe. Envisagez d'ajouter des règles d'attributs.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Tous les membres assignés au type d'événement d'équipe. Envisagez d'ajouter des règles d'attributs pour le repli.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Tous les membres assignés au type d'événement d'équipe. Envisagez d'ajuster le repli pour obtenir une correspondance.",
"warning": "Avertissement",
"fallback_attribute_logic_description": "Repli : si aucun membre de l'équipe ne correspond, utilisez ceux qui correspondent aux critères suivants (correspond par défaut à tous les membres de l'équipe assignés à l'événement)",
"fallback_attribute_logic_warning": "Avertissement de repli",
"fallback_not_needed": "Non nécessaire",
"confirm_reassign_unavailable": "Hôte indisponible",
"confirm_reassign_available": "Hôte disponible",
"reassign_unavailable_team_member_description": "Êtes-vous sûr de vouloir réassigner cette réservation à un hôte indisponible ?",
"reassign_available_team_member_description": "Êtes-vous sûr de vouloir réassigner cette réservation ?",
"yes_reassign": "Oui, réassigner",
"reassign_reason": "Raison de la réassignation",
"reassigned_by": "Réassigné par",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "אין לך הרשאה לבצע שינוי זה בהזמנה",
"matching_members": "חברים תואמים",
"no_matching_members": "אין חברים תואמים. המערכת תשתמש בחברי הצוות שהוקצו לסוג האירוע.",
"no_matching_members_will_fallback_to_all_assigned_members": "אין חברי צוות תואמים. יחזור לשימוש בחברי הצוות המשויכים לסוג האירוע. שקול להוסיף גיבוי או לתקן את הלוגיקה של using_fallback_members",
"hide_calendar_event_details": "הסתר פרטי אירועי לוח שנה בלוחות שנה משותפים",
"description_hide_calendar_event_details": "כאשר לוח שנה משותף, האירועים נראים לקוראים אך פרטיהם מוסתרים מאלו שאין להם גישת כתיבה.",
"last_number_of_days": "ב{{count}} הימים האחרונים",
@@ -2703,8 +2704,21 @@
"add_new_field": "הוסף שדה חדש",
"you_dont_have_access_to_reroute_this_booking": "אין לך גישה לניתוב מחדש של הזמנה זו",
"form_response_not_found": "תגובת הטופס לא נמצאה",
"using_fallback_members": "משתמש בחברי גיבוי",
"chosen_route": "מסלול נבחר",
"attribute_logic_matched": "לוגיקת המאפיינים תואמת",
"attribute_logic_fallback_matched": "לוגיקת הגיבוי של המאפיינים תואמת",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "כל חברי הצוות המשויכים לסוג האירוע הקבוצתי. שקול להוסיף כמה כללי מאפיינים",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "כל חברי הצוות המשויכים לסוג האירוע הקבוצתי. שקול להוסיף כמה כללי מאפיינים לגיבוי",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "כל חברי הצוות המשויכים לסוג האירוע הקבוצתי. שקול לשנות את הגיבוי כדי להשיג התאמה",
"warning": "אזהרה",
"fallback_attribute_logic_description": "גיבוי: אם אין חברי צוות תואמים, השתמש באלה שעומדים בקריטריונים הבאים (תואם את כל חברי הצוות המשויכים לאירוע כברירת מחדל)",
"fallback_attribute_logic_warning": "אזהרת גיבוי",
"fallback_not_needed": "לא נדרש",
"confirm_reassign_unavailable": "המארח אינו זמין",
"confirm_reassign_available": "המארח זמין",
"reassign_unavailable_team_member_description": "האם אתה בטוח שברצונך להקצות מחדש הזמנה זו למארח שאינו זמין?",
"reassign_available_team_member_description": "האם אתה בטוח שברצונך לשייך מחדש הזמנה זו?",
"yes_reassign": "כן, הקצה מחדש",
"reassign_reason": "סיבה להקצאה מחדש",
"reassigned_by": "הוקצה מחדש על ידי",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Nincs jogosultsága a foglalás módosításához",
"matching_members": "Megfelelő tagok",
"no_matching_members": "Nincsenek megfelelő tagok. Az eseménytípushoz rendelt csapattagok kerülnek felhasználásra.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nincs megfelelő tag. Visszaesik az eseménytípushoz rendelt csapattagok használatára. Fontolja meg egy tartalék hozzáadását vagy a using_fallback_members logika javítását",
"hide_calendar_event_details": "Naptáresemény részleteinek elrejtése megosztott naptárakon",
"description_hide_calendar_event_details": "Amikor egy naptár meg van osztva, az események láthatóak az olvasók számára, de a részletek rejtve maradnak azok elől, akiknek nincs írási hozzáférésük.",
"last_number_of_days": "az elmúlt {{count}} nap",
@@ -2703,8 +2704,21 @@
"add_new_field": "Új mező hozzáadása",
"you_dont_have_access_to_reroute_this_booking": "Nincs jogosultságod átirányítani ezt a foglalást",
"form_response_not_found": "Űrlap válasz nem található",
"using_fallback_members": "Tartalék tagok használata",
"chosen_route": "Választott útvonal",
"attribute_logic_matched": "Attribútum logika egyezik",
"attribute_logic_fallback_matched": "Attribútum logika tartalék egyezik",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "A csapat eseménytípusához rendelt összes tag. Fontolja meg néhány attribútum szabály hozzáadását.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "A csapat eseménytípusához rendelt összes tag. Fontolja meg néhány attribútum szabály hozzáadását a tartalékhoz.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "A csapat eseménytípusához rendelt összes tag. Fontolja meg a tartalék finomhangolását az egyezés érdekében.",
"warning": "Figyelmeztetés",
"fallback_attribute_logic_description": "Tartalék: Ha egyetlen csapattag sem felel meg, használja azokat, amelyek megfelelnek a következő kritériumoknak (alapértelmezetten az eseményhez rendelt összes csapattagnak megfelel)",
"fallback_attribute_logic_warning": "Tartalék figyelmeztetés",
"fallback_not_needed": "Nem szükséges",
"confirm_reassign_unavailable": "Házigazda nem elérhető",
"confirm_reassign_available": "Házigazda elérhető",
"reassign_unavailable_team_member_description": "Biztosan újra szeretnéd rendelni ezt a foglalást egy nem elérhető házigazdához?",
"reassign_available_team_member_description": "Biztosan át szeretné ütemezni ezt a foglalást?",
"yes_reassign": "Igen, újrahozzárendelés",
"reassign_reason": "Újrahozzárendelés oka",
"reassigned_by": "Újrahozzárendelő",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Non sei autorizzato a effettuare questa modifica alla prenotazione",
"matching_members": "Membri corrispondenti",
"no_matching_members": "Nessun membro corrispondente. Verrà utilizzato il fallback con i membri del team assegnati al tipo di evento.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nessun membro corrispondente. Si utilizzeranno i membri del team assegnati al tipo di evento come fallback. Considera di aggiungere un fallback o correggere la logica di using_fallback_members",
"hide_calendar_event_details": "Nascondi i dettagli degli eventi del calendario nei calendari condivisi",
"description_hide_calendar_event_details": "Quando un calendario è condiviso, gli eventi sono visibili ai lettori ma i loro dettagli sono nascosti a chi non ha accesso in scrittura.",
"last_number_of_days": "ultimi {{count}} giorni",
@@ -2703,8 +2704,21 @@
"add_new_field": "Aggiungi nuovo campo",
"you_dont_have_access_to_reroute_this_booking": "Non hai l'accesso per reindirizzare questa prenotazione",
"form_response_not_found": "Risposta del modulo non trovata",
"using_fallback_members": "Utilizzo dei membri di fallback",
"chosen_route": "Percorso scelto",
"attribute_logic_matched": "Logica degli attributi corrispondente",
"attribute_logic_fallback_matched": "Logica di fallback degli attributi corrispondente",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Tutti i membri assegnati al tipo di evento del team. Considera di aggiungere alcune regole di attributo.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Tutti i membri assegnati al tipo di evento del team. Considera di aggiungere alcune regole di attributo al fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Tutti i membri assegnati al tipo di evento del team. Considera di modificare il fallback per ottenere una corrispondenza.",
"warning": "Attenzione",
"fallback_attribute_logic_description": "Fallback: se nessun membro del team corrisponde, usa quelli che soddisfano i seguenti criteri (corrisponde a tutti i membri del team assegnati all'evento per impostazione predefinita)",
"fallback_attribute_logic_warning": "Avviso di fallback",
"fallback_not_needed": "Non necessario",
"confirm_reassign_unavailable": "Host non disponibile",
"confirm_reassign_available": "Host disponibile",
"reassign_unavailable_team_member_description": "Sei sicuro di voler riassegnare questa prenotazione a un host non disponibile?",
"reassign_available_team_member_description": "Sei sicuro di voler riassegnare questa prenotazione?",
"yes_reassign": "Sì, riassegna",
"reassign_reason": "Motivo della riassegnazione",
"reassigned_by": "Riassegnato da",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "この予約の変更を行う権限がありません",
"matching_members": "一致するメンバー",
"no_matching_members": "一致するメンバーがいません。イベントタイプに割り当てられたチームメンバーが使用されます。",
"no_matching_members_will_fallback_to_all_assigned_members": "一致するメンバーがいません。イベントタイプに割り当てられたチームメンバーを使用します。フォールバックを追加するか、using_fallback_membersのロジックを修正することを検討してください",
"hide_calendar_event_details": "共有カレンダーのイベント詳細を非表示にする",
"description_hide_calendar_event_details": "カレンダーが共有されると、イベントは閲覧者に表示されますが、書き込み権限のない人には詳細が非表示になります。",
"last_number_of_days": "過去{{count}}日間",
@@ -2703,8 +2704,21 @@
"add_new_field": "新しいフィールドを追加",
"you_dont_have_access_to_reroute_this_booking": "この予約を変更する権限がありません",
"form_response_not_found": "フォームの回答が見つかりません",
"using_fallback_members": "フォールバックメンバーを使用中",
"chosen_route": "選択されたルート",
"attribute_logic_matched": "属性ロジックが一致しました",
"attribute_logic_fallback_matched": "属性ロジックのフォールバックが一致しました",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "チームイベントタイプに割り当てられたすべてのメンバー。属性ルールの追加を検討してください",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "チームイベントタイプに割り当てられたすべてのメンバー。フォールバックに属性ルールの追加を検討してください",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "チームイベントタイプに割り当てられたすべてのメンバー。一致するようにフォールバックの調整を検討してください",
"warning": "警告",
"fallback_attribute_logic_description": "フォールバック:チームメンバーが一致しない場合、以下の基準に一致するメンバーを使用します(デフォルトではイベントに割り当てられたすべてのチームメンバーが一致します)",
"fallback_attribute_logic_warning": "フォールバックの警告",
"fallback_not_needed": "不要",
"confirm_reassign_unavailable": "ホストが利用できません",
"confirm_reassign_available": "利用可能なホスト",
"reassign_unavailable_team_member_description": "利用できないホストにこの予約を再割り当てしてもよろしいですか?",
"reassign_available_team_member_description": "この予約を再割り当てしてもよろしいですか?",
"yes_reassign": "はい、再割り当てします",
"reassign_reason": "再割り当ての理由",
"reassigned_by": "再割り当て者",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "អ្នកមិនមានសិទ្ធិធ្វើការផ្លាស់ប្តូរនេះលើការកក់ទេ",
"matching_members": "សមាជិកដែលត្រូវគ្នា",
"no_matching_members": "គ្មានសមាជិកដែលត្រូវគ្នា។ វានឹងត្រឡប់ទៅប្រើសមាជិកក្រុមដែលបានចាត់តាំងទៅប្រភេទព្រឹត្តិការណ៍។",
"no_matching_members_will_fallback_to_all_assigned_members": "គ្មានសមាជិកដែលត្រូវគ្នា។ វានឹងត្រឡប់ទៅប្រើសមាជិកក្រុមដែលបានចាត់តាំងឱ្យប្រភេទព្រឹត្តិការណ៍។ សូមពិចារណាបន្ថែមការត្រឡប់ក្រោយ ឬកែតម្រូវតក្កវិជ្ជានៃការប្រើប្រាស់សមាជិកត្រឡប់ក្រោយ",
"hide_calendar_event_details": "លាក់ព័ត៌មានលម្អិតនៃព្រឹត្តិការណ៍ប្រចាំប្រតិទិននៅលើប្រតិទិនដែលបានចែករំលែក",
"description_hide_calendar_event_details": "នៅពេលដែលប្រតិទិនត្រូវបានចែករំលែក ព្រឹត្តិការណ៍នឹងមើលឃើញបានសម្រាប់អ្នកអាន ប៉ុន្តែព័ត៌មានលម្អិតនឹងត្រូវបានលាក់ពីអ្នកដែលមិនមានសិទ្ធិចូលសរសេរ។",
"last_number_of_days": "ចុងក្រោយ {{count}} ថ្ងៃ",
@@ -2703,8 +2704,21 @@
"add_new_field": "បន្ថែមវាលថ្មី",
"you_dont_have_access_to_reroute_this_booking": "អ្នកមិនមានសិទ្ធិប្តូរទិសការកក់នេះទេ",
"form_response_not_found": "រកមិនឃើញការឆ្លើយតបទម្រង់",
"using_fallback_members": "កំពុងប្រើសមាជិកត្រឡប់ក្រោយ",
"chosen_route": "ផ្លូវដែលបានជ្រើសរើស",
"attribute_logic_matched": "តក្កវិជ្ជាលក្ខណៈសម្បត្តិត្រូវគ្នា",
"attribute_logic_fallback_matched": "តក្កវិជ្ជាលក្ខណៈសម្បត្តិត្រឡប់ក្រោយត្រូវគ្នា",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "សមាជិកទាំងអស់ដែលបានចាត់តាំងនៃប្រភេទព្រឹត្តិការណ៍ក្រុម។ សូមពិចារណាបន្ថែមច្បាប់លក្ខណៈសម្បត្តិមួយចំនួន។",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "សមាជិកទាំងអស់ដែលបានចាត់តាំងនៃប្រភេទព្រឹត្តិការណ៍ក្រុម។ សូមពិចារណាបន្ថែមច្បាប់លក្ខណៈសម្បត្តិមួយចំនួនសម្រាប់ការត្រឡប់ក្រោយ។",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "សមាជិកទាំងអស់ដែលបានចាត់តាំងនៃប្រភេទព្រឹត្តិការណ៍ក្រុម។ សូមពិចារណាកែសម្រួលការត្រឡប់ក្រោយដើម្បីឱ្យមានការផ្គូផ្គង។",
"warning": "ការព្រមាន",
"fallback_attribute_logic_description": "ត្រឡប់ក្រោយ៖ ប្រសិនបើគ្មានសមាជិកក្រុមដែលត្រូវគ្នា សូមប្រើអ្នកដែលត្រូវនឹងលក្ខណៈវិនិច្ឆ័យខាងក្រោម (ផ្គូផ្គងសមាជិកក្រុមទាំងអស់ដែលបានចាត់តាំងសម្រាប់ព្រឹត្តិការណ៍តាមលំនាំដើម)",
"fallback_attribute_logic_warning": "ការព្រមានអំពីការត្រឡប់ក្រោយ",
"fallback_not_needed": "មិនចាំបាច់",
"confirm_reassign_unavailable": "ម្ចាស់កម្មវិធីមិនទំនេរ",
"confirm_reassign_available": "ម្ចាស់កម្មវិធីមានទំនេរ",
"reassign_unavailable_team_member_description": "តើអ្នកប្រាកដថាចង់កំណត់ការកក់នេះទៅម្ចាស់កម្មវិធីដែលមិនទំនេរមែនទេ?",
"reassign_available_team_member_description": "តើអ្នកប្រាកដថាចង់ចាត់តាំងការកក់នេះឡើងវិញឬ?",
"yes_reassign": "បាទ/ចាស កំណត់ឡើងវិញ",
"reassign_reason": "មូលហេតុនៃការកំណត់ឡើងវិញ",
"reassigned_by": "កំណត់ឡើងវិញដោយ",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "이 예약을 변경할 권한이 없습니다",
"matching_members": "일치하는 멤버",
"no_matching_members": "일치하는 멤버가 없습니다. 이벤트 유형에 할당된 팀 멤버를 사용할 것입니다.",
"no_matching_members_will_fallback_to_all_assigned_members": "일치하는 멤버가 없습니다. 이벤트 유형에 할당된 팀 멤버를 사용하는 것으로 대체됩니다. 대체 옵션을 추가하거나 using_fallback_members 로직을 수정하는 것을 고려해보세요.",
"hide_calendar_event_details": "공유 캘린더에서 이벤트 세부 정보 숨기기",
"description_hide_calendar_event_details": "캘린더가 공유되면 이벤트는 읽기 권한이 있는 사람에게 보이지만, 쓰기 권한이 없는 사람에게는 세부 정보가 숨겨집니다.",
"last_number_of_days": "지난 {{count}}일",
@@ -2703,8 +2704,21 @@
"add_new_field": "새 필드 추가",
"you_dont_have_access_to_reroute_this_booking": "이 예약을 재라우팅할 권한이 없습니다",
"form_response_not_found": "양식 응답을 찾을 수 없습니다",
"using_fallback_members": "대체 멤버 사용 중",
"chosen_route": "선택된 경로",
"attribute_logic_matched": "속성 로직 일치함",
"attribute_logic_fallback_matched": "속성 로직 대체 일치함",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "팀 이벤트 유형에 할당된 모든 멤버입니다. 일부 속성 규칙을 추가하는 것을 고려해보세요.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "팀 이벤트 유형에 할당된 모든 멤버입니다. 대체 옵션에 일부 속성 규칙을 추가하는 것을 고려해보세요.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "팀 이벤트 유형에 할당된 모든 멤버입니다. 일치하는 결과를 얻기 위해 대체 옵션을 조정하는 것을 고려해보세요.",
"warning": "경고",
"fallback_attribute_logic_description": "대체: 일치하는 팀 멤버가 없는 경우, 다음 기준에 맞는 멤버를 사용합니다 (기본적으로 이벤트에 할당된 모든 팀 멤버와 일치)",
"fallback_attribute_logic_warning": "대체 경고",
"fallback_not_needed": "필요 없음",
"confirm_reassign_unavailable": "호스트 불가능",
"confirm_reassign_available": "호스트 가능",
"reassign_unavailable_team_member_description": "이 예약을 불가능한 호스트에게 재배정하시겠습니까?",
"reassign_available_team_member_description": "이 예약을 재할당하시겠습니까?",
"yes_reassign": "네, 재배정합니다",
"reassign_reason": "재배정 이유",
"reassigned_by": "재배정한 사람",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "U bent niet gemachtigd om deze wijziging in de boeking aan te brengen",
"matching_members": "Overeenkomende leden",
"no_matching_members": "Geen overeenkomende leden. Er wordt teruggevallen op de teamleden die aan het type evenement zijn toegewezen.",
"no_matching_members_will_fallback_to_all_assigned_members": "Geen overeenkomende leden. Er wordt teruggevallen op de teamleden die aan het evenementtype zijn toegewezen. Overweeg een fallback toe te voegen of de logica van using_fallback_members te corrigeren",
"hide_calendar_event_details": "Verberg details van kalendergebeurtenissen op gedeelde kalenders",
"description_hide_calendar_event_details": "Wanneer een kalender wordt gedeeld, zijn gebeurtenissen zichtbaar voor lezers, maar hun details zijn verborgen voor degenen zonder schrijfrechten.",
"last_number_of_days": "laatste {{count}} dagen",
@@ -2703,8 +2704,21 @@
"add_new_field": "Nieuw veld toevoegen",
"you_dont_have_access_to_reroute_this_booking": "Je hebt geen toegang om deze boeking om te leiden",
"form_response_not_found": "Formulierreactie niet gevonden",
"using_fallback_members": "Gebruik van fallback-leden",
"chosen_route": "Gekozen route",
"attribute_logic_matched": "Attribuutlogica komt overeen",
"attribute_logic_fallback_matched": "Attribuutlogica fallback komt overeen",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Alle toegewezen leden van het team-evenementtype. Overweeg enkele attribuutregels toe te voegen.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Alle toegewezen leden van het team-evenementtype. Overweeg enkele attribuutregels toe te voegen aan de fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Alle toegewezen leden van het team-evenementtype. Overweeg de fallback aan te passen om een match te krijgen.",
"warning": "Waarschuwing",
"fallback_attribute_logic_description": "Fallback: Als er geen teamleden overeenkomen, gebruik dan degenen die aan de volgende criteria voldoen (standaard alle toegewezen teamleden van het evenement)",
"fallback_attribute_logic_warning": "Fallback-waarschuwing",
"fallback_not_needed": "Niet nodig",
"confirm_reassign_unavailable": "Host niet beschikbaar",
"confirm_reassign_available": "Host beschikbaar",
"reassign_unavailable_team_member_description": "Weet je zeker dat je deze boeking wilt toewijzen aan een niet-beschikbare host?",
"reassign_available_team_member_description": "Weet je zeker dat je deze boeking opnieuw wilt toewijzen?",
"yes_reassign": "Ja, opnieuw toewijzen",
"reassign_reason": "Reden voor opnieuw toewijzen",
"reassigned_by": "Opnieuw toegewezen door",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Du har ikke tillatelse til å gjøre denne endringen i bestillingen",
"matching_members": "Matchende medlemmer",
"no_matching_members": "Ingen matchende medlemmer. Det vil falle tilbake til å bruke teammedlemmene som er tildelt hendelsestypen.",
"no_matching_members_will_fallback_to_all_assigned_members": "Ingen matchende medlemmer. Det vil falle tilbake til å bruke teammedlemmene som er tildelt hendelsestypen. Vurder å legge til en reserveløsning eller korrigere logikken for using_fallback_members",
"hide_calendar_event_details": "Skjul kalenderhendelsesdetaljer på delte kalendere",
"description_hide_calendar_event_details": "Når en kalender deles, er hendelser synlige for lesere, men detaljene er skjult for de uten skriveadgang.",
"last_number_of_days": "siste {{count}} dager",
@@ -2703,8 +2704,21 @@
"add_new_field": "Legg til nytt felt",
"you_dont_have_access_to_reroute_this_booking": "Du har ikke tilgang til å omdirigere denne bookingen",
"form_response_not_found": "Skjemasvar ikke funnet",
"using_fallback_members": "Bruker reservemedlemmer",
"chosen_route": "Valgt rute",
"attribute_logic_matched": "Attributtlogikk matchet",
"attribute_logic_fallback_matched": "Attributtlogikk reserveløsning matchet",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Alle tildelte medlemmer av team-hendelsestypen. Vurder å legge til noen attributtregler.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Alle tildelte medlemmer av team-hendelsestypen. Vurder å legge til noen attributtregler som reserveløsning.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Alle tildelte medlemmer av team-hendelsestypen. Vurder å justere reserveløsningen for å få en match.",
"warning": "Advarsel",
"fallback_attribute_logic_description": "Reserveløsning: Hvis ingen teammedlemmer matcher, bruk de som matcher følgende kriterier (matcher som standard alle tildelte teammedlemmer for hendelsen)",
"fallback_attribute_logic_warning": "Advarsel for reserveløsning",
"fallback_not_needed": "Ikke nødvendig",
"confirm_reassign_unavailable": "Vert utilgjengelig",
"confirm_reassign_available": "Vert tilgjengelig",
"reassign_unavailable_team_member_description": "Er du sikker på at du vil omfordele denne bookingen til en utilgjengelig vert?",
"reassign_available_team_member_description": "Er du sikker på at du vil tildele denne bookingen på nytt?",
"yes_reassign": "Ja, omfordel",
"reassign_reason": "Årsak til omfordeling",
"reassigned_by": "Omfordelt av",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Nie masz uprawnień do wprowadzenia tej zmiany w rezerwacji",
"matching_members": "Pasujący członkowie",
"no_matching_members": "Brak pasujących członków. Zostaną użyci członkowie zespołu przypisani do typu wydarzenia.",
"no_matching_members_will_fallback_to_all_assigned_members": "Brak pasujących członków. Nastąpi powrót do użycia członków zespołu przypisanych do typu wydarzenia. Rozważ dodanie zapasowego rozwiązania lub poprawienie logiki using_fallback_members",
"hide_calendar_event_details": "Ukryj szczegóły wydarzeń w kalendarzu na udostępnionych kalendarzach",
"description_hide_calendar_event_details": "Gdy kalendarz jest udostępniony, wydarzenia są widoczne dla czytelników, ale ich szczegóły są ukryte dla osób bez dostępu do edycji.",
"last_number_of_days": "ostatnie {{count}} dni",
@@ -2703,8 +2704,21 @@
"add_new_field": "Dodaj nowe pole",
"you_dont_have_access_to_reroute_this_booking": "Nie masz dostępu do przekierowania tej rezerwacji",
"form_response_not_found": "Nie znaleziono odpowiedzi formularza",
"using_fallback_members": "Używanie zapasowych członków",
"chosen_route": "Wybrana trasa",
"attribute_logic_matched": "Logika atrybutów dopasowana",
"attribute_logic_fallback_matched": "Zapasowa logika atrybutów dopasowana",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Wszyscy przypisani członkowie zespołowego typu wydarzenia. Rozważ dodanie kilku reguł atrybutów.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Wszyscy przypisani członkowie zespołowego typu wydarzenia. Rozważ dodanie kilku reguł atrybutów do zapasowego rozwiązania.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Wszyscy przypisani członkowie zespołowego typu wydarzenia. Rozważ dostosowanie zapasowego rozwiązania, aby uzyskać dopasowanie.",
"warning": "Ostrzeżenie",
"fallback_attribute_logic_description": "Zapasowe rozwiązanie: Jeśli żaden członek zespołu nie pasuje, użyj tych, którzy spełniają następujące kryteria (domyślnie pasuje do wszystkich przypisanych członków zespołu dla wydarzenia)",
"fallback_attribute_logic_warning": "Ostrzeżenie zapasowego rozwiązania",
"fallback_not_needed": "Niepotrzebne",
"confirm_reassign_unavailable": "Host niedostępny",
"confirm_reassign_available": "Host dostępny",
"reassign_unavailable_team_member_description": "Czy na pewno chcesz przypisać tę rezerwację do niedostępnego hosta?",
"reassign_available_team_member_description": "Czy na pewno chcesz przypisać tę rezerwację ponownie?",
"yes_reassign": "Tak, przypisz ponownie",
"reassign_reason": "Powód ponownego przypisania",
"reassigned_by": "Przypisane ponownie przez",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Você não tem autorização para fazer essa alteração na reserva",
"matching_members": "Membros correspondentes",
"no_matching_members": "Nenhum membro correspondente. Será utilizado como alternativa os membros da equipe atribuídos ao tipo de evento.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nenhum membro correspondente. Será usado como fallback os membros da equipe atribuídos ao tipo de evento. Considere adicionar um fallback ou corrigir a lógica de using_fallback_members",
"hide_calendar_event_details": "Ocultar detalhes dos eventos do calendário em calendários compartilhados",
"description_hide_calendar_event_details": "Quando um calendário é compartilhado, os eventos são visíveis para os leitores, mas seus detalhes são ocultados para aqueles que não têm acesso de escrita.",
"last_number_of_days": "últimos {{count}} dias",
@@ -2703,8 +2704,21 @@
"add_new_field": "Adicionar novo campo",
"you_dont_have_access_to_reroute_this_booking": "Você não tem acesso para redirecionar esta reserva",
"form_response_not_found": "Resposta do formulário não encontrada",
"using_fallback_members": "Usando membros de fallback",
"chosen_route": "Rota escolhida",
"attribute_logic_matched": "Lógica de atributos correspondida",
"attribute_logic_fallback_matched": "Lógica de atributos de fallback correspondida",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Todos os membros atribuídos ao tipo de evento da equipe. Considere adicionar algumas regras de atributos.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Todos os membros atribuídos ao tipo de evento da equipe. Considere adicionar algumas regras de atributos para fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Todos os membros atribuídos ao tipo de evento da equipe. Considere ajustar o fallback para obter uma correspondência.",
"warning": "Aviso",
"fallback_attribute_logic_description": "Fallback: Se nenhum membro da equipe corresponder, use aqueles que atendam aos seguintes critérios (por padrão, corresponde a todos os membros da equipe atribuídos ao evento)",
"fallback_attribute_logic_warning": "Aviso de fallback",
"fallback_not_needed": "Não necessário",
"confirm_reassign_unavailable": "Anfitrião indisponível",
"confirm_reassign_available": "Anfitrião disponível",
"reassign_unavailable_team_member_description": "Tem certeza de que deseja reatribuir esta reserva a um anfitrião indisponível?",
"reassign_available_team_member_description": "Tem certeza de que deseja reatribuir esta reserva?",
"yes_reassign": "Sim, reatribuir",
"reassign_reason": "Motivo da reatribuição",
"reassigned_by": "Reatribuído por",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Não tem autorização para fazer esta alteração na reserva",
"matching_members": "Membros correspondentes",
"no_matching_members": "Nenhum membro correspondente. Será utilizado como alternativa os membros da equipa atribuídos ao tipo de evento.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nenhum membro correspondente. Irá recorrer aos membros da equipa atribuídos ao tipo de evento. Considere adicionar um fallback ou corrigir a lógica de using_fallback_members",
"hide_calendar_event_details": "Ocultar detalhes dos eventos do calendário em calendários partilhados",
"description_hide_calendar_event_details": "Quando um calendário é partilhado, os eventos são visíveis para os leitores, mas os seus detalhes são ocultados para aqueles que não têm acesso de escrita.",
"last_number_of_days": "últimos {{count}} dias",
@@ -2703,8 +2704,21 @@
"add_new_field": "Adicionar novo campo",
"you_dont_have_access_to_reroute_this_booking": "Não tem acesso para redirecionar esta reserva",
"form_response_not_found": "Resposta do formulário não encontrada",
"using_fallback_members": "A usar membros de fallback",
"chosen_route": "Rota escolhida",
"attribute_logic_matched": "Lógica de atributos correspondida",
"attribute_logic_fallback_matched": "Lógica de atributos de fallback correspondida",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Todos os membros atribuídos ao tipo de evento da equipa. Considere adicionar algumas regras de atributos.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Todos os membros atribuídos ao tipo de evento da equipa. Considere adicionar algumas regras de atributos para fallback.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Todos os membros atribuídos ao tipo de evento da equipa. Considere ajustar o fallback para obter uma correspondência.",
"warning": "Aviso",
"fallback_attribute_logic_description": "Fallback: Se nenhum membro da equipa corresponder, use aqueles que correspondem aos seguintes critérios (por padrão, corresponde a todos os membros da equipa atribuídos ao evento)",
"fallback_attribute_logic_warning": "Aviso de fallback",
"fallback_not_needed": "Não necessário",
"confirm_reassign_unavailable": "Anfitrião indisponível",
"confirm_reassign_available": "Anfitrião disponível",
"reassign_unavailable_team_member_description": "Tem a certeza de que quer reatribuir esta reserva a um anfitrião indisponível?",
"reassign_available_team_member_description": "Tem a certeza de que deseja reatribuir esta marcação?",
"yes_reassign": "Sim, reatribuir",
"reassign_reason": "Motivo da reatribuição",
"reassigned_by": "Reatribuído por",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Nu aveți autorizația necesară pentru a face această modificare în rezervare",
"matching_members": "Membri corespunzători",
"no_matching_members": "Nu există membri corespunzători. Se va reveni la utilizarea membrilor echipei alocați tipului de eveniment.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nu există membri potriviți. Se va reveni la utilizarea membrilor echipei atribuiți tipului de eveniment. Luați în considerare adăugarea unei opțiuni de rezervă sau corectarea logicii de utilizare a membrilor de rezervă",
"hide_calendar_event_details": "Ascunde detaliile evenimentelor din calendar pe calendarele partajate",
"description_hide_calendar_event_details": "Când un calendar este partajat, evenimentele sunt vizibile pentru cititori, dar detaliile lor sunt ascunse celor fără acces de scriere.",
"last_number_of_days": "ultimele {{count}} zile",
@@ -2703,8 +2704,21 @@
"add_new_field": "Adaugă câmp nou",
"you_dont_have_access_to_reroute_this_booking": "Nu ai acces pentru a redirecționa această rezervare",
"form_response_not_found": "Răspunsul formularului nu a fost găsit",
"using_fallback_members": "Se utilizează membrii de rezervă",
"chosen_route": "Ruta aleasă",
"attribute_logic_matched": "Logica atributelor corespunde",
"attribute_logic_fallback_matched": "Logica de rezervă a atributelor corespunde",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Toți membrii atribuiți tipului de eveniment de echipă. Luați în considerare adăugarea unor reguli de atribute.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Toți membrii atribuiți tipului de eveniment de echipă. Luați în considerare adăugarea unor reguli de atribute pentru opțiunea de rezervă.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Toți membrii atribuiți tipului de eveniment de echipă. Luați în considerare ajustarea opțiunii de rezervă pentru a obține o potrivire.",
"warning": "Avertisment",
"fallback_attribute_logic_description": "Opțiune de rezervă: Dacă niciun membru al echipei nu corespunde, folosiți pe cei care îndeplinesc următoarele criterii (implicit, se potrivesc toți membrii echipei atribuiți evenimentului)",
"fallback_attribute_logic_warning": "Avertisment pentru opțiunea de rezervă",
"fallback_not_needed": "Nu este necesară",
"confirm_reassign_unavailable": "Gazda nu este disponibilă",
"confirm_reassign_available": "Gazdă disponibilă",
"reassign_unavailable_team_member_description": "Ești sigur că vrei să reatribui această rezervare unei gazde indisponibile?",
"reassign_available_team_member_description": "Sunteți sigur că doriți să reatribuiți această rezervare?",
"yes_reassign": "Da, reatribuie",
"reassign_reason": "Motivul reatribuirii",
"reassigned_by": "Reatribuit de",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Nemate ovlašćenje da izvršite ovu promenu u rezervaciji",
"matching_members": "Одговарајући чланови",
"no_matching_members": "Нема одговарајућих чланова. Систем ће се вратити на коришћење чланова тима додељених типу догађаја.",
"no_matching_members_will_fallback_to_all_assigned_members": "Nema odgovarajućih članova. Koristiće se članovi tima dodeljeni tipu događaja. Razmotrite dodavanje rezervne opcije ili ispravku logike korišćenja rezervnih članova",
"hide_calendar_event_details": "Sakrij detalje događaja u deljenim kalendarima",
"description_hide_calendar_event_details": "Kada je kalendar podeljen, događaji su vidljivi čitaocima, ali su njihovi detalji skriveni od onih koji nemaju pravo izmene.",
"last_number_of_days": "poslednjih {{count}} dana",
@@ -2703,8 +2704,21 @@
"add_new_field": "Dodaj novo polje",
"you_dont_have_access_to_reroute_this_booking": "Nemate pristup za preusmeravanje ove rezervacije",
"form_response_not_found": "Odgovor na formular nije pronađen",
"using_fallback_members": "Korišćenje rezervnih članova",
"chosen_route": "Izabrana ruta",
"attribute_logic_matched": "Logika atributa se podudara",
"attribute_logic_fallback_matched": "Rezervna logika atributa se podudara",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Svi dodeljeni članovi tipa timskog događaja. Razmotrite dodavanje nekih pravila atributa.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Svi dodeljeni članovi tipa timskog događaja. Razmotrite dodavanje nekih pravila atributa za rezervnu opciju.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Svi dodeljeni članovi tipa timskog događaja. Razmotrite podešavanje rezervne opcije da bi se dobilo podudaranje.",
"warning": "Upozorenje",
"fallback_attribute_logic_description": "Rezervna opcija: Ako se nijedan član tima ne podudara, koristite one koji odgovaraju sledećim kriterijumima (podrazumevano odgovara svim dodeljenim članovima tima za događaj)",
"fallback_attribute_logic_warning": "Upozorenje za rezervnu opciju",
"fallback_not_needed": "Nije potrebno",
"confirm_reassign_unavailable": "Domaćin nije dostupan",
"confirm_reassign_available": "Domaćin dostupan",
"reassign_unavailable_team_member_description": "Da li ste sigurni da želite da preraspodelite ovu rezervaciju nedostupnom domaćinu?",
"reassign_available_team_member_description": "Da li ste sigurni da želite da preraspodelite ovu rezervaciju?",
"yes_reassign": "Da, preraspodelite",
"reassign_reason": "Razlog za preraspodelu",
"reassigned_by": "Preraspodelilo",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Du har inte behörighet att göra denna ändring i bokningen",
"matching_members": "Matchande medlemmar",
"no_matching_members": "Inga matchande medlemmar. Det kommer att återgå till att använda de teammedlemmar som är tilldelade till händelsetypen.",
"no_matching_members_will_fallback_to_all_assigned_members": "Inga matchande medlemmar. Det kommer att falla tillbaka på att använda teammedlemmarna som är tilldelade till händelsetypen. Överväg att lägga till en reservplan eller korrigera logiken för using_fallback_members",
"hide_calendar_event_details": "Dölj kalenderhändelsedetaljer på delade kalendrar",
"description_hide_calendar_event_details": "När en kalender delas är händelser synliga för läsare, men deras detaljer är dolda för dem utan skrivåtkomst.",
"last_number_of_days": "senaste {{count}} dagarna",
@@ -2703,8 +2704,21 @@
"add_new_field": "Lägg till nytt fält",
"you_dont_have_access_to_reroute_this_booking": "Du har inte behörighet att omdirigera denna bokning",
"form_response_not_found": "Formulärsvar hittades inte",
"using_fallback_members": "Använder reservmedlemmar",
"chosen_route": "Vald rutt",
"attribute_logic_matched": "Attributlogik matchad",
"attribute_logic_fallback_matched": "Attributlogik reservplan matchad",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Alla tilldelade medlemmar av teamhändelsetypen. Överväg att lägga till några attributregler.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Alla tilldelade medlemmar av teamhändelsetypen. Överväg att lägga till några attributregler som reservplan.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Alla tilldelade medlemmar av teamhändelsetypen. Överväg att justera reservplanen för att få en match.",
"warning": "Varning",
"fallback_attribute_logic_description": "Reservplan: Om inga teammedlemmar matchar, använd de som uppfyller följande kriterier (matchar som standard alla tilldelade teammedlemmar för händelsen)",
"fallback_attribute_logic_warning": "Varning för reservplan",
"fallback_not_needed": "Behövs inte",
"confirm_reassign_unavailable": "Värd otillgänglig",
"confirm_reassign_available": "Värd tillgänglig",
"reassign_unavailable_team_member_description": "Är du säker på att du vill omfördela denna bokning till en otillgänglig värd?",
"reassign_available_team_member_description": "Är du säker på att du vill omfördela denna bokning?",
"yes_reassign": "Ja, omfördela",
"reassign_reason": "Anledning till omfördelning",
"reassigned_by": "Omfördelad av",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Bu rezervasyonda değişiklik yapma yetkiniz yok",
"matching_members": "Eşleşen üyeler",
"no_matching_members": "Eşleşen üye yok. Etkinlik türüne atanan takım üyeleri kullanılacaktır.",
"no_matching_members_will_fallback_to_all_assigned_members": "Eşleşen üye yok. Etkinlik türüne atanmış tüm ekip üyelerine geri dönülecek. Yedek eklemeyi veya using_fallback_members mantığını düzeltmeyi düşünün",
"hide_calendar_event_details": "Paylaşılan takvimlerde etkinlik ayrıntılarını gizle",
"description_hide_calendar_event_details": "Bir takvim paylaşıldığında, etkinlikler okuyuculara görünür ancak yazma erişimi olmayanlar için ayrıntılar gizlenir.",
"last_number_of_days": "son {{count}} gün",
@@ -2703,8 +2704,21 @@
"add_new_field": "Yeni alan ekle",
"you_dont_have_access_to_reroute_this_booking": "Bu rezervasyonu yeniden yönlendirme erişiminiz yok",
"form_response_not_found": "Form yanıtı bulunamadı",
"using_fallback_members": "Yedek üyeler kullanılıyor",
"chosen_route": "Seçilen rota",
"attribute_logic_matched": "Özellik mantığı eşleşti",
"attribute_logic_fallback_matched": "Özellik mantığı yedeği eşleşti",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Ekip etkinlik türüne atanmış tüm üyeler. Bazı özellik kuralları eklemeyi düşünün.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Ekip etkinlik türüne atanmış tüm üyeler. Yedeğe bazı özellik kuralları eklemeyi düşünün.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Ekip etkinlik türüne atanmış tüm üyeler. Eşleşme sağlamak için yedeği ayarlamayı düşünün.",
"warning": "Uyarı",
"fallback_attribute_logic_description": "Yedek: Eğer hiçbir ekip üyesi eşleşmezse, aşağıdaki kriterlere uyan üyeleri kullan (varsayılan olarak etkinliğe atanmış tüm ekip üyeleriyle eşleşir)",
"fallback_attribute_logic_warning": "Yedek uyarısı",
"fallback_not_needed": "Gerekli değil",
"confirm_reassign_unavailable": "Ev sahibi uygun değil",
"confirm_reassign_available": "Uygun ev sahibi",
"reassign_unavailable_team_member_description": "Bu rezervasyonu uygun olmayan bir ev sahibine yeniden atamak istediğinizden emin misiniz?",
"reassign_available_team_member_description": "Bu rezervasyonu yeniden atamak istediğinizden emin misiniz?",
"yes_reassign": "Evet, yeniden ata",
"reassign_reason": "Yeniden atama nedeni",
"reassigned_by": "Yeniden atayan",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Ви не маєте дозволу на внесення цієї зміни до бронювання",
"matching_members": "Відповідні учасники",
"no_matching_members": "Відповідних учасників немає. Буде використано учасників команди, призначених для цього типу події.",
"no_matching_members_will_fallback_to_all_assigned_members": "Немає відповідних учасників. Буде використано всіх учасників команди, призначених для типу події. Розгляньте можливість додавання резервного варіанту або виправлення логіки використання резервних учасників",
"hide_calendar_event_details": "Приховати деталі подій календаря у спільних календарях",
"description_hide_calendar_event_details": "Коли календар спільний, події видимі для читачів, але їхні деталі приховані для тих, хто не має прав на редагування.",
"last_number_of_days": "останні {{count}} днів",
@@ -2703,8 +2704,21 @@
"add_new_field": "Додати нове поле",
"you_dont_have_access_to_reroute_this_booking": "У вас немає доступу для перенаправлення цього бронювання",
"form_response_not_found": "Відповідь форми не знайдено",
"using_fallback_members": "Використання резервних учасників",
"chosen_route": "Обраний маршрут",
"attribute_logic_matched": "Логіка атрибутів співпала",
"attribute_logic_fallback_matched": "Співпала резервна логіка атрибутів",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Всі призначені учасники командного типу події. Розгляньте можливість додавання деяких правил атрибутів",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Всі призначені учасники командного типу події. Розгляньте можливість додавання деяких правил атрибутів для резервного варіанту",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Всі призначені учасники командного типу події. Розгляньте можливість налаштування резервного варіанту для отримання збігу",
"warning": "Попередження",
"fallback_attribute_logic_description": "Резервний варіант: Якщо жоден учасник команди не відповідає, використовуйте тих, хто відповідає наступним критеріям (за замовчуванням відповідає всім призначеним учасникам команди для події)",
"fallback_attribute_logic_warning": "Попередження про резервний варіант",
"fallback_not_needed": "Не потрібно",
"confirm_reassign_unavailable": "Організатор недоступний",
"confirm_reassign_available": "Організатор доступний",
"reassign_unavailable_team_member_description": "Ви впевнені, що хочете перепризначити це бронювання недоступному організатору?",
"reassign_available_team_member_description": "Ви впевнені, що хочете перепризначити це бронювання?",
"yes_reassign": "Так, перепризначити",
"reassign_reason": "Причина перепризначення",
"reassigned_by": "Перепризначено",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "Bạn không có quyền thực hiện thay đổi này đối với đặt chỗ",
"matching_members": "Thành viên phù hợp",
"no_matching_members": "Không có thành viên phù hợp. Hệ thống sẽ sử dụng các thành viên trong nhóm được chỉ định cho loại sự kiện.",
"no_matching_members_will_fallback_to_all_assigned_members": "Không có thành viên phù hợp. Hệ thống sẽ sử dụng tất cả thành viên nhóm được chỉ định cho loại sự kiện. Hãy cân nhắc thêm phương án dự phòng hoặc điều chỉnh logic sử dụng thành viên dự phòng",
"hide_calendar_event_details": "Ẩn chi tiết sự kiện trên lịch chia sẻ",
"description_hide_calendar_event_details": "Khi một lịch được chia sẻ, các sự kiện sẽ hiển thị cho người xem nhưng chi tiết của chúng sẽ bị ẩn đối với những người không có quyền chỉnh sửa.",
"last_number_of_days": "{{count}} ngày qua",
@@ -2703,8 +2704,21 @@
"add_new_field": "Thêm trường mới",
"you_dont_have_access_to_reroute_this_booking": "Bạn không có quyền chuyển hướng đặt lịch này",
"form_response_not_found": "Không tìm thấy phản hồi biểu mẫu",
"using_fallback_members": "Đang sử dụng thành viên dự phòng",
"chosen_route": "Lộ trình đã chọn",
"attribute_logic_matched": "Logic thuộc tính phù hợp",
"attribute_logic_fallback_matched": "Logic thuộc tính dự phòng phù hợp",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "Tất cả thành viên được chỉ định của loại sự kiện nhóm. Hãy cân nhắc thêm một số quy tắc thuộc tính.",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "Tất cả thành viên được chỉ định của loại sự kiện nhóm. Hãy cân nhắc thêm một số quy tắc thuộc tính cho phương án dự phòng.",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "Tất cả thành viên được chỉ định của loại sự kiện nhóm. Hãy cân nhắc điều chỉnh phương án dự phòng để có sự phù hợp.",
"warning": "Cảnh báo",
"fallback_attribute_logic_description": "Dự phòng: Nếu không có Thành viên Nhóm nào phù hợp, sử dụng những người phù hợp với các tiêu chí sau (mặc định là tất cả thành viên nhóm được chỉ định cho sự kiện)",
"fallback_attribute_logic_warning": "Cảnh báo dự phòng",
"fallback_not_needed": "Không cần thiết",
"confirm_reassign_unavailable": "Chủ trì không khả dụng",
"confirm_reassign_available": "Người tổ chức khả dụng",
"reassign_unavailable_team_member_description": "Bạn có chắc chắn muốn phân công lại đặt lịch này cho một chủ trì không khả dụng không?",
"reassign_available_team_member_description": "Bạn có chắc chắn muốn phân công lại đặt chỗ này không?",
"yes_reassign": "Có, phân công lại",
"reassign_reason": "Lý do phân công lại",
"reassigned_by": "Được phân công lại bởi",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "您无权对此预订进行更改",
"matching_members": "匹配的成员",
"no_matching_members": "没有匹配的成员。将回退到使用分配给事件类型的团队成员。",
"no_matching_members_will_fallback_to_all_assigned_members": "没有匹配的成员。将回退使用分配给该事件类型的团队成员。考虑添加一个回退选项或修正使用回退成员的逻辑",
"hide_calendar_event_details": "隐藏共享日历中的事件详情",
"description_hide_calendar_event_details": "当日历被共享时,事件对读者可见,但其详情对没有写入权限的人隐藏。",
"last_number_of_days": "过去{{count}}天",
@@ -2703,8 +2704,21 @@
"add_new_field": "添加新字段",
"you_dont_have_access_to_reroute_this_booking": "您无权重新路由此预订",
"form_response_not_found": "未找到表单响应",
"using_fallback_members": "正在使用回退成员",
"chosen_route": "选定路线",
"attribute_logic_matched": "属性逻辑匹配",
"attribute_logic_fallback_matched": "属性逻辑回退匹配",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "团队事件类型的所有分配成员。考虑添加一些属性规则。",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "团队事件类型的所有分配成员。考虑为回退添加一些属性规则。",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "团队事件类型的所有分配成员。考虑调整回退以获得匹配。",
"warning": "警告",
"fallback_attribute_logic_description": "回退:如果没有团队成员匹配,使用符合以下条件的成员(默认匹配事件的所有分配团队成员)",
"fallback_attribute_logic_warning": "回退警告",
"fallback_not_needed": "不需要",
"confirm_reassign_unavailable": "主持人不可用",
"confirm_reassign_available": "主持人可用",
"reassign_unavailable_team_member_description": "您确定要将此预订重新分配给不可用的主持人吗?",
"reassign_available_team_member_description": "您确定要重新分配这个预订吗?",
"yes_reassign": "是的,重新分配",
"reassign_reason": "重新分配原因",
"reassigned_by": "重新分配者",
@@ -2656,6 +2656,7 @@
"you_are_unauthorized_to_make_this_change_to_the_booking": "您無權更改此預訂",
"matching_members": "符合條件的成員",
"no_matching_members": "沒有符合條件的成員。將回退使用分配給事件類型的團隊成員。",
"no_matching_members_will_fallback_to_all_assigned_members": "沒有符合條件的成員。將回退使用指派給該事件類型的團隊成員。請考慮添加回退選項或修正使用回退成員的邏輯",
"hide_calendar_event_details": "隱藏共享行事曆中的事件詳情",
"description_hide_calendar_event_details": "當行事曆被共享時,事件對讀者可見,但其詳情對沒有寫入權限的人隱藏。",
"last_number_of_days": "過去 {{count}} 天",
@@ -2703,8 +2704,21 @@
"add_new_field": "新增欄位",
"you_dont_have_access_to_reroute_this_booking": "您沒有權限重新安排此預訂",
"form_response_not_found": "找不到表單回應",
"using_fallback_members": "使用回退成員",
"chosen_route": "選定路徑",
"attribute_logic_matched": "屬性邏輯匹配",
"attribute_logic_fallback_matched": "屬性邏輯回退匹配",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules": "團隊事件類型的所有指派成員。請考慮添加一些屬性規則",
"all_assigned_members_of_the_team_event_type_consider_adding_some_attribute_rules_to_fallback": "團隊事件類型的所有指派成員。請考慮為回退添加一些屬性規則",
"all_assigned_members_of_the_team_event_type_consider_tweaking_fallback_to_have_a_match": "團隊事件類型的所有指派成員。請考慮調整回退以獲得匹配",
"warning": "警告",
"fallback_attribute_logic_description": "回退:如果沒有團隊成員匹配,則使用符合以下條件的成員(預設匹配事件的所有指派團隊成員)",
"fallback_attribute_logic_warning": "回退警告",
"fallback_not_needed": "不需要",
"confirm_reassign_unavailable": "主持人不可用",
"confirm_reassign_available": "主持人可用",
"reassign_unavailable_team_member_description": "您確定要將此預訂重新分配給一位不可用的主持人嗎?",
"reassign_available_team_member_description": "您確定要重新指派這個預約嗎?",
"yes_reassign": "是的,重新分配",
"reassign_reason": "重新分配的原因",
"reassigned_by": "重新分配者",
@@ -9,8 +9,6 @@ checkRoute () {
checkRoute "$APP_ROUTER_EVENT_TYPES_ENABLED" app/future/event-types
checkRoute "$APP_ROUTER_AVAILABILITY_ENABLED" app/future/availability
checkRoute "$APP_ROUTER_SETTINGS_DEVELOPER_ENABLED" app/future/settings/developer
checkRoute "$APP_ROUTER_SETTINGS_MY_ACCOUNT_ENABLED" app/future/settings/my-account
checkRoute "$APP_ROUTER_SETTINGS_PLATFORM_ENABLED" app/future/settings/platform
checkRoute "$APP_ROUTER_SETTINGS_ORG_ENABLED" app/future/settings/organizations
checkRoute "$APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED" app/future/apps/installed
checkRoute "$APP_ROUTER_APPS_SLUG_ENABLED" app/future/apps/\[slug\]
+5
View File
@@ -1,5 +1,10 @@
import * as Sentry from "@sentry/nextjs";
Sentry.init({
debug: !!process.env.SENTRY_DEBUG,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE ?? "0.0") || 0.0,
_experiments: {
maxSpans: parseInt(process.env.SENTRY_MAX_SPANS ?? "1000") || 1000,
},
});
+4
View File
@@ -0,0 +1,4 @@
{
"status": "failed",
"failedTests": []
}
+228
View File
@@ -0,0 +1,228 @@
import type { Table } from "@tanstack/react-table";
import { describe, it, expect, vi } from "vitest";
import type { UserTableUser } from "@calcom/features/users/components/UserTable/types";
import { generateCsvRaw, generateHeaderFromReactTable } from "@calcom/lib/csvUtils";
import type { MembershipRole } from "@calcom/prisma/enums";
function createMockTable(data: UserTableUser[]): Table<UserTableUser> {
return {
getHeaderGroups: vi.fn().mockReturnValue([
{
headers: [
{
id: "select",
column: { columnDef: { header: "" } },
getContext: () => ({}),
},
{
id: "member",
column: { columnDef: { header: "Members" } },
getContext: () => ({}),
},
{
id: "role",
column: { columnDef: { header: "Role" } },
getContext: () => ({}),
},
{
id: "teams",
column: { columnDef: { header: "Teams" } },
getContext: () => ({}),
},
{
id: "attr1",
column: { columnDef: { header: () => "Attribute 1" } },
getContext: () => ({}),
},
{
id: "attr2",
column: { columnDef: { header: () => "Attribute 2" } },
getContext: () => ({}),
},
{
id: "actions",
column: { columnDef: { header: "" } },
getContext: () => ({}),
},
],
},
]),
getRowModel: vi.fn().mockReturnValue({ rows: data.map((item) => ({ original: item })) }),
} as unknown as Table<UserTableUser>;
}
describe("generate Csv for Org Users Table", () => {
const mockAttributeIds = ["attr1", "attr2"];
const HEADER_IDS_TO_EXCLUDE = ["select", "actions"];
const mockUser: UserTableUser = {
id: 1,
username: "testuser",
email: "test@example.com",
timeZone: "UTC",
role: "MEMBER" as MembershipRole,
avatarUrl: null,
accepted: true,
disableImpersonation: false,
completedOnboarding: true,
teams: [],
attributes: [],
};
it("should return null if no headers", () => {
const mockTableNoHeaders = {
getHeaderGroups: vi.fn().mockReturnValue([]),
} as unknown as Table<UserTableUser>;
expect(generateCsvRaw([], [], mockAttributeIds)).toBeNull();
});
it("should generate correct CSV headers", () => {
const mockTable = createMockTable([]);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
[],
mockAttributeIds
);
const headers = csv?.split("\n")[0];
expect(headers).toBe("Members,Role,Teams,Attribute 1,Attribute 2");
});
it("should handle user with single attribute value", () => {
const mockData: UserTableUser[] = [
{
...mockUser,
teams: [{ id: 1, name: "Team1", slug: "team1" }],
attributes: [{ id: "1", attributeId: "attr1", value: "value1", slug: "slug1" }],
},
];
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
test@example.com,MEMBER,Team1,value1,"
`);
});
it("should handle user with multiple attribute values for same attribute", () => {
const mockData: UserTableUser[] = [
{
...mockUser,
teams: [{ id: 1, name: "Team1", slug: "team1" }],
attributes: [
{ id: "1", attributeId: "attr1", value: "value1", slug: "slug1" },
{ id: "2", attributeId: "attr1", value: "value2", slug: "slug1" },
],
},
];
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
test@example.com,MEMBER,Team1,"value1,value2","
`);
});
it("should handle user with multiple teams", () => {
const mockData: UserTableUser[] = [
{
...mockUser,
teams: [
{ id: 1, name: "Team1", slug: "team1" },
{ id: 2, name: "Team2", slug: "team2" },
],
attributes: [],
},
];
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
test@example.com,MEMBER,"Team1,Team2",,"
`);
});
it("should handle values that need sanitization", () => {
const mockData: UserTableUser[] = [
{
...mockUser,
teams: [{ id: 1, name: "Team,1", slug: "team1" }],
attributes: [{ id: "1", attributeId: "attr1", value: "value,1", slug: "slug1" }],
},
];
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
test@example.com,MEMBER,"Team,1","value,1","
`);
});
it("should handle all membership roles", () => {
const roles: MembershipRole[] = ["OWNER", "ADMIN", "MEMBER"];
const mockData: UserTableUser[] = roles.map((role) => ({
...mockUser,
role,
email: `${role.toLowerCase()}@example.com`,
}));
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
owner@example.com,OWNER,,,
admin@example.com,ADMIN,,,
member@example.com,MEMBER,,,"
`);
});
it("should handle users without teams and attributes", () => {
const mockData: UserTableUser[] = [
{
...mockUser,
username: null,
avatarUrl: null,
},
];
const mockTable = createMockTable(mockData);
const csv = generateCsvRaw(
generateHeaderFromReactTable(mockTable, HEADER_IDS_TO_EXCLUDE) ?? [],
mockData,
mockAttributeIds
);
expect(csv).toMatchInlineSnapshot(`
"Members,Role,Teams,Attribute 1,Attribute 2
test@example.com,MEMBER,,,"
`);
});
});
@@ -83,4 +83,4 @@ A sample failure in create command
Once your app is working in your environment, you can publish it to Cal.com App Store by opening a PR [here](https://github.com/calcom/cal.com).
If you need any help feel free to join us on [Discord](https://go.cal.com/discord).
If you need any help feel free to join us in our [GitHub Discussions](https://github.com/calcom/cal.com/discussions).
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

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