Merge branch 'main' of https://github.com/calcom/cal.com
This commit is contained in:
@@ -8,7 +8,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Cache production build
|
||||
uses: actions/cache@v3
|
||||
uses: buildjet/cache@v3
|
||||
id: cache-build
|
||||
env:
|
||||
cache-name: prod-build
|
||||
|
||||
@@ -12,7 +12,7 @@ runs:
|
||||
steps:
|
||||
- name: Cache database
|
||||
id: cache-db
|
||||
uses: actions/cache@v3
|
||||
uses: buildjet/cache@v3
|
||||
env:
|
||||
cache-name: cache-db
|
||||
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
||||
|
||||
@@ -4,7 +4,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Create env file
|
||||
uses: actions/cache@v3
|
||||
uses: buildjet/cache@v3
|
||||
id: env-cache
|
||||
with:
|
||||
path: gh.env
|
||||
@@ -13,4 +13,4 @@ runs:
|
||||
- name: Set Environment Variables
|
||||
uses: tw3lveparsecs/github-actions-setvars@latest
|
||||
with:
|
||||
envFilePath: gh.env
|
||||
envFilePath: gh.env
|
||||
|
||||
@@ -20,7 +20,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Use Node ${{ inputs.node_version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
||||
@@ -32,7 +32,7 @@ runs:
|
||||
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
|
||||
# Yarn cache is also reusable between arch and os.
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
uses: buildjet/cache@v3
|
||||
id: yarn-download-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||
@@ -43,7 +43,7 @@ runs:
|
||||
# Invalidated on yarn.lock changes
|
||||
- name: Restore yarn install state
|
||||
id: yarn-install-state-cache
|
||||
uses: actions/cache@v3
|
||||
uses: buildjet/cache@v3
|
||||
with:
|
||||
path: .yarn/ci-cache/
|
||||
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||
|
||||
@@ -5,7 +5,7 @@ runs:
|
||||
steps:
|
||||
- name: Cache playwright binaries
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v2
|
||||
uses: buildjet/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/ms-playwright
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -5,7 +5,7 @@ env:
|
||||
NODE_OPTIONS: "--max-old-space-size=8192"
|
||||
jobs:
|
||||
check-types:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
e2e-app-store:
|
||||
timeout-minutes: 20
|
||||
name: E2E App-Store Apps
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12.1
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
jobs:
|
||||
e2e-embed:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12.1
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
jobs:
|
||||
e2e-embed:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12.1
|
||||
|
||||
@@ -4,8 +4,8 @@ on:
|
||||
jobs:
|
||||
e2e:
|
||||
timeout-minutes: 20
|
||||
name: E2E tests
|
||||
runs-on: ubuntu-latest
|
||||
name: E2E tests (${{ matrix.shard }}/${{ strategy.job-total }})
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12.1
|
||||
@@ -14,6 +14,10 @@ jobs:
|
||||
POSTGRES_DB: calendso
|
||||
ports:
|
||||
- 5432:5432
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
@@ -24,12 +28,12 @@ jobs:
|
||||
- uses: ./.github/actions/cache-db
|
||||
- uses: ./.github/actions/cache-build
|
||||
- name: Run Tests
|
||||
run: yarn test-e2e
|
||||
run: yarn e2e --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
env:
|
||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||
- name: Upload Test Results
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-results
|
||||
name: test-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
||||
path: test-results
|
||||
|
||||
@@ -20,7 +20,7 @@ env:
|
||||
INPUT_ENV_PAYMENT_FEE_FIXED: 10
|
||||
INPUT_ENV_SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
|
||||
INPUT_ENV_SAML_ADMINS: pro@example.com
|
||||
INPUT_ENV_NEXTAUTH_URL: http://127.0.0.1:3000/api/auth
|
||||
INPUT_ENV_NEXTAUTH_URL: http://localhost:3000
|
||||
INPUT_ENV_NEXT_PUBLIC_IS_E2E: 1
|
||||
# INPUT_ENV_EMAIL_FROM: e2e@cal.com
|
||||
# INPUT_ENV_EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||
@@ -36,14 +36,14 @@ env:
|
||||
|
||||
jobs:
|
||||
create_env_file:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ozaytsev86/create-env-file@v1
|
||||
with:
|
||||
file-name: ${{ github.workspace }}/gh.env
|
||||
- uses: actions/cache@v3
|
||||
- uses: buildjet/cache@v3
|
||||
id: env-cache
|
||||
with:
|
||||
path: gh.env
|
||||
|
||||
@@ -3,7 +3,7 @@ on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
analyze:
|
||||
needs: build
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
@@ -16,7 +16,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
@@ -66,6 +66,12 @@ jobs:
|
||||
uses: ./.github/workflows/production-build.yml
|
||||
secrets: inherit
|
||||
|
||||
build-without-database:
|
||||
name: Production build (without database)
|
||||
needs: env
|
||||
uses: ./.github/workflows/production-build-without-database.yml
|
||||
secrets: inherit
|
||||
|
||||
e2e:
|
||||
name: E2E tests
|
||||
needs: [changes, lint, build]
|
||||
@@ -101,7 +107,7 @@ jobs:
|
||||
required:
|
||||
needs: [lint, type-check, test, build, e2e]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: fail if conditional jobs failed
|
||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Production Build (without database)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/env-read-file
|
||||
- uses: ./.github/actions/cache-build
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 30
|
||||
services:
|
||||
postgres:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "Validate PRs"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
validate-pr:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
setup:
|
||||
name: Yarn install & cache
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -106,7 +106,7 @@ To get a local copy up and running, please follow these simple steps.
|
||||
|
||||
Here is what you need to be able to run Cal.com.
|
||||
|
||||
- Node.js (Version: >=15.x <17)
|
||||
- Node.js (Version: >=18.x)
|
||||
- PostgreSQL
|
||||
- Yarn _(recommended)_
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("@calcom/config/eslint-preset");
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { _BookingModel as Booking, _AttendeeModel, _UserModel } from "@calcom/prisma/zod";
|
||||
import { _BookingModel as Booking, _AttendeeModel, _UserModel, _PaymentModel } from "@calcom/prisma/zod";
|
||||
import { extendedBookingCreateBody, iso8601 } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { schemaQueryUserId } from "./shared/queryUserId";
|
||||
@@ -49,6 +49,15 @@ export const schemaBookingReadPublic = Booking.extend({
|
||||
locale: true,
|
||||
})
|
||||
.optional(),
|
||||
payment: z
|
||||
.array(
|
||||
_PaymentModel.pick({
|
||||
id: true,
|
||||
success: true,
|
||||
paymentOption: true,
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
}).pick({
|
||||
id: true,
|
||||
userId: true,
|
||||
@@ -61,6 +70,7 @@ export const schemaBookingReadPublic = Booking.extend({
|
||||
timeZone: true,
|
||||
attendees: true,
|
||||
user: true,
|
||||
payment: true,
|
||||
metadata: true,
|
||||
status: true,
|
||||
responses: true,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { withValidation } from "next-validations";
|
||||
import { z } from "zod";
|
||||
|
||||
import { baseApiParams } from "./baseApiParams";
|
||||
|
||||
// Extracted out as utility function so can be reused
|
||||
// at different endpoints that require this validation.
|
||||
export const schemaQueryAttendeeEmail = baseApiParams.extend({
|
||||
attendeeEmail: z.string().email(),
|
||||
});
|
||||
|
||||
export const schemaQuerySingleOrMultipleAttendeeEmails = z.object({
|
||||
attendeeEmail: z.union([z.string().email(), z.array(z.string().email())]).optional(),
|
||||
});
|
||||
|
||||
export const withValidQueryAttendeeEmail = withValidation({
|
||||
schema: schemaQueryAttendeeEmail,
|
||||
type: "Zod",
|
||||
mode: "query",
|
||||
});
|
||||
@@ -11,10 +11,8 @@
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"dev": "PORT=3002 next dev",
|
||||
"lint": "eslint . --ignore-path .gitignore",
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/api.json",
|
||||
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||
"start": "PORT=3002 next start",
|
||||
"test": "echo 'No tests yet' && exit 0",
|
||||
"type-check": "tsc --pretty --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getUserAvailability } from "@calcom/core/getUserAvailability";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { availabilityUserSelect } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,9 @@ import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
||||
* 404:
|
||||
* description: User not found | Team not found | Team has no members
|
||||
*/
|
||||
interface MemberRoles {
|
||||
[userId: number | string]: MembershipRole;
|
||||
}
|
||||
|
||||
const availabilitySchema = z
|
||||
.object({
|
||||
@@ -113,7 +117,7 @@ const availabilitySchema = z
|
||||
);
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { prisma, isAdmin } = req;
|
||||
const { prisma, isAdmin, userId: reqUserId } = req;
|
||||
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
|
||||
if (!teamId)
|
||||
return getUserAvailability({
|
||||
@@ -134,7 +138,16 @@ async function handler(req: NextApiRequest) {
|
||||
where: { id: { in: allMemberIds } },
|
||||
select: availabilityUserSelect,
|
||||
});
|
||||
if (!isAdmin) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||
const memberRoles: MemberRoles = team.members.reduce((acc: MemberRoles, membership) => {
|
||||
acc[membership.userId] = membership.role;
|
||||
return acc;
|
||||
}, {} as MemberRoles);
|
||||
// check if the user is a team Admin or Owner, if it is a team request, or a system Admin
|
||||
const isUserAdminOrOwner =
|
||||
memberRoles[reqUserId] == MembershipRole.ADMIN ||
|
||||
memberRoles[reqUserId] == MembershipRole.OWNER ||
|
||||
isAdmin;
|
||||
if (!isUserAdminOrOwner) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||
const availabilities = members.map(async (user) => {
|
||||
return {
|
||||
userId: user.id,
|
||||
|
||||
@@ -32,40 +32,65 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/ArrayOfBookings"
|
||||
* $ref: "#/components/schemas/Booking"
|
||||
* examples:
|
||||
* bookings:
|
||||
* value: [
|
||||
* booking:
|
||||
* value:
|
||||
* {
|
||||
* "id": 1,
|
||||
* "description": "Meeting with John",
|
||||
* "eventTypeId": 2,
|
||||
* "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
|
||||
* "title": "Business Meeting",
|
||||
* "startTime": "2023-04-20T10:00:00.000Z",
|
||||
* "endTime": "2023-04-20T11:00:00.000Z",
|
||||
* "timeZone": "Europe/London",
|
||||
* "attendees": [
|
||||
* {
|
||||
* "email": "example@cal.com",
|
||||
* "name": "John Doe",
|
||||
* "timeZone": "Europe/London",
|
||||
* "booking": {
|
||||
* "id": 91,
|
||||
* "userId": 5,
|
||||
* "description": "",
|
||||
* "eventTypeId": 7,
|
||||
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
|
||||
* "title": "60min between Pro Example and John Doe",
|
||||
* "startTime": "2023-05-25T09:30:00.000Z",
|
||||
* "endTime": "2023-05-25T10:30:00.000Z",
|
||||
* "attendees": [
|
||||
* {
|
||||
* "email": "john.doe@example.com",
|
||||
* "name": "John Doe",
|
||||
* "timeZone": "Asia/Kolkata",
|
||||
* "locale": "en"
|
||||
* }
|
||||
* ],
|
||||
* "user": {
|
||||
* "email": "pro@example.com",
|
||||
* "name": "Pro Example",
|
||||
* "timeZone": "Asia/Kolkata",
|
||||
* "locale": "en"
|
||||
* },
|
||||
* "payment": [
|
||||
* {
|
||||
* "id": 1,
|
||||
* "success": true,
|
||||
* "paymentOption": "ON_BOOKING"
|
||||
* }
|
||||
* ],
|
||||
* "metadata": {},
|
||||
* "status": "ACCEPTED",
|
||||
* "responses": {
|
||||
* "email": "john.doe@example.com",
|
||||
* "name": "John Doe",
|
||||
* "location": {
|
||||
* "optionValue": "",
|
||||
* "value": "inPerson"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* 401:
|
||||
* description: Authorization information is missing or invalid.
|
||||
* 404:
|
||||
* description: Booking was not found
|
||||
*/
|
||||
|
||||
export async function getHandler(req: NextApiRequest) {
|
||||
const { prisma, query } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(query);
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: { id },
|
||||
include: { attendees: true, user: true },
|
||||
include: { attendees: true, user: true, payment: true },
|
||||
});
|
||||
return { booking: schemaBookingReadPublic.parse(booking) };
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,19 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||
* items:
|
||||
* type: integer
|
||||
* example: [2, 3, 4]
|
||||
* - in: query
|
||||
* name: attendeeEmails
|
||||
* required: false
|
||||
* schema:
|
||||
* oneOf:
|
||||
* - type: string
|
||||
* format: email
|
||||
* example: john.doe@example.com
|
||||
* - type: array
|
||||
* items:
|
||||
* type: string
|
||||
* format: email
|
||||
* example: [john.doe@example.com, jane.doe@example.com]
|
||||
* operationId: listBookings
|
||||
* tags:
|
||||
* - bookings
|
||||
@@ -45,22 +59,47 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||
* bookings:
|
||||
* value: [
|
||||
* {
|
||||
* "id": 1,
|
||||
* "description": "Meeting with John",
|
||||
* "eventTypeId": 2,
|
||||
* "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
|
||||
* "title": "Business Meeting",
|
||||
* "startTime": "2023-04-20T10:00:00.000Z",
|
||||
* "endTime": "2023-04-20T11:00:00.000Z",
|
||||
* "timeZone": "Europe/London",
|
||||
* "attendees": [
|
||||
* {
|
||||
* "email": "example@cal.com",
|
||||
* "name": "John Doe",
|
||||
* "timeZone": "Europe/London",
|
||||
* "booking": {
|
||||
* "id": 91,
|
||||
* "userId": 5,
|
||||
* "description": "",
|
||||
* "eventTypeId": 7,
|
||||
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
|
||||
* "title": "60min between Pro Example and John Doe",
|
||||
* "startTime": "2023-05-25T09:30:00.000Z",
|
||||
* "endTime": "2023-05-25T10:30:00.000Z",
|
||||
* "attendees": [
|
||||
* {
|
||||
* "email": "john.doe@example.com",
|
||||
* "name": "John Doe",
|
||||
* "timeZone": "Asia/Kolkata",
|
||||
* "locale": "en"
|
||||
* }
|
||||
* ],
|
||||
* "user": {
|
||||
* "email": "pro@example.com",
|
||||
* "name": "Pro Example",
|
||||
* "timeZone": "Asia/Kolkata",
|
||||
* "locale": "en"
|
||||
* },
|
||||
* "payment": [
|
||||
* {
|
||||
* "id": 1,
|
||||
* "success": true,
|
||||
* "paymentOption": "ON_BOOKING"
|
||||
* }
|
||||
* ],
|
||||
* "metadata": {},
|
||||
* "status": "ACCEPTED",
|
||||
* "responses": {
|
||||
* "email": "john.doe@example.com",
|
||||
* "name": "John Doe",
|
||||
* "location": {
|
||||
* "optionValue": "",
|
||||
* "value": "inPerson"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* 401:
|
||||
@@ -69,26 +108,42 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||
* description: No bookings were found
|
||||
*/
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isAdmin, prisma } = req;
|
||||
const args: Prisma.BookingFindManyArgs = {};
|
||||
args.include = {
|
||||
attendees: true,
|
||||
user: true,
|
||||
};
|
||||
|
||||
/** Only admins can query other users */
|
||||
if (isAdmin && req.query.userId) {
|
||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||
const users = await prisma.user.findMany({
|
||||
where: { id: { in: userIds } },
|
||||
select: { email: true },
|
||||
});
|
||||
const userEmails = users.map((u) => u.email);
|
||||
args.where = {
|
||||
/**
|
||||
* Constructs the WHERE clause for Prisma booking findMany operation.
|
||||
*
|
||||
* @param userId - The ID of the user making the request. This is used to filter bookings where the user is either the host or an attendee.
|
||||
* @param attendeeEmails - An array of emails provided in the request for filtering bookings by attendee emails, used in case of Admin calls.
|
||||
* @param userIds - An array of user IDs to be included in the filter. Defaults to an empty array, and an array of user IDs in case of Admin call containing it.
|
||||
* @param userEmails - An array of user emails to be included in the filter if it is an Admin call and contains userId in query parameter. Defaults to an empty array.
|
||||
*
|
||||
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
||||
*/
|
||||
function buildWhereClause(
|
||||
userId: number,
|
||||
attendeeEmails: string[],
|
||||
userIds: number[] = [],
|
||||
userEmails: string[] = []
|
||||
) {
|
||||
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : { userId };
|
||||
let whereClause = {};
|
||||
if (filterByAttendeeEmails) {
|
||||
whereClause = {
|
||||
AND: [
|
||||
userFilter,
|
||||
{
|
||||
attendees: {
|
||||
some: {
|
||||
email: { in: attendeeEmails },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
whereClause = {
|
||||
OR: [
|
||||
{ userId: { in: userIds } },
|
||||
userFilter,
|
||||
{
|
||||
attendees: {
|
||||
some: {
|
||||
@@ -98,7 +153,45 @@ async function handler(req: NextApiRequest) {
|
||||
},
|
||||
],
|
||||
};
|
||||
} else if (!isAdmin) {
|
||||
}
|
||||
|
||||
return {
|
||||
...whereClause,
|
||||
};
|
||||
}
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isAdmin, prisma } = req;
|
||||
const args: Prisma.BookingFindManyArgs = {};
|
||||
args.include = {
|
||||
attendees: true,
|
||||
user: true,
|
||||
payment: true,
|
||||
};
|
||||
|
||||
const queryFilterForAttendeeEmails = schemaQuerySingleOrMultipleAttendeeEmails.parse(req.query);
|
||||
const attendeeEmails = Array.isArray(queryFilterForAttendeeEmails.attendeeEmail)
|
||||
? queryFilterForAttendeeEmails.attendeeEmail
|
||||
: typeof queryFilterForAttendeeEmails.attendeeEmail === "string"
|
||||
? [queryFilterForAttendeeEmails.attendeeEmail]
|
||||
: [];
|
||||
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||
|
||||
/** Only admins can query other users */
|
||||
if (isAdmin) {
|
||||
if (req.query.userId) {
|
||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||
const users = await prisma.user.findMany({
|
||||
where: { id: { in: userIds } },
|
||||
select: { email: true },
|
||||
});
|
||||
const userEmails = users.map((u) => u.email);
|
||||
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
||||
} else if (filterByAttendeeEmails) {
|
||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||
}
|
||||
} else {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
@@ -108,20 +201,7 @@ async function handler(req: NextApiRequest) {
|
||||
if (!user) {
|
||||
throw new HttpError({ message: "User not found", statusCode: 500 });
|
||||
}
|
||||
args.where = {
|
||||
OR: [
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{
|
||||
attendees: {
|
||||
some: {
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||
}
|
||||
const data = await prisma.booking.findMany(args);
|
||||
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
||||
|
||||
@@ -98,6 +98,25 @@ const swaggerHandler = withSwagger({
|
||||
},
|
||||
},
|
||||
},
|
||||
payment: {
|
||||
type: Array,
|
||||
items: {
|
||||
properties: {
|
||||
id: {
|
||||
type: "number",
|
||||
example: 1,
|
||||
},
|
||||
success: {
|
||||
type: "boolean",
|
||||
example: true,
|
||||
},
|
||||
paymentOption: {
|
||||
type: "string",
|
||||
example: "ON_BOOKING",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @calcom/web
|
||||
|
||||
## 2.9.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @calcom/embed-snippet@1.0.9
|
||||
- @calcom/embed-react@1.1.1
|
||||
- @calcom/embed-core@1.2.1
|
||||
|
||||
## 2.9.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @calcom/embed-react@1.1.0
|
||||
- @calcom/embed-core@1.2.0
|
||||
- @calcom/embed-snippet@1.0.8
|
||||
|
||||
## 2.7.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -129,7 +129,10 @@ const Component = ({
|
||||
className="bg-subtle text-emphasis rounded-md p-1 text-xs capitalize">
|
||||
{categories[0]}
|
||||
</Link>{" "}
|
||||
• {t("published_by", { author })}
|
||||
•{" "}
|
||||
<a target="_blank" rel="noreferrer" href={website}>
|
||||
{t("published_by", { author })}
|
||||
</a>
|
||||
</h2>
|
||||
{isTemplate && (
|
||||
<Badge variant="red" className="mt-4">
|
||||
@@ -257,7 +260,7 @@ const Component = ({
|
||||
)}
|
||||
</span>
|
||||
|
||||
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("learn_more")}</h4>
|
||||
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("contact")}</h4>
|
||||
<ul className="prose-sm -ml-1 -mr-1 leading-5">
|
||||
{docs && (
|
||||
<li>
|
||||
|
||||
@@ -123,7 +123,8 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
disabled: mutation.isLoading,
|
||||
},
|
||||
// For bookings with payment, only confirm if the booking is paid for
|
||||
...((isPending && !booking?.eventType?.price) || (!!booking?.eventType?.price && booking.paid)
|
||||
...((isPending && !paymentAppData.enabled) ||
|
||||
(paymentAppData.enabled && !!paymentAppData.price && booking.paid)
|
||||
? [
|
||||
{
|
||||
id: "confirm",
|
||||
@@ -318,6 +319,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
<DialogClose />
|
||||
<Button
|
||||
disabled={mutation.isLoading}
|
||||
data-testid="rejection-confirm"
|
||||
onClick={() => {
|
||||
bookingConfirm(false);
|
||||
}}>
|
||||
|
||||
@@ -238,20 +238,34 @@ function EventTypeSingleLayout({
|
||||
<div className="flex items-center justify-end">
|
||||
{!eventType.metadata.managedEventConfig && (
|
||||
<>
|
||||
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 lg:flex">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 hidden cursor-pointer self-center whitespace-nowrap pr-2 sm:inline">
|
||||
{t("hide_from_profile")}
|
||||
</Skeleton>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
checked={formMethods.watch("hidden")}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hidden", e);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
"sm:hover:bg-muted hidden cursor-pointer items-center rounded-md",
|
||||
formMethods.watch("hidden") ? "px-2" : "",
|
||||
"lg:flex"
|
||||
)}>
|
||||
{formMethods.watch("hidden") && (
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 hidden cursor-pointer self-center whitespace-nowrap pr-2 sm:inline">
|
||||
{t("hidden")}
|
||||
</Skeleton>
|
||||
)}
|
||||
<Tooltip
|
||||
content={
|
||||
formMethods.watch("hidden") ? t("show_eventtype_on_profile") : t("hide_from_profile")
|
||||
}>
|
||||
<div className="self-center rounded-md p-2">
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
checked={!formMethods.watch("hidden")}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hidden", !e);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<VerticalDivider className="hidden lg:block" />
|
||||
</>
|
||||
@@ -344,18 +358,18 @@ function EventTypeSingleLayout({
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="sm:hover:bg-subtle flex h-9 flex-row items-center justify-between py-2 px-4">
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 inline cursor-pointer self-center pr-2 ">
|
||||
{t("hide_from_profile")}
|
||||
{formMethods.watch("hidden") ? t("show_eventtype_on_profile") : t("hide_from_profile")}
|
||||
</Skeleton>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
checked={formMethods.watch("hidden")}
|
||||
checked={!formMethods.watch("hidden")}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hidden", e);
|
||||
formMethods.setValue("hidden", !e);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@ const DisableTwoFactorAuthModal = ({
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<Button type="submit" className="ms-2 me-2" disabled={isDisabling}>
|
||||
<Button type="submit" className="ms-2 me-2" data-testid="disable-2fa" disabled={isDisabling}>
|
||||
{t("disable")}
|
||||
</Button>
|
||||
<Button color="secondary" onClick={onCancel}>
|
||||
|
||||
@@ -156,7 +156,9 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
<img src={dataUri} alt="" />
|
||||
}
|
||||
</div>
|
||||
<p className="text-center font-mono text-xs">{secret}</p>
|
||||
<p data-testid="two-factor-secret" className="text-center font-mono text-xs">
|
||||
{secret}
|
||||
</p>
|
||||
</>
|
||||
</WithStep>
|
||||
<Form handleSubmit={handleEnable} form={form}>
|
||||
@@ -164,7 +166,11 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
<div className="mb-4">
|
||||
<TwoFactor center />
|
||||
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||
{errorMessage && (
|
||||
<p data-testid="error-submitting-code" className="mt-1 text-sm text-red-700">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</WithStep>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
@@ -178,12 +184,16 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
</Button>
|
||||
</WithStep>
|
||||
<WithStep step={SetupStep.DisplayQrCode} current={step}>
|
||||
<Button type="submit" className="ms-2 me-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="goto-otp-screen"
|
||||
className="ms-2 me-2"
|
||||
onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
||||
<Button type="submit" className="ms-2 me-2" disabled={isSubmitting}>
|
||||
<Button type="submit" className="ms-2 me-2" data-testid="enable-2fa" disabled={isSubmitting}>
|
||||
{t("enable")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
|
||||
@@ -7,16 +7,10 @@ import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { isIpInBanlist } from "@calcom/lib/getIP";
|
||||
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
|
||||
|
||||
let cold = true;
|
||||
|
||||
const middleware: NextMiddleware = async (req) => {
|
||||
const url = req.nextUrl;
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
|
||||
// This console.log is required to create a report in axios for hot and cold requests
|
||||
console.log(cold ? "Cold Start" : "Hot Start");
|
||||
requestHeaders.set("x-cal-cold-start", cold ? "true" : "false");
|
||||
cold = false;
|
||||
if (!url.pathname.startsWith("/api")) {
|
||||
//
|
||||
// NOTE: When tRPC hits an error a 500 is returned, when this is received
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"react-intl": "^5.25.1",
|
||||
"react-live-chat-loader": "^2.7.3",
|
||||
"react-multi-email": "^0.5.3",
|
||||
"react-phone-input-2": "^2.15.1",
|
||||
"react-phone-number-input": "^3.2.7",
|
||||
"react-schemaorg": "^2.0.0",
|
||||
"react-select": "^5.7.0",
|
||||
|
||||
+3
-47
@@ -6,7 +6,7 @@ import { useEffect, useState } from "react";
|
||||
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
import { BookOpen, Check, ChevronRight, FileText } from "@calcom/ui/components/icon";
|
||||
import { BookOpen, Check, ChevronRight, FileText, Slack } from "@calcom/ui/components/icon";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
@@ -165,29 +165,7 @@ export default function Custom404() {
|
||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<svg
|
||||
viewBox="0 0 2447.6 2452.5"
|
||||
className="h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipRule="evenodd" fillRule="evenodd">
|
||||
<path
|
||||
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<Slack strokeWidth="1.5" fill="currentColor" className="h-6 w-6" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -313,29 +291,7 @@ export default function Custom404() {
|
||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<svg
|
||||
viewBox="0 0 2447.6 2452.5"
|
||||
className="h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipRule="evenodd" fillRule="evenodd">
|
||||
<path
|
||||
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
<path
|
||||
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
||||
fill="rgba(55, 65, 81)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<Slack strokeWidth="1.5" fill="currentColor" className="h-6 w-6" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -40,9 +40,9 @@ class MyDocument extends Document<Props> {
|
||||
return (
|
||||
<Html lang={locale}>
|
||||
<Head nonce={nonce}>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/api/logo?type=apple-touch-icon" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/api/logo?type=favicon-32" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/api/logo?type=favicon-16" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
|
||||
<meta name="msapplication-TileColor" content="#ff0000" />
|
||||
|
||||
@@ -6,6 +6,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
||||
import rateLimit from "@calcom/lib/rateLimit";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
@@ -13,28 +14,39 @@ const limiter = rateLimit({
|
||||
intervalInMs: 60 * 1000, // 1 minute
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const t = await getTranslation(req.body.language ?? "en", "common");
|
||||
|
||||
const email = z
|
||||
.string()
|
||||
.email()
|
||||
.transform((val) => val.toLowerCase())
|
||||
.parse(req.body?.email);
|
||||
.safeParse(req.body?.email);
|
||||
|
||||
const { isRateLimited } = limiter.check(10, email); // 10 requests per minute
|
||||
|
||||
if (isRateLimited) {
|
||||
return res.status(429).json({ message: "Too Many Requests." });
|
||||
if (!email.success) {
|
||||
return res.status(400).json({ message: "email is required" });
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).end();
|
||||
// fallback to email if ip is not present
|
||||
let ip = (req.headers["x-real-ip"] as string) ?? email.data;
|
||||
|
||||
const forwardedFor = req.headers["x-forwarded-for"] as string;
|
||||
if (!ip && forwardedFor) {
|
||||
ip = forwardedFor?.split(",").at(0) ?? email.data;
|
||||
}
|
||||
|
||||
// 10 requests per minute
|
||||
|
||||
try {
|
||||
limiter.check(10, ip);
|
||||
} catch (e) {
|
||||
return res.status(429).json({ message: "Too Many Requests." });
|
||||
}
|
||||
|
||||
try {
|
||||
const maybeUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
email,
|
||||
email: email.data,
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
@@ -97,3 +109,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(500).json({ message: "Unable to create password reset request" });
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
POST: Promise.resolve({ default: handler }),
|
||||
});
|
||||
|
||||
+116
-16
@@ -1,7 +1,18 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import {
|
||||
ANDROID_CHROME_ICON_192,
|
||||
ANDROID_CHROME_ICON_256,
|
||||
APPLE_TOUCH_ICON,
|
||||
FAVICON_16,
|
||||
FAVICON_32,
|
||||
IS_SELF_HOSTED,
|
||||
LOGO,
|
||||
LOGO_ICON,
|
||||
MSTILE_ICON,
|
||||
WEBAPP_URL,
|
||||
} from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["[api/logo]"] });
|
||||
@@ -20,11 +31,79 @@ function extractSubdomainAndDomain(hostname: string) {
|
||||
}
|
||||
|
||||
const logoApiSchema = z.object({
|
||||
icon: z.coerce.boolean().optional(),
|
||||
type: z.coerce.string().optional(),
|
||||
});
|
||||
|
||||
const SYSTEM_SUBDOMAINS = ["console", "app", "www"];
|
||||
|
||||
type LogoType =
|
||||
| "logo"
|
||||
| "icon"
|
||||
| "favicon-16"
|
||||
| "favicon-32"
|
||||
| "apple-touch-icon"
|
||||
| "mstile"
|
||||
| "android-chrome-192"
|
||||
| "android-chrome-256";
|
||||
|
||||
type LogoTypeDefinition = {
|
||||
fallback: string;
|
||||
w?: number;
|
||||
h?: number;
|
||||
source: "appLogo" | "appIconLogo";
|
||||
};
|
||||
|
||||
const logoDefinitions: Record<LogoType, LogoTypeDefinition> = {
|
||||
logo: {
|
||||
fallback: `${WEBAPP_URL}${LOGO}`,
|
||||
source: "appLogo",
|
||||
},
|
||||
icon: {
|
||||
fallback: `${WEBAPP_URL}${LOGO_ICON}`,
|
||||
source: "appIconLogo",
|
||||
},
|
||||
"favicon-16": {
|
||||
fallback: `${WEBAPP_URL}${FAVICON_16}`,
|
||||
w: 16,
|
||||
h: 16,
|
||||
source: "appIconLogo",
|
||||
},
|
||||
"favicon-32": {
|
||||
fallback: `${WEBAPP_URL}${FAVICON_32}`,
|
||||
w: 32,
|
||||
h: 32,
|
||||
source: "appIconLogo",
|
||||
},
|
||||
"apple-touch-icon": {
|
||||
fallback: `${WEBAPP_URL}${APPLE_TOUCH_ICON}`,
|
||||
w: 180,
|
||||
h: 180,
|
||||
source: "appLogo",
|
||||
},
|
||||
mstile: {
|
||||
fallback: `${WEBAPP_URL}${MSTILE_ICON}`,
|
||||
w: 150,
|
||||
h: 150,
|
||||
source: "appLogo",
|
||||
},
|
||||
"android-chrome-192": {
|
||||
fallback: `${WEBAPP_URL}${ANDROID_CHROME_ICON_192}`,
|
||||
w: 192,
|
||||
h: 192,
|
||||
source: "appLogo",
|
||||
},
|
||||
"android-chrome-256": {
|
||||
fallback: `${WEBAPP_URL}${ANDROID_CHROME_ICON_256}`,
|
||||
w: 256,
|
||||
h: 256,
|
||||
source: "appLogo",
|
||||
},
|
||||
};
|
||||
|
||||
function isValidLogoType(type: string): type is LogoType {
|
||||
return type in logoDefinitions;
|
||||
}
|
||||
|
||||
async function getTeamLogos(subdomain: string) {
|
||||
try {
|
||||
if (
|
||||
@@ -39,7 +118,7 @@ async function getTeamLogos(subdomain: string) {
|
||||
}
|
||||
// load from DB
|
||||
const { default: prisma } = await import("@calcom/prisma");
|
||||
const team = await prisma.team.findUniqueOrThrow({
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
slug: subdomain,
|
||||
},
|
||||
@@ -48,16 +127,16 @@ async function getTeamLogos(subdomain: string) {
|
||||
appIconLogo: true,
|
||||
},
|
||||
});
|
||||
// try to use team logos, otherwise default to LOGO/LOGO_ICON regardless
|
||||
|
||||
return {
|
||||
appLogo: team.appLogo || `${WEBAPP_URL}${LOGO}`,
|
||||
appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`,
|
||||
appLogo: team?.appLogo,
|
||||
appIconLogo: team?.appIconLogo,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) log.debug(error.message);
|
||||
return {
|
||||
appLogo: `${WEBAPP_URL}${LOGO}`,
|
||||
appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`,
|
||||
appLogo: undefined,
|
||||
appIconLogo: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -75,14 +154,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
if (!domains) throw new Error("No domains");
|
||||
|
||||
const [subdomain] = domains;
|
||||
const { appLogo, appIconLogo } = await getTeamLogos(subdomain);
|
||||
const teamLogos = await getTeamLogos(subdomain);
|
||||
|
||||
const filteredLogo = parsedQuery?.icon ? appIconLogo : appLogo;
|
||||
// Resolve all icon types to team logos, falling back to Cal.com defaults.
|
||||
const type: LogoType = parsedQuery?.type && isValidLogoType(parsedQuery.type) ? parsedQuery.type : "logo";
|
||||
const logoDefinition = logoDefinitions[type];
|
||||
const filteredLogo = teamLogos[logoDefinition.source] ?? logoDefinition.fallback;
|
||||
|
||||
const response = await fetch(filteredLogo);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
res.setHeader("Content-Type", response.headers.get("content-type") as string);
|
||||
res.setHeader("Cache-Control", "s-maxage=86400");
|
||||
res.send(buffer);
|
||||
try {
|
||||
const response = await fetch(filteredLogo);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
let buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
// If we need to resize the team logos (via Next.js' built-in image processing)
|
||||
if (teamLogos[logoDefinition.source] && logoDefinition.w) {
|
||||
const { detectContentType, optimizeImage } = await import("next/dist/server/image-optimizer");
|
||||
buffer = await optimizeImage({
|
||||
buffer,
|
||||
contentType: detectContentType(buffer) ?? "image/jpeg",
|
||||
quality: 100,
|
||||
width: logoDefinition.w,
|
||||
height: logoDefinition.h, // optional
|
||||
});
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", response.headers.get("content-type") as string);
|
||||
res.setHeader("Cache-Control", "s-maxage=86400");
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
res.statusCode = 404;
|
||||
res.json({ error: "Failed fetching logo" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import fs from "fs";
|
||||
import matter from "gray-matter";
|
||||
import MarkdownIt from "markdown-it";
|
||||
@@ -88,8 +89,18 @@ function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
|
||||
}
|
||||
|
||||
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
|
||||
const appStore = await prisma.app.findMany({ select: { slug: true } });
|
||||
const paths = appStore.map(({ slug }) => ({ params: { slug } }));
|
||||
let paths: { params: { slug: string } }[] = [];
|
||||
|
||||
try {
|
||||
const appStore = await prisma.app.findMany({ select: { slug: true } });
|
||||
paths = appStore.map(({ slug }) => ({ params: { slug } }));
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Prisma.PrismaClientInitializationError) {
|
||||
// Database is not available at build time, but that's ok – we fall back to resolving paths on demand
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
paths,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -54,6 +55,20 @@ Apps.PageWrapper = PageWrapper;
|
||||
export const getStaticPaths = async () => {
|
||||
const paths = Object.keys(AppCategories);
|
||||
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Prisma.PrismaClientInitializationError) {
|
||||
// Database is not available at build time. Make sure we fall back to building these pages on demand
|
||||
return {
|
||||
paths: [],
|
||||
fallback: "blocking",
|
||||
};
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
paths: paths.map((category) => ({ params: { category } })),
|
||||
fallback: false,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import type { InferGetStaticPropsType } from "next";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
import { ArrowLeft, ArrowRight } from "@calcom/ui/components/icon";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export default function Apps({ categories }: inferSSRProps<typeof getServerSideProps>) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
return (
|
||||
@@ -47,8 +51,20 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
|
||||
|
||||
Apps.PageWrapper = PageWrapper;
|
||||
|
||||
export const getStaticProps = async () => {
|
||||
const appStore = await getAppRegistry();
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req, res } = context;
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const session = await getServerSession({ req, res });
|
||||
|
||||
let appStore;
|
||||
if (session?.user?.id) {
|
||||
appStore = await getAppRegistryWithCredentials(session.user.id);
|
||||
} else {
|
||||
appStore = await getAppRegistry();
|
||||
}
|
||||
|
||||
const categories = appStore.reduce((c, app) => {
|
||||
for (const category of app.categories) {
|
||||
c[category] = c[category] ? c[category] + 1 : 1;
|
||||
@@ -59,6 +75,7 @@ export const getStaticProps = async () => {
|
||||
return {
|
||||
props: {
|
||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -279,7 +279,9 @@ export default function Success(props: SuccessProps) {
|
||||
darkBrandColor: props.profile.darkBrandColor,
|
||||
});
|
||||
const title = t(
|
||||
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
||||
`booking_${needsConfirmation ? "booking_submitted" : "confirmed"}${
|
||||
props.recurringBookings ? "_recurring" : ""
|
||||
}`
|
||||
);
|
||||
|
||||
const locationToDisplay = getSuccessPageLocationMessage(
|
||||
@@ -364,7 +366,7 @@ export default function Success(props: SuccessProps) {
|
||||
{needsConfirmation && !isCancelled
|
||||
? props.recurringBookings
|
||||
? t("submitted_recurring")
|
||||
: t("submitted")
|
||||
: t("booking_submitted")
|
||||
: isCancelled
|
||||
? seatReferenceUid
|
||||
? t("no_longer_attending")
|
||||
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
CreateButton,
|
||||
HorizontalTabs,
|
||||
HeadSeo,
|
||||
Skeleton,
|
||||
Label,
|
||||
} from "@calcom/ui";
|
||||
import {
|
||||
ArrowDown,
|
||||
@@ -416,7 +418,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
{!isManagedEventType && (
|
||||
<>
|
||||
{type.hidden && <Badge variant="gray">{t("hidden")}</Badge>}
|
||||
<Tooltip content={t("show_eventtype_on_profile")}>
|
||||
<Tooltip
|
||||
content={type.hidden ? t("show_eventtype_on_profile") : t("hide_from_profile")}>
|
||||
<div className="self-center rounded-md p-2">
|
||||
<Switch
|
||||
name="Hidden"
|
||||
@@ -607,7 +610,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
|
||||
!isChildrenManagedEventType && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
@@ -623,6 +625,25 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{!isManagedEventType && (
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 inline cursor-pointer self-center pr-2 ">
|
||||
{type.hidden ? t("show_eventtype_on_profile") : t("hide_from_profile")}
|
||||
</Skeleton>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
name="Hidden"
|
||||
checked={!type.hidden}
|
||||
onCheckedChange={() => {
|
||||
setHiddenMutation.mutate({ id: type.id, hidden: !type.hidden });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</Dropdown>
|
||||
|
||||
@@ -66,7 +66,15 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||
}
|
||||
|
||||
await ssr.viewer.public.event.prefetch({ username: user, eventSlug: slug });
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username: user, eventSlug: slug });
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -107,7 +115,15 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||
}
|
||||
|
||||
await ssr.viewer.public.event.prefetch({ username, eventSlug: slug });
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -66,7 +66,17 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||
}
|
||||
await ssr.viewer.public.event.prefetch({ username: teamSlug, eventSlug: meetingSlug });
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
booking,
|
||||
|
||||
@@ -67,12 +67,14 @@ const AppearanceView = () => {
|
||||
|
||||
const {
|
||||
formState: { isSubmitting, isDirty },
|
||||
reset,
|
||||
} = formMethods;
|
||||
|
||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: async () => {
|
||||
onSuccess: async (data) => {
|
||||
await utils.viewer.me.invalidate();
|
||||
showToast(t("settings_updated_successfully"), "success");
|
||||
reset(data);
|
||||
},
|
||||
onError: () => {
|
||||
showToast(t("error_updating_settings"), "error");
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Label,
|
||||
Meta,
|
||||
Select,
|
||||
SettingsToggle,
|
||||
showToast,
|
||||
SkeletonButton,
|
||||
SkeletonContainer,
|
||||
@@ -120,6 +121,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
||||
value: user.weekStart,
|
||||
label: nameOfDay(localeProp, user.weekStart === "Sunday" ? 0 : 1),
|
||||
},
|
||||
allowDynamicBooking: user.allowDynamicBooking ?? true,
|
||||
},
|
||||
});
|
||||
const {
|
||||
@@ -213,6 +215,22 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
<Controller
|
||||
name="allowDynamicBooking"
|
||||
control={formMethods.control}
|
||||
render={() => (
|
||||
<SettingsToggle
|
||||
title={t("dynamic_booking")}
|
||||
description={t("allow_dynamic_booking")}
|
||||
checked={formMethods.getValues("allowDynamicBooking")}
|
||||
onCheckedChange={(checked) => {
|
||||
formMethods.setValue("allowDynamicBooking", checked, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isDisabled} color="primary" type="submit" className="mt-8">
|
||||
<>{t("update")}</>
|
||||
</Button>
|
||||
|
||||
@@ -203,6 +203,7 @@ const ProfileView = () => {
|
||||
<ProfileForm
|
||||
key={JSON.stringify(defaultValues)}
|
||||
defaultValues={defaultValues}
|
||||
isLoading={mutation.isLoading}
|
||||
onSubmit={(values) => {
|
||||
if (values.email !== user.email && isCALIdentityProviver) {
|
||||
setTempFormValues(values);
|
||||
@@ -314,10 +315,12 @@ const ProfileForm = ({
|
||||
defaultValues,
|
||||
onSubmit,
|
||||
extraField,
|
||||
isLoading = false,
|
||||
}: {
|
||||
defaultValues: FormValues;
|
||||
onSubmit: (values: FormValues) => void;
|
||||
extraField?: React.ReactNode;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
@@ -390,7 +393,7 @@ const ProfileForm = ({
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
|
||||
<Button loading={isLoading} disabled={isDisabled} color="primary" className="mt-8" type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
@@ -40,6 +40,7 @@ const TwoFactorAuthView = () => {
|
||||
{!isCalProvider && <Alert severity="neutral" message={t("2fa_disabled")} />}
|
||||
<div className="mt-6 flex items-start space-x-4">
|
||||
<Switch
|
||||
data-testid="two-factor-switch"
|
||||
disabled={!isCalProvider}
|
||||
checked={user?.twoFactorEnabled}
|
||||
onCheckedChange={() =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { test } from "../lib/fixtures";
|
||||
import { todo } from "../lib/testUtils";
|
||||
|
||||
test.describe("Can signup from a team invite", async () => {
|
||||
test.beforeEach(async ({ users }) => {
|
||||
@@ -96,5 +95,3 @@ test.describe("Can signup from a team invite", async () => {
|
||||
expect(createdUser.teams[0].accepted).toBe(true);*/
|
||||
});
|
||||
});
|
||||
|
||||
todo("Can login using 2FA");
|
||||
|
||||
@@ -13,6 +13,7 @@ test("Can delete user account", async ({ page, users }) => {
|
||||
await page.waitForSelector("[data-testid=dashboard-shell]");
|
||||
|
||||
await page.goto(`/settings/my-account/profile`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.click("[data-testid=delete-account]");
|
||||
if (!user.username) throw Error(`Test user doesn't have a username`);
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
|
||||
// Click text=Submit
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await page.waitForSelector("text=Password updated", {
|
||||
timeout: 3000,
|
||||
});
|
||||
await page.waitForSelector("text=Password updated");
|
||||
|
||||
await expect(page.locator(`text=Password updated`)).toBeVisible();
|
||||
// Click button:has-text("Login")
|
||||
|
||||
@@ -65,7 +65,6 @@ testBothBookers.describe("pro user", () => {
|
||||
});
|
||||
|
||||
test("pro user's page has at least 2 visible events", async ({ page }) => {
|
||||
// await page.pause();
|
||||
const $eventTypes = page.locator("[data-testid=event-types] > *");
|
||||
expect(await $eventTypes.count()).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
@@ -99,373 +99,367 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
|
||||
// todo("Attendee #1 should be able to reschedule his booking");
|
||||
// TODO: Make E2E test: All attendees canceling should delete the booking for the User
|
||||
// todo("All attendees canceling should delete the booking for the User");
|
||||
});
|
||||
|
||||
testBothBookers.describe("Reschedule for booking with seats", () => {
|
||||
test("Should reschedule booking with seats", async ({ page, users, bookings }) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[2].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
|
||||
await page.goto(`/reschedule/${references[2].referenceUid}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
// expect input to be filled with attendee number 3 data
|
||||
const thirdAttendeeElement = await page.locator("input[name=name]");
|
||||
const attendeeName = await thirdAttendeeElement.inputValue();
|
||||
expect(attendeeName).toBe("John Third");
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
// Should expect old booking to be accepted with two attendees
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED);
|
||||
expect(oldBooking?.attendees.length).toBe(2);
|
||||
expect(oldBooking?.seatsReferences.length).toBe(2);
|
||||
testBothBookers.describe("Reschedule for booking with seats", () => {
|
||||
test("Should reschedule booking with seats", async ({ page, users, bookings }) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
test("Should reschedule booking with seats and if everyone rescheduled it should be deleted", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[2].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
|
||||
await page.goto(`/reschedule/${references[0].referenceUid}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
await page.waitForURL(/.*booking/);
|
||||
|
||||
await page.goto(`/reschedule/${references[1].referenceUid}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled
|
||||
await page.waitForNavigation({ url: /.*booking/ });
|
||||
|
||||
// Should expect old booking to be cancelled
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: {
|
||||
seatsReferences: true,
|
||||
attendees: true,
|
||||
eventType: {
|
||||
include: { users: true, hosts: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
test("Should cancel with seats and have no attendees and cancelled", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Now we cancel the booking as the organizer
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true`);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
// Should expect old booking to be cancelled
|
||||
const updatedBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
expect(oldBooking?.startTime).not.toBe(updatedBooking?.startTime);
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
|
||||
test("If rescheduled/cancelled booking with seats it should display the correct number of seats", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await page.goto(`/reschedule/${references[2].referenceUid}`);
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
// expect input to be filled with attendee number 3 data
|
||||
const thirdAttendeeElement = await page.locator("input[name=name]");
|
||||
const attendeeName = await thirdAttendeeElement.inputValue();
|
||||
expect(attendeeName).toBe("John Third");
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.goto(
|
||||
`/booking/${references[0].referenceUid}?cancel=true&seatReferenceUid=${references[0].referenceUid}`
|
||||
);
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED);
|
||||
|
||||
await page.goto(`/reschedule/${references[1].referenceUid}`);
|
||||
|
||||
await page.click('[data-testid="incrementMonth"]');
|
||||
|
||||
await page.locator('[data-testid="day"][data-disabled="false"]').nth(1).click();
|
||||
|
||||
// Validate that the number of seats its 10
|
||||
expect(await page.locator("text=9 / 10 Seats available").count()).toEqual(0);
|
||||
// Should expect old booking to be accepted with two attendees
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
test("Should cancel with seats but event should be still accesible and with one less attendee/seat", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED);
|
||||
expect(oldBooking?.attendees.length).toBe(2);
|
||||
expect(oldBooking?.seatsReferences.length).toBe(2);
|
||||
});
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
test("Should reschedule booking with seats and if everyone rescheduled it should be deleted", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Now we cancel the booking as the first attendee
|
||||
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
|
||||
);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
|
||||
);
|
||||
|
||||
// Page should not be 404
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
test("Should book with seats and hide attendees info from showAttendees true", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
const bookingWithEventType = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
select: {
|
||||
id: true,
|
||||
eventTypeId: true,
|
||||
},
|
||||
});
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.eventType.update({
|
||||
data: {
|
||||
seatsShowAttendees: false,
|
||||
},
|
||||
where: {
|
||||
id: bookingWithEventType?.eventTypeId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Go to cancel page and see that attendees are listed and myself as I'm owner of the booking
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true&allRemainingBookings=false`);
|
||||
|
||||
const foundFirstAttendeeAsOwner = await page.locator(
|
||||
'p[data-testid="attendee-email-first+seats@cal.com"]'
|
||||
);
|
||||
await expect(foundFirstAttendeeAsOwner).toHaveCount(1);
|
||||
const foundSecondAttendeeAsOwner = await page.locator(
|
||||
'p[data-testid="attendee-email-second+seats@cal.com"]'
|
||||
);
|
||||
await expect(foundSecondAttendeeAsOwner).toHaveCount(1);
|
||||
await page.pause();
|
||||
await page.goto("auth/logout");
|
||||
|
||||
// Now we cancel the booking as the first attendee
|
||||
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
|
||||
);
|
||||
|
||||
// No attendees should be displayed only the one that it's cancelling
|
||||
const notFoundSecondAttendee = await page.locator(
|
||||
'p[data-testid="attendee-email-second+seats@cal.com"]'
|
||||
);
|
||||
|
||||
await expect(notFoundSecondAttendee).toHaveCount(0);
|
||||
const foundFirstAttendee = await page.locator('p[data-testid="attendee-email-first+seats@cal.com"]');
|
||||
await expect(foundFirstAttendee).toHaveCount(1);
|
||||
|
||||
await prisma.eventType.update({
|
||||
data: {
|
||||
seatsShowAttendees: true,
|
||||
},
|
||||
where: {
|
||||
id: bookingWithEventType?.eventTypeId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
|
||||
);
|
||||
|
||||
// Now attendees should be displayed
|
||||
const foundSecondAttendee = await page.locator('p[data-testid="attendee-email-second+seats@cal.com"]');
|
||||
|
||||
await expect(foundSecondAttendee).toHaveCount(1);
|
||||
const foundFirstAttendeeAgain = await page
|
||||
.locator('p[data-testid="attendee-email-first+seats@cal.com"]')
|
||||
.first();
|
||||
await expect(foundFirstAttendeeAgain).toHaveCount(1);
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
|
||||
await page.goto(`/reschedule/${references[0].referenceUid}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
await page.waitForURL(/.*booking/);
|
||||
|
||||
await page.goto(`/reschedule/${references[1].referenceUid}`);
|
||||
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled
|
||||
await page.waitForNavigation({ url: /.*booking/ });
|
||||
|
||||
// Should expect old booking to be cancelled
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: {
|
||||
seatsReferences: true,
|
||||
attendees: true,
|
||||
eventType: {
|
||||
include: { users: true, hosts: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);
|
||||
});
|
||||
|
||||
test("Should cancel with seats and have no attendees and cancelled", async ({ page, users, bookings }) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Now we cancel the booking as the organizer
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true`);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
// Should expect old booking to be cancelled
|
||||
const updatedBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
include: { seatsReferences: true, attendees: true },
|
||||
});
|
||||
|
||||
expect(oldBooking?.startTime).not.toBe(updatedBooking?.startTime);
|
||||
});
|
||||
|
||||
test("If rescheduled/cancelled booking with seats it should display the correct number of seats", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
const references = await prisma.bookingSeat.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
});
|
||||
|
||||
await page.goto(
|
||||
`/booking/${references[0].referenceUid}?cancel=true&seatReferenceUid=${references[0].referenceUid}`
|
||||
);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
const oldBooking = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(oldBooking?.status).toBe(BookingStatus.ACCEPTED);
|
||||
|
||||
await page.goto(`/reschedule/${references[1].referenceUid}`);
|
||||
|
||||
await page.click('[data-testid="incrementMonth"]');
|
||||
|
||||
await page.locator('[data-testid="day"][data-disabled="false"]').nth(1).click();
|
||||
|
||||
// Validate that the number of seats its 10
|
||||
expect(await page.locator("text=9 / 10 Seats available").count()).toEqual(0);
|
||||
});
|
||||
|
||||
test("Should cancel with seats but event should be still accesible and with one less attendee/seat", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Now we cancel the booking as the first attendee
|
||||
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
|
||||
);
|
||||
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
|
||||
);
|
||||
|
||||
// Page should not be 404
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page).toHaveURL(/.*booking/);
|
||||
});
|
||||
|
||||
test("Should book with seats and hide attendees info from showAttendees true", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
|
||||
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
{ name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" },
|
||||
]);
|
||||
await user.login();
|
||||
const bookingWithEventType = await prisma.booking.findFirst({
|
||||
where: { uid: booking.uid },
|
||||
select: {
|
||||
id: true,
|
||||
eventTypeId: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.eventType.update({
|
||||
data: {
|
||||
seatsShowAttendees: false,
|
||||
},
|
||||
where: {
|
||||
id: bookingWithEventType?.eventTypeId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingAttendees = await prisma.attendee.findMany({
|
||||
where: { bookingId: booking.id },
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const bookingSeats = [
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
|
||||
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
|
||||
];
|
||||
|
||||
await prisma.bookingSeat.createMany({
|
||||
data: bookingSeats,
|
||||
});
|
||||
|
||||
// Go to cancel page and see that attendees are listed and myself as I'm owner of the booking
|
||||
await page.goto(`/booking/${booking.uid}?cancel=true&allRemainingBookings=false`);
|
||||
|
||||
const foundFirstAttendeeAsOwner = await page.locator(
|
||||
'p[data-testid="attendee-email-first+seats@cal.com"]'
|
||||
);
|
||||
await expect(foundFirstAttendeeAsOwner).toHaveCount(1);
|
||||
const foundSecondAttendeeAsOwner = await page.locator(
|
||||
'p[data-testid="attendee-email-second+seats@cal.com"]'
|
||||
);
|
||||
await expect(foundSecondAttendeeAsOwner).toHaveCount(1);
|
||||
|
||||
await page.goto("auth/logout");
|
||||
|
||||
// Now we cancel the booking as the first attendee
|
||||
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
|
||||
);
|
||||
|
||||
// No attendees should be displayed only the one that it's cancelling
|
||||
const notFoundSecondAttendee = await page.locator('p[data-testid="attendee-email-second+seats@cal.com"]');
|
||||
|
||||
await expect(notFoundSecondAttendee).toHaveCount(0);
|
||||
const foundFirstAttendee = await page.locator('p[data-testid="attendee-email-first+seats@cal.com"]');
|
||||
await expect(foundFirstAttendee).toHaveCount(1);
|
||||
|
||||
await prisma.eventType.update({
|
||||
data: {
|
||||
seatsShowAttendees: true,
|
||||
},
|
||||
where: {
|
||||
id: bookingWithEventType?.eventTypeId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
await page.goto(
|
||||
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
|
||||
);
|
||||
|
||||
// Now attendees should be displayed
|
||||
const foundSecondAttendee = await page.locator('p[data-testid="attendee-email-second+seats@cal.com"]');
|
||||
|
||||
await expect(foundSecondAttendee).toHaveCount(1);
|
||||
const foundFirstAttendeeAgain = await page
|
||||
.locator('p[data-testid="attendee-email-first+seats@cal.com"]')
|
||||
.first();
|
||||
await expect(foundFirstAttendeeAgain).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,7 +178,7 @@ test.describe("Embed Code Generator Tests", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/event-types`);
|
||||
await Promise.all([
|
||||
page.locator('[href*="/event-types/"]').first().click(),
|
||||
page.locator('a[href*="/event-types/"]').first().click(),
|
||||
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ test.fixme("hash my url", () => {
|
||||
await users.deleteAll();
|
||||
});
|
||||
test("generate url hash", async ({ page }) => {
|
||||
// await page.pause();
|
||||
await page.goto("/event-types");
|
||||
// We wait until loading is finished
|
||||
await page.waitForSelector('[data-testid="event-types"]');
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { authenticator } from "otplib";
|
||||
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
// a test to logout requires both a succesfull login as logout, to prevent
|
||||
// a doubling of tests failing on logout & logout, we can group them.
|
||||
test.describe("2FA Tests", async () => {
|
||||
test.afterAll(async ({ users }) => {
|
||||
await users.deleteAll();
|
||||
});
|
||||
test("should allow a user to enable 2FA and login using 2FA", async ({ page, users }) => {
|
||||
// log in trail user
|
||||
const user = await test.step("Enable 2FA", async () => {
|
||||
const user = await users.create();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const userPassword = user.username!;
|
||||
await user.login();
|
||||
|
||||
// expects the home page for an authorized user
|
||||
await page.goto("/settings/security/two-factor-auth");
|
||||
await page.click(`[data-testid=two-factor-switch]`);
|
||||
await page.fill('input[name="password"]', userPassword);
|
||||
await page.press('input[name="password"]', "Enter");
|
||||
const secret = await page.locator(`[data-testid=two-factor-secret]`).textContent();
|
||||
expect(secret).toHaveLength(32);
|
||||
await page.click('[data-testid="goto-otp-screen"]');
|
||||
|
||||
/**
|
||||
* Try a wrong code and test that wrong code is rejected.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await fillOtp({ page, secret: "123456", noRetry: true });
|
||||
await page.press('input[name="2fa6"]', "Enter");
|
||||
await expect(page.locator('[data-testid="error-submitting-code"]')).toBeVisible();
|
||||
|
||||
await fillOtp({
|
||||
page,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
secret: secret!,
|
||||
});
|
||||
await page.press('input[name="2fa6"]', "Enter");
|
||||
|
||||
await expect(page.locator(`[data-testid=two-factor-switch][data-state="checked"]`)).toBeVisible();
|
||||
|
||||
return user;
|
||||
});
|
||||
|
||||
await test.step("Logout", async () => {
|
||||
await page.goto("/auth/logout");
|
||||
});
|
||||
|
||||
await test.step("Login with 2FA enabled", async () => {
|
||||
await user.login();
|
||||
const userWith2FaSecret = await prisma?.user.findFirst({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const secret = symmetricDecrypt(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
userWith2FaSecret!.twoFactorSecret!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
process.env.CALENDSO_ENCRYPTION_KEY!
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await fillOtp({ page, secret: secret! });
|
||||
await Promise.all([
|
||||
page.press('input[name="2fa6"]', "Enter"),
|
||||
page.waitForResponse("**/api/auth/callback/credentials**"),
|
||||
]);
|
||||
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
|
||||
|
||||
// expects the home page for an authorized user
|
||||
await page.goto("/");
|
||||
await expect(shellLocator).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("should allow a user to disable 2FA", async ({ page, users }) => {
|
||||
// log in trail user
|
||||
const user = await test.step("Enable 2FA", async () => {
|
||||
const user = await users.create();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const userPassword = user.username!;
|
||||
await user.login();
|
||||
|
||||
// expects the home page for an authorized user
|
||||
await page.goto("/settings/security/two-factor-auth");
|
||||
await page.click(`[data-testid=two-factor-switch][data-state="unchecked"]`);
|
||||
await page.fill('input[name="password"]', userPassword);
|
||||
await page.press('input[name="password"]', "Enter");
|
||||
const secret = await page.locator(`[data-testid=two-factor-secret]`).textContent();
|
||||
expect(secret).toHaveLength(32);
|
||||
await page.click('[data-testid="goto-otp-screen"]');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await fillOtp({ page, secret: secret! });
|
||||
await page.click('[data-testid="enable-2fa"]');
|
||||
await expect(page.locator(`[data-testid=two-factor-switch][data-state="checked"]`)).toBeVisible();
|
||||
|
||||
return user;
|
||||
});
|
||||
|
||||
await test.step("Disable 2FA", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const userPassword = user.username!;
|
||||
|
||||
// expects the home page for an authorized user
|
||||
await page.goto("/settings/security/two-factor-auth");
|
||||
await page.click(`[data-testid=two-factor-switch][data-state="checked"]`);
|
||||
await page.fill('input[name="password"]', userPassword);
|
||||
|
||||
const userWith2FaSecret = await prisma?.user.findFirst({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const secret = symmetricDecrypt(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
userWith2FaSecret!.twoFactorSecret!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
process.env.CALENDSO_ENCRYPTION_KEY!
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await fillOtp({ page, secret: secret! });
|
||||
await page.click('[data-testid="disable-2fa"]');
|
||||
await expect(page.locator(`[data-testid=two-factor-switch][data-state="unchecked"]`)).toBeVisible();
|
||||
|
||||
return user;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function fillOtp({ page, secret, noRetry }: { page: Page; secret: string; noRetry?: boolean }) {
|
||||
let token = authenticator.generate(secret);
|
||||
if (!noRetry && !authenticator.check(token, secret)) {
|
||||
console.log("Token expired, Renerating.");
|
||||
// Maybe token was just about to expire, try again just once more
|
||||
token = authenticator.generate(secret);
|
||||
}
|
||||
await page.fill('input[name="2fa1"]', token[0]);
|
||||
await page.fill('input[name="2fa2"]', token[1]);
|
||||
await page.fill('input[name="2fa3"]', token[2]);
|
||||
await page.fill('input[name="2fa4"]', token[3]);
|
||||
await page.fill('input[name="2fa5"]', token[4]);
|
||||
await page.fill('input[name="2fa6"]', token[5]);
|
||||
}
|
||||
@@ -35,11 +35,9 @@ test.describe("user can login & logout succesfully", async () => {
|
||||
const signOutBtn = await page.locator(`text=${signOutLabel}`);
|
||||
await signOutBtn.click();
|
||||
|
||||
// 2s of delay to assure the session is cleared
|
||||
await page.waitForURL("/auth/logout");
|
||||
await page.locator('a[href="/auth/login"]').click();
|
||||
|
||||
// Reroute to the home page to check if the login form shows up
|
||||
await page.goto("/");
|
||||
await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
+365
-109
@@ -1,131 +1,387 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { createHttpServer, selectFirstAvailableTimeSlotNextMonth, waitFor } from "./lib/testUtils";
|
||||
import {
|
||||
bookOptinEvent,
|
||||
createHttpServer,
|
||||
selectFirstAvailableTimeSlotNextMonth,
|
||||
waitFor,
|
||||
} from "./lib/testUtils";
|
||||
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
test("add webhook & test that creating an event triggers a webhook call", async ({
|
||||
page,
|
||||
users,
|
||||
}, testInfo) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
const user = await users.create();
|
||||
const [eventType] = user.eventTypes;
|
||||
await user.login();
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
test.describe("BOOKING_CREATED", async () => {
|
||||
test("add webhook & test that creating an event triggers a webhook call", async ({
|
||||
page,
|
||||
users,
|
||||
}, testInfo) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
const user = await users.create();
|
||||
const [eventType] = user.eventTypes;
|
||||
await user.login();
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
|
||||
// --- Book the first available day next month in the pro user's "30min"-event
|
||||
await page.goto(`/${user.username}/${eventType.slug}`);
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
// --- Book the first available day next month in the pro user's "30min"-event
|
||||
await page.goto(`/${user.username}/${eventType.slug}`);
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
// --- fill form
|
||||
await page.fill('[name="name"]', "Test Testson");
|
||||
await page.fill('[name="email"]', "test@example.com");
|
||||
await page.press('[name="email"]', "Enter");
|
||||
// --- fill form
|
||||
await page.fill('[name="name"]', "Test Testson");
|
||||
await page.fill('[name="email"]', "test@example.com");
|
||||
await page.press('[name="email"]', "Enter");
|
||||
|
||||
// --- check that webhook was called
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
// --- check that webhook was called
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
body.payload.metadata.videoCallUrl = dynamic;
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "30 min",
|
||||
title: "30 min between Nameless and Test Testson",
|
||||
description: "",
|
||||
additionalNotes: "",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Nameless",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email_address",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "your_name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "30 min",
|
||||
title: "30 min between Nameless and Test Testson",
|
||||
description: "",
|
||||
additionalNotes: "",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Nameless",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
hideCalendarNotes: false,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
seatsShowAttendees: true,
|
||||
seatsPerTimeSlot: null,
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "30 min",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
status: "ACCEPTED",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
},
|
||||
});
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email_address",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "your_name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
hideCalendarNotes: false,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
seatsShowAttendees: true,
|
||||
seatsPerTimeSlot: null,
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "30 min",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
status: "ACCEPTED",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
},
|
||||
});
|
||||
|
||||
webhookReceiver.close();
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("BOOKING_REJECTED", async () => {
|
||||
test("can book an event that requires confirmation and then that booking can be rejected by organizer", async ({
|
||||
page,
|
||||
users,
|
||||
}) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
// --- create a user
|
||||
const user = await users.create();
|
||||
|
||||
// --- visit user page
|
||||
await page.goto(`/${user.username}`);
|
||||
|
||||
// --- book the user's event
|
||||
await bookOptinEvent(page);
|
||||
|
||||
// --- login as that user
|
||||
await user.login();
|
||||
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
|
||||
await page.goto("/bookings/unconfirmed");
|
||||
await page.click('[data-testid="reject"]');
|
||||
await page.click('[data-testid="rejection-confirm"]');
|
||||
await page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm"));
|
||||
|
||||
// --- check that webhook was called
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
// body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_REJECTED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "Opt in",
|
||||
title: "Opt in between Nameless and Test Testson",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Unnamed",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
// hideCalendarNotes: false,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "Opt in",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
// metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
status: "REJECTED",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
},
|
||||
});
|
||||
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("BOOKING_REQUESTED", async () => {
|
||||
test("can book an event that requires confirmation and get a booking requested event", async ({
|
||||
page,
|
||||
users,
|
||||
}) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
// --- create a user
|
||||
const user = await users.create();
|
||||
|
||||
// --- login as that user
|
||||
await user.login();
|
||||
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
|
||||
// --- visit user page
|
||||
await page.goto(`/${user.username}`);
|
||||
|
||||
// --- book the user's opt in
|
||||
await bookOptinEvent(page);
|
||||
|
||||
// --- check that webhook was called
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "Opt in",
|
||||
title: "Opt in between Nameless and Test Testson",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Nameless",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email_address",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "your_name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "Opt in",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
status: "PENDING",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
},
|
||||
});
|
||||
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<square150x150logo src="/api/logo?type=mstile"/>
|
||||
<TileColor>#ff0000</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
"short_name": "Cal.com",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"src": "/api/logo?type=android-chrome-192",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-256x256.png",
|
||||
"src": "/api/logo?type=android-chrome-256",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@
|
||||
"page_doesnt_exist": "This page does not exist.",
|
||||
"check_spelling_mistakes_or_go_back": "Check for spelling mistakes or go back to the previous page.",
|
||||
"404_page_not_found": "404: This page could not be found.",
|
||||
"booker_event_not_found": "We could not find the event you are trying to book.",
|
||||
"getting_started": "Getting Started",
|
||||
"15min_meeting": "15 Min Meeting",
|
||||
"30min_meeting": "30 Min Meeting",
|
||||
@@ -272,7 +273,6 @@
|
||||
"user_needs_to_confirm_or_reject_booking_recurring": "{{user}} still needs to confirm or reject each booking of the recurring meeting.",
|
||||
"meeting_is_scheduled": "This meeting is scheduled",
|
||||
"meeting_is_scheduled_recurring": "The recurring events are scheduled",
|
||||
"submitted": "Your booking has been submitted",
|
||||
"submitted_recurring": "Your recurring meeting has been submitted",
|
||||
"booking_submitted": "Your booking has been submitted",
|
||||
"booking_submitted_recurring": "Your recurring meeting has been submitted",
|
||||
@@ -364,6 +364,7 @@
|
||||
"user_dynamic_booking_disabled": "Some of the users in the group have currently disabled dynamic group bookings",
|
||||
"allow_dynamic_booking_tooltip": "Group booking links that can be created dynamically by adding multiple usernames with a '+'. example: '{{appName}}/bailey+peer'",
|
||||
"allow_dynamic_booking": "Allow attendees to book you through dynamic group bookings",
|
||||
"dynamic_booking": "Dynamic group links",
|
||||
"email": "Email",
|
||||
"email_placeholder": "jdoe@example.com",
|
||||
"full_name": "Full name",
|
||||
@@ -374,6 +375,8 @@
|
||||
"booking_rescheduled": "Booking Rescheduled",
|
||||
"recording_ready":"Recording Download Link Ready",
|
||||
"booking_created": "Booking Created",
|
||||
"booking_rejected":"Booking Rejected",
|
||||
"booking_requested":"Booking Requested",
|
||||
"meeting_ended": "Meeting Ended",
|
||||
"form_submitted": "Form Submitted",
|
||||
"event_triggers": "Event Triggers",
|
||||
@@ -1816,6 +1819,7 @@
|
||||
"email_invite_team_bulk": "{{userCount}} users have been invited",
|
||||
"error_collecting_card": "Error collecting card",
|
||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit",
|
||||
"unauthorized_workflow_error_message": "{{errorCode}}: You are not authorized to enable or disable this workflow",
|
||||
"inline_embed": "Inline Embed",
|
||||
"load_inline_content": "Loads your event type directly inline with your other website content.",
|
||||
"floating_pop_up_button": "Floating pop-up button",
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Rechazar la reserva",
|
||||
"manage_this_event": "Administrar este evento",
|
||||
"invite_team_member": "Invite a un miembro del equipo",
|
||||
"invite_team_individual_segment": "Invitar individualmente",
|
||||
"invite_team_bulk_segment": "Importar en masa",
|
||||
"invite_team_notifcation_badge": "Inv.",
|
||||
"your_event_has_been_scheduled": "Tu evento ha sido reprogramado",
|
||||
"your_event_has_been_scheduled_recurring": "Su evento recurrente se ha programado",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Volver a la página de inicio",
|
||||
"error_during_login": "Se ha producido un error al iniciar sesión. Vuelve a la pantalla de inicio de sesión e inténtalo de nuevo.",
|
||||
"request_password_reset": "Restablecer contraseña",
|
||||
"send_invite": "Enviar invitación",
|
||||
"forgot_password": "Olvidé la contraseña",
|
||||
"forgot": "¿Olvidado?",
|
||||
"done": "Hecho",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Establecer Disponibilidad",
|
||||
"continue_without_calendar": "Continuar sin calendario",
|
||||
"connect_your_calendar": "Conecta tu calendario",
|
||||
"connect_your_video_app": "Conecte sus aplicaciones favoritas",
|
||||
"connect_your_video_app_instructions": "Conecte sus aplicaciones de vídeo para usarlas en sus tipos de eventos.",
|
||||
"connect_your_calendar_instructions": "Conecta tu calendario para comprobar automáticamente los horarios ocupados y los nuevos eventos a medida que se programan.",
|
||||
"set_up_later": "Configurar más tarde",
|
||||
"current_time": "Hora Actual",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Crear Webhook",
|
||||
"booking_cancelled": "Reserva Cancelada",
|
||||
"booking_rescheduled": "Reserva habilitada",
|
||||
"recording_ready": "Enlace de descarga de grabación listo",
|
||||
"booking_created": "Reserva Creada",
|
||||
"meeting_ended": "Reunión finalizada",
|
||||
"form_submitted": "Formulario enviado",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Viernes",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"all_booked_today": "Todo reservado.",
|
||||
"slots_load_fail": "No se pudo cargar el intervalo de tiempo disponible.",
|
||||
"additional_guests": "Añadir invitados",
|
||||
"your_name": "Tu Nombre",
|
||||
@@ -591,7 +598,7 @@
|
||||
"round_robin_description": "Ciclo de reuniones entre varios miembros del equipo.",
|
||||
"managed_event": "Evento gestionado",
|
||||
"username_placeholder": "nombre de usuario",
|
||||
"managed_event_description": "Cree distribuya tipos de eventos de forma masiva a los miembros del equipo",
|
||||
"managed_event_description": "Cree y distribuya tipos de eventos de forma masiva a los miembros del equipo",
|
||||
"managed": "Gestionado",
|
||||
"managed_event_url_clarification": "El \"nombre de usuario\" se completará con el nombre de usuario de los miembros asignados",
|
||||
"assign_to": "Asignar a",
|
||||
@@ -600,7 +607,7 @@
|
||||
"count_members_other": "{{count}} miembros",
|
||||
"no_assigned_members": "No hay miembros asignados",
|
||||
"assigned_to": "Asignado a",
|
||||
"start_assigning_members_above": "Empezar a asignar miembros arriba",
|
||||
"start_assigning_members_above": "Empezar a asignar miembros más arriba",
|
||||
"locked_fields_admin_description": "Los miembros no podrán editar esto",
|
||||
"locked_fields_member_description": "El administrador del equipo bloqueó esta opción",
|
||||
"url": "URL",
|
||||
@@ -752,12 +759,16 @@
|
||||
"new_event_type_to_book_description": "Crea un nuevo tipo de evento para que las personas reserven horarios.",
|
||||
"length": "Tamaño",
|
||||
"minimum_booking_notice": "Aviso de Reserva",
|
||||
"offset_toggle": "Horas de inicio desplazadas",
|
||||
"offset_toggle_description": "Intervalos de tiempo desplazados que se muestran a los que reservan por un número específico de minutos",
|
||||
"offset_start": "Desplazar por",
|
||||
"offset_start_description": "por ejemplo esto mostrará los intervalos de tiempo a quienes reservan a las {{ adjustedTime }} en lugar de {{ originalTime }}",
|
||||
"slot_interval": "Intervalos de tiempo",
|
||||
"slot_interval_default": "Usar duración del evento (por defecto)",
|
||||
"delete_event_type": "¿Eliminar tipo de evento?",
|
||||
"delete_managed_event_type": "¿Eliminar tipo de evento administrado?",
|
||||
"delete_event_type_description": "Cualquier persona con la que hayas compartido este enlace ya no podrá reservar con él.",
|
||||
"delete_managed_event_type_description": "<ul><li>Los miembros asignados a este tipo de evento también verán eliminados sus tipos de evento.</li><li>Cualquier persona con la que hayan compartido su enlace ya no podrá reservar mediante su uso.</li> </ul>",
|
||||
"delete_managed_event_type": "¿Eliminar tipo de evento gestionado?",
|
||||
"delete_event_type_description": "Cualquier persona con la que haya compartido este enlace ya no podrá reservar con él.",
|
||||
"delete_managed_event_type_description": "<ul><li>Los miembros asignados a este tipo de evento también verán eliminados sus tipos de evento.</li><li>Cualquier persona con la que hayan compartido su enlace ya no podrá reservar con él.</li> </ul>",
|
||||
"confirm_delete_event_type": "Sí, eliminar",
|
||||
"delete_account": "Eliminar cuenta",
|
||||
"confirm_delete_account": "Sí, eliminar cuenta",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Duplicado",
|
||||
"offer_seats": "Ofrecer plazas",
|
||||
"offer_seats_description": "Ofrecer plazas para las reservas (desactiva las reservas de invitados y las optativas)",
|
||||
"seats_available_one": "Plazas disponibles",
|
||||
"seats_available_other": "Plazas disponibles",
|
||||
"number_of_seats": "Número de plazas por reserva",
|
||||
"enter_number_of_seats": "Introduzca el número de plazas",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "cuando se cancela el evento",
|
||||
"new_event_trigger": "cuando se reserva un nuevo evento",
|
||||
"email_host_action": "enviar correo electrónico al anfitrión",
|
||||
"email_attendee_action": "enviar correo electrónico a los asistentes",
|
||||
"sms_attendee_action": "enviar SMS al asistente",
|
||||
"sms_number_action": "enviar SMS a un número específico",
|
||||
"workflows": "Flujos de trabajo",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Crear un flujo de trabajo",
|
||||
"do_this": "Hacer esto",
|
||||
"turn_off": "Apagar",
|
||||
"turn_on": "Activar",
|
||||
"settings_updated_successfully": "Configuración actualizada correctamente",
|
||||
"error_updating_settings": "Error al actualizar la configuración",
|
||||
"personal_cal_url": "Mi URL personal de {{appName}}",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Configura cómo interactúan tus tipos de eventos con tus calendarios",
|
||||
"appearance_description": "Administra los ajustes para el aspecto de tu reserva",
|
||||
"conferencing_description": "Añada sus aplicaciones de videoconferencia favoritas para sus reuniones",
|
||||
"add_conferencing_app": "Añadir aplicación de conferencia",
|
||||
"password_description": "Administra los ajustes para las contraseñas de tu cuenta",
|
||||
"2fa_description": "Administra los ajustes para las contraseñas de tu cuenta",
|
||||
"we_just_need_basic_info": "Solo necesitamos información básica para configurar tu perfil.",
|
||||
@@ -1326,7 +1341,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Aquí puedes ver todos los formularios y rutas que has creado.",
|
||||
"routing_forms_description": "Crea formularios para dirigir a los asistentes a sus destinos correctos",
|
||||
"routing_forms_send_email_owner": "Enviar correo electrónico al propietario",
|
||||
"routing_forms_send_email_owner_description": "Mande un correo electrónico al propietario cuando se envía el formulario",
|
||||
"add_new_form": "Añadir formulario nuevo",
|
||||
@@ -1380,7 +1395,7 @@
|
||||
"add_limit": "Agregue un límite",
|
||||
"team_name_required": "Nombre de equipo requerido",
|
||||
"show_attendees": "Comparta información de los asistentes entre los invitados",
|
||||
"how_booking_questions_as_variables": "¿Cómo se utilizan las preguntas de reserva como variables?",
|
||||
"how_booking_questions_as_variables": "¿Cómo utilizar las preguntas de reserva como variables?",
|
||||
"format": "Formato",
|
||||
"uppercase_for_letters": "Use mayúsculas para todas las letras",
|
||||
"replace_whitespaces_underscores": "Reemplace espacios en blanco con guiones bajos",
|
||||
@@ -1486,7 +1501,7 @@
|
||||
"disabled_app_affects_event_type": "Un administrador desactivó {{appName}}, lo que afecta a su tipo de evento {{eventType}}",
|
||||
"event_replaced_notice": "Un administrador ha reemplazado uno de sus tipos de eventos",
|
||||
"email_subject_slug_replacement": "Un administrador del equipo ha reemplazado su evento /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "Un administrador del equipo <strong>{{teamName}}</strong> reemplazó su tipo de evento <strong>/{{slug}}</strong> con un tipo de evento administrado que él controla.",
|
||||
"email_body_slug_replacement_notice": "Un administrador del equipo <strong>{{teamName}}</strong> reemplazó su tipo de evento <strong>/{{slug}}</strong> con un tipo de evento gestionado que él controla.",
|
||||
"email_body_slug_replacement_info": "Su enlace seguirá funcionando, pero algunas configuraciones pueden haber cambiado. Puede revisarlo en tipos de eventos.",
|
||||
"email_body_slug_replacement_suggestion": "Si tiene alguna pregunta sobre el tipo de evento, comuníquese con su administrador.<br /><br />Feliz programación, <br />El equipo de Cal.com",
|
||||
"disable_payment_app": "El administrador ha deshabilitado {{appName}}, lo que afecta a su tipo de evento {{title}}. Los asistentes todavía pueden reservar este tipo de evento, pero no se les pedirá que paguen. Puede ocultar el tipo de evento para evitar esto hasta que su administrador habilite de nuevo su método de pago.",
|
||||
@@ -1551,7 +1566,7 @@
|
||||
"create_your_first_team_webhook_description": "Cree su primer webhook para este tipo de evento de equipo",
|
||||
"create_webhook_team_event_type": "Crear un webhook para este tipo de evento de equipo",
|
||||
"disable_success_page": "Desactivar página de éxito (solo funciona si tiene una URL de redireccionamiento)",
|
||||
"invalid_admin_password": "Usted es administrador pero no tiene una contraseña de al menos 15 caracteres o ni autenticación de 2 factores",
|
||||
"invalid_admin_password": "Usted es administrador pero no tiene una contraseña de al menos 15 caracteres o una autenticación de 2 factores aún",
|
||||
"change_password_admin": "Cambie la contraseña para obtener acceso de administrador",
|
||||
"username_already_taken": "Este nombre de usuario ya está en uso",
|
||||
"assignment": "Asignación",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Ver la invitación",
|
||||
"email_no_user_invite_heading": "Lo han invitado a unirse a un equipo en {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} lo invitó a unirse a su equipo en {{appName}}. {{appName}} es el programador de eventos que les permite a usted y a su equipo programar reuniones sin necesidad de enviar correos electrónicos.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} lo ha invitado a unirse a su equipo \"{{teamName}}\" en {{appName}}. {{appName}} es el planificador de eventos que le permite a usted y a su equipo programar reuniones sin correos electrónicos de ida y vuelta.",
|
||||
"email_no_user_invite_steps_intro": "Lo guiaremos a través de unos pocos pasos y disfrutará de una programación con su equipo sin estresarse y empleando muy poco tiempo.",
|
||||
"email_no_user_step_one": "Elija su nombre de usuario",
|
||||
"email_no_user_step_two": "Conecte su cuenta de calendario",
|
||||
@@ -1686,15 +1702,24 @@
|
||||
"select_a_router": "Seleccione un enrutador",
|
||||
"add_a_new_route": "Agregar una ruta nueva",
|
||||
"make_informed_decisions": "Tome decisiones informadas con Insights",
|
||||
"make_informed_decisions_description": "Nuestro panel de Insights muestra toda la actividad de su equipo y le muestra las tendencias que permiten una mejor programación y toma de decisiones del equipo.",
|
||||
"view_bookings_across": "Ver reservas entre todos los miembros",
|
||||
"make_informed_decisions_description": "Nuestro panel de Insights muestra toda la actividad de su equipo y las tendencias que permiten una mejor programación y toma de decisiones.",
|
||||
"view_bookings_across": "Ver reservas de todos los miembros",
|
||||
"view_bookings_across_description": "Vea quién recibe la mayor cantidad de reservas y garantice la mejor distribución en su equipo",
|
||||
"identify_booking_trends": "Identificar tendencias de reserva",
|
||||
"identify_booking_trends_description": "Consultar qué horas de la semana y qué horas durante el día son populares para sus reservas",
|
||||
"identify_booking_trends_description": "Consultar cuáles son las horas de la semana y del día más populares para sus reservas",
|
||||
"spot_popular_event_types": "Detectar tipos de eventos populares",
|
||||
"spot_popular_event_types_description": "Consulte cuál de sus tipos de eventos están recibiendo más clics y reservas",
|
||||
"no_responses_yet": "Aún no hay respuestas",
|
||||
"this_will_be_the_placeholder": "Este será el marcador de posición",
|
||||
"error_booking_event": "Ocurrió un error al reservar el evento, actualice la página e intente nuevamente",
|
||||
"timeslot_missing_title": "No hay intervalo de tiempo seleccionado",
|
||||
"timeslot_missing_description": "Seleccione un intervalo de tiempo para reservar el evento.",
|
||||
"timeslot_missing_cta": "Seleccionar intervalo de tiempo",
|
||||
"switch_monthly": "Cambiar a la vista mensual",
|
||||
"switch_weekly": "Cambiar a la vista semanal",
|
||||
"switch_multiday": "Cambiar a la vista diaria",
|
||||
"num_locations": "{{num}} opciones de ubicación",
|
||||
"select_on_next_step": "Seleccionar en el siguiente paso",
|
||||
"this_meeting_has_not_started_yet": "Esta reunión aún no ha comenzado",
|
||||
"this_app_requires_connected_account": "{{appName}} requiere una cuenta {{dependencyName}} conectada",
|
||||
"connect_app": "Conectar {{dependencyName}}",
|
||||
@@ -1704,7 +1729,7 @@
|
||||
"can_you_try_again": "¿Puede intentarlo de nuevo con una hora diferente?",
|
||||
"verify": "Verificar",
|
||||
"timezone_variable": "Zona Horaria",
|
||||
"timezone_info": "La zona horaria de la persona que recibe",
|
||||
"timezone_info": "Zona horaria de la persona que recibe",
|
||||
"event_end_time_variable": "Hora de finalización del evento",
|
||||
"event_end_time_info": "La hora de finalización del evento",
|
||||
"cancel_url_variable": "URL de cancelación",
|
||||
@@ -1717,13 +1742,14 @@
|
||||
"members_default_schedule": "Horario predeterminado del miembro",
|
||||
"set_by_admin": "Establecido por el administrador del equipo",
|
||||
"members_default_location": "Ubicación predeterminada del miembro",
|
||||
"members_default_schedule_description": "Usaremos el horario de disponibilidad predeterminado de cada miembro. Podrán editarlo o cambiarlo.",
|
||||
"members_default_schedule_description": "Usaremos el horario de disponibilidad predeterminado de cada miembro. Este se podrá editar o cambiar.",
|
||||
"requires_at_least_one_schedule": "Es necesario tener al menos un horario",
|
||||
"default_conferencing_bulk_description": "Actualizar las ubicaciones para los tipos de eventos seleccionados",
|
||||
"locked_for_members": "Bloqueado para miembros",
|
||||
"locked_apps_description": "Los miembros podrán ver las aplicaciones activas pero no podrán editar ninguna configuración de la aplicación",
|
||||
"locked_apps_description": "Los miembros podrán ver las aplicaciones activas, pero no podrán editar ninguna configuración de la aplicación",
|
||||
"locked_webhooks_description": "Los miembros podrán ver los webhooks activos, pero no podrán editar ninguna configuración de webhooks",
|
||||
"locked_workflows_description": "Los miembros podrán ver los flujos de trabajo activos, pero no podrán editar ninguna configuración de flujo de trabajo",
|
||||
"locked_by_admin": "Bloqueado por el administrador del equipo",
|
||||
"app_not_connected": "No ha conectado una cuenta de {{appName}}.",
|
||||
"connect_now": "Conectar ahora",
|
||||
"managed_event_dialog_confirm_button_one": "Reemplazar y notificar a {{count}} miembro",
|
||||
@@ -1735,7 +1761,7 @@
|
||||
"managed_event_dialog_clarification": "Si elige reemplazarlo, se lo notificaremos. Regrese y elimínelos si no desea sobrescribirlo.",
|
||||
"review_event_type": "Revisar tipo de evento",
|
||||
"looking_for_more_analytics": "¿Busca más datos de análisis?",
|
||||
"looking_for_more_insights": "¿Busca más perspectivas?",
|
||||
"looking_for_more_insights": "¿Quiere saber más sobre Insights?",
|
||||
"add_filter": "Añadir filtro",
|
||||
"select_user": "Seleccionar usuario",
|
||||
"select_event_type": "Seleccionar tipo de evento",
|
||||
@@ -1751,7 +1777,7 @@
|
||||
"events_rescheduled": "Eventos reprogramados",
|
||||
"from_last_period": "desde el último período",
|
||||
"from_to_date_period": "Desde: {{startDate}} Hasta: {{endDate}}",
|
||||
"analytics_for_organisation": "Perspectivas",
|
||||
"analytics_for_organisation": "Insights",
|
||||
"subtitle_analytics": "Obtenga más información sobre la actividad de su equipo",
|
||||
"redirect_url_warning": "Agregar una redirección deshabilitará la página de éxito. Asegúrese de mencionar \"Reserva confirmada\" en su página de éxito personalizada.",
|
||||
"event_trends": "Tendencias del evento",
|
||||
@@ -1776,10 +1802,39 @@
|
||||
"insights": "Insights",
|
||||
"testing_workflow_info_message": "Al probar este flujo de trabajo, tenga en cuenta que los correos electrónicos y los SMS solo se pueden programar con al menos 1 hora de anticipación",
|
||||
"insights_no_data_found_for_filter": "No se encontraron datos para el filtro seleccionado o las fechas seleccionadas.",
|
||||
"acknowledge_booking_no_show_fee": "Reconozco que si no asisto a este evento, se aplicará a mi tarjeta una tarifa de {{amount, currency}} por no presentarse.",
|
||||
"acknowledge_booking_no_show_fee": "Reconozco que si no asisto a este evento, se aplicará a mi tarjeta una tarifa de {{amount, currency}} por no presentarme.",
|
||||
"card_details": "Detalles de la tarjeta",
|
||||
"seats_and_no_show_fee_error": "Actualmente no se pueden habilitar cupos ni cobrar una tarifa por no presentarse",
|
||||
"seats_and_no_show_fee_error": "Actualmente, no se pueden habilitar cupos ni cobrar una tarifa por no presentarse",
|
||||
"complete_your_booking": "Complete su reserva",
|
||||
"complete_your_booking_subject": "Complete su reserva: {{title}} el {{date}}",
|
||||
"email_invite_team": "Se ha invitado a {{email}}"
|
||||
"confirm_your_details": "Confirme sus datos",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Está a punto de cobrarle {{amount, currency}} al asistente. ¿Está seguro de que desea continuar?",
|
||||
"charge_attendee": "Cobrarle al asistente {{amount, currency}}",
|
||||
"payment_app_commission": "Solicitar pago ({{paymentFeePercentage}}% + {{fee, currency}} comisión por transacción)",
|
||||
"email_invite_team": "Se ha invitado a {{email}}",
|
||||
"email_invite_team_bulk": "Se han invitado {{userCount}} usuarios",
|
||||
"error_collecting_card": "Error al recolectar la tarjeta",
|
||||
"image_size_limit_exceed": "La imagen cargada no debe exceder el límite de tamaño de 5 Mb",
|
||||
"inline_embed": "Incrustar en línea",
|
||||
"load_inline_content": "Carga su tipo de evento directamente en línea con otro contenido de su sitio web.",
|
||||
"floating_pop_up_button": "Botón emergente flotante",
|
||||
"floating_button_trigger_modal": "Pone un botón flotante en su sitio que activa un modal con su tipo de evento.",
|
||||
"pop_up_element_click": "Aparece mediante un clic en el elemento",
|
||||
"open_dialog_with_element_click": "Abre su cuadro de diálogo Cal cuando alguien hace clic en un elemento.",
|
||||
"need_help_embedding": "Necesita ayuda? Consulte nuestras guías para incrustar Cal en Wix, Squarespace o WordPress, consulte nuestras preguntas comunes o explore las opciones avanzadas de incrustación.",
|
||||
"book_my_cal": "Reservar mi Cal",
|
||||
"invite_as": "Invitar como",
|
||||
"form_updated_successfully": "Formulario actualizado con éxito.",
|
||||
"email_not_cal_member_cta": "Únase a su equipo",
|
||||
"disable_attendees_confirmation_emails": "Desactivar los correos electrónicos de confirmación predeterminados para los asistentes",
|
||||
"disable_attendees_confirmation_emails_description": "Al menos un flujo de trabajo está activo en este tipo de evento que envía un correo electrónico a los asistentes cuando se reserva el evento.",
|
||||
"disable_host_confirmation_emails": "Desactivar los correos electrónicos de confirmación predeterminados para el anfitrión",
|
||||
"disable_host_confirmation_emails_description": "Al menos un flujo de trabajo está activo en este tipo de evento que envía un correo electrónico al anfitrión cuando se reserva el evento.",
|
||||
"add_an_override": "Agregar una anulación",
|
||||
"import_from_google_workspace": "Importar usuarios de Google Workspace",
|
||||
"connect_google_workspace": "Conectar Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Debe ser administrador del espacio de trabajo para usar esta función",
|
||||
"first_event_type_webhook_description": "Cree su primer webhook para este tipo de evento",
|
||||
"create_for": "Crear para"
|
||||
}
|
||||
|
||||
@@ -1807,6 +1807,7 @@
|
||||
"email_invite_team": "{{email}} a été invité",
|
||||
"error_collecting_card": "Erreur lors de la collecte de la carte",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo",
|
||||
"unauthorized_workflow_error_message": "{{errorCode}} : Vous n'êtes pas autorisé à activer ou désactiver ce workflow",
|
||||
"inline_embed": "Intégration en ligne",
|
||||
"load_inline_content": "Charge votre type d'événement directement en ligne avec le contenu de votre site web.",
|
||||
"floating_pop_up_button": "Bouton pop-up flottant",
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Rifiuta la prenotazione",
|
||||
"manage_this_event": "Gestisci questo evento",
|
||||
"invite_team_member": "Invia un membro del team",
|
||||
"invite_team_individual_segment": "Invita utente privato",
|
||||
"invite_team_bulk_segment": "Importazione in blocco",
|
||||
"invite_team_notifcation_badge": "Inv.",
|
||||
"your_event_has_been_scheduled": "Il tuo evento è stato programmato",
|
||||
"your_event_has_been_scheduled_recurring": "Il tuo evento ricorrente è stato programmato",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Torna alla pagina di accesso",
|
||||
"error_during_login": "Si è verificato un errore durante il login. Torna alla schermata di accesso e riprova.",
|
||||
"request_password_reset": "Invia email di ripristino",
|
||||
"send_invite": "Invia invito",
|
||||
"forgot_password": "Password dimenticata?",
|
||||
"forgot": "Dimenticato?",
|
||||
"done": "Fatto",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Imposta la tua disponibilità",
|
||||
"continue_without_calendar": "Continua senza calendario",
|
||||
"connect_your_calendar": "Collega il tuo calendario",
|
||||
"connect_your_video_app": "Collega le tue applicazioni video",
|
||||
"connect_your_video_app_instructions": "Collega le tue applicazioni video per utilizzarle per i tuoi tipi di evento.",
|
||||
"connect_your_calendar_instructions": "Collega il tuo calendario per controllare automaticamente gli orari occupati e i nuovi eventi appena vengono pianificati.",
|
||||
"set_up_later": "Configura più tardi",
|
||||
"current_time": "Orario corrente",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Crea Webhook",
|
||||
"booking_cancelled": "Prenotazione Annullata",
|
||||
"booking_rescheduled": "Prenotazione Riprogrammata",
|
||||
"recording_ready": "Link per scaricare la registrazione pronto",
|
||||
"booking_created": "Prenotazione Creata",
|
||||
"meeting_ended": "Riunione terminata",
|
||||
"form_submitted": "Modulo inviato",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Venerdì",
|
||||
"saturday": "Sabato",
|
||||
"sunday": "Domenica",
|
||||
"all_booked_today": "Tutto prenotato.",
|
||||
"slots_load_fail": "Impossibile caricare gli intervalli di tempo disponibili.",
|
||||
"additional_guests": "Aggiungi ospiti",
|
||||
"your_name": "Il tuo nome",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Crea un nuovo tipo di evento per le persone con cui prenotare.",
|
||||
"length": "Durata",
|
||||
"minimum_booking_notice": "Preavviso minimo",
|
||||
"offset_toggle": "Sposta orari di inizio",
|
||||
"offset_toggle_description": "Sposta le fasce orarie mostrate alle persone che prenotano di un numero specificato di muniti",
|
||||
"offset_start": "Spostamento di",
|
||||
"offset_start_description": "ad es., le fascie orarie saranno mostrate alle persone che prenotano alle {{ adjustedTime }} anziché alle {{ originalTime }}",
|
||||
"slot_interval": "Intervalli di fascia oraria",
|
||||
"slot_interval_default": "Usa durata evento (impostazione predefinita)",
|
||||
"delete_event_type": "Eliminare il tipo di evento?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Duplica",
|
||||
"offer_seats": "Imposta posti",
|
||||
"offer_seats_description": "Indica i posti disponibili (ciò disabiliterà le prenotazioni ospite e le prenotazioni opt-in).",
|
||||
"seats_available_one": "Posto disponibile",
|
||||
"seats_available_other": "Posti disponibili",
|
||||
"number_of_seats": "Numero di posti per prenotazione",
|
||||
"enter_number_of_seats": "Immettere il numero di posti",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "quando l'evento viene annullato",
|
||||
"new_event_trigger": "quando viene prenotato un nuovo evento",
|
||||
"email_host_action": "invia e-mail all'organizzatore",
|
||||
"email_attendee_action": "invia e-mail ai partecipanti",
|
||||
"sms_attendee_action": "invia SMS al partecipante",
|
||||
"sms_number_action": "invia SMS a un numero specifico",
|
||||
"workflows": "Flussi di lavoro",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Crea un flusso di lavoro",
|
||||
"do_this": "Esegui questa operazione",
|
||||
"turn_off": "Disattiva",
|
||||
"turn_on": "Attiva",
|
||||
"settings_updated_successfully": "Impostazioni aggiornate correttamente",
|
||||
"error_updating_settings": "Errore durante l'aggiornamento delle impostazioni",
|
||||
"personal_cal_url": "Il mio URL personale di {{appName}}",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Configura come i tipi di eventi devono interagire con i tuoi calendari",
|
||||
"appearance_description": "Gestisci le impostazioni di aspetto delle tue prenotazioni",
|
||||
"conferencing_description": "Aggiungi le tue app di videoconferenza preferite per le tue riunioni",
|
||||
"add_conferencing_app": "Aggiungi app di videoconferenza",
|
||||
"password_description": "Gestisci le impostazioni relative alle password del tuo account",
|
||||
"2fa_description": "Gestisci le impostazioni relative alle password del tuo account",
|
||||
"we_just_need_basic_info": "Abbiamo bisogno di alcune semplici informazioni per completare la configurazione del tuo profilo.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Visualizza invito",
|
||||
"email_no_user_invite_heading": "Sei stato invitato a unirti a un team su {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_no_user_invite_steps_intro": "Ti assisteremo nei passaggi iniziali in modo che tu possa pianificare eventi per il tuo team senza stress e in pochissimo tempo.",
|
||||
"email_no_user_step_one": "Scegli il tuo nome utente",
|
||||
"email_no_user_step_two": "Collega il tuo account di calendario",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Scopri quali tipi di evento ricevono più clic e prenotazioni",
|
||||
"no_responses_yet": "Per ora non ci sono risposte",
|
||||
"this_will_be_the_placeholder": "Questo sarà il segnaposto",
|
||||
"error_booking_event": "Si è verificato un errore durante la prenotazione dell'evento. Aggiornare la pagina e riprovare",
|
||||
"timeslot_missing_title": "Nessuna fascia oraria selezionata",
|
||||
"timeslot_missing_description": "Selezionare una fascia oraria per prenotare l'evento.",
|
||||
"timeslot_missing_cta": "Seleziona fascia oraria",
|
||||
"switch_monthly": "Passa alla vista mensile",
|
||||
"switch_weekly": "Passa alla vista settimanale",
|
||||
"switch_multiday": "Passa alla vista giornaliera",
|
||||
"num_locations": "{{num}} opzioni di luogo",
|
||||
"select_on_next_step": "Seleziona nel passaggio successivo",
|
||||
"this_meeting_has_not_started_yet": "Questa riunione non è ancora iniziata",
|
||||
"this_app_requires_connected_account": "{{appName}} richiede un account di {{dependencyName}} connesso",
|
||||
"connect_app": "Connetti {{dependencyName}}",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "I membri potranno vedere le app attive, ma non potranno modificare le impostazioni delle app",
|
||||
"locked_webhooks_description": "I membri potranno vedere i webhook attivi, ma non potranno modificare le impostazioni dei webhook",
|
||||
"locked_workflows_description": "I membri potranno vedere i flussi di lavoro attivi, ma non potranno modificare le impostazioni dei flussi di lavoro",
|
||||
"locked_by_admin": "Bloccato dall'amministratore del team",
|
||||
"app_not_connected": "Non è stato connesso un account di {{appName}}.",
|
||||
"connect_now": "Connetti ora",
|
||||
"managed_event_dialog_confirm_button_one": "Sostituisci e invia notifica a {{count}} membro",
|
||||
@@ -1781,5 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "Attualmente non è possibile abilitare i posti e addebitare la penale per mancata presentazione",
|
||||
"complete_your_booking": "Completa la prenotazione",
|
||||
"complete_your_booking_subject": "Completa la prenotazione: {{title}} del {{date}}",
|
||||
"email_invite_team": "{{email}} è stato invitato"
|
||||
"confirm_your_details": "Conferma i tuoi dati",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Stai per addebitare {{amount, currency}} al partecipante. Continuare?",
|
||||
"charge_attendee": "Addebita {{amount, currency}} al partecipante",
|
||||
"payment_app_commission": "Pagamento richiesto ({{paymentFeePercentage}}% + {{fee, currency}} di commissione per transazione)",
|
||||
"email_invite_team": "{{email}} è stato invitato",
|
||||
"email_invite_team_bulk": "{{userCount}} utenti sono stati invitati",
|
||||
"error_collecting_card": "Errore durante la raccolta dei dati della carta",
|
||||
"image_size_limit_exceed": "Le dimensioni delle immagini caricate non devono superare il limite di 5 MB",
|
||||
"inline_embed": "Incorporamento inline",
|
||||
"load_inline_content": "Carica il tuo tipo di evento direttamente inline con altri contenuti del sito web.",
|
||||
"floating_pop_up_button": "Pulsante a comparsa mobile",
|
||||
"floating_button_trigger_modal": "Inserisce nel sito un pulsante mobile che attiva una finestra modale con il tuo tipo di evento.",
|
||||
"pop_up_element_click": "Finestra popup tramite clic su un elemento",
|
||||
"open_dialog_with_element_click": "Apre la finestra di dialogo di Cal quando un utente clicca su un elemento.",
|
||||
"need_help_embedding": "Hai bisogno di aiuto? Consulta le nostre guide per integrare Cal su Wix, Squarespace o WordPress, legge le risposte alle domande più frequenti o esplora le opzioni di icorporamento avanzate.",
|
||||
"book_my_cal": "Prenota il mio Cal",
|
||||
"invite_as": "Invita come",
|
||||
"form_updated_successfully": "Modulo aggiornato correttamente.",
|
||||
"email_not_cal_member_cta": "Unisciti al tuo team",
|
||||
"disable_attendees_confirmation_emails": "Disabilita le e-mail di conferma predefinite per i partecipanti",
|
||||
"disable_attendees_confirmation_emails_description": "Per questo tipo di evento è attivo almeno un flusso di lavoro che invia un'e-mail ai partecipanti quando l'evento viene prenotato.",
|
||||
"disable_host_confirmation_emails": "Disabilita le e-mail di conferma predefinite per gli organizzatori",
|
||||
"disable_host_confirmation_emails_description": "Per questo tipo di evento è attivo almeno un flusso di lavoro che invia un'e-mail agli organizzatori quando l'evento viene prenotato.",
|
||||
"add_an_override": "Aggiungi sostituzione",
|
||||
"import_from_google_workspace": "Importa utenti da Google Workspace",
|
||||
"connect_google_workspace": "Collega Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Per usare questa funzione è necessario essere un amministratore di Workspace",
|
||||
"first_event_type_webhook_description": "Crea il primo webhook per questo tipo di evento",
|
||||
"create_for": "Crea per"
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
"add_time_availability": "新しい時間帯を追加",
|
||||
"add_an_extra_layer_of_security": "パスワードが盗まれる場合に備えて、アカウントのセキュリティを強化します。",
|
||||
"2fa": "二要素認証",
|
||||
"2fa_disabled": "二要素認証はメール認証とパスワード認証のみ有効にできます",
|
||||
"2fa_disabled": "二要素認証は、メール認証とパスワード認証に対してのみ有効化できます",
|
||||
"enable_2fa": "二要素認証を有効にする",
|
||||
"disable_2fa": "二要素認証を無効にする",
|
||||
"disable_2fa_recommendation": "2FA を無効にする必要がある場合は、その必要がなくなった後すぐに有効にしなおすことをおすすめします。",
|
||||
@@ -589,20 +589,20 @@
|
||||
"minutes": "分",
|
||||
"round_robin": "ラウンドロビン",
|
||||
"round_robin_description": "複数のチームメンバー間のミーティングを定期化する。",
|
||||
"managed_event": "管理イベント",
|
||||
"managed_event": "管理済みのイベント",
|
||||
"username_placeholder": "ユーザー名",
|
||||
"managed_event_description": "イベントタイプを作成し、チームメンバーに一括配布する",
|
||||
"managed": "管理",
|
||||
"managed_event_url_clarification": "「ユーザー名」には、割り当てられたメンバーのユーザー名が入ります",
|
||||
"managed_event_description": "イベントの種類を作成し、チームメンバーに一括配布する",
|
||||
"managed": "管理済み",
|
||||
"managed_event_url_clarification": "\"ユーザー名\" には割り当てられたメンバーのユーザー名が入ります",
|
||||
"assign_to": "割り当て先",
|
||||
"add_members": "メンバーを追加…",
|
||||
"count_members_one": "{{count}} 人のメンバー",
|
||||
"count_members_other": "{{count}} 人のメンバー",
|
||||
"no_assigned_members": "割り当てられたメンバーはいません",
|
||||
"assigned_to": "割り当て先",
|
||||
"start_assigning_members_above": "上記のメンバーへの割り当てを開始する",
|
||||
"locked_fields_admin_description": "メンバーはこれを編集することはできません",
|
||||
"locked_fields_member_description": "このオプションはチーム管理者によってロックされました",
|
||||
"start_assigning_members_above": "上記のメンバーに対する割り当てを開始する",
|
||||
"locked_fields_admin_description": "メンバーがこれを編集することはできません",
|
||||
"locked_fields_member_description": "このオプションはチームの管理者によってロックされました",
|
||||
"url": "URL",
|
||||
"hidden": "隠れている",
|
||||
"readonly": "読み込み専用",
|
||||
@@ -754,10 +754,10 @@
|
||||
"minimum_booking_notice": "最低頻度の通知",
|
||||
"slot_interval": "時間帯の間隔",
|
||||
"slot_interval_default": "イベントの長さを使用する (デフォルト)",
|
||||
"delete_event_type": "イベントタイプを削除しますか?",
|
||||
"delete_managed_event_type": "管理イベントタイプを削除しますか?",
|
||||
"delete_event_type_description": "これまでにこのリンクを共有したユーザーは、このリンクを使用して予約することができなくなります。",
|
||||
"delete_managed_event_type_description": "<ul><li>このイベントタイプに割り当てられたメンバーは、そのイベントタイプも削除されます。</li><li>リンクを共有した人は、そのリンクを使用して予約することができなくなります。</li></ul>",
|
||||
"delete_event_type": "イベントの種類を削除しますか?",
|
||||
"delete_managed_event_type": "管理済みのイベントの種類を削除しますか?",
|
||||
"delete_event_type_description": "これまでにこのリンクを共有したことがあるユーザーは、このリンクを使用して予約することができなくなります。",
|
||||
"delete_managed_event_type_description": "<ul><li>このイベントの種類に割り当てられているメンバーは、そのイベントの種類も削除されます。</li><li>リンクを共有したことがあるユーザーは、そのリンクを使用して予約することができなくなります。</li></ul>",
|
||||
"confirm_delete_event_type": "はい、削除します",
|
||||
"delete_account": "アカウントを削除する",
|
||||
"confirm_delete_account": "はい。アカウントを削除します",
|
||||
@@ -1148,13 +1148,13 @@
|
||||
"current_username": "現在のユーザー名",
|
||||
"example_1": "例 1",
|
||||
"example_2": "例 2",
|
||||
"booking_question_identifier": "予約質問識別子",
|
||||
"booking_question_identifier": "予約時質問識別子",
|
||||
"company_size": "会社の規模",
|
||||
"what_help_needed": "何かお困りですか?",
|
||||
"variable_format": "変数の形式",
|
||||
"webhook_subscriber_url_reserved": "ウェブフック購読者 URL は既に定義されています",
|
||||
"custom_input_as_variable_info": "追加の入力ラベルに含まれている特殊文字はすべて無視し (アルファベットと数字のみを使用)、アルファベットをすべて大文字に、そして空白をアンダースコアへと変換します。",
|
||||
"using_booking_questions_as_variables": "予約時の質問を変数として使う方法は?",
|
||||
"using_booking_questions_as_variables": "予約時の質問を変数として使うにはどうすればいいですか?",
|
||||
"download_desktop_app": "デスクトップアプリをダウンロード",
|
||||
"set_ping_link": "Ping リンクを設定",
|
||||
"rate_limit_exceeded": "試行回数の上限を超えました",
|
||||
@@ -1177,7 +1177,7 @@
|
||||
"event_advanced_tab_title": "詳細設定",
|
||||
"event_setup_multiple_duration_error": "イベント設定:複数の期間には少なくとも1つのオプションが必要です。",
|
||||
"event_setup_multiple_duration_default_error": "イベント設定:有効なデフォルト期間を選択してください。",
|
||||
"event_setup_booking_limits_error": "予約上限の日時表記は昇順にする必要があります。[日、週、月、年]",
|
||||
"event_setup_booking_limits_error": "予約制限の日時表記は、昇順にする必要があります。[日、週、月、年]",
|
||||
"event_setup_duration_limits_error": "期間の上限は昇順にする必要があります。[日、週、月、年]",
|
||||
"select_which_cal": "予約を追加するカレンダーを選択してください",
|
||||
"custom_event_name": "カスタムイベント名",
|
||||
@@ -1326,7 +1326,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "作成したすべてのフォームとルートをここで確認できます。",
|
||||
"routing_forms_description": "作成したすべてのフォームとルートをここで確認できます",
|
||||
"routing_forms_send_email_owner": "所有者にメールを送信",
|
||||
"routing_forms_send_email_owner_description": "フォームの送信時に所有者にメールを送信します",
|
||||
"add_new_form": "新しいフォームを追加",
|
||||
@@ -1380,7 +1380,7 @@
|
||||
"add_limit": "上限を追加する",
|
||||
"team_name_required": "チーム名が必要です",
|
||||
"show_attendees": "ゲスト間で出席者情報を共有する",
|
||||
"how_booking_questions_as_variables": "予約時の質問を変数として使う方法は?",
|
||||
"how_booking_questions_as_variables": "予約時の質問を変数として使うにはどうすればいいですか?",
|
||||
"format": "書式",
|
||||
"uppercase_for_letters": "すべての文字に大文字を使用する",
|
||||
"replace_whitespaces_underscores": "空白をアンダーバー「 _ 」に置き換える",
|
||||
@@ -1395,7 +1395,7 @@
|
||||
"billing_help_title": "他にもお手伝いできることはありませんか?",
|
||||
"billing_help_description": "請求に関してサポートが必要な場合には、サポートチームがお手伝いさせていただきます。",
|
||||
"billing_help_cta": "サポートに問い合わせる",
|
||||
"ignore_special_characters_booking_questions": "予約質問識別子に含まれている特殊文字はすべて無視し、アルファベットと数字のみを使用してください",
|
||||
"ignore_special_characters_booking_questions": "予約時質問識別子に含まれている特殊文字はすべて無視し、アルファベットと数字のみを使用してください",
|
||||
"retry": "再試行",
|
||||
"fetching_calendars_error": "カレンダーの取得中にエラーが発生しました。<1>もう一度お試しいただく</1>か、カスタマーサポートまでお問い合わせください。",
|
||||
"calendar_connection_fail": "カレンダーの接続に失敗しました",
|
||||
@@ -1484,11 +1484,11 @@
|
||||
"duration_limit_reached": "このイベントの種類の期間の上限に達しました",
|
||||
"admin_has_disabled": "管理者が {{appName}} を無効にしています",
|
||||
"disabled_app_affects_event_type": "管理者が {{appName}} を無効にしているため、あなたのイベントの種類 {{eventType}} に影響を与える可能性があります",
|
||||
"event_replaced_notice": "管理者があなたのイベントタイプの1つを置き換えました",
|
||||
"email_subject_slug_replacement": "チーム管理者があなたのイベントの /{{slug}} を置き換えました",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong>チームの管理者が、あなたのイベントタイプの <strong>/{{slug}}</strong> を彼らが管理する管理イベントタイプに置き換えました。",
|
||||
"email_body_slug_replacement_info": "あなたのリンクは引き続き機能しますが、リンクの設定の一部が変更された可能性があります。変更はイベントタイプで確認できます。",
|
||||
"email_body_slug_replacement_suggestion": "イベントタイプについてご質問がある場合は、管理者までお問合せください。<br /><br />スケジュール設定をお楽しみください、<br />Cal.comチーム",
|
||||
"event_replaced_notice": "管理者があなたのイベントの種類の 1 つを置き換えました",
|
||||
"email_subject_slug_replacement": "チームの管理者があなたのイベント /{{slug}} を置き換えました",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong> チームの管理者が、あなたのイベントの種類 <strong>/{{slug}}</strong> を彼らが管理する管理済みのイベントの種類へと置き換えました。",
|
||||
"email_body_slug_replacement_info": "あなたのリンクは引き続き機能しますが、リンクの設定の一部が変更された可能性があります。変更内容はイベントの種類で確認できます。",
|
||||
"email_body_slug_replacement_suggestion": "イベントの種類について質問がある場合には、管理者までお問合せください。<br /><br />それでは、スケジュール設定をお楽しみください。<br />Cal.com チーム",
|
||||
"disable_payment_app": "管理者が {{appName}} を無効にしたため、イベントの種類 {{title}} に影響を与える可能性があります。出席者がこの種類のイベントを予約することは可能ですが、支払いを促されることはありません。管理者が支払い方法を再度有効にするまでの間これを防ぐために、イベントの種類を非表示にすることができます。",
|
||||
"payment_disabled_still_able_to_book": "出席者がこの種類のイベントを予約することは可能ですが、支払いを促されることはありません。管理者が支払い方法を再度有効にするまでの間これを防ぐために、イベントの種類を非表示にすることができます。",
|
||||
"app_disabled_with_event_type": "管理者が {{appName}} を無効にしているため、あなたのイベントの種類 {{title}} に影響を与える可能性があります.",
|
||||
@@ -1537,7 +1537,7 @@
|
||||
"date_overrides_update_btn": "上書きを更新",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copy",
|
||||
"set_as_default": "デフォルトとして設定",
|
||||
"hide_eventtype_details": "イベントタイプの詳細を非表示にする",
|
||||
"hide_eventtype_details": "イベントの種類の詳細を非表示にする",
|
||||
"show_navigation": "ナビゲーションを表示",
|
||||
"hide_navigation": "ナビゲーションを非表示にする",
|
||||
"verification_code_sent": "確認コードが送信されました",
|
||||
@@ -1551,7 +1551,7 @@
|
||||
"create_your_first_team_webhook_description": "このチームイベントの種類の初めての webhook を作成します",
|
||||
"create_webhook_team_event_type": "このチームイベントの種類の webhook を作成します",
|
||||
"disable_success_page": "予約完了ページを無効化する (リダイレクト URL がある場合にのみ有効)",
|
||||
"invalid_admin_password": "あなたは管理者ですが、パスワードの長さが15文字以上ではないか、二要素認証をまだしていません",
|
||||
"invalid_admin_password": "あなたは管理者ですが、パスワードの長さが 15 文字以上になっていないか、二要素認証がまだ完了していません",
|
||||
"change_password_admin": "パスワードを変更して管理者アクセス権を取得する",
|
||||
"username_already_taken": "ユーザー名が既に使用されています",
|
||||
"assignment": "割り当て",
|
||||
@@ -1584,7 +1584,7 @@
|
||||
"ee_enterprise_license": "\"/ee\" Enterprise ライセンス",
|
||||
"enterprise_booking_fee": "{{enterprise_booking_fee}}/月で開始する",
|
||||
"enterprise_license_includes": "商用ユースケースで必要となるすべてのもの",
|
||||
"no_need_to_keep_your_code_open_source": "コードをオープンソースにしておく必要はありません",
|
||||
"no_need_to_keep_your_code_open_source": "コードをオープンソースのままにしておく必要はありません",
|
||||
"repackage_rebrand_resell": "再パッケージ、リブランディング、再販売が簡単になります",
|
||||
"a_vast_suite_of_enterprise_features": "膨大な量の企業向け機能パッケージ",
|
||||
"free_license_fee": "0.00 ドル/月",
|
||||
@@ -1646,7 +1646,7 @@
|
||||
"default_app_link_title": "デフォルトのアプリリンクを設定",
|
||||
"default_app_link_description": "デフォルトのアプリリンクを設定することで、新たに作成するすべてのイベントの種類が設定したアプリリンクを使用できるようになります。",
|
||||
"change_default_conferencing_app": "デフォルトとして設定",
|
||||
"organizer_default_conferencing_app": "主催者のデフォルトアプリ",
|
||||
"organizer_default_conferencing_app": "主催者のデフォルトのアプリ",
|
||||
"under_maintenance": "メンテナンスのため停止中",
|
||||
"under_maintenance_description": "{{appName}} チームが定期メンテナンスを行っています。質問がある場合には、サポートへとお問い合わせください。",
|
||||
"event_type_seats": "{{numberOfSeats}} 席",
|
||||
@@ -1685,14 +1685,14 @@
|
||||
"add_1_option_per_line": "1 行に 1 つのオプションを追加する",
|
||||
"select_a_router": "ルーターを選択",
|
||||
"add_a_new_route": "新しいルートを追加",
|
||||
"make_informed_decisions": "Insights を使って情報に基づいた決定をする",
|
||||
"make_informed_decisions_description": "Insights ダッシュボードは、チーム内のすべてのアクティビティを表示し、チームのスケジューリングや意思決定をより良いものにするための傾向を示します。",
|
||||
"make_informed_decisions": "Insights を使って情報に基づいた決定を行う",
|
||||
"make_informed_decisions_description": "Insights ダッシュボードには、チーム内のすべてのアクティビティが表示され、チームのスケジュール設定や意思決定をより良いものにするための傾向が示されます。",
|
||||
"view_bookings_across": "メンバー全体の予約を表示",
|
||||
"view_bookings_across_description": "誰が最も予約されているか確認し、チーム全体で最適な配分ができるようにします",
|
||||
"view_bookings_across_description": "誰が最も予約されているかを確認し、チーム全体で最適な配分が行われるようにします",
|
||||
"identify_booking_trends": "予約の傾向を明らかにする",
|
||||
"identify_booking_trends_description": "週のどの期間と1日のどの時間帯が予約者に人気か確認します",
|
||||
"spot_popular_event_types": "人気のイベントタイプを見つける",
|
||||
"spot_popular_event_types_description": "どのイベントタイプが最もクリックされ、予約されているのか確認します",
|
||||
"identify_booking_trends_description": "1 週間の内のどの時間帯が、そして 1 日の内のどの時間帯が予約するユーザーに人気があるかを確認します",
|
||||
"spot_popular_event_types": "人気のあるイベントの種類を見つける",
|
||||
"spot_popular_event_types_description": "どのイベントの種類が最もクリックされ、予約されているのかを確認します",
|
||||
"no_responses_yet": "回答はまだありません",
|
||||
"this_will_be_the_placeholder": "これがプレースホルダーになります",
|
||||
"this_meeting_has_not_started_yet": "このミーティングはまだ開始されていません",
|
||||
@@ -1704,21 +1704,21 @@
|
||||
"can_you_try_again": "別の時間帯でもう 1 度お試しください",
|
||||
"verify": "確認する",
|
||||
"timezone_variable": "タイムゾーン",
|
||||
"timezone_info": "受け取る人のタイムゾーン",
|
||||
"event_end_time_variable": "イベント終了時刻",
|
||||
"event_end_time_info": "イベント終了時刻",
|
||||
"timezone_info": "受信するユーザーのタイムゾーン",
|
||||
"event_end_time_variable": "イベントの終了時刻",
|
||||
"event_end_time_info": "イベントの終了時刻",
|
||||
"cancel_url_variable": "キャンセル URL",
|
||||
"cancel_url_info": "予約をキャンセルするための URL",
|
||||
"reschedule_url_variable": "スケジュール変更 URL",
|
||||
"reschedule_url_info": "予約をスケジュール変更するための URL",
|
||||
"reschedule_url_info": "予約のスケジュールを変更するための URL",
|
||||
"invalid_event_name_variables": "イベント名に無効な変数があります",
|
||||
"select_all": "すべて選択",
|
||||
"default_conferencing_bulk_title": "既存のイベントの種類を一括更新する",
|
||||
"members_default_schedule": "メンバーのデフォルトスケジュール",
|
||||
"set_by_admin": "チーム管理者による設定",
|
||||
"members_default_schedule": "メンバーのデフォルトのスケジュール",
|
||||
"set_by_admin": "チームの管理者による設定",
|
||||
"members_default_location": "メンバーのデフォルトの場所",
|
||||
"members_default_schedule_description": "各メンバーのデフォルトの空き状況スケジュールを使用します。メンバーはスケジュールを編集したり、変更したりできます。",
|
||||
"requires_at_least_one_schedule": "少なくとも 1 つのスケジュールが必要です",
|
||||
"members_default_schedule_description": "各メンバーのデフォルトの空きスケジュールを使用します。メンバーはこのスケジュールを編集したり、変更したりできます。",
|
||||
"requires_at_least_one_schedule": "少なくとも 1 件のスケジュールが必要です",
|
||||
"default_conferencing_bulk_description": "選択したイベントの種類の場所を更新します",
|
||||
"locked_for_members": "メンバーに対してロックされています",
|
||||
"locked_apps_description": "メンバーは有効なアプリを確認できますが、アプリの設定を編集することはできません",
|
||||
@@ -1732,54 +1732,54 @@
|
||||
"managed_event_dialog_title_other": "{{count}} 人のメンバーには url/{{slug}} が既に存在します。置き換えますか?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong>は既に <strong>/{{slug}}</strong> url を使用しています。",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong>は既に <string>/{{slug}}</strong> url を使用しています。",
|
||||
"managed_event_dialog_clarification": "置き換えを選択すると、通知が送られます。上書きしたくない場合は、戻って削除してください。",
|
||||
"review_event_type": "イベントタイプを確認",
|
||||
"looking_for_more_analytics": "詳しい分析をお探しですか?",
|
||||
"looking_for_more_insights": "詳しい Insights をお探しですか?",
|
||||
"managed_event_dialog_clarification": "置き換えを選択すると、通知が送られます。上書きしたくない場合には、戻って削除してください。",
|
||||
"review_event_type": "イベントの種類を確認",
|
||||
"looking_for_more_analytics": "詳しい分析情報をお探しですか?",
|
||||
"looking_for_more_insights": "Insights をさらにお探しですか?",
|
||||
"add_filter": "フィルターを追加",
|
||||
"select_user": "ユーザーを選択",
|
||||
"select_event_type": "イベントタイプを選択",
|
||||
"select_date_range": "日付範囲を選択",
|
||||
"select_event_type": "イベントの種類を選択",
|
||||
"select_date_range": "日付の範囲を選択",
|
||||
"popular_events": "人気のイベント",
|
||||
"no_event_types_found": "イベントタイプが見つかりません",
|
||||
"no_event_types_found": "イベントの種類が見つかりません",
|
||||
"average_event_duration": "イベントの平均時間",
|
||||
"most_booked_members": "最も予約が多いメンバー",
|
||||
"least_booked_members": "最も予約が少ないメンバー",
|
||||
"most_booked_members": "最も予約数が多いメンバー",
|
||||
"least_booked_members": "最も予約数が少ないメンバー",
|
||||
"events_created": "イベントが作成されました",
|
||||
"events_completed": "イベントが完了しました",
|
||||
"events_cancelled": "イベントがキャンセルされました",
|
||||
"events_rescheduled": "イベントがスケジュール変更されました",
|
||||
"events_rescheduled": "イベントのスケジュールが変更されました",
|
||||
"from_last_period": "最後の期間から",
|
||||
"from_to_date_period": "開始:{{startDate}} 終了:{{endDate}}",
|
||||
"from_to_date_period": "開始: {{startDate}} 終了: {{endDate}}",
|
||||
"analytics_for_organisation": "Insights",
|
||||
"subtitle_analytics": "チームのアクティビティについてさらに詳しく知る",
|
||||
"redirect_url_warning": "リダイレクトを追加すると予約完了ページが無効化されます。必ずカスタムの予約完了ページで「予約が確認されました」と伝えてください。",
|
||||
"event_trends": "イベントのトレンド",
|
||||
"redirect_url_warning": "リダイレクトを追加すると、予約完了ページが無効化されます。カスタム予約完了ページには、必ず \"予約が完了しました\" と記載してください。",
|
||||
"event_trends": "イベントの傾向",
|
||||
"clear_filters": "フィルターをクリア",
|
||||
"hold": "保留",
|
||||
"on_booking_option": "予約の支払いを受け取る",
|
||||
"hold_option": "予約不参加料金を請求",
|
||||
"card_held": "カードを保留",
|
||||
"charge_card": "カードに請求",
|
||||
"card_charged": "カードに請求されました",
|
||||
"no_show_fee_amount": "{{amount, currency}} の予約不参加料金",
|
||||
"no_show_fee": "予約不参加料金",
|
||||
"on_booking_option": "予約時に料金を徴収する",
|
||||
"hold_option": "不参加費用を請求する",
|
||||
"card_held": "カードを保留中",
|
||||
"charge_card": "カードに請求する",
|
||||
"card_charged": "カードに請求が行われました",
|
||||
"no_show_fee_amount": "不参加費用 {{amount, currency}}",
|
||||
"no_show_fee": "不参加費用",
|
||||
"submit_card": "カードを送信",
|
||||
"submit_payment_information": "支払い情報を送信",
|
||||
"meeting_awaiting_payment_method": "会議の支払いを待っています",
|
||||
"no_show_fee_charged_email_subject": "{{date}} の {{title}} に {{amount, currency}} の予約不参加料金が請求されました",
|
||||
"no_show_fee_charged_text_body": "予約不参加料金が請求されました",
|
||||
"no_show_fee_charged_subtitle": "次のイベントに {{amount, currency}} の予約不参加料金が請求されました",
|
||||
"error_charging_card": "予約不参加料金の請求に問題が発生しました。後でもう一度お試しください。",
|
||||
"collect_no_show_fee": "予約不参加料金を受け取る",
|
||||
"no_show_fee_charged": "予約不参加料金が請求されました",
|
||||
"submit_payment_information": "お支払い情報を送信",
|
||||
"meeting_awaiting_payment_method": "会議がお支払い方法の確定を待機しています",
|
||||
"no_show_fee_charged_email_subject": "{{date}} の {{title}} について不参加費用 {{amount, currency}} が請求されました",
|
||||
"no_show_fee_charged_text_body": "不参加費用が請求されました",
|
||||
"no_show_fee_charged_subtitle": "次のイベントについて不参加費用 {{amount, currency}} が請求されました",
|
||||
"error_charging_card": "不参加費用の請求時にエラーが発生しました。後でもう一度お試しください。",
|
||||
"collect_no_show_fee": "不参加費用を徴収する",
|
||||
"no_show_fee_charged": "不参加費用が請求されました",
|
||||
"insights": "Insights",
|
||||
"testing_workflow_info_message": "このワークフローをテストする際は、少なくとも1時間前でないとメールとSMSをスケジュール設定できないことにご注意ください",
|
||||
"insights_no_data_found_for_filter": "選択したフィルターまたは選択した日付のデータが見つかりません。",
|
||||
"acknowledge_booking_no_show_fee": "このイベントに参加しない場合、{{amount, currency}} の予約不参加料金が私のカードに請求されることに同意します。",
|
||||
"testing_workflow_info_message": "このワークフローをテストする場合、少なくとも 1 時間前でなければメールと SMS のスケジュール設定ができないことにご注意ください",
|
||||
"insights_no_data_found_for_filter": "選択したフィルターまたは日付のデータが見つかりませんでした。",
|
||||
"acknowledge_booking_no_show_fee": "このイベントに出席しなかった場合、不参加費用として {{amount, currency}} が私のカードに請求されることに同意します。",
|
||||
"card_details": "カードの詳細",
|
||||
"seats_and_no_show_fee_error": "現在、座席の有効化と予約不参加料金の請求はできません",
|
||||
"seats_and_no_show_fee_error": "現在、座席の有効化と不参加費用の請求はできません",
|
||||
"complete_your_booking": "予約を完了する",
|
||||
"complete_your_booking_subject": "次の予約を完了する:{{date}} の {{title}}",
|
||||
"email_invite_team": "{{email}} は招待されました"
|
||||
"complete_your_booking_subject": "次の予約を完了する: {{date}} の {{title}}",
|
||||
"email_invite_team": "{{email}} が招待されました"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "예약 취소",
|
||||
"manage_this_event": "이 이벤트 관리",
|
||||
"invite_team_member": "팀원 초대",
|
||||
"invite_team_individual_segment": "개인 초대",
|
||||
"invite_team_bulk_segment": "일괄 가져오기",
|
||||
"invite_team_notifcation_badge": "초대",
|
||||
"your_event_has_been_scheduled": "귀하의 이벤트가 예약되었습니다.",
|
||||
"your_event_has_been_scheduled_recurring": "되풀이 이벤트가 예약되었습니다",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "로그인 페이지로 돌아가기",
|
||||
"error_during_login": "로그인하는 과정에서 오류가 발생했습니다. 로그인 화면으로 돌아가서 다시 한 번 시도해 보세요.",
|
||||
"request_password_reset": "재설정 이메일 보내기",
|
||||
"send_invite": "초대장 보내기",
|
||||
"forgot_password": "비밀번호를 잊었을 경우",
|
||||
"forgot": "잊어버리셨나요?",
|
||||
"done": "완료",
|
||||
@@ -237,6 +240,7 @@
|
||||
"set_availability": "언제 여유가 있는지 설정하세요.",
|
||||
"continue_without_calendar": "캘린더 없이 계속하기",
|
||||
"connect_your_calendar": "캘린더 연결하기",
|
||||
"connect_your_video_app": "비디오 앱 연결",
|
||||
"connect_your_video_app_instructions": "이벤트 타입에서 사용하기 위한 비디오 앱을 연결해주세요.",
|
||||
"connect_your_calendar_instructions": "바쁜 시간과 새 이벤트 일정을 자동으로 확인할 수 있도록 캘린더 연결",
|
||||
"set_up_later": "나중에 하기",
|
||||
@@ -368,6 +372,7 @@
|
||||
"create_webhook": "웹훅 만들기",
|
||||
"booking_cancelled": "예약 취소",
|
||||
"booking_rescheduled": "예약 변경",
|
||||
"recording_ready": "녹화 다운로드 링크 준비됨",
|
||||
"booking_created": "예약 생성",
|
||||
"meeting_ended": "회의 종료됨",
|
||||
"form_submitted": "양식 제출됨",
|
||||
@@ -409,7 +414,7 @@
|
||||
"add_time_availability": "새 타임 슬롯 추가",
|
||||
"add_an_extra_layer_of_security": "비밀번호가 도용될 경우에 대비하여 계정에 보안방법을 추가하십시오.",
|
||||
"2fa": "이중 인증",
|
||||
"2fa_disabled": "이중 인증은 이메일 및 비밀번호 인증에만 사용할 수 있습니다.",
|
||||
"2fa_disabled": "2단계 인증은 이메일 및 비밀번호 인증에만 사용할 수 있습니다.",
|
||||
"enable_2fa": "이중 인증 활성화",
|
||||
"disable_2fa": "이중 인증 비활성화",
|
||||
"disable_2fa_recommendation": "2FA를 비활성화해야 하는 경우, 가능한 한 빨리 다시 활성화하십시오.",
|
||||
@@ -593,17 +598,17 @@
|
||||
"round_robin_description": "여러 팀 구성원 간의 주기적인 회의",
|
||||
"managed_event": "관리형 이벤트",
|
||||
"username_placeholder": "username",
|
||||
"managed_event_description": "팀원들에게 대량으로 이벤트 유형 생성 및 배포",
|
||||
"managed": "관리됨",
|
||||
"managed_event_description": "팀원들에게 대량으로 이벤트 타입 생성 및 배포",
|
||||
"managed": "관리형",
|
||||
"managed_event_url_clarification": "\"username\"은 할당된 회원의 사용자 이름으로 채워집니다",
|
||||
"assign_to": "할당:",
|
||||
"add_members": "회원 추가:",
|
||||
"assign_to": "할당 대상",
|
||||
"add_members": "회원 추가...",
|
||||
"count_members_one": "회원 {{count}}명",
|
||||
"count_members_other": "회원 {{count}}명",
|
||||
"no_assigned_members": "할당된 회원 없음",
|
||||
"assigned_to": "할당된 이벤트 유형:",
|
||||
"assigned_to": "할당된 이벤트 유형",
|
||||
"start_assigning_members_above": "위에서 회원 할당 시작",
|
||||
"locked_fields_admin_description": "회원은 이 내용을 수정할 수 없게 됩니다",
|
||||
"locked_fields_admin_description": "회원이 이 내용을 수정할 수 없게 됩니다",
|
||||
"locked_fields_member_description": "이 옵션은 팀 관리자에 의해 잠겼습니다",
|
||||
"url": "링크",
|
||||
"hidden": "숨기기",
|
||||
@@ -754,12 +759,16 @@
|
||||
"new_event_type_to_book_description": "사람들이 시간을 예약할 수 있는 새 이벤트 타입을 만듭니다.",
|
||||
"length": "길이",
|
||||
"minimum_booking_notice": "최소 고지",
|
||||
"offset_toggle": "오프셋 시작 시간",
|
||||
"offset_toggle_description": "지정된 시간(분)으로 예약자에게 표시되는 오프셋 시간 슬롯",
|
||||
"offset_start": "오프셋 기준",
|
||||
"offset_start_description": "예: 이렇게 하면 예약자에게 {{ adjustedTime }}에 {{ originalTime }} 대신 시간 슬롯이 표시됨",
|
||||
"slot_interval": "시간 슬롯 간격",
|
||||
"slot_interval_default": "이벤트 길이 사용(기본값)",
|
||||
"delete_event_type": "이벤트 타입을 삭제하시겠습니까?",
|
||||
"delete_managed_event_type": "관리 이벤트 유형을 삭제하시겠습니까?",
|
||||
"delete_event_type_description": "링크를 공유한 회원은 더 이상 링크를 사용하여 예약할 수 없게 됩니다.",
|
||||
"delete_managed_event_type_description": "<ul><li>이 이벤트 유형에 할당된 회원은 이벤트 유형도 삭제됩니다.</li><li>링크를 공유한 회원은 더 이상 링크를 사용하여 예약할 수 없게 됩니다.</li> </ul>",
|
||||
"delete_managed_event_type": "관리형 이벤트 타입을 삭제하시겠습니까?",
|
||||
"delete_event_type_description": "이 링크를 공유한 회원이 더 이상 링크를 사용하여 예약할 수 없게 됩니다.",
|
||||
"delete_managed_event_type_description": "<ul><li>이 이벤트 타입에 할당된 회원은 이벤트 타입도 삭제됩니다.</li><li>이 링크를 공유한 회원이 더 이상 링크를 사용하여 예약할 수 없게 됩니다.</li> </ul>",
|
||||
"confirm_delete_event_type": "네, 삭제합니다",
|
||||
"delete_account": "계정 삭제",
|
||||
"confirm_delete_account": "예, 계정을 삭제합니다",
|
||||
@@ -906,6 +915,7 @@
|
||||
"duplicate": "복제",
|
||||
"offer_seats": "좌석 제공",
|
||||
"offer_seats_description": "예약에 좌석 제공(게스트를 비활성화하고 예약을 선택함)",
|
||||
"seats_available_one": "사용 가능한 좌석",
|
||||
"seats_available_other": "사용 가능한 좌석",
|
||||
"number_of_seats": "예약당 좌석 수",
|
||||
"enter_number_of_seats": "좌석 수 입력",
|
||||
@@ -1157,7 +1167,7 @@
|
||||
"variable_format": "변수 형식",
|
||||
"webhook_subscriber_url_reserved": "웹훅 구독자 URL이 이미 정의되었습니다",
|
||||
"custom_input_as_variable_info": "추가 입력 레이블의 모든 특수 문자는 무시하고(문자와 숫자만 사용) 모든 문자에 대문자를 사용하며 공백은 밑줄로 대체합니다.",
|
||||
"using_booking_questions_as_variables": "예약 질문을 변수로 어떻게 사용합니까?",
|
||||
"using_booking_questions_as_variables": "예약 질문을 어떻게 변수로 사용합니까?",
|
||||
"download_desktop_app": "데스크탑 앱 다운로드",
|
||||
"set_ping_link": "Ping 링크 설정",
|
||||
"rate_limit_exceeded": "요금 한도 초과됨",
|
||||
@@ -1180,7 +1190,7 @@
|
||||
"event_advanced_tab_title": "고급",
|
||||
"event_setup_multiple_duration_error": "이벤트 설정: 복수 기간에는 최소 1개의 옵션이 필요합니다.",
|
||||
"event_setup_multiple_duration_default_error": "이벤트 설정: 유효한 기본 기간을 선택하십시오.",
|
||||
"event_setup_booking_limits_error": "예약 한도는 오름차순이어야 합니다. [일,주,월,년]",
|
||||
"event_setup_booking_limits_error": "예약 한도는 오름차순이어야 합니다. [일, 주, 월, 년]",
|
||||
"event_setup_duration_limits_error": "기간 한도는 오름차순이어야 합니다. [일, 주, 월, 년]",
|
||||
"select_which_cal": "예약을 추가할 캘린더 선택",
|
||||
"custom_event_name": "사용자 정의 이벤트 이름",
|
||||
@@ -1193,6 +1203,7 @@
|
||||
"create_workflow": "워크플로를 만듭니다",
|
||||
"do_this": "이렇게 하세요",
|
||||
"turn_off": "끄기",
|
||||
"turn_on": "켜기",
|
||||
"settings_updated_successfully": "설정이 업데이트되었습니다",
|
||||
"error_updating_settings": "설정 업데이트 중 오류 발생",
|
||||
"personal_cal_url": "내 개인 {{appName}} URL",
|
||||
@@ -1203,7 +1214,7 @@
|
||||
"start_of_week": "주의 시작",
|
||||
"recordings_title": "녹음",
|
||||
"recording": "녹음",
|
||||
"happy_scheduling": "즐거운 일정 관리 되세요",
|
||||
"happy_scheduling": "즐겁게 일정 관리하세요",
|
||||
"select_calendars": "중복 예약을 방지하기 위해 충돌을 확인하려는 캘린더를 선택합니다.",
|
||||
"check_for_conflicts": "충돌 확인",
|
||||
"view_recordings": "녹음 보기",
|
||||
@@ -1280,11 +1291,11 @@
|
||||
"attendee_name_info": "예약자 이름",
|
||||
"to": "대상",
|
||||
"workflow_turned_on_successfully": "{{workflowName}} 워크플로 {{offOn}} 설정 완료",
|
||||
"download_responses": "다운로드 응답",
|
||||
"download_responses": "응답 다운로드",
|
||||
"download_responses_description": "양식에 대한 모든 응답을 CSV 형식으로 다운로드합니다.",
|
||||
"download": "다운로드",
|
||||
"download_recording": "기록 다운로드",
|
||||
"recording_from_your_recent_call": "Cal.com에서 최근 통화 내용 녹음 파일을 다운로드 할 준비가 되었습니다",
|
||||
"recording_from_your_recent_call": "Cal.com에서 이루어진 최근 통화 녹음을 지금 바로 다운로드할 수 있습니다",
|
||||
"create_your_first_form": "첫 양식 작성",
|
||||
"create_your_first_form_description": "라우팅 양식을 사용하면 적격성 질문을 올리고 올바른 사람 또는 이벤트 유형으로 라우팅할 수 있습니다.",
|
||||
"create_your_first_webhook": "첫 웹훅 생성",
|
||||
@@ -1330,7 +1341,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "여기에서 생성했던 모든 양식과 경로를 볼 수 있습니다.",
|
||||
"routing_forms_description": "참석자를 올바른 대상으로 안내하는 양식 만들기",
|
||||
"routing_forms_send_email_owner": "소유자에게 이메일 보내기",
|
||||
"routing_forms_send_email_owner_description": "양식이 제출되면 소유자에게 이메일을 발송합니다",
|
||||
"add_new_form": "새 양식 추가",
|
||||
@@ -1384,7 +1395,7 @@
|
||||
"add_limit": "제한 추가",
|
||||
"team_name_required": "팀 이름 필수",
|
||||
"show_attendees": "게스트 간에 참석자 정보 공유",
|
||||
"how_booking_questions_as_variables": "예약 질문을 변수로 어떻게 사용합니까?",
|
||||
"how_booking_questions_as_variables": "예약 질문을 어떻게 변수로 사용합니까?",
|
||||
"format": "형식",
|
||||
"uppercase_for_letters": "모든 문자에 대문자 사용",
|
||||
"replace_whitespaces_underscores": "공백을 밑줄로 바꾸기",
|
||||
@@ -1399,7 +1410,7 @@
|
||||
"billing_help_title": "또 필요한 것이 있나요?",
|
||||
"billing_help_description": "청구와 관련하여 추가적인 도움이 필요한 경우 지원 팀이 도와드리겠습니다.",
|
||||
"billing_help_cta": "지원 문의",
|
||||
"ignore_special_characters_booking_questions": "예약 질문 ID의 특수 문자는 무시하십시오. 문자와 숫자만 사용하십시오",
|
||||
"ignore_special_characters_booking_questions": "예약 질문 ID의 특수 문자는 무시합니다. 문자와 숫자만 사용하십시오",
|
||||
"retry": "재시도",
|
||||
"fetching_calendars_error": "캘린더를 가져오는 중 문제가 발생했습니다. <1>다시 시도</1>하거나 고객 지원에 문의하십시오.",
|
||||
"calendar_connection_fail": "캘린더 연결 실패",
|
||||
@@ -1482,17 +1493,17 @@
|
||||
"fixed_round_robin": "고정 라운드 로빈",
|
||||
"add_one_fixed_attendee": "한 명의 고정 참석자를 추가하고 여러 참석자를 라운드 로빈 방식으로 순환합니다.",
|
||||
"calcom_is_better_with_team": "Cal.com이 팀 기능으로 더욱 좋아졌습니다",
|
||||
"the_calcom_team": "Cal.com 팀",
|
||||
"the_calcom_team": "Cal.com 팀 드림",
|
||||
"add_your_team_members": "이벤트 타입에 팀원을 추가하세요. 집단 일정 관리를 사용해 모든 사람을 포함하거나 라운드 로빈 일정 관리로 가장 적합한 인원을 찾으세요.",
|
||||
"booking_limit_reached": "이 이벤트 타입에 대한 예약 한도에 도달했습니다",
|
||||
"duration_limit_reached": "이 이벤트 타입에 대한 기간 한도에 도달했습니다",
|
||||
"admin_has_disabled": "관리자가 {{appName}} 앱을 비활성화했습니다",
|
||||
"disabled_app_affects_event_type": "관리자가 이벤트 타입 {{eventType}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다",
|
||||
"event_replaced_notice": "관리자가 이벤트 유형 중 하나를 바꿨습니다",
|
||||
"email_subject_slug_replacement": "팀 관리자가 당신의 이벤트 /{{slug}}를 변경하였습니다",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong> 팀의 관리자가 귀하의 이벤트 유형 <strong>/{{slug}}</strong>을(를) 관리 이벤트 유형으로 바꿨습니다.",
|
||||
"event_replaced_notice": "관리자가 이벤트 타입 중 하나를 교체했습니다",
|
||||
"email_subject_slug_replacement": "팀 관리자가 귀하의 이벤트 /{{slug}}를 교체했습니다",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong> 팀의 관리자가 귀하의 이벤트 타입 <strong>/{{slug}}</strong>을(를) 제어 대상인 관리형 이벤트 타입으로 교체했습니다.",
|
||||
"email_body_slug_replacement_info": "링크는 계속 유효하지만 일부 설정이 변경될 수 있습니다. 이벤트 타입에서 이를 검토할 수 있습니다.",
|
||||
"email_body_slug_replacement_suggestion": "이벤트 유형에 대해 질문이 있는 경우 관리자에게 문의하십시오.<br /><br />즐거운 일정 되시기 바랍니다. <br />Cal.com 팀",
|
||||
"email_body_slug_replacement_suggestion": "이벤트 타입에 대한 질문이 있는 경우, 관리자에게 문의하십시오.<br /><br />즐겁게 일정 관리하세요! <br />Cal.com 팀 드림",
|
||||
"disable_payment_app": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다. 참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
|
||||
"payment_disabled_still_able_to_book": "참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
|
||||
"app_disabled_with_event_type": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다.",
|
||||
@@ -1541,7 +1552,7 @@
|
||||
"date_overrides_update_btn": "재정의 업데이트",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-카피",
|
||||
"set_as_default": "기본값으로 설정",
|
||||
"hide_eventtype_details": "이벤트 타입 세부 정보 숨기기",
|
||||
"hide_eventtype_details": "이벤트 타입 세부정보 숨기기",
|
||||
"show_navigation": "탐색 표시",
|
||||
"hide_navigation": "탐색 숨기기",
|
||||
"verification_code_sent": "인증 코드 발송됨",
|
||||
@@ -1555,7 +1566,7 @@
|
||||
"create_your_first_team_webhook_description": "이 팀 이벤트 유형에 대한 첫 번째 웹훅 만들기",
|
||||
"create_webhook_team_event_type": "이 팀 이벤트 유형에 대한 웹훅 만들기",
|
||||
"disable_success_page": "예약 성공 페이지 비활성화(리디렉션 URL이 있는 경우에만 작동)",
|
||||
"invalid_admin_password": "관리자이지만 비밀번호 길이가 15자 이상이 아니거나, 이중 인증이 설정되지 않았습니다",
|
||||
"invalid_admin_password": "관리자이지만 비밀번호 길이가 15자 이상이 아니거나, 2단계 인증이 설정되지 않았습니다",
|
||||
"change_password_admin": "관리자 액세스를 위해 비밀번호 변경",
|
||||
"username_already_taken": "이미 사용 중인 사용자 이름입니다",
|
||||
"assignment": "할당",
|
||||
@@ -1690,14 +1701,14 @@
|
||||
"add_1_option_per_line": "한 줄에 옵션 1개 추가",
|
||||
"select_a_router": "라우터 선택",
|
||||
"add_a_new_route": "새 경로 추가",
|
||||
"make_informed_decisions": "인사이트를 활용하여 정보를 바탕으로 합리적인 결정을 내려보세요",
|
||||
"make_informed_decisions_description": "Insights 대시보드는 팀의 모든 활동들을 보여주고, 일정 및 의사 결정을 개선하기 위한 트렌드를 보여줍니다.",
|
||||
"view_bookings_across": "모든 멤버들의 예약 내역을 조회해보세요",
|
||||
"view_bookings_across_description": "가장 많은 예약을 받고 있는 멤버를 확인하고, 멤버들 간 공정한 예약 분배가 이루어지도록 해보세요",
|
||||
"identify_booking_trends": "예약 트렌드를 파악해보세요",
|
||||
"identify_booking_trends_description": "예약자에게 인기 있는 요일 및 시간을 확인해보세요",
|
||||
"spot_popular_event_types": "인기 있는 이벤트 타입을 찾아보세요",
|
||||
"spot_popular_event_types_description": "어떤 이벤트 타입이 가장 많은 클릭을 받고 예약이 되고 있는지 확인해보세요",
|
||||
"make_informed_decisions": "Insights를 이용해 정확한 정보에 입각한 의사 결정 내리기",
|
||||
"make_informed_decisions_description": "Insights 대시보드는 팀의 모든 활동을 드러내고, 팀의 일정 관리와 의사 결정을 개선하기 위한 트렌드를 보여줍니다.",
|
||||
"view_bookings_across": "모든 회원의 예약 내용 보기",
|
||||
"view_bookings_across_description": "가장 많은 예약을 받고 있는 회원을 확인하고, 전체 회원 사이에 최선의 분배가 이루어지도록 보장",
|
||||
"identify_booking_trends": "예약 트렌드 파악",
|
||||
"identify_booking_trends_description": "예약자에게 인기 있는 요일과 시간 확인",
|
||||
"spot_popular_event_types": "인기 있는 이벤트 타입 찾기",
|
||||
"spot_popular_event_types_description": "어떤 이벤트 타입이 가장 많은 클릭과 예약을 받고 있는지 확인",
|
||||
"no_responses_yet": "아직 응답 없음",
|
||||
"this_will_be_the_placeholder": "이것은 자리 표시자가 됩니다",
|
||||
"error_booking_event": "이벤트를 예약하는 도중 오류가 발생했습니다. 페이지를 새로고침한 후 다시 시도해주세요.",
|
||||
@@ -1708,6 +1719,7 @@
|
||||
"switch_weekly": "주간 보기로 전환",
|
||||
"switch_multiday": "일간 보기로 전환",
|
||||
"num_locations": "{{num}}개의 위치 옵션",
|
||||
"select_on_next_step": "다음 단계에 대해 선택",
|
||||
"this_meeting_has_not_started_yet": "이 회의는 아직 시작되지 않았습니다",
|
||||
"this_app_requires_connected_account": "{{appName}}에 연결된 {{dependencyName}} 계정이 필요합니다",
|
||||
"connect_app": "{{dependencyName}} 연결",
|
||||
@@ -1719,25 +1731,26 @@
|
||||
"timezone_variable": "시간대",
|
||||
"timezone_info": "예약 수신자의 시간대",
|
||||
"event_end_time_variable": "이벤트 종료 시간",
|
||||
"event_end_time_info": "이벤트 종료 시간",
|
||||
"event_end_time_info": "이벤트가 종료되는 시간",
|
||||
"cancel_url_variable": "취소 URL",
|
||||
"cancel_url_info": "예약 취소 URL",
|
||||
"cancel_url_info": "예약 취소를 위한 URL",
|
||||
"reschedule_url_variable": "일정 변경 URL",
|
||||
"reschedule_url_info": "예약 일정 변경 URL",
|
||||
"reschedule_url_info": "예약 일정을 변경하기 위한 URL",
|
||||
"invalid_event_name_variables": "이벤트 이름에 유효하지 않은 변수가 있습니다",
|
||||
"select_all": "모두 선택",
|
||||
"default_conferencing_bulk_title": "기존 이벤트 타입을 일괄 업데이트",
|
||||
"members_default_schedule": "회원의 기본 일정",
|
||||
"set_by_admin": "팀 관리자에 의해 설정됨",
|
||||
"members_default_location": "회원의 기본 위치",
|
||||
"members_default_schedule_description": "각 회원의 기본 사용 가능 일정을 사용합니다. 편집 또는 변경할 수 있습니다.",
|
||||
"requires_at_least_one_schedule": "적어도 한 개 이상의 스케줄이 필요합니다",
|
||||
"members_default_schedule_description": "각 회원의 기본 가용성 일정을 사용합니다. 편집 또는 변경할 수 있습니다.",
|
||||
"requires_at_least_one_schedule": "적어도 하나 이상의 일정이 필요합니다",
|
||||
"default_conferencing_bulk_description": "선택한 이벤트 타입의 위치 업데이트",
|
||||
"locked_for_members": "회원에 대해 잠김",
|
||||
"locked_apps_description": "회원은 활성 앱을 볼 수 있지만 앱 설정을 편집할 수는 없습니다",
|
||||
"locked_webhooks_description": "회원은 활성 웹훅을 볼 수 있지만 웹훅 설정을 편집할 수는 없습니다",
|
||||
"locked_workflows_description": "회원은 활성 워크플로를 볼 수 있지만 워크플로 설정을 편집할 수는 없습니다",
|
||||
"app_not_connected": "{{appName}} 계정을 연결하지 않으셨습니다.",
|
||||
"locked_by_admin": "팀 관리자에 의해 잠김",
|
||||
"app_not_connected": "{{appName}} 계정을 연결하지 않았습니다.",
|
||||
"connect_now": "지금 연결",
|
||||
"managed_event_dialog_confirm_button_one": "회원 {{count}}명 바꾸기 및 알림",
|
||||
"managed_event_dialog_confirm_button_other": "회원 {{count}}명 바꾸기 및 알림",
|
||||
@@ -1747,8 +1760,8 @@
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> 님이 이미 <string>/{{slug}}</strong> url을 사용하는 중입니다.",
|
||||
"managed_event_dialog_clarification": "URL 바꾸기를 선택하면 회원에게 알려드립니다. 덮어쓰지 않으려면 돌아가서 제거하십시오.",
|
||||
"review_event_type": "이벤트 타입 검토",
|
||||
"looking_for_more_analytics": "추가적인 분석을 원하시나요?",
|
||||
"looking_for_more_insights": "더 많은 인사이트를 찾고계신가요?",
|
||||
"looking_for_more_analytics": "더 많은 분석 정보를 원합니까?",
|
||||
"looking_for_more_insights": "더 많은 인사이트를 찾고 있습니까?",
|
||||
"add_filter": "필터 추가",
|
||||
"select_user": "사용자 선택",
|
||||
"select_event_type": "이벤트 타입 선택",
|
||||
@@ -1756,37 +1769,37 @@
|
||||
"popular_events": "인기 이벤트",
|
||||
"no_event_types_found": "이벤트 타입을 찾을 수 없습니다",
|
||||
"average_event_duration": "평균 이벤트 기간",
|
||||
"most_booked_members": "가장 많이 예약한 회원",
|
||||
"least_booked_members": "가장 적게 예약한 회원",
|
||||
"most_booked_members": "예약이 가장 많은 회원",
|
||||
"least_booked_members": "예약이 가장 적은 회원",
|
||||
"events_created": "생성된 이벤트",
|
||||
"events_completed": "완료된 이벤트",
|
||||
"events_cancelled": "취소된 이벤트",
|
||||
"events_rescheduled": "일정이 변경된 이벤트",
|
||||
"from_last_period": "마지막 기간부터",
|
||||
"from_to_date_period": "시작일: {{startDate}} 종료일: {{endDate}}",
|
||||
"analytics_for_organisation": "인사이트",
|
||||
"analytics_for_organisation": "Insights",
|
||||
"subtitle_analytics": "팀 활동에 대해 자세히 알아보기",
|
||||
"redirect_url_warning": "리디렉션을 추가하면 성공 페이지가 비활성화됩니다. 사용자 지정 성공 페이지에 \"예약 확인됨\"을 언급해야 합니다.",
|
||||
"redirect_url_warning": "리디렉션을 추가하면 성공 페이지가 비활성화됩니다. 사용자 지정 성공 페이지에 \"예약 확정\"을 언급해야 합니다.",
|
||||
"event_trends": "이벤트 트렌드",
|
||||
"clear_filters": "필터 삭제",
|
||||
"clear_filters": "필터 지우기",
|
||||
"hold": "보류",
|
||||
"on_booking_option": "예약 시 결제 징수",
|
||||
"hold_option": "노쇼 수수료 부과",
|
||||
"card_held": "카드 보류됨",
|
||||
"charge_card": "카드 부과",
|
||||
"card_charged": "카드 부과됨",
|
||||
"charge_card": "카드에 요금 부과",
|
||||
"card_charged": "카드에 요금 부과됨",
|
||||
"no_show_fee_amount": "{{amount, currency}} 노쇼 수수료",
|
||||
"no_show_fee": "No-show 수수료",
|
||||
"no_show_fee": "노쇼 수수료",
|
||||
"submit_card": "카드 제출",
|
||||
"submit_payment_information": "결제 정보 제출",
|
||||
"meeting_awaiting_payment_method": "회의에 대한 결제 수단을 기다리는 중입니다",
|
||||
"no_show_fee_charged_email_subject": "{{amount, currency}}에 대한 노쇼 수수료 부과됨: {{title}} 날짜 {{date}}",
|
||||
"no_show_fee_charged_text_body": "No-show 수수료가 부과되었습니다",
|
||||
"no_show_fee_charged_text_body": "노쇼 수수료가 부과되었습니다",
|
||||
"no_show_fee_charged_subtitle": "다음 이벤트에 대해 {{amount, currency}}에 대한 노쇼 수수료가 부과되었습니다",
|
||||
"error_charging_card": "No-show 수수료 청구 중 문제가 발생했습니다. 나중에 다시 시도해주세요.",
|
||||
"collect_no_show_fee": "No-show 수수료를 청구하세요",
|
||||
"no_show_fee_charged": "No-show 수수료가 부과됩니다",
|
||||
"insights": "인사이트",
|
||||
"error_charging_card": "노쇼 수수료를 부과하는 중 문제가 발생했습니다. 나중에 다시 시도해주세요.",
|
||||
"collect_no_show_fee": "노쇼 수수료 청구",
|
||||
"no_show_fee_charged": "노쇼 수수료 부과됨",
|
||||
"insights": "Insights",
|
||||
"testing_workflow_info_message": "이 워크플로를 테스트할 때 이메일 및 SMS는 최소 1시간 전에만 예약할 수 있다는 점에 유의하세요",
|
||||
"insights_no_data_found_for_filter": "선택한 필터 또는 날짜에 대한 데이터를 찾을 수 없습니다.",
|
||||
"acknowledge_booking_no_show_fee": "본인은 이 이벤트에 참석하지 않을 경우 {{amount, currency}}에 대한 노쇼 수수료가 내 카드에 부과됨을 인정합니다.",
|
||||
@@ -1794,15 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "현재 좌석을 활성화할 수 없으므로 노쇼 수수료를 부과할 수 없습니다",
|
||||
"complete_your_booking": "예약을 완료하세요",
|
||||
"complete_your_booking_subject": "예약 완료: {{title}} 날짜 {{date}}",
|
||||
"confirm_your_details": "세부 정보 확인",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "참석자에게 {{amount, currency}}을 청구하려고 합니다. 계속 진행하시겠습니까?",
|
||||
"charge_attendee": "참석자에게 {{amount, currency}}을 청구합니다.",
|
||||
"payment_app_commission": "결제가 필요합니다 (건당 수수료 {{paymentFeePercentage}}% + {{fee, currency}})",
|
||||
"email_invite_team": "{{email}} 이 초대되었습니다.",
|
||||
"email_invite_team": "{{email}}이(가) 초대되었습니다",
|
||||
"email_invite_team_bulk": "사용자 {{userCount}}명 초대됨",
|
||||
"error_collecting_card": "카드 수집 중 오류 발생",
|
||||
"image_size_limit_exceed": "업로드되는 이미지의 크기는 5MB를 초과하면 안 됩니다.",
|
||||
"inline_embed": "인라인 임베드",
|
||||
"load_inline_content": "다른 웹 사이트 콘텐츠와 함께 이벤트 유형을 인라인으로 직접 로드합니다.",
|
||||
"floating_pop_up_button": "플로팅 팝업 버튼",
|
||||
"floating_button_trigger_modal": "이벤트 유형으로 모달을 트리거하는 플로팅 버튼을 사이트에 배치합니다.",
|
||||
"pop_up_element_click": "요소 클릭을 통한 팝업",
|
||||
"open_dialog_with_element_click": "누군가 요소를 클릭하면 Cal 대화 상자를 엽니다.",
|
||||
"need_help_embedding": "도움이 필요하신가요? Wix, Squarespace, 또는 WordPress에서 Cal을 임베드하는 가이드를 참조하거나, 자주 묻는 질문을 확인하거나, 고급 임베드 옵션을 살펴보세요.",
|
||||
"book_my_cal": "내 Cal 예약",
|
||||
"invite_as": "다른 이름으로 초대",
|
||||
"form_updated_successfully": "양식이 성공적으로 업데이트되었습니다.",
|
||||
"email_not_cal_member_cta": "팀 합류",
|
||||
"disable_attendees_confirmation_emails": "참석자에 대한 기본 확인 이메일 비활성화",
|
||||
"disable_attendees_confirmation_emails_description": "이 이벤트 유형에는 이벤트가 예약되었을 때 참석자들에게 이메일을 보내는 최소한 하나의 워크플로가 활성화되어 있습니다.",
|
||||
"disable_host_confirmation_emails": "호스트에 대한 기본 확인 이메일 비활성화",
|
||||
"disable_host_confirmation_emails_description": "이 이벤트 유형에는 이벤트가 예약될 때 호스트에게 이메일을 보내는 최소한 하나의 워크플로가 활성화되어 있습니다."
|
||||
"disable_host_confirmation_emails_description": "이 이벤트 유형에는 이벤트가 예약될 때 호스트에게 이메일을 보내는 최소한 하나의 워크플로가 활성화되어 있습니다.",
|
||||
"add_an_override": "재정의 추가",
|
||||
"import_from_google_workspace": "Google Workspace에서 사용자 가져오기",
|
||||
"connect_google_workspace": "Google Workspace 연결",
|
||||
"google_workspace_admin_tooltip": "이 기능을 사용하려면 Workspace 관리자여야 합니다",
|
||||
"first_event_type_webhook_description": "이 이벤트 유형에 대한 첫 번째 웹훅 만들기",
|
||||
"create_for": "작성 대상"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"rejection_confirmation": "Odrzuć prośbę o rezerwację",
|
||||
"manage_this_event": "Zarządzaj wydarzeniem",
|
||||
"invite_team_member": "Zaproś członka zespołu",
|
||||
"invite_team_bulk_segment": "Import zbiorczy",
|
||||
"invite_team_notifcation_badge": "Zap.",
|
||||
"your_event_has_been_scheduled": "Zaplanowano wydarzenie",
|
||||
"your_event_has_been_scheduled_recurring": "Twoje wydarzenie cykliczne zostało zaplanowane",
|
||||
@@ -222,6 +223,7 @@
|
||||
"go_back_login": "Wróć na stronę logowania",
|
||||
"error_during_login": "Wystąpił błąd podczas logowania. Wróć do ekranu logowania i spróbuj ponownie.",
|
||||
"request_password_reset": "Wyślij wiadomość e-mail umożliwiającą zresetowanie",
|
||||
"send_invite": "Wyślij zaproszenie",
|
||||
"forgot_password": "Nie pamiętasz hasła?",
|
||||
"forgot": "Nie pamiętasz?",
|
||||
"done": "Gotowe",
|
||||
@@ -237,6 +239,8 @@
|
||||
"set_availability": "Ustaw swoją dostępność",
|
||||
"continue_without_calendar": "Kontynuuj bez kalendarza",
|
||||
"connect_your_calendar": "Połącz swój kalendarz",
|
||||
"connect_your_video_app": "Połącz aplikacje wideo",
|
||||
"connect_your_video_app_instructions": "Podłącz aplikacje wideo, aby korzystać z ich w swoich typach wydarzeń.",
|
||||
"connect_your_calendar_instructions": "Podłącz swój kalendarz do automatycznego sprawdzania dostępności i nowych wydarzeń, zgodnie z planem.",
|
||||
"set_up_later": "Skonfiguruj później",
|
||||
"current_time": "Aktualny czas",
|
||||
@@ -367,6 +371,7 @@
|
||||
"create_webhook": "Utwórz Webhook",
|
||||
"booking_cancelled": "Rezerwacja anulowana",
|
||||
"booking_rescheduled": "Rezerwacja Przełożona",
|
||||
"recording_ready": "Link do pobrania nagrania gotowy",
|
||||
"booking_created": "Rezerwacja Utworzona",
|
||||
"meeting_ended": "Spotkanie zakończone",
|
||||
"form_submitted": "Przesłano formularz",
|
||||
@@ -465,6 +470,7 @@
|
||||
"friday": "Piątek",
|
||||
"saturday": "Sobota",
|
||||
"sunday": "Niedziela",
|
||||
"all_booked_today": "Wszystko zarezerwowane.",
|
||||
"slots_load_fail": "Nie można załadować dostępnych przedziałów czasu.",
|
||||
"additional_guests": "Dodaj gości",
|
||||
"your_name": "Twoje imię",
|
||||
@@ -752,6 +758,9 @@
|
||||
"new_event_type_to_book_description": "Utwórz nowy typ wydarzenia, z którym ludzie mogą rezerwować czasy.",
|
||||
"length": "Długość",
|
||||
"minimum_booking_notice": "Minimalne powiadomienie",
|
||||
"offset_toggle": "Opóźnij czasy rozpoczęcia",
|
||||
"offset_toggle_description": "Przesuń przedziały czasowe wyświetlane rezerwującym o określoną liczbę minut",
|
||||
"offset_start": "Przesuń o",
|
||||
"slot_interval": "Przedziały czasowe",
|
||||
"slot_interval_default": "Użyj długości zdarzenia (domyślnie)",
|
||||
"delete_event_type": "Usunąć typ wydarzenia?",
|
||||
@@ -904,6 +913,7 @@
|
||||
"duplicate": "Duplikat",
|
||||
"offer_seats": "Zaproponuj miejsca",
|
||||
"offer_seats_description": "Zaoferuj miejsca do zarezerwowania. To automatycznie wyłącza rezerwacje gości i rezerwacje wstępne.",
|
||||
"seats_available_one": "Dostępne miejsce",
|
||||
"seats_available_other": "Dostępne miejsca",
|
||||
"number_of_seats": "Liczba miejsc na rezerwację",
|
||||
"enter_number_of_seats": "Wprowadź liczbę miejsc",
|
||||
@@ -1038,6 +1048,7 @@
|
||||
"event_cancelled_trigger": "gdy wydarzenie zostanie odwołane",
|
||||
"new_event_trigger": "gdy nowe wydarzenie zostanie zarezerwowane",
|
||||
"email_host_action": "wyślij wiadomość e-mail do hosta",
|
||||
"email_attendee_action": "wyślij wiadomość e-mail do uczestników",
|
||||
"sms_attendee_action": "wyślij SMS do uczestnika",
|
||||
"sms_number_action": "wyślij wiadomość SMS na określony numer",
|
||||
"workflows": "Przepływy pracy",
|
||||
@@ -1190,6 +1201,7 @@
|
||||
"create_workflow": "Utwórz przepływ pracy",
|
||||
"do_this": "Zrób to",
|
||||
"turn_off": "Wyłącz",
|
||||
"turn_on": "Włącz",
|
||||
"settings_updated_successfully": "Ustawienia zaktualizowane pomyślnie",
|
||||
"error_updating_settings": "Błąd podczas aktualizacji ustawień",
|
||||
"personal_cal_url": "Mój osobisty adres URL aplikacji {{appName}}",
|
||||
@@ -1246,6 +1258,7 @@
|
||||
"calendars_description": "Skonfiguruj sposoby interakcji typów wydarzeń z kalendarzami",
|
||||
"appearance_description": "Zarządzaj ustawieniami wyglądu rezerwacji",
|
||||
"conferencing_description": "Dodaj ulubione aplikacje do wideokonferencji, aby umożliwić korzystanie z nich podczas spotkań",
|
||||
"add_conferencing_app": "Dodaj aplikację konferencyjną",
|
||||
"password_description": "Zarządzaj ustawieniami haseł do kont",
|
||||
"2fa_description": "Zarządzaj ustawieniami haseł do kont",
|
||||
"we_just_need_basic_info": "Potrzebujemy podstawowych informacji, aby skonfigurować Twój profil.",
|
||||
@@ -1695,6 +1708,14 @@
|
||||
"spot_popular_event_types_description": "Zobacz, które typy wydarzeń są najczęściej klikane i rezerwowane",
|
||||
"no_responses_yet": "Nie ma jeszcze odpowiedzi",
|
||||
"this_will_be_the_placeholder": "To będzie symbol zastępczy",
|
||||
"timeslot_missing_title": "Nie wybrano przedziału czasowego",
|
||||
"timeslot_missing_description": "Wybierz przedział czasowy, aby zarezerwować wydarzenie.",
|
||||
"timeslot_missing_cta": "Wybierz przedział czasowy",
|
||||
"switch_monthly": "Przełącz na widok miesięczny",
|
||||
"switch_weekly": "Przełącz na widok tygodniowy",
|
||||
"switch_multiday": "Przełącz na widok dzienny",
|
||||
"num_locations": "Lokalizacje do wyboru: {{num}}",
|
||||
"select_on_next_step": "Wybierz następny krok",
|
||||
"this_meeting_has_not_started_yet": "To spotkanie jeszcze się nie rozpoczęło",
|
||||
"this_app_requires_connected_account": "Aplikacja {{appName}} wymaga podłączonego konta {{dependencyName}}",
|
||||
"connect_app": "Podłącz {{dependencyName}}",
|
||||
@@ -1724,6 +1745,7 @@
|
||||
"locked_apps_description": "Członkowie będą mogli sprawdzić aktywne aplikacje, ale nie będą mogli edytować ich ustawień",
|
||||
"locked_webhooks_description": "Członkowie będą mogli sprawdzić aktywne webhooki, ale nie będą mogli edytować ich ustawień",
|
||||
"locked_workflows_description": "Członkowie będą mogli sprawdzić aktywne przepływy pracy, ale nie będą mogli edytować ich ustawień",
|
||||
"locked_by_admin": "Zablokowane przez administratora zespołu",
|
||||
"app_not_connected": "Nie podłączono konta {{appName}}.",
|
||||
"connect_now": "Podłącz teraz",
|
||||
"managed_event_dialog_confirm_button_one": "Zastąp i poinformuj {{count}} członka",
|
||||
@@ -1781,5 +1803,20 @@
|
||||
"seats_and_no_show_fee_error": "Obecnie nie można udostępnić miejsc ani naliczyć opłaty za niestawienie się.",
|
||||
"complete_your_booking": "Ukończ rezerwację",
|
||||
"complete_your_booking_subject": "Ukończ rezerwację: {{title}} dnia {{date}}",
|
||||
"email_invite_team": "Zaproszono {{email}}"
|
||||
"confirm_your_details": "Potwierdź swoje dane",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Zamierzasz obciążyć uczestnika kwotą {{amount, currency}}. Czy na pewno chcesz kontynuować?",
|
||||
"charge_attendee": "Pobierz od uczestnika opłatę w wysokości {{amount, currency}}",
|
||||
"payment_app_commission": "Wymagaj płatności ({{paymentFeePercentage}}% + {{fee, currency}} prowizji od transakcji)",
|
||||
"email_invite_team": "Zaproszono {{email}}",
|
||||
"email_invite_team_bulk": "Zaproszono {{userCount}} użytkowników",
|
||||
"error_collecting_card": "Błąd podczas pobierania karty",
|
||||
"image_size_limit_exceed": "Przesłany obraz nie powinien być większy niż 5 MB.",
|
||||
"floating_pop_up_button": "Pływający przycisk wyskakujący",
|
||||
"invite_as": "Zaproś jako",
|
||||
"form_updated_successfully": "Formularz zaktualizowany pomyślnie.",
|
||||
"email_not_cal_member_cta": "Dołącz do zespołu",
|
||||
"add_an_override": "Dodaj zastąpienie",
|
||||
"import_from_google_workspace": "Importuj użytkowników z usługi Google Workspace",
|
||||
"create_for": "Utwórz dla"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Recusar a reserva",
|
||||
"manage_this_event": "Gerenciar este evento",
|
||||
"invite_team_member": "Convidar membro da equipe",
|
||||
"invite_team_individual_segment": "Convite individual",
|
||||
"invite_team_bulk_segment": "Importação em massa",
|
||||
"invite_team_notifcation_badge": "Con.",
|
||||
"your_event_has_been_scheduled": "A sua reunião foi agendada",
|
||||
"your_event_has_been_scheduled_recurring": "Seu evento recorrente foi agendado",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Voltar à página de login",
|
||||
"error_during_login": "Ocorreu um erro ao iniciar sessão. Volte a tela de login e tente novamente.",
|
||||
"request_password_reset": "Solicitar redefinição da senha",
|
||||
"send_invite": "Enviar convite",
|
||||
"forgot_password": "Esqueceu a senha?",
|
||||
"forgot": "Esqueceu?",
|
||||
"done": "Concluído",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Definir disponibilidade",
|
||||
"continue_without_calendar": "Continuar sem calendário",
|
||||
"connect_your_calendar": "Conectar seu calendário",
|
||||
"connect_your_video_app": "Conecte seus aplicativos de vídeo",
|
||||
"connect_your_video_app_instructions": "Conecte seus aplicativos de vídeo para usá-los nos seus tipos de evento.",
|
||||
"connect_your_calendar_instructions": "Conecte o seu calendário para verificar automaticamente as horas ocupadas e os novos eventos à medida em que são agendados.",
|
||||
"set_up_later": "Configurar mais tarde",
|
||||
"current_time": "Hora atual",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Criar Webhook",
|
||||
"booking_cancelled": "Reserva Cancelada",
|
||||
"booking_rescheduled": "Reserva Reagendada",
|
||||
"recording_ready": "Link para download de gravação pronto",
|
||||
"booking_created": "Reserva Criada",
|
||||
"meeting_ended": "Reunião encerrada",
|
||||
"form_submitted": "Formulário enviado",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Sexta-feira",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"all_booked_today": "Tudo reservado.",
|
||||
"slots_load_fail": "Não foi possível carregar os horários disponíveis.",
|
||||
"additional_guests": "+ Participantes Adicionais",
|
||||
"your_name": "Seu nome",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Crie um novo tipo de evento para as pessoas agendarem horários.",
|
||||
"length": "Comprimento",
|
||||
"minimum_booking_notice": "Aviso mínimo",
|
||||
"offset_toggle": "Deslocar horas de início",
|
||||
"offset_toggle_description": "Desloca na quantidade de minutos especificada as alocações de tempo mostradas aos reservantes",
|
||||
"offset_start": "Deslocar em",
|
||||
"offset_start_description": "ex.: serão mostrados espaços de tempo aos seus reservantes à(s) {{ adjustedTime }} em vez de {{ originalTime }}",
|
||||
"slot_interval": "Intervalos de tempo",
|
||||
"slot_interval_default": "Usar a duração do evento (padrão)",
|
||||
"delete_event_type": "Excluir tipo de evento?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Duplicar",
|
||||
"offer_seats": "Oferecer assentos",
|
||||
"offer_seats_description": "Oferecer assentos para reserva. Isto desativa automaticamente reservas de convidados e confirmações.",
|
||||
"seats_available_one": "Assento disponível",
|
||||
"seats_available_other": "Assentos disponíveis",
|
||||
"number_of_seats": "Número de assentos por reserva",
|
||||
"enter_number_of_seats": "Insira a quantidade de assentos",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "quando o evento é cancelado",
|
||||
"new_event_trigger": "quando um novo evento for reservado",
|
||||
"email_host_action": "enviar e-mail para o host",
|
||||
"email_attendee_action": "enviar e-mail aos participantes",
|
||||
"sms_attendee_action": "enviar SMS para participante",
|
||||
"sms_number_action": "enviar SMS para um número específico",
|
||||
"workflows": "Fluxos de trabalho",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Criar fluxo de trabalho",
|
||||
"do_this": "Faça isso",
|
||||
"turn_off": "Desligar",
|
||||
"turn_on": "Ligar",
|
||||
"settings_updated_successfully": "Configurações atualizadas com sucesso",
|
||||
"error_updating_settings": "Erro ao atualizar configurações",
|
||||
"personal_cal_url": "Meu URL pessoal da {{appName}}",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Configure como seus tipos de evento interagem com seus calendários",
|
||||
"appearance_description": "Gerencie as configurações da aparência da sua reserva",
|
||||
"conferencing_description": "Gerencie seus aplicativos de videoconferência para suas reuniões",
|
||||
"add_conferencing_app": "Adicionar aplicativo de conferência",
|
||||
"password_description": "Gerencie as configurações para as senhas da sua conta",
|
||||
"2fa_description": "Gerencie as configurações para as senhas da sua conta",
|
||||
"we_just_need_basic_info": "Precisamos apenas de algumas informações básicas para configurar seu perfil.",
|
||||
@@ -1326,7 +1341,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Você pode ver todos os formulários e rotas que criou aqui.",
|
||||
"routing_forms_description": "Você pode ver todos os formulários e rotas que criou aqui",
|
||||
"routing_forms_send_email_owner": "Enviar e-mail ao proprietário",
|
||||
"routing_forms_send_email_owner_description": "Envia um e-mail ao proprietário após o envio do formulário",
|
||||
"add_new_form": "Adicionar novo formulário",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Ver convite",
|
||||
"email_no_user_invite_heading": "Você precisa ter recebido um convite para ingressar em uma equipe de {{appName}}",
|
||||
"email_no_user_invite_subheading": "Você recebeu um convite de {{invitedBy}} para ingressar em sua equipe em {{appName}}. {{appName}} é um agendador que concilia eventos e permite que sua equipe agende reuniões sem precisar trocar e-mails.",
|
||||
"email_user_invite_subheading": "Você recebeu um convite de {{invitedBy}} para ingressar na equipe \"{{teamName}}\" em {{appName}}. {{appName}} é um agendador que concilia eventos e permite que sua equipe agende reuniões sem precisar trocar e-mails.",
|
||||
"email_no_user_invite_steps_intro": "Orientaremos ao longo de alguns passos rápidos para que você comece logo a agendar eventos com sua equipe sem preocupação.",
|
||||
"email_no_user_step_one": "Escolha seu nome de usuário",
|
||||
"email_no_user_step_two": "Conecte com sua conta de calendário",
|
||||
@@ -1686,7 +1702,7 @@
|
||||
"select_a_router": "Selecione um formulário de roteamento",
|
||||
"add_a_new_route": "Adicione uma nova Rota",
|
||||
"make_informed_decisions": "Tome decisões conscientes com o Insights",
|
||||
"make_informed_decisions_description": "Nosso painel de informações do Insights traz à tona todas as atividades da sua equipe e mostra as tendências que permitem melhor tomada de decisão e agendamento da equipe.",
|
||||
"make_informed_decisions_description": "Nosso painel de informações do Insights apresenta todas as atividades da sua equipe e mostra as tendências que permitem melhor tomada de decisão e agendamento da equipe.",
|
||||
"view_bookings_across": "Ver reservas em todos os membros",
|
||||
"view_bookings_across_description": "Veja quem está recebendo a maioria das reservas e garanta a melhor distribuição para a sua equipe",
|
||||
"identify_booking_trends": "Identificar tendências de reserva",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Veja quais tipos de eventos estão recebendo a maioria dos cliques e reservas",
|
||||
"no_responses_yet": "Sem respostas por enquanto",
|
||||
"this_will_be_the_placeholder": "Este será o espaço reservado",
|
||||
"error_booking_event": "Ocorreu um erro ao reservar o evento, atualize a página e tente novamente",
|
||||
"timeslot_missing_title": "Nenhuma alocação de tempo selecionada",
|
||||
"timeslot_missing_description": "Selecione uma alocação de tempo para reservar o evento.",
|
||||
"timeslot_missing_cta": "Selecione a alocação de tempo",
|
||||
"switch_monthly": "Trocar para visualização mensal",
|
||||
"switch_weekly": "Trocar para visualização semanal",
|
||||
"switch_multiday": "Trocar para visualização diária",
|
||||
"num_locations": "{{num}} opções de local",
|
||||
"select_on_next_step": "Selecione na próxima etapa",
|
||||
"this_meeting_has_not_started_yet": "Esta reunião ainda não começou",
|
||||
"this_app_requires_connected_account": "{{appName}} requer uma conta de {{dependencyName}} conectada",
|
||||
"connect_app": "Conectar {{dependencyName}}",
|
||||
@@ -1708,9 +1733,9 @@
|
||||
"event_end_time_variable": "Horário de término do evento",
|
||||
"event_end_time_info": "O horário de término do evento",
|
||||
"cancel_url_variable": "Cancelar URL",
|
||||
"cancel_url_info": "A URL para cancelar a reserva",
|
||||
"cancel_url_info": "O URL para cancelar a reserva",
|
||||
"reschedule_url_variable": "URL de reagendamento",
|
||||
"reschedule_url_info": "A URL para reagendar a reserva",
|
||||
"reschedule_url_info": "O URL para reagendar a reserva",
|
||||
"invalid_event_name_variables": "Há uma variável inválida no seu nome do evento",
|
||||
"select_all": "Selecionar tudo",
|
||||
"default_conferencing_bulk_title": "Atualize em massa os tipos de evento existentes",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Os membros poderão ver os aplicativos ativos, mas não poderão editar as configurações",
|
||||
"locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar as configurações",
|
||||
"locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar as configurações",
|
||||
"locked_by_admin": "Bloqueado pelo administrador da equipe",
|
||||
"app_not_connected": "Você não conectou uma conta do {{appName}}.",
|
||||
"connect_now": "Conectar agora",
|
||||
"managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro",
|
||||
@@ -1781,5 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "Neste momento, não é possível habilitar assentos e cobrar taxa de não comparecimento",
|
||||
"complete_your_booking": "Conclua sua reserva",
|
||||
"complete_your_booking_subject": "Conclua sua reserva: {{title}} à(s) {{date}}",
|
||||
"email_invite_team": "{{email}} recebeu um convite"
|
||||
"confirm_your_details": "Confirme suas informações",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Você está prestes a cobrar {{amount, currency}} do participante. Tem certeza de que deseja continuar?",
|
||||
"charge_attendee": "Cobrar {{amount, currency}} do participante",
|
||||
"payment_app_commission": "Solicitar pagamento ({{paymentFeePercentage}}% + {{fee, currency}} de comissão a cada transação)",
|
||||
"email_invite_team": "{{email}} recebeu um convite",
|
||||
"email_invite_team_bulk": "{{userCount}} usuários foram convidados",
|
||||
"error_collecting_card": "Erro ao coletar cartão",
|
||||
"image_size_limit_exceed": "A imagem carregada não deve ultrapassar o limite de 5 MB",
|
||||
"inline_embed": "Incorporar embutido",
|
||||
"load_inline_content": "Carrega seu tipo de evento embutido diretamente no conteúdo restante do site.",
|
||||
"floating_pop_up_button": "Botão pop-up flutuante",
|
||||
"floating_button_trigger_modal": "Insere um botão flutuante no seu site para ativar um modal com seu tipo de evento.",
|
||||
"pop_up_element_click": "Pop-up ao clicar em elemento",
|
||||
"open_dialog_with_element_click": "Abra uma caixa de diálogo quando alguém clicar em um elemento.",
|
||||
"need_help_embedding": "Precisa de ajuda? Confira nosso guia para incorporar o Cal no Wix, Squarespace ou WordPress. Consulte as perguntas frequentes ou explore opções de incorporação avançadas.",
|
||||
"book_my_cal": "Reservar meu Cal",
|
||||
"invite_as": "Convidar como",
|
||||
"form_updated_successfully": "Formulário atualizado com êxito.",
|
||||
"email_not_cal_member_cta": "Ingresse na sua equipe",
|
||||
"disable_attendees_confirmation_emails": "Desative os e-mails de confirmação padrão para participantes",
|
||||
"disable_attendees_confirmation_emails_description": "Pelo menos um fluxo de trabalho está ativo neste tipo de evento, que envia um e-mail aos participantes quando o evento for reservado.",
|
||||
"disable_host_confirmation_emails": "Desative e-mails de confirmação padrão para hosts",
|
||||
"disable_host_confirmation_emails_description": "Pelo menos um fluxo de trabalho está ativo neste tipo de evento, que envia um e-mail aos hosts quando o evento for reservado.",
|
||||
"add_an_override": "Adicionar uma substituição",
|
||||
"import_from_google_workspace": "Importar usuários do Google Workspace",
|
||||
"connect_google_workspace": "Conectar com o Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Você deve ser administrador do Google Workspace para usar este recurso",
|
||||
"first_event_type_webhook_description": "Crie seu primeiro webhook para este tipo de evento",
|
||||
"create_for": "Criar para"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Rejeitar a reserva",
|
||||
"manage_this_event": "Gerir este evento",
|
||||
"invite_team_member": "Convidar membro da equipa",
|
||||
"invite_team_individual_segment": "Convidar indivíduo",
|
||||
"invite_team_bulk_segment": "Importação em conjunto",
|
||||
"invite_team_notifcation_badge": "Conv.",
|
||||
"your_event_has_been_scheduled": "O seu evento foi agendado",
|
||||
"your_event_has_been_scheduled_recurring": "O seu evento recorrente foi agendado",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Voltar à página de início de sessão",
|
||||
"error_during_login": "Ocorreu um erro ao iniciar sessão. Volte ao ecrã de início de sessão e tente de novo.",
|
||||
"request_password_reset": "Pedir restauro da palavra-passe",
|
||||
"send_invite": "Enviar convite",
|
||||
"forgot_password": "Esqueceu a palavra-passe?",
|
||||
"forgot": "Esqueceu-se?",
|
||||
"done": "Concluído",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Definir disponibilidade",
|
||||
"continue_without_calendar": "Continuar sem calendário",
|
||||
"connect_your_calendar": "Ligar o seu calendário",
|
||||
"connect_your_video_app": "Associar as suas aplicações de vídeo",
|
||||
"connect_your_video_app_instructions": "Associe as suas aplicações de vídeo para utilizar as mesmas nos tipos de eventos.",
|
||||
"connect_your_calendar_instructions": "Ligue o seu calendário para verificar automaticamente as horas ocupadas e os novos eventos à medida que são agendados.",
|
||||
"set_up_later": "Configurar mais tarde",
|
||||
"current_time": "Hora atual",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Criar Webhook",
|
||||
"booking_cancelled": "Reserva Cancelada",
|
||||
"booking_rescheduled": "Reserva Reagendada",
|
||||
"recording_ready": "Ligação de transferência da gravação pronta",
|
||||
"booking_created": "Reserva Criada",
|
||||
"meeting_ended": "A reunião terminou",
|
||||
"form_submitted": "Formulário enviado",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Sexta-Feira",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"all_booked_today": "Tudo reservado.",
|
||||
"slots_load_fail": "Não foi possível carregar os horários disponíveis.",
|
||||
"additional_guests": "+ Convidados Adicionais",
|
||||
"your_name": "O seu nome",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Crie um novo tipo de evento para as pessoas reservarem uma hora.",
|
||||
"length": "Comprimento",
|
||||
"minimum_booking_notice": "Aviso de mínimo",
|
||||
"offset_toggle": "Desfasamento dos horários de início",
|
||||
"offset_toggle_description": "Desfasar janelas horárias por um determinado número de minutos na apresentação das mesmas aos responsáveis pela reserva",
|
||||
"offset_start": "Desfasamento de",
|
||||
"offset_start_description": "por exemplo, irá mostrar as janelas horárias aos responsáveis pelas reservas às {{ adjustedTime }} em vez de {{ originalTime }}",
|
||||
"slot_interval": "Intervalos do horário",
|
||||
"slot_interval_default": "Usar duração do evento (por omissão)",
|
||||
"delete_event_type": "Eliminar tipo de evento?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Duplicar",
|
||||
"offer_seats": "Oferecer lugares",
|
||||
"offer_seats_description": "Oferecer lugares para reservas (isto desactiva os convidados e confirmações nas reservas)",
|
||||
"seats_available_one": "Lugar disponível",
|
||||
"seats_available_other": "Lugares disponíveis",
|
||||
"number_of_seats": "Número de lugares por reserva",
|
||||
"enter_number_of_seats": "Digite um número de lugares",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "quando o evento for cancelado",
|
||||
"new_event_trigger": "quando um novo evento for reservado",
|
||||
"email_host_action": "enviar email ao anfitrião",
|
||||
"email_attendee_action": "enviar e-mail aos participantes",
|
||||
"sms_attendee_action": "enviar SMS ao participante",
|
||||
"sms_number_action": "enviar SMS para um número específico",
|
||||
"workflows": "Fluxos de trabalho",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Criar um fluxo de trabalho",
|
||||
"do_this": "Fazer isto",
|
||||
"turn_off": "Desligar",
|
||||
"turn_on": "Ativar",
|
||||
"settings_updated_successfully": "Definições atualizadas com sucesso",
|
||||
"error_updating_settings": "Erro ao atualizar as definições",
|
||||
"personal_cal_url": "O meu endereço pessoal do {{appName}}",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Configure como os tipos de evento devem interagir com os seus calendários",
|
||||
"appearance_description": "Faça a gestão das definições de aparência da sua reserva",
|
||||
"conferencing_description": "Adicione as suas aplicações de videoconferência para reuniões",
|
||||
"add_conferencing_app": "Adicionar aplicação de conferência",
|
||||
"password_description": "Faça a gestão das definições para as palavras-passe da sua conta",
|
||||
"2fa_description": "Faça a gestão das definições para as palavras-passe da sua conta",
|
||||
"we_just_need_basic_info": "Precisamos apenas de algumas informações básicas para configurar o seu perfil.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Ver convite",
|
||||
"email_no_user_invite_heading": "Recebeu um convite para fazer parte de uma equipa em {{appName}}",
|
||||
"email_no_user_invite_subheading": "Recebeu um convite de {{invitedBy}} para fazer parte da respetiva equipa em {{appName}}. {{appName}} é um ferramenta de conciliação e agendamento de eventos que lhe permite a si e à sua equipa agendar reuniões sem o pingue-pongue de e-mails.",
|
||||
"email_user_invite_subheading": "Recebeu um convite de {{invitedBy}} para fazer parte da equipa `{{teamName}}` em {{appName}}. {{appName}} é uma ferramenta de conciliação e agendamento de eventos que lhe permite a si e à sua equipa agendar reuniões sem o pingue-pongue de mensagens eletrónicas.",
|
||||
"email_no_user_invite_steps_intro": "Vamos ajudar com alguns pequenos passos para que você e a sua equipa possam desfrutar rapidamente de uma gestão de agendamentos sem complicações.",
|
||||
"email_no_user_step_one": "Escolha o seu nome de utilizador",
|
||||
"email_no_user_step_two": "Associe a sua conta de calendário",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Veja quais os tipos de eventos que estão a receber mais cliques e reservas",
|
||||
"no_responses_yet": "Ainda sem respostas",
|
||||
"this_will_be_the_placeholder": "Isto será o marcador de posição",
|
||||
"error_booking_event": "Ocorreu um erro ao reservar o evento. Atualize a página e tente novamente",
|
||||
"timeslot_missing_title": "Nenhuma janela horária selecionada",
|
||||
"timeslot_missing_description": "Selecione uma janela horária para reservar o evento.",
|
||||
"timeslot_missing_cta": "Selecionar janela horária",
|
||||
"switch_monthly": "Mudar para a vista mensal",
|
||||
"switch_weekly": "Mudar para a vista semanal",
|
||||
"switch_multiday": "Mudar para a vista diária",
|
||||
"num_locations": "{{num}} opções de localização",
|
||||
"select_on_next_step": "Selecione no próximo passo",
|
||||
"this_meeting_has_not_started_yet": "Esta reunião ainda não começou",
|
||||
"this_app_requires_connected_account": "{{appName}} requer a associação de uma conta {{dependencyName}}",
|
||||
"connect_app": "Associar {{dependencyName}}",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Os membros poderão ver as aplicações ativas, mas não poderão editar quaisquer configurações das aplicações",
|
||||
"locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar quaisquer configurações dos webhooks",
|
||||
"locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar quaisquer configurações dos fluxos de trabalho",
|
||||
"locked_by_admin": "Bloqueado pelo administrador da equipa",
|
||||
"app_not_connected": "Não associou uma conta {{appName}}.",
|
||||
"connect_now": "Associar agora",
|
||||
"managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro",
|
||||
@@ -1781,5 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "Atualmente, não é possível ativar lugares e cobrar uma taxa de não-comparência",
|
||||
"complete_your_booking": "Concluir a sua reserva",
|
||||
"complete_your_booking_subject": "Concluir a sua reserva: {{title}} a {{date}}",
|
||||
"email_invite_team": "{{email}} foi convidado/a"
|
||||
"confirm_your_details": "Confirme os seus dados",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Está em vias de cobrar {{amount, currency}} ao participante. Tem a certeza que pretende continuar?",
|
||||
"charge_attendee": "Cobrar {{amount, currency}} ao participante",
|
||||
"payment_app_commission": "Exigir pagamento ({{paymentFeePercentage}}% + {{fee, currency}} de comissão por transação)",
|
||||
"email_invite_team": "{{email}} foi convidado/a",
|
||||
"email_invite_team_bulk": "Foram convidados {{userCount}} utilizadores",
|
||||
"error_collecting_card": "Erro ao recolher o cartão",
|
||||
"image_size_limit_exceed": "A imagem enviada não deve exceder o limite de tamanho de 5 mb",
|
||||
"inline_embed": "Incorporação em linha",
|
||||
"load_inline_content": "Carrega o seu tipo de evento diretamente e em linha, em conjunto com os conteúdos do seu outro site.",
|
||||
"floating_pop_up_button": "Botão pop-up flutuante",
|
||||
"floating_button_trigger_modal": "Coloca um botão flutuante no seu site que ativa uma modal com o seu tipo de evento.",
|
||||
"pop_up_element_click": "Pop-up via clique no elemento",
|
||||
"open_dialog_with_element_click": "Abra a sua janela do Cal quando alguém clicar num elemento.",
|
||||
"need_help_embedding": "Precisa de ajuda? Consulte os nossos guias para incorporar o Cal no Wix, Squarespace, ou WordPress. Consulte ainda as nossas perguntas frequentes ou explore opções avançadas de incorporação.",
|
||||
"book_my_cal": "Reservar o meu Cal",
|
||||
"invite_as": "Convidar como",
|
||||
"form_updated_successfully": "Formulário atualizado com sucesso.",
|
||||
"email_not_cal_member_cta": "Inscreva a sua equipa",
|
||||
"disable_attendees_confirmation_emails": "Desativar mensagens eletrónicas de confirmação predefinidas para participantes",
|
||||
"disable_attendees_confirmation_emails_description": "Está ativo pelo menos um fluxo de trabalho neste tipo de evento que envia um e-mail para os participantes quando o evento é reservado.",
|
||||
"disable_host_confirmation_emails": "Desativar mensagens eletrónicas de confirmação predefinidas para o anfitrião",
|
||||
"disable_host_confirmation_emails_description": "Está ativo pelo menos um fluxo de trabalho neste tipo de evento que envia um e-mail para o anfitrião quando o evento é reservado.",
|
||||
"add_an_override": "Adicionar uma sobreposição",
|
||||
"import_from_google_workspace": "Importar utilizadores do Google Workspace",
|
||||
"connect_google_workspace": "Associar o Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Deve ser um Administrador do Workspace para utilizar esta funcionalidade",
|
||||
"first_event_type_webhook_description": "Crie o seu primeiro webhook para este tipo de evento",
|
||||
"create_for": "Criar para"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Odbij rezervaciju",
|
||||
"manage_this_event": "Upravljaj ovim dogadjajem",
|
||||
"invite_team_member": "Pozovite člana tima",
|
||||
"invite_team_individual_segment": "Pozovite pojedinačno",
|
||||
"invite_team_bulk_segment": "Pozovite grupno",
|
||||
"invite_team_notifcation_badge": "Poz.",
|
||||
"your_event_has_been_scheduled": "Vaš dogadjaj je odložen",
|
||||
"your_event_has_been_scheduled_recurring": "Vaš ponavljajući događaj je zakazan",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Idite nazad na stranicu za prijavu",
|
||||
"error_during_login": "Došlo je do greške pri prijavi. Idite nazad na stranicu za prijavu i pokušajte ponovo.",
|
||||
"request_password_reset": "Zatražite imejl za ponovno postavljanje lozinke",
|
||||
"send_invite": "Pošaljite pozivnicu",
|
||||
"forgot_password": "Zaboravili ste lozinku?",
|
||||
"forgot": "Zaboravili ste?",
|
||||
"done": "Urađeno",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Podesite svoju dostupnost",
|
||||
"continue_without_calendar": "Nastavite bez kalendara",
|
||||
"connect_your_calendar": "Povežite vaš kalendar",
|
||||
"connect_your_video_app": "Povežite svoje video aplikacije",
|
||||
"connect_your_video_app_instructions": "Povežite svoje video aplikacije da biste ih koristili u vašim tipovima događaja.",
|
||||
"connect_your_calendar_instructions": "Povežite vaš kalendar da automatski radi proveru zauzetih vremena i vidi nove dogadjaje kad se zakažu.",
|
||||
"set_up_later": "Postavi kasnije",
|
||||
"current_time": "Trenutno vreme",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Napravite Webhook",
|
||||
"booking_cancelled": "Rezervacija Otkazana",
|
||||
"booking_rescheduled": "Rezervacija Odložena",
|
||||
"recording_ready": "Link za preuzimanje snimka je spreman",
|
||||
"booking_created": "Rezervacija Napravljena",
|
||||
"meeting_ended": "Sastanak se završio",
|
||||
"form_submitted": "Formular poslat",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Petak",
|
||||
"saturday": "Subota",
|
||||
"sunday": "Nedelja",
|
||||
"all_booked_today": "Sve je zakazano.",
|
||||
"slots_load_fail": "Problem pri učitavanju dostupnih termina.",
|
||||
"additional_guests": "+ Dodatni Gosti",
|
||||
"your_name": "Vaše ime",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Napravi novi tip događaja preko koga će ljudi moći da naprave rezervacije.",
|
||||
"length": "Dužina",
|
||||
"minimum_booking_notice": "Minimalni rok za najavu sastanka",
|
||||
"offset_toggle": "Pomeri vremena početka",
|
||||
"offset_toggle_description": "Pomeri vremena termina prikazana polaznicima za određeni broj minuta",
|
||||
"offset_start": "Pomeri za",
|
||||
"offset_start_description": "npr. ovo će prikazati vremena termina vašim polaznicima u {{ adjustedTime }} umesto {{ originalTime }}",
|
||||
"slot_interval": "Intervali vremenskih slotova",
|
||||
"slot_interval_default": "Koristi dužinu događaja (uobičajeno podešavanje)",
|
||||
"delete_event_type": "Izbriši tip događaja?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Dupliraj",
|
||||
"offer_seats": "Ponudi mesta",
|
||||
"offer_seats_description": "Ponudite mesta za zakazivanje. Ovo automatski onemogućava gostujuća i opciona zakazivanja.",
|
||||
"seats_available_one": "Dostupno mesto",
|
||||
"seats_available_other": "Slobodnih mesta",
|
||||
"number_of_seats": "Broj mesta po zakazivanju",
|
||||
"enter_number_of_seats": "Unesite broj slobodnih mesta",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "kada je događaj otkazan",
|
||||
"new_event_trigger": "kada je zakazan novi događaj",
|
||||
"email_host_action": "pošalji imejl domaćinu",
|
||||
"email_attendee_action": "pošalji imejl polaznicima",
|
||||
"sms_attendee_action": "pošalji SMS polazniku",
|
||||
"sms_number_action": "pošalji SMS na određeni broj",
|
||||
"workflows": "Radni tokovi",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Kreirajte radni tok",
|
||||
"do_this": "Uradi ovo",
|
||||
"turn_off": "Isključi",
|
||||
"turn_on": "Uključi",
|
||||
"settings_updated_successfully": "Podešavanja su uspešno ažurirana",
|
||||
"error_updating_settings": "Greška pri ažuriranju podešavanja",
|
||||
"personal_cal_url": "Moj lični {{appName}} URL",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Konfigurišite način na koji tipovi događaja vrše interakciju sa vašim kalendarima",
|
||||
"appearance_description": "Upravljajte podešavanjima za izgled vaše rezervacije",
|
||||
"conferencing_description": "Dodajte svoje omiljene aplikacije za video konferencije za vaše sastanke",
|
||||
"add_conferencing_app": "Dodaj aplikaciju za konferenciju",
|
||||
"password_description": "Upravljajte podešavanjima za lozinke vašeg naloga",
|
||||
"2fa_description": "Upravljajte podešavanjima za lozinke vašeg naloga",
|
||||
"we_just_need_basic_info": "Trebaju nam neke osnovne informacije da bismo podesili vaš profil.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Prikaži pozivnicu",
|
||||
"email_no_user_invite_heading": "Pozvani ste da se pridružite timu u {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} vas je pozvao/la da se pridružite njihovom timu u {{appName}}. {{appName}} je planer za koordinaciju događaja koji omogućava vama i vašem timu da zakazujete sastanke bez dopisivanja imejlovima.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} vas je pozvao/la da se pridružite njihovom timu `{{teamName}}` u {{appName}}. {{appName}} je planer za koordinaciju događaja koji omogućava vama i vašem timu da zakazujete sastanke bez dopisivanja imejlovima.",
|
||||
"email_no_user_invite_steps_intro": "Provešćemo vas kroz nekoliko kratkih koraka, i za tren oka ćete uživati u zakazivanju bez stresa sa svojim timom.",
|
||||
"email_no_user_step_one": "Izaberite korisničko ime",
|
||||
"email_no_user_step_two": "Povežite se sa nalogom kalendara",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Pogledajte koji tipovi događaja dobijaju najviše klikova i zakazivanja",
|
||||
"no_responses_yet": "Još uvek nema odgovora",
|
||||
"this_will_be_the_placeholder": "Ovo će biti čuvar mesta",
|
||||
"error_booking_event": "Došlo je do greške prilikom zakazivanja događaja, osvežite stranicu i pokušajte ponovo",
|
||||
"timeslot_missing_title": "Nisu izabrani termini",
|
||||
"timeslot_missing_description": "Izaberite termin da biste zakazali događaj.",
|
||||
"timeslot_missing_cta": "Izaberite termin",
|
||||
"switch_monthly": "Pređite na mesečni pregled",
|
||||
"switch_weekly": "Pređite na nedeljni pregled",
|
||||
"switch_multiday": "Pređite na dnevni pregled",
|
||||
"num_locations": "{{num}} opcija lokacije",
|
||||
"select_on_next_step": "Izaberite na sledećem koraku",
|
||||
"this_meeting_has_not_started_yet": "Ovaj sastanak još nije počeo",
|
||||
"this_app_requires_connected_account": "{{appName}} zahteva povezani {{dependencyName}} nalog",
|
||||
"connect_app": "Povežite {{dependencyName}}",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju podešavanja aplikacije",
|
||||
"locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju podešavanja webhook-ova",
|
||||
"locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju podešavanja radnih tokova",
|
||||
"locked_by_admin": "Zaključao administrator tima",
|
||||
"app_not_connected": "Niste povezali {{appName}} nalog.",
|
||||
"connect_now": "Povežite odmah",
|
||||
"managed_event_dialog_confirm_button_one": "Zameni i obavesti {{count}} člana",
|
||||
@@ -1781,10 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "Trenutno nije moguće aktiviranje mesta i naplata naknade za izostanak",
|
||||
"complete_your_booking": "Završite svoje zakazivanje",
|
||||
"complete_your_booking_subject": "Završite svoje zakazivanje: {{title}} dana {{date}}",
|
||||
"confirm_your_details": "Potvrdite svoje podatke",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Upravo ćete naplatiti polazniku {{amount, currency}}. Jeste li sigurni da želite da nastavite?",
|
||||
"charge_attendee": "Naplatite polazniku {{amount, currency}}",
|
||||
"payment_app_commission": "Zahtevajte plaćanje ({{paymentFeePercentage}}% + {{fee, currency}} provizija po transakciji)",
|
||||
"email_invite_team": "{{email}} je pozvan/a",
|
||||
"image_size_limit_exceed": "Otpremljena slika ne sme da prevazilazi ograničenje od 5mb"
|
||||
"email_invite_team_bulk": "Pozvano je {{userCount}} korisnika",
|
||||
"error_collecting_card": "Greška u sakupljanju kartice",
|
||||
"image_size_limit_exceed": "Otpremljena slika ne sme da prevazilazi ograničenje od 5mb",
|
||||
"inline_embed": "Ugradi neposredno",
|
||||
"load_inline_content": "Direktno učitava vaš tip događaja neposredno sa vašim ostalim sadržajem na veb sajtu.",
|
||||
"floating_pop_up_button": "Plutajuće iskačuće dugme",
|
||||
"floating_button_trigger_modal": "Postavlja plutajuće dugme na vaš sajt koje pokreće modal sa vašim tipom događaja.",
|
||||
"pop_up_element_click": "Iskačući prozor preko klika na element",
|
||||
"open_dialog_with_element_click": "Otvara Cal dijalog prozor kada neko klikne na element.",
|
||||
"need_help_embedding": "Potrebna vam je pomoć? Pogledajte naše vodiče za ugrađivanje Cal-a na Wix, Squarespace ili WordPress, pogledajte naša često postavljana pitanja, ili istražite napredne opcije za ugrađivanje.",
|
||||
"book_my_cal": "Zakaži moj Cal",
|
||||
"invite_as": "Pozovi kao",
|
||||
"form_updated_successfully": "Formular je uspešno ažuriran.",
|
||||
"email_not_cal_member_cta": "Pridružite se našem timu",
|
||||
"disable_attendees_confirmation_emails": "Onemogući podrazumevane imejlove za potvrdu za polaznike",
|
||||
"disable_attendees_confirmation_emails_description": "Najmanje jedan radni tok je aktivan na ovom tipu događaja koji šalje imejl polaznicima kada je događaj zakazan.",
|
||||
"disable_host_confirmation_emails": "Onemogući podrazumevane imejlove za potvrdu za domaćina",
|
||||
"disable_host_confirmation_emails_description": "Najmanje jedan radni tok je aktivan na ovom tipu događaja koji šalje imejl domaćinima kada je događaj zakazan.",
|
||||
"add_an_override": "Dodaj zamenu",
|
||||
"import_from_google_workspace": "Uvezi korisnike sa Google Workspace",
|
||||
"connect_google_workspace": "Poveži Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Morate da budete Workspace administrator da biste koristili ovu opciju",
|
||||
"first_event_type_webhook_description": "Napravite svoj prvi webhook za ovaj tip događaja",
|
||||
"create_for": "Napravi za"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Avböj bokningen",
|
||||
"manage_this_event": "Hantera denna bokning",
|
||||
"invite_team_member": "Bjud in teammedlem",
|
||||
"invite_team_individual_segment": "Bjud in individ",
|
||||
"invite_team_bulk_segment": "Importera i bulk",
|
||||
"invite_team_notifcation_badge": "Inbj.",
|
||||
"your_event_has_been_scheduled": "Din bokning har schemalagts",
|
||||
"your_event_has_been_scheduled_recurring": "Din återkommande händelseförfrågan har schemalagts",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Gå tillbaka till inloggningssidan",
|
||||
"error_during_login": "Ett fel inträffade när du loggade in. Gå tillbaka till inloggningsskärmen och försök igen.",
|
||||
"request_password_reset": "Skicka e-postmeddelande om återställning",
|
||||
"send_invite": "Skicka inbjudan",
|
||||
"forgot_password": "Glömt lösenordet?",
|
||||
"forgot": "Glömt?",
|
||||
"done": "Klar",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Ställ in din tillgänglighet",
|
||||
"continue_without_calendar": "Fortsätt utan kalender",
|
||||
"connect_your_calendar": "Anslut din kalender",
|
||||
"connect_your_video_app": "Anslut dina videoappar",
|
||||
"connect_your_video_app_instructions": "Anslut dina videoappar för att använda dem på dina evenemangstyper.",
|
||||
"connect_your_calendar_instructions": "Anslut din kalender för att automatiskt upptäcka uppbokade tider samt fylla på med nya bokningar.",
|
||||
"set_up_later": "Konfigurera senare",
|
||||
"current_time": "Aktuell tid",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Skapa webhook",
|
||||
"booking_cancelled": "Bokning inställd",
|
||||
"booking_rescheduled": "Bokning ombokad",
|
||||
"recording_ready": "Länk för nedladdning av inspelning klar",
|
||||
"booking_created": "Bokning skapad",
|
||||
"meeting_ended": "Mötet avslutat",
|
||||
"form_submitted": "Formulär skickat",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Fredag",
|
||||
"saturday": "Lördag",
|
||||
"sunday": "Söndag",
|
||||
"all_booked_today": "Alla bokade.",
|
||||
"slots_load_fail": "Det gick inte att ladda tillgängliga tidsluckor.",
|
||||
"additional_guests": "Lägg till gäster",
|
||||
"your_name": "Ditt namn",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Skapa en ny händelsetyp som personer kan boka tider med.",
|
||||
"length": "Längd",
|
||||
"minimum_booking_notice": "Senaste tid för att boka",
|
||||
"offset_toggle": "Förskjutna starttider",
|
||||
"offset_toggle_description": "Förskjutning av tidsluckor som visas för bokare med ett angivet antal minuter",
|
||||
"offset_start": "Förskjutning av",
|
||||
"offset_start_description": "detta visar t.ex. tidsluckor för dina bokare vid {{ adjustedTime }} istället för {{ originalTime }}",
|
||||
"slot_interval": "Tid mellan bokningar",
|
||||
"slot_interval_default": "Använd händelsens längd (standard)",
|
||||
"delete_event_type": "Radera händelsetyp?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Duplicera",
|
||||
"offer_seats": "Erbjud platser",
|
||||
"offer_seats_description": "Erbjud platser till bokningar. Detta inaktiverar gäster och bokningar.",
|
||||
"seats_available_one": "Tillgänglig plats",
|
||||
"seats_available_other": "Tillgängliga platser",
|
||||
"number_of_seats": "Antal platser per bokning",
|
||||
"enter_number_of_seats": "Ange antal platser",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "när händelsen ställs in",
|
||||
"new_event_trigger": "när ny händelse bokas",
|
||||
"email_host_action": "skicka e-post till värd",
|
||||
"email_attendee_action": "skicka e-post till deltagare",
|
||||
"sms_attendee_action": "skicka SMS till deltagare",
|
||||
"sms_number_action": "skicka SMS till ett specifikt nummer",
|
||||
"workflows": "Arbetsflöden",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Skapa ett arbetsflöde",
|
||||
"do_this": "Gör detta",
|
||||
"turn_off": "Stäng av",
|
||||
"turn_on": "Slå på",
|
||||
"settings_updated_successfully": "Inställningar har uppdaterats",
|
||||
"error_updating_settings": "Fel vid uppdatering av inställningar",
|
||||
"personal_cal_url": "Min personliga {{appName}}-URL",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Ställ in hur dina händelsetyper interagerar med dina kalendrar",
|
||||
"appearance_description": "Hantera inställningar för ditt bokningsutseende",
|
||||
"conferencing_description": "Lägg till dina favoritappar för videokonferenser för möten",
|
||||
"add_conferencing_app": "Lägg till konferensapp",
|
||||
"password_description": "Hantera inställningar för dina kontolösenord",
|
||||
"2fa_description": "Hantera inställningar för dina kontolösenord",
|
||||
"we_just_need_basic_info": "Vi behöver bara lite grundläggande information för att ställa in din profil.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Visa inbjudan",
|
||||
"email_no_user_invite_heading": "Du är inbjuden att gå med i ett team på {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} har bjudit in dig att gå med i sitt team på {{appName}}. {{appName}} är en händelsejonglerande schemaläggare som du och ditt team kan använda för att planera möten utan att skicka e-post fram och tillbaka.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} har bjudit in dig att gå med i sitt team {{teamName}} den {{appName}}. {{appName}} är en händelsejonglerande schemaläggare som du och ditt team kan använda för att planera möten utan att skicka e-post fram och tillbaka.",
|
||||
"email_no_user_invite_steps_intro": "Vi går igenom några enkla steg så att du kan schemalägga utan stress med ditt team på nolltid.",
|
||||
"email_no_user_step_one": "Välj ditt användarnamn",
|
||||
"email_no_user_step_two": "Anslut ditt kalenderkonto",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Se vilka av dina händelsetyper som får flest klick och bokningar",
|
||||
"no_responses_yet": "Inga svar ännu",
|
||||
"this_will_be_the_placeholder": "Det här blir platshållaren",
|
||||
"error_booking_event": "Ett fel uppstod när du bokade evenemanget, uppdatera sidan och försök igen",
|
||||
"timeslot_missing_title": "Ingen tidslucka vald",
|
||||
"timeslot_missing_description": "Välj en tidslucka för att boka evenemanget.",
|
||||
"timeslot_missing_cta": "Välj tidslucka",
|
||||
"switch_monthly": "Växla till månatlig vy",
|
||||
"switch_weekly": "Växla till veckovis vy",
|
||||
"switch_multiday": "Växla till dagvy",
|
||||
"num_locations": "{{num}} platsalternativ",
|
||||
"select_on_next_step": "Välj i nästa steg",
|
||||
"this_meeting_has_not_started_yet": "Mötet har inte startat än",
|
||||
"this_app_requires_connected_account": "{{appName}} kräver ett anslutet {{dependencyName}}-konto",
|
||||
"connect_app": "Anslut {{dependencyName}}",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Medlemmarna kan se de aktiva apparna men kan inte ändra appinställningarna",
|
||||
"locked_webhooks_description": "Medlemmarna kan se aktiva webhooks men kan inte redigera inställningar för webhooks",
|
||||
"locked_workflows_description": "Medlemmarna kan se aktiva arbetsflöden men kan inte ändra några arbetsflödesinställningar",
|
||||
"locked_by_admin": "Låst av teamadministratör",
|
||||
"app_not_connected": "Du har inte anslutit ett {{appName}}-konto.",
|
||||
"connect_now": "Anslut nu",
|
||||
"managed_event_dialog_confirm_button_one": "Ersätt och meddela {{count}} medlem",
|
||||
@@ -1781,5 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "För närvarande går det inte att aktivera platser och ta ut en avgift för utebliven närvaro",
|
||||
"complete_your_booking": "Slutför din bokning",
|
||||
"complete_your_booking_subject": "Slutför din bokning: {{title}} {{date}}",
|
||||
"email_invite_team": "{{email}} har bjudits in"
|
||||
"confirm_your_details": "Bekräfta dina uppgifter",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Du är på väg att debitera deltagaren {{amount, currency}}. Är du säker på att du vill fortsätta?",
|
||||
"charge_attendee": "Debitera deltagare {{amount, currency}}",
|
||||
"payment_app_commission": "Kräv betalning ({{paymentFeePercentage}}% + {{fee, currency}} provision per transaktion)",
|
||||
"email_invite_team": "{{email}} har bjudits in",
|
||||
"email_invite_team_bulk": "{{userCount}} användare har blivit inbjudna",
|
||||
"error_collecting_card": "Fel vid insamling av kort",
|
||||
"image_size_limit_exceed": "Uppladdad bild bör inte överstiga 5 MB storleksgräns",
|
||||
"inline_embed": "Inline Embed",
|
||||
"load_inline_content": "Laddar din evenemangstyp direkt i linje med ditt övriga webbplatsinnehåll.",
|
||||
"floating_pop_up_button": "Flytande popup-knapp",
|
||||
"floating_button_trigger_modal": "Sätter en flytande knapp på din webbplats som utlöser en modal med din evenemangstyp.",
|
||||
"pop_up_element_click": "Pop up via elementklick",
|
||||
"open_dialog_with_element_click": "Öppna din Cal-dialogruta när någon klickar på ett element.",
|
||||
"need_help_embedding": "Behöver du hjälp? Se våra guider för inbäddning av Cal på Wix, Squarespace eller WordPress, kolla våra vanliga frågor eller utforska avancerade inbäddningsalternativ.",
|
||||
"book_my_cal": "Boka min Cal",
|
||||
"invite_as": "Bjud in som",
|
||||
"form_updated_successfully": "Formuläret har uppdaterats.",
|
||||
"email_not_cal_member_cta": "Gå med i teamet",
|
||||
"disable_attendees_confirmation_emails": "Inaktivera e-postbekräftelse som standard för deltagare",
|
||||
"disable_attendees_confirmation_emails_description": "Minst ett arbetsflöde är aktivt på denna evenemangstyp som skickar ett e-postmeddelande till deltagarna när evenemanget är bokat.",
|
||||
"disable_host_confirmation_emails": "Inaktivera e-postbekräftelse som standard för värdar",
|
||||
"disable_host_confirmation_emails_description": "Minst ett arbetsflöde är aktivt för denna evenemangstyp som skickar ett e-postmeddelande till värden när evenemanget är bokat.",
|
||||
"add_an_override": "Lägg till åsidosättning",
|
||||
"import_from_google_workspace": "Importera användare från Google Workspace",
|
||||
"connect_google_workspace": "Anslut Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Du måste vara Workspace-administratör för att kunna använda denna funktion",
|
||||
"first_event_type_webhook_description": "Skapa din första webhook för denna evenemangstyp",
|
||||
"create_for": "Skapa för"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Rezervasyonu reddet",
|
||||
"manage_this_event": "Bu etkinliği yönet",
|
||||
"invite_team_member": "Ekip üyesi davet et",
|
||||
"invite_team_individual_segment": "Kişiyi davet et",
|
||||
"invite_team_bulk_segment": "Toplu içe aktarma",
|
||||
"invite_team_notifcation_badge": "Dvt.",
|
||||
"your_event_has_been_scheduled": "Etkinliğiniz planlandı",
|
||||
"your_event_has_been_scheduled_recurring": "Yinelenen etkinliğiniz planlandı",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Giriş sayfasına geri dön",
|
||||
"error_during_login": "Giriş yapılırken bir hata oluştu. Giriş ekranına geri dönün ve tekrar deneyin.",
|
||||
"request_password_reset": "Sıfırlama e-postası gönder",
|
||||
"send_invite": "Davet gönder",
|
||||
"forgot_password": "Şifremi Unuttum",
|
||||
"forgot": "Unuttunuz mu?",
|
||||
"done": "Tamam",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Müsaitlik durumunuzu ayarlayın",
|
||||
"continue_without_calendar": "Takvim olmadan devam et",
|
||||
"connect_your_calendar": "Takviminizi bağlayın",
|
||||
"connect_your_video_app": "Video uygulamalarınızı bağlayın",
|
||||
"connect_your_video_app_instructions": "Etkinlik türlerinizde kullanmak için video uygulamalarınızı bağlayın.",
|
||||
"connect_your_calendar_instructions": "Yeni etkinlikler planlanırken dolu zamanları otomatik olarak kontrol etmek için takviminizi bağlayın.",
|
||||
"set_up_later": "Daha sonra ayarla",
|
||||
"current_time": "Geçerli saat",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Web kancası oluştur",
|
||||
"booking_cancelled": "Rezervasyon İptal Edildi",
|
||||
"booking_rescheduled": "Rezervasyon Yeniden Planlandı",
|
||||
"recording_ready": "Kayıt İndirme Bağlantısı Hazır",
|
||||
"booking_created": "Rezervasyon Oluşturuldu",
|
||||
"meeting_ended": "Toplantı Sona Erdi",
|
||||
"form_submitted": "Form Gönderildi",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Cuma",
|
||||
"saturday": "Cumartesi",
|
||||
"sunday": "Pazar",
|
||||
"all_booked_today": "Tümü rezerve edildi.",
|
||||
"slots_load_fail": "Kullanılabilir zaman aralıkları yüklenemedi.",
|
||||
"additional_guests": "Misafir ekle",
|
||||
"your_name": "Adınız",
|
||||
@@ -752,6 +759,9 @@
|
||||
"new_event_type_to_book_description": "İnsanların rezervasyon yapabileceği yeni bir etkinlik türü oluşturun.",
|
||||
"length": "Uzunluk",
|
||||
"minimum_booking_notice": "Minimum Bildirim",
|
||||
"offset_toggle": "Ofset başlangıç saatleri",
|
||||
"offset_toggle_description": "Belirli bir dakika sayısına göre rezervasyon yapanlara gösterilen ofset zaman aralıkları",
|
||||
"offset_start_description": "örneğin; bu işlem rezervasyon yapan kişinin zaman aralıklarını {{ originalTime }} yerine {{ adjustedTime }} olarak görmesine neden olur",
|
||||
"slot_interval": "Zaman dilimi aralıkları",
|
||||
"slot_interval_default": "Etkinlik uzunluğunu kullan (varsayılan)",
|
||||
"delete_event_type": "Etkinlik türü silinsin mi?",
|
||||
@@ -904,6 +914,7 @@
|
||||
"duplicate": "Çoğalt",
|
||||
"offer_seats": "Yer teklif et",
|
||||
"offer_seats_description": "Rezervasyonlar için yer teklif edin. Bu işlem, misafirleri ve rezervasyon onaylarını devre dışı bırakır.",
|
||||
"seats_available_one": "Koltuk mevcut",
|
||||
"seats_available_other": "Yer mevcut",
|
||||
"number_of_seats": "Rezervasyon başına yer sayısı",
|
||||
"enter_number_of_seats": "Yer sayısını girin",
|
||||
@@ -1038,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "etkinlik iptal edildiğinde",
|
||||
"new_event_trigger": "yeni bir etkinlik için rezervasyon yapıldığında",
|
||||
"email_host_action": "organizatöre e-posta gönder",
|
||||
"email_attendee_action": "katılımcılara e-posta gönder",
|
||||
"sms_attendee_action": "katılımcıya SMS gönder",
|
||||
"sms_number_action": "belirli bir numaraya SMS gönder",
|
||||
"workflows": "İş akışları",
|
||||
@@ -1190,6 +1202,7 @@
|
||||
"create_workflow": "Bir iş akışı oluşturun",
|
||||
"do_this": "Bunu yap",
|
||||
"turn_off": "Kapat",
|
||||
"turn_on": "Aç",
|
||||
"settings_updated_successfully": "Ayarlar başarıyla güncellendi",
|
||||
"error_updating_settings": "Ayarlar güncellenirken bir hata oluştu",
|
||||
"personal_cal_url": "Kişisel {{appName}} URL'm",
|
||||
@@ -1246,6 +1259,7 @@
|
||||
"calendars_description": "Etkinlik türlerinizin takvimlerinizle nasıl etkileşimde bulunacağını yapılandırın",
|
||||
"appearance_description": "Rezervasyon görünümünüz için ayarları yönetin",
|
||||
"conferencing_description": "Toplantılarınız için favori video konferans uygulamalarınızı yönetin",
|
||||
"add_conferencing_app": "Konferans Uygulaması Ekle",
|
||||
"password_description": "Hesap şifreleriniz için ayarları yönetin",
|
||||
"2fa_description": "Hesap şifreleriniz için ayarları yönetin",
|
||||
"we_just_need_basic_info": "Profil kurulumunuzu almak için sadece bazı temel bilgilere ihtiyacımız var.",
|
||||
@@ -1620,6 +1634,7 @@
|
||||
"email_user_cta": "Daveti görüntüle",
|
||||
"email_no_user_invite_heading": "{{appName}} uygulamasındaki bir ekibe katılmaya davet edildiniz",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}}, sizi {{appName}} ekibine katılmaya davet etti. {{appName}}, size ve ekibinize e-posta iletişimine ihtiyaç duymadan toplantı planlama yapma olanağı sağlayan bir etkinlik planlayıcıdır.",
|
||||
"email_user_invite_subheading": "{{invitedBy}}, sizi {{appName}} uygulamasındaki `{{teamName}}` ekibine katılmaya davet etti. {{appName}}, size ve ekibinize e-posta iletişimine ihtiyaç duymadan toplantı planlama yapma olanağı sağlayan bir etkinlik planlayıcıdır.",
|
||||
"email_no_user_invite_steps_intro": "Ekibinizle birlikte kısa sürede ve sorunsuz planlama yapmanın keyfini çıkarmanız için birkaç adımda size rehberlik edeceğiz.",
|
||||
"email_no_user_step_one": "Kullanıcı adınızı seçin",
|
||||
"email_no_user_step_two": "Takvim hesabınızı bağlayın",
|
||||
@@ -1695,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "Hangi etkinlik türünüzün en çok tıklandığını ve rezervasyon aldığını görün",
|
||||
"no_responses_yet": "Henüz yanıt yok",
|
||||
"this_will_be_the_placeholder": "Bu, yer tutucu olacak",
|
||||
"error_booking_event": "Etkinlik rezervasyonu yapılırken bir hata oluştu, lütfen sayfayı yenileyerek tekrar deneyin",
|
||||
"timeslot_missing_title": "Zaman aralığı seçilmedi",
|
||||
"timeslot_missing_description": "Etkinlik rezervasyonu için lütfen bir zaman aralığı seçin.",
|
||||
"timeslot_missing_cta": "Zaman aralığını seçin",
|
||||
"switch_monthly": "Aylık görünüme geç",
|
||||
"switch_weekly": "Haftalık görünüme geç",
|
||||
"switch_multiday": "Günlük görünüme geç",
|
||||
"num_locations": "{{num}} konum seçenekleri",
|
||||
"select_on_next_step": "Bir sonraki adımda seç",
|
||||
"this_meeting_has_not_started_yet": "Bu toplantı henüz başlamadı",
|
||||
"this_app_requires_connected_account": "{{appName}} bağlı bir {{dependencyName}} hesabı gerektirir",
|
||||
"connect_app": "{{dependencyName}} hesabını bağla",
|
||||
@@ -1724,6 +1748,7 @@
|
||||
"locked_apps_description": "Üyeler aktif uygulamaları görebilir, ancak hiçbir uygulama ayarını düzenleyemez",
|
||||
"locked_webhooks_description": "Üyeler aktif web kancalarını görebilir, ancak hiçbir web kancası ayarını düzenleyemez",
|
||||
"locked_workflows_description": "Üyeler aktif iş akışlarını görebilir, ancak hiçbir iş akışı ayarını düzenleyemez",
|
||||
"locked_by_admin": "Ekip yöneticisi tarafından kilitlendi",
|
||||
"app_not_connected": "Bir {{appName}} hesabı bağlamadınız.",
|
||||
"connect_now": "Hemen bağlan",
|
||||
"managed_event_dialog_confirm_button_one": "{{count}} üyeyi değiştirin ve bilgilendirin",
|
||||
@@ -1781,5 +1806,22 @@
|
||||
"seats_and_no_show_fee_error": "Şu anda koltuklar etkinleştirilemiyor ve katılmama ücreti tahsil edilemiyor",
|
||||
"complete_your_booking": "Rezervasyonunuzu tamamlayın",
|
||||
"complete_your_booking_subject": "Rezervasyonunuzu tamamlayın: {{title}}, {{date}}",
|
||||
"email_invite_team": "{{email}} davet edildi"
|
||||
"confirm_your_details": "Bilgilerinizi onaylayın",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Katılımcıdan {{amount, currency}} tahsil etmek üzeresiniz. Devam etmek istediğinizden emin misiniz?",
|
||||
"charge_attendee": "Katılımcıdan {{amount, currency}} ücret tahsil edin",
|
||||
"payment_app_commission": "Ödeme talep edin (işlem başına %{{paymentFeePercentage}} + {{fee, currency}} komisyon)",
|
||||
"email_invite_team": "{{email}} davet edildi",
|
||||
"email_invite_team_bulk": "{{userCount}} kullanıcı davet edildi",
|
||||
"image_size_limit_exceed": "Yüklenen resim boyutu 5 MB boyut sınırını aşmamalıdır",
|
||||
"need_help_embedding": "Yardıma mı ihtiyacınız var? Cal'ı Wix, Squarespace veya WordPress'e yerleştirmek için kılavuzlarımıza göz atın. Ayrıca Sıkça Sorulan Sorular bölümümüze göz atın veya gelişmiş yerleştirme seçeneklerini keşfedin.",
|
||||
"book_my_cal": "Cal'ımı rezerve et",
|
||||
"form_updated_successfully": "Form başarıyla güncellendi.",
|
||||
"disable_attendees_confirmation_emails": "Katılımcılar için varsayılan onay e-postalarını devre dışı bırakın",
|
||||
"disable_attendees_confirmation_emails_description": "Bu etkinlik türünde etkinlik rezerve edildiğinde katılımcılara bir e-posta gönderen en az bir iş akışı etkin.",
|
||||
"disable_host_confirmation_emails": "Organizatörler için varsayılan onay e-postalarını devre dışı bırakın",
|
||||
"disable_host_confirmation_emails_description": "Bu etkinlik türündeki bir etkinlik rezerve edildiğinde organizatörlere e-posta gönderen en az bir iş akışı etkin.",
|
||||
"connect_google_workspace": "Google Workspace'i bağlayın",
|
||||
"google_workspace_admin_tooltip": "Bu özelliği kullanmak için Çalışma Alanı Yöneticisi olmalısınız",
|
||||
"first_event_type_webhook_description": "Bu etkinlik türü için ilk web kancanızı oluşturun"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Відмовити в бронюванні",
|
||||
"manage_this_event": "Керувати цим заходом",
|
||||
"invite_team_member": "Запросити учасника команди",
|
||||
"invite_team_individual_segment": "Запросити фізичну особу",
|
||||
"invite_team_bulk_segment": "Пакетний імпорт",
|
||||
"invite_team_notifcation_badge": "Запр.",
|
||||
"your_event_has_been_scheduled": "Захід заплановано",
|
||||
"your_event_has_been_scheduled_recurring": "Періодичний захід заплановано",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Повернутися на сторінку входу",
|
||||
"error_during_login": "Сталася помилка під час входу в систему. Поверніться на екран входу та повторіть спробу.",
|
||||
"request_password_reset": "Надіслати лист для скидання",
|
||||
"send_invite": "Надіслати запрошення",
|
||||
"forgot_password": "Не пам’ятаєте пароль?",
|
||||
"forgot": "Забули?",
|
||||
"done": "Готово",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Укажіть, коли ви доступні",
|
||||
"continue_without_calendar": "Продовжити без календаря",
|
||||
"connect_your_calendar": "Підключити календар",
|
||||
"connect_your_video_app": "Підключайте улюблені відеозастосунки",
|
||||
"connect_your_video_app_instructions": "Підключайте свої відеозастосунки, щоб використовувати їх у своїх типах заходів.",
|
||||
"connect_your_calendar_instructions": "Підключіть свій календар, щоб автоматично перевіряти, коли ви зайняті, і стежити за новими запланованими заходами.",
|
||||
"set_up_later": "Налаштувати пізніше",
|
||||
"current_time": "Поточний час",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Створити вебгук",
|
||||
"booking_cancelled": "Бронювання скасовано",
|
||||
"booking_rescheduled": "Бронювання перенесено",
|
||||
"recording_ready": "Посилання для завантаження запису готове",
|
||||
"booking_created": "Бронювання створено",
|
||||
"meeting_ended": "Нарада завершилася",
|
||||
"form_submitted": "Форму надіслано",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "П’ятниця",
|
||||
"saturday": "Субота",
|
||||
"sunday": "Неділя",
|
||||
"all_booked_today": "Усе заброньовано.",
|
||||
"slots_load_fail": "Не вдалося завантажити доступні часові вікна.",
|
||||
"additional_guests": "Додати гостей",
|
||||
"your_name": "Ваше ім’я",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "Створіть новий тип заходу, на який люди зможуть бронювати час.",
|
||||
"length": "Тривалість",
|
||||
"minimum_booking_notice": "Мінімальний час до наступного заходу",
|
||||
"offset_toggle": "Змістити час початку",
|
||||
"offset_toggle_description": "Проміжки часу для зміщення (у хвилинах), показані користувачам, які здійснюють бронювання",
|
||||
"offset_start": "Зміщення на",
|
||||
"offset_start_description": "наприклад, користувачі, які здійснюють бронювання, бачитимуть часові проміжки о {{ adjustedTime }} замість {{ originalTime }}",
|
||||
"slot_interval": "Інтервали між бронюваннями",
|
||||
"slot_interval_default": "Використовувати тривалість заходу (за замовчуванням)",
|
||||
"delete_event_type": "Видалити тип заходу?",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Дублювати",
|
||||
"offer_seats": "Запропонувати місця",
|
||||
"offer_seats_description": "Пропонуйте місця під час бронювання (при цьому вимикається гостьовий режим і бронювання з підтвердженням).",
|
||||
"seats_available_one": "Є доступне місце",
|
||||
"seats_available_other": "Доступні місця",
|
||||
"number_of_seats": "Кількість місць на одне бронювання",
|
||||
"enter_number_of_seats": "Введіть кількість місць",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "у момент скасування заходу",
|
||||
"new_event_trigger": "у момент бронювання нового заходу",
|
||||
"email_host_action": "надсилати електронний лист ведучому",
|
||||
"email_attendee_action": "надсилати ел. листи учасникам",
|
||||
"sms_attendee_action": "надсилати SMS учаснику",
|
||||
"sms_number_action": "надсилати SMS на певний номер",
|
||||
"workflows": "Робочі процеси",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Створити робочий процес",
|
||||
"do_this": "Зробити це",
|
||||
"turn_off": "Вимкнути",
|
||||
"turn_on": "Увімкнути",
|
||||
"settings_updated_successfully": "Параметри оновлено",
|
||||
"error_updating_settings": "Помилка оновлення параметрів",
|
||||
"personal_cal_url": "Моя персональна URL-адреса {{appName}}",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Налаштуйте, як типи заходів мають взаємодіяти з вашими календарями",
|
||||
"appearance_description": "Налаштуйте варіанти оформлення свого бронювання",
|
||||
"conferencing_description": "Керуйте додатками для відеоконференцій і нарад",
|
||||
"add_conferencing_app": "Додати застосунок для проведення конференцій",
|
||||
"password_description": "Налаштуйте параметри паролів облікових записів",
|
||||
"2fa_description": "Налаштуйте параметри паролів облікових записів",
|
||||
"we_just_need_basic_info": "Щоб налаштувати ваш профіль, нам потрібні базові дані.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Переглянути запрошення",
|
||||
"email_no_user_invite_heading": "Вас запрошено приєднатися до команди в {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} запрошує вас приєднатися до команди в {{appName}}. {{appName}} — планувальник подій, який дає змогу вам і вашій команді планувати зустрічі без тривалої переписки електронною поштою.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} запрошує вас приєднатися до команди «{{teamName}}» в {{appName}}. {{appName}} — планувальник подій, який дає змогу вам і вашій команді планувати зустрічі без тривалої переписки електронною поштою.",
|
||||
"email_no_user_invite_steps_intro": "Ми ознайомимо вас із додатком, і вже скоро ви насолоджуватиметесь плануванням зі своєю командою без жодного клопоту.",
|
||||
"email_no_user_step_one": "Виберіть ім’я користувача",
|
||||
"email_no_user_step_two": "Підключіть обліковий запис календаря",
|
||||
@@ -1695,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "Дізнайтеся, які типи заходів мають найбільшу кількість переходів і бронювань",
|
||||
"no_responses_yet": "Поки немає відповідей",
|
||||
"this_will_be_the_placeholder": "Це заповнювач",
|
||||
"error_booking_event": "Сталася помилка під час бронювання заходу, оновіть сторінку й повторіть спробу",
|
||||
"timeslot_missing_title": "Не вибрано проміжок часу",
|
||||
"timeslot_missing_description": "Виберіть проміжок часу для бронювання заходу.",
|
||||
"timeslot_missing_cta": "Вибрати проміжок часу",
|
||||
"switch_monthly": "Переключитися на перегляд місяця",
|
||||
"switch_weekly": "Переключитися на перегляд тижня",
|
||||
"switch_multiday": "Переключитися на перегляд дня",
|
||||
"num_locations": "Варіантів розташування: {{num}}",
|
||||
"select_on_next_step": "Вибрати на наступному кроці",
|
||||
"this_meeting_has_not_started_yet": "Ця нарада ще не почалася",
|
||||
"this_app_requires_connected_account": "Щоб використовувати {{appName}}, потрібно підключити обліковий запис {{dependencyName}}",
|
||||
"connect_app": "Підключити {{dependencyName}}",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Учасники бачитимуть активні додатки, але не зможуть змінювати їх налаштування",
|
||||
"locked_webhooks_description": "Учасники бачитимуть активні вебгуки, але не зможуть змінювати їх налаштування",
|
||||
"locked_workflows_description": "Учасники бачитимуть активні робочі процеси, але не зможуть змінювати їх налаштування",
|
||||
"locked_by_admin": "Заблоковано адміністратором команди",
|
||||
"app_not_connected": "Ви не під’єднали обліковий запис {{appName}}.",
|
||||
"connect_now": "Під’єднати",
|
||||
"managed_event_dialog_confirm_button_one": "Замінити й повідомити {{count}} учасника",
|
||||
@@ -1781,5 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "Зараз неможливо активувати місця і стягнути плату за відсутність",
|
||||
"complete_your_booking": "Завершіть бронювання",
|
||||
"complete_your_booking_subject": "Завершіть бронювання: {{title}} від {{date}}",
|
||||
"email_invite_team": "{{email}} запрошено"
|
||||
"confirm_your_details": "Підтвердьте ваші дані",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Ви збираєтеся списати кошти з рахунку учасника: {{amount, currency}}. Бажаєте продовжити?",
|
||||
"charge_attendee": "Списати з рахунку учасника {{amount, currency}}",
|
||||
"payment_app_commission": "Потрібна оплата ({{paymentFeePercentage}}% + комісія за транзакцію в розмірі {{fee, currency}})",
|
||||
"email_invite_team": "{{email}} запрошено",
|
||||
"email_invite_team_bulk": "Запрошено стільки користувачів: {{userCount}}",
|
||||
"error_collecting_card": "Не вдалося забрати картку",
|
||||
"image_size_limit_exceed": "Розмір переданого зображення не повинен перевищувати 5 МБ",
|
||||
"inline_embed": "Вбудовування",
|
||||
"load_inline_content": "Завантажує ваш тип заходу, вбудовуючи його у ваш інший контент на вебсайті.",
|
||||
"floating_pop_up_button": "Спливаюча кнопка",
|
||||
"floating_button_trigger_modal": "Додає спливаючу кнопку на ваш сайт, яка викликає модальне вікно з вашим типом заходу.",
|
||||
"pop_up_element_click": "Спливаюче вікно після натискання на елемент",
|
||||
"open_dialog_with_element_click": "Відкриває ваш діалог із Cal, коли хтось натискає елемент.",
|
||||
"need_help_embedding": "Потрібна допомога? Перегляньте наші посібники з вбудовування Cal на Wix, Squarespace або WordPress, ознайомтеся з поширеними запитаннями або розширеними параметрами вбудовування.",
|
||||
"book_my_cal": "Забронювати мій Cal",
|
||||
"invite_as": "Запросити як",
|
||||
"form_updated_successfully": "Форму оновлено.",
|
||||
"email_not_cal_member_cta": "Приєднатися до команди",
|
||||
"disable_attendees_confirmation_emails": "Вимкнути листи підтвердження за замовчуванням для учасників",
|
||||
"disable_attendees_confirmation_emails_description": "Принаймні один робочий процес активний у цьому типі заходів, який надсилає електронний лист учасникам у разі бронювання заходу.",
|
||||
"disable_host_confirmation_emails": "Вимкнути листи підтвердження за замовчуванням для ведучого",
|
||||
"disable_host_confirmation_emails_description": "Принаймні один робочий процес активний у цьому типі заходів, який надсилає електронний лист ведучому в разі бронювання заходу.",
|
||||
"add_an_override": "Додати перевизначення",
|
||||
"import_from_google_workspace": "Імпортувати користувачів із Google Workspace",
|
||||
"connect_google_workspace": "Підключити Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Ви повинні бути адміністратором Workspace, щоб використовувати цю функцію",
|
||||
"first_event_type_webhook_description": "Створіть свій перший вебгук для цього типу заходів",
|
||||
"create_for": "Створити для"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Nhà cung cấp danh tính",
|
||||
"identity_provider": "Đơn vị cung cấp danh tính",
|
||||
"trial_days_left": "Bạn có $t(day, {\"count\": {{days}} }) còn lại trên bản thử nghiệm PRO",
|
||||
"day_one": "{{count}} ngày",
|
||||
"day_other": "{{count}} ngày",
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Từ chối lịch hẹn",
|
||||
"manage_this_event": "Quản lý sự kiện này",
|
||||
"invite_team_member": "Mời thành viên nhóm",
|
||||
"invite_team_individual_segment": "Mời cá nhân",
|
||||
"invite_team_bulk_segment": "Nhập hàng loạt",
|
||||
"invite_team_notifcation_badge": "Mời",
|
||||
"your_event_has_been_scheduled": "Sự kiện của bạn đã được lên lịch",
|
||||
"your_event_has_been_scheduled_recurring": "Sự kiện định kỳ của bạn đã được lên lịch",
|
||||
@@ -70,7 +72,7 @@
|
||||
"event_awaiting_approval_subject": "Đang chờ phê duyệt: {{title}} lúc {{date}}",
|
||||
"event_still_awaiting_approval": "Một sự kiện vẫn đang chờ bạn phê duyệt",
|
||||
"booking_submitted_subject": "Lịch hẹn đã được gửi: {{title}} lúc {{date}}",
|
||||
"download_recording_subject": "Tải xuống bản ghi: {{title}} vào ngày {{date}}",
|
||||
"download_recording_subject": "Tải xuống bản ghi âm: {{title}} vào ngày {{date}}",
|
||||
"download_your_recording": "Tải xuống bản ghi âm của bạn",
|
||||
"your_meeting_has_been_booked": "Cuộc họp của bạn đã được đặt",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{title}} của bạn đã được chuyển sang ngày {{date}}.",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Quay lại trang đăng nhập",
|
||||
"error_during_login": "Đã xảy ra lỗi khi đăng nhập. Bạn hãy quay lại màn hình đăng nhập và thử lại.",
|
||||
"request_password_reset": "Gửi email đặt lại",
|
||||
"send_invite": "Gửi lời mời",
|
||||
"forgot_password": "Quên mật khẩu?",
|
||||
"forgot": "Quên?",
|
||||
"done": "Xong",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Đặt lịch khả dụng của bạn",
|
||||
"continue_without_calendar": "Tiếp tục mà không có lịch",
|
||||
"connect_your_calendar": "Kết nối lịch của bạn",
|
||||
"connect_your_video_app": "Kết nối các video yêu thích của bạn",
|
||||
"connect_your_video_app_instructions": "Kết nối những ứng dụng video để dùng chúng ở những loại sự kiện của bạn.",
|
||||
"connect_your_calendar_instructions": "Kết nối lịch của bạn để tự động kiểm tra thời gian bận và các sự kiện mới khi chúng được lên lịch.",
|
||||
"set_up_later": "Thiết lập sau",
|
||||
"current_time": "Thời gian hiện tại",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "Tạo Webhook",
|
||||
"booking_cancelled": "Lịch hẹn đã bị hủy",
|
||||
"booking_rescheduled": "Lịch hẹn đã được đổi",
|
||||
"recording_ready": "Đã sẵn sàng liên kết tải xuống bản ghi",
|
||||
"booking_created": "Lịch hẹn đã được tạo",
|
||||
"meeting_ended": "Cuộc họp đã kết thúc",
|
||||
"form_submitted": "Biểu mẫu đã được gửi",
|
||||
@@ -408,7 +414,7 @@
|
||||
"add_time_availability": "Thêm khoảng thời gian mới",
|
||||
"add_an_extra_layer_of_security": "Thêm một lớp bảo mật bổ sung cho tài khoản của bạn trong trường hợp mật khẩu của bạn bị đánh cắp.",
|
||||
"2fa": "Xác thực hai yếu tố",
|
||||
"2fa_disabled": "Xác thực đa yếu tố chỉ được bật cho chức năng xác thực qua email và mật khẩu",
|
||||
"2fa_disabled": "Xác thực hai yếu tố chỉ được bật cho chức năng xác thực qua email và mật khẩu",
|
||||
"enable_2fa": "Bật xác thực hai yếu tố",
|
||||
"disable_2fa": "Tắt xác thực hai yếu tố",
|
||||
"disable_2fa_recommendation": "Nếu bạn cần tắt xác thực hai yếu tố, chúng tôi khuyên bạn nên bật lại nó càng sớm càng tốt.",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "Thứ Sáu",
|
||||
"saturday": "Thứ Bảy",
|
||||
"sunday": "Chủ Nhật",
|
||||
"all_booked_today": "Tất cả đã được đặt chỗ.",
|
||||
"slots_load_fail": "Không thể tải thời gian có thể đặt lịch.",
|
||||
"additional_guests": "Thêm khách",
|
||||
"your_name": "Tên của bạn",
|
||||
@@ -591,9 +598,9 @@
|
||||
"round_robin_description": "Luân phiên những cuộc họp giữa các thành viên trong nhóm.",
|
||||
"managed_event": "Sự kiện được quản lý",
|
||||
"username_placeholder": "tên người dùng",
|
||||
"managed_event_description": "Tạo & phân phối cả cụm nhiều loại sự kiện đến các thành viên nhóm",
|
||||
"managed_event_description": "Tạo & phân phối hàng loạt nhiều loại sự kiện cho các thành viên nhóm",
|
||||
"managed": "Được quản lý",
|
||||
"managed_event_url_clarification": "\"tên người dùng\" sẽ được điền vào theo tên người dùng của những thành viên được phân công",
|
||||
"managed_event_url_clarification": "\"username\" sẽ được điền vào theo tên người dùng của những thành viên được phân công",
|
||||
"assign_to": "Phân công cho",
|
||||
"add_members": "Thêm thành viên...",
|
||||
"count_members_one": "{{count}} thành viên",
|
||||
@@ -602,7 +609,7 @@
|
||||
"assigned_to": "Được phân công cho",
|
||||
"start_assigning_members_above": "Bắt đầu phân công những thành viên bên trên",
|
||||
"locked_fields_admin_description": "Các thành viên sẽ không sửa được phần này",
|
||||
"locked_fields_member_description": "Tuỳ chọn này do quản trị nhóm khoá lại",
|
||||
"locked_fields_member_description": "Tuỳ chọn này do quản trị nhóm khóa lại",
|
||||
"url": "URL",
|
||||
"hidden": "Ẩn giấu",
|
||||
"readonly": "Chỉ đọc",
|
||||
@@ -752,13 +759,17 @@
|
||||
"new_event_type_to_book_description": "Tạo một loại sự kiện mới để mọi người đặt thời gian.",
|
||||
"length": "Thời gian",
|
||||
"minimum_booking_notice": "Thời gian đệm tối thiểu",
|
||||
"offset_toggle": "Thời gian bắt đầu được bù lại",
|
||||
"offset_toggle_description": "Suất thời gian bù lại được hiển thị cho những người đặt lịch theo số phút cụ thể",
|
||||
"offset_start": "Bù lại",
|
||||
"offset_start_description": "ví dụ cái này sẽ hiển thị những suất thời gian cho người đặt lịch vào lúc {{ adjustedTime }} thay vì {{ originalTime }}",
|
||||
"slot_interval": "Khoảng thời gian giữa các lịch hẹn",
|
||||
"slot_interval_default": "Sử dụng độ dài sự kiện (mặc định)",
|
||||
"delete_event_type": "Xoá loại sự kiện?",
|
||||
"delete_managed_event_type": "Xoá loại sự kiện được quản lý?",
|
||||
"delete_event_type": "Xóa loại sự kiện?",
|
||||
"delete_managed_event_type": "Xóa loại sự kiện được quản lý?",
|
||||
"delete_event_type_description": "Đối với những người mà bạn chia sẻ liên kết này, họ sẽ không thể đặt lịch hẹn bằng liên kết đó được nữa.",
|
||||
"delete_managed_event_type_description": "<ul><li>Những thành viên được phân công cho loại sự kiện này cũng sẽ khiến cho loại sự kiện của họ được xoá đi.</li><li>Đối với những người mà bạn chia sẻ liên kết này, họ sẽ không thể đặt lịch hẹn bằng liên kết đó được nữa.</li></ul>",
|
||||
"confirm_delete_event_type": "Có, hãy xoá",
|
||||
"delete_managed_event_type_description": "<ul><li>Những thành viên được phân công cho loại sự kiện này cũng sẽ khiến cho loại sự kiện của họ được xóa đi.</li><li>Đối với những người mà bạn chia sẻ liên kết này, họ sẽ không thể đặt lịch hẹn bằng liên kết đó được nữa.</li></ul>",
|
||||
"confirm_delete_event_type": "Có, hãy xóa",
|
||||
"delete_account": "Xóa tài khoản",
|
||||
"confirm_delete_account": "Có, xóa tài khoản",
|
||||
"delete_account_confirmation_message": "Bạn có chắc chắn muốn xóa tài khoản {{appName}} của mình không? Bất kỳ ai mà bạn đã chia sẻ liên kết tài khoản của mình sẽ không thể đặt trước bằng liên kết đó nữa và mọi tùy chọn bạn đã lưu sẽ bị mất.",
|
||||
@@ -904,6 +915,7 @@
|
||||
"duplicate": "Sao chép",
|
||||
"offer_seats": "Cung cấp ghế ngồi",
|
||||
"offer_seats_description": "Cung cấp ghế để đặt lịch. Việc này sẽ tự động vô hiệu hoá đặt lịch của khách & đặt lịch tham gia.",
|
||||
"seats_available_one": "Số ghế trống",
|
||||
"seats_available_other": "Số ghế trống",
|
||||
"number_of_seats": "Số ghế cho mỗi lần đặt",
|
||||
"enter_number_of_seats": "Điền vào số lượng ghế",
|
||||
@@ -1038,6 +1050,7 @@
|
||||
"event_cancelled_trigger": "khi sự kiện bị huỷ",
|
||||
"new_event_trigger": "khi sự kiện mới được đặt lịch hẹn",
|
||||
"email_host_action": "gửi email đến chủ sự kiện",
|
||||
"email_attendee_action": "gửi email đến người tham gia",
|
||||
"sms_attendee_action": "gửi SMS đến người tham gia",
|
||||
"sms_number_action": "gửi SMS đến một số cụ thể",
|
||||
"workflows": "Tiến độ công việc",
|
||||
@@ -1154,7 +1167,7 @@
|
||||
"variable_format": "Định dạng biến số",
|
||||
"webhook_subscriber_url_reserved": "Url ghi danh Webhook đã được lập định",
|
||||
"custom_input_as_variable_info": "Bỏ qua tất cả các ký tự đặc biệt của nhãn thông tin đầu vào bổ sung (chỉ dùng chữ cái và con số), dùng chữ in hoa cho tất cả các chữ cái và thay thế khoảng trắng bằng dấu gạch dưới.",
|
||||
"using_booking_questions_as_variables": "Làm cách nào tôi dùng những câu hỏi lịch hẹn ở dạng tham số?",
|
||||
"using_booking_questions_as_variables": "Làm thế nào để tôi có thể dùng những câu hỏi lịch hẹn ở dạng tham số?",
|
||||
"download_desktop_app": "Tải ứng dụng trên máy tính bàn",
|
||||
"set_ping_link": "Đặt liên kết Ping",
|
||||
"rate_limit_exceeded": "Đã vượt quá giới hạn đăng nhập",
|
||||
@@ -1177,7 +1190,7 @@
|
||||
"event_advanced_tab_title": "Nâng cao",
|
||||
"event_setup_multiple_duration_error": "Thiết lập sự kiện: Nhiều khoảng thời gian cần ít nhất 1 tuỳ chọn.",
|
||||
"event_setup_multiple_duration_default_error": "Thiết lập sự kiện: Vui lòng chọn một khoảng thời gian mặc định hợp lệ.",
|
||||
"event_setup_booking_limits_error": "Giới hạn lịch hẹn phải theo thứ tự tăng dần. [ngày,tuần,tháng,năm]",
|
||||
"event_setup_booking_limits_error": "Giới hạn lịch hẹn phải theo thứ tự tăng dần. [ngày, tuần, tháng, năm]",
|
||||
"event_setup_duration_limits_error": "Giới hạn thời gian phải được đặt theo thứ tự tăng dần. [ngày, tuần, tháng, năm]",
|
||||
"select_which_cal": "Chọn lịch nào cần thêm lịch hẹn vào",
|
||||
"custom_event_name": "Tên sự kiện tuỳ chỉnh",
|
||||
@@ -1190,6 +1203,7 @@
|
||||
"create_workflow": "Tạo một tiến độ công việc",
|
||||
"do_this": "Làm cái này",
|
||||
"turn_off": "Tắt",
|
||||
"turn_on": "Bật",
|
||||
"settings_updated_successfully": "Cập nhật thành công phần cài đặt",
|
||||
"error_updating_settings": "Lỗi khi cập nhật cài đặt",
|
||||
"personal_cal_url": "URL {{appName}} cá nhân của tôi",
|
||||
@@ -1200,7 +1214,7 @@
|
||||
"start_of_week": "Khởi đầu tuần",
|
||||
"recordings_title": "Bản ghi âm",
|
||||
"recording": "Bản ghi âm",
|
||||
"happy_scheduling": "Đặt lịch vui",
|
||||
"happy_scheduling": "Chúc bạn đặt lịch vui vẻ",
|
||||
"select_calendars": "Chọn lịch nào mà bạn muốn kiểm tra xung đột nhằm tránh đặt lịch hẹn trùng.",
|
||||
"check_for_conflicts": "Kiểm tra xung đột",
|
||||
"view_recordings": "Xem các bản ghi âm",
|
||||
@@ -1246,6 +1260,7 @@
|
||||
"calendars_description": "Cấu hình cách các loại sự kiện của bạn tương tác với lịch của bạn",
|
||||
"appearance_description": "Quản lí cài đặt cho giao diện lịch hẹn của bạn",
|
||||
"conferencing_description": "Quản lí ứng dụng hội nghị video dùng cho các cuộc họp",
|
||||
"add_conferencing_app": "Thêm ứng dụng hội nghị",
|
||||
"password_description": "Quản lí cài đặt cho mật khẩu tài khoản",
|
||||
"2fa_description": "Quản lí cài đặt cho mật khẩu tài khoản",
|
||||
"we_just_need_basic_info": "Chúng tôi chỉ cần một số thông tin cơ bản để thiết lập hồ sơ của bạn.",
|
||||
@@ -1276,7 +1291,7 @@
|
||||
"attendee_name_info": "Tên người tham gia lịch hẹn",
|
||||
"to": "Đến",
|
||||
"workflow_turned_on_successfully": "Đã {{offOn}} thành công dòng công việc {{workflowName}}",
|
||||
"download_responses": "Tải xuống Phản hồi",
|
||||
"download_responses": "Tải xuống phản hồi",
|
||||
"download_responses_description": "Tải xuống tất cả phản hồi vào biểu mẫu của bạn ở định dạng CSV.",
|
||||
"download": "Tải về",
|
||||
"download_recording": "Tải xuống bản ghi âm",
|
||||
@@ -1326,7 +1341,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Bạn có thể thấy tất cả các biểu mẫu và định hướng mà bạn đã tạo ra ở đây.",
|
||||
"routing_forms_description": "Tạo các biểu mẫu để chuyển hướng người tham dự đến đúng địa điểm",
|
||||
"routing_forms_send_email_owner": "Gửi email đến chủ sở hữu",
|
||||
"routing_forms_send_email_owner_description": "Gửi một email đến chủ sở hữu khi biểu mẫu được gửi đi",
|
||||
"add_new_form": "Thêm biểu mẫu mới",
|
||||
@@ -1380,7 +1395,7 @@
|
||||
"add_limit": "Thêm giới hạn",
|
||||
"team_name_required": "Cần có tên nhóm",
|
||||
"show_attendees": "Chia sẻ thông tin người tham gia cho khách",
|
||||
"how_booking_questions_as_variables": "Làm cách nào dùng những câu hỏi lịch hẹn ở dạng tham số?",
|
||||
"how_booking_questions_as_variables": "Làm thế nào để dùng những câu hỏi lịch hẹn ở dạng tham số?",
|
||||
"format": "Định dạng",
|
||||
"uppercase_for_letters": "In hoa hết tất cả các chữ cái",
|
||||
"replace_whitespaces_underscores": "Thay thế khoảng trắng bằng dấu gạch dưới",
|
||||
@@ -1395,7 +1410,7 @@
|
||||
"billing_help_title": "Cần gì nữa không?",
|
||||
"billing_help_description": "Nếu bạn cần thêm bất kỳ trợ giúp nào về thanh toán, nhóm hỗ trợ của chúng tôi luôn sẵn sàng trợ giúp.",
|
||||
"billing_help_cta": "Liên hệ với bộ phận hỗ trợ",
|
||||
"ignore_special_characters_booking_questions": "Bỏ qua những kí tự đặc biệt trong mã định danh câu hỏi lịch hẹn. Chỉ dùng chữ cái và số",
|
||||
"ignore_special_characters_booking_questions": "Bỏ qua những ký tự đặc biệt trong mã định danh câu hỏi lịch hẹn. Chỉ dùng chữ cái và số",
|
||||
"retry": "Thử lại",
|
||||
"fetching_calendars_error": "Có vấn đề khi tìm dữ liệu của bạn. Vui lòng <1>thử lại</1> hoặc liên hệ với bộ phận hỗ trợ khách hàng.",
|
||||
"calendar_connection_fail": "Kết nối lịch bất thành",
|
||||
@@ -1488,7 +1503,7 @@
|
||||
"email_subject_slug_replacement": "Một quản trị viên của nhóm đã thay thế sự kiện của bạn /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "Một quản trị viên trên nhóm <strong>{{teamName}}</strong> đã thay thế loại sự kiện của bạn <strong>/{{slug}}</strong> bằng một loại sự kiện được quản lý mà họ kiểm soát.",
|
||||
"email_body_slug_replacement_info": "Liên kết của bạn sẽ tiếp tục hoạt động nhưng một số thiết lập cho nó có thể thay đổi. Bạn có thể xem lại nó trong những loại sự kiện.",
|
||||
"email_body_slug_replacement_suggestion": "Nếu bạn có thắc mắc gì về loại sự kiện này, vui lòng liên lạc với quản trị viên của bạn. <br /><br />Đặt lịch vui,<br /> Nhóm Cal.com",
|
||||
"email_body_slug_replacement_suggestion": "Nếu bạn có thắc mắc gì về loại sự kiện này, vui lòng liên lạc với quản trị viên của bạn. <br /><br />Chúc bạn đặt lịch vui vẻ,<br /> Nhóm Cal.com",
|
||||
"disable_payment_app": "Một quản trị viên đã tắt {{appName}} vốn có ảnh hưởng đến loại sự kiện {{title}}. Người tham dự vẫn có thể đặt hẹn cho loại sự kiện này nhưng sẽ được nhắc trả phí. Bạn có thể ẩn loại sự kiện này để tránh điều này cho đến khi quản trị viên kích hoạt lại phương thức thanh toán của bạn.",
|
||||
"payment_disabled_still_able_to_book": "Người tham dự vẫn có thể đặt hẹn cho loại sự kiện này nhưng sẽ được nhắc trả phí. Bạn có thể ẩn loại sự kiện này để tránh điều này cho đến khi quản trị viên kích hoạt lại phương thức thanh toán của bạn.",
|
||||
"app_disabled_with_event_type": "Quản trị viên đã tắt {{appName}} vốn có ảnh hưởng đến loại sự kiện {{title}}.",
|
||||
@@ -1620,6 +1635,7 @@
|
||||
"email_user_cta": "Xem lời mời",
|
||||
"email_no_user_invite_heading": "Bạn đã được mời gia nhập nhóm trên {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} đã mời bạn gia nhập nhóm của họ trên {{appName}}. {{appName}} là công cụ lên lịch sắp xếp sự kiện cho phép bạn và nhóm bạn lên lịch các cuộc gặp mà không cần trao đổi email nhiều.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} đã mời bạn gia nhập nhóm '{{teamName}}' của họ trên {{appName}}. {{appName}} là công cụ lên lịch sắp xếp sự kiện cho phép bạn và nhóm bạn lên lịch các cuộc gặp mà không cần trao đổi email nhiều.",
|
||||
"email_no_user_invite_steps_intro": "Chúng tôi sẽ hướng dẫn cho bạn qua vài bước nhỏ và bạn sẽ tận hưởng được ngay cảm giác thoải mái không căng thẳng trong việc lên lịch cùng nhóm của mình.",
|
||||
"email_no_user_step_one": "Chọn tên người dùng của bạn",
|
||||
"email_no_user_step_two": "Kết nối tài khoản lịch của bạn",
|
||||
@@ -1687,14 +1703,23 @@
|
||||
"add_a_new_route": "Thêm một Định hướng mới",
|
||||
"make_informed_decisions": "Đưa ra những quyết định sáng suốt với Insights",
|
||||
"make_informed_decisions_description": "Bảng điều khiển Insights của chúng tôi nằm ở bề mặt xuyên suốt nhóm bạn và hiển thị cho bạn thấy những xu hướng giúp đặt lịch nhóm và ra quyết định tốt hơn.",
|
||||
"view_bookings_across": "Xem lịch hẹn ở mọi thành viên",
|
||||
"view_bookings_across": "Xem lịch hẹn cho mọi thành viên",
|
||||
"view_bookings_across_description": "Xem ai nhận được nhiều lịch hẹn nhất và bảo đảm việc phân phối tốt nhất cho khắp nhóm",
|
||||
"identify_booking_trends": "Nhận diện xu hướng lịch hẹn",
|
||||
"identify_booking_trends": "Xác định xu hướng lịch hẹn",
|
||||
"identify_booking_trends_description": "Xem thời điểm nào trong tuần và thời điểm nào trong ngày là thời điểm phổ biến cho người đặt lịch hẹn bên bạn",
|
||||
"spot_popular_event_types": "Nhận ra các loại sự kiện phổ biến",
|
||||
"spot_popular_event_types_description": "Xem loại sự kiện nào của bạn nhận được nhiều lần nhấp chuột và nhiều lịch hẹn nhất",
|
||||
"no_responses_yet": "Chưa có hồi đáp",
|
||||
"this_will_be_the_placeholder": "Đây sẽ là mã giữ chỗ",
|
||||
"error_booking_event": "Có lỗi khi đặt lịch sự kiện, hãy làm mới trang rồi thử lại",
|
||||
"timeslot_missing_title": "Chưa chọn suất thời gian nào",
|
||||
"timeslot_missing_description": "Vui lòng chọn một suất thời gian để đặt lịch sự kiện.",
|
||||
"timeslot_missing_cta": "Chọn suất thời gian",
|
||||
"switch_monthly": "Chuyển sang giao diện tháng",
|
||||
"switch_weekly": "Chuyển sang giao diện tuần",
|
||||
"switch_multiday": "Chuyển sang giao diện ngày",
|
||||
"num_locations": "{{num}} lựa chọn vị trí",
|
||||
"select_on_next_step": "Chọn ở bước kế tiếp",
|
||||
"this_meeting_has_not_started_yet": "Cuộc hẹn này chưa được bắt đầu",
|
||||
"this_app_requires_connected_account": "{{appName}} cần có một tài khoản {{dependencyName}} đã kết nối",
|
||||
"connect_app": "Kết nối {{dependencyName}}",
|
||||
@@ -1707,8 +1732,8 @@
|
||||
"timezone_info": "Múi giờ của người nhận",
|
||||
"event_end_time_variable": "Thời gian kết thúc sự kiện",
|
||||
"event_end_time_info": "Thời gian kết thúc sự kiện",
|
||||
"cancel_url_variable": "URL huỷ lịch",
|
||||
"cancel_url_info": "URL để huỷ lịch hẹn",
|
||||
"cancel_url_variable": "URL hủy lịch",
|
||||
"cancel_url_info": "URL để hủy lịch hẹn",
|
||||
"reschedule_url_variable": "URL đặt lịch lại",
|
||||
"reschedule_url_info": "URL để đặt lịch hẹn lại",
|
||||
"invalid_event_name_variables": "Có biến số không hợp lệ trong tên sự kiện của bạn",
|
||||
@@ -1724,6 +1749,7 @@
|
||||
"locked_apps_description": "Các thành viên có thể thấy được những ứng dụng đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của ứng dụng",
|
||||
"locked_webhooks_description": "Các thành viên có thể thấy được những webhook đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của webhook",
|
||||
"locked_workflows_description": "Các thành viên có thể thấy được những tiến độ công việc đang hoạt động nhưng không thể sửa bất kỳ thiết lập nào của tiến độ công việc",
|
||||
"locked_by_admin": "Bị khoá bởi quản trị viên của nhóm",
|
||||
"app_not_connected": "Bạn không có tài khoản {{appName}} đã được kết nối.",
|
||||
"connect_now": "Kết nối ngay",
|
||||
"managed_event_dialog_confirm_button_one": "Thay thế & thông báo cho {{count}} thành viên",
|
||||
@@ -1732,10 +1758,10 @@
|
||||
"managed_event_dialog_title_other": "URL /{{slug}} đã có sẵn cho {{count}} thành viên. Bạn có muốn thay thế nó?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> đã dùng URL <strong>/{{slug}}</strong> rồi.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> đã dùng URL <string>/{{slug}}</strong> rồi.",
|
||||
"managed_event_dialog_clarification": "Nếu bạn chọn phương án thay thế nó, chúng tôi sẽ thông báo cho họ biết. Hãy quay lại và xoá bỏ họ nếu bạn không muốn ghi đè nó.",
|
||||
"managed_event_dialog_clarification": "Nếu bạn chọn phương án thay thế nó, chúng tôi sẽ thông báo cho họ biết. Hãy quay lại và xóa bỏ họ nếu bạn không muốn ghi đè nó.",
|
||||
"review_event_type": "Xem lại loại sự kiện",
|
||||
"looking_for_more_analytics": "Đang tìm thêm công cụ phân tích?",
|
||||
"looking_for_more_insights": "Đang tìm thêm Insights?",
|
||||
"looking_for_more_analytics": "Bạn muốn có thêm thông tin phân tích?",
|
||||
"looking_for_more_insights": "Bạn muốn có thêm Insights?",
|
||||
"add_filter": "Thêm bộ lọc",
|
||||
"select_user": "Chọn người dùng",
|
||||
"select_event_type": "Chọn loại sự kiện",
|
||||
@@ -1747,7 +1773,7 @@
|
||||
"least_booked_members": "Thành viên được đặt lịch hẹn ít nhất",
|
||||
"events_created": "Những sự kiện đã tạo",
|
||||
"events_completed": "Những sự kiện đã hoàn thành",
|
||||
"events_cancelled": "Những sự kiện đã huỷ",
|
||||
"events_cancelled": "Những sự kiện đã hủy",
|
||||
"events_rescheduled": "Những sự kiện đã đặt lịch lại",
|
||||
"from_last_period": "từ thời gian gần nhất",
|
||||
"from_to_date_period": "Từ: {{startDate}} Đến: {{endDate}}",
|
||||
@@ -1755,11 +1781,11 @@
|
||||
"subtitle_analytics": "Tìm hiểu thêm về hoạt động của nhóm bạn",
|
||||
"redirect_url_warning": "Thêm mục chuyển hướng sẽ tắt đi trang thành công. Hãy bảo đảm có đề cập \"Lịch hẹn đã xác nhận\" trên trang thành công tuỳ chỉnh riêng của bạn.",
|
||||
"event_trends": "Xu hướng sự kiện",
|
||||
"clear_filters": "Xoá bộ lọc",
|
||||
"hold": "Giữ đó",
|
||||
"clear_filters": "Xóa bộ lọc",
|
||||
"hold": "Tạm giữ",
|
||||
"on_booking_option": "Thu tiền thanh toán cho lịch hẹn",
|
||||
"hold_option": "Tính phí vắng mặt",
|
||||
"card_held": "Thẻ đã được giữ",
|
||||
"card_held": "Thẻ đã được tạm giữ",
|
||||
"charge_card": "Tính phí cho thẻ",
|
||||
"card_charged": "Thẻ đã thu phí",
|
||||
"no_show_fee_amount": "Phí vắng mặt {{amount, currency}}",
|
||||
@@ -1776,10 +1802,39 @@
|
||||
"insights": "Insights",
|
||||
"testing_workflow_info_message": "Khi kiểm tra tiến độ công việc này, hãy để ý rằng Email và SMS chỉ có thể được đặt lịch trước ít nhất 1 giờ",
|
||||
"insights_no_data_found_for_filter": "Không tìm thấy dữ liệu cho bộ lọc hay ngày đã chọn.",
|
||||
"acknowledge_booking_no_show_fee": "Tôi công nhận là nếu tôi không tham dự sự kiện này thì một khoản phí vắng mặt gồm {{amount, currency}} sẽ được áp dụng cho thẻ của tôi.",
|
||||
"acknowledge_booking_no_show_fee": "Tôi công nhận là nếu tôi không tham dự sự kiện này thì một khoản phí vắng mặt là {{amount, currency}} sẽ được áp dụng cho thẻ của tôi.",
|
||||
"card_details": "Chi tiết thẻ",
|
||||
"seats_and_no_show_fee_error": "Hiện không thể kích hoạt chỗ ngồi và tính khoản phí vắng mặt",
|
||||
"complete_your_booking": "Hoàn thành lịch hẹn của bạn",
|
||||
"complete_your_booking_subject": "Hoàn thành lịch hẹn của bạn: {{title}} vào {{date}}",
|
||||
"email_invite_team": "{{email}} đã được mời"
|
||||
"confirm_your_details": "Xác nhận các chi tiết của bạn",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Bạn sắp sửa thu phí người tham gia một khoản {{amount, currency}}. Bạn có chắc chắn muốn tiếp tục?",
|
||||
"charge_attendee": "Thu phí người tham gia một khoản {{amount, currency}}",
|
||||
"payment_app_commission": "Yêu cầu thanh toán ({{paymentFeePercentage}}% + {{fee, currency}} hoa hồng cho mỗi giao dịch)",
|
||||
"email_invite_team": "{{email}} đã được mời",
|
||||
"email_invite_team_bulk": "{{userCount}} người dùng đã được mời",
|
||||
"error_collecting_card": "Lỗi khi thu thập thẻ",
|
||||
"image_size_limit_exceed": "Ảnh tải lên không được vượt quá giới hạn kích cỡ 5mb",
|
||||
"inline_embed": "Nội tuyến đã nhúng",
|
||||
"load_inline_content": "Tải trực tiếp loại sự kiện của bạn ở dạng nội tuyến với nội dung khác trên website của bạn.",
|
||||
"floating_pop_up_button": "Nút popup dạng nổi",
|
||||
"floating_button_trigger_modal": "Đặt một nút dạng nổi trên website giúp kích hoạt chế độ với loại sự kiện này.",
|
||||
"pop_up_element_click": "Popup thông qua nhấp chuột trong phần tử",
|
||||
"open_dialog_with_element_click": "Mở hộp thoại Cal khi ai đó nhấp vào một phần tử.",
|
||||
"need_help_embedding": "Cần trợ giúp? Xem hướng dẫn của chúng tôi dành cho việc nhúng Cal trên Wix, Squarespace hoặc WordPress, xem qua những câu hỏi thường gặp của chúng tôi, hoặc khám phá thêm các tuỳ chọn nhúng nâng cao.",
|
||||
"book_my_cal": "Đặt lịch Cal của tôi",
|
||||
"invite_as": "Mời ở vai trò",
|
||||
"form_updated_successfully": "Đã cập nhật biểu mẫu thành công.",
|
||||
"email_not_cal_member_cta": "Gia nhập nhóm của bạn",
|
||||
"disable_attendees_confirmation_emails": "Vô hiệu hoá những email xác nhận mặc định dành cho người tham dự",
|
||||
"disable_attendees_confirmation_emails_description": "Ít nhất một tiến độ công việc đang hoạt động ở loại sự kiện này, và tiến độ này sẽ gửi email đến những người tham dự khi sự kiện này được đặt lịch.",
|
||||
"disable_host_confirmation_emails": "Vô hiệu hoá những email xác nhận mặc định dành cho chủ toạ",
|
||||
"disable_host_confirmation_emails_description": "Ít nhất một tiến độ công việc đang hoạt động ở loại sự kiện này, và tiến độ này sẽ gửi email đến chủ toạ khi sự kiện này được đặt lịch.",
|
||||
"add_an_override": "Thêm ngày ghi đè",
|
||||
"import_from_google_workspace": "Nhập người dùng từ Google Workspace",
|
||||
"connect_google_workspace": "Kết nối Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Bạn phải là một Quản trị viên của Workspace mới dùng được tính năng này",
|
||||
"first_event_type_webhook_description": "Tạo webhook đầu tiên của bạn cho loại sự kiện này",
|
||||
"create_for": "Tạo cho"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "拒绝预约",
|
||||
"manage_this_event": "管理此活动",
|
||||
"invite_team_member": "邀请团队成员",
|
||||
"invite_team_individual_segment": "邀请个人",
|
||||
"invite_team_bulk_segment": "批量导入",
|
||||
"invite_team_notifcation_badge": "邀请",
|
||||
"your_event_has_been_scheduled": "您的活动已预约",
|
||||
"your_event_has_been_scheduled_recurring": "您的定期活动已安排",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "返回登录页面",
|
||||
"error_during_login": "登录时发生错误,请返回登录界面,然后重试。",
|
||||
"request_password_reset": "请求重置电子邮件",
|
||||
"send_invite": "发送邀请",
|
||||
"forgot_password": "忘记密码?",
|
||||
"forgot": "忘记了?",
|
||||
"done": "完成",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "设置您的可预约状态",
|
||||
"continue_without_calendar": "无日历继续",
|
||||
"connect_your_calendar": "连接您的日历",
|
||||
"connect_your_video_app": "连接您的视频应用",
|
||||
"connect_your_video_app_instructions": "连接您的视频应用,以将它们用于您的活动类型。",
|
||||
"connect_your_calendar_instructions": "连接您的日历以自动检查您繁忙的时间和同步新活动的预约。",
|
||||
"set_up_later": "稍后设置",
|
||||
"current_time": "当前时间",
|
||||
@@ -367,6 +372,7 @@
|
||||
"create_webhook": "创建 Webhook",
|
||||
"booking_cancelled": "预约已取消",
|
||||
"booking_rescheduled": "预约已重新安排",
|
||||
"recording_ready": "录制内容下载链接就绪",
|
||||
"booking_created": "预约已创建",
|
||||
"meeting_ended": "会议已结束",
|
||||
"form_submitted": "表格已提交",
|
||||
@@ -465,6 +471,7 @@
|
||||
"friday": "周五",
|
||||
"saturday": "周六",
|
||||
"sunday": "周日",
|
||||
"all_booked_today": "全部已预约。",
|
||||
"slots_load_fail": "无法加载可用的时间段。",
|
||||
"additional_guests": "添加访客",
|
||||
"your_name": "您的姓名",
|
||||
@@ -752,6 +759,10 @@
|
||||
"new_event_type_to_book_description": "新建一个活动类型以让他人预约。",
|
||||
"length": "时长",
|
||||
"minimum_booking_notice": "最小预约通知提前时间",
|
||||
"offset_toggle": "偏移开始时间",
|
||||
"offset_toggle_description": "将显示给预约者的时间段偏移指定的分钟数",
|
||||
"offset_start": "偏移量",
|
||||
"offset_start_description": "例如,这将向您的预约者显示时间段 {{ adjustedTime }},而不是 {{ originalTime }}",
|
||||
"slot_interval": "时间段间隔",
|
||||
"slot_interval_default": "使用活动时长 (默认)",
|
||||
"delete_event_type": "删除活动类型?",
|
||||
@@ -1624,6 +1635,7 @@
|
||||
"email_user_cta": "查看邀请",
|
||||
"email_no_user_invite_heading": "您已被邀请在 {{appName}} 加入一个团队",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} 已邀请您加入他们在 {{appName}} 上的团队。{{appName}} 是一个活动安排调度程序,让您和您的团队无需通过电子邮件沟通即可安排会议。",
|
||||
"email_user_invite_subheading": "{{invitedBy}} 已邀请您加入他们在 {{appName}} 上的团队“{{teamName}}”。{{appName}} 是一个活动安排调度程序,让您和您的团队无需通过电子邮件沟通即可安排会议。",
|
||||
"email_no_user_invite_steps_intro": "我们将引导您完成几个简短步骤,您将立即与您的团队一起享受无压力的日程安排。",
|
||||
"email_no_user_step_one": "选择您的用户名",
|
||||
"email_no_user_step_two": "连接您的日历账户",
|
||||
@@ -1699,6 +1711,15 @@
|
||||
"spot_popular_event_types_description": "查看获得最多点击和预约的活动类型",
|
||||
"no_responses_yet": "尚无回复",
|
||||
"this_will_be_the_placeholder": "这将是占位符",
|
||||
"error_booking_event": "预订活动时发生错误,请刷新页面并重试",
|
||||
"timeslot_missing_title": "未选择时间段",
|
||||
"timeslot_missing_description": "请选择要预约活动的时间段。",
|
||||
"timeslot_missing_cta": "选择时间段",
|
||||
"switch_monthly": "切换到每月视图",
|
||||
"switch_weekly": "切换到每周视图",
|
||||
"switch_multiday": "切换到日视图",
|
||||
"num_locations": "{{num}} 个位置选项",
|
||||
"select_on_next_step": "在下一步选择",
|
||||
"this_meeting_has_not_started_yet": "此会议尚未开始",
|
||||
"this_app_requires_connected_account": "{{appName}} 需要已连接的 {{dependencyName}} 账户",
|
||||
"connect_app": "连接 {{dependencyName}}",
|
||||
@@ -1786,12 +1807,34 @@
|
||||
"seats_and_no_show_fee_error": "当前无法启用位置和收取失约费",
|
||||
"complete_your_booking": "完成您的预约",
|
||||
"complete_your_booking_subject": "完成您的预约: {{date}} 的 {{title}}",
|
||||
"confirm_your_details": "确认您的详细信息",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "您即将向参与者收取费用 {{amount, currency}}。您确定要继续吗?",
|
||||
"charge_attendee": "向参与者收取费用 {{amount, currency}}",
|
||||
"payment_app_commission": "需要付款({{paymentFeePercentage}}% + 每笔交易 {{fee, currency}} 佣金)",
|
||||
"email_invite_team": "{{email}} 已被邀请",
|
||||
"email_invite_team_bulk": "{{userCount}} 名用户已被邀请",
|
||||
"error_collecting_card": "收集卡时出错",
|
||||
"image_size_limit_exceed": "上传的图像不应超过 5mb 大小限制",
|
||||
"inline_embed": "内联嵌入",
|
||||
"load_inline_content": "将您的活动类型直接与您的其他网站内容内联加载。",
|
||||
"floating_pop_up_button": "浮动弹出式按钮",
|
||||
"floating_button_trigger_modal": "在您的网站上放置一个浮动按钮,用于根据您的活动类型触发模式。",
|
||||
"pop_up_element_click": "通过点击元素弹出",
|
||||
"open_dialog_with_element_click": "当有人点击元素时,打开您的 Cal 对话框。",
|
||||
"need_help_embedding": "需要帮助?看一下在 Wix、Squarespace 或 WordPress 上嵌入 Cal 的指南,查看常见问题,或浏览高级嵌入选项。",
|
||||
"book_my_cal": "预约我的 Cal",
|
||||
"invite_as": "邀请为",
|
||||
"form_updated_successfully": "表单更新成功。",
|
||||
"email_not_cal_member_cta": "加入您的团队",
|
||||
"disable_attendees_confirmation_emails": "禁用参与者的默认确认电子邮件",
|
||||
"disable_attendees_confirmation_emails_description": "该活动类型中至少有一个工作流程处于活动状态,当该活动被预约时,将向参与者发送电子邮件。",
|
||||
"disable_host_confirmation_emails": "禁用主持人的默认确认电子邮件",
|
||||
"disable_host_confirmation_emails_description": "该活动类型中至少有一个工作流程处于活动状态,当该活动被预约时,将向主持人发送电子邮件。",
|
||||
"add_an_override": "添加替代",
|
||||
"import_from_google_workspace": "从 Google Workspace 导入用户",
|
||||
"connect_google_workspace": "连接 Google Workspace",
|
||||
"google_workspace_admin_tooltip": "您必须是 Workspace 管理员才能使用此功能",
|
||||
"first_event_type_webhook_description": "为此活动类型创建第一个 Webhook",
|
||||
"create_for": "创建"
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ html.todesktop-platform-darwin.light main.bg-default{
|
||||
background: rgba(255,255,255,0.8) !important
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
html.todesktop aside a {
|
||||
height: 28px;
|
||||
padding: 0px 8px;
|
||||
@@ -497,4 +497,16 @@ select:focus {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country:hover,
|
||||
.react-tel-input .country-list .country.highlight {
|
||||
@apply !bg-emphasis;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown .selected-flag,
|
||||
.react-tel-input .flag-dropdown.open .selected-flag {
|
||||
@apply !bg-default;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown {
|
||||
@apply !border-r-default !border-y-0 !border-l-0 left-0.5;
|
||||
}
|
||||
|
||||
@@ -77,5 +77,5 @@ it("should render city name as option label if cityData is not empty", () => {
|
||||
});
|
||||
|
||||
it("should return timezone as option label if cityData is empty", () => {
|
||||
expect(handleOptionLabel(option, [])).toMatchInlineSnapshot(`"America/Los_Angeles GMT -8:00"`);
|
||||
expect(handleOptionLabel(option, [])).toMatchInlineSnapshot(`"America/Los Angeles GMT -8:00"`);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import prisma, { safeAppSelect, safeCredentialSelect } from "@calcom/prisma";
|
||||
@@ -35,6 +37,7 @@ export async function getAppRegistry() {
|
||||
select: { dirName: true, slug: true, categories: true, enabled: true },
|
||||
});
|
||||
const apps = [] as App[];
|
||||
const mostPopularApps = await getMostPopularApps();
|
||||
for await (const dbapp of dbApps) {
|
||||
const app = await getAppWithMetadata(dbapp);
|
||||
if (!app) continue;
|
||||
@@ -47,6 +50,7 @@ export async function getAppRegistry() {
|
||||
category: app.category || "other",
|
||||
installed:
|
||||
true /* All apps from DB are considered installed by default. @TODO: Add and filter our by `enabled` property */,
|
||||
installCount: mostPopularApps[dbapp.slug] || 0,
|
||||
});
|
||||
}
|
||||
return apps;
|
||||
@@ -82,6 +86,7 @@ export async function getAppRegistryWithCredentials(userId: number) {
|
||||
credentials: Credential[];
|
||||
isDefault?: boolean;
|
||||
})[];
|
||||
const mostPopularApps = await getMostPopularApps();
|
||||
for await (const dbapp of dbApps) {
|
||||
const app = await getAppWithMetadata(dbapp);
|
||||
if (!app) continue;
|
||||
@@ -108,6 +113,7 @@ export async function getAppRegistryWithCredentials(userId: number) {
|
||||
categories: dbapp.categories,
|
||||
credentials: dbapp.credentials,
|
||||
installed: true,
|
||||
installCount: mostPopularApps[dbapp.slug] || 0,
|
||||
isDefault: usersDefaultApp === dbapp.slug,
|
||||
...(app.dependencies && { dependencyData }),
|
||||
});
|
||||
@@ -115,3 +121,25 @@ export async function getAppRegistryWithCredentials(userId: number) {
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
async function getMostPopularApps() {
|
||||
const mostPopularApps = z.array(z.object({ appId: z.string(), installCount: z.number() })).parse(
|
||||
await prisma.$queryRaw`
|
||||
SELECT
|
||||
c."appId",
|
||||
COUNT(*)::integer AS "installCount"
|
||||
FROM
|
||||
"Credential" c
|
||||
WHERE
|
||||
c."appId" IS NOT NULL
|
||||
GROUP BY
|
||||
c."appId"
|
||||
ORDER BY
|
||||
"installCount" DESC
|
||||
`
|
||||
);
|
||||
return mostPopularApps.reduce((acc, { appId, installCount }) => {
|
||||
acc[appId] = installCount;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "amie",
|
||||
"type": "amie_other",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/amie",
|
||||
"url": "https://cal.com",
|
||||
"variant": "other",
|
||||
"categories": ["calendar"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"slug": "around",
|
||||
"type": "around_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/around",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
"publisher": "Cal.com",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "campfire",
|
||||
"type": "campfire_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/campfire",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"slug": "closecom",
|
||||
"type": "closecom_other_calendar",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/closecom",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
"publisher": "Cal.com",
|
||||
"publisher": "Cal.com, Inc.",
|
||||
"email": "help@cal.com",
|
||||
"description": "Close is the inside sales CRM of choice for startups and SMBs. Make more calls, send more emails and close more deals starting today.",
|
||||
"__createdUsingCli": true
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"slug": "cron",
|
||||
"type": "cron_other",
|
||||
"logo": "logo.png",
|
||||
"url": "https://cal.com/apps/cron",
|
||||
"url": "https://github.com/miguelnietoa",
|
||||
"variant": "other",
|
||||
"categories": ["calendar"],
|
||||
"publisher": "miguelnietoa",
|
||||
"publisher": "Miguel Nieto",
|
||||
"email": "support@cal.com",
|
||||
"description": "Cron is the next-generation calendar for professionals and teams.",
|
||||
"isTemplate": false,
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
"slug": "eightxeight",
|
||||
"type": "eightxeight_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/eightxeight",
|
||||
"url": "https://github.com/shivamklr",
|
||||
"variant": "conferencing",
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"publisher": "shivamklr",
|
||||
"categories": ["video"],
|
||||
"publisher": "Shivam Kalra",
|
||||
"email": "shivamkalra98@gmail.com",
|
||||
"appData": {
|
||||
"location": {
|
||||
@@ -25,4 +23,4 @@
|
||||
"__createdUsingCli": true,
|
||||
"__template": "event-type-location-video-static",
|
||||
"dirName": "eightxeight"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dirName": "exchangecalendar",
|
||||
"type": "exchange_calendar",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/exchange",
|
||||
"url": "https://cal.com",
|
||||
"variant": "calendar",
|
||||
"categories": ["calendar"],
|
||||
"publisher": "Cal.com",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"slug": "facetime",
|
||||
"type": "facetime_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/facetime",
|
||||
"url": "https://github.com/Mythie",
|
||||
"variant": "conferencing",
|
||||
"categories": ["video"],
|
||||
"publisher": "Mythie",
|
||||
"publisher": "Lucas Smith",
|
||||
"email": "help@cal.com",
|
||||
"description": "Facetime makes it super simple for collaborating teams to jump on a video call.",
|
||||
"__createdUsingCli": true,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "fathom",
|
||||
"type": "fathom_analytics",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/fathom",
|
||||
"url": "https://cal.com",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"tag": {
|
||||
"scripts": [
|
||||
{
|
||||
"content": "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-{TRACKING_ID}');"
|
||||
"content": "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','{TRACKING_ID}');"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod.merge(
|
||||
z.object({
|
||||
trackingId: z.string(),
|
||||
trackingId: z.string().transform((val) => {
|
||||
let trackingId = val.trim();
|
||||
// Ensure that trackingId is transformed if needed to begin with "GTM-" always
|
||||
trackingId = !trackingId.startsWith("GTM-") ? `GTM-${trackingId}` : trackingId;
|
||||
return trackingId;
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"slug": "metapixel",
|
||||
"type": "metapixel_analytics",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://developers.facebook.com/docs/metapixel/",
|
||||
"url": "https://github.com/regexyl",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
"publisher": "regexyl",
|
||||
"publisher": "Regina Liu",
|
||||
"email": "info@regexyl.com",
|
||||
"description": "Add Meta Pixel to your bookings page to measure, optimize and build audiences for your ad campaigns.",
|
||||
"extendsFeature": "EventType",
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
"slug": "mirotalk",
|
||||
"type": "mirotalk_video",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/mirotalk",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "conferencing",
|
||||
"categories": [
|
||||
"video"
|
||||
],
|
||||
"categories": ["video"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
"email": "support@cal.com",
|
||||
"appData": {
|
||||
@@ -25,4 +23,4 @@
|
||||
"__createdUsingCli": true,
|
||||
"__template": "event-type-location-video-static",
|
||||
"dirName": "mirotalk"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "n8n",
|
||||
"type": "n8n_automation",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/n8n",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "automation",
|
||||
"categories": ["automation"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "plausible",
|
||||
"type": "plausible_analytics",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/plausible",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "analytics",
|
||||
"categories": ["analytics"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "qr_code",
|
||||
"type": "qr_code_other",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/qr_code",
|
||||
"url": "https://cal.com/",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
"extendsFeature": "EventType",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"slug": "rainbow",
|
||||
"type": "rainbow_web3",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/rainbow",
|
||||
"url": "https://github.com/hexcowboy",
|
||||
"variant": "web3",
|
||||
"categories": ["web3"],
|
||||
"extendsFeature": "EventType",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"slug": "raycast",
|
||||
"type": "raycast_other",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://cal.com/apps/raycast",
|
||||
"url": "https://github.com/eluce2",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
"publisher": "eluce2",
|
||||
"publisher": "Eric Luce",
|
||||
"email": "info@restlessmindstech.com",
|
||||
"description": "Quickly share your Cal.com meeting links with Raycast",
|
||||
"__createdUsingCli": true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user