Compare commits

...
Author SHA1 Message Date
emrysal 65f6689025 chore: release v5.6.18
Post release / release (push) Canceled after 0s
2025-09-11 10:33:13 +00:00
Hariom Balhara 92005b93c3 All handleNewBooking tests import from getNewBookingHandler (#23741) 2025-09-11 06:58:16 -03:00
Anik Dhabal Babu 0a7e573117 fix: scdules reset to default (#23759) 2025-09-11 04:58:30 +00:00
Anik Dhabal Babu 943c1a3c03 fix: events not removed from the previous host calendar due to wrong credentials usage (#23756)
* fix: events not removed from the previous host claendar

* fix test

* Update roundRobinManualReassignment.test.ts
2025-09-11 04:03:50 +00:00
Benny Joo 9156848503 refactor: circular deps between app store and lib [2] (#23734)
* mv getDefaultLocations

* fix

* 2 more utils

* update imports

* fix

* move getConnectedApps

* move handlePaymentSuccess

* update imports

* fix import

* publish platform libraries
2025-09-10 23:18:59 -03:00
emrysal 2e6ad8be9a chore: release v5.6.17
Post release / release (push) Canceled after 0s
2025-09-10 23:17:01 +00:00
Calcom BotandLingo.dev 1aca18324d feat: update translations via @LingoDotDev (#23753)
Co-authored-by: Lingo.dev <support@lingo.dev>
2025-09-10 21:38:05 +05:30
Udit TakkarandPeer Richelsen a8a3e48465 feat: add web phone call for cal ai (#23527)
* feat: add web phone call for cal ai

* fix: web call

* refactor: improvements

* chore

* refactor: web call improvements

* chore: error message

* refactor: improvements

* fix: err

* refactor: feedback and improvements

* fix: type errors

* refactor: hasAvailable

* chore: use orgid

* fix: types

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2025-09-10 12:41:10 -03:00
Volnei Munhoz 2e96600529 Revert "fix(workflows): hide organizer email in workflow emails (#23525)" (#23739)
This reverts commit 7f200df1ac.
2025-09-10 11:49:38 +00:00
Hariom Balhara b9074aeb81 Better ioctopus type-safety (#23541) 2025-09-10 17:10:25 +05:30
Udit Takkar 13add03479 chore: update vitest (#23732) 2025-09-10 16:59:25 +05:30
Hariom Balhara bb2dcc51c6 Ignore requests from bot for headless router (#23733) 2025-09-10 16:59:04 +05:30
RomitandKartik Saini 8f82e78597 fix: loading skeleton is rendered even when no attributes filter is selected (#23656)
* fix

* fix tests

* fix tests

* test fidx

* chore

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
2025-09-10 16:57:56 +05:30
Eunjae LeeandDevin AI 34d2a13080 fix: move admin permission check to server-side to prevent flash (#23517)
- Move permission validation from AdminLayoutAppDirClient.tsx (client) to layout.tsx (server)
- Add server-side redirect using next/navigation redirect() function
- Remove client-side useEffect permission check to eliminate flash
- Follow same pattern as organizations layout for consistent auth handling
- Users without admin role are redirected to /settings/my-account/profile before any content renders

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-10 11:15:37 +01:00
Eunjae LeeandDevin AI 6066ed206b fix: Fix booking count discrepancy between KPI cards and trends chart (#23619)
* fix: correct booking count in EventTrendsChart by restructuring SQL query

- Replace JOIN-based query with CTE approach to count distinct bookings
- Separate booking stats from guest stats to prevent duplicate counting
- Preserve no-show guest functionality with LEFT JOIN
- Ensure EventTrendsChart 'Created' sum matches BookingKPICards 'events_created'

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: use positional GROUP BY references to resolve PostgreSQL error

- Replace column expressions in GROUP BY with positional references (1, 2, 3)
- Fixes PostgreSQL error 42803 caused by parameter resolution in CTE queries
- Maintains the same query logic while avoiding column reference conflicts

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* address feedback

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-10 11:13:29 +01:00
d30d2792cd fix: hide notes (#22061)
* fix: hide notes

* test: re-enable confirmHandler tests and add tests for hide notes fix

* fix: replace deprecated getDate with hardcoded dates for timezone safety

* fix: hide notes test

* refactor: move test from trpc to web package

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2025-09-10 10:51:41 +01:00
ba50268634 feat: support auto create agent (#23493)
* feat: support auto create agent

* feat: add prompts

* chore: add number to call

* fix typo

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2025-09-10 08:42:39 +00:00
Benny Joo 4ca78f46df chore: bump platform libraries version (#23730) 2025-09-10 08:37:17 +00:00
Eunjae LeeandAnik Dhabal Babu 3a7ca0246c docs: add knowledge base against unnecessary Day.js usage (#23719)
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-09-10 09:54:48 +02:00
Vansh GilhotraandAnik Dhabal Babu 2cc8b1c2af fix: Use db-deploy instead of db-migrate for yarn dx in non-interactive environments (#23253)
Fixes the issue where 'yarn dx' fails in non-interactive environments
(CI/CD, Docker, automated scripts) by changing Turbo dependencies:

- @calcom/prisma#db-seed: db-migrate → db-deploy
- @calcom/prisma#dx: db-migrate → db-up

This ensures that non-interactive 'prisma migrate deploy' is used
instead of interactive 'prisma migrate dev', allowing yarn dx to
work correctly in automated environments.

Resolves: Cal.com App Store performance optimization - yarn dx failure

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-09-10 06:46:46 +00:00
Lauris Skraucis a2d0cbfe5b chore: v2 dont allow mixing platform teams/users with non-platform (#20296)
* chore: v2 dont allow mixing platform teams/users with non-platform

* chore: finish main merge

* test: e2e

* refactor: simplify can add user to team logic

* refactor: fetch only needed oauth data

* refactor: cleanup
2025-09-09 21:24:18 +02:00
PallavandKartik Saini d65c0753ef conventional left aligned dropdown (#23643)
Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
2025-09-09 16:02:43 +00:00
180 changed files with 3664 additions and 614 deletions
+9
View File
@@ -318,3 +318,12 @@ dates.map((date) => dayjs.utc(date).add(1, "day").format());
// ✅ Best - Use native Date when possible
dates.map((date) => new Date(date.valueOf() + 24 * 60 * 60 * 1000));
```
## Avoid using Dayjs if you don’t need to be strictly tz aware.
When doing logic like Dayjs.startOf(".."), you can instead use date-fns' `startOfMonth(dateObj)` / `endOfDay(dateObj)`;
When doing logic that depends on Browser locale, use i18n.language (prefer to deconstruct) like: `const { i18n: { language } } = useLocale();`, in combination with built-in Intl.
Note that with Date, you’re dealing with System time, so it’s not suited to everywhere (such as in the Booker, where instead we’ll likely migrate to Temporal) - but in most cases the above are suitable.
The main reason for doing so is that Dayjs uses a useful, but highly risky plugin system, which has led us to create `@calcom/dayjs` - this is heavy however, because it pre-loads ALL plugins, including locale handling. It’s a non-ideal solution to a problem that unfortunately exists due to Dayjs.
View File
View File
+1 -1
View File
@@ -38,7 +38,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.349",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.351",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
"@calcom/prisma": "*",
@@ -119,6 +119,15 @@ export class OAuthClientRepository {
});
}
async getByOrgId(organizationId: number) {
return this.dbRead.prisma.platformOAuthClient.findMany({
where: {
organizationId,
},
select: { id: true },
});
}
async getByEventTypeHosts(eventTypeId: number) {
const hostWithUserPlatformClient = await this.dbRead.prisma.host.findFirst({
select: {
@@ -1,17 +1,23 @@
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { CreateOrgMembershipDto } from "@/modules/organizations/memberships/inputs/create-organization-membership.input";
import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
import { UpdateOrgMembershipDto } from "../inputs/update-organization-membership.input";
import { OrganizationsMembershipOutputService } from "./organizations-membership-output.service";
export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR = `Can't add user to organization - the user is platform managed user but organization is not because organization probably was not created using OAuth credentials.`;
export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR = `Can't add user to organization - the user is not platform managed user but organization is platform managed. Both have to be created using OAuth credentials.`;
export const MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to organization - managed user and organization were created using different OAuth clients.`;
@Injectable()
export class OrganizationsMembershipService {
constructor(
private readonly organizationsMembershipRepository: OrganizationsMembershipRepository,
private readonly organizationsMembershipOutputService: OrganizationsMembershipOutputService
private readonly organizationsMembershipOutputService: OrganizationsMembershipOutputService,
private readonly oAuthClientsRepository: OAuthClientRepository
) {}
async getOrgMembership(organizationId: number, membershipId: number) {
@@ -95,7 +101,33 @@ export class OrganizationsMembershipService {
}
async createOrgMembership(organizationId: number, data: CreateOrgMembershipDto) {
await this.canUserBeAddedToOrg(data.userId, organizationId);
const membership = await this.organizationsMembershipRepository.createOrgMembership(organizationId, data);
return this.organizationsMembershipOutputService.getOrgMembershipOutput(membership);
}
async canUserBeAddedToOrg(userId: number, orgId: number) {
const [userOAuthClient, orgOAuthClients] = await Promise.all([
this.oAuthClientsRepository.getByUserId(userId),
this.oAuthClientsRepository.getByOrgId(orgId),
]);
if (!userOAuthClient && orgOAuthClients.length === 0) {
return true;
}
if (userOAuthClient && orgOAuthClients.some((orgClient) => orgClient.id === userOAuthClient.id)) {
return true;
}
if (!userOAuthClient) {
throw new BadRequestException(REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR);
}
if (orgOAuthClients.length === 0) {
throw new BadRequestException(PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR);
}
throw new BadRequestException(MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR);
}
}
@@ -13,7 +13,8 @@ import { ZoomVideoService } from "@/modules/conferencing/services/zoom-video.ser
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { EmailModule } from "@/modules/email/email.module";
import { EmailService } from "@/modules/email/email.service";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
import { UserOOOService } from "@/modules/ooo/services/ooo.service";
import { OrganizationsAttributesController } from "@/modules/organizations/attributes/index/controllers/organizations-attributes.controller";
@@ -65,6 +66,8 @@ import { RedisModule } from "@/modules/redis/redis.module";
import { RedisService } from "@/modules/redis/redis.service";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
import { TeamsModule } from "@/modules/teams/teams/teams.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
@@ -93,6 +96,7 @@ import { Module } from "@nestjs/common";
OrganizationsOrganizationsModule,
OrganizationsStripeModule,
OrganizationsTeamsRoutingFormsModule,
MembershipsModule,
OrganizationsConferencingModule,
EventTypesPrivateLinksModule,
],
@@ -101,7 +105,6 @@ import { Module } from "@nestjs/common";
OrganizationsTeamsRepository,
OrganizationsService,
OrganizationsTeamsService,
MembershipsRepository,
OrganizationsSchedulesService,
OrganizationsUsersRepository,
OrganizationsUsersService,
@@ -147,6 +150,9 @@ import { Module } from "@nestjs/common";
TeamsSchedulesService,
SchedulesService_2024_06_11,
InputSchedulesService_2024_06_11,
TeamsMembershipsService,
TeamsMembershipsRepository,
OAuthClientRepository,
],
exports: [
OrganizationsService,
@@ -0,0 +1,470 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { EmailService } from "@/modules/email/email.service";
import {
CreateManagedUserData,
CreateManagedUserOutput,
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
import {
PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR,
REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR,
} from "@/modules/organizations/memberships/services/organizations-membership.service";
import { CreateOrgTeamDto } from "@/modules/organizations/teams/index/inputs/create-organization-team.input";
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/create-organization-team-membership.input";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import {
PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR,
REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR,
PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR,
} from "@/modules/teams/memberships/services/teams-memberships.service";
import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { PlatformOAuthClient, User } from "@prisma/client";
import * as request from "supertest";
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { randomString } from "test/utils/randomString";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ApiSuccessResponse } from "@calcom/platform-types";
import { Team } from "@calcom/prisma/client";
describe("Organizations Teams Memberships Endpoints", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let profilesRepositoryFixture: ProfileRepositoryFixture;
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
let org: Team;
let orgOwner: User;
let orgUser: User;
let orgTeam: Team;
let orgApiKey: string;
let platformOrg: Team;
let platformOrgOwner: User;
let platformOrgUser: CreateManagedUserData;
let platformOrgTeam: Team;
let platformOAuthClient: PlatformOAuthClient;
let secondPlatformOAuthClient: PlatformOAuthClient;
let secondPlatformOrgUser: CreateManagedUserData;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule, TeamsMembershipsModule],
}).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
const orgName = `organizations-teams-memberships-organization-${randomString()}`;
org = await organizationsRepositoryFixture.create({
name: orgName,
slug: orgName,
isOrganization: true,
});
const platformOrgName = `organizations-teams-memberships-platform-organization-${randomString()}`;
platformOrg = await organizationsRepositoryFixture.create({
isPlatform: true,
name: platformOrgName,
slug: platformOrgName,
isOrganization: true,
platformBilling: {
create: {
customerId: "cus_999",
plan: "ESSENTIALS",
subscriptionId: "sub_999",
},
},
});
platformOAuthClient = await createOAuthClient(platformOrg.id);
secondPlatformOAuthClient = await createOAuthClient(platformOrg.id);
orgOwner = await userRepositoryFixture.create({
email: `organizations-teams-memberships-org-owner-${randomString()}@api.com`,
username: `organizations-teams-memberships-org-owner-${randomString()}`,
});
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
const { keyString } = await apiKeysRepositoryFixture.createApiKey(orgOwner.id, null);
orgApiKey = `cal_test_${keyString}`;
await membershipsRepositoryFixture.create({
role: "OWNER",
user: { connect: { id: orgOwner.id } },
team: { connect: { id: org.id } },
accepted: true,
});
platformOrgOwner = await userRepositoryFixture.create({
email: `platform-org-owner-${randomString()}@api.com`,
username: `platform-org-owner-${randomString()}`,
});
await membershipsRepositoryFixture.create({
role: "OWNER",
user: { connect: { id: platformOrgOwner.id } },
team: { connect: { id: platformOrg.id } },
accepted: true,
});
await profilesRepositoryFixture.create({
uid: "asd1qwwqeqw-asddsadasd",
username: `platform-org-owner-${randomString()}`,
organization: { connect: { id: platformOrg.id } },
user: {
connect: { id: platformOrgOwner.id },
},
});
await profilesRepositoryFixture.create({
uid: "asd1qwwqeqw-asddsadasd-2",
username: `org-owner-${randomString()}`,
organization: { connect: { id: org.id } },
user: {
connect: { id: orgOwner.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:4321"],
permissions: 1023,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
describe("should create org users", () => {
it("should create a new org user", async () => {
const newOrgUser: CreateUserInput = {
email: `organization-user-${randomString()}@api.com`,
};
jest
.spyOn(EmailService.prototype, "sendSignupToOrganizationEmail")
.mockImplementation(() => Promise.resolve());
const { body } = await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/users`)
.send(newOrgUser)
.set("Content-Type", "application/json")
.set("Authorization", `Bearer ${orgApiKey}`)
.set("Accept", "application/json");
const userData = body.data;
expect(body.status).toBe(SUCCESS_STATUS);
expect(userData.email).toBe(newOrgUser.email);
orgUser = userData;
});
it(`should create a new platform org manager user using first oAuth client`, async () => {
const managedUserEmail = `platform-organization-manager-user-${randomString()}@api.com`;
const requestBody: CreateManagedUserInput = {
email: managedUserEmail,
timeZone: "Europe/Berlin",
weekStart: "Monday",
timeFormat: 24,
name: "Alice Smith",
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
bio: "I am a bio",
metadata: {
key: "value",
},
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${platformOAuthClient.id}/users`)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
await userConnectedToOAuth(platformOAuthClient.id, responseBody.data.user.email, 1);
platformOrgUser = responseBody.data;
});
it(`should create a new platform org manager user using second oAuth client`, async () => {
const managedUserEmail = `platform-organization-manager-user-${randomString()}@api.com`;
const requestBody: CreateManagedUserInput = {
email: managedUserEmail,
timeZone: "Europe/Berlin",
weekStart: "Monday",
timeFormat: 24,
name: "Alice Smith",
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
bio: "I am a bio",
metadata: {
key: "value",
},
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${secondPlatformOAuthClient.id}/users`)
.set("x-cal-secret-key", secondPlatformOAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
await userConnectedToOAuth(secondPlatformOAuthClient.id, responseBody.data.user.email, 1);
secondPlatformOrgUser = responseBody.data;
});
});
describe("should create org teams", () => {
it("should create the team for org", async () => {
const teamName = `organization team ${randomString()}`;
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
orgTeam = responseBody.data;
expect(orgTeam.name).toEqual(teamName);
expect(orgTeam.parentId).toEqual(org.id);
});
});
it("should create the team for platform org", async () => {
const teamName = `platform organization team ${randomString()}`;
return request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
platformOrgTeam = responseBody.data;
expect(platformOrgTeam.name).toEqual(teamName);
expect(platformOrgTeam.parentId).toEqual(platformOrg.id);
});
});
});
describe("organization memberships", () => {
describe("negative tests", () => {
it("should not add managed user to organization", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR);
});
it("should not add managed user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(422);
});
it("should not add managed user to organization team", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR);
});
});
describe("positive tests", () => {
it("should add user to organization", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
it("should add user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
});
});
describe("platform organization memberships", () => {
describe("negative tests", () => {
it("should not add user to platform organization", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR);
});
it("should not add user to platform organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(422);
});
it("should not add user to platform organization team", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR);
});
it("should not add user to platform organization team because user is created using different oAuth client", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: secondPlatformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(
PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR
);
});
});
describe("positive tests", () => {
it("should add user to organization", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
it("should add user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
});
});
async function userConnectedToOAuth(oAuthClientId: string, userEmail: string, usersCount: number) {
const oAuthUsers = await oauthClientRepositoryFixture.getUsers(oAuthClientId);
const newOAuthUser = oAuthUsers?.find((user) => user.email === userEmail);
expect(oAuthUsers?.length).toEqual(usersCount);
expect(newOAuthUser?.email).toEqual(userEmail);
}
afterAll(async () => {
await organizationsRepositoryFixture.delete(org.id);
await teamsRepositoryFixture.delete(orgTeam.id);
await organizationsRepositoryFixture.delete(platformOrg.id);
await teamsRepositoryFixture.delete(platformOrgTeam.id);
await userRepositoryFixture.deleteByEmail(platformOrgUser.user.email);
await userRepositoryFixture.deleteByEmail(orgUser.email);
await userRepositoryFixture.deleteByEmail(orgOwner.email);
await userRepositoryFixture.deleteByEmail(platformOrgOwner.email);
await userRepositoryFixture.deleteByEmail(secondPlatformOrgUser.user.email);
await app.close();
});
});
@@ -1,6 +1,7 @@
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/update-organization-team-membership.input";
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/teams/memberships/organizations-teams-memberships.repository";
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
import { Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
@@ -8,10 +9,12 @@ import { TeamService } from "@calcom/platform-libraries";
@Injectable()
export class OrganizationsTeamsMembershipsService {
constructor(
private readonly organizationsTeamsMembershipsRepository: OrganizationsTeamsMembershipsRepository
private readonly organizationsTeamsMembershipsRepository: OrganizationsTeamsMembershipsRepository,
private readonly teamsMembershipsService: TeamsMembershipsService
) {}
async createOrgTeamMembership(teamId: number, data: CreateOrgTeamMembershipDto) {
await this.teamsMembershipsService.canUserBeAddedToTeam(data.userId, teamId);
const teamMembership = await this.organizationsTeamsMembershipsRepository.createOrgTeamMembership(
teamId,
data
@@ -1,15 +1,24 @@
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { CreateTeamMembershipInput } from "@/modules/teams/memberships/inputs/create-team-membership.input";
import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/update-team-membership.input";
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR = `Can't add user to team - the user is platform managed user but team is not because team probably was not created using OAuth credentials.`;
export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR = `Can't add user to team - the user is not platform managed user but team is platform managed. Both have to be created using OAuth credentials.`;
export const PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to team - managed user and team were created using different OAuth clients.`;
@Injectable()
export class TeamsMembershipsService {
constructor(private readonly teamsMembershipsRepository: TeamsMembershipsRepository) {}
constructor(
private readonly teamsMembershipsRepository: TeamsMembershipsRepository,
private readonly oAuthClientsRepository: OAuthClientRepository
) {}
async createTeamMembership(teamId: number, data: CreateTeamMembershipInput) {
await this.canUserBeAddedToTeam(data.userId, teamId);
const teamMembership = await this.teamsMembershipsRepository.createTeamMembership(teamId, data);
return teamMembership;
}
@@ -54,4 +63,29 @@ export class TeamsMembershipsService {
return teamMembership;
}
async canUserBeAddedToTeam(userId: number, teamId: number) {
const [userOAuthClient, teamOAuthClient] = await Promise.all([
this.oAuthClientsRepository.getByUserId(userId),
this.oAuthClientsRepository.getByTeamId(teamId),
]);
if (!userOAuthClient && !teamOAuthClient) {
return true;
}
if (userOAuthClient && teamOAuthClient && userOAuthClient.id === teamOAuthClient.id) {
return true;
}
if (!teamOAuthClient) {
throw new BadRequestException(PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR);
}
if (!userOAuthClient) {
throw new BadRequestException(REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR);
}
throw new BadRequestException(PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR);
}
}
@@ -12,5 +12,6 @@ import { Module } from "@nestjs/common";
imports: [PrismaModule, RedisModule, OrganizationsModule, MembershipsModule, TeamsEventTypesModule],
providers: [TeamsMembershipsRepository, TeamsMembershipsService],
controllers: [TeamsMembershipsController],
exports: [TeamsMembershipsService],
})
export class TeamsMembershipsModule {}
@@ -1,11 +1,11 @@
"use client";
import { usePathname, useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
import type { ComponentProps } from "react";
import React, { useEffect } from "react";
import React from "react";
import type Shell from "@calcom/features/shell/Shell";
import { UserPermissionRole } from "@calcom/prisma/enums";
import type { UserPermissionRole } from "@calcom/prisma/enums";
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
export type AdminLayoutProps = {
@@ -14,14 +14,6 @@ export type AdminLayoutProps = {
} & ComponentProps<typeof Shell>;
export default function AdminLayoutAppDirClient({ userRole, children }: AdminLayoutProps) {
const pathname = usePathname();
const router = useRouter();
// Force redirect on component level
useEffect(() => {
if (userRole !== UserPermissionRole.ADMIN) {
router.replace("/settings/my-account/profile");
}
}, [userRole, router]);
const isAppsPage = pathname?.startsWith("/settings/admin/apps");
return (
@@ -1,7 +1,9 @@
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import React from "react";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
@@ -15,5 +17,9 @@ export default async function AdminLayoutAppDir(props: AdminLayoutAppDirProps) {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const userRole = session?.user?.role;
if (userRole !== UserPermissionRole.ADMIN) {
return redirect("/settings/my-account/profile");
}
return await SettingsLayoutAppDir({ children: <AdminLayoutAppDirClient {...props} userRole={userRole} /> });
}
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "5.6.16",
"version": "5.6.18",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -93,6 +93,7 @@
"handlebars": "^4.7.7",
"ical.js": "^1.4.0",
"ics": "^2.37.0",
"isbot": "^5.1.30",
"jose": "^4.13.1",
"jotai": "^2.12.2",
"jsdom": "^22.0.0",
@@ -138,6 +139,7 @@
"react-use-intercom": "1.5.1",
"recoil": "^0.7.7",
"remove-markdown": "^0.5.0",
"retell-client-js-sdk": "^2.0.0",
"retell-sdk": "^4.40.0",
"rrule": "^2.7.1",
"sanitize-html": "^2.10.0",
+22
View File
@@ -478,6 +478,24 @@
<path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z" ></path>
<path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" ></path>
</symbol>
<symbol class="lucide lucide-mic-off" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="mic-off">
<line x1="2" x2="22" y1="2" y2="22" ></line>
<path d="M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" ></path>
<path d="M5 10v2a7 7 0 0 0 12 5" ></path>
<path d="M15 9.34V5a3 3 0 0 0-5.68-1.33" ></path>
<path d="M9 9v3a3 3 0 0 0 5.12 2.12" ></path>
<line x1="12" x2="12" y1="19" y2="22" ></line>
</symbol>
<symbol class="lucide lucide-mic" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="mic">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" ></path>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" ></path>
<line x1="12" x2="12" y1="19" y2="22" ></line>
</symbol>
<symbol class="lucide lucide-monitor" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="monitor">
<rect width="20" height="14" x="2" y="3" rx="2" ></rect>
<line x1="8" x2="16" y1="21" y2="21" ></line>
<line x1="12" x2="12" y1="17" y2="21" ></line>
</symbol>
<symbol class="lucide lucide-moon" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="moon">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" ></path>
</symbol>
@@ -498,6 +516,10 @@
<path d="M14.05 2a9 9 0 0 1 8 7.94" ></path>
<path d="M14.05 6A5 5 0 0 1 18 10" ></path>
</symbol>
<symbol class="lucide lucide-phone-off" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="phone-off">
<path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91" ></path>
<line x1="22" x2="2" y1="2" y2="22" ></line>
</symbol>
<symbol class="lucide lucide-phone" viewBox="0 0 24 24" fill="inherit" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" id="phone">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" ></path>
</symbol>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 53 KiB

@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "رقم الهاتف (ليتصل به وكيلنا)",
"buy_a_phone_number_or_import_one_you_already_have": "شراء رقم هاتف أو استيراد رقم لديك بالفعل على twilio",
"test_agent": "اختبار الوكيل",
"test_web_call": "اختبار المكالمة عبر الويب",
"phone_numbers": "أرقام الهواتف",
"unsubscribe": "إلغاء الاشتراك",
"delete_workflow_step": "حذف خطوة سير العمل",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "تمت إزالة التطبيق بنجاح",
"error_removing_app": "خطأ في إزالة التطبيق",
"web_conference": "مؤتمر عبر الإنترنت",
"web_call": "مكالمة ويب",
"start_web_call": "بدء مكالمة الويب",
"end_call": "إنهاء المكالمة",
"test_your_agent_with_web_call": "اختبر وكيلك بمكالمة ويب دون الحاجة إلى رقم هاتف",
"phone_call": "مكالمة هاتفية",
"connecting_to_agent": "جارٍ الاتصال بالوكيل...",
"call_active": "المكالمة نشطة",
"call_ended": "انتهت المكالمة",
"call_error": "خطأ في المكالمة",
"ready_to_start_call": "جاهز لبدء المكالمة",
"failed_to_start_web_call_try_again": "فشل بدء مكالمة الويب. يرجى المحاولة مرة أخرى.",
"call_encountered_error_try_again": "واجهت المكالمة خطأ. يرجى المحاولة مرة أخرى.",
"failed_initialize_web_call_microphone_permissions": "فشل تهيئة مكالمة الويب. يرجى التأكد من منح أذونات الميكروفون.",
"start_call_to_see_conversation": "ابدأ مكالمة لرؤية المحادثة",
"waiting_for_conversation": "في انتظار المحادثة...",
"ai_agent": "وكيل الذكاء الاصطناعي",
"live": "مباشر",
"connecting": "جارٍ الاتصال...",
"requires_confirmation": "يتطلب التأكيد",
"requires_confirmation_threshold": "يتطلب تأكيد إذا تم حجزه بإشعار أقل من {{time}} $t({{unit}}_timeUnit) ",
"may_require_confirmation": "قد يتطلب التأكيد",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "التفريغ النصي مفعّل الآن",
"transcription_stopped": "التفريغ النصي متوقف الآن",
"download_transcript": "تحميل التفريغ النصي",
"transcript": "النص المكتوب",
"recording_from_your_recent_call": "تسجيل من مكالمتك الأخيرة على {{appName}} جاهز للتنزيل",
"transcript_from_previous_call": "التفريغ النصي من مكالمة الأخيرة على {{appName}} جاهز للتحميل. الروابط متاحة لمدة ساعة واحدة فقط",
"you_can_download_transcript_from_attachments": "يمكنك أيضاً تحميل النص المكتوب من المرفقات",
@@ -3622,6 +3642,11 @@
"send_another_message": "إرسال رسالة أخرى",
"sending": "جارٍ الإرسال",
"no_variables_found": "لم يتم العثور على متغيرات",
"credits_required": "الرصيد المطلوب",
"web_call_credits_info": "تستهلك مكالمات الويب رصيداً. تأكد من وجود رصيد كافٍ لبدء المكالمة.",
"web_call_no_credits": "تتطلب مكالمات الويب رصيداً. لديك 0 رصيد متبقي.",
"purchase_credits": "شراء رصيد",
"credits_required_tooltip": "يتطلب هذا الإجراء رصيداً للمتابعة",
"api_key_name_too_long": "يجب ألا يتجاوز الاسم {{max}} حرفًا",
"auto_charge_for_last_minute_cancellation": "فرض رسوم التخلف عن الحضور تلقائياً للإلغاءات في اللحظة الأخيرة",
"before_scheduled_start_time": "قبل وقت البدء المحدد",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefon nömrəsi (agentimizin zəng edəcəyi)",
"buy_a_phone_number_or_import_one_you_already_have": "Telefon nömrəsi alın və ya artıq twilio-da malik olduğunuz nömrəni idxal edin",
"test_agent": "Test agent",
"test_web_call": "Veb Zəngi Test Edin",
"phone_numbers": "Telefon nömrələri",
"unsubscribe": "Abunəlikdən çıx",
"delete_workflow_step": "İş axını addımını sil",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Tətbiq uğurla silindi",
"error_removing_app": "Tətbiqi silərkən səhv baş verdi",
"web_conference": "Veb konfrans",
"web_call": "Veb zəng",
"start_web_call": "Veb zəngi başladın",
"end_call": "Zəngi bitirin",
"test_your_agent_with_web_call": "Telefon nömrəsi tələb etmədən agentinizi veb zənglə test edin",
"phone_call": "Telefon zəngi",
"connecting_to_agent": "Agentə qoşulur...",
"call_active": "Zəng aktivdir",
"call_ended": "Zəng bitdi",
"call_error": "Zəng xətası",
"ready_to_start_call": "Zəngi başlatmağa hazırdır",
"failed_to_start_web_call_try_again": "Veb zəngi başlatmaq alınmadı. Zəhmət olmasa yenidən cəhd edin.",
"call_encountered_error_try_again": "Zəng zamanı xəta baş verdi. Zəhmət olmasa yenidən cəhd edin.",
"failed_initialize_web_call_microphone_permissions": "Veb zəngi başlatmaq alınmadı. Zəhmət olmasa mikrofon icazələrinin verildiyindən əmin olun.",
"start_call_to_see_conversation": "Söhbəti görmək üçün zəngi başladın",
"waiting_for_conversation": "Söhbət gözlənilir...",
"ai_agent": "Sİ Agenti",
"live": "Canlı",
"connecting": "Qoşulur...",
"requires_confirmation": "Təsdiq tələb edir",
"requires_confirmation_threshold": "< {{time}} $t({{unit}}_timeUnit) xəbərdarlıqla rezervasiya edildikdə təsdiq tələb edir",
"may_require_confirmation": "Təsdiq tələb edə bilər",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkripsiyalar indi aktivdir",
"transcription_stopped": "Transkripsiyalar dayandırıldı",
"download_transcript": "Transkripti yüklə",
"transcript": "Transkript",
"recording_from_your_recent_call": "{{appName}} üzərindən son zənginizin qeydi yükləməyə hazırdır",
"transcript_from_previous_call": "{{appName}} üzərindən son zənginizin transkripti yükləməyə hazırdır. Link yalnız 1 saat etibarlıdır",
"you_can_download_transcript_from_attachments": "Siz həmçinin transkripti əlavələrdən yükləyə bilərsiniz",
@@ -3622,6 +3642,11 @@
"send_another_message": "Başqa bir mesaj göndər",
"sending": "Göndərilir",
"no_variables_found": "Heç bir dəyişən tapılmadı",
"credits_required": "Kreditlər Tələb Olunur",
"web_call_credits_info": "Veb zənglər kredit istifadə edir. Zəngi başlatmaq üçün kifayət qədər kreditinizin olduğundan əmin olun.",
"web_call_no_credits": "Veb zənglər kredit tələb edir. Sizin 0 kredit qalıb.",
"purchase_credits": "Kredit Alın",
"credits_required_tooltip": "Bu əməliyyat davam etmək üçün kredit tələb edir",
"api_key_name_too_long": "Ad {{max}} simvolu keçməməlidir",
"auto_charge_for_last_minute_cancellation": "Son dəqiqə ləğvləri üçün avtomatik olaraq gəlməmə haqqını tutun",
"before_scheduled_start_time": "Planlaşdırılmış başlama vaxtından əvvəl",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Телефонен номер (за обаждане от нашия агент)",
"buy_a_phone_number_or_import_one_you_already_have": "Купете телефонен номер или импортирайте такъв, който вече имате в twilio",
"test_agent": "Тестов агент",
"test_web_call": "Тестово уеб обаждане",
"phone_numbers": "Телефонни номера",
"unsubscribe": "Отпишете се",
"delete_workflow_step": "Изтрийте стъпка от работния процес",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Приложението е премахнато успешно",
"error_removing_app": "Грешка при премахване на приложението",
"web_conference": "Уеб конференция",
"web_call": "Уеб обаждане",
"start_web_call": "Започнете уеб обаждане",
"end_call": "Прекратете обаждането",
"test_your_agent_with_web_call": "Тествайте вашия агент с уеб обаждане без да е необходим телефонен номер",
"phone_call": "Телефонно обаждане",
"connecting_to_agent": "Свързване с агент...",
"call_active": "Активно обаждане",
"call_ended": "Обаждането приключи",
"call_error": "Грешка при обаждането",
"ready_to_start_call": "Готови за започване на обаждане",
"failed_to_start_web_call_try_again": "Неуспешно започване на уеб обаждане. Моля, опитайте отново.",
"call_encountered_error_try_again": "Възникна грешка при обаждането. Моля, опитайте отново.",
"failed_initialize_web_call_microphone_permissions": "Неуспешно инициализиране на уеб обаждане. Моля, уверете се, че са предоставени разрешения за микрофона.",
"start_call_to_see_conversation": "Започнете обаждане, за да видите разговора",
"waiting_for_conversation": "Изчакване на разговор...",
"ai_agent": "AI агент",
"live": "На живо",
"connecting": "Свързване...",
"requires_confirmation": "Изисква потвърждение",
"requires_confirmation_threshold": "Изисква потвърждение, ако е резервирано с < {{time}} $t({{unit}}_timeUnit) предизвестие",
"may_require_confirmation": "Може да изисква потвърждение",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Транскрипциите са активирани",
"transcription_stopped": "Транскрипциите са спрени",
"download_transcript": "Изтегли транскрипт",
"transcript": "Транскрипт",
"recording_from_your_recent_call": "Записът от последния ви разговор в {{appName}} е готов за изтегляне",
"transcript_from_previous_call": "Транскриптът от последния ви разговор в {{appName}} е готов за изтегляне. Линковете са валидни само за 1 час",
"you_can_download_transcript_from_attachments": "Можете също да изтеглите транскрипта от прикачените файлове",
@@ -3622,6 +3642,11 @@
"send_another_message": "Изпратете друго съобщение",
"sending": "Изпращане",
"no_variables_found": "Не са намерени променливи",
"credits_required": "Необходими кредити",
"web_call_credits_info": "Уеб обажданията консумират кредити. Уверете се, че имате достатъчно кредити, за да започнете обаждане.",
"web_call_no_credits": "Уеб обажданията изискват кредити. Имате 0 оставащи кредити.",
"purchase_credits": "Закупете кредити",
"credits_required_tooltip": "Това действие изисква кредити, за да продължите",
"api_key_name_too_long": "Името не трябва да надвишава {{max}} символа",
"auto_charge_for_last_minute_cancellation": "Автоматично таксуване на такса за неявяване при отмяна в последния момент",
"before_scheduled_start_time": "Преди планираното начално време",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "ফোন নম্বর (আমাদের এজেন্ট কল করার জন্য)",
"buy_a_phone_number_or_import_one_you_already_have": "একটি ফোন নম্বর কিনুন অথবা আপনার কাছে ইতিমধ্যে থাকা twilio নম্বর ইম্পোর্ট করুন",
"test_agent": "টেস্ট এজেন্ট",
"test_web_call": "ওয়েব কল পরীক্ষা করুন",
"phone_numbers": "ফোন নম্বরসমূহ",
"unsubscribe": "আনসাবস্ক্রাইব",
"delete_workflow_step": "ওয়ার্কফ্লো স্টেপ মুছুন",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "অ্যাপ্লিকেশন সফলভাবে সরানো হয়েছে",
"error_removing_app": "অ্যাপ্লিকেশন অপসারণ করতে ত্রুটি",
"web_conference": "ওয়েব সম্মেলন",
"web_call": "ওয়েব কল",
"start_web_call": "ওয়েব কল শুরু করুন",
"end_call": "কল শেষ করুন",
"test_your_agent_with_web_call": "ফোন নম্বর ছাড়াই ওয়েব কলের মাধ্যমে আপনার এজেন্ট পরীক্ষা করুন",
"phone_call": "ফোন কল",
"connecting_to_agent": "এজেন্টের সাথে সংযোগ করা হচ্ছে...",
"call_active": "কল সক্রিয়",
"call_ended": "কল শেষ হয়েছে",
"call_error": "কল ত্রুটি",
"ready_to_start_call": "কল শুরু করতে প্রস্তুত",
"failed_to_start_web_call_try_again": "ওয়েব কল শুরু করতে ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"call_encountered_error_try_again": "কলে একটি ত্রুটি দেখা দিয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"failed_initialize_web_call_microphone_permissions": "ওয়েব কল শুরু করতে ব্যর্থ হয়েছে। অনুগ্রহ করে মাইক্রোফোন অনুমতি প্রদান করা হয়েছে তা নিশ্চিত করুন।",
"start_call_to_see_conversation": "কথোপকথন দেখতে একটি কল শুরু করুন",
"waiting_for_conversation": "কথোপকথনের জন্য অপেক্ষা করছে...",
"ai_agent": "এআই এজেন্ট",
"live": "লাইভ",
"connecting": "সংযোগ করা হচ্ছে...",
"requires_confirmation": "নিশ্চিতকরণ প্রয়োজন",
"requires_confirmation_threshold": "<দিয়ে বুক করা থাকলে নিশ্চিতকরণের প্রয়োজন {{time}} $t({{unit}}_timeUnit) বিজ্ঞপ্তি",
"may_require_confirmation": "নিশ্চিতকরণের প্রয়োজন হতে পারে",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "প্রতিলিপি এখন সক্ষম করা হয়",
"transcription_stopped": "প্রতিলিপি এখন বন্ধ",
"download_transcript": "ট্রান্সক্রিপ্ট ডাউনলোড করুন",
"transcript": "ট্রান্সক্রিপ্ট",
"recording_from_your_recent_call": "আপনার সাম্প্রতিক কল থেকে একটি রেকর্ডিং {{appName}} ডাউনলোডের জন্য প্রস্তুত",
"transcript_from_previous_call": "আপনার সাম্প্রতিক কল থেকে প্রতিলিপি {{appName}} ডাউনলোড করতে প্রস্তুত। ",
"you_can_download_transcript_from_attachments": "আপনি সংযুক্তি থেকেও ট্রান্সক্রিপ্ট ডাউনলোড করতে পারেন",
@@ -3622,6 +3642,11 @@
"send_another_message": "আরেকটি বার্তা পাঠান",
"sending": "পাঠানো হচ্ছে",
"no_variables_found": "কোন ভেরিয়েবল পাওয়া যায়নি",
"credits_required": "ক্রেডিট প্রয়োজন",
"web_call_credits_info": "ওয়েব কল ক্রেডিট ব্যবহার করে। কল শুরু করার জন্য আপনার পর্যাপ্ত ক্রেডিট আছে তা নিশ্চিত করুন।",
"web_call_no_credits": "ওয়েব কলের জন্য ক্রেডিট প্রয়োজন। আপনার ০ ক্রেডিট অবশিষ্ট আছে।",
"purchase_credits": "ক্রেডিট কিনুন",
"credits_required_tooltip": "এই কাজটি করতে ক্রেডিট প্রয়োজন",
"api_key_name_too_long": "নাম অবশ্যই {{max}} অক্ষরের বেশি হবে না",
"auto_charge_for_last_minute_cancellation": "শেষ মুহূর্তের বাতিলকরণের জন্য স্বয়ংক্রিয়ভাবে অনুপস্থিতি ফি চার্জ করুন",
"before_scheduled_start_time": "নির্ধারিত শুরুর সময়ের আগে",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Número de telèfon (perquè el nostre agent truqui)",
"buy_a_phone_number_or_import_one_you_already_have": "Compra un número de telèfon o importa'n un que ja tinguis a Twilio",
"test_agent": "Prova l'agent",
"test_web_call": "Prova de trucada web",
"phone_numbers": "Números de telèfon",
"unsubscribe": "Donar de baixa",
"delete_workflow_step": "Eliminar pas del flux de treball",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "L'aplicació s'ha eliminat correctament",
"error_removing_app": "Error en eliminar l'aplicació",
"web_conference": "Conferència web",
"web_call": "Trucada web",
"start_web_call": "Inicia la trucada web",
"end_call": "Finalitza la trucada",
"test_your_agent_with_web_call": "Prova el teu agent amb una trucada web sense necessitar un número de telèfon",
"phone_call": "Trucada telefònica",
"connecting_to_agent": "Connectant amb l'agent...",
"call_active": "Trucada activa",
"call_ended": "Trucada finalitzada",
"call_error": "Error de trucada",
"ready_to_start_call": "Preparat per iniciar la trucada",
"failed_to_start_web_call_try_again": "No s'ha pogut iniciar la trucada web. Si us plau, torna-ho a provar.",
"call_encountered_error_try_again": "La trucada ha trobat un error. Si us plau, torna-ho a provar.",
"failed_initialize_web_call_microphone_permissions": "No s'ha pogut inicialitzar la trucada web. Assegura't que els permisos del micròfon estiguin concedits.",
"start_call_to_see_conversation": "Inicia una trucada per veure la conversa",
"waiting_for_conversation": "Esperant la conversa...",
"ai_agent": "Agent d'IA",
"live": "En directe",
"connecting": "Connectant...",
"requires_confirmation": "Requereix confirmació",
"requires_confirmation_threshold": "Requereix confirmació si es reserva amb < {{time}} $t({{unit}}_timeUnit) d'antelació",
"may_require_confirmation": "Pot requerir confirmació",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Les transcripcions estan activades ara",
"transcription_stopped": "Les transcripcions s'han aturat ara",
"download_transcript": "Descarrega la transcripció",
"transcript": "Transcripció",
"recording_from_your_recent_call": "Un enregistrament de la teva trucada recent a {{appName}} està llest per descarregar",
"transcript_from_previous_call": "La transcripció de la teva trucada recent a {{appName}} està llesta per descarregar. Els enllaços només són vàlids durant 1 hora",
"you_can_download_transcript_from_attachments": "També podeu descarregar la transcripció des dels arxius adjunts",
@@ -3622,6 +3642,11 @@
"send_another_message": "Envia un altre missatge",
"sending": "Enviant",
"no_variables_found": "No s'han trobat variables",
"credits_required": "Crèdits necessaris",
"web_call_credits_info": "Les trucades web consumeixen crèdits. Assegura't de tenir prou crèdits per iniciar una trucada.",
"web_call_no_credits": "Les trucades web requereixen crèdits. Et queden 0 crèdits.",
"purchase_credits": "Compra crèdits",
"credits_required_tooltip": "Aquesta acció requereix crèdits per continuar",
"api_key_name_too_long": "El nom no ha de superar els {{max}} caràcters",
"auto_charge_for_last_minute_cancellation": "Càrrec automàtic de la tarifa per no assistència en cas de cancel·lacions d'última hora",
"before_scheduled_start_time": "Abans de l'hora d'inici programada",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonní číslo (pro volání našeho agenta)",
"buy_a_phone_number_or_import_one_you_already_have": "Kupte si telefonní číslo nebo importujte to, které již máte na Twilio",
"test_agent": "Otestovat agenta",
"test_web_call": "Testovací webový hovor",
"phone_numbers": "Telefonní čísla",
"unsubscribe": "Odhlásit",
"delete_workflow_step": "Smazat krok workflow",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplikace byla odstraněna",
"error_removing_app": "Při odstraňování aplikace došlo k chybě",
"web_conference": "Webová konference",
"web_call": "Webový hovor",
"start_web_call": "Zahájit webový hovor",
"end_call": "Ukončit hovor",
"test_your_agent_with_web_call": "Otestujte svého agenta webovým hovorem bez nutnosti telefonního čísla",
"phone_call": "Telefonní hovor",
"connecting_to_agent": "Připojování k agentovi...",
"call_active": "Hovor je aktivní",
"call_ended": "Hovor byl ukončen",
"call_error": "Chyba hovoru",
"ready_to_start_call": "Připraveno k zahájení hovoru",
"failed_to_start_web_call_try_again": "Nepodařilo se zahájit webový hovor. Zkuste to prosím znovu.",
"call_encountered_error_try_again": "Během hovoru došlo k chybě. Zkuste to prosím znovu.",
"failed_initialize_web_call_microphone_permissions": "Nepodařilo se inicializovat webový hovor. Ujistěte se, že jsou udělena oprávnění pro mikrofon.",
"start_call_to_see_conversation": "Zahajte hovor pro zobrazení konverzace",
"waiting_for_conversation": "Čekání na konverzaci...",
"ai_agent": "AI Agent",
"live": "Živě",
"connecting": "Připojování...",
"requires_confirmation": "Vyžaduje potvrzení",
"requires_confirmation_threshold": "Pokud je rezervace provedena s předstihem < {{time}} $t({{unit}}_timeUnit), je nutné potvrzení",
"may_require_confirmation": "Může vyžadovat potvrzení",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Přepisy jsou nyní povoleny",
"transcription_stopped": "Přepisy byly zastaveny",
"download_transcript": "Stáhnout přepis",
"transcript": "Přepis",
"recording_from_your_recent_call": "Nahrávka z vašeho nedávného hovoru v {{appName}} je připravena ke stažení",
"transcript_from_previous_call": "Přepis z vašeho nedávného hovoru na {{appName}} je připraven ke stažení. Odkazy jsou platné pouze 1 hodinu",
"you_can_download_transcript_from_attachments": "Přepis si můžete také stáhnout z příloh",
@@ -3622,6 +3642,11 @@
"send_another_message": "Poslat další zprávu",
"sending": "Odesílání",
"no_variables_found": "Nebyly nalezeny žádné proměnné",
"credits_required": "Vyžadovány kredity",
"web_call_credits_info": "Webové hovory spotřebovávají kredity. Ujistěte se, že máte dostatek kreditů pro zahájení hovoru.",
"web_call_no_credits": "Webové hovory vyžadují kredity. Zbývá vám 0 kreditů.",
"purchase_credits": "Zakoupit kredity",
"credits_required_tooltip": "Tato akce vyžaduje kredity k pokračování",
"api_key_name_too_long": "Název nesmí přesáhnout {{max}} znaků",
"auto_charge_for_last_minute_cancellation": "Automaticky účtovat poplatek za nedostavení se při zrušení na poslední chvíli",
"before_scheduled_start_time": "Před plánovaným časem zahájení",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonnummer (til vores agent at ringe til)",
"buy_a_phone_number_or_import_one_you_already_have": "Køb et telefonnummer eller importer et, du allerede har på Twilio",
"test_agent": "Test agent",
"test_web_call": "Test webopkald",
"phone_numbers": "Telefonnumre",
"unsubscribe": "Afmeld",
"delete_workflow_step": "Slet workflow-trin",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "App fjernet",
"error_removing_app": "Fejl ved fjernelse af app",
"web_conference": "Web konference",
"web_call": "Webopkald",
"start_web_call": "Start webopkald",
"end_call": "Afslut opkald",
"test_your_agent_with_web_call": "Test din agent med et webopkald uden at kræve et telefonnummer",
"phone_call": "Telefonopkald",
"connecting_to_agent": "Forbinder til agent...",
"call_active": "Opkald aktivt",
"call_ended": "Opkald afsluttet",
"call_error": "Opkaldsfejl",
"ready_to_start_call": "Klar til at starte opkald",
"failed_to_start_web_call_try_again": "Kunne ikke starte webopkald. Prøv igen.",
"call_encountered_error_try_again": "Opkaldet stødte på en fejl. Prøv igen.",
"failed_initialize_web_call_microphone_permissions": "Kunne ikke initialisere webopkald. Sørg for, at mikrofontilladelser er givet.",
"start_call_to_see_conversation": "Start et opkald for at se samtalen",
"waiting_for_conversation": "Venter på samtale...",
"ai_agent": "AI-agent",
"live": "Live",
"connecting": "Forbinder...",
"requires_confirmation": "Kræver bekræftelse",
"requires_confirmation_threshold": "Kræver bekræftelse hvis booket med < {{time}} $t({{unit}}_timeUnit) meddelelse",
"may_require_confirmation": "Kan kræve bekræftelse",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transskriptioner er nu aktiveret",
"transcription_stopped": "Transskriptioner er nu stoppet",
"download_transcript": "Download transskription",
"transcript": "Transskription",
"recording_from_your_recent_call": "En optagelse fra dit seneste opkald på {{appName}} er klar til download",
"transcript_from_previous_call": "Udskrift fra dit seneste opkald på {{appName}} er klar til download. Links er kun gyldige i 1 time",
"you_can_download_transcript_from_attachments": "Du kan også downloade udskriften fra vedhæftede filer",
@@ -3622,6 +3642,11 @@
"send_another_message": "Send en ny besked",
"sending": "Sender",
"no_variables_found": "Ingen variable fundet",
"credits_required": "Credits påkrævet",
"web_call_credits_info": "Webopkald bruger credits. Sørg for, at du har nok credits til at starte et opkald.",
"web_call_no_credits": "Webopkald kræver credits. Du har 0 credits tilbage.",
"purchase_credits": "Køb credits",
"credits_required_tooltip": "Denne handling kræver credits for at fortsætte",
"api_key_name_too_long": "Navnet må ikke overstige {{max}} tegn",
"auto_charge_for_last_minute_cancellation": "Automatisk opkrævning af gebyr for udeblivelse ved aflysning i sidste øjeblik",
"before_scheduled_start_time": "Før den planlagte starttid",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonnummer (für unseren Agenten zum Anrufen)",
"buy_a_phone_number_or_import_one_you_already_have": "Kaufen Sie eine Telefonnummer oder importieren Sie eine, die Sie bereits bei Twilio haben",
"test_agent": "Agent testen",
"test_web_call": "Web-Anruf testen",
"phone_numbers": "Telefonnummern",
"unsubscribe": "Abmelden",
"delete_workflow_step": "Workflow-Schritt löschen",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "App erfolgreich entfernt",
"error_removing_app": "Fehler beim Entfernen der App",
"web_conference": "Webkonferenz",
"web_call": "Web-Anruf",
"start_web_call": "Web-Anruf starten",
"end_call": "Anruf beenden",
"test_your_agent_with_web_call": "Testen Sie Ihren Agenten mit einem Web-Anruf ohne Telefonnummer",
"phone_call": "Telefonanruf",
"connecting_to_agent": "Verbindung zum Agenten wird hergestellt...",
"call_active": "Anruf aktiv",
"call_ended": "Anruf beendet",
"call_error": "Anruffehler",
"ready_to_start_call": "Bereit zum Starten des Anrufs",
"failed_to_start_web_call_try_again": "Web-Anruf konnte nicht gestartet werden. Bitte versuchen Sie es erneut.",
"call_encountered_error_try_again": "Bei dem Anruf ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"failed_initialize_web_call_microphone_permissions": "Web-Anruf konnte nicht initialisiert werden. Bitte stellen Sie sicher, dass Mikrofonberechtigungen erteilt wurden.",
"start_call_to_see_conversation": "Starten Sie einen Anruf, um die Konversation zu sehen",
"waiting_for_conversation": "Warte auf Konversation...",
"ai_agent": "KI-Agent",
"live": "Live",
"connecting": "Verbinde...",
"requires_confirmation": "Erfordert Bestätigung",
"requires_confirmation_threshold": "Erfordert eine Bestätigung bei Buchung mit < {{time}} $t({{unit}}_timeUnit) Vorlauf",
"may_require_confirmation": "Kann Bestätigung erfordern",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkriptionen sind jetzt aktiviert",
"transcription_stopped": "Transkriptionen werden jetzt gestoppt",
"download_transcript": "Transkript herunterladen",
"transcript": "Transkript",
"recording_from_your_recent_call": "Eine Aufnahme von Ihrem letzten Anruf auf {{appName}} steht zum Download bereit",
"transcript_from_previous_call": "Transkript von Ihrem letzten Anruf bei {{appName}} ist zum Herunterladen bereit. Links sind nur für 1 Stunde gültig",
"you_can_download_transcript_from_attachments": "Sie können das Protokoll auch aus den Anhängen herunterladen",
@@ -3622,6 +3642,11 @@
"send_another_message": "Weitere Nachricht senden",
"sending": "Wird gesendet",
"no_variables_found": "Keine Variablen gefunden",
"credits_required": "Credits erforderlich",
"web_call_credits_info": "Web-Anrufe verbrauchen Credits. Stellen Sie sicher, dass Sie genügend Credits haben, um einen Anruf zu starten.",
"web_call_no_credits": "Web-Anrufe erfordern Credits. Sie haben 0 Credits übrig.",
"purchase_credits": "Credits kaufen",
"credits_required_tooltip": "Diese Aktion erfordert Credits, um fortzufahren",
"api_key_name_too_long": "Der Name darf {{max}} Zeichen nicht überschreiten",
"auto_charge_for_last_minute_cancellation": "Automatische Berechnung der Ausfallgebühr für kurzfristige Stornierungen",
"before_scheduled_start_time": "Vor dem geplanten Startzeitpunkt",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Αριθμός τηλεφώνου (για κλήση από τον πράκτορά μας)",
"buy_a_phone_number_or_import_one_you_already_have": "Αγοράστε έναν αριθμό τηλεφώνου ή εισάγετε έναν που έχετε ήδη στο twilio",
"test_agent": "Δοκιμή πράκτορα",
"test_web_call": "Δοκιμή διαδικτυακής κλήσης",
"phone_numbers": "Αριθμοί τηλεφώνου",
"unsubscribe": "Κατάργηση εγγραφής",
"delete_workflow_step": "Διαγραφή βήματος ροής εργασίας",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Η εφαρμογή αφαιρέθηκε με επιτυχία",
"error_removing_app": "Σφάλμα κατά την αφαίρεση της εφαρμογής",
"web_conference": "Διαδικτυακή διάσκεψη",
"web_call": "Διαδικτυακή κλήση",
"start_web_call": "Έναρξη διαδικτυακής κλήσης",
"end_call": "Τερματισμός κλήσης",
"test_your_agent_with_web_call": "Δοκιμάστε τον πράκτορά σας με μια διαδικτυακή κλήση χωρίς να απαιτείται αριθμός τηλεφώνου",
"phone_call": "Τηλεφωνική κλήση",
"connecting_to_agent": "Σύνδεση με τον πράκτορα...",
"call_active": "Ενεργή κλήση",
"call_ended": "Η κλήση τερματίστηκε",
"call_error": "Σφάλμα κλήσης",
"ready_to_start_call": "Έτοιμοι για έναρξη κλήσης",
"failed_to_start_web_call_try_again": "Αποτυχία έναρξης διαδικτυακής κλήσης. Παρακαλώ δοκιμάστε ξανά.",
"call_encountered_error_try_again": "Η κλήση αντιμετώπισε σφάλμα. Παρακαλώ δοκιμάστε ξανά.",
"failed_initialize_web_call_microphone_permissions": "Αποτυχία αρχικοποίησης διαδικτυακής κλήσης. Βεβαιωθείτε ότι έχουν παραχωρηθεί δικαιώματα μικροφώνου.",
"start_call_to_see_conversation": "Ξεκινήστε μια κλήση για να δείτε τη συνομιλία",
"waiting_for_conversation": "Αναμονή για συνομιλία...",
"ai_agent": "Πράκτορας AI",
"live": "Ζωντανά",
"connecting": "Σύνδεση...",
"requires_confirmation": "Απαιτεί επιβεβαίωση",
"requires_confirmation_threshold": "Απαιτεί επιβεβαίωση εάν η κράτηση γίνει με < {{time}} $t({{unit}}_timeUnit) ειδοποίηση",
"may_require_confirmation": "Ενδέχεται να απαιτεί επιβεβαίωση",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Οι απομαγνητοφωνήσεις ενεργοποιήθηκαν",
"transcription_stopped": "Οι απομαγνητοφωνήσεις σταμάτησαν",
"download_transcript": "Λήψη απομαγνητοφώνησης",
"transcript": "Απομαγνητοφώνηση",
"recording_from_your_recent_call": "Μια εγγραφή από την πρόσφατη κλήση σας στο {{appName}} είναι έτοιμη για λήψη",
"transcript_from_previous_call": "Η απομαγνητοφώνηση από την πρόσφατη κλήση σας στο {{appName}} είναι έτοιμη για λήψη. Οι σύνδεσμοι ισχύουν μόνο για 1 ώρα",
"you_can_download_transcript_from_attachments": "Μπορείτε επίσης να κατεβάσετε το αντίγραφο από τα συνημμένα",
@@ -3622,6 +3642,11 @@
"send_another_message": "Αποστολή άλλου μηνύματος",
"sending": "Αποστολή",
"no_variables_found": "Δεν βρέθηκαν μεταβλητές",
"credits_required": "Απαιτούνται μονάδες",
"web_call_credits_info": "Οι διαδικτυακές κλήσεις καταναλώνουν μονάδες. Βεβαιωθείτε ότι έχετε αρκετές μονάδες για να ξεκινήσετε μια κλήση.",
"web_call_no_credits": "Οι διαδικτυακές κλήσεις απαιτούν μονάδες. Έχετε 0 μονάδες που απομένουν.",
"purchase_credits": "Αγορά μονάδων",
"credits_required_tooltip": "Αυτή η ενέργεια απαιτεί μονάδες για να προχωρήσει",
"api_key_name_too_long": "Το όνομα δεν πρέπει να υπερβαίνει τους {{max}} χαρακτήρες",
"auto_charge_for_last_minute_cancellation": "Αυτόματη χρέωση τέλους μη εμφάνισης για ακυρώσεις τελευταίας στιγμής",
"before_scheduled_start_time": "Πριν την προγραμματισμένη ώρα έναρξης",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Phone number (for our agent to call)",
"buy_a_phone_number_or_import_one_you_already_have": "Buy a phone number or import one you already have on twilio",
"test_agent": "Test Agent",
"test_web_call": "Test Web Call",
"phone_numbers": "Phone Numbers",
"unsubscribe": "Unsubscribe",
"delete_workflow_step": "Delete Workflow Step",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "App removed successfully",
"error_removing_app": "Error removing app",
"web_conference": "Web conference",
"web_call": "Web call",
"start_web_call": "Start web call",
"end_call": "End call",
"test_your_agent_with_web_call": "Test your agent with a web call without requiring a phone number",
"phone_call": "Phone call",
"connecting_to_agent": "Connecting to agent...",
"call_active": "Call active",
"call_ended": "Call ended",
"call_error": "Call error",
"ready_to_start_call": "Ready to start call",
"failed_to_start_web_call_try_again": "Failed to start web call. Please try again.",
"call_encountered_error_try_again": "Call encountered an error. Please try again.",
"failed_initialize_web_call_microphone_permissions": "Failed to initialize web call. Please ensure microphone permissions are granted.",
"start_call_to_see_conversation": "Start a call to see the conversation",
"waiting_for_conversation": "Waiting for conversation...",
"ai_agent": "AI Agent",
"live": "Live",
"connecting": "Connecting...",
"requires_confirmation": "Requires confirmation",
"requires_confirmation_threshold": "Requires confirmation if booked with < {{time}} $t({{unit}}_timeUnit) notice",
"may_require_confirmation": "May require confirmation",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transcriptions are enabled now",
"transcription_stopped": "Transcriptions are stopped now",
"download_transcript": "Download Transcript",
"transcript": "Transcript",
"recording_from_your_recent_call": "A recording from your recent call on {{appName}} is ready for download",
"transcript_from_previous_call": "Transcript from your recent call on {{appName}} is ready to download. Links are valid only for 1 Hour",
"you_can_download_transcript_from_attachments": "You can also download transcript from attachments",
@@ -3622,6 +3642,11 @@
"send_another_message": "Send another message",
"sending": "Sending",
"no_variables_found": "No variables found",
"credits_required": "Credits Required",
"web_call_credits_info": "Web calls consume credits. Make sure you have enough credits to start a call.",
"web_call_no_credits": "Web calls require credits. You have 0 credits remaining.",
"purchase_credits": "Purchase Credits",
"credits_required_tooltip": "This action requires credits to proceed",
"api_key_name_too_long": "Name must not exceed {{max}} characters",
"auto_charge_for_last_minute_cancellation": "Auto charge no-show fee for last minute cancellations",
"before_scheduled_start_time": "Before scheduled start time",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Número de teléfono (para que nuestro agente llame)",
"buy_a_phone_number_or_import_one_you_already_have": "Compra un número de teléfono o importa uno que ya tengas en Twilio",
"test_agent": "Probar agente",
"test_web_call": "Probar llamada web",
"phone_numbers": "Números de teléfono",
"unsubscribe": "Dar de baja",
"delete_workflow_step": "Eliminar paso del flujo de trabajo",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplicación eliminada con éxito",
"error_removing_app": "Error al eliminar la aplicación",
"web_conference": "Conferencia web",
"web_call": "Llamada web",
"start_web_call": "Iniciar llamada web",
"end_call": "Finalizar llamada",
"test_your_agent_with_web_call": "Prueba tu agente con una llamada web sin necesidad de un número telefónico",
"phone_call": "Llamada telefónica",
"connecting_to_agent": "Conectando con el agente...",
"call_active": "Llamada activa",
"call_ended": "Llamada finalizada",
"call_error": "Error en la llamada",
"ready_to_start_call": "Listo para iniciar la llamada",
"failed_to_start_web_call_try_again": "No se pudo iniciar la llamada web. Por favor, inténtalo de nuevo.",
"call_encountered_error_try_again": "La llamada encontró un error. Por favor, inténtalo de nuevo.",
"failed_initialize_web_call_microphone_permissions": "No se pudo inicializar la llamada web. Por favor, asegúrate de otorgar permisos de micrófono.",
"start_call_to_see_conversation": "Inicia una llamada para ver la conversación",
"waiting_for_conversation": "Esperando conversación...",
"ai_agent": "Agente de IA",
"live": "En vivo",
"connecting": "Conectando...",
"requires_confirmation": "Requiere confirmación",
"requires_confirmation_threshold": "Requiere confirmación si se reserva con menos de {{time}} $t({{unit}}_timeUnit) de antelación",
"may_require_confirmation": "Puede requerir confirmación",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Las transcripciones están habilitadas ahora",
"transcription_stopped": "Las transcripciones se han detenido ahora",
"download_transcript": "Descargar transcripción",
"transcript": "Transcripción",
"recording_from_your_recent_call": "Una grabación de tu llamada reciente en {{appName}} está lista para descargar",
"transcript_from_previous_call": "La transcripción de tu llamada reciente en {{appName}} está lista para descargar. Los enlaces son válidos solo por 1 hora",
"you_can_download_transcript_from_attachments": "También puedes descargar la transcripción desde los archivos adjuntos",
@@ -3622,6 +3642,11 @@
"send_another_message": "Enviar otro mensaje",
"sending": "Enviando",
"no_variables_found": "No se encontraron variables",
"credits_required": "Créditos requeridos",
"web_call_credits_info": "Las llamadas web consumen créditos. Asegúrate de tener suficientes créditos para iniciar una llamada.",
"web_call_no_credits": "Las llamadas web requieren créditos. Te quedan 0 créditos.",
"purchase_credits": "Comprar créditos",
"credits_required_tooltip": "Esta acción requiere créditos para continuar",
"api_key_name_too_long": "El nombre no debe exceder los {{max}} caracteres",
"auto_charge_for_last_minute_cancellation": "Cobrar automáticamente la tarifa por no presentarse en caso de cancelaciones de último minuto",
"before_scheduled_start_time": "Antes de la hora de inicio programada",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Número de teléfono (para que nuestro agente llame)",
"buy_a_phone_number_or_import_one_you_already_have": "Compra un número de teléfono o importa uno que ya tengas en twilio",
"test_agent": "Probar agente",
"test_web_call": "Probar llamada web",
"phone_numbers": "Números de teléfono",
"unsubscribe": "Dar de baja",
"delete_workflow_step": "Eliminar paso del flujo de trabajo",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplicación eliminada correctamente",
"error_removing_app": "Error al eliminar la aplicación",
"web_conference": "Conferencia web",
"web_call": "Llamada web",
"start_web_call": "Iniciar llamada web",
"end_call": "Finalizar llamada",
"test_your_agent_with_web_call": "Prueba tu agente con una llamada web sin necesidad de un número de teléfono",
"phone_call": "Llamada telefónica",
"connecting_to_agent": "Conectando con el agente...",
"call_active": "Llamada activa",
"call_ended": "Llamada finalizada",
"call_error": "Error en la llamada",
"ready_to_start_call": "Listo para iniciar la llamada",
"failed_to_start_web_call_try_again": "No se pudo iniciar la llamada web. Por favor, inténtalo de nuevo.",
"call_encountered_error_try_again": "La llamada encontró un error. Por favor, inténtalo de nuevo.",
"failed_initialize_web_call_microphone_permissions": "No se pudo inicializar la llamada web. Asegúrate de que los permisos del micrófono estén concedidos.",
"start_call_to_see_conversation": "Inicia una llamada para ver la conversación",
"waiting_for_conversation": "Esperando conversación...",
"ai_agent": "Agente de IA",
"live": "En vivo",
"connecting": "Conectando...",
"requires_confirmation": "Requiere confirmación",
"requires_confirmation_threshold": "Requiere confirmación si se reservó con < {{time}} $t({{unit}}_timeUnit) de aviso",
"may_require_confirmation": "Puede requerir confirmación",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Las transcripciones están habilitadas ahora",
"transcription_stopped": "Las transcripciones están detenidas ahora",
"download_transcript": "Descargar transcripción",
"transcript": "Transcripción",
"recording_from_your_recent_call": "Una grabación de su llamada reciente en {{appName}} está lista para descargar",
"transcript_from_previous_call": "La transcripción de su llamada reciente en {{appName}} está lista para descargar. Los enlaces son válidos solo por 1 hora.",
"you_can_download_transcript_from_attachments": "También puedes descargar la transcripción desde los archivos adjuntos",
@@ -3622,6 +3642,11 @@
"send_another_message": "Enviar otro mensaje",
"sending": "Enviando",
"no_variables_found": "No se encontraron variables",
"credits_required": "Créditos requeridos",
"web_call_credits_info": "Las llamadas web consumen créditos. Asegúrate de tener suficientes créditos para iniciar una llamada.",
"web_call_no_credits": "Las llamadas web requieren créditos. Te quedan 0 créditos.",
"purchase_credits": "Comprar créditos",
"credits_required_tooltip": "Esta acción requiere créditos para continuar",
"api_key_name_too_long": "El nombre no debe exceder los {{max}} caracteres",
"auto_charge_for_last_minute_cancellation": "Cobrar automáticamente la tarifa por no presentarse en caso de cancelaciones de último minuto",
"before_scheduled_start_time": "Antes de la hora de inicio programada",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefoninumber (millele meie agent helistab)",
"buy_a_phone_number_or_import_one_you_already_have": "Osta telefoninumber või impordi juba olemasolev Twilio number",
"test_agent": "Testi agenti",
"test_web_call": "Testi veebikõnet",
"phone_numbers": "Telefoninumbrid",
"unsubscribe": "Tühista tellimus",
"delete_workflow_step": "Kustuta töövoo samm",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Rakendus on edukalt eemaldatud",
"error_removing_app": "Viga rakenduse eemaldamisel",
"web_conference": "Veebikonverents",
"web_call": "Veebikõne",
"start_web_call": "Alusta veebikõnet",
"end_call": "Lõpeta kõne",
"test_your_agent_with_web_call": "Testi oma agenti veebikõnega ilma telefoninumbrit vajamata",
"phone_call": "Telefonikõne",
"connecting_to_agent": "Ühendamine agendiga...",
"call_active": "Kõne aktiivne",
"call_ended": "Kõne lõpetatud",
"call_error": "Kõne viga",
"ready_to_start_call": "Valmis kõnet alustama",
"failed_to_start_web_call_try_again": "Veebikõne alustamine ebaõnnestus. Palun proovi uuesti.",
"call_encountered_error_try_again": "Kõnes tekkis viga. Palun proovi uuesti.",
"failed_initialize_web_call_microphone_permissions": "Veebikõne initsialiseerimine ebaõnnestus. Palun veendu, et mikrofonilubad on antud.",
"start_call_to_see_conversation": "Alusta kõnet, et vestlust näha",
"waiting_for_conversation": "Ootan vestlust...",
"ai_agent": "Tehisintellekti agent",
"live": "Reaalajas",
"connecting": "Ühendamine...",
"requires_confirmation": "Nõuab kinnitust",
"requires_confirmation_threshold": "Nõuab kinnitust, kui broneering on < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Võib nõuda kinnitust",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkriptsioonid on nüüd lubatud",
"transcription_stopped": "Transkriptsioonid on nüüd peatatud",
"download_transcript": "Laadi alla transkript",
"transcript": "Transkript",
"recording_from_your_recent_call": "Teie hiljutise kõne salvestis rakenduses {{appName}} on allalaadimiseks valmis",
"transcript_from_previous_call": "Teie hiljutise kõne transkriptsioon rakenduses {{appName}} on allalaadimiseks valmis. Lingid kehtivad ainult 1 tund",
"you_can_download_transcript_from_attachments": "Transkriptsiooni saate alla laadida ka manustest",
@@ -3622,6 +3642,11 @@
"send_another_message": "Saada uus sõnum",
"sending": "Saatmine",
"no_variables_found": "Muutujaid ei leitud",
"credits_required": "Vajalikud krediidid",
"web_call_credits_info": "Veebikõned tarbivad krediite. Veendu, et sul oleks piisavalt krediite kõne alustamiseks.",
"web_call_no_credits": "Veebikõned nõuavad krediite. Sul on jäänud 0 krediiti.",
"purchase_credits": "Osta krediite",
"credits_required_tooltip": "Selle toimingu jätkamiseks on vaja krediite.",
"api_key_name_too_long": "Nimi ei tohi ületada {{max}} tähemärki",
"auto_charge_for_last_minute_cancellation": "Viimase hetke tühistamiste eest puudumise tasu automaatne arvestamine",
"before_scheduled_start_time": "Enne planeeritud algusaega",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefono zenbakia (gure agenteak deitzeko)",
"buy_a_phone_number_or_import_one_you_already_have": "Erosi telefono zenbaki bat edo inportatu dagoeneko twilio-n duzun bat",
"test_agent": "Probatu agentea",
"test_web_call": "Web deia probatu",
"phone_numbers": "Telefono zenbakiak",
"unsubscribe": "Harpidetza kendu",
"delete_workflow_step": "Ezabatu lan-fluxu urratsa",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplikazioa behar bezala kendu da",
"error_removing_app": "Errorea aplikazioa kentzean",
"web_conference": "Web konferentzia",
"web_call": "Web deia",
"start_web_call": "Hasi web deia",
"end_call": "Amaitu deia",
"test_your_agent_with_web_call": "Probatu zure agentea web dei batekin telefono zenbakirik behar izan gabe",
"phone_call": "Telefono deia",
"connecting_to_agent": "Agentearekiko konexioa egiten...",
"call_active": "Deia aktibo",
"call_ended": "Deia amaituta",
"call_error": "Dei errorea",
"ready_to_start_call": "Deia hasteko prest",
"failed_to_start_web_call_try_again": "Ezin izan da web deia hasi. Saiatu berriro.",
"call_encountered_error_try_again": "Deiak errore bat izan du. Saiatu berriro.",
"failed_initialize_web_call_microphone_permissions": "Ezin izan da web deia hasieratu. Ziurtatu mikrofonoaren baimenak emanda daudela.",
"start_call_to_see_conversation": "Hasi dei bat elkarrizketa ikusteko",
"waiting_for_conversation": "Elkarrizketaren zain...",
"ai_agent": "IA Agentea",
"live": "Zuzenean",
"connecting": "Konektatzen...",
"requires_confirmation": "Baieztapena behar du",
"requires_confirmation_threshold": "Baieztapena behar du < {{time}} $t({{unit}}_timeUnit) aurretiaz erreserbatuz gero",
"may_require_confirmation": "Baieztapena behar izan dezake",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkripzioak aktibatuta daude orain",
"transcription_stopped": "Transkripzioak gelditu dira orain",
"download_transcript": "Deskargatu transkripzioa",
"transcript": "Transkripzioa",
"recording_from_your_recent_call": "{{appName}}-en egin berri duzun deiaren grabaketa deskargatzeko prest dago",
"transcript_from_previous_call": "{{appName}}-en egin berri duzun deiaren transkripzioa deskargatzeko prest dago. Estekak ordubetez bakarrik dira baliozkoak",
"you_can_download_transcript_from_attachments": "Transkripzioa eranskinetan ere deskarga dezakezu",
@@ -3622,6 +3642,11 @@
"send_another_message": "Bidali beste mezu bat",
"sending": "Bidaltzen",
"no_variables_found": "Ez da aldagairik aurkitu",
"credits_required": "Kredituak beharrezkoak",
"web_call_credits_info": "Web deiek kredituak kontsumitzen dituzte. Ziurtatu kreditu nahikoa duzula dei bat hasteko.",
"web_call_no_credits": "Web deiek kredituak behar dituzte. 0 kreditu geratzen zaizkizu.",
"purchase_credits": "Erosi kredituak",
"credits_required_tooltip": "Ekintza honek kredituak behar ditu aurrera egiteko",
"api_key_name_too_long": "Izenak ezin ditu {{max}} karaktere baino gehiago izan",
"auto_charge_for_last_minute_cancellation": "Azken minutuko ezeztapenengatik ez agertzeko tasa automatikoki kobratu",
"before_scheduled_start_time": "Programatutako hasiera ordua baino lehen",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Puhelinnumero (johon agenttimme soittaa)",
"buy_a_phone_number_or_import_one_you_already_have": "Osta puhelinnumero tai tuo jo Twiliossa oleva numerosi",
"test_agent": "Testaa agentti",
"test_web_call": "Testaa verkkopuhelu",
"phone_numbers": "Puhelinnumerot",
"unsubscribe": "Peruuta tilaus",
"delete_workflow_step": "Poista työnkulun vaihe",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Sovellus poistettu onnistuneesti",
"error_removing_app": "Virhe sovelluksen poistamisessa",
"web_conference": "Verkkokokous",
"web_call": "Verkkopuhelu",
"start_web_call": "Aloita verkkopuhelu",
"end_call": "Lopeta puhelu",
"test_your_agent_with_web_call": "Testaa agenttisi verkkopuhelulla ilman puhelinnumeroa",
"phone_call": "Puhelinkeskustelu",
"connecting_to_agent": "Yhdistetään agenttiin...",
"call_active": "Puhelu aktiivinen",
"call_ended": "Puhelu päättyi",
"call_error": "Puheluvirhe",
"ready_to_start_call": "Valmis aloittamaan puhelun",
"failed_to_start_web_call_try_again": "Verkkopuhelun aloittaminen epäonnistui. Yritä uudelleen.",
"call_encountered_error_try_again": "Puhelussa tapahtui virhe. Yritä uudelleen.",
"failed_initialize_web_call_microphone_permissions": "Verkkopuhelun alustaminen epäonnistui. Varmista, että mikrofonin käyttöoikeudet on myönnetty.",
"start_call_to_see_conversation": "Aloita puhelu nähdäksesi keskustelun",
"waiting_for_conversation": "Odotetaan keskustelua...",
"ai_agent": "Tekoälyagentti",
"live": "Suora",
"connecting": "Yhdistetään...",
"requires_confirmation": "Vaatii vahvistuksen",
"requires_confirmation_threshold": "Vaatii vahvistuksen, jos varattu alle {{time}} $t({{unit}}_timeUnit) varoitusajalla",
"may_require_confirmation": "Saattaa vaatia vahvistuksen",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Litterointi on nyt käytössä",
"transcription_stopped": "Litterointi on nyt pysäytetty",
"download_transcript": "Lataa litterointi",
"transcript": "Litterointi",
"recording_from_your_recent_call": "Tallenne viimeisimmästä {{appName}}-puhelustasi on valmis ladattavaksi",
"transcript_from_previous_call": "Litterointi viimeisimmästä {{appName}}-puhelustasi on valmis ladattavaksi. Linkit ovat voimassa vain tunnin",
"you_can_download_transcript_from_attachments": "Voit myös ladata muistion liitteistä",
@@ -3622,6 +3642,11 @@
"send_another_message": "Lähetä toinen viesti",
"sending": "Lähetetään",
"no_variables_found": "Muuttujia ei löytynyt",
"credits_required": "Krediittejä vaaditaan",
"web_call_credits_info": "Verkkopuhelut kuluttavat krediittejä. Varmista, että sinulla on tarpeeksi krediittejä puhelun aloittamiseen.",
"web_call_no_credits": "Verkkopuhelut vaativat krediittejä. Sinulla on 0 krediittiä jäljellä.",
"purchase_credits": "Osta krediittejä",
"credits_required_tooltip": "Tämä toiminto vaatii krediittejä jatkaaksesi",
"api_key_name_too_long": "Nimi ei saa ylittää {{max}} merkkiä",
"auto_charge_for_last_minute_cancellation": "Veloita automaattisesti peruutusmaksu viime hetken peruutuksista",
"before_scheduled_start_time": "Ennen sovittua aloitusaikaa",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Numéro de téléphone (pour que notre agent puisse appeler)",
"buy_a_phone_number_or_import_one_you_already_have": "Achetez un numéro de téléphone ou importez-en un que vous possédez déjà sur Twilio",
"test_agent": "Tester l'agent",
"test_web_call": "Tester l'appel web",
"phone_numbers": "Numéros de téléphone",
"unsubscribe": "Se désabonner",
"delete_workflow_step": "Supprimer l'étape du workflow",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Application supprimée avec succès",
"error_removing_app": "Erreur lors de la suppression de l'application",
"web_conference": "Visioconférence",
"web_call": "Appel web",
"start_web_call": "Démarrer l'appel web",
"end_call": "Terminer l'appel",
"test_your_agent_with_web_call": "Testez votre agent avec un appel web sans nécessiter de numéro de téléphone",
"phone_call": "Appel téléphonique",
"connecting_to_agent": "Connexion à l'agent...",
"call_active": "Appel en cours",
"call_ended": "Appel terminé",
"call_error": "Erreur d'appel",
"ready_to_start_call": "Prêt à démarrer l'appel",
"failed_to_start_web_call_try_again": "Échec du démarrage de l'appel web. Veuillez réessayer.",
"call_encountered_error_try_again": "L'appel a rencontré une erreur. Veuillez réessayer.",
"failed_initialize_web_call_microphone_permissions": "Échec d'initialisation de l'appel web. Veuillez vous assurer que les permissions du microphone sont accordées.",
"start_call_to_see_conversation": "Démarrez un appel pour voir la conversation",
"waiting_for_conversation": "En attente de conversation...",
"ai_agent": "Agent IA",
"live": "En direct",
"connecting": "Connexion en cours...",
"requires_confirmation": "Nécessite une confirmation",
"requires_confirmation_threshold": "Une confirmation est requise si réservé avec un préavis de moins de {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Peut nécessiter une confirmation",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Les transcriptions sont désormais activées",
"transcription_stopped": "Les transcriptions sont désormais arrêtées",
"download_transcript": "Télécharger la transcription",
"transcript": "Transcription",
"recording_from_your_recent_call": "Un enregistrement de votre appel récent sur {{appName}} est prêt à être téléchargé",
"transcript_from_previous_call": "La transcription de votre récent appel sur {{appName}} est prête à être téléchargée. Les liens ne sont valides que pendant 1 heure",
"you_can_download_transcript_from_attachments": "Vous pouvez également télécharger la transcription depuis les pièces jointes",
@@ -3622,6 +3642,11 @@
"send_another_message": "Envoyer un autre message",
"sending": "Envoi en cours",
"no_variables_found": "Aucune variable trouvée",
"credits_required": "Crédits requis",
"web_call_credits_info": "Les appels web consomment des crédits. Assurez-vous d'avoir suffisamment de crédits pour démarrer un appel.",
"web_call_no_credits": "Les appels web nécessitent des crédits. Il vous reste 0 crédit.",
"purchase_credits": "Acheter des crédits",
"credits_required_tooltip": "Cette action nécessite des crédits pour continuer",
"api_key_name_too_long": "Le nom ne doit pas dépasser {{max}} caractères",
"auto_charge_for_last_minute_cancellation": "Facturer automatiquement des frais de non-présentation pour les annulations de dernière minute",
"before_scheduled_start_time": "Avant l'heure de début prévue",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "מספר טלפון (עבור הסוכן שלנו להתקשר אליו)",
"buy_a_phone_number_or_import_one_you_already_have": "קנה מספר טלפון או ייבא אחד שכבר יש לך ב-Twilio",
"test_agent": "בדוק סוכן",
"test_web_call": "בדיקת שיחת אינטרנט",
"phone_numbers": "מספרי טלפון",
"unsubscribe": "בטל מנוי",
"delete_workflow_step": "מחק שלב בתהליך העבודה",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "האפליקציה הוסרה בהצלחה",
"error_removing_app": "שגיאה בהסרת האפליקציה",
"web_conference": "שיחת ועידה מקוונת",
"web_call": "שיחת אינטרנט",
"start_web_call": "התחל שיחת אינטרנט",
"end_call": "סיים שיחה",
"test_your_agent_with_web_call": "בדוק את הסוכן שלך עם שיחת אינטרנט ללא צורך במספר טלפון",
"phone_call": "שיחת טלפון",
"connecting_to_agent": "מתחבר לסוכן...",
"call_active": "שיחה פעילה",
"call_ended": "השיחה הסתיימה",
"call_error": "שגיאת שיחה",
"ready_to_start_call": "מוכן להתחיל שיחה",
"failed_to_start_web_call_try_again": "נכשל בהתחלת שיחת אינטרנט. אנא נסה שוב.",
"call_encountered_error_try_again": "השיחה נתקלה בשגיאה. אנא נסה שוב.",
"failed_initialize_web_call_microphone_permissions": "נכשל באתחול שיחת אינטרנט. אנא ודא שהרשאות המיקרופון מאושרות.",
"start_call_to_see_conversation": "התחל שיחה כדי לראות את השיחה",
"waiting_for_conversation": "ממתין לשיחה...",
"ai_agent": "סוכן בינה מלאכותית",
"live": "חי",
"connecting": "מתחבר...",
"requires_confirmation": "נדרש אישור",
"requires_confirmation_threshold": "מחייב אישור אם ההזמנה בוצעה פחות מ-{{time}} $t({{unit}}_timeUnit) מראש",
"may_require_confirmation": "עשוי לחייב אישור",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "תמלולים פעילים כרגע",
"transcription_stopped": "תמלולים מופסקים כרגע",
"download_transcript": "הורדת תמלול",
"transcript": "תמליל",
"recording_from_your_recent_call": "הקלטה של שיחה שערכת לאחרונה ב-{{appName}} מוכנה להורדה",
"transcript_from_previous_call": "תמלול השיחה האחרונה שלך ב־{{appName}} זמין להורדה. קישורים תקפים למשך שעה בלבד",
"you_can_download_transcript_from_attachments": "ניתן גם להוריד את התמליל מהקבצים המצורפים",
@@ -3622,6 +3642,11 @@
"send_another_message": "שלח הודעה נוספת",
"sending": "שולח",
"no_variables_found": "לא נמצאו משתנים",
"credits_required": "נדרשים קרדיטים",
"web_call_credits_info": "שיחות אינטרנט צורכות קרדיטים. ודא שיש לך מספיק קרדיטים להתחלת שיחה.",
"web_call_no_credits": "שיחות אינטרנט דורשות קרדיטים. נותרו לך 0 קרדיטים.",
"purchase_credits": "רכוש קרדיטים",
"credits_required_tooltip": "פעולה זו דורשת קרדיטים כדי להמשיך",
"api_key_name_too_long": "השם לא יכול לעלות על {{max}} תווים",
"auto_charge_for_last_minute_cancellation": "חיוב אוטומטי של דמי אי-הופעה עבור ביטולים ברגע האחרון",
"before_scheduled_start_time": "לפני זמן ההתחלה המתוכנן",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonszám (amit az ügynökünk hívni fog)",
"buy_a_phone_number_or_import_one_you_already_have": "Vásárolj telefonszámot vagy importálj egyet, amit már használsz a Twilio-n",
"test_agent": "Ügynök tesztelése",
"test_web_call": "Webes hívás tesztelése",
"phone_numbers": "Telefonszámok",
"unsubscribe": "Leiratkozás",
"delete_workflow_step": "Munkafolyamat lépés törlése",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Az alkalmazás sikeresen eltávolítva",
"error_removing_app": "Hiba az alkalmazás eltávolításakor",
"web_conference": "Online konferencia",
"web_call": "Webes hívás",
"start_web_call": "Webes hívás indítása",
"end_call": "Hívás befejezése",
"test_your_agent_with_web_call": "Tesztelje ügynökét webes hívással telefonszám megadása nélkül",
"phone_call": "Telefonhívás",
"connecting_to_agent": "Kapcsolódás az ügynökhöz...",
"call_active": "Hívás aktív",
"call_ended": "Hívás befejezve",
"call_error": "Hívási hiba",
"ready_to_start_call": "Készen áll a hívás indítására",
"failed_to_start_web_call_try_again": "Nem sikerült elindítani a webes hívást. Kérjük, próbálja újra.",
"call_encountered_error_try_again": "A hívás során hiba történt. Kérjük, próbálja újra.",
"failed_initialize_web_call_microphone_permissions": "Nem sikerült inicializálni a webes hívást. Kérjük, ellenőrizze, hogy a mikrofon-hozzáférés engedélyezve van.",
"start_call_to_see_conversation": "Indítson hívást a beszélgetés megtekintéséhez",
"waiting_for_conversation": "Várakozás a beszélgetésre...",
"ai_agent": "MI ügynök",
"live": "Élő",
"connecting": "Kapcsolódás...",
"requires_confirmation": "Megerősítést igényel",
"requires_confirmation_threshold": "Megerősítést igényel, ha < {{time}} $t({{unit}}_timeUnit) értesítéssel foglalt",
"may_require_confirmation": "Megerősítést igényelhet",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Az átírások most engedélyezve vannak",
"transcription_stopped": "Az átírások most le lett állítva",
"download_transcript": "Átirat letöltése",
"transcript": "Átirat",
"recording_from_your_recent_call": "A legutóbbi {{appName}} hívásáról készült felvétel letölthető",
"transcript_from_previous_call": "A(z) {{appName}} nevű legutóbbi hívásának átirata letölthető. A linkek csak 1 óráig érvényesek",
"you_can_download_transcript_from_attachments": "A jegyzőkönyvet letöltheti a csatolmányok közül",
@@ -3622,6 +3642,11 @@
"send_another_message": "Másik üzenet küldése",
"sending": "Küldés folyamatban",
"no_variables_found": "Nem találhatók változók",
"credits_required": "Kreditek szükségesek",
"web_call_credits_info": "A webes hívások krediteket használnak. Győződjön meg róla, hogy elegendő kredittel rendelkezik a hívás indításához.",
"web_call_no_credits": "A webes hívásokhoz kreditek szükségesek. 0 kredit maradt.",
"purchase_credits": "Kreditek vásárlása",
"credits_required_tooltip": "Ehhez a művelethez kreditek szükségesek",
"api_key_name_too_long": "A név nem haladhatja meg a {{max}} karaktert",
"auto_charge_for_last_minute_cancellation": "Automatikus lemondási díj felszámolása az utolsó pillanatban történő lemondásokért",
"before_scheduled_start_time": "A tervezett kezdési idő előtt",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Numero di telefono (che il nostro agente chiamerà)",
"buy_a_phone_number_or_import_one_you_already_have": "Acquista un numero di telefono o importane uno che hai già su Twilio",
"test_agent": "Testa l'agente",
"test_web_call": "Prova chiamata web",
"phone_numbers": "Numeri di telefono",
"unsubscribe": "Disiscriviti",
"delete_workflow_step": "Elimina fase del workflow",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "App rimossa correttamente",
"error_removing_app": "Errore durante la rimozione dell'app",
"web_conference": "Conferenza Web",
"web_call": "Chiamata web",
"start_web_call": "Inizia chiamata web",
"end_call": "Termina chiamata",
"test_your_agent_with_web_call": "Prova il tuo agente con una chiamata web senza richiedere un numero di telefono",
"phone_call": "Chiamata telefonica",
"connecting_to_agent": "Connessione all'agente in corso...",
"call_active": "Chiamata attiva",
"call_ended": "Chiamata terminata",
"call_error": "Errore nella chiamata",
"ready_to_start_call": "Pronto per iniziare la chiamata",
"failed_to_start_web_call_try_again": "Impossibile avviare la chiamata web. Riprova.",
"call_encountered_error_try_again": "La chiamata ha riscontrato un errore. Riprova.",
"failed_initialize_web_call_microphone_permissions": "Impossibile inizializzare la chiamata web. Assicurati che i permessi del microfono siano concessi.",
"start_call_to_see_conversation": "Inizia una chiamata per vedere la conversazione",
"waiting_for_conversation": "In attesa della conversazione...",
"ai_agent": "Agente IA",
"live": "In diretta",
"connecting": "Connessione in corso...",
"requires_confirmation": "Richiede conferma",
"requires_confirmation_threshold": "Richiede conferma se si prenota con un preavviso di < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Può richiedere conferma",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Le trascrizioni sono attivate",
"transcription_stopped": "Le trascrizioni non sono attive",
"download_transcript": "Scarica trascrizione",
"transcript": "Trascrizione",
"recording_from_your_recent_call": "La registrazione della tua recente chiamata su {{appName}} è pronta per il download",
"transcript_from_previous_call": "La trascrizione dalla tua recente chiamata su {{appName}} è pronta per essere scaricata. I link sono validi solo per 1 ora",
"you_can_download_transcript_from_attachments": "Puoi anche scaricare la trascrizione dagli allegati",
@@ -3622,6 +3642,11 @@
"send_another_message": "Invia un altro messaggio",
"sending": "Invio in corso",
"no_variables_found": "Nessuna variabile trovata",
"credits_required": "Crediti richiesti",
"web_call_credits_info": "Le chiamate web consumano crediti. Assicurati di avere crediti sufficienti per iniziare una chiamata.",
"web_call_no_credits": "Le chiamate web richiedono crediti. Hai 0 crediti rimanenti.",
"purchase_credits": "Acquista crediti",
"credits_required_tooltip": "Questa azione richiede crediti per procedere",
"api_key_name_too_long": "Il nome non deve superare i {{max}} caratteri",
"auto_charge_for_last_minute_cancellation": "Addebita automaticamente la penale per cancellazioni dell'ultimo minuto",
"before_scheduled_start_time": "Prima dell'orario di inizio programmato",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "電話番号(エージェントが通話するため)",
"buy_a_phone_number_or_import_one_you_already_have": "電話番号を購入するか、すでにTwilioで持っている番号をインポートする",
"test_agent": "エージェントをテスト",
"test_web_call": "Webコールのテスト",
"phone_numbers": "電話番号",
"unsubscribe": "登録解除",
"delete_workflow_step": "ワークフローステップを削除",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "アプリは正常に削除されました",
"error_removing_app": "アプリの削除中にエラーが発生しました",
"web_conference": "ウェブ会議",
"web_call": "Webコール",
"start_web_call": "Webコールを開始",
"end_call": "通話を終了",
"test_your_agent_with_web_call": "電話番号不要でWebコールでエージェントをテスト",
"phone_call": "電話",
"connecting_to_agent": "エージェントに接続中...",
"call_active": "通話中",
"call_ended": "通話終了",
"call_error": "通話エラー",
"ready_to_start_call": "通話開始の準備完了",
"failed_to_start_web_call_try_again": "Webコールの開始に失敗しました。もう一度お試しください。",
"call_encountered_error_try_again": "通話中にエラーが発生しました。もう一度お試しください。",
"failed_initialize_web_call_microphone_permissions": "Webコールの初期化に失敗しました。マイクの権限が許可されていることを確認してください。",
"start_call_to_see_conversation": "会話を表示するには通話を開始してください",
"waiting_for_conversation": "会話を待機中...",
"ai_agent": "AIエージェント",
"live": "ライブ",
"connecting": "接続中...",
"requires_confirmation": "確認が必要です",
"requires_confirmation_threshold": "< {{time}} $t({{unit}}_timeUnit) の通知で予約した場合には確認が必要です",
"may_require_confirmation": "確認が必要となる可能性があります",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "文字起こしが有効になりました",
"transcription_stopped": "文字起こしが停止しました",
"download_transcript": "トランスクリプトをダウンロード",
"transcript": "文字起こし",
"recording_from_your_recent_call": "{{appName}} での最近の通話のレコーディングがダウンロードできます",
"transcript_from_previous_call": "{{appName}}での最近の通話のトランスクリプトがダウンロード可能です。リンクは1時間のみ有効です。",
"you_can_download_transcript_from_attachments": "添付ファイルから議事録をダウンロードすることもできます",
@@ -3622,6 +3642,11 @@
"send_another_message": "別のメッセージを送信する",
"sending": "送信中",
"no_variables_found": "変数が見つかりません",
"credits_required": "クレジットが必要です",
"web_call_credits_info": "Webコールはクレジットを消費します。通話を開始するのに十分なクレジットがあることを確認してください。",
"web_call_no_credits": "Webコールにはクレジットが必要です。残りクレジットは0です。",
"purchase_credits": "クレジットを購入",
"credits_required_tooltip": "この操作を進めるにはクレジットが必要です",
"api_key_name_too_long": "名前は{{max}}文字を超えてはいけません",
"auto_charge_for_last_minute_cancellation": "直前のキャンセルに対して自動的に無断キャンセル料を請求する",
"before_scheduled_start_time": "予定開始時刻より前",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "លេខទូរស័ព្ទ (សម្រាប់ភ្នាក់ងាររបស់យើងដើម្បីហៅ)",
"buy_a_phone_number_or_import_one_you_already_have": "ទិញលេខទូរស័ព្ទមួយឬនាំចូលលេខដែលអ្នកមានរួចហើយនៅលើ twilio",
"test_agent": "សាកល្បងភ្នាក់ងារ",
"test_web_call": "សាកល្បងការហៅទូរស័ព្ទតាមវេប",
"phone_numbers": "លេខទូរស័ព្ទ",
"unsubscribe": "ដកការជាវ",
"delete_workflow_step": "លុបជំហានលំហូរការងារ",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "កម្មវិធីត្រូវបានយកចេញដោយជោគជ័យ",
"error_removing_app": "មានកំហុសក្នុងការយកកម្មវិធីចេញ",
"web_conference": "សន្និសីទតាមអ៊ីនធឺណិត",
"web_call": "ការហៅទូរស័ព្ទតាមវេប",
"start_web_call": "ចាប់ផ្តើមការហៅទូរស័ព្ទតាមវេប",
"end_call": "បញ្ចប់ការហៅទូរស័ព្ទ",
"test_your_agent_with_web_call": "សាកល្បងភ្នាក់ងាររបស់អ្នកជាមួយការហៅទូរស័ព្ទតាមវេបដោយមិនចាំបាច់មានលេខទូរស័ព្ទ",
"phone_call": "ការហៅទូរស័ព្ទ",
"connecting_to_agent": "កំពុងភ្ជាប់ទៅភ្នាក់ងារ...",
"call_active": "ការហៅទូរស័ព្ទសកម្ម",
"call_ended": "ការហៅទូរស័ព្ទបានបញ្ចប់",
"call_error": "កំហុសក្នុងការហៅទូរស័ព្ទ",
"ready_to_start_call": "រួចរាល់ដើម្បីចាប់ផ្តើមការហៅទូរស័ព្ទ",
"failed_to_start_web_call_try_again": "បរាជ័យក្នុងការចាប់ផ្តើមការហៅទូរស័ព្ទតាមវេប។ សូមព្យាយាមម្តងទៀត។",
"call_encountered_error_try_again": "ការហៅទូរស័ព្ទជួបប្រទះកំហុស។ សូមព្យាយាមម្តងទៀត។",
"failed_initialize_web_call_microphone_permissions": "បរាជ័យក្នុងការចាប់ផ្តើមការហៅទូរស័ព្ទតាមវេប។ សូមប្រាកដថាការអនុញ្ញាតមីក្រូហ្វូនត្រូវបានផ្តល់។",
"start_call_to_see_conversation": "ចាប់ផ្តើមការហៅទូរស័ព្ទដើម្បីមើលការសន្ទនា",
"waiting_for_conversation": "កំពុងរង់ចាំការសន្ទនា...",
"ai_agent": "ភ្នាក់ងារ AI",
"live": "ផ្ទាល់",
"connecting": "កំពុងភ្ជាប់...",
"requires_confirmation": "ទាមទារការបញ្ជាក់",
"requires_confirmation_threshold": "ត្រូវការការបញ្ជាក់ប្រសិនបើបានកក់ជាមួយ < {{time}} $t({{unit}}_timeUnit) ការជូនដំណឹង",
"may_require_confirmation": "អាចត្រូវការការបញ្ជាក់",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "ការបកប្រែត្រូវបានបើកដំណើរការឥឡូវនេះ",
"transcription_stopped": "ការបកប្រែត្រូវបានបញ្ឈប់ឥឡូវនេះ",
"download_transcript": "ទាញយកការបកប្រែ",
"transcript": "កំណត់ត្រាការសន្ទនា",
"recording_from_your_recent_call": "ការថតពីការហៅថ្មីៗរបស់អ្នកនៅលើ {{appName}} ត្រូវបានត្រៀមរួចរាល់សម្រាប់ទាញយក",
"transcript_from_previous_call": "ការបកប្រែពីការហៅថ្មីៗរបស់អ្នកនៅលើ {{appName}} ត្រូវបានត្រៀមរួចរាល់សម្រាប់ទាញយក។ តំណភ្ជាប់មានសុពលភាពតែ 1 ម៉ោងប៉ុណ្ណោះ",
"you_can_download_transcript_from_attachments": "អ្នកក៏អាចទាញយកកំណត់ត្រាពីឯកសារភ្ជាប់បានដែរ",
@@ -3622,6 +3642,11 @@
"send_another_message": "ផ្ញើសារមួយទៀត",
"sending": "កំពុងផ្ញើ",
"no_variables_found": "រកមិនឃើញអថេរទេ",
"credits_required": "តម្រូវឱ្យមានក្រេឌីត",
"web_call_credits_info": "ការហៅទូរស័ព្ទតាមវេបប្រើប្រាស់ក្រេឌីត។ សូមប្រាកដថាអ្នកមានក្រេឌីតគ្រប់គ្រាន់ដើម្បីចាប់ផ្តើមការហៅទូរស័ព្ទ។",
"web_call_no_credits": "ការហៅទូរស័ព្ទតាមវេបតម្រូវឱ្យមានក្រេឌីត។ អ្នកមានក្រេឌីតនៅសល់ 0។",
"purchase_credits": "ទិញក្រេឌីត",
"credits_required_tooltip": "សកម្មភាពនេះតម្រូវឱ្យមានក្រេឌីតដើម្បីបន្ត",
"api_key_name_too_long": "ឈ្មោះមិនត្រូវលើសពី {{max}} តួអក្សរទេ",
"auto_charge_for_last_minute_cancellation": "គិតថ្លៃសេវាដោយស្វ័យប្រវត្តិសម្រាប់ការលុបចោលនៅនាទីចុងក្រោយ",
"before_scheduled_start_time": "មុនពេលចាប់ផ្តើមដែលបានកំណត់",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "전화번호(에이전트가 전화할 번호)",
"buy_a_phone_number_or_import_one_you_already_have": "전화번호 구매 또는 이미 트윌리오에 있는 번호 가져오기",
"test_agent": "에이전트 테스트",
"test_web_call": "웹 통화 테스트",
"phone_numbers": "전화번호",
"unsubscribe": "구독 해지",
"delete_workflow_step": "워크플로우 단계 삭제",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "앱이 제거됨",
"error_removing_app": "앱 제거 중 오류 발생",
"web_conference": "웹 화상 회의",
"web_call": "웹 통화",
"start_web_call": "웹 통화 시작",
"end_call": "통화 종료",
"test_your_agent_with_web_call": "전화번호 없이 웹 통화로 에이전트 테스트하기",
"phone_call": "전화 통화",
"connecting_to_agent": "에이전트에 연결 중...",
"call_active": "통화 중",
"call_ended": "통화 종료됨",
"call_error": "통화 오류",
"ready_to_start_call": "통화 시작 준비 완료",
"failed_to_start_web_call_try_again": "웹 통화 시작에 실패했습니다. 다시 시도해 주세요.",
"call_encountered_error_try_again": "통화 중 오류가 발생했습니다. 다시 시도해 주세요.",
"failed_initialize_web_call_microphone_permissions": "웹 통화 초기화에 실패했습니다. 마이크 권한이 허용되어 있는지 확인해 주세요.",
"start_call_to_see_conversation": "대화를 보려면 통화를 시작하세요",
"waiting_for_conversation": "대화 대기 중...",
"ai_agent": "AI 에이전트",
"live": "실시간",
"connecting": "연결 중...",
"requires_confirmation": "확인 필수",
"requires_confirmation_threshold": "< {{time}} $t({{unit}}_timeUnit) 알림으로 예약한 경우 확인이 필요합니다",
"may_require_confirmation": "확인이 필요할 수 있음",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "전사 기능이 활성화되었습니다",
"transcription_stopped": "전사 기능이 중지되었습니다",
"download_transcript": "대화 기록 다운로드",
"transcript": "대화 내용",
"recording_from_your_recent_call": "{{appName}}에서 이루어진 최근 통화 녹음을 지금 바로 다운로드할 수 있습니다",
"transcript_from_previous_call": "{{appName}}에서 최근 통화한 대화 기록이 다운로드 준비되었습니다. 링크는 1시간 동안만 유효합니다.",
"you_can_download_transcript_from_attachments": "첨부 파일에서 대화 내용을 다운로드할 수 있습니다",
@@ -3622,6 +3642,11 @@
"send_another_message": "다른 메시지 보내기",
"sending": "전송 중",
"no_variables_found": "변수를 찾을 수 없음",
"credits_required": "크레딧 필요",
"web_call_credits_info": "웹 통화는 크레딧을 소모합니다. 통화를 시작하기 전에 충분한 크레딧이 있는지 확인하세요.",
"web_call_no_credits": "웹 통화에는 크레딧이 필요합니다. 남은 크레딧이 0개입니다.",
"purchase_credits": "크레딧 구매",
"credits_required_tooltip": "이 작업을 진행하려면 크레딧이 필요합니다",
"api_key_name_too_long": "이름은 {{max}}자를 초과할 수 없습니다",
"auto_charge_for_last_minute_cancellation": "마지막 순간 취소에 대해 노쇼 수수료 자동 청구",
"before_scheduled_start_time": "예약된 시작 시간 전",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefoonnummer (voor onze agent om te bellen)",
"buy_a_phone_number_or_import_one_you_already_have": "Koop een telefoonnummer of importeer er een die je al hebt bij Twilio",
"test_agent": "Test agent",
"test_web_call": "Test webgesprek",
"phone_numbers": "Telefoonnummers",
"unsubscribe": "Afmelden",
"delete_workflow_step": "Workflow-stap verwijderen",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "De app is verwijderd",
"error_removing_app": "Fout bij het verwijderen van de app",
"web_conference": "Webconferentie",
"web_call": "Webgesprek",
"start_web_call": "Start webgesprek",
"end_call": "Gesprek beëindigen",
"test_your_agent_with_web_call": "Test je agent met een webgesprek zonder dat een telefoonnummer nodig is",
"phone_call": "Telefoongesprek",
"connecting_to_agent": "Verbinding maken met agent...",
"call_active": "Gesprek actief",
"call_ended": "Gesprek beëindigd",
"call_error": "Gespreksfout",
"ready_to_start_call": "Klaar om gesprek te starten",
"failed_to_start_web_call_try_again": "Webgesprek starten mislukt. Probeer het opnieuw.",
"call_encountered_error_try_again": "Er is een fout opgetreden tijdens het gesprek. Probeer het opnieuw.",
"failed_initialize_web_call_microphone_permissions": "Initialiseren van webgesprek mislukt. Zorg ervoor dat microfoonrechten zijn toegestaan.",
"start_call_to_see_conversation": "Start een gesprek om de conversatie te zien",
"waiting_for_conversation": "Wachten op conversatie...",
"ai_agent": "AI-agent",
"live": "Live",
"connecting": "Verbinding maken...",
"requires_confirmation": "Vereist bevestiging",
"requires_confirmation_threshold": "Vereist bevestiging indien geboekt met < {{time}} $t({{unit}}_timeUnit) kennisgeving",
"may_require_confirmation": "Kan bevestiging vereisen",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transcripties zijn nu ingeschakeld",
"transcription_stopped": "Transcripties zijn nu gestopt",
"download_transcript": "Transcriptie downloaden",
"transcript": "Transcript",
"recording_from_your_recent_call": "Een opname van uw recente gesprek op {{appName}} is klaar om te downloaden",
"transcript_from_previous_call": "Transcriptie van uw recente gesprek op {{appName}} is klaar om te downloaden. Links zijn slechts 1 uur geldig",
"you_can_download_transcript_from_attachments": "Je kunt het transcript ook downloaden via de bijlagen",
@@ -3622,6 +3642,11 @@
"send_another_message": "Nog een bericht versturen",
"sending": "Bezig met verzenden",
"no_variables_found": "Geen variabelen gevonden",
"credits_required": "Credits vereist",
"web_call_credits_info": "Webgesprekken verbruiken credits. Zorg ervoor dat je voldoende credits hebt om een gesprek te starten.",
"web_call_no_credits": "Webgesprekken vereisen credits. Je hebt 0 credits over.",
"purchase_credits": "Credits kopen",
"credits_required_tooltip": "Deze actie vereist credits om verder te gaan",
"api_key_name_too_long": "Naam mag niet langer zijn dan {{max}} tekens",
"auto_charge_for_last_minute_cancellation": "Automatisch no-show kosten in rekening brengen voor last-minute annuleringen",
"before_scheduled_start_time": "Voor geplande starttijd",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonnummer (for at agenten vår skal ringe)",
"buy_a_phone_number_or_import_one_you_already_have": "Kjøp et telefonnummer eller importer et du allerede har på twilio",
"test_agent": "Test agent",
"test_web_call": "Test nettanrop",
"phone_numbers": "Telefonnumre",
"unsubscribe": "Avslutt abonnement",
"delete_workflow_step": "Slett arbeidsflyttrinn",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Appen ble fjernet",
"error_removing_app": "Feil ved fjerning av app",
"web_conference": "Nett-konferanse",
"web_call": "Nettanrop",
"start_web_call": "Start nettanrop",
"end_call": "Avslutt samtale",
"test_your_agent_with_web_call": "Test agenten din med et nettanrop uten å kreve telefonnummer",
"phone_call": "Telefonsamtale",
"connecting_to_agent": "Kobler til agent...",
"call_active": "Samtale aktiv",
"call_ended": "Samtale avsluttet",
"call_error": "Samtalefeil",
"ready_to_start_call": "Klar til å starte samtale",
"failed_to_start_web_call_try_again": "Kunne ikke starte nettanrop. Vennligst prøv igjen.",
"call_encountered_error_try_again": "Samtalen støtte på en feil. Vennligst prøv igjen.",
"failed_initialize_web_call_microphone_permissions": "Kunne ikke initialisere nettanrop. Sørg for at mikrofontillatelser er gitt.",
"start_call_to_see_conversation": "Start en samtale for å se konversasjonen",
"waiting_for_conversation": "Venter på samtale...",
"ai_agent": "KI-agent",
"live": "Direkte",
"connecting": "Kobler til...",
"requires_confirmation": "Krever bekreftelse",
"requires_confirmation_threshold": "Krever bekreftelse hvis booket med < {{time}} $t({{unit}}_timeUnit) varsel",
"may_require_confirmation": "Kan kreve bekreftelse",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkripsjoner er nå aktivert",
"transcription_stopped": "Transkripsjoner er nå stoppet",
"download_transcript": "Last ned transkripsjon",
"transcript": "Transkripsjon",
"recording_from_your_recent_call": "Et opptak fra din nylige samtale på {{appName}} er klart for nedlasting",
"transcript_from_previous_call": "Transkripsjon fra din nylige samtale på {{appName}} er klar for nedlasting. Lenker er kun gyldige i 1 time",
"you_can_download_transcript_from_attachments": "Du kan også laste ned transkripsjonen fra vedleggene",
@@ -3622,6 +3642,11 @@
"send_another_message": "Send en ny melding",
"sending": "Sender",
"no_variables_found": "Ingen variabler funnet",
"credits_required": "Kreditter kreves",
"web_call_credits_info": "Nettanrop bruker kreditter. Sørg for at du har nok kreditter til å starte en samtale.",
"web_call_no_credits": "Nettanrop krever kreditter. Du har 0 kreditter igjen.",
"purchase_credits": "Kjøp kreditter",
"credits_required_tooltip": "Denne handlingen krever kreditter for å fortsette",
"api_key_name_too_long": "Navnet må ikke overstige {{max}} tegn",
"auto_charge_for_last_minute_cancellation": "Auto-belast gebyr for uteblivelse ved avbestillinger i siste liten",
"before_scheduled_start_time": "Før planlagt starttid",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Numer telefonu (dla naszego agenta do wykonania połączenia)",
"buy_a_phone_number_or_import_one_you_already_have": "Kup numer telefonu lub zaimportuj taki, który już posiadasz w Twilio",
"test_agent": "Przetestuj agenta",
"test_web_call": "Przetestuj połączenie internetowe",
"phone_numbers": "Numery telefonów",
"unsubscribe": "Wypisz się",
"delete_workflow_step": "Usuń krok przepływu pracy",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplikacja została usunięta",
"error_removing_app": "Błąd podczas usuwania aplikacji",
"web_conference": "Konferencja internetowa",
"web_call": "Połączenie internetowe",
"start_web_call": "Rozpocznij połączenie internetowe",
"end_call": "Zakończ połączenie",
"test_your_agent_with_web_call": "Przetestuj swojego agenta za pomocą połączenia internetowego bez potrzeby użycia numeru telefonu",
"phone_call": "Połączenie telefoniczne",
"connecting_to_agent": "Łączenie z agentem...",
"call_active": "Połączenie aktywne",
"call_ended": "Połączenie zakończone",
"call_error": "Błąd połączenia",
"ready_to_start_call": "Gotowy do rozpoczęcia połączenia",
"failed_to_start_web_call_try_again": "Nie udało się rozpocząć połączenia internetowego. Spróbuj ponownie.",
"call_encountered_error_try_again": "Wystąpił błąd podczas połączenia. Spróbuj ponownie.",
"failed_initialize_web_call_microphone_permissions": "Nie udało się zainicjować połączenia internetowego. Upewnij się, że przyznano uprawnienia do mikrofonu.",
"start_call_to_see_conversation": "Rozpocznij połączenie, aby zobaczyć rozmowę",
"waiting_for_conversation": "Oczekiwanie na rozmowę...",
"ai_agent": "Agent AI",
"live": "Na żywo",
"connecting": "Łączenie...",
"requires_confirmation": "Wymaga potwierdzenia",
"requires_confirmation_threshold": "Wymaga potwierdzenia w przypadku rezerwacji z wyprzedzeniem < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Może wymagać potwierdzenia",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkrypcje są teraz włączone",
"transcription_stopped": "Transkrypcje zostały zatrzymane",
"download_transcript": "Pobierz transkrypcję",
"transcript": "Transkrypcja",
"recording_from_your_recent_call": "Nagranie z ostatniego połączenia w aplikacji {{appName}} jest gotowe do pobrania.",
"transcript_from_previous_call": "Transkrypcja z Twojej ostatniej rozmowy na {{appName}} jest gotowa do pobrania. Linki są ważne tylko przez 1 godzinę",
"you_can_download_transcript_from_attachments": "Możesz również pobrać transkrypcję z załączników",
@@ -3622,6 +3642,11 @@
"send_another_message": "Wyślij kolejną wiadomość",
"sending": "Wysyłanie",
"no_variables_found": "Nie znaleziono zmiennych",
"credits_required": "Wymagane kredyty",
"web_call_credits_info": "Połączenia internetowe zużywają kredyty. Upewnij się, że masz wystarczającą liczbę kredytów, aby rozpocząć połączenie.",
"web_call_no_credits": "Połączenia internetowe wymagają kredytów. Masz 0 pozostałych kredytów.",
"purchase_credits": "Kup kredyty",
"credits_required_tooltip": "Ta akcja wymaga kredytów, aby kontynuować",
"api_key_name_too_long": "Nazwa nie może przekraczać {{max}} znaków",
"auto_charge_for_last_minute_cancellation": "Automatyczne naliczanie opłaty za niepojawienie się w przypadku odwołania w ostatniej chwili",
"before_scheduled_start_time": "Przed zaplanowanym czasem rozpoczęcia",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Número de telefone (para nosso agente ligar)",
"buy_a_phone_number_or_import_one_you_already_have": "Compre um número de telefone ou importe um que você já tenha na Twilio",
"test_agent": "Testar agente",
"test_web_call": "Testar chamada web",
"phone_numbers": "Números de telefone",
"unsubscribe": "Cancelar inscrição",
"delete_workflow_step": "Excluir etapa do fluxo de trabalho",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplicativo removido com sucesso",
"error_removing_app": "Erro ao remover o aplicativo",
"web_conference": "Conferência via web",
"web_call": "Chamada web",
"start_web_call": "Iniciar chamada web",
"end_call": "Encerrar chamada",
"test_your_agent_with_web_call": "Teste seu agente com uma chamada web sem precisar de um número de telefone",
"phone_call": "Chamada telefônica",
"connecting_to_agent": "Conectando ao agente...",
"call_active": "Chamada ativa",
"call_ended": "Chamada encerrada",
"call_error": "Erro na chamada",
"ready_to_start_call": "Pronto para iniciar a chamada",
"failed_to_start_web_call_try_again": "Falha ao iniciar a chamada web. Por favor, tente novamente.",
"call_encountered_error_try_again": "A chamada encontrou um erro. Por favor, tente novamente.",
"failed_initialize_web_call_microphone_permissions": "Falha ao inicializar a chamada web. Verifique se as permissões do microfone estão concedidas.",
"start_call_to_see_conversation": "Inicie uma chamada para ver a conversa",
"waiting_for_conversation": "Aguardando conversa...",
"ai_agent": "Agente de IA",
"live": "Ao vivo",
"connecting": "Conectando...",
"requires_confirmation": "Requer confirmação",
"requires_confirmation_threshold": "Requer confirmação caso a reserva seja feita com menos de {{time}} $t({{unit}}_timeUnit) de antecedência",
"may_require_confirmation": "Pode exigir confirmação",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "As transcrições foram habilitadas",
"transcription_stopped": "As transcrições foram desabilitadas",
"download_transcript": "Baixar Transcrição",
"transcript": "Transcrição",
"recording_from_your_recent_call": "Uma gravação da sua chamada recente no {{appName}} está pronta para ser baixada",
"transcript_from_previous_call": "As transcrições da sua ligação recente no {{appName}} já estão prontas para serem baixadas. Os links são válidos por apenas 1 hora",
"you_can_download_transcript_from_attachments": "Você também pode baixar a transcrição dos anexos",
@@ -3622,6 +3642,11 @@
"send_another_message": "Enviar outra mensagem",
"sending": "Enviando",
"no_variables_found": "Nenhuma variável encontrada",
"credits_required": "Créditos necessários",
"web_call_credits_info": "Chamadas web consomem créditos. Certifique-se de ter créditos suficientes para iniciar uma chamada.",
"web_call_no_credits": "Chamadas web requerem créditos. Você tem 0 créditos restantes.",
"purchase_credits": "Comprar créditos",
"credits_required_tooltip": "Esta ação requer créditos para prosseguir",
"api_key_name_too_long": "O nome não deve exceder {{max}} caracteres",
"auto_charge_for_last_minute_cancellation": "Cobrar automaticamente taxa de não comparecimento para cancelamentos de última hora",
"before_scheduled_start_time": "Antes do horário de início agendado",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Número de telefone (para nosso agente ligar)",
"buy_a_phone_number_or_import_one_you_already_have": "Compre um número de telefone ou importe um que você já tenha na Twilio",
"test_agent": "Testar agente",
"test_web_call": "Testar chamada web",
"phone_numbers": "Números de telefone",
"unsubscribe": "Cancelar inscrição",
"delete_workflow_step": "Excluir etapa do fluxo de trabalho",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplicação removida com sucesso",
"error_removing_app": "Erro ao remover a aplicação",
"web_conference": "Conferência web",
"web_call": "Chamada web",
"start_web_call": "Iniciar chamada web",
"end_call": "Terminar chamada",
"test_your_agent_with_web_call": "Teste o seu agente com uma chamada web sem necessidade de número de telefone",
"phone_call": "Chamada telefónica",
"connecting_to_agent": "A conectar ao agente...",
"call_active": "Chamada ativa",
"call_ended": "Chamada terminada",
"call_error": "Erro na chamada",
"ready_to_start_call": "Pronto para iniciar a chamada",
"failed_to_start_web_call_try_again": "Falha ao iniciar a chamada web. Por favor, tente novamente.",
"call_encountered_error_try_again": "A chamada encontrou um erro. Por favor, tente novamente.",
"failed_initialize_web_call_microphone_permissions": "Falha ao inicializar a chamada web. Certifique-se de que as permissões do microfone estão concedidas.",
"start_call_to_see_conversation": "Inicie uma chamada para ver a conversa",
"waiting_for_conversation": "A aguardar pela conversa...",
"ai_agent": "Agente de IA",
"live": "Ao vivo",
"connecting": "A conectar...",
"requires_confirmation": "Requer confirmação",
"requires_confirmation_threshold": "Requer confirmação se reservado com um aviso prévio inferior a {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Pode necessitar de confirmação",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "As transcrições estão agora ativadas",
"transcription_stopped": "As transcrições estão agora desativadas",
"download_transcript": "Transferir transcrição",
"transcript": "Transcrição",
"recording_from_your_recent_call": "A gravação da sua chamada recente em {{appName}} está pronta para descarregar",
"transcript_from_previous_call": "A transcrição da sua chamada recente em {{appName}} está pronta ser transferida. As ligações são válidas apenas durante 1 hora",
"you_can_download_transcript_from_attachments": "Você também pode baixar a transcrição dos anexos",
@@ -3622,6 +3642,11 @@
"send_another_message": "Enviar outra mensagem",
"sending": "Enviando",
"no_variables_found": "Nenhuma variável encontrada",
"credits_required": "Créditos necessários",
"web_call_credits_info": "As chamadas web consomem créditos. Certifique-se de que tem créditos suficientes para iniciar uma chamada.",
"web_call_no_credits": "As chamadas web requerem créditos. Tem 0 créditos restantes.",
"purchase_credits": "Comprar créditos",
"credits_required_tooltip": "Esta ação requer créditos para prosseguir",
"api_key_name_too_long": "O nome não deve exceder {{max}} caracteres",
"auto_charge_for_last_minute_cancellation": "Cobrar automaticamente taxa de não comparecimento para cancelamentos de última hora",
"before_scheduled_start_time": "Antes do horário de início agendado",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Număr de telefon (pentru ca agentul nostru să sune)",
"buy_a_phone_number_or_import_one_you_already_have": "Cumpărați un număr de telefon sau importați unul pe care îl aveți deja pe Twilio",
"test_agent": "Testați agentul",
"test_web_call": "Testează apelul web",
"phone_numbers": "Numere de telefon",
"unsubscribe": "Dezabonați-vă",
"delete_workflow_step": "Ștergeți pasul fluxului de lucru",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplicație ștearsă cu succes",
"error_removing_app": "Eroare la ștergerea aplicației",
"web_conference": "Conferință web",
"web_call": "Apel web",
"start_web_call": "Pornește apelul web",
"end_call": "Încheie apelul",
"test_your_agent_with_web_call": "Testează agentul tău cu un apel web fără a necesita un număr de telefon",
"phone_call": "Apel telefonic",
"connecting_to_agent": "Se conectează la agent...",
"call_active": "Apel activ",
"call_ended": "Apel încheiat",
"call_error": "Eroare apel",
"ready_to_start_call": "Pregătit pentru a începe apelul",
"failed_to_start_web_call_try_again": "Nu s-a putut porni apelul web. Vă rugăm să încercați din nou.",
"call_encountered_error_try_again": "Apelul a întâmpinat o eroare. Vă rugăm să încercați din nou.",
"failed_initialize_web_call_microphone_permissions": "Nu s-a putut inițializa apelul web. Asigurați-vă că permisiunile pentru microfon sunt acordate.",
"start_call_to_see_conversation": "Porniți un apel pentru a vedea conversația",
"waiting_for_conversation": "Se așteaptă conversația...",
"ai_agent": "Agent AI",
"live": "În direct",
"connecting": "Se conectează...",
"requires_confirmation": "Necesită confirmare",
"requires_confirmation_threshold": "Necesită confirmare dacă este rezervat cu aviz de < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Poate necesita confirmare",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transcrierile sunt acum activate",
"transcription_stopped": "Transcrierile sunt acum oprite",
"download_transcript": "Descarcă transcrierea",
"transcript": "Transcriere",
"recording_from_your_recent_call": "O înregistrare a apelului dvs. recent de pe {{appName}} poate fi acum descărcată",
"transcript_from_previous_call": "Transcrierea de la apelul recent pe {{appName}} este gata pentru descărcare. Linkurile sunt valabile doar pentru 1 oră",
"you_can_download_transcript_from_attachments": "Puteți descărca transcrierea din atașamente",
@@ -3622,6 +3642,11 @@
"send_another_message": "Trimiteți un alt mesaj",
"sending": "Se trimite",
"no_variables_found": "Nu s-au găsit variabile",
"credits_required": "Credite necesare",
"web_call_credits_info": "Apelurile web consumă credite. Asigurați-vă că aveți suficiente credite pentru a începe un apel.",
"web_call_no_credits": "Apelurile web necesită credite. Aveți 0 credite rămase.",
"purchase_credits": "Cumpără credite",
"credits_required_tooltip": "Această acțiune necesită credite pentru a continua",
"api_key_name_too_long": "Numele nu trebuie să depășească {{max}} caractere",
"auto_charge_for_last_minute_cancellation": "Taxare automată a taxei de neprezentare pentru anulările de ultim moment",
"before_scheduled_start_time": "Înainte de ora programată de început",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Номер телефона (для звонка нашего агента)",
"buy_a_phone_number_or_import_one_you_already_have": "Купите номер телефона или импортируйте уже имеющийся у вас в Twilio",
"test_agent": "Тестовый агент",
"test_web_call": "Тестовый веб-звонок",
"phone_numbers": "Номера телефонов",
"unsubscribe": "Отписаться",
"delete_workflow_step": "Удалить шаг рабочего процесса",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Приложение успешно удалено",
"error_removing_app": "Ошибка при удалении приложения",
"web_conference": "Веб-конференция",
"web_call": "Веб-звонок",
"start_web_call": "Начать веб-звонок",
"end_call": "Завершить звонок",
"test_your_agent_with_web_call": "Протестируйте вашего агента с помощью веб-звонка без необходимости указывать номер телефона",
"phone_call": "Телефонный звонок",
"connecting_to_agent": "Подключение к агенту...",
"call_active": "Звонок активен",
"call_ended": "Звонок завершён",
"call_error": "Ошибка звонка",
"ready_to_start_call": "Готовы начать звонок",
"failed_to_start_web_call_try_again": "Не удалось начать веб-звонок. Пожалуйста, попробуйте ещё раз.",
"call_encountered_error_try_again": "Во время звонка произошла ошибка. Пожалуйста, попробуйте ещё раз.",
"failed_initialize_web_call_microphone_permissions": "Не удалось инициализировать веб-звонок. Убедитесь, что разрешения на использование микрофона предоставлены.",
"start_call_to_see_conversation": "Начните звонок, чтобы увидеть разговор",
"waiting_for_conversation": "Ожидание разговора...",
"ai_agent": "ИИ-агент",
"live": "В реальном времени",
"connecting": "Подключение...",
"requires_confirmation": "Требуется подтверждение",
"requires_confirmation_threshold": "Требуется подтверждение, если бронирование оформляется с уведомлением < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Может потребоваться подтверждение",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Транскрипции теперь включены",
"transcription_stopped": "Транскрипции теперь остановлены",
"download_transcript": "Скачать транскрипт",
"transcript": "Транскрипт",
"recording_from_your_recent_call": "Запись вашего недавнего созвона в {{appName}} готова для скачивания",
"transcript_from_previous_call": "Транскрипт вашего недавнего звонка в {{appName}} готов к скачиванию. Ссылки действительны только 1 час",
"you_can_download_transcript_from_attachments": "Вы также можете скачать расшифровку из вложений",
@@ -3622,6 +3642,11 @@
"send_another_message": "Отправить ещё одно сообщение",
"sending": "Отправка",
"no_variables_found": "Переменные не найдены",
"credits_required": "Требуются кредиты",
"web_call_credits_info": "Веб-звонки расходуют кредиты. Убедитесь, что у вас достаточно кредитов для начала звонка.",
"web_call_no_credits": "Для веб-звонков требуются кредиты. У вас осталось 0 кредитов.",
"purchase_credits": "Купить кредиты",
"credits_required_tooltip": "Для выполнения этого действия требуются кредиты.",
"api_key_name_too_long": "Имя не должно превышать {{max}} символов",
"auto_charge_for_last_minute_cancellation": "Автоматическое списание платы за неявку при отмене в последний момент",
"before_scheduled_start_time": "До запланированного времени начала",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefónne číslo (pre volanie nášho agenta)",
"buy_a_phone_number_or_import_one_you_already_have": "Kúpte si telefónne číslo alebo importujte to, ktoré už máte na twilio",
"test_agent": "Testovať agenta",
"test_web_call": "Testovací webový hovor",
"phone_numbers": "Telefónne čísla",
"unsubscribe": "Odhlásiť",
"delete_workflow_step": "Odstrániť krok workflow",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplikácia bola úspešne odstránená",
"error_removing_app": "Chyba pri odstraňovaní aplikácie",
"web_conference": "Webová konferencia",
"web_call": "Webový hovor",
"start_web_call": "Začať webový hovor",
"end_call": "Ukončiť hovor",
"test_your_agent_with_web_call": "Otestujte svojho agenta webovým hovorom bez potreby telefónneho čísla",
"phone_call": "Telefónny hovor",
"connecting_to_agent": "Pripájanie k agentovi...",
"call_active": "Hovor je aktívny",
"call_ended": "Hovor ukončený",
"call_error": "Chyba hovoru",
"ready_to_start_call": "Pripravené na začatie hovoru",
"failed_to_start_web_call_try_again": "Nepodarilo sa začať webový hovor. Skúste to znova.",
"call_encountered_error_try_again": "Počas hovoru sa vyskytla chyba. Skúste to znova.",
"failed_initialize_web_call_microphone_permissions": "Nepodarilo sa inicializovať webový hovor. Uistite sa, že sú povolené oprávnenia pre mikrofón.",
"start_call_to_see_conversation": "Začnite hovor pre zobrazenie konverzácie",
"waiting_for_conversation": "Čakanie na konverzáciu...",
"ai_agent": "AI Agent",
"live": "Naživo",
"connecting": "Pripájanie...",
"requires_confirmation": "Vyžaduje potvrdenie",
"requires_confirmation_threshold": "Vyžaduje potvrdenie, ak je rezervované s menej ako {{time}} $t({{unit}}_timeUnit) upozornením",
"may_require_confirmation": "Môže vyžadovať potvrdenie",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Prepisy sú teraz povolené",
"transcription_stopped": "Prepisy sú teraz zastavené",
"download_transcript": "Stiahnuť prepis",
"transcript": "Prepis",
"recording_from_your_recent_call": "Nahrávka z vášho nedávneho hovoru v {{appName}} je pripravená na stiahnutie",
"transcript_from_previous_call": "Prepis z vášho nedávneho hovoru v {{appName}} je pripravený na stiahnutie. Odkazy sú platné len 1 hodinu",
"you_can_download_transcript_from_attachments": "Prepis si môžete stiahnuť aj z príloh",
@@ -3622,6 +3642,11 @@
"send_another_message": "Poslať ďalšiu správu",
"sending": "Odosielanie",
"no_variables_found": "Neboli nájdené žiadne premenné",
"credits_required": "Vyžadované kredity",
"web_call_credits_info": "Webové hovory spotrebúvajú kredity. Uistite sa, že máte dostatok kreditov na začatie hovoru.",
"web_call_no_credits": "Webové hovory vyžadujú kredity. Zostáva vám 0 kreditov.",
"purchase_credits": "Kúpiť kredity",
"credits_required_tooltip": "Táto akcia vyžaduje kredity na pokračovanie",
"api_key_name_too_long": "Názov nesmie presiahnuť {{max}} znakov",
"auto_charge_for_last_minute_cancellation": "Automaticky účtovať poplatok za neúčasť pri zrušení na poslednú chvíľu",
"before_scheduled_start_time": "Pred plánovaným časom začiatku",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Broj telefona (koji će naš agent pozvati)",
"buy_a_phone_number_or_import_one_you_already_have": "Kupite broj telefona ili uvezite onaj koji već imate na twilio",
"test_agent": "Testiraj agenta",
"test_web_call": "Test veb poziva",
"phone_numbers": "Brojevi telefona",
"unsubscribe": "Odjavi se",
"delete_workflow_step": "Obriši korak toka rada",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Aplikacija je uspešno uklonjena",
"error_removing_app": "Greška prilikom uklanjanja aplikacije",
"web_conference": "Veb konferencija",
"web_call": "Veb poziv",
"start_web_call": "Započni veb poziv",
"end_call": "Završi poziv",
"test_your_agent_with_web_call": "Testirajte svog agenta putem veb poziva bez potrebe za brojem telefona",
"phone_call": "Telefonski poziv",
"connecting_to_agent": "Povezivanje sa agentom...",
"call_active": "Poziv aktivan",
"call_ended": "Poziv završen",
"call_error": "Greška u pozivu",
"ready_to_start_call": "Spremni za početak poziva",
"failed_to_start_web_call_try_again": "Neuspelo pokretanje veb poziva. Pokušajte ponovo.",
"call_encountered_error_try_again": "Došlo je do greške tokom poziva. Pokušajte ponovo.",
"failed_initialize_web_call_microphone_permissions": "Neuspela inicijalizacija veb poziva. Proverite da li su dozvole za mikrofon odobrene.",
"start_call_to_see_conversation": "Započnite poziv da biste videli razgovor",
"waiting_for_conversation": "Čekanje na razgovor...",
"ai_agent": "AI agent",
"live": "Uživo",
"connecting": "Povezivanje...",
"requires_confirmation": "Potrebna je potvrda",
"requires_confirmation_threshold": "Zahteva potvrdu ako je rezervisano sa rokom < {{time}}$t({{unit}}_timeUnit)",
"may_require_confirmation": "Može da zahteva potvrdu",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkripcije su sada omogućene",
"transcription_stopped": "Transkripcije su sada zaustavljene",
"download_transcript": "Preuzmi transkript",
"transcript": "Transkript",
"recording_from_your_recent_call": "Snimak vašeg nedavnog poziva na {{appName}} je spreman za preuzimanje",
"transcript_from_previous_call": "Transkript sa vašeg nedavnog poziva na {{appName}} je spreman za preuzimanje. Linkovi su važeći samo 1 sat",
"you_can_download_transcript_from_attachments": "Takođe možete preuzeti transkript iz priloga",
@@ -3622,6 +3642,11 @@
"send_another_message": "Pošalji još jednu poruku",
"sending": "Slanje",
"no_variables_found": "Nisu pronađene promenljive",
"credits_required": "Potrebni krediti",
"web_call_credits_info": "Veb pozivi troše kredite. Proverite da li imate dovoljno kredita za započinjanje poziva.",
"web_call_no_credits": "Veb pozivi zahtevaju kredite. Imate 0 preostalih kredita.",
"purchase_credits": "Kupite kredite",
"credits_required_tooltip": "Ova akcija zahteva kredite za nastavak",
"api_key_name_too_long": "Ime ne sme biti duže od {{max}} karaktera",
"auto_charge_for_last_minute_cancellation": "Auto naplata naknade za nedolazak kod otkazivanja u poslednjem trenutku",
"before_scheduled_start_time": "Pre zakazanog vremena početka",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefonnummer (för vår agent att ringa)",
"buy_a_phone_number_or_import_one_you_already_have": "Köp ett telefonnummer eller importera ett du redan har på Twilio",
"test_agent": "Testa agent",
"test_web_call": "Testa webbsamtal",
"phone_numbers": "Telefonnummer",
"unsubscribe": "Avprenumerera",
"delete_workflow_step": "Ta bort arbetsflödessteg",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Appen har tagits bort",
"error_removing_app": "Det gick inte att ta bort app",
"web_conference": "Webbkonferens",
"web_call": "Webbsamtal",
"start_web_call": "Starta webbsamtal",
"end_call": "Avsluta samtal",
"test_your_agent_with_web_call": "Testa din agent med ett webbsamtal utan att behöva ett telefonnummer",
"phone_call": "Telefonsamtal",
"connecting_to_agent": "Ansluter till agent...",
"call_active": "Samtal aktivt",
"call_ended": "Samtal avslutat",
"call_error": "Samtalsfel",
"ready_to_start_call": "Redo att starta samtal",
"failed_to_start_web_call_try_again": "Kunde inte starta webbsamtal. Försök igen.",
"call_encountered_error_try_again": "Samtalet stötte på ett fel. Försök igen.",
"failed_initialize_web_call_microphone_permissions": "Kunde inte initiera webbsamtal. Kontrollera att mikrofontillstånd är beviljade.",
"start_call_to_see_conversation": "Starta ett samtal för att se konversationen",
"waiting_for_conversation": "Väntar på konversation...",
"ai_agent": "AI-agent",
"live": "Live",
"connecting": "Ansluter...",
"requires_confirmation": "Kräver bekräftelse",
"requires_confirmation_threshold": "Kräver bekräftelse om bokad med < {{time}} $t({{unit}}_timeUnit) förvarning",
"may_require_confirmation": "Kan kräva bekräftelse",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkriptioner är nu aktiverade",
"transcription_stopped": "Transkriptioner är nu stoppade",
"download_transcript": "Ladda ner transkript",
"transcript": "Transkript",
"recording_from_your_recent_call": "En inspelning från ditt senaste samtal om {{appName}} kan laddas ned",
"transcript_from_previous_call": "Transkript från ditt senaste samtal på {{appName}} är redo att laddas ner. Länkar är endast giltiga i 1 timme",
"you_can_download_transcript_from_attachments": "Du kan också ladda ner transkriptionen från bilagorna",
@@ -3622,6 +3642,11 @@
"send_another_message": "Skicka ett annat meddelande",
"sending": "Skickar",
"no_variables_found": "Inga variabler hittades",
"credits_required": "Krediter krävs",
"web_call_credits_info": "Webbsamtal förbrukar krediter. Se till att du har tillräckligt med krediter för att starta ett samtal.",
"web_call_no_credits": "Webbsamtal kräver krediter. Du har 0 krediter kvar.",
"purchase_credits": "Köp krediter",
"credits_required_tooltip": "Denna åtgärd kräver krediter för att fortsätta",
"api_key_name_too_long": "Namnet får inte överstiga {{max}} tecken",
"auto_charge_for_last_minute_cancellation": "Automatisk debitering av uteblivandeavgift vid sena avbokningar",
"before_scheduled_start_time": "Före schemalagd starttid",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Telefon numarası (ajanımızın arayacağı)",
"buy_a_phone_number_or_import_one_you_already_have": "Bir telefon numarası satın alın veya Twilio'da zaten sahip olduğunuz bir numarayı içe aktarın",
"test_agent": "Test Ajanı",
"test_web_call": "Web Aramasını Test Et",
"phone_numbers": "Telefon Numaraları",
"unsubscribe": "Abonelikten Çık",
"delete_workflow_step": "İş Akışı Adımını Sil",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Uygulama başarıyla kaldırıldı",
"error_removing_app": "Uygulama kaldırılırken bir hata oluştu",
"web_conference": "Web konferansı",
"web_call": "Web araması",
"start_web_call": "Web aramasını başlat",
"end_call": "Aramayı sonlandır",
"test_your_agent_with_web_call": "Telefon numarası gerektirmeden web araması ile ajanınızı test edin",
"phone_call": "Telefon araması",
"connecting_to_agent": "Ajana bağlanıyor...",
"call_active": "Arama aktif",
"call_ended": "Arama sonlandı",
"call_error": "Arama hatası",
"ready_to_start_call": "Aramayı başlatmaya hazır",
"failed_to_start_web_call_try_again": "Web araması başlatılamadı. Lütfen tekrar deneyin.",
"call_encountered_error_try_again": "Arama sırasında bir hata oluştu. Lütfen tekrar deneyin.",
"failed_initialize_web_call_microphone_permissions": "Web araması başlatılamadı. Lütfen mikrofon izinlerinin verildiğinden emin olun.",
"start_call_to_see_conversation": "Konuşmayı görmek için bir arama başlatın",
"waiting_for_conversation": "Konuşma bekleniyor...",
"ai_agent": "Yapay Zeka Ajanı",
"live": "Canlı",
"connecting": "Bağlanıyor...",
"requires_confirmation": "Onay gerekli",
"requires_confirmation_threshold": "{{time}} $t({{unit}}_timeUnit) bildiriminden daha az süre ile rezervasyon yapıldığında onay gerekir",
"may_require_confirmation": "Onay gerektirebilir",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Transkripsiyonlar şimdi etkinleştirildi",
"transcription_stopped": "Transkripsiyonlar şimdi durduruldu",
"download_transcript": "Transkripti İndir",
"transcript": "Transkript",
"recording_from_your_recent_call": "{{appName}} uygulamasındaki son aramanızın kaydı indirilmeye hazır",
"transcript_from_previous_call": "{{appName}} üzerindeki son görüşmenizin transkripti indirilmeye hazır. Bağlantılar sadece 1 saat geçerlidir.",
"you_can_download_transcript_from_attachments": "Transkripti eklerden de indirebilirsiniz",
@@ -3622,6 +3642,11 @@
"send_another_message": "Başka bir mesaj gönder",
"sending": "Gönderiliyor",
"no_variables_found": "Değişken bulunamadı",
"credits_required": "Kredi Gerekli",
"web_call_credits_info": "Web aramaları kredi tüketir. Arama başlatmak için yeterli krediniz olduğundan emin olun.",
"web_call_no_credits": "Web aramaları kredi gerektirir. 0 krediniz kaldı.",
"purchase_credits": "Kredi Satın Al",
"credits_required_tooltip": "Bu işlem için kredi gereklidir",
"api_key_name_too_long": "İsim {{max}} karakteri geçmemelidir",
"auto_charge_for_last_minute_cancellation": "Son dakika iptalleri için otomatik olarak no-show ücreti tahsil et",
"before_scheduled_start_time": "Planlanan başlangıç zamanından önce",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Номер телефону (для дзвінка нашого агента)",
"buy_a_phone_number_or_import_one_you_already_have": "Купіть номер телефону або імпортуйте той, який у вас вже є в twilio",
"test_agent": "Тестовий агент",
"test_web_call": "Тестовий веб-дзвінок",
"phone_numbers": "Номери телефонів",
"unsubscribe": "Відписатися",
"delete_workflow_step": "Видалити крок робочого процесу",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Додаток вилучено",
"error_removing_app": "Не вдалося вилучити додаток",
"web_conference": "Вебконференція",
"web_call": "Веб-дзвінок",
"start_web_call": "Почати веб-дзвінок",
"end_call": "Завершити дзвінок",
"test_your_agent_with_web_call": "Протестуйте вашого агента за допомогою веб-дзвінка без потреби вказувати номер телефону",
"phone_call": "Телефонний дзвінок",
"connecting_to_agent": "З'єднання з агентом...",
"call_active": "Дзвінок активний",
"call_ended": "Дзвінок завершено",
"call_error": "Помилка дзвінка",
"ready_to_start_call": "Готовий почати дзвінок",
"failed_to_start_web_call_try_again": "Не вдалося розпочати веб-дзвінок. Будь ласка, спробуйте знову.",
"call_encountered_error_try_again": "Під час дзвінка сталася помилка. Будь ласка, спробуйте знову.",
"failed_initialize_web_call_microphone_permissions": "Не вдалося ініціалізувати веб-дзвінок. Переконайтеся, що надано дозвіл на використання мікрофона.",
"start_call_to_see_conversation": "Почніть дзвінок, щоб побачити розмову",
"waiting_for_conversation": "Очікування розмови...",
"ai_agent": "ШІ-агент",
"live": "Наживо",
"connecting": "З'єднання...",
"requires_confirmation": "Потрібне підтвердження",
"requires_confirmation_threshold": "Вимагати підтвердження, якщо бронювання створено менше ніж за {{time}} $t({{unit}}_timeUnit) до заходу",
"may_require_confirmation": "Може вимагати підтвердження",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Транскрипції тепер увімкнені",
"transcription_stopped": "Транскрипції зупинені",
"download_transcript": "Завантажити транскрипт",
"transcript": "Транскрипт",
"recording_from_your_recent_call": "Запис вашого останнього дзвінка в застосунку {{appName}} готовий до завантаження",
"transcript_from_previous_call": "Транскрипт вашого недавнього дзвінка на {{appName}} готовий до завантаження. Посилання дійсні лише протягом 1 години",
"you_can_download_transcript_from_attachments": "Ви також можете завантажити розшифровку з вкладень",
@@ -3622,6 +3642,11 @@
"send_another_message": "Надіслати ще одне повідомлення",
"sending": "Надсилання",
"no_variables_found": "Змінних не знайдено",
"credits_required": "Потрібні кредити",
"web_call_credits_info": "Веб-дзвінки споживають кредити. Переконайтеся, що у вас достатньо кредитів для початку дзвінка.",
"web_call_no_credits": "Веб-дзвінки потребують кредитів. У вас залишилося 0 кредитів.",
"purchase_credits": "Придбати кредити",
"credits_required_tooltip": "Ця дія потребує кредитів для продовження",
"api_key_name_too_long": "Назва не повинна перевищувати {{max}} символів",
"auto_charge_for_last_minute_cancellation": "Автоматично стягувати плату за неявку при скасуванні в останню хвилину",
"before_scheduled_start_time": "До запланованого часу початку",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "Số điện thoại (để tác nhân của chúng tôi gọi)",
"buy_a_phone_number_or_import_one_you_already_have": "Mua số điện thoại mới hoặc nhập số bạn đã có trên twilio",
"test_agent": "Kiểm tra tác nhân",
"test_web_call": "Kiểm tra cuộc gọi web",
"phone_numbers": "Số điện thoại",
"unsubscribe": "Hủy đăng ký",
"delete_workflow_step": "Xóa bước quy trình làm việc",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "Ứng dụng được gỡ bỏ thành công",
"error_removing_app": "Lỗi khi gỡ bỏ ứng dụng",
"web_conference": "Hội nghị web",
"web_call": "Cuộc gọi web",
"start_web_call": "Bắt đầu cuộc gọi web",
"end_call": "Kết thúc cuộc gọi",
"test_your_agent_with_web_call": "Kiểm tra tác nhân của bạn bằng cuộc gọi web mà không cần số điện thoại",
"phone_call": "Cuộc gọi điện thoại",
"connecting_to_agent": "Đang kết nối với tác nhân...",
"call_active": "Cuộc gọi đang hoạt động",
"call_ended": "Cuộc gọi đã kết thúc",
"call_error": "Lỗi cuộc gọi",
"ready_to_start_call": "Sẵn sàng bắt đầu cuộc gọi",
"failed_to_start_web_call_try_again": "Không thể bắt đầu cuộc gọi web. Vui lòng thử lại.",
"call_encountered_error_try_again": "Cuộc gọi gặp lỗi. Vui lòng thử lại.",
"failed_initialize_web_call_microphone_permissions": "Không thể khởi tạo cuộc gọi web. Vui lòng đảm bảo quyền truy cập micrô được cấp.",
"start_call_to_see_conversation": "Bắt đầu cuộc gọi để xem cuộc hội thoại",
"waiting_for_conversation": "Đang chờ cuộc hội thoại...",
"ai_agent": "Tác nhân AI",
"live": "Trực tiếp",
"connecting": "Đang kết nối...",
"requires_confirmation": "Yêu cầu xác nhận",
"requires_confirmation_threshold": "Cần xác nhận nếu đặt hẹn với thông báo trước chưa đầy {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Có thể yêu cầu xác nhận",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "Chép lời đã được bật",
"transcription_stopped": "Chép lời đã dừng",
"download_transcript": "Tải xuống bản chép lời",
"transcript": "Bản ghi",
"recording_from_your_recent_call": "Một bản ghi từ cuộc gọi gần đây trên {{appName}} đã sẵn sàng để tải xuống",
"transcript_from_previous_call": "Bản chép lời từ cuộc gọi gần đây của bạn trên {{appName}} đã sẵn sàng để tải xuống. Liên kết chỉ có hiệu lực trong 1 giờ",
"you_can_download_transcript_from_attachments": "Bạn cũng có thể tải bản ghi từ phần đính kèm",
@@ -3622,6 +3642,11 @@
"send_another_message": "Gửi tin nhắn khác",
"sending": "Đang gửi",
"no_variables_found": "Không tìm thấy biến nào",
"credits_required": "Yêu cầu tín dụng",
"web_call_credits_info": "Cuộc gọi web tiêu thụ tín dụng. Đảm bảo bạn có đủ tín dụng để bắt đầu cuộc gọi.",
"web_call_no_credits": "Cuộc gọi web yêu cầu tín dụng. Bạn còn 0 tín dụng.",
"purchase_credits": "Mua tín dụng",
"credits_required_tooltip": "Hành động này yêu cầu tín dụng để tiếp tục",
"api_key_name_too_long": "Tên không được vượt quá {{max}} ký tự",
"auto_charge_for_last_minute_cancellation": "Tự động tính phí vắng mặt cho các trường hợp hủy phút chót",
"before_scheduled_start_time": "Trước thời gian bắt đầu đã lên lịch",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "电话号码(供我们的代理拨打)",
"buy_a_phone_number_or_import_one_you_already_have": "购买一个电话号码或导入您在 Twilio 上已有的号码",
"test_agent": "测试代理",
"test_web_call": "测试网络通话",
"phone_numbers": "电话号码",
"unsubscribe": "取消订阅",
"delete_workflow_step": "删除工作流步骤",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "应用已成功删除",
"error_removing_app": "删除应用时出错",
"web_conference": "网络会议",
"web_call": "网络通话",
"start_web_call": "开始网络通话",
"end_call": "结束通话",
"test_your_agent_with_web_call": "通过网络通话测试您的代理,无需电话号码",
"phone_call": "电话通话",
"connecting_to_agent": "正在连接代理...",
"call_active": "通话进行中",
"call_ended": "通话已结束",
"call_error": "通话错误",
"ready_to_start_call": "准备开始通话",
"failed_to_start_web_call_try_again": "网络通话启动失败,请重试。",
"call_encountered_error_try_again": "通话遇到错误,请重试。",
"failed_initialize_web_call_microphone_permissions": "网络通话初始化失败,请确保已授予麦克风权限。",
"start_call_to_see_conversation": "开始通话以查看对话",
"waiting_for_conversation": "等待对话...",
"ai_agent": "AI代理",
"live": "实时",
"connecting": "正在连接...",
"requires_confirmation": "需要确认",
"requires_confirmation_threshold": "如果预约通知提前时间少于 {{time}}$t({{unit}}_timeUnit),则需要确认",
"may_require_confirmation": "可能需要确认",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "转录功能已启用",
"transcription_stopped": "转录功能已停止",
"download_transcript": "下载转录文件",
"transcript": "通话记录",
"recording_from_your_recent_call": "您最近在 {{appName}} 上的通话录制内容可供下载",
"transcript_from_previous_call": "您最近在{{appName}}上的通话转录文件已准备好下载。链接仅在1小时内有效",
"you_can_download_transcript_from_attachments": "您也可以从附件中下载会议记录",
@@ -3622,6 +3642,11 @@
"send_another_message": "发送另一条消息",
"sending": "正在发送",
"no_variables_found": "未找到变量",
"credits_required": "需要积分",
"web_call_credits_info": "网络通话会消耗积分,请确保您有足够的积分开始通话。",
"web_call_no_credits": "网络通话需要积分,您当前剩余积分为0。",
"purchase_credits": "购买积分",
"credits_required_tooltip": "此操作需要积分才能继续",
"api_key_name_too_long": "名称不得超过{{max}}个字符",
"auto_charge_for_last_minute_cancellation": "对临时取消自动收取未到场费用",
"before_scheduled_start_time": "在预定开始时间之前",
@@ -860,6 +860,7 @@
"phone_number_for_ai_call": "電話號碼(供我們的代理撥打)",
"buy_a_phone_number_or_import_one_you_already_have": "購買電話號碼或匯入您已在 Twilio 上擁有的號碼",
"test_agent": "測試代理",
"test_web_call": "測試網路通話",
"phone_numbers": "電話號碼",
"unsubscribe": "取消訂閱",
"delete_workflow_step": "刪除工作流程步驟",
@@ -1479,6 +1480,24 @@
"app_removed_successfully": "已成功移除應用程式",
"error_removing_app": "移除應用程式時發生錯誤",
"web_conference": "網路會議",
"web_call": "網路通話",
"start_web_call": "開始網路通話",
"end_call": "結束通話",
"test_your_agent_with_web_call": "使用網路通話測試您的代理,不需要電話號碼",
"phone_call": "電話通話",
"connecting_to_agent": "正在連接代理...",
"call_active": "通話進行中",
"call_ended": "通話已結束",
"call_error": "通話錯誤",
"ready_to_start_call": "準備開始通話",
"failed_to_start_web_call_try_again": "無法開始網路通話。請再試一次。",
"call_encountered_error_try_again": "通話發生錯誤。請再試一次。",
"failed_initialize_web_call_microphone_permissions": "無法初始化網路通話。請確保已授予麥克風權限。",
"start_call_to_see_conversation": "開始通話以查看對話",
"waiting_for_conversation": "正在等待對話...",
"ai_agent": "AI 代理",
"live": "直播",
"connecting": "正在連接...",
"requires_confirmation": "需要確認",
"requires_confirmation_threshold": "預約的提前通知時間短於 {{time}} $t({{unit}}_timeUnit) 時,就需要確認",
"may_require_confirmation": "可能需要確認",
@@ -1714,6 +1733,7 @@
"transcription_enabled": "現在已啟用轉錄",
"transcription_stopped": "轉錄已停止",
"download_transcript": "下載轉錄",
"transcript": "文字記錄",
"recording_from_your_recent_call": "您最近在 {{appName}} 的通話錄製內容已可供下載",
"transcript_from_previous_call": "您最近在 {{appName}} 上的通話轉錄已準備好下載。鏈接僅有效 1 小時",
"you_can_download_transcript_from_attachments": "您也可以從附件中下載會議記錄",
@@ -3622,6 +3642,11 @@
"send_another_message": "再發送一則訊息",
"sending": "正在發送",
"no_variables_found": "未找到變數",
"credits_required": "需要點數",
"web_call_credits_info": "網路通話會消耗點數。請確保您有足夠的點數來開始通話。",
"web_call_no_credits": "網路通話需要點數。您剩餘 0 點數。",
"purchase_credits": "購買點數",
"credits_required_tooltip": "此操作需要點數才能繼續",
"api_key_name_too_long": "名稱不得超過 {{max}} 個字元",
"auto_charge_for_last_minute_cancellation": "自動收取臨時取消的未出席費用",
"before_scheduled_start_time": "預定開始時間之前",
@@ -1,14 +1,35 @@
import { wrapGetServerSidePropsWithSentry } from "@sentry/nextjs";
import { isbot } from "isbot";
import type { GetServerSidePropsContext } from "next";
import { getRoutedUrl, hasEmbedPath } from "@calcom/features/routing-forms/lib/getRoutedUrl";
import logger from "@calcom/lib/logger";
import { TRPCError } from "@trpc/server";
const log = logger.getSubLogger({ prefix: ["router/getServerSideProps"] });
// Show a positive message to the bots so that they don't think that the form is broken.
const BOT_MESSAGE = "Thank you for your interest! We will be in touch soon.";
export const getServerSideProps = wrapGetServerSidePropsWithSentry(async function getServerSideProps(
context: GetServerSidePropsContext
) {
try {
// Check if the request is from a bot
const userAgent = context.req.headers["user-agent"];
if (userAgent && isbot(userAgent)) {
log.info(`[BOT] Ignoring request from user agent: ${userAgent}`);
return {
props: {
isEmbed: hasEmbedPath(context.req.url || ""),
form: null,
message: BOT_MESSAGE,
errorMessage: null,
},
};
}
return await getRoutedUrl(context);
} catch (error) {
if (error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS") {
+110
View File
@@ -0,0 +1,110 @@
import {
createBookingScenario,
getOrganizer,
getScenarioData,
TestData,
mockSuccessfulVideoMeetingCreation,
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
import { describe, it, beforeEach, vi, expect } from "vitest";
import * as handleConfirmationModule from "@calcom/features/bookings/lib/handleConfirmation";
import { BookingStatus } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { confirmHandler } from "@calcom/trpc/server/routers/viewer/bookings/confirm.handler";
describe("confirmHandler", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should pass hideCalendarNotes property to CalendarEvent when enabled", async () => {
vi.setSystemTime("2050-01-07T00:00:00Z");
const handleConfirmationSpy = vi.spyOn(handleConfirmationModule, "handleConfirmation");
const attendeeUser = getOrganizer({
email: "test@example.com",
name: "test name",
id: 102,
schedules: [TestData.schedules.IstWorkHours],
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
});
const uidOfBooking = "hideNotes123";
const iCalUID = `${uidOfBooking}@Cal.com`;
const plus1DateString = "2050-01-08";
await createBookingScenario(
getScenarioData({
eventTypes: [
{
id: 1,
slotInterval: 15,
length: 15,
locations: [],
hideCalendarNotes: true,
hideCalendarEventDetails: true,
requiresConfirmation: true,
users: [
{
id: 101,
},
],
},
],
bookings: [
{
id: 101,
uid: uidOfBooking,
eventTypeId: 1,
status: BookingStatus.PENDING,
startTime: `${plus1DateString}T05:00:00.000Z`,
endTime: `${plus1DateString}T05:15:00.000Z`,
references: [],
iCalUID,
location: "integrations:daily",
attendees: [attendeeUser],
responses: { name: attendeeUser.name, email: attendeeUser.email, notes: "Sensitive information" },
},
],
organizer,
apps: [TestData.apps["daily-video"]],
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
});
const ctx = {
user: {
id: organizer.id,
name: organizer.name,
timeZone: organizer.timeZone,
username: organizer.username,
} as NonNullable<TrpcSessionUser>,
};
const res = await confirmHandler({
ctx,
input: { bookingId: 101, confirmed: true, reason: "", emailsEnabled: true },
});
expect(res?.status).toBe(BookingStatus.ACCEPTED);
expect(handleConfirmationSpy).toHaveBeenCalledTimes(1);
const handleConfirmationCall = handleConfirmationSpy.mock.calls[0][0];
const calendarEvent = handleConfirmationCall.evt;
expect(calendarEvent.hideCalendarNotes).toBe(true);
expect(calendarEvent.hideCalendarEventDetails).toBe(true);
});
});
+25
View File
@@ -862,6 +862,7 @@ checksums:
phone_number_for_ai_call: 2c6b2fcd19cc68bc13d6f0dac0131707
buy_a_phone_number_or_import_one_you_already_have: 357cb368b71216101eca1151b8bdf581
test_agent: aa54fc4c6393391bee371fc5f746746b
test_web_call: 21b24cea70abe3d7b32794275e014203
phone_numbers: e4c9c7f7f46b5ea5c995a67a20dd6fc8
unsubscribe: d1a0b07295687d1f08d4df1c411072dd
delete_workflow_step: 374663ed46d303b4454482669e44a91c
@@ -1480,6 +1481,24 @@ checksums:
app_removed_successfully: c3a10d7f9273a570fb199f45207b0bf6
error_removing_app: 392d8006399c4f08c7ea98248a2b10cc
web_conference: 090bdbaa7e3c7c7238c4b6d70e347734
web_call: bcef1f05d0874af3c1f44f41f11bcfe6
start_web_call: 7bc4c78b429bb18613b2de6ff62eca32
end_call: c51769d96722bc21664c37f6016edb3e
test_your_agent_with_web_call: 724520878607adaf3668dc9b3b4cfc3b
phone_call: 55de8748abbe27428f4464ee2ca6c88f
connecting_to_agent: aa6b7399a6741676e210194200cd2d9d
call_active: 62c1f624a7602b623f50d1682bfaacce
call_ended: f9253363a216907f063b227b62c848da
call_error: 1d7c36f2ef46452b2838b7275f41dd14
ready_to_start_call: e6d7d16bb23f436ad389761a207d14ac
failed_to_start_web_call_try_again: e5ccd704da41c82d6bdb520cc2bce5b4
call_encountered_error_try_again: 9ed38f82d49bbb9bfff47989fe912206
failed_initialize_web_call_microphone_permissions: c285ec6a1640427b2a58f7dfe070cbcf
start_call_to_see_conversation: 2816d4693c0a8dca5300f13ae3906593
waiting_for_conversation: 0d069744dee8207287fd6d84f2740abb
ai_agent: 4901e070adf86d4c58b9b69d31c5e7f1
live: 77a9c14fc9762e0e058e9b5ae04c8d15
connecting: 0627ab04701831ecbde4135e1ef9210c
requires_confirmation: 4cae2e553af2409683efe1b1e51d8053
requires_confirmation_threshold: 01f1f50b07af4b505c3fbc7f11e66d02
may_require_confirmation: c403887dc2ed1a9cda5307c38f3849c6
@@ -1715,6 +1734,7 @@ checksums:
transcription_enabled: 0fdc203ca56b3c7c48c77c16b5366f5d
transcription_stopped: 12b193d31b9d101ccbc07f4804dbfd62
download_transcript: e6200f050dd5d0277c0dbad0cfe3b41a
transcript: 6bbda4f7939d627251d37995a1639998
recording_from_your_recent_call: b668afa4ace7a561853eff98d583532e
transcript_from_previous_call: 7485bc94e7ca0e7b2f9cefa8f5b12168
you_can_download_transcript_from_attachments: bd1f8b66fa7a44eeed9c113112a042e5
@@ -3619,6 +3639,11 @@ checksums:
send_another_message: 13dd55d64292dea6666fc42b2cd81ca5
sending: 841166a79844bd7474536877016a29f0
no_variables_found: ff245d81f9b63e74656862119685bb55
credits_required: ceb73aa98e540a7efa4fcf1e6753694e
web_call_credits_info: aee65c01a61389e6c2e73532ed5d2d24
web_call_no_credits: bdfc3bb168281d0a6ae29341d8eac6c5
purchase_credits: b1e12db2744789a7ef83c7ef4e148d7d
credits_required_tooltip: ddd87961d790a9b90cf761cab0ff8673
api_key_name_too_long: 1175b1d59bbe58c2a1b1e86de785dce8
auto_charge_for_last_minute_cancellation: 8d438d61dc249f0ca003734e4a9dac18
before_scheduled_start_time: 35bbe398965e631eaf969ee89266ee84
+2 -2
View File
@@ -91,7 +91,7 @@
"@snaplet/copycat": "^4.1.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^16.0.1",
"@vitest/ui": "^2.1.1",
"@vitest/ui": "^2.1.9",
"c8": "^7.13.0",
"checkly": "latest",
"dotenv-checker": "^1.1.5",
@@ -110,7 +110,7 @@
"resize-observer-polyfill": "^1.5.1",
"tsc-absolute": "^1.0.0",
"typescript": "5.9.0-beta",
"vitest": "^2.1.1",
"vitest": "^2.1.9",
"vitest-fetch-mock": "^0.3.0",
"vitest-mock-extended": "^2.0.2"
},
@@ -1,10 +1,9 @@
import type { LocationObject } from "@calcom/app-store/locations";
import { getAppFromSlug } from "@calcom/app-store/utils";
import type { PrismaClient } from "@calcom/prisma";
import type { User } from "@calcom/prisma/client";
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
import type { LocationObject } from "../locations";
import { getAppFromSlug } from "../utils";
export const bulkUpdateEventsToDefaultLocation = async ({
eventTypeIds,
@@ -18,19 +17,13 @@ export const bulkUpdateEventsToDefaultLocation = async ({
const defaultApp = userMetadataSchema.parse(user.metadata)?.defaultConferencingApp;
if (!defaultApp) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Default conferencing app not set",
});
throw new Error("Default conferencing app not set");
}
const foundApp = getAppFromSlug(defaultApp.appSlug);
const appType = foundApp?.appData?.location?.type;
if (!appType) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Default conferencing app '${defaultApp.appSlug}' doesnt exist.`,
});
throw new Error(`Default conferencing app '${defaultApp.appSlug}' doesnt exist.`);
}
const credential = await prisma.credential.findFirst({
@@ -1,9 +1,8 @@
import type { LocationObject } from "@calcom/app-store/locations";
import { getAppFromSlug } from "@calcom/app-store/utils";
import type { PrismaClient } from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
import type { LocationObject } from "../locations";
import { getAppFromSlug } from "../utils";
export const bulkUpdateTeamEventsToDefaultLocation = async ({
eventTypeIds,
@@ -18,22 +17,17 @@ export const bulkUpdateTeamEventsToDefaultLocation = async ({
where: { id: teamId },
select: { metadata: true },
});
const defaultApp = teamMetadataSchema.parse(team?.metadata)?.defaultConferencingApp;
const metadataResult = teamMetadataSchema.safeParse(team?.metadata);
const defaultApp = metadataResult.success ? metadataResult.data?.defaultConferencingApp : null;
if (!defaultApp) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Default conferencing app not set",
});
throw new Error("Default conferencing app not set");
}
const foundApp = getAppFromSlug(defaultApp.appSlug);
const appType = foundApp?.appData?.location?.type;
if (!appType) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Default conferencing app '${defaultApp.appSlug}' doesnt exist.`,
});
throw new Error(`Default conferencing app '${defaultApp.appSlug}' doesnt exist.`);
}
const credential = await prisma.credential.findFirst({
@@ -1,19 +1,19 @@
import type { Prisma } from "@prisma/client";
import type { TDependencyData } from "@calcom/app-store/_appRegistry";
import { PaymentServiceMap } from "@calcom/app-store/payment.services.generated";
import type { CredentialOwner } from "@calcom/app-store/types";
import { getAppFromSlug } from "@calcom/app-store/utils";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import getEnabledAppsFromCredentials from "@calcom/lib/apps/getEnabledAppsFromCredentials";
import getInstallCountPerApp from "@calcom/lib/apps/getInstallCountPerApp";
import { buildNonDelegationCredentials } from "@calcom/lib/delegationCredential/clientAndServer";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
import type { PrismaClient } from "@calcom/prisma";
import type { User } from "@calcom/prisma/client";
import type { AppCategories } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { buildNonDelegationCredentials } from "./delegationCredential/clientAndServer";
import type { TDependencyData } from "../_appRegistry";
import { PaymentServiceMap } from "../payment.services.generated";
import type { CredentialOwner } from "../types";
import { getAppFromSlug } from "../utils";
export type ConnectedApps = Awaited<ReturnType<typeof getConnectedApps>>;
type InputSchema = {
@@ -4,13 +4,12 @@ import { getGoogleMeetCredential, TestData } from "@calcom/web/test/utils/bookin
import { describe, expect, it } from "vitest";
import { DailyLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { DailyLocationType, MeetLocationType } from "../locations";
import { getDefaultLocations } from "./getDefaultLocations";
type User = {
id: number;
email?: string;
email: string;
name?: string;
metadata: {
defaultConferencingApp?: {
@@ -34,6 +33,7 @@ describe("getDefaultLocation ", async () => {
it("should return location based on user default conferencing app", async () => {
const user: User = {
id: 101,
email: "test@example.com",
metadata: {
defaultConferencingApp: {
appSlug: "google-meet",
@@ -53,6 +53,7 @@ describe("getDefaultLocation ", async () => {
it("should return calvideo when default conferencing app is not set", async () => {
const user: User = {
id: 101,
email: "test@example.com",
metadata: {},
};
await mockUser(user);
@@ -1,16 +1,16 @@
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
import { DailyLocationType } from "@calcom/app-store/constants";
import getApps from "@calcom/app-store/utils";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
import type { Prisma } from "@calcom/prisma/client";
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
import type { EventTypeLocation } from "@calcom/prisma/zod/custom/eventtype";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
type SessionUser = NonNullable<TrpcSessionUser>;
import { DailyLocationType } from "../constants";
import getApps from "../utils";
import getAppKeysFromSlug from "./getAppKeysFromSlug";
type User = {
id: SessionUser["id"];
email: SessionUser["email"];
metadata: SessionUser["metadata"];
id: number;
email: string;
metadata: Prisma.JsonValue;
};
export async function getDefaultLocations(user: User): Promise<EventTypeLocation[]> {
@@ -9,14 +9,13 @@ import { getPlatformParams } from "@calcom/features/platform-oauth-client/get-pl
import { PlatformOAuthClientRepository } from "@calcom/features/platform-oauth-client/platform-oauth-client.repository";
import EventManager, { placeholderCreatedEvent } from "@calcom/lib/EventManager";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { getBooking } from "@calcom/lib/payment/getBooking";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils";
import logger from "../logger";
const log = logger.getSubLogger({ prefix: ["[handlePaymentSuccess]"] });
export async function handlePaymentSuccess(paymentId: number, bookingId: number) {
log.debug(`handling payment success for bookingId ${bookingId}`);
+1 -1
View File
@@ -2,12 +2,12 @@ import type { NextApiRequest, NextApiResponse } from "next";
import getRawBody from "raw-body";
import { z } from "zod";
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
import { albyCredentialKeysSchema } from "@calcom/app-store/alby/lib";
import parseInvoice from "@calcom/app-store/alby/lib/parseInvoice";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
import prisma from "@calcom/prisma";
export const config = {
@@ -3,10 +3,10 @@ import type { NextApiRequest, NextApiResponse } from "next";
import getRawBody from "raw-body";
import { z } from "zod";
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
import { PrismaBookingPaymentRepository as BookingPaymentRepository } from "@calcom/lib/server/repository/PrismaBookingPaymentRepository";
import appConfig from "../config.json";
+1 -1
View File
@@ -2,10 +2,10 @@ import { createHmac } from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import type z from "zod";
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
import prisma from "@calcom/prisma";
import type { hitpayCredentialKeysSchema } from "../lib/hitpayCredentialKeysSchema";
+1 -1
View File
@@ -2,12 +2,12 @@ import type { NextApiRequest, NextApiResponse } from "next";
import getRawBody from "raw-body";
import { z } from "zod";
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
import { paypalCredentialKeysSchema } from "@calcom/app-store/paypal/lib";
import Paypal from "@calcom/app-store/paypal/lib/Paypal";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
import prisma from "@calcom/prisma";
export const config = {
+1 -1
View File
@@ -1,7 +1,7 @@
import type { ConnectedApps } from "_utils/getConnectedApps";
import type React from "react";
import type { z } from "zod";
import type { ConnectedApps } from "@calcom/lib/getConnectedApps";
import type { EventTypeFormMetadataSchema } from "@calcom/prisma/zod-utils";
import type { ButtonProps } from "@calcom/ui/components/button";
+40 -10
View File
@@ -1,4 +1,5 @@
import { render, screen, waitFor } from "@testing-library/react";
import React from "react";
import { describe, expect, it, vi, beforeEach } from "vitest";
import type { Mock } from "vitest";
@@ -49,9 +50,10 @@ const mockAttributesWithSingleSelect = () => {
slug: "department",
name: "Department",
type: AttributeType.SINGLE_SELECT,
isWeightsEnabled: false,
options: [
{ id: "1", value: "Sales", slug: "sales" },
{ id: "2", value: "Engineering", slug: "engineering" },
{ id: "1", value: "Sales", slug: "sales", contains: [], isGroup: false },
{ id: "2", value: "Engineering", slug: "engineering", contains: [], isGroup: false },
],
},
],
@@ -85,9 +87,24 @@ vi.mock("@calcom/lib/hooks/useLocale", () => ({
}));
describe("Segment", () => {
const defaultQueryValue = {
id: "root",
type: "group",
children1: {
"rule-1": {
type: "rule",
properties: {
field: "department",
operator: "select_equals",
value: ["Sales"],
},
},
},
} as AttributesQueryValue;
const defaultProps = {
teamId: 1,
queryValue: null as AttributesQueryValue | null,
queryValue: defaultQueryValue,
onQueryValueChange: vi.fn(),
className: "test-class",
};
@@ -132,7 +149,25 @@ describe("Segment", () => {
});
});
it("shows matching team members when query value is provided", async () => {
it("shows no filter selected message when empty query value is provided", async () => {
mockGetMatchingTeamMembers({
isPending: true,
data: undefined,
});
const emptyQueryValue = {
id: "root",
type: "group",
children1: {},
} as AttributesQueryValue;
render(<Segment {...defaultProps} queryValue={emptyQueryValue} />);
await waitFor(() => {
expect(screen.getByText("no_filter_set")).toBeInTheDocument();
});
});
it("shows matching team members when valid query value is provided", async () => {
mockGetMatchingTeamMembers({
isPending: false,
data: {
@@ -149,12 +184,7 @@ describe("Segment", () => {
},
});
const queryValue = {
id: "root",
type: "group",
} as AttributesQueryValue;
render(<Segment {...defaultProps} queryValue={queryValue} />);
render(<Segment {...defaultProps} queryValue={defaultQueryValue} />);
await waitFor(() => {
expect(screen.getByText("John Doe")).toBeInTheDocument();
expect(screen.getByText("(john@example.com)")).toBeInTheDocument();
+12 -11
View File
@@ -125,6 +125,16 @@ function MatchingTeamMembers({
}
);
if (!hasValidValue) {
return (
<div className="border-subtle bg-muted mt-4 space-y-3 rounded-md border p-4">
<div className="text-subtle flex items-center text-sm font-medium">
<span>{t("no_filter_set")}</span>
</div>
</div>
);
}
if (isPending) {
return (
<div
@@ -149,23 +159,14 @@ function MatchingTeamMembers({
if (!matchingTeamMembersWithResult) return <span>{t("something_went_wrong")}</span>;
const { result: matchingTeamMembers } = matchingTeamMembersWithResult;
if (!matchingTeamMembers || !queryValue) {
return (
<div className="border-subtle bg-muted mt-4 space-y-3 rounded-md border p-4">
<div className="text-subtle flex items-center text-sm font-medium">
<span>{t("no_filter_set")}</span>
</div>
</div>
);
}
return (
<div className="border-subtle bg-muted mt-4 space-y-3 rounded-md border p-4">
<div className="text-emphasis flex items-center text-sm font-medium">
<span>{t("x_matching_members", { x: matchingTeamMembers.length })}</span>
<span>{t("x_matching_members", { x: matchingTeamMembers?.length ?? 0 })}</span>
</div>
<ul className="divide-subtle divide-y">
{matchingTeamMembers.map((member) => (
{matchingTeamMembers?.map((member) => (
<li key={member.id} className="flex items-center py-2">
<div className="flex flex-1 items-center space-x-2 text-sm">
<span className="font-medium">{member.name}</span>
@@ -1,20 +0,0 @@
import { createContainer } from "@evyweb/ioctopus";
import { DI_TOKENS } from "@calcom/lib/di/tokens";
import type { RegularBookingService } from "../modules/RegularBookingServiceModule";
import {
regularBookingServiceModule,
loadModuleDeps,
moduleToken,
} from "../modules/RegularBookingServiceModule";
const regularBookingServiceContainer = createContainer();
regularBookingServiceContainer.load(DI_TOKENS.REGULAR_BOOKING_SERVICE_MODULE, regularBookingServiceModule);
export function getRegularBookingService(): RegularBookingService {
loadModuleDeps(regularBookingServiceContainer);
return regularBookingServiceContainer.get<RegularBookingService>(moduleToken);
}
@@ -1,39 +0,0 @@
import type { Container } from "@evyweb/ioctopus";
import { createModule } from "@evyweb/ioctopus";
import { bookingRepositoryModule } from "@calcom/lib/di/modules/Booking";
import { cacheModule } from "@calcom/lib/di/modules/Cache";
import { checkBookingAndDurationLimitsModule } from "@calcom/lib/di/modules/CheckBookingAndDurationLimits";
import { checkBookingLimitsModule } from "@calcom/lib/di/modules/CheckBookingLimits";
import { featuresRepositoryModule } from "@calcom/lib/di/modules/Features";
import { DI_TOKENS } from "@calcom/lib/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { RegularBookingService } from "../../handleNewBooking";
export const regularBookingServiceModule = createModule();
const moduleToken = DI_TOKENS.REGULAR_BOOKING_SERVICE;
regularBookingServiceModule.bind(moduleToken).toClass(RegularBookingService, {
cacheService: DI_TOKENS.CACHE_SERVICE,
checkBookingAndDurationLimitsService: DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE,
prismaClient: DI_TOKENS.PRISMA_CLIENT,
bookingRepository: DI_TOKENS.BOOKING_REPOSITORY,
featuresRepository: DI_TOKENS.FEATURES_REPOSITORY,
checkBookingLimitsService: DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE,
});
// Load the dependencies defined for the module above
function loadModuleDeps(container: Container) {
container.load(DI_TOKENS.CACHE_SERVICE_MODULE, cacheModule);
container.load(
DI_TOKENS.CHECK_BOOKING_AND_DURATION_LIMITS_SERVICE_MODULE,
checkBookingAndDurationLimitsModule
);
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule);
container.load(DI_TOKENS.FEATURES_REPOSITORY_MODULE, featuresRepositoryModule);
container.load(DI_TOKENS.CHECK_BOOKING_LIMITS_SERVICE_MODULE, checkBookingLimitsModule);
}
export { loadModuleDeps, moduleToken };
export type { RegularBookingService };
@@ -17,13 +17,13 @@ import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./test/getNewBookingHandler";
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
const eventLength = 30;
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -69,6 +69,7 @@ describe(
// eslint-disable-next-line playwright/no-skipped-test
test(`Booking limits per week
`, async ({}) => {
const handleNewBooking = getNewBookingHandler();
await createBookingScenario(
getScenarioData({
eventTypes: [
@@ -207,6 +208,7 @@ describe(
});
test(`Booking limits per day`, async ({}) => {
const handleNewBooking = getNewBookingHandler();
await createBookingScenario(
getScenarioData({
eventTypes: [
@@ -293,6 +295,7 @@ describe(
});
test(`Booking limits per month`, async ({}) => {
const handleNewBooking = getNewBookingHandler();
await createBookingScenario(
getScenarioData({
eventTypes: [
@@ -424,6 +427,7 @@ describe(
});
test(`Booking limits per year`, async ({}) => {
const handleNewBooking = getNewBookingHandler();
await createBookingScenario(
getScenarioData({
eventTypes: [
@@ -30,6 +30,8 @@ import { PeriodType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
@@ -49,7 +51,7 @@ describe("handleNewBooking", () => {
2. following year without bookings: should create a booking in the database
`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -167,7 +169,7 @@ describe("handleNewBooking", () => {
2. following year without bookings: should create a booking in the database
`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -296,7 +298,7 @@ describe("handleNewBooking", () => {
test.skipIf([todayDate, tomorrowDate].includes("01"))(
`should fail a booking if exceeds booking limits with bookings in the past`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -377,7 +379,7 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if exceeds booking limits with bookings in week across two months`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -466,7 +468,7 @@ describe("handleNewBooking", () => {
describe("Buffers", () => {
test("should throw error when booking is not respecting buffers with event types that have before and after buffer ", async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -560,7 +562,7 @@ describe("handleNewBooking", () => {
test(`should throw error when booking is within a before event buffer of an existing booking
`, async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -627,7 +629,7 @@ describe("handleNewBooking", () => {
});
test(`should throw error when booking is within a after event buffer of an existing booking
`, async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -695,7 +697,7 @@ describe("handleNewBooking", () => {
test(
`should fail booking if the start date is in the past`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -770,7 +772,7 @@ describe("handleNewBooking", () => {
async () => {
// In IST it is 2024-05-22 12:39am
vi.setSystemTime("2024-05-21T19:09:13Z");
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -28,6 +28,8 @@ import dayjs from "@calcom/dayjs";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
export const Timezones = {
"-05:00": "America/New_York",
"00:00": "Europe/London",
@@ -48,7 +50,7 @@ describe("handleNewBooking", () => {
async ({ emails }) => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const newYorkTimeZone = Timezones["-05:00"];
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -28,6 +28,8 @@ import dayjs from "@calcom/dayjs";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
export const Timezones = {
"-05:00": "America/New_York",
"00:00": "Europe/London",
@@ -48,7 +50,7 @@ describe("handleNewBooking", () => {
async ({ emails }) => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const newYorkTimeZone = Timezones["-05:00"];
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -210,7 +212,7 @@ describe("handleNewBooking", () => {
async ({ emails }) => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const newYorkTimeZone = Timezones["-05:00"];
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -45,6 +45,8 @@ import { BookingStatus } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
vi.mock("@calcom/app-store/calendar.services.generated", () => {
class MockGoogleCalendarService {
credential: any;
@@ -160,7 +162,7 @@ describe("handleNewBooking", () => {
3. Should use Google Meet as the location even when not explicitly set.
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -339,7 +341,7 @@ describe("handleNewBooking", () => {
3. Should use Google Meet as the location even when not explicitly set and no destination calendar is set for User/EventType
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -516,7 +518,7 @@ describe("handleNewBooking", () => {
2. Should create an event in Outlook calendar with Delegation credential
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -709,7 +711,7 @@ describe("handleNewBooking", () => {
test(
`should fail calendar event creation when organizer isn't part of the organization of DelegationCredential Credential`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -878,7 +880,7 @@ describe("handleNewBooking", () => {
3. Should use Google Meet as the location even when not explicitly set.
`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -1031,7 +1033,7 @@ describe("handleNewBooking", () => {
test(
`should use Cal Video as the location if that is the default conferencing app set by the user. It must not use Google Meet coming from Delegation credential.`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
@@ -29,6 +29,8 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
@@ -41,7 +43,7 @@ describe("handleNewBooking", () => {
test(
`should allow a booking if there is no conflicting booking in any of the users' selectedCalendars`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const groupUserId1 = 101;
const groupUserId2 = 102;
const booker = getBooker({
@@ -137,7 +139,7 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if there is already a conflicting booking in the first user's selectedCalendars`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const groupUserId1 = 101;
const groupUserId2 = 102;
const booker = getBooker({
@@ -217,7 +219,7 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if there is already a conflicting booking in the second user's selectedCalendars`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const groupUserId1 = 101;
const groupUserId2 = 102;
const booker = getBooker({
@@ -63,6 +63,8 @@ import { CreationSource, WatchlistSeverity, WatchlistType } from "@calcom/prisma
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
@@ -81,7 +83,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_CREATED webhook
`,
async ({ emails, org }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -262,7 +264,7 @@ describe("handleNewBooking", () => {
3. Should trigger BOOKING_CREATED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -422,7 +424,7 @@ describe("handleNewBooking", () => {
3. Should trigger BOOKING_CREATED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -577,7 +579,7 @@ describe("handleNewBooking", () => {
test(
`an error in creating a calendar event should not stop the booking creation - Current behaviour is wrong as the booking is created but no-one is notified of it`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -698,7 +700,7 @@ describe("handleNewBooking", () => {
test(
"If destination calendar has no credential ID due to some reason, it should create the event in first connected calendar instead",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -864,7 +866,7 @@ describe("handleNewBooking", () => {
test(
"If destination calendar is there for Google Calendar but there are no Google Calendar credentials but there is an Apple Calendar credential connected, it should create the event in Apple Calendar",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1021,7 +1023,7 @@ describe("handleNewBooking", () => {
test(
`should create a successful booking with right location app when event has location option as video client`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1114,7 +1116,7 @@ describe("handleNewBooking", () => {
`,
//test with inPerson event type
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1197,7 +1199,7 @@ describe("handleNewBooking", () => {
test(
`should create a successful booking with organizer default conferencing app when event's location is not set`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1291,7 +1293,7 @@ describe("handleNewBooking", () => {
test(
`should create a successful booking with Zoom if used`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -1379,7 +1381,7 @@ describe("handleNewBooking", () => {
test(
`Booking should still be created using calvideo, if error occurs with zoom`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -1473,7 +1475,7 @@ describe("handleNewBooking", () => {
test(
`should fail if the time difference between a booking's start and end times is not equal to the event length.`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -1533,7 +1535,7 @@ describe("handleNewBooking", () => {
test(
"should create a booking with UTM tracking",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1618,7 +1620,7 @@ describe("handleNewBooking", () => {
test(
`should create a booking with creation source as WEBAPP`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1699,7 +1701,7 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if there is already a Cal.com booking overlapping the time`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -1771,7 +1773,7 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if there is already a booking in the organizer's selectedCalendars(Single Calendar) with the overlapping time`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const organizerId = 101;
const booker = getBooker({
email: "booker@example.com",
@@ -1856,7 +1858,7 @@ describe("handleNewBooking", () => {
test(
`should allow a booking even if there is already a booking in the organizer's selectedCalendars(Single Calendar) with the overlapping time but useEventLevelSelectedCalendars is false and SelectedCalendar is for an event`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const organizerId = 101;
const eventTypeId = 1;
const useEventLevelSelectedCalendars = false;
@@ -1940,8 +1942,8 @@ describe("handleNewBooking", () => {
test(
`should fail a booking if there is already a booking in the organizer's selectedCalendars(Single Calendar) with the overlapping time as chosen in the event type`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking"))
.default;
const handleNewBooking = getNewBookingHandler();
const organizerId = 101;
const eventTypeId = 1;
const useEventLevelSelectedCalendars = true;
@@ -2020,8 +2022,8 @@ describe("handleNewBooking", () => {
test(
`should allow a booking even if there is already a booking in the organizer's selectedCalendars(Single Calendar) with the overlapping time and useEventLevelSelectedCalendars is true but the selectedCalendar is of different eventType`,
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking"))
.default;
const handleNewBooking = getNewBookingHandler();
const organizerId = 101;
const eventTypeId = 1;
const anotherEventTypeId = 2;
@@ -2118,7 +2120,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_REQUESTED workflow
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -2255,7 +2257,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_REQUESTED workflow
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -2381,7 +2383,7 @@ describe("handleNewBooking", () => {
3. Should trigger BOOKING_CREATED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2517,7 +2519,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_REQUESTED workflows
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -2647,7 +2649,7 @@ describe("handleNewBooking", () => {
test(
`if booking with Cal Video(Daily Video) fails, booking creation fails with uncaught error`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.org",
name: "Booker",
@@ -2715,7 +2717,7 @@ describe("handleNewBooking", () => {
3. Should trigger BOOKING_CREATED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2841,7 +2843,7 @@ describe("handleNewBooking", () => {
7. Workflow triggers once payment is successful
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3024,7 +3026,7 @@ describe("handleNewBooking", () => {
`,
async ({ emails }) => {
const bookingInitiatedEmail = "booking_initiated@workflow.com";
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -3202,7 +3204,7 @@ describe("handleNewBooking", () => {
test(
`cannot book same slot multiple times `,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3350,7 +3352,7 @@ describe("handleNewBooking", () => {
test(
`Payment retry scenario - should return existing payment UID and prevent duplicate bookings when retrying canceled payment`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3467,7 +3469,7 @@ describe("handleNewBooking", () => {
describe("Returning original booking", () => {
test("Return the original booking if a request is made twice by the same attendee when a booking requires confirmation", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3525,7 +3527,7 @@ describe("handleNewBooking", () => {
expect(secondResponse.id).toEqual(response.id);
});
test("Return the original booking if request is made by the same attendee for a round robin event", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3587,7 +3589,7 @@ describe("handleNewBooking", () => {
describe("Blocked users", () => {
test("user is locked", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3642,7 +3644,7 @@ describe("handleNewBooking", () => {
});
test("username is critical in watchlist", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3704,7 +3706,7 @@ describe("handleNewBooking", () => {
});
test("domain is critical in watchlist", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -3766,7 +3768,7 @@ describe("handleNewBooking", () => {
});
test("domain is critical in watchlist", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -0,0 +1,8 @@
async function handler(input: any) {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
return handleNewBooking(input);
}
export function getNewBookingHandler() {
return handler;
}
@@ -45,6 +45,8 @@ import { resetTestSMS } from "@calcom/lib/testSMS";
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
@@ -65,7 +67,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_RESCHEDULED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -304,7 +306,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_RESCHEDULED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -510,7 +512,7 @@ describe("handleNewBooking", () => {
test(
`an error in updating a calendar event should not stop the rescheduling - Current behaviour is wrong as the booking is resheduled but no-one is notified of it`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -697,7 +699,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_REQUESTED webhook instead of BOOKING_RESCHEDULED
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -903,7 +905,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_RESCHEDULED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1147,7 +1149,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_RESCHEDULED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1388,7 +1390,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_REQUESTED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const subscriberUrl = "http://my-webhook.example.com";
const booker = getBooker({
email: "booker@example.com",
@@ -1598,7 +1600,7 @@ describe("handleNewBooking", () => {
4. Should trigger BOOKING_RESCHEDULED webhook
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1856,7 +1858,7 @@ describe("handleNewBooking", () => {
4. Should update/create necessary video conference links
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2008,7 +2010,7 @@ describe("handleNewBooking", () => {
test(
"should send correct schedule/cancellation/reassigned emails to hosts when round robin is rescheduled to different host",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2167,7 +2169,7 @@ describe("handleNewBooking", () => {
test(
"should send rescheduling emails when round robin is rescheduled to same host",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2315,7 +2317,7 @@ describe("handleNewBooking", () => {
test(
"[Event Type with Both Email and Attendee Phone Number as required fields] should send rescheduling emails when round robin is rescheduled to same host",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const TEST_ATTENDEE_NUMBER = "+919876543210";
const booker = getBooker({
email: "booker@example.com",
@@ -2516,7 +2518,7 @@ describe("handleNewBooking", () => {
test(
"should reschedule event with same round robin host",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2673,7 +2675,7 @@ describe("handleNewBooking", () => {
test(
"should reschedule as per routedTeamMemberIds(instead of same host) even if rescheduleWithSameRoundRobinHost is true but it is a rerouting scenario",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2832,7 +2834,7 @@ describe("handleNewBooking", () => {
test(
"should use correct credentials when round robin reschedule changes host - original host credentials for deletion, new host for creation",
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -2960,7 +2962,7 @@ describe("handleNewBooking", () => {
test(
"should set correct booking reference when rescheduling with phone location change",
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "test@example.com",
name: "Booker",
@@ -18,6 +18,8 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
import { SchedulingType } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
const timeout = process.env.CI ? 5000 : 20000;
describe("handleNewBooking - Round Robin Host Validation", () => {
@@ -26,7 +28,7 @@ describe("handleNewBooking - Round Robin Host Validation", () => {
test(
"should throw NoAvailableUsersFound when Round Robin event has both fixed and round robin hosts busy",
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -139,7 +141,7 @@ describe("handleNewBooking - Round Robin Host Validation", () => {
test(
"should throw RoundRobinHostsUnavailableForBooking when Round Robin event has fixed hosts but no round robin host is available",
async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -24,6 +24,8 @@ import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "../getNewBookingHandler";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
@@ -46,7 +48,7 @@ describe("handleNewBooking", () => {
4. Verify both hosts have the booking in their calendars
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -43,6 +43,8 @@ import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "../getNewBookingHandler";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
@@ -63,7 +65,7 @@ describe("handleNewBooking", () => {
- Destination calendars for event-type and non-first hosts are used to create calendar events
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -245,7 +247,7 @@ describe("handleNewBooking", () => {
test(
`rejects a booking when even one of the hosts is busy`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -371,7 +373,7 @@ describe("handleNewBooking", () => {
- Destination calendars for event-type and non-first hosts are used to create calendar events
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -544,7 +546,7 @@ describe("handleNewBooking", () => {
- Destination calendars for event-type and non-first hosts are used to create calendar events
`,
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -777,7 +779,7 @@ describe("handleNewBooking", () => {
- Destination calendars for event-type and non-first hosts are used to create calendar events
`,
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -1014,7 +1016,7 @@ describe("handleNewBooking", () => {
- Destination calendars for event-type and non-first hosts are used to create calendar events
`,
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -1214,7 +1216,7 @@ describe("handleNewBooking", () => {
test(
`rejects a booking when the timeslot isn't within the common schedule`,
async ({}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1328,7 +1330,7 @@ describe("handleNewBooking", () => {
test(
`When Cal Video is the location, it uses global instance credentials and createMeeting is called for it`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1508,7 +1510,7 @@ describe("handleNewBooking", () => {
test(
`When Zoom is the location, it uses credentials of the first host and createMeeting is called for it.`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1709,7 +1711,7 @@ describe("handleNewBooking", () => {
test(
`When event type location is Organizer Default App and user metadata is empty, default to Cal Video`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -1903,7 +1905,7 @@ describe("handleNewBooking", () => {
- Reschedule and Cancel link in email are not of the org domain because the team is not part of any org
`,
async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -2096,7 +2098,7 @@ describe("handleNewBooking", () => {
describe("Round Robin Assignment", () => {
test(`successfully books contact owner if rr lead skip is enabled`, async ({ emails }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -22,12 +22,14 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { getNewBookingHandler } from "../getNewBookingHandler";
describe("Round Robin handleNewBooking", () => {
setupAndTeardown();
describe("Round Robin with groups", () => {
test("Books one host from each round robin group", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -208,7 +210,7 @@ describe("Round Robin handleNewBooking", () => {
});
test("Throws error when one round robin group has no available hosts", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -369,7 +371,7 @@ describe("Round Robin handleNewBooking", () => {
});
test("Creates successful booking even when one group has no hosts", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -500,7 +502,7 @@ describe("Round Robin handleNewBooking", () => {
});
test("Correctly handles hosts without groupId falling back to DEFAULT_GROUP_ID", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -648,7 +650,7 @@ describe("Round Robin handleNewBooking", () => {
});
test("Handles edge case where host.groupId is null vs undefined properly", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -780,7 +782,7 @@ describe("Round Robin handleNewBooking", () => {
describe("Seated Round Robin Event", () => {
test("For second seat booking, organizer remains the same with no team members included", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const EventManager = (await import("@calcom/lib/EventManager")).default;
const eventManagerSpy = vi.spyOn(EventManager.prototype, "updateCalendarAttendees");
@@ -26,6 +26,8 @@ import { resetTestSMS } from "@calcom/lib/testSMS";
import { SMSLockState, SchedulingType } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
vi.mock("@calcom/lib/constants", async () => {
const actual = await vi.importActual<typeof import("@calcom/lib/constants")>("@calcom/lib/constants");
@@ -49,7 +51,7 @@ describe("handleNewBooking", () => {
test(
"should send workflow email and sms when booking is created",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -160,7 +162,7 @@ describe("handleNewBooking", () => {
test(
"should not send workflow sms when booking is created if the organizer is locked for sms sending",
async ({ sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -253,7 +255,7 @@ describe("handleNewBooking", () => {
test(
"should send workflow email and sms when booking is created",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -401,7 +403,7 @@ describe("handleNewBooking", () => {
test(
"should not send workflow sms when booking is created if the team is locked for sms sending",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
@@ -539,7 +541,7 @@ describe("handleNewBooking", () => {
test("should trigger workflow when a new team event is booked and this team is active on org workflow", async ({
emails,
}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -653,7 +655,7 @@ describe("handleNewBooking", () => {
test("should trigger workflow when a new user event is booked and the user is part of an org team that is active on a org workflow", async ({
sms,
}) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -25,6 +25,7 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { getNewBookingHandler } from "../../handleNewBooking/test/getNewBookingHandler";
import * as handleSeatsModule from "../handleSeats";
describe("handleSeats", () => {
@@ -33,7 +34,7 @@ describe("handleSeats", () => {
describe("Correct parameters being passed into handleSeats from handleNewBooking", () => {
vi.mock("./handleSeats");
test("On new booking handleSeats is not called", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const spy = vi.spyOn(handleSeatsModule, "default");
const booker = getBooker({
@@ -96,7 +97,7 @@ describe("handleSeats", () => {
test("handleSeats is called when a new attendee is added", async () => {
const spy = vi.spyOn(handleSeatsModule, "default");
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -241,7 +242,7 @@ describe("handleSeats", () => {
test("handleSeats is called on rescheduling a seated event", async () => {
const spy = vi.spyOn(handleSeatsModule, "default");
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -383,7 +384,7 @@ describe("handleSeats", () => {
describe("As an attendee", () => {
describe("Creating a new booking", () => {
test("Attendee should be added to existing seated event", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "seat2@example.com",
@@ -510,7 +511,7 @@ describe("handleSeats", () => {
// Testing in case of a wave of people book a time slot at the same time
test("Attendee should be added to existing seated event when bookingUid is not present", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "seat2@example.com",
@@ -635,7 +636,7 @@ describe("handleSeats", () => {
});
test("If attendee is already a part of the booking then throw an error", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "seat1@example.com",
@@ -744,7 +745,7 @@ describe("handleSeats", () => {
});
test("If event is already full, fail", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "seat3@example.com",
@@ -866,7 +867,7 @@ describe("handleSeats", () => {
});
test("Verify Seat Availability Calculation Based on Booked Seats, Not Total Attendees", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "seat2@example.com",
@@ -1045,7 +1046,7 @@ describe("handleSeats", () => {
describe("Rescheduling a booking", () => {
test("When rescheduling to an existing booking, move attendee", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const attendeeToReschedule = getMockBookingAttendee({
id: 2,
@@ -1248,7 +1249,7 @@ describe("handleSeats", () => {
});
test("When rescheduling to an empty timeslot, create a new booking", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const attendeeToReschedule = getMockBookingAttendee({
id: 2,
@@ -1408,7 +1409,7 @@ describe("handleSeats", () => {
});
test("When last attendee is rescheduled, delete old booking", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const attendeeToReschedule = getMockBookingAttendee({
id: 2,
@@ -1841,7 +1842,7 @@ describe("handleSeats", () => {
describe("As an owner", () => {
describe("Rescheduling a booking", () => {
test("When rescheduling to new timeslot, ensure all attendees are moved", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -2000,7 +2001,7 @@ describe("handleSeats", () => {
});
test("When rescheduling to existing booking, merge attendees ", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -2207,7 +2208,7 @@ describe("handleSeats", () => {
expect(originalBooking?.status).toEqual(BookingStatus.CANCELLED);
});
test("When merging more attendees than seats, fail ", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -2384,7 +2385,7 @@ describe("handleSeats", () => {
});
test("When trying to reschedule in a non-available slot, throw an error", async () => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const handleNewBooking = getNewBookingHandler();
const booker = getBooker({
email: "booker@example.com",
@@ -327,6 +327,21 @@ export interface AIPhoneServiceProvider<T extends AIPhoneServiceProviderType = A
message: string;
}>;
/**
* Create a web call
*/
createWebCall(params: {
agentId: string;
userId: number;
teamId?: number;
timeZone: string;
eventTypeId: number;
}): Promise<{
callId: string;
accessToken: string;
agentId: string;
}>;
/**
* Update tools from event type ID
*/
@@ -35,7 +35,7 @@ export const DEFAULT_PROMPT_VALUE = `## You are helping user set up a call with
Avoid multiple questions in a single response.
Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.
Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).
If you are saying a time like 8:00 AM, just say 8 AM and emit the trailing zeros.
If you are saying a time like 8:00 AM, just say 8 AM and omit the trailing zeros.
## Response Guideline
Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.
@@ -53,7 +53,7 @@ export const DEFAULT_PROMPT_VALUE = `## You are helping user set up a call with
- if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
- if availability does not exist, ask user to select another time range for the appointment, repeat this step 3.
5. Confirm the date and time selected by user: \"Just to confirm, you want to book the appointment at ...\".
6. Once confirmed, call function book_appointment_{{eventTypeId}} to book the appointment.
6. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
- if booking returned booking detail, it means booking is successful, proceed to step 7.
- if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3.
7. Inform the user booking is successful, and ask if user have any questions. Answer them if there are any.
@@ -266,6 +266,20 @@ export class RetellAIPhoneServiceProvider
return await this.service.createTestCall(params);
}
async createWebCall(params: {
agentId: string;
userId: number;
teamId?: number;
timeZone: string;
eventTypeId: number;
}): Promise<{
callId: string;
accessToken: string;
agentId: string;
}> {
return await this.service.createWebCall(params);
}
async updateToolsFromAgentId(
agentId: string,
data: { eventTypeId: number | null; timeZone: string; userId: number | null; teamId?: number | null }
@@ -220,6 +220,16 @@ export class RetellAIService {
return this.callService.createTestCall(params);
}
async createWebCall(params: {
agentId: string;
userId: number;
teamId?: number;
timeZone: string;
eventTypeId: number;
}) {
return this.callService.createWebCall(params);
}
async generatePhoneNumberCheckoutSession(params: {
userId: number;
teamId?: number;
@@ -12,6 +12,7 @@ import type {
RetellAgent,
CreatePhoneNumberParams,
CreatePhoneCallParams,
CreateWebCallParams,
ImportPhoneNumberParams,
} from "./types";
@@ -234,4 +235,17 @@ export class RetellSDKClient implements RetellAIRepository {
throw error;
}
}
async createWebCall(data: CreateWebCallParams) {
try {
const response = await this.client.call.createWebCall({
agent_id: data.agentId,
retell_llm_dynamic_variables: data.dynamicVariables,
});
return response;
} catch (error) {
this.logger.error("Failed to create web call", { error });
throw error;
}
}
}
@@ -155,6 +155,7 @@ export class CallService {
EVENT_START_TIME_IN_ATTENDEE_TIMEZONE: "2:00 PM",
EVENT_END_TIME_IN_ATTENDEE_TIMEZONE: "2:30 PM",
eventTypeId: eventTypeId.toString(),
NUMBER_TO_CALL: toNumber,
},
});
@@ -165,6 +166,93 @@ export class CallService {
};
}
async createWebCall({
agentId,
userId,
teamId,
timeZone,
eventTypeId,
}: {
agentId: string;
userId: number;
teamId?: number;
timeZone: string;
eventTypeId: number;
}) {
if (!agentId?.trim()) {
throw new HttpError({
statusCode: 400,
message: "Agent ID is required and cannot be empty",
});
}
await this.validateCreditsForTestCall({ userId, teamId });
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: `web-call:${userId}`,
});
const agent = await this.agentRepository.findByIdWithCallAccess({
id: agentId,
userId,
});
if (!agent) {
throw new HttpError({
statusCode: 404,
message: "Agent not found or you don't have permission to use it.",
});
}
if (!agent.providerAgentId) {
throw new HttpError({
statusCode: 400,
message: "Agent provider ID not found.",
});
}
const dynamicVariables = {
EVENT_NAME: "Web Call Test with Agent",
EVENT_DATE: "Monday, January 15, 2025",
EVENT_TIME: "2:00 PM",
EVENT_END_TIME: "2:30 PM",
TIMEZONE: timeZone,
LOCATION: "Web Call",
ORGANIZER_NAME: "Cal.com AI Agent",
ATTENDEE_NAME: "Test User",
ATTENDEE_FIRST_NAME: "Test",
ATTENDEE_LAST_NAME: "User",
ATTENDEE_EMAIL: "testuser@example.com",
ATTENDEE_TIMEZONE: timeZone,
ADDITIONAL_NOTES: "This is a test web call to verify the AI phone agent",
EVENT_START_TIME_IN_ATTENDEE_TIMEZONE: "2:00 PM",
EVENT_END_TIME_IN_ATTENDEE_TIMEZONE: "2:30 PM",
eventTypeId: eventTypeId.toString(),
};
try {
const webCall = await this.retellRepository.createWebCall({
agentId: agent.providerAgentId,
dynamicVariables,
});
return {
callId: webCall.call_id,
accessToken: webCall.access_token,
agentId: webCall.agent_id,
};
} catch (error) {
this.logger.error("Failed to create web call in external AI service", {
agentId: agent.providerAgentId,
error,
});
throw new HttpError({
statusCode: 500,
message: "Failed to create web call",
});
}
}
private async validateCreditsForTestCall({ userId, teamId }: { userId: number; teamId?: number }) {
try {
const { CreditService } = await import("@calcom/features/ee/billing/credit-service");
@@ -86,6 +86,11 @@ export type CreatePhoneCallParams = {
toNumber: string;
dynamicVariables?: RetellDynamicVariables;
};
export type CreateWebCallParams = {
agentId: string;
dynamicVariables?: RetellDynamicVariables;
};
export type UpdatePhoneNumberParams = Retell.PhoneNumberUpdateParams;
export type ImportPhoneNumberParams = Retell.PhoneNumberImportParams;
export type RetellLLMGeneralTools = Retell.LlmCreateParams["general_tools"];
@@ -171,4 +176,7 @@ export interface RetellAIRepository {
// Call operations
createPhoneCall(data: CreatePhoneCallParams): Promise<RetellCall>;
createWebCall(
data: CreateWebCallParams
): Promise<{ call_id: string; access_token: string; agent_id: string }>;
}
@@ -0,0 +1,92 @@
const styleGuardrails = `## Style Guardrails
Be Concise: Respond succinctly, addressing one topic at most.
Embrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content.
Be Conversational: Use everyday language, making the chat feel like talking to a friend.
Be Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion.
Avoid multiple questions in a single response.
Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.
Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).
If you are saying a time like 8:00 AM, just say 8 AM and omit the trailing zeros.`;
const responseGuideline = `## Response Guideline
Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.
Stay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating.
Ensure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.`;
const scheduleRule = ` ## Schedule Rule
Current time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.`;
// Key are from components/sections/template/data/workflows.ts page in https://github.com/calcom/website
export const calAIPhoneWorkflowTemplates = {
// name: "Cal AI No-show Follow-up Call",
// description: "Automatically call attendee when marked as no-show"
"wf-10": {
generalPrompt: `## You are calling an attendee who was marked as a no-show for their appointment. Your goal is to help them reschedule. Be understanding, friendly, and non-judgmental.
${styleGuardrails}
${responseGuideline}
${scheduleRule}
## Task Steps
1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a courtesy call from {{ORGANIZER_NAME}}. I noticed you weren't able to make your {{EVENT_NAME}} appointment on {{EVENT_DATE}} at {{EVENT_TIME}}."
2. Express understanding: "I understand things come up. I'm calling to see if you'd like to reschedule for another time that works better for you."
3. If they want to reschedule:
3a. Ask "When would work best for you to reschedule?"
3b. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range.
- if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
- if availability does not exist, ask user to select another time range for the appointment, repeat this step 3a.
5. If {{ATTENDEE_EMAIL}} is not unknown then Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking else Ask for user name and email and Confirm the name and email with user by reading it back to user.
6. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
- if booking returned booking detail, it means booking is successful, proceed to step 7.
- if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3a.
7. If they don't want to reschedule:
- Thank them for their time and let them know they can always reach out if they change their mind.
8. Before ending, ask if there's anything else you can help with.
9. Thank them for their time and call function end_call to hang up.`,
},
// name: "Cal AI 1-hour Meeting Reminder",
// description: "Remind attendee 1 hour before the meeting"
"wf-11": {
generalPrompt: `## You are calling to remind an attendee about their upcoming appointment in 1 hour. Be friendly, helpful, and concise.
${styleGuardrails}
${responseGuideline}
${scheduleRule}
## Task Steps
1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a quick reminder call from {{ORGANIZER_NAME}} about your upcoming {{EVENT_NAME}} appointment."
2. Provide the meeting details: "Your appointment is scheduled for today at {{EVENT_TIME}} {{TIMEZONE}}. That's in about an hour."
3. Ask if they'll be able to make it: "Will you be able to join us?"
4. If they confirm attendance:
- Thank them and remind them of any preparation needed.
- Say "Great! We'll see you at {{EVENT_TIME}}."
5. If they need to reschedule or cancel:
- Express understanding: "No problem, these things happen."
- Ask: "Would you like to reschedule now, or would you prefer to contact us later?"
- If they want to reschedule now:
5a. If {{ATTENDEE_EMAIL}} is not unknown: Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking
5b. If {{ATTENDEE_EMAIL}} is unknown: Ask for user name and email and confirm by reading it back to user
5c. Ask user for "When would you want to reschedule?"
5d. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range.
5e. If availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
5f. If availability does not exist, ask user to select another time range for the appointment (repeat step 5c).
5g. Confirm the date and time selected by user: "Just to confirm, you want to book the appointment at ..."
5h. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
5i. If booking returned booking detail, it means booking is successful, proceed to step 7.
5j. If booking returned error message, let user know why the booking was not successful, and maybe start over (return to step 5c).
- If they prefer to reschedule later: "No problem. You can reschedule anytime through the link in your confirmation email or by contacting us."
6. If they have questions about the meeting:
- Answer based on available information ({{ADDITIONAL_NOTES}}, {{LOCATION}}, etc.).
- Common questions to handle:
- Duration: Use {{EVENT_END_TIME}} to calculate and state duration
- Location details: Provide {{LOCATION}} information
- What to prepare: Check {{ADDITIONAL_NOTES}} for any preparation instructions
- Who they're meeting: {{ORGANIZER_NAME}} is the person they'll be meeting
7. End with: "Thanks for your time. Have a great day!" and call function end_call to hang up.`,
},
};
+1 -1
View File
@@ -3,6 +3,7 @@ import { buffer } from "micro";
import type { NextApiRequest, NextApiResponse } from "next";
import type Stripe from "stripe";
import { handlePaymentSuccess } from "@calcom/app-store/_utils/payments/handlePaymentSuccess";
import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails";
import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation";
import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials";
@@ -16,7 +17,6 @@ import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { getBooking } from "@calcom/lib/payment/getBooking";
import { handlePaymentSuccess } from "@calcom/lib/payment/handlePaymentSuccess";
import { safeStringify } from "@calcom/lib/safeStringify";
import { prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
@@ -53,6 +53,8 @@ export const handleRescheduleEventManager = async ({
prefix: ["handleRescheduleEventManager", `${bookingId}`],
});
const skipDeleteEventsAndMeetings = changedOrganizer;
const allCredentials = await getAllCredentialsIncludeServiceAccountKey(
initParams.user,
initParams?.eventType
@@ -68,7 +70,9 @@ export const handleRescheduleEventManager = async ({
rescheduleUid,
newBookingId,
changedOrganizer,
previousHostDestinationCalendar
previousHostDestinationCalendar,
undefined,
skipDeleteEventsAndMeetings
);
const results = updateManager.results ?? [];
@@ -115,6 +115,8 @@ describe("roundRobinReassignment test", () => {
await roundRobinReassignment({
bookingId: 123,
reassignedById: 101,
orgId: null,
});
// Verify that calendar deletion occurred (may be called multiple times due to duplicate references)
@@ -122,7 +124,7 @@ describe("roundRobinReassignment test", () => {
const deleteCall = calendarMock.deleteEventCalls[0];
expect(deleteCall.args.uid).toBe("ORIGINAL_EVENT_ID");
expect(deleteCall.args.externalCalendarId).toBe("MOCK_EXTERNAL_CALENDAR_ID");
expect(deleteCall.args.event.organizer.email).toBe(newHost.email); // Current implementation uses new host credentials
expect(deleteCall.args.event.organizer.email).toBe(originalHost.email);
expect(deleteCall.args.event.uid).toBe(bookingToReassignUid);
// Verify that creation occurred with new host credentials
@@ -154,7 +154,9 @@ const expectEventManagerCalledWith = (
expectedParams.uid,
undefined,
expectedParams.changedOrganizer,
expectedParams.destinationCalendars ?? expect.any(Array)
expectedParams.destinationCalendars ?? expect.any(Array),
undefined,
expectedParams.changedOrganizer
);
};
@@ -7,6 +7,7 @@ import {
sendRoundRobinScheduledEmailsAndSMS,
sendRoundRobinUpdatedEmailsAndSMS,
} from "@calcom/emails";
import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials";
import getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB";
@@ -21,6 +22,7 @@ import {
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
import { getEventName } from "@calcom/features/eventtypes/lib/eventNaming";
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
import EventManager from "@calcom/lib/EventManager";
import { SENDER_NAME } from "@calcom/lib/constants";
import { enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "@calcom/lib/delegationCredential/server";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
@@ -32,6 +34,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma } from "@calcom/prisma";
import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums";
import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils";
import { eventTypeAppMetadataOptionalSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { handleRescheduleEventManager } from "./handleRescheduleEventManager";
@@ -324,6 +327,41 @@ export const roundRobinManualReassignment = async ({
})
: null;
const apps = eventTypeAppMetadataOptionalSchema.parse(eventType?.metadata?.apps);
// remove the event and meeting using the old host's credentials
if (hasOrganizerChanged && originalOrganizer.id !== newUser.id) {
const previousHostCredentials = await getAllCredentialsIncludeServiceAccountKey(
originalOrganizer,
eventType
);
const originalHostEventManager = new EventManager(
{ ...originalOrganizer, credentials: previousHostCredentials },
apps
);
const deletionEvent: CalendarEvent = {
...evt,
organizer: {
id: originalOrganizer.id,
name: originalOrganizer.name || "",
email: originalOrganizer.email,
username: originalOrganizer.username || undefined,
timeZone: originalOrganizer.timeZone,
language: { translate: originalOrganizerT, locale: originalOrganizer.locale ?? "en" },
timeFormat: getTimeFormatStringFromUserTimeFormat(originalOrganizer.timeFormat),
},
destinationCalendar: previousHostDestinationCalendar ? [previousHostDestinationCalendar] : [],
title: booking.title,
};
await originalHostEventManager.deleteEventsAndMeetings({
event: deletionEvent,
bookingReferences: booking.references,
});
}
const { evtWithAdditionalInfo } = await handleRescheduleEventManager({
evt,
rescheduleUid: booking.uid,

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