Compare commits

..
73 Commits
Author SHA1 Message Date
Keith Williams 75d5576e17 v3.8.2
release.yml / release (push) Canceled after 0s
2024-02-20 21:41:13 -03:00
Keith Williams 0a6dde4fcc chore: Add extra logging for no available users error (#13791)
* chore: Add extra logging for no available users error

* Changed warn to info
2024-02-20 23:51:31 +00:00
Omar López 73b5839284 fix: og images avatars (#13790)
* fix og images avatars

* Update OgImages.tsx

* More fixes

* Update getPublicEvent.ts

* Update HeadSeo.tsx
2024-02-20 23:04:59 +00:00
Peer Richelsen 1a42b94b13 feat: added linears google calendar app (#13789) 2024-02-20 20:08:08 +00:00
Erik acb910f680 v3.8.1
release.yml / release (push) Canceled after 0s
2024-02-20 15:20:09 -03:00
Crowdin Bot 38e857f9a9 New Crowdin translations by Github Action 2024-02-20 15:30:02 +00:00
Crowdin Bot b8149dee36 New Crowdin translations by Github Action 2024-02-20 15:25:22 +00:00
Crowdin Bot f583ef40f2 New Crowdin translations by Github Action 2024-02-20 15:19:54 +00:00
Keith WilliamsandOmar López 71574488c5 fix: No available users found (#13738)
* fix: No available users found

* Update getUserAvailability.ts

* Update getUserAvailability.ts

* Update handleNewBooking.ts

* Reusing the parsed dates throughout the full stack

* Fixed typo

* Added unit tests for stringToDayjs

* Fixing types

* Reverted the getSchedule utc change since that endpoint takes in UTC formatted dates but with a given timezone

* chore: Add test replicating 'no available users' error

* Test is passing

* Name clarifications

* Test clean up

* Update packages/features/bookings/lib/handleNewBooking.ts

Co-authored-by: Omar López <zomars@me.com>

* Update handleNewBooking.ts

* Fixed issue where date overrides ending at midnight don't allow bookings

* Removed comment

---------

Co-authored-by: Omar López <zomars@me.com>
2024-02-20 12:15:05 -03:00
Omar López 008e0e43a2 chore: Drop deprecated password column (#13698) 2024-02-20 14:52:51 +00:00
Crowdin Bot da22eda662 New Crowdin translations by Github Action 2024-02-20 10:27:49 +00:00
sean-brydonandUdit Takkar f94d696885 fix: text-overflow (#13775)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-20 10:23:49 +00:00
Crowdin Bot 9e84eb1b2c New Crowdin translations by Github Action 2024-02-19 21:01:08 +00:00
zomars 4c549811a1 chore: Add explainer to RawHtml 2024-02-19 13:57:01 -07:00
Hariom Balhara 05502f0cfb fix: Old username link on org user's profile. (#13753) 2024-02-19 19:09:11 +00:00
mokkin e436b8e06b fix: correct German translation (#13742) 2024-02-19 19:07:18 +00:00
Crowdin Bot 8146cf8c93 New Crowdin translations by Github Action 2024-02-19 16:55:08 +00:00
Henrik KleeandKeith Williams c05f595ef0 reimplement #12681 (#13639)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-19 13:51:09 -03:00
Crowdin Bot 0a795f5363 New Crowdin translations by Github Action 2024-02-16 17:53:19 +00:00
5b50a469b2 fix: double bookings on seated event (#13406)
* fix: double bookings on seated event

* test: correcting handleSeats.test.ts

fix: seat rescheduling

* Add test for booking w/o bookingUid in req

* Add back `bookingUid`

* add 'reqBookingUid' parameter

* Add `reqBookingUid`

* Fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-02-16 12:48:57 -05:00
Alex van Andel c08365f09c feat: Implement orgs/teams logoUrls (#13699)
* feat: Implement orgs/teams logoUrls

* Use getAvatarUrl in team/[slug]

* Extract uploadLogo to standalone file for reuse

* Allow removing the team/organisation logo

* fix: API build fail due to server-only, removed
2024-02-16 10:35:53 -03:00
Shyam Raghuwanshi 477e102388 feat: Split-large-code-file-functions (#13592)
* Split large code file functions

* move the files from root into lib
2024-02-16 10:32:00 -03:00
Udit Takkar e1b10b4ec5 fix: update email flaky test (#13727) 2024-02-16 10:29:08 -03:00
Hariom BalharaandUdit Takkar ff6db3c572 fix: team invite signup not working (#13717)
* Fix team invite signup not working

* Add unit test

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-16 12:50:07 +00:00
Crowdin Bot 4fa3fdef97 New Crowdin translations by Github Action 2024-02-16 12:35:41 +00:00
Hariom Balhara f29f6632e8 fix: Managed children event listing for organization members (#13722)
* Fetch event-types by userId for childEvents and filter out those events that dont belong to the user

* Add test
2024-02-16 09:32:05 -03:00
Crowdin Bot 36747988e6 New Crowdin translations by Github Action 2024-02-16 11:41:38 +00:00
Morgan 3dd312b88b chore: booker web wrapper (#13676)
* chore: booker web wrapper

* fixup! chore: booker web wrapper

* fixup! fixup! chore: booker web wrapper

* fixup! fixup! fixup! chore: booker web wrapper

* fixup! fixup! fixup! fixup! chore: booker web wrapper

* fix: CreateANewOrganizationForm verify dialog

* fix: EventBooker not rerendering when it should

* fix: init booker store properly

* fixup! fix: init booker store properly

* fix: useMemo EventBooker

* fix: remove unnecessary file
2024-02-16 11:38:12 +00:00
Crowdin Bot 3c9e9329b0 New Crowdin translations by Github Action 2024-02-16 10:58:45 +00:00
Udit Takkar 8617f43a8e chore: add toast and tooltip (#13724)
* chore: add toast and tooltop

* chore: update text
2024-02-16 10:54:31 +00:00
Crowdin Bot 5f0eb652b0 New Crowdin translations by Github Action 2024-02-16 08:35:42 +00:00
JackandUdit Takkar 26d7301885 fix: [CAL-3141] (+1) badge not aligned (#13718)
* Fixes [CAL-3141] (+1) badge not aligned

* fix: centering

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-16 08:32:46 +00:00
Crowdin Bot ffc4d22ff1 New Crowdin translations by Github Action 2024-02-15 22:47:32 +00:00
ee286a600f refactor: Only send dirty fields for event type update call (#13292)
* init

* typefix maybe

* fix type

* fix e2e

* Extract delete dialog to its own function to optimise rerender

* wip

* Delete isEqual

* adds eslint tsignore comment

* only update changed fields

* fix type err

* --WIP

* fix type err isLoading—>isPending

* isLoading—>isPending

* post conflict resoution fix

* shouldDirty minimumBookingNotice

* adds shouldDirty to setValues

* reset form to ensure updated default values

* shouldDirty changes and fixed deep check of dirty

* --WIP

* should dirty recurring

* shouldDirty team tab

* multiple-fixes

* adding console log to test something in e2e

* shouldDirty apps tab

* fixes managed event type children association

* fixed managed events update

* dirty managed fix

* fixes some onChange versus setValue and periodCountCalendarDays

* Seeing different behaviour explicitly unwrapping formState

* only mutate if at least 1 field dirty

* fix type error

* bunch of fixes

* --WIP

* update only dirtyFields for Managed Events existing children

* Fixes update old managed event type tests --WIP

* Fix unit tests

A bunch of unit test fixes which started failing
due to update fields only requiring actual updated
fields. some other small fixes also included

* type fix

* remove unnecessary comment

* Adds dirtyField to instant event

* fix: add shouldDirty

* fixes availability and team tabs, and disable save button unless dirty

* fix for hosts and bookerLayout

* add shouldDirty when resetting hosts after changing schedulingType

* add missing shouldDirty to useEventTypeDestinationCalendarEmail

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-15 22:44:20 +00:00
Udit Takkar 72c493ee21 chore: add instruction for #12821 (#13713)
release.yml / release (push) Canceled after 0s
2024-02-16 01:22:12 +05:30
Alex van Andel 5bd0428d18 v3.8.0
release.yml / release (push) Canceled after 0s
2024-02-15 15:54:47 +00:00
c7089b9db6 feat: store event-type filters to localStorage (#13407)
* Updated useTypedQuery to store values to localstorage

* removed the redundant function params

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-02-15 10:03:42 -05:00
Morgan 81ad00a816 fix: safely delete userPassword (#13705)
* fix: safely delete userPassword

* fixup! fix: safely delete userPassword

* fixup! Merge branch 'main' into fix-safely-delete-userpassword
2024-02-15 13:19:31 +00:00
Udit TakkarandMorgan b5730da56d fix: password reset when provider is changed (#13706)
* fix: password reset when provider is changed

* chore: remove log

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-02-15 13:19:11 +00:00
Zeeshan BhatiandJoe Au-Yeung 9455d14ad5 feat: Add Google Profile Photo when connecting Google Calendar (#13627)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-15 11:51:24 +00:00
LAVIN RAJ MOHANandUdit Takkar a056160404 fix(fix/null-safety):#13673 (#13674)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-15 07:17:19 +00:00
Omar López 6d53cd84f8 v3.7.19
release.yml / release (push) Canceled after 0s
2024-02-14 14:19:56 -07:00
Omar López 30cb8f745d revert: Revert UserPassword (#13680) (#13685)
* Revert "revert: UserPassword (#13680)"

This reverts commit 0a68c6d048.

* fix: avoid updates for possible missing passwords
2024-02-14 16:53:27 +00:00
bafa3c97ac fix: Organizations Admin improvements (#13656)
* Admin improvements

* chore: improve spacing

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-14 10:34:45 -03:00
Alex van Andel bb48ccbad9 v3.7.18
release.yml / release (push) Canceled after 0s
2024-02-14 12:42:33 +00:00
Alex van Andel 0a68c6d048 revert: UserPassword (#13680) 2024-02-14 12:36:29 +00:00
Peer RichelsenandHariom bb9f8e5d38 fix: dont link to org url inside embeds (#13668)
* dont link to org url inside embeds

* Add comment

* Fix type issue

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-02-14 11:31:24 +05:30
Carina WollendorferandCarinaWolli c99bb190f1 fix: route always selects 'custom' after saving an event redirect (#13665)
* fix to not always switch to custom on fresh load

* type fix

* type fix

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-13 22:08:03 +00:00
Omar López f0d2a45fdf v3.7.17
release.yml / release (push) Canceled after 0s
2024-02-13 14:58:51 -07:00
zomars e5f9315ee6 fix: faulty redirect 2024-02-13 12:45:21 -07:00
zomars f7782b2080 fix: hotfix failed migration 2024-02-13 12:16:38 -07:00
Omar López e1f2dbab84 fix: amend user password migration (#13662) 2024-02-13 18:40:50 +00:00
Adam Azzam c2edbbcea7 Update SettingsToggle.tsx (#13660) 2024-02-13 22:54:27 +05:30
c7e78cc181 fix: AI build (#13657)
* fix: AI build

* Updated zod to 3.22.4

* yarn.lock

* Updated yarn.lock to prevent next/tw downgrade

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-02-13 16:10:08 +00:00
7ba1557d1d fix: use destination calendar email (#12821)
* fix: use destination calendar email

* fix: success page

* feat: add primary email in destination calendar

* fix: set destination bug

* chore: fix tests

* test: add test for different email

* wip: save progress

* feat: add switch and email in booking

* fix: use userPrimaryEmail

* chore: fix type errr

* chore: update buileder

* fix: type err in advanced tab

* fix: unit tests

* Update apps/web/public/static/locales/en/common.json

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

* fix: cancel, request reschedule, paymeny

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-02-13 10:05:00 -05:00
Suyash SrivastavaandPeer Richelsen d5b45b757a chore: Removed Skiff from the App Store (#13650)
* rule for Prisma that includes using only "true" and not a field selector

* Removed Skiff from the app store

* Apply suggestions from code review

* Delete packages/eslint-plugin/src/rules/disallow-prisma-include-only-true-not-field-selector.ts

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
2024-02-13 11:13:12 +00:00
bb3b262bd7 fix: Add location to ics file (#13646)
* Add location to ics file

* Add tests

* fix: type err

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-13 16:35:04 +05:30
d4f41e803a fix: Collective Events: Duration parameter in link not working (#13523)
* set the selectedDuration in store to duration passed in query

* Pass durationConfig prop to Booker component in /team/[slig]/[type] pages

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-02-13 12:23:27 +05:30
74eafa59e1 fix: loading animation of update buttons (#13131)
* fixed loading animation of update buttons

* Update BookerLayoutSelector.tsx

* fix: update button animation in settings appearance page

* Update appearance.tsx

* Update appearance.tsx

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-13 06:32:21 +00:00
Udit Takkarandzomars e6cd016cad fix: usage of CAL_URL (#13326)
Co-authored-by: zomars <zomars@me.com>
2024-02-13 06:03:45 +00:00
Omar López 7b0d5959cb refactor: migrates user password to it's own table (#13628) 2024-02-12 14:13:21 -07:00
Alex van Andel 7702ded85e v3.7.16
release.yml / release (push) Canceled after 0s
2024-02-12 18:31:59 +00:00
Alex van Andel 5acb3e8f97 fix: Missing date ranges when specifying a specific slot time (#13645) 2024-02-12 18:25:49 +00:00
sean-brydon 41046aaf27 fix: use org logo for sub teams (#13632)
* fix: use org logo for sub teams

* chore: remove unneeded type
2024-02-12 12:59:25 -03:00
Hariom Balhara 7cffb30daa fix: Errors due to namespacing of embed (#13637) 2024-02-12 07:46:08 -07:00
Alex van Andel 26bacb6c8e fix: Regenerated lock file 2024-02-12 13:38:13 +00:00
Crowdin Bot 01b5c1f11e New Crowdin translations by Github Action 2024-02-11 14:32:05 +00:00
Anik Dhabal Babu f5ba04599b fix: unable to setup localy (#13634) 2024-02-11 14:28:40 +00:00
Brace Sproul 994e602c8d refactor: Bump LangChain to 0.1 & update other LangChain imports (#13622) 2024-02-10 19:30:42 +00:00
Carina WollendorferandCarinaWolli a776c72d48 fix: improvements for variables in routing-from routes (#13624)
* add variable description

* type fixes

* type fixes

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-10 19:28:59 +00:00
sean-brydon 6adb5b413d fix: troubleshooter scroll (#13631) 2024-02-10 19:26:21 +00:00
alannnc b183a5779a fix: oncancel modal action week layout (#13625) 2024-02-09 23:02:05 +00:00
alannnc 16c1cf7b23 fix ooo section to use settings layout (#13623) 2024-02-09 19:57:08 -03:00
228 changed files with 4250 additions and 1584 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ CALCOM_LICENSE_KEY=
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
# Needed to run migrations while using a connection pooler like PgBouncer
# Use the same one as DATABASE_URL if you're not using a connection pooler
DATABASE_DIRECT_URL=
DATABASE_DIRECT_URL="postgresql://postgres:@localhost:5450/calendso"
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
INSIGHTS_DATABASE_URL=
+4 -2
View File
@@ -5,12 +5,14 @@
"author": "Cal.com Inc.",
"dependencies": {
"@calcom/prisma": "*",
"@langchain/core": "^0.1.26",
"@langchain/openai": "^0.0.14",
"@t3-oss/env-nextjs": "^0.6.1",
"langchain": "^0.0.131",
"langchain": "^0.1.17",
"mailparser": "^3.6.5",
"next": "^13.5.4",
"supports-color": "8.1.1",
"zod": "^3.22.2"
"zod": "^3.22.4"
},
"devDependencies": {
"@types/mailparser": "^3.4.0"
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import type { UserList } from "~/src/types/user";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "~/src/env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,5 +1,5 @@
import { ChatOpenAI } from "@langchain/openai";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { env } from "../env.mjs";
import createBookingIfAvailable from "../tools/createBooking";
+1 -1
View File
@@ -41,6 +41,6 @@
"typescript": "^4.9.4",
"tzdata": "^1.0.30",
"uuid": "^8.3.2",
"zod": "^3.22.2"
"zod": "^3.22.4"
}
}
@@ -1,4 +1,4 @@
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
export default function getCalLink(eventType: {
team?: { slug: string | null } | null;
@@ -6,7 +6,7 @@ export default function getCalLink(eventType: {
users?: { username: string | null }[];
slug: string;
}) {
return `${CAL_URL}/${
return `${WEBSITE_URL}/${
eventType?.team
? `team/${eventType?.team?.slug}`
: eventType?.owner
+1
View File
@@ -24,6 +24,7 @@
"@types/node": "16.9.1",
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@types/swagger-ui-react": "^4.18.3",
"typescript": "^4.9.4"
}
}
+2 -1
View File
@@ -1,9 +1,10 @@
import "highlight.js/styles/default.css";
import { type AppProps } from "next/app";
import "swagger-ui-react/swagger-ui.css";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
+1 -4
View File
@@ -1,9 +1,8 @@
import dynamic from "next/dynamic";
import type { SwaggerUI } from "swagger-ui-react";
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
ssr: false,
});
@@ -17,8 +16,6 @@ export default function APIDocs() {
requestSnippets={requestSnippets}
plugins={[SnippedGenerator]}
tryItOutEnabled={true}
syntaxHighlight={true}
enableCORS={false} // Doesn't seem to work either
docExpansion="list"
filter={true}
/>
@@ -18,7 +18,8 @@ export const generateMetadata = async ({
}) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
const { entity, user, slug, booking } = props;
const { eventData, user, slug, booking } = props;
const entity = eventData.entity;
const { trpc } = await import("@calcom/trpc");
const { data: event } = trpc.viewer.public.event.useQuery(
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
@@ -16,7 +16,8 @@ export const generateMetadata = async ({
}) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
const { entity, user, slug, booking } = props;
const { eventData, user, slug, booking } = props;
const entity = eventData.entity;
const { trpc } = await import("@calcom/trpc");
const { data: event } = trpc.viewer.public.event.useQuery(
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
@@ -2,7 +2,7 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { AppFrontendPayload } from "@calcom/types/App";
@@ -125,7 +125,7 @@ export const InstallAppButtonChild = ({
StartIcon={(props) => (
<Avatar
alt={team.logo || ""}
imageSrc={team.logo || `${CAL_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
imageSrc={team.logo || `${WEBAPP_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
size="sm"
{...props}
/>
@@ -1,3 +1,4 @@
import { InfoIcon } from "lucide-react";
import dynamic from "next/dynamic";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
@@ -18,8 +19,7 @@ import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import type { EditableSchema } from "@calcom/features/form-builder/schema";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
import { APP_NAME, IS_VISUAL_REGRESSION_TESTING, WEBSITE_URL } from "@calcom/lib/constants";
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { Prisma } from "@calcom/prisma/client";
@@ -31,6 +31,7 @@ import {
Label,
SettingsToggle,
showToast,
Switch,
TextField,
Tooltip,
} from "@calcom/ui";
@@ -45,10 +46,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const { data: user } = trpc.viewer.me.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const [useEventTypeDestinationCalendarEmail, setUseEventTypeDestinationCalendarEmail] = useState(
formMethods.getValues("useEventTypeDestinationCalendarEmail")
);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const bookingFields: Prisma.JsonObject = {};
@@ -57,7 +60,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const selectedThemeIsDark =
user?.theme === "dark" ||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
formMethods.getValues().bookingFields.forEach(({ name }) => {
bookingFields[name] = `${name} input`;
});
@@ -74,7 +76,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const [requiresConfirmation, setRequiresConfirmation] = useState(
formMethods.getValues("requiresConfirmation")
);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
const placeholderHashedLink = `${WEBSITE_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const noShowFeeEnabled =
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
@@ -99,7 +101,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
};
}
return field;
})
}),
{ shouldDirty: true }
);
};
@@ -117,6 +120,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
const closeEventNameTip = () => setShowEventNameTip(false);
const displayDestinationCalendarSelector =
!!connectedCalendarsQuery.data?.connectedCalendars.length && !team;
return (
<div className="flex flex-col space-y-4">
@@ -126,7 +131,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
* This will fallback to each user selected destination calendar.
*/}
<div className="border-subtle space-y-6 rounded-lg border p-6">
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
{displayDestinationCalendarSelector && (
<div className="flex flex-col">
<div className="flex justify-between">
<div>
@@ -172,6 +177,27 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
/>
</div>
{displayDestinationCalendarSelector && (
<div className="w-full">
<Switch
tooltip={t("reconnect_calendar_to_use")}
label={
<>
{t("display_add_to_calendar_organizer")}
<InfoIcon className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md" />
</>
}
checked={useEventTypeDestinationCalendarEmail}
onCheckedChange={(val) => {
setUseEventTypeDestinationCalendarEmail(val);
formMethods.setValue("useEventTypeDestinationCalendarEmail", val, { shouldDirty: true });
if (val) {
showToast(t("reconnect_calendar_to_use"), "warning");
}
}}
/>
</div>
)}
</div>
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} isOuterBorder={true} />
@@ -303,7 +329,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
description={t("private_link_description", { appName: APP_NAME })}
checked={hashedLinkVisible}
onCheckedChange={(e) => {
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
formMethods.setValue("hashedLink", e ? hashedUrl : undefined, { shouldDirty: true });
setHashedLinkVisible(e);
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -367,10 +393,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
// Enabling seats will disable guests and requiring confirmation until fully supported
if (e) {
toggleGuests(false);
formMethods.setValue("requiresConfirmation", false);
formMethods.setValue("requiresConfirmation", false, { shouldDirty: true });
setRequiresConfirmation(false);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2, {
shouldDirty: true,
});
} else {
formMethods.setValue("seatsPerTimeSlot", null);
toggleGuests(true);
@@ -490,7 +518,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
{showEventNameTip && (
<CustomEventTypeModal
close={closeEventNameTip}
setValue={(val: string) => formMethods.setValue("eventName", val)}
setValue={(val: string) => formMethods.setValue("eventName", val, { shouldDirty: true })}
defaultValue={formMethods.getValues("eventName")}
placeHolder={eventNamePlaceholder}
event={eventNameObject}
@@ -31,10 +31,14 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const allAppsData = formMethods.watch("metadata")?.apps || {};
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
formMethods.setValue("metadata", {
...formMethods.getValues("metadata"),
apps: _allAppsData,
});
formMethods.setValue(
"metadata",
{
...formMethods.getValues("metadata"),
apps: _allAppsData,
},
{ shouldDirty: true }
);
};
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
@@ -230,7 +230,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
);
setValue("availability", value);
setValue("availability", value, { shouldDirty: true });
},
[data]
);
@@ -238,7 +238,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
useEffect(() => {
if (!availabilityValue?.value) return;
setValue("schedule", availabilityValue.value);
setValue("schedule", availabilityValue.value, { shouldDirty: true });
}, [availabilityValue, setValue]);
return (
@@ -260,7 +260,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
isSearchable={false}
onChange={(selected) => {
field.onChange(selected?.value || null);
if (selected?.value) setValue("availability", selected);
if (selected?.value) setValue("availability", selected, { shouldDirty: true });
}}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
value={availabilityValue}
@@ -298,7 +298,7 @@ const UseCommonScheduleSettingsToggle = ({ eventType }: { eventType: EventTypeSe
onCheckedChange={(checked) => {
onChange(!checked);
if (!checked) {
setValue("schedule", null);
setValue("schedule", null, { shouldDirty: true });
}
}}
title={t("choose_common_schedule_team_event")}
@@ -61,7 +61,8 @@ const MinimumBookingNoticeInput = React.forwardRef<
minimumBookingNoticeDisplayValues.type,
"minutes",
minimumBookingNoticeDisplayValues.value
)
),
{ shouldDirty: true }
);
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
@@ -230,7 +231,9 @@ export const EventLimitsTab = () => {
<Select
isSearchable={false}
onChange={(val) => {
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null, {
shouldDirty: true,
});
}}
defaultValue={
slotIntervalOptions.find(
@@ -258,11 +261,15 @@ export const EventLimitsTab = () => {
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("bookingLimits", {
PER_DAY: 1,
});
formMethods.setValue(
"bookingLimits",
{
PER_DAY: 1,
},
{ shouldDirty: true }
);
} else {
formMethods.setValue("bookingLimits", {});
formMethods.setValue("bookingLimits", {}, { shouldDirty: true });
}
}}
switchContainerClassName={classNames(
@@ -279,7 +286,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="onlyShowFirstAvailableSlot"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = value;
return (
<SettingsToggle
@@ -289,7 +296,7 @@ export const EventLimitsTab = () => {
description={t("limit_booking_only_first_slot_description")}
checked={isChecked}
onCheckedChange={(active) => {
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
onChange(active ?? false);
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
@@ -301,7 +308,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="durationLimits"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0;
return (
<SettingsToggle
@@ -317,11 +324,11 @@ export const EventLimitsTab = () => {
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("durationLimits", {
onChange({
PER_DAY: 60,
});
} else {
formMethods.setValue("durationLimits", {});
onChange({});
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -338,7 +345,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="periodType"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = value && value !== "UNLIMITED";
return (
@@ -353,11 +360,16 @@ export const EventLimitsTab = () => {
title={t("limit_future_bookings")}
description={t("limit_future_bookings_description")}
checked={isChecked}
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
onCheckedChange={(bool) =>
// formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED", { shouldDirty: true })
onChange(bool ? "ROLLING" : "UNLIMITED")
}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<RadioGroup.Root
value={watchPeriodType}
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
onValueChange={(val) =>
formMethods.setValue("periodType", val as PeriodType, { shouldDirty: true })
}>
{PERIOD_TYPES.map((period) => {
if (period.type === "UNLIMITED") return null;
return (
@@ -388,17 +400,21 @@ export const EventLimitsTab = () => {
options={optionsPeriod}
isSearchable={false}
onChange={(opt) =>
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
formMethods.setValue(
"periodCountCalendarDays",
opt?.value === 1 ? true : false,
{ shouldDirty: true }
)
}
name="periodCoundCalendarDays"
value={optionsPeriod.find((opt) => {
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0);
})}
defaultValue={optionsPeriod.find(
(opt) =>
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0)
)}
/>
</div>
@@ -407,12 +423,12 @@ export const EventLimitsTab = () => {
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
<Controller
name="periodDates"
render={() => (
render={({ field: { onChange } }) => (
<DateRangePicker
startDate={formMethods.getValues("periodDates").startDate}
endDate={formMethods.getValues("periodDates").endDate}
onDatesChange={({ startDate, endDate }) => {
formMethods.setValue("periodDates", {
onChange({
startDate,
endDate,
});
@@ -446,7 +462,7 @@ export const EventLimitsTab = () => {
onCheckedChange={(active) => {
setOffsetToggle(active);
if (!active) {
formMethods.setValue("offsetStart", 0);
formMethods.setValue("offsetStart", 0, { shouldDirty: true });
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -577,11 +593,16 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
);
if (!rest || !currentKeys.length) return;
//currentDurationLimits is always defined so can be casted
// @ts-expect-error FIXME Fix these typings
setValue(propertyName, {
...watchIntervalLimits,
[rest.value]: defaultLimit,
});
setValue(
propertyName,
// @ts-expect-error FIXME Fix these typings
{
...watchIntervalLimits,
[rest.value]: defaultLimit,
},
{ shouldDirty: true }
);
};
return (
@@ -611,7 +632,7 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
)}
onLimitChange={(intervalLimitKey, val) =>
// @ts-expect-error FIXME Fix these typings
setValue(`${propertyName}.${intervalLimitKey}`, val)
setValue(`${propertyName}.${intervalLimitKey}`, val, { shouldDirty: true })
}
onDelete={(intervalLimitKey) => {
const current = currentIntervalLimits;
+14 -13
View File
@@ -11,7 +11,7 @@ import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { slugify } from "@calcom/lib/slugify";
@@ -83,7 +83,7 @@ const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
return mounted ? (
<Editor
getText={() => md.render(formMethods.getValues("description") || "")}
setText={(value: string) => formMethods.setValue("description", turndown(value))}
setText={(value: string) => formMethods.setValue("description", turndown(value), { shouldDirty: true })}
excludedToolbarItems={["blockType"]}
placeholder={t("quick_video_meeting")}
editable={isEditable}
@@ -401,7 +401,7 @@ export const EventSetupTab = (
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
Change it{" "}
<Link
href={`${CAL_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
href={`${WEBAPP_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
className="underline">
here.
</Link>{" "}
@@ -412,7 +412,7 @@ export const EventSetupTab = (
{isChildrenManagedEventType && !locationAvailable && locationDetails && (
<p className="pl-1 text-sm leading-none text-red-600">
{t("app_not_connected", { appName: locationDetails.name })}{" "}
<a className="underline" href={`${CAL_URL}/apps/${locationDetails.slug}`}>
<a className="underline" href={`${WEBAPP_URL}/apps/${locationDetails.slug}`}>
{t("connect_now")}
</a>
</p>
@@ -444,9 +444,10 @@ export const EventSetupTab = (
const lengthLockedProps = shouldLockDisableProps("length");
const descriptionLockedProps = shouldLockDisableProps("description");
const urlPrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<div>
@@ -511,16 +512,16 @@ export const EventSetupTab = (
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
if (newOptions.length > 0) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
} else {
setDefaultDuration(null);
}
}
if (newOptions.length === 1 && defaultDuration === null) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
}
formMethods.setValue("metadata.multipleDuration", values);
formMethods.setValue("metadata.multipleDuration", values, { shouldDirty: true });
}}
/>
</div>
@@ -541,7 +542,7 @@ export const EventSetupTab = (
setDefaultDuration(
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
);
if (option) formMethods.setValue("length", option.value);
if (option) formMethods.setValue("length", option.value, { shouldDirty: true });
}}
/>
</div>
@@ -570,12 +571,12 @@ export const EventSetupTab = (
setMultipleDuration(undefined);
setSelectedMultipleDuration([]);
setDefaultDuration(null);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length);
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
formMethods.setValue("length", eventType.length, { shouldDirty: true });
} else {
setMultipleDuration([]);
formMethods.setValue("metadata.multipleDuration", []);
formMethods.setValue("length", 0);
formMethods.setValue("metadata.multipleDuration", [], { shouldDirty: true });
formMethods.setValue("length", 0, { shouldDirty: true });
}
}}
/>
+18 -13
View File
@@ -127,7 +127,7 @@ const AssignAllTeamMembers = ({
labelClassName="mt-0.5 font-normal"
checked={assignAllTeamMembers}
onCheckedChange={(active) => {
setValue("assignAllTeamMembers", active);
setValue("assignAllTeamMembers", active, { shouldDirty: true });
setAssignAllTeamMembers(active);
if (active) {
onActive();
@@ -250,7 +250,8 @@ const FixedHosts = ({
isFixed: true,
userId: parseInt(teamMember.value, 10),
priority: 2,
}))
})),
{ shouldDirty: true }
)
}
/>
@@ -269,7 +270,7 @@ const FixedHosts = ({
const rrHosts = getValues("hosts")
.filter((host) => !host.isFixed)
.sort((a, b) => (b.priority ?? 2) - (a.priority ?? 2));
setValue("hosts", rrHosts);
setValue("hosts", rrHosts, { shouldDirty: true });
}
setIsDisabled(checked);
}}
@@ -290,7 +291,8 @@ const FixedHosts = ({
isFixed: true,
userId: parseInt(teamMember.value, 10),
priority: 2,
}))
})),
{ shouldDirty: true }
)
}
/>
@@ -332,7 +334,7 @@ const AddMembersWithSwitch = ({
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
onActive={onActive}
onInactive={() => setValue("hosts", [])}
onInactive={() => setValue("hosts", [], { shouldDirty: true })}
/>
</div>
) : (
@@ -395,7 +397,8 @@ const RoundRobinHosts = ({
userId: parseInt(teamMember.value, 10),
priority: 2,
}))
.sort((a, b) => b.priority - a.priority)
.sort((a, b) => b.priority - a.priority),
{ shouldDirty: true }
)
}
/>
@@ -420,7 +423,7 @@ const ChildrenEventTypes = ({
<AssignAllTeamMembers
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
onActive={() => setValue("children", childrenEventTypeOptions)}
onActive={() => setValue("children", childrenEventTypeOptions, { shouldDirty: true })}
/>
{!assignAllTeamMembers ? (
<Controller<FormValues>
@@ -449,7 +452,7 @@ const Hosts = ({
const { t } = useLocale();
const {
control,
resetField,
setValue,
getValues,
formState: { submitCount },
} = useFormContext<FormValues>();
@@ -469,10 +472,12 @@ const Hosts = ({
initialValue.current = { hosts: getValues("hosts"), schedulingType, submitCount };
return;
}
resetField("hosts", {
defaultValue: initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
});
}, [schedulingType, resetField, getValues, submitCount]);
setValue(
"hosts",
initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
{ shouldDirty: true }
);
}, [schedulingType, setValue, getValues, submitCount]);
return (
<Controller<FormValues>
@@ -586,7 +591,7 @@ export const EventTeamTab = ({
className="w-full"
onChange={(val) => {
onChange(val?.value);
setValue("assignAllTeamMembers", false);
setValue("assignAllTeamMembers", false, { shouldDirty: true });
setAssignAllTeamMembers(false);
}}
/>
@@ -14,6 +14,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import type { DialogProps } from "@calcom/ui";
import {
Button,
ButtonGroup,
@@ -133,6 +134,60 @@ function getNavigation({
];
}
function DeleteDialog({
isManagedEvent,
eventTypeId,
open,
onOpenChange,
}: { isManagedEvent: string; eventTypeId: number } & Pick<DialogProps, "open" | "onOpenChange">) {
const utils = trpc.useContext();
const { t } = useLocale();
const router = useRouter();
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
onSuccess: async () => {
await utils.viewer.eventTypes.invalidate();
showToast(t("event_type_deleted_successfully"), "success");
router.push("/event-types");
onOpenChange?.(false);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
onOpenChange?.(false);
} else if (err instanceof TRPCClientError) {
showToast(err.message, "error");
}
},
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t(`delete${isManagedEvent}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => {
e.preventDefault();
deleteMutation.mutate({ id: eventTypeId });
}}>
<p className="mt-5">
<Trans
i18nKey={`delete${isManagedEvent}_event_type_description`}
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
<ul>
<li>Members assigned to this event type will also have their event types deleted.</li>
<li>Anyone who they&apos;ve shared their link with will no longer be able to book using it.</li>
</ul>
</Trans>
</p>
</ConfirmationDialogContent>
</Dialog>
);
}
function EventTypeSingleLayout({
children,
eventType,
@@ -149,9 +204,7 @@ function EventTypeSingleLayout({
bookerUrl,
activeWebhooksNumber,
}: Props) {
const utils = trpc.useContext();
const { t } = useLocale();
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const hasPermsToDelete =
@@ -160,24 +213,6 @@ function EventTypeSingleLayout({
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
isUserOrganizationAdmin;
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
onSuccess: async () => {
await utils.viewer.eventTypes.invalidate();
showToast(t("event_type_deleted_successfully"), "success");
router.push("/event-types");
setDeleteDialogOpen(false);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
setDeleteDialogOpen(false);
} else if (err instanceof TRPCClientError) {
showToast(err.message, "error");
}
},
});
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
formMethods.getValues(),
t("locked_fields_admin_description"),
@@ -308,7 +343,7 @@ function EventTypeSingleLayout({
id="hiddenSwitch"
checked={!formMethods.watch("hidden")}
onCheckedChange={(e) => {
formMethods.setValue("hidden", !e);
formMethods.setValue("hidden", !e, { shouldDirty: true });
}}
/>
</div>
@@ -352,7 +387,7 @@ function EventTypeSingleLayout({
StartIcon={Code}
color="secondary"
variant="icon"
namespace={eventType.slug}
namespace=""
tooltip={t("embed")}
tooltipSide="bottom"
tooltipOffset={4}
@@ -424,7 +459,7 @@ function EventTypeSingleLayout({
id="hiddenSwitch"
checked={!formMethods.watch("hidden")}
onCheckedChange={(e) => {
formMethods.setValue("hidden", !e);
formMethods.setValue("hidden", !e, { shouldDirty: true });
}}
/>
</div>
@@ -435,6 +470,7 @@ function EventTypeSingleLayout({
className="ml-4 lg:ml-0"
type="submit"
loading={isUpdateMutationLoading}
disabled={!formMethods.formState.isDirty}
data-testid="update-eventtype"
form="event-type-form">
{t("save")}
@@ -467,31 +503,13 @@ function EventTypeSingleLayout({
</div>
</div>
</Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t(`delete${isManagedEvent}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => {
e.preventDefault();
deleteMutation.mutate({ id: formMethods.getValues("id") });
}}>
<p className="mt-5">
<Trans
i18nKey={`delete${isManagedEvent}_event_type_description`}
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
<ul>
<li>Members assigned to this event type will also have their event types deleted.</li>
<li>
Anyone who they&apos;ve shared their link with will no longer be able to book using it.
</li>
</ul>
</Trans>
</p>
</ConfirmationDialogContent>
</Dialog>
<DeleteDialog
eventTypeId={eventType.id}
isManagedEvent={isManagedEvent}
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
/>
<EventTypeEmbedDialog />
</Shell>
);
@@ -81,10 +81,10 @@ export default function InstantEventController({
data-testid="instant-event-check"
onCheckedChange={(e) => {
if (!e) {
formMethods.setValue("isInstantEvent", false);
formMethods.setValue("isInstantEvent", false, { shouldDirty: true });
setInstantEventState(false);
} else {
formMethods.setValue("isInstantEvent", true);
formMethods.setValue("isInstantEvent", true, { shouldDirty: true });
setInstantEventState(true);
}
}}>
@@ -66,7 +66,7 @@ export default function RecurringEventController({
data-testid="recurring-event-check"
onCheckedChange={(e) => {
if (!e) {
formMethods.setValue("recurringEvent", null);
formMethods.setValue("recurringEvent", null, { shouldDirty: true });
setRecurringEventState(null);
} else {
const newVal = eventType.recurringEvent || {
@@ -74,7 +74,7 @@ export default function RecurringEventController({
count: 12,
freq: Frequency.WEEKLY,
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}
}}>
@@ -95,7 +95,7 @@ export default function RecurringEventController({
...recurringEventState,
interval: parseInt(event?.target.value),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -110,7 +110,7 @@ export default function RecurringEventController({
...recurringEventState,
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -129,7 +129,7 @@ export default function RecurringEventController({
...recurringEventState,
count: parseInt(event?.target.value),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -37,7 +37,7 @@ export default function RequiresConfirmationController({
useEffect(() => {
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, { shouldDirty: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [requiresConfirmation]);
@@ -82,7 +82,7 @@ export default function RequiresConfirmationController({
checked={requiresConfirmation}
LockedIcon={requiresConfirmationLockedProps.LockedIcon}
onCheckedChange={(val) => {
formMethods.setValue("requiresConfirmation", val);
formMethods.setValue("requiresConfirmation", val, { shouldDirty: true });
onRequiresConfirmation(val);
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -96,16 +96,19 @@ export default function RequiresConfirmationController({
}
onValueChange={(val) => {
if (val === "always") {
formMethods.setValue("requiresConfirmation", true);
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
onRequiresConfirmation(true);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, {
shouldDirty: true,
});
setRequiresConfirmationSetup(undefined);
} else if (val === "notice") {
formMethods.setValue("requiresConfirmation", true);
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
onRequiresConfirmation(true);
formMethods.setValue(
"metadata.requiresConfirmationThreshold",
requiresConfirmationSetup || defaultRequiresConfirmationSetup
requiresConfirmationSetup || defaultRequiresConfirmationSetup,
{ shouldDirty: true }
);
}
}}>
@@ -146,7 +149,8 @@ export default function RequiresConfirmationController({
});
formMethods.setValue(
"metadata.requiresConfirmationThreshold.time",
val
val,
{ shouldDirty: true }
);
}}
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
@@ -171,7 +175,8 @@ export default function RequiresConfirmationController({
});
formMethods.setValue(
"metadata.requiresConfirmationThreshold.unit",
opt?.value as UnitTypeLongPlural
opt?.value as UnitTypeLongPlural,
{ shouldDirty: true }
);
}}
defaultValue={defaultValue}
@@ -4,7 +4,7 @@ import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import { WEBSITE_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
@@ -45,7 +45,7 @@ export const UsernameAvailabilityField = ({
const usernamePrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<Controller
@@ -11,10 +11,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!session) {
return {
redirect: {
redirect: {
permanent: false,
destination: "/auth/login",
},
permanent: false,
destination: "/auth/login",
},
};
}
@@ -2,7 +2,7 @@ import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
@@ -82,12 +82,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
entity: eventData.entity,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
eventData: {
entity: eventData.entity,
length: eventData.length,
metadata: eventData.metadata,
},
booking,
away: false,
user: teamSlug,
@@ -82,6 +82,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
cancellationReason: true,
responses: true,
rejectionReason: true,
userPrimaryEmail: true,
user: {
select: {
id: true,
@@ -203,7 +203,7 @@ export default function Success(props: PageProps) {
duration: calculatedDuration,
organizer: {
name: users[0].name || "Nameless",
email: users[0].email || "Email-less",
email: bookingInfo?.userPrimaryEmail || users[0].email || "Email-less",
timeZone: users[0].timeZone,
},
confirmed: !needsConfirmation,
@@ -450,7 +450,9 @@ export default function Success(props: PageProps) {
</span>
<Badge variant="blue">{t("Host")}</Badge>
</div>
<p className="text-default">{bookingInfo.user.email}</p>
<p className="text-default">
{bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email}
</p>
</div>
)}
{bookingInfo?.attendees.map((attendee) => (
@@ -18,7 +18,7 @@ import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
import Shell from "@calcom/features/shell/Shell";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
@@ -407,7 +407,7 @@ export const EventTypeList = ({
const embedLink = `${group.profile.slug}/${type.slug}`;
const calLink = `${bookerUrl}/${embedLink}`;
const isPrivateURLEnabled = type.hashedLink?.link;
const placeholderHashedLink = `${CAL_URL}/d/${type.hashedLink?.link}/${type.slug}`;
const placeholderHashedLink = `${WEBSITE_URL}/d/${type.hashedLink?.link}/${type.slug}`;
const isManagedEventType = type.schedulingType === SchedulingType.MANAGED;
const isChildrenManagedEventType =
type.metadata?.managedEventConfig !== undefined && type.schedulingType !== SchedulingType.MANAGED;
@@ -425,7 +425,7 @@ export const EventTypeList = ({
<MemoizedItem type={type} group={group} readOnly={readOnly} />
<div className="mt-4 hidden sm:mt-0 sm:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
{type.team && !isManagedEventType && (
{!!type.teamId && !isManagedEventType && (
<UserAvatarGroup
className="relative right-3"
size="sm"
@@ -539,7 +539,7 @@ export const EventTypeList = ({
{!isManagedEventType && (
<DropdownMenuItem className="outline-none">
<EventTypeEmbedButton
namespace={type.slug}
namespace=""
as={DropdownItem}
type="button"
StartIcon={Code}
@@ -15,7 +15,7 @@ import { validateCustomEventName } from "@calcom/core/event";
import type { EventLocationType } from "@calcom/core/location";
import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import { validateIntervalLimitOrder } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
@@ -119,7 +119,7 @@ export type FormValues = {
schedule: number | null;
periodType: PeriodType;
periodDays: number;
periodCountCalendarDays: "1" | "0";
periodCountCalendarDays: boolean;
periodDates: { startDate: Date; endDate: Date };
seatsPerTimeSlot: number | null;
seatsShowAttendees: boolean | null;
@@ -148,6 +148,7 @@ export type FormValues = {
multipleDurationEnabled: boolean;
users: EventTypeSetup["users"];
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -191,14 +192,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
onSuccess: async () => {
formMethods.setValue(
"children",
formMethods.getValues().children.map((child) => ({
...child,
created: true,
}))
);
formMethods.setValue("assignAllTeamMembers", formMethods.getValues("assignAllTeamMembers") || false);
const currentValues = formMethods.getValues();
currentValues.children = currentValues.children.map((child) => ({
...child,
created: true,
}));
currentValues.assignAllTeamMembers = currentValues.assignAllTeamMembers || false;
// Reset the form with these values as new default values to ensure the correct comparison for dirtyFields eval
formMethods.reset(currentValues);
showToast(t("event_type_updated_successfully", { eventTypeTitle: eventType.title }), "success");
},
async onSettled() {
@@ -252,7 +256,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
eventType.bookingFields.forEach(({ name }) => {
bookingFields[name] = name;
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const defaultValues: any = useMemo(() => {
return {
@@ -289,7 +292,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
offsetStart: eventType.offsetStart,
bookingFields: eventType.bookingFields,
periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
periodCountCalendarDays: eventType.periodCountCalendarDays ? true : false,
schedulingType: eventType.schedulingType,
requiresConfirmation: eventType.requiresConfirmation,
slotInterval: eventType.slotInterval,
@@ -298,6 +301,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
hosts: eventType.hosts,
successRedirectUrl: eventType.successRedirectUrl || "",
users: eventType.users,
useEventTypeDestinationCalendarEmail: eventType.useEventTypeDestinationCalendarEmail,
children: eventType.children.map((ch) => ({
...ch,
created: true,
@@ -314,7 +318,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
assignAllTeamMembers: eventType.assignAllTeamMembers,
};
}, [eventType, periodDates, metadata]);
const formMethods = useForm<FormValues>({
defaultValues,
resolver: zodResolver(
@@ -403,14 +406,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
.passthrough()
),
});
useEffect(() => {
if (!formMethods.formState.isDirty) {
//TODO: What's the best way to sync the form with backend
formMethods.setValue("bookingFields", defaultValues.bookingFields);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValues]);
const {
formState: { isDirty: isFormDirty, dirtyFields },
} = formMethods;
const appsMetadata = formMethods.getValues("metadata")?.apps;
const availability = formMethods.watch("availability");
@@ -423,7 +421,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
).length;
}
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
const permalink = `${WEBSITE_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
const tabMap = {
@@ -451,8 +449,100 @@ const EventTypePage = (props: EventTypeSetupProps) => {
),
webhooks: <EventWebhooksTab eventType={eventType} />,
} as const;
const isObject = <T,>(value: T): boolean => {
return value !== null && typeof value === "object" && !Array.isArray(value);
};
const isArray = <T,>(value: T): boolean => {
return Array.isArray(value);
};
const isFieldDirty = (fieldName: keyof FormValues) => {
// If the field itself is directly marked as dirty
if (dirtyFields[fieldName] === true) {
return true;
}
// Check if the field is an object or an array
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fieldValue: any = getNestedField(dirtyFields, fieldName);
if (isObject(fieldValue)) {
for (const key in fieldValue) {
if (fieldValue[key] === true) {
return true;
}
if (isObject(fieldValue[key]) || isArray(fieldValue[key])) {
const nestedFieldName = `${fieldName}.${key}` as keyof FormValues;
// Recursive call for nested objects or arrays
if (isFieldDirty(nestedFieldName)) {
return true;
}
}
}
}
if (isArray(fieldValue)) {
for (const element of fieldValue) {
// If element is an object, check each property of the object
if (isObject(element)) {
for (const key in element) {
if (element[key] === true) {
return true;
}
if (isObject(element[key]) || isArray(element[key])) {
const nestedFieldName = `${fieldName}.${key}` as keyof FormValues;
// Recursive call for nested objects or arrays within each element
if (isFieldDirty(nestedFieldName)) {
return true;
}
}
}
} else if (element === true) {
return true;
}
}
}
return false;
};
const getNestedField = (obj: typeof dirtyFields, path: string) => {
const keys = path.split(".");
let current = obj;
for (let i = 0; i < keys.length; i++) {
// @ts-expect-error /—— currentKey could be any deeply nested fields thanks to recursion
const currentKey = current[keys[i]];
if (currentKey === undefined) return undefined;
current = currentKey;
}
return current;
};
const getDirtyFields = (values: FormValues): Partial<FormValues> => {
if (!isFormDirty) {
return {};
}
const updatedFields: Partial<FormValues> = {};
Object.keys(dirtyFields).forEach((key) => {
const typedKey = key as keyof typeof dirtyFields;
updatedFields[typedKey] = undefined;
const isDirty = isFieldDirty(typedKey);
if (isDirty) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
updatedFields[typedKey] = values[typedKey];
}
});
return updatedFields;
};
const handleSubmit = async (values: FormValues) => {
const { children } = values;
const dirtyValues = getDirtyFields(values);
const dirtyFieldExists = Object.keys(dirtyValues).length !== 0;
const {
periodDates,
periodCountCalendarDays,
@@ -468,7 +558,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
locations,
metadata,
customInputs,
children,
assignAllTeamMembers,
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -481,8 +570,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
multipleDurationEnabled,
length,
...input
} = values;
} = dirtyValues;
if (!Number(length)) throw new Error(t("event_setup_length_error"));
if (bookingLimits) {
@@ -502,8 +590,12 @@ const EventTypePage = (props: EventTypeSetupProps) => {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
} else {
if (!length && !metadata?.multipleDuration?.includes(length)) {
throw new Error(t("event_setup_multiple_duration_default_error"));
// if length is unchanged, we skip this check
if (length !== undefined) {
if (!length && !metadata?.multipleDuration?.includes(length)) {
//This would work but it leaves the potential of this check being useless. Need to check against length and not eventType.length, but length can be undefined
throw new Error(t("event_setup_multiple_duration_default_error"));
}
}
}
}
@@ -519,14 +611,14 @@ const EventTypePage = (props: EventTypeSetupProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, users, scheduleName, ...rest } = input;
updateMutation.mutate({
const payload = {
...rest,
length,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
periodEndDate: periodDates.endDate,
periodCountCalendarDays: periodCountCalendarDays === "1",
periodStartDate: periodDates?.startDate,
periodEndDate: periodDates?.endDate,
periodCountCalendarDays,
id: eventType.id,
beforeEventBuffer,
afterEventBuffer,
@@ -540,7 +632,19 @@ const EventTypePage = (props: EventTypeSetupProps) => {
customInputs,
children,
assignAllTeamMembers,
});
};
// Filter out undefined values
const filteredPayload = Object.entries(payload).reduce((acc, [key, value]) => {
if (value !== undefined) {
// @ts-expect-error Element implicitly has any type
acc[key] = value;
}
return acc;
}, {});
if (dirtyFieldExists) {
updateMutation.mutate({ ...filteredPayload, id: eventType.id });
}
};
const [slugExistsChildrenDialogOpen, setSlugExistsChildrenDialogOpen] = useState<ChildrenEventType[]>([]);
@@ -593,6 +697,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
form={formMethods}
id="event-type-form"
handleSubmit={async (values) => {
const { children } = values;
const dirtyValues = getDirtyFields(values);
const dirtyFieldExists = Object.keys(dirtyValues).length !== 0;
const {
periodDates,
periodCountCalendarDays,
@@ -615,9 +722,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
multipleDurationEnabled,
length,
...input
} = values;
} = dirtyValues;
if (!Number(length)) throw new Error(t("event_setup_length_error"));
if (length && !Number(length)) throw new Error(t("event_setup_length_error"));
if (bookingLimits) {
const isValid = validateIntervalLimitOrder(bookingLimits);
@@ -636,8 +743,11 @@ const EventTypePage = (props: EventTypeSetupProps) => {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
} else {
if (!length && !metadata?.multipleDuration?.includes(length)) {
throw new Error(t("event_setup_multiple_duration_default_error"));
if (length !== undefined) {
if (!length && !metadata?.multipleDuration?.includes(length)) {
//This would work but it leaves the potential of this check being useless. Need to check against length and not eventType.length, but length can be undefined
throw new Error(t("event_setup_multiple_duration_default_error"));
}
}
}
}
@@ -649,14 +759,15 @@ const EventTypePage = (props: EventTypeSetupProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, users, scheduleName, ...rest } = input;
updateMutation.mutate({
const payload = {
...rest,
children,
length,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
periodEndDate: periodDates.endDate,
periodCountCalendarDays: periodCountCalendarDays === "1",
periodStartDate: periodDates?.startDate,
periodEndDate: periodDates?.endDate,
periodCountCalendarDays,
id: eventType.id,
beforeEventBuffer,
afterEventBuffer,
@@ -668,7 +779,19 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsShowAvailabilityCount,
metadata,
customInputs,
});
};
// Filter out undefined values
const filteredPayload = Object.entries(payload).reduce((acc, [key, value]) => {
if (value !== undefined) {
// @ts-expect-error Element implicitly has any type
acc[key] = value;
}
return acc;
}, {});
if (dirtyFieldExists) {
updateMutation.mutate({ ...filteredPayload, id: eventType.id });
}
}}>
<div ref={animationParentRef}>{tabMap[tabName]}</div>
</Form>
@@ -7,6 +7,7 @@ import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import logger from "@calcom/lib/logger";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { safeStringify } from "@calcom/lib/safeStringify";
@@ -206,7 +207,10 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
const profile = {
name: user.name || user.username || "",
image: user.avatar,
image: getUserAvatarUrl({
...user,
profile: user.profile,
}),
theme: user.theme,
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
avatarUrl: user.avatarUrl,
@@ -155,7 +155,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
<Link
prefetch={false}
href={{
pathname: `/${user.username}/${type.slug}`,
pathname: `/${user.profile.username}/${type.slug}`,
query,
}}
passHref
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.7.15",
"version": "3.8.2",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -130,7 +130,7 @@
"tailwindcss-radix": "^2.6.0",
"turndown": "^7.1.1",
"uuid": "^8.3.2",
"zod": "^3.22.2"
"zod": "^3.22.4"
},
"devDependencies": {
"@babel/core": "^7.19.6",
+9 -5
View File
@@ -38,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const oldPassword = req.body.oldPassword;
const newPassword = req.body.newPassword;
const currentPassword = user.password;
const currentPassword = user.password?.hash;
if (!currentPassword) {
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
}
@@ -53,12 +53,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const hashedPassword = await hashPassword(newPassword);
await prisma.user.update({
await prisma.userPassword.upsert({
where: {
id: user.id,
userId: user.id,
},
data: {
password: hashedPassword,
create: {
hash: hashedPassword,
userId: user.id,
},
update: {
hash: hashedPassword,
},
});
+6 -1
View File
@@ -41,7 +41,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
email: maybeRequest.email,
},
data: {
password: hashedPassword,
password: {
upsert: {
create: { hash: hashedPassword },
update: { hash: hashedPassword },
},
},
emailVerified: new Date(),
},
});
+1 -1
View File
@@ -41,7 +41,7 @@ async function handler(req: NextApiRequest) {
data: {
username,
email: userEmail,
password: hashedPassword,
password: { create: { hash: hashedPassword } },
role: "ADMIN",
name: parsedQuery.data.full_name,
emailVerified: new Date(),
@@ -23,13 +23,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500).json({ error: ErrorCode.InternalServerError });
}
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
if (!user) {
console.error(`Session references user that no longer exists.`);
return res.status(401).json({ message: "Not authenticated" });
}
if (!user.password && user.identityProvider === IdentityProvider.CAL) {
if (!user.password?.hash && user.identityProvider === IdentityProvider.CAL) {
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
}
@@ -37,8 +37,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.json({ message: "Two factor disabled" });
}
if (user.password && user.identityProvider === IdentityProvider.CAL) {
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
if (user.password?.hash && user.identityProvider === IdentityProvider.CAL) {
const isCorrectPassword = await verifyPassword(req.body.password, user.password.hash);
if (!isCorrectPassword) {
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
}
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500).json({ error: ErrorCode.InternalServerError });
}
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
if (!user) {
console.error(`Session references user that no longer exists.`);
return res.status(401).json({ message: "Not authenticated" });
@@ -35,7 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(400).json({ error: ErrorCode.ThirdPartyIdentityProviderEnabled });
}
if (!user.password) {
if (!user.password?.hash) {
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
}
@@ -48,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500).json({ error: ErrorCode.InternalServerError });
}
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
const isCorrectPassword = await verifyPassword(req.body.password, user.password.hash);
if (!isCorrectPassword) {
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
}
+1 -1
View File
@@ -119,7 +119,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
endTime: booking.endTime.toISOString(),
organizer: {
id: user.id,
email: user.email,
email: booking?.userPrimaryEmail ?? user.email,
name,
timeZone: user.timeZone,
language: { translate: tOrganizer, locale: user.locale ?? "en" },
+1 -1
View File
@@ -230,7 +230,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: booking.user?.email || "Email-less",
email: booking?.userPrimaryEmail || booking.user?.email || "Email-less",
name: booking.user?.name || "Nameless",
timeZone: booking.user?.timeZone || "Europe/London",
language: { translate: t, locale: booking?.user?.locale ?? "en" },
@@ -47,7 +47,7 @@ export default function Apps({ apps }: PageProps) {
<div className="mb-16">
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
{apps
.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
?.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
.map((app) => {
return <AppCard key={app.slug} app={app} />;
})}
@@ -139,6 +139,9 @@ const AppearanceView = ({
showToast(t("error_updating_settings"), "error");
}
},
onSettled: async () => {
await utils.viewer.me.invalidate();
},
});
return (
@@ -184,6 +187,7 @@ const AppearanceView = ({
</div>
<SectionBottomActions className="mb-6" align="end">
<Button
loading={mutation.isPending}
disabled={isUserThemeSubmitting || !isUserThemeDirty}
type="submit"
data-testid="update-theme-btn"
@@ -210,6 +214,7 @@ const AppearanceView = ({
title={t("bookerlayout_user_settings_title")}
description={t("bookerlayout_user_settings_description")}
isDisabled={isBookerLayoutFormSubmitting || !isBookerLayoutFormDirty}
isLoading={mutation.isPending}
/>
</Form>
@@ -295,6 +300,7 @@ const AppearanceView = ({
</div>
<SectionBottomActions align="end">
<Button
loading={mutation.isPending}
disabled={isBrandColorsFormSubmitting || !isBrandColorsFormDirty}
color="primary"
type="submit">
@@ -4,7 +4,7 @@ import { Controller, useForm, useFormState } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import Shell from "@calcom/features/shell/Shell";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -244,14 +244,13 @@ const OutOfOfficePage = () => {
return (
<>
<Meta title={t("out_of_office")} description={t("out_of_office_description")} borderInShellHeader />
<Shell withoutMain={false} withoutSeo>
<OutOfOfficeSection />
<OutOfOfficeEntriesList />
</Shell>
<OutOfOfficeSection />
<OutOfOfficeEntriesList />
</>
);
};
OutOfOfficePage.getLayout = getLayout;
OutOfOfficePage.PageWrapper = PageWrapper;
export default OutOfOfficePage;
+8 -11
View File
@@ -13,7 +13,7 @@ import { useEffect } from "react";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getOrgAvatarUrl, getTeamAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -157,6 +157,11 @@ function TeamPage({
</div>
);
const profileImageSrc =
isValidOrgDomain || team.metadata?.isOrganization
? getOrgAvatarUrl({ slug: currentOrgDomain, logoUrl: team.logoUrl })
: getTeamAvatarUrl({ slug: team.slug, logoUrl: team.logoUrl, organizationId: team.parent?.id });
return (
<>
<HeadSeo
@@ -166,22 +171,14 @@ function TeamPage({
title: markdownStrippedBio,
profile: {
name: `${team.name}`,
image: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`,
image: profileImageSrc,
},
}}
/>
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
<div className="mx-auto mb-8 max-w-3xl text-center">
<div className="relative">
<Avatar
alt={teamName}
imageSrc={
isValidOrgDomain
? `/org/${currentOrgDomain}/avatar.png`
: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`
}
size="lg"
/>
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
</div>
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
{team.parent && `${team.parent.name} `}
+25 -5
View File
@@ -1,5 +1,7 @@
"use client";
import { useSearchParams } from "next/navigation";
import { Booker } from "@calcom/atoms";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
@@ -14,6 +16,15 @@ export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export { getServerSideProps };
export const getMultipleDurationValue = (
multipleDurationConfig: number[] | undefined,
queryDuration: string | string[] | null | undefined,
defaultValue: number
) => {
if (!multipleDurationConfig) return null;
if (multipleDurationConfig.includes(Number(queryDuration))) return Number(queryDuration);
return defaultValue;
};
export default function Type({
slug,
user,
@@ -21,10 +32,11 @@ export default function Type({
away,
isEmbed,
isBrandingHidden,
entity,
duration,
eventData,
isInstantMeeting,
}: PageProps) {
const searchParams = useSearchParams();
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
<BookerSeo
@@ -33,7 +45,7 @@ export default function Type({
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
entity={entity}
entity={eventData.entity}
bookingData={booking}
/>
<Booker
@@ -44,8 +56,16 @@ export default function Type({
isInstantMeeting={isInstantMeeting}
hideBranding={isBrandingHidden}
isTeamEvent
entity={entity}
duration={duration}
entity={eventData.entity}
durationConfig={eventData.metadata?.multipleDuration}
/* TODO: Currently unused, evaluate it is needed-
* Possible alternative approach is to have onDurationChange.
*/
duration={getMultipleDurationValue(
eventData.metadata?.multipleDuration,
searchParams?.get("duration"),
eventData.length
)}
/>
</main>
);
@@ -86,7 +86,8 @@ testBothFutureAndLegacyRoutes.describe("Forgot password", async () => {
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
const updatedPassword = updatedUser.password!.hash;
expect(await verifyPassword(newPassword, updatedPassword)).toBeTruthy();
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
await page.goto(`/auth/forgot-password/${id}`);
+8 -4
View File
@@ -652,7 +652,6 @@ type SupportedTestWorkflows = PrismaType.WorkflowCreateInput;
type CustomUserOptsKeys =
| "username"
| "password"
| "completedOnboarding"
| "locale"
| "name"
@@ -669,6 +668,7 @@ type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & {
useExactUsername?: boolean;
roleInOrganization?: MembershipRole;
schedule?: Schedule;
password?: string | null;
};
// creates the actual user in the db.
@@ -690,7 +690,11 @@ const createUser = (
username: uname,
name: opts?.name,
email: opts?.email ?? `${uname}@example.com`,
password: hashPassword(uname),
password: {
create: {
hash: hashPassword(uname),
},
},
emailVerified: new Date(),
completedOnboarding: opts?.completedOnboarding ?? true,
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
@@ -791,7 +795,7 @@ async function confirmPendingPayment(page: Page) {
// login using a replay of an E2E routine.
export async function login(
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "email">> & { password?: string | null },
page: Page
) {
// get locators
@@ -812,7 +816,7 @@ export async function login(
}
export async function apiLogin(
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "email">> & { password: string | null },
page: Page
) {
const csrfToken = await page
+5 -1
View File
@@ -143,8 +143,12 @@ test.describe("Update Profile", () => {
const successLocator = await page.waitForSelector('[data-testId="toast-success"]');
expect(await successLocator.textContent()).toContain(email);
// After email verification is successfull. user is sent to /event-types
await page.waitForURL("/event-types");
await page.goto("/settings/my-account/profile");
const emailInputUpdated = page.getByTestId("profile-form-email");
const emailInputUpdated = await page.getByTestId("profile-form-email");
expect(await emailInputUpdated.inputValue()).toEqual(email);
});
test("Can update a users email (verification disabled)", async ({ page, users, prisma, features }) => {
+166 -1
View File
@@ -13,10 +13,17 @@
"reset_password_subject": "{{appName}}: Anleitung zum Zurücksetzen des Passworts",
"verify_email_subject": "{{appName}}: Bestätigen Sie Ihr Konto",
"check_your_email": "Schauen Sie in Ihrem E-Mail-Postfach nach",
"old_email_address": "Alte E-Mail-Adresse",
"new_email_address": "Neue E-Mail-Adresse",
"verify_email_page_body": "Wir haben eine E-Mail an {{email}} gesendet. Es ist wichtig, Ihre E-Mail-Adresse zu bestätigen, um die Zustellbarkeit für E-Mails und Kalenderbenachrichtigungen von {{appName}} zu gewährleisten.",
"verify_email_banner_body": "Bestätigen Sie Ihre E-Mail-Adresse, um die Zustellbarkeit von E-Mails und Kalenderbenachrichtigungen zu gewährleisten",
"verify_email_email_header": "Bestätigen Sie Ihre E-Mail-Adresse",
"verify_email_email_button": "E-Mail bestätigen",
"verify_email_change_description": "Sie haben vor kurzem angefordert, die E-Mail-Adresse zu ändern, die Sie verwenden, um sich bei Ihrem {{appName}} Konto anzumelden. Bitte klicken Sie auf die Schaltfläche unten, um Ihre neue E-Mail-Adresse zu bestätigen.",
"verify_email_change_success_toast": "Ihre E-Mail wurde auf {{email}} aktualisiert",
"verify_email_change_failure_toast": "Fehler beim Aktualisieren der E-Mail.",
"change_of_email": "Bestätigen Sie Ihre neue {{appName}} E-Mail-Adresse",
"change_of_email_toast": "Wir haben einen Bestätigungslink an {{email}} gesendet. Wir werden Ihre E-Mail-Adresse aktualisieren, sobald Sie auf diesen Link klicken.",
"copy_somewhere_safe": "Speichern Sie diesen API-Schlüssel an einem sicheren Ort. Sie können ihn nicht erneut ansehen.",
"verify_email_email_body": "Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie unten auf die Schaltfläche klicken.",
"verify_email_by_code_email_body": "Bitte bestätigen Sie Ihre E-Mail-Adresse mit dem unten stehenden Code.",
@@ -56,6 +63,17 @@
"a_refund_failed": "Erstattung fehlgeschlagen",
"awaiting_payment_subject": "Warte auf Zahlung: {{title}} am {{date}}",
"meeting_awaiting_payment": "Zahlung für den Termin noch offen",
"dark_theme_contrast_error": "Die Farbe des dunklen Designs hat die Kontrastprüfung nicht bestanden. Wir empfehlen Ihnen, diese Farbe zu ändern, damit Ihre Schaltflächen sichtbarer sind.",
"light_theme_contrast_error": "Die Farbe des hellen Designs hat die Kontrastprüfung nicht bestanden. Wir empfehlen Ihnen, diese Farbe zu ändern, damit Ihre Schaltflächen sichtbarer sind.",
"payment_not_created_error": "Zahlung konnte nicht erstellt werden",
"couldnt_charge_card_error": "Karte konnte nicht für die Zahlung belastet werden",
"no_available_users_found_error": "Keine verfügbaren Benutzer gefunden. Können Sie es mit einem anderen Zeitfenster versuchen?",
"request_body_end_time_internal_error": "Interner Fehler. Request-Body enthält keinen Endzeitpunkt",
"create_calendar_event_error": "Kalenderereignis konnte nicht im Kalender des Organisators erstellt werden",
"update_calendar_event_error": "Kalenderereignis konnte nicht aktualisiert werden.",
"delete_calendar_event_error": "Kalenderereignis konnte nicht gelöscht werden.",
"already_signed_up_for_this_booking_error": "Sie sind bereits für diese Buchung registriert.",
"hosts_unavailable_for_booking": "Einige der Veranstalter sind nicht für die Buchung verfügbar.",
"help": "Hilfe",
"price": "Preis",
"paid": "Bezahlt",
@@ -101,6 +119,7 @@
"requested_to_reschedule_subject_attendee": "Aktion erforderlich - Neuplanung: Bitte buchen Sie eine neue Zeit für „{{eventType}}“ mit {{name}}",
"hi_user_name": "Hallo, {{name}}",
"ics_event_title": "{{eventType}} mit {{name}}",
"please_book_a_time_sometime_later": "Ups, wir konnten Sie dieses Mal nicht verbinden. Bitte planen Sie stattdessen einen zukünftigen Anruf",
"new_event_subject": "Neuer Termin: {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "{{entryPoint}} beitreten",
"notes": "Notizen",
@@ -117,15 +136,20 @@
"meeting_id": "Termin-ID",
"meeting_password": "Termin-Passwort",
"meeting_url": "Termin-URL",
"meeting_url_not_found": "Meeting-URL nicht gefunden",
"token_not_found": "Token nicht gefunden",
"some_other_host_already_accepted_the_meeting": "Ein anderer Veranstalter hat das Meeting bereits akzeptiert. Möchten Sie trotzdem beitreten? <1>Mit dem Meeting fortfahren</1>",
"meeting_request_rejected": "Ihre Terminanfrage wurde abgelehnt",
"rejected_event_type_with_organizer": "Abgelehnt: {{eventType}} mit {{organizer}} auf {{date}}",
"hi": "Hi",
"join_team": "Team beitreten",
"manage_this_team": "Dieses Team verwalten",
"team_info": "Informationen zum Team",
"join_meeting": "Meeting beitreten",
"request_another_invitation_email": "Falls Sie {{toEmail}} lieber nicht als Ihre {{appName}}-E-Mail verwenden möchten oder bereits über ein {{appName}}-Konto verfügen, fordern Sie bitte eine weitere Einladung zu dieser E-Mail an.",
"you_have_been_invited": "Sie wurden eingeladen, dem Team {{teamName}} beizutreten",
"user_invited_you": "{{user}} hat Sie eingeladen, dem {{entity}} {{team}} auf {{appName}} beizutreten",
"user_invited_you_to_subteam": "{{user}} hat Sie eingeladen, dem Team {{team}} der Organisation {{parentTeamName}} auf {{appName}} beizutreten",
"hidden_team_member_title": "Sie sind in diesem Team ausgeblendet",
"hidden_team_member_message": "Ihre Teammitgliedschaft ist nicht bezahlt, entweder upgraden Sie Ihren Account auf Pro oder lassen den Team-Organisator wissen, dass er für Ihren Platz im Team bezahlen kann.",
"hidden_team_owner_message": "Sie benötigen einen Pro-Account um die Teamfunktionen zu verweden. Sie werden dem Team nicht angezeigt, bis der Account upgegradet wurde.",
@@ -229,6 +253,7 @@
"reset_your_password": "Legen Sie Ihr neues Passwort mit den Anweisungen fest, die an Ihre E-Mail-Adresse gesendet wurden.",
"email_change": "Melden Sie sich mit Ihrer neuen E-Mail-Adresse und Ihrem Passwort wieder an.",
"create_your_account": "Erstellen Sie Ihr Konto",
"create_your_calcom_account": "Erstellen Sie Ihr Cal.com-Konto",
"sign_up": "Registrieren",
"youve_been_logged_out": "Sie wurden abgemeldet",
"hope_to_see_you_soon": "Wir hoffen, Sie bald wieder zu sehen!",
@@ -266,6 +291,9 @@
"nearly_there_instructions": "Zu guter Letzt: Ein Foto und eine Kurzbeschreibung zu Ihnen helfen, öfter gebucht zu werden, da Kunden dann genauer wissen, bei wem sie einen Termin buchen.",
"set_availability_instructions": "Definieren Sie Zeiträume, wenn Sie wiederkehrend verfügbar sind. Sie können später weitere Zeiträume erstellen und verschiedenen Kalendern zuordnen.",
"set_availability": "Verfügbarkeit festlegen",
"set_availbility_description": "Legen Sie Zeitpläne für die Zeiten fest, in denen Sie gebucht werden möchten.",
"share_a_link_or_embed": "Link oder Einbettung teilen",
"share_a_link_or_embed_description": "Teilen Sie Ihren {{appName}} Link oder betten Sie ihn auf Ihrer Website ein.",
"availability_settings": "Verfügbarkeitseinstellungen",
"continue_without_calendar": "Ohne Kalender fortfahren",
"continue_with": "Mit {{appName}} fortfahren",
@@ -281,6 +309,7 @@
"welcome_to_calcom": "Willkommen bei {{appName}}",
"welcome_instructions": "Wie ist Ihr Name und in welcher Zeitzone sind Sie? Dies kann später bearbeitet werden.",
"connect_caldav": "Mit CalDav-Server verbinden",
"connect_ics_feed": "Mit einem ICS-Feed verbinden",
"connect": "Verbinden",
"try_for_free": "Kostenlos testen",
"create_booking_link_with_calcom": "Erstellen Sie Ihren eigenen Buchungslink mit {{appName}}",
@@ -419,6 +448,7 @@
"browse_api_documentation": "Durchsuchen Sie unsere API Dokumentation",
"leverage_our_api": "Nutzen Sie unsere API für die volle Kontrolle und Anpassbarkeit.",
"create_webhook": "Webhook erstellen",
"instant_meeting": "Sofortiges Meeting erstellt",
"booking_cancelled": "Termin abgesagt",
"booking_rescheduled": "Termin verlegt",
"recording_ready": "Download-Link der Aufnahme ist nun verfügbar",
@@ -533,6 +563,7 @@
"enter_number_between_range": "Bitte geben Sie eine Zahl zwischen 1 und {{maxOccurences}} ein",
"email_address": "E-Mail Adresse",
"enter_valid_email": "Bitte geben Sie eine gültige Emaildresse ein",
"please_schedule_future_call": " Bitte planen Sie einen Anruf in der Zukunft ein, falls wir nicht innerhalb von {{seconds}} Sekunden verfügbar sind",
"location": "Ort",
"address": "Adresse",
"enter_address": "Geben Sie eine Adresse ein",
@@ -592,6 +623,7 @@
"new_member": "Neues Mitglied",
"invite": "Einladen",
"add_team_members": "Teammitglieder hinzufügen",
"add_org_members": "Mitglieder hinzufügen",
"add_team_members_description": "Lade andere ein, deinem Team beizutreten",
"add_team_member": "Teammitglied hinzufügen",
"invite_new_member": "Ein neues Mitglied einladen",
@@ -606,8 +638,9 @@
"hide_book_a_team_member_description": "Blendet die „Ein Teammitglied buchen“-Schaltflächen auf Ihren öffentlichen Seiten aus.",
"danger_zone": "Achtung",
"account_deletion_cannot_be_undone": "Vorsicht. Löschen eines Kontos kann nicht rückgängig gemacht werden.",
"team_deletion_cannot_be_undone": "Seien Sie vorsichtig. Das Löschen eines Teams kann nicht rückgängig gemacht werden",
"back": "Zurück",
"cancel": "Absagen",
"cancel": "Abbrechen",
"cancel_all_remaining": "Alle verbleibenden absagen",
"apply": "Anwenden",
"cancel_event": "Termin absagen",
@@ -656,6 +689,7 @@
"default_duration": "Standardlaufzeit",
"default_duration_no_options": "Bitte wählen Sie zuerst die verfügbaren Laufzeiten aus",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"minutes": "Minuten",
"round_robin": "Round Robin",
"round_robin_description": "Treffen zwischen mehreren Teammitgliedern durchwechseln.",
@@ -668,6 +702,7 @@
"add_members": "Mitglieder hinzufügen...",
"no_assigned_members": "Keine zugeordneten Mitglieder",
"assigned_to": "Zugeordnet zu",
"you_must_be_logged_in_to": "Sie müssen bei {{url}} angemeldet sein",
"start_assigning_members_above": "Mit der Zuordnung von Mitgliedern oben beginnen",
"locked_fields_admin_description": "Mitglieder werden dies nicht bearbeiten können",
"locked_fields_member_description": "Diese Option wurde vom Team-Admin gesperrt",
@@ -772,6 +807,7 @@
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Terminbestätigung",
"label": "Bezeichnung",
"placeholder": "Platzhalter",
"display_add_to_calendar_organizer": "„Zum Kalender hinzufügen“-E-Mail als Organisator anzeigen",
"type": "Typ",
"edit": "Bearbeiten",
"add_input": "Eingabe hinzufügen",
@@ -780,6 +816,9 @@
"requires_confirmation_description": "Die Buchung muss manuell bestätigt werden, bevor die Buchungsinformationen an die Integrationen weitergeleitet werden und eine Bestätigungsmail gesendet wird.",
"recurring_event": "Wiederkehrender Termin",
"recurring_event_description": "Personen können sich für wiederkehrende Termine anmelden",
"cannot_be_used_with_paid_event_types": "Es kann nicht mit bezahlten Ereignistypen verwendet werden",
"warning_payment_instant_meeting_event": "Softige Meetings werden von wiederkehrenden Ereignissen und Zahlungs-Apps noch nicht unterstützt",
"warning_instant_meeting_experimental": "Experimentell: „Sofortiges Meeting“-Ereignisse sind derzeit eine experimentelle Funktion.",
"starting": "Beginnen",
"disable_guests": "Gäste deaktivieren",
"disable_guests_description": "Das Hinzufügen zusätzlicher Gäste deaktivieren.",
@@ -788,6 +827,7 @@
"private_link_label": "Privater Link",
"private_link_hint": "Ihr privater Link wird nach jeder Nutzung neu generiert",
"copy_private_link": "Privaten Link kopieren",
"copy_private_link_to_event": "Privaten Link zum Ereignis kopieren",
"private_link_description": "Erzeugen Sie einen privaten Link, den Sie teilen können, ohne Ihren {{appName}}-Benutzername preiszugeben",
"invitees_can_schedule": "Teilnehmende können folgendes buchen",
"date_range": "Datumszeitraum",
@@ -847,6 +887,7 @@
"next_step": "Schritt überspringen",
"prev_step": "Vorheriger Schritt",
"install": "Installieren",
"start_paid_trial": "Kostenlose Testphase starten",
"installed": "Installiert",
"active_install_one": "{{count}} aktive Installation",
"active_install_other": "{{count}} aktive Installationen",
@@ -861,6 +902,7 @@
"toggle_calendars_conflict": "Wechseln Sie die Kalender in denen nach Konflikten gesucht werden sollen, um Doppelbuchungen zu vermeiden.",
"connect_additional_calendar": "Zusätzlichen Kalender verbinden",
"calendar_updated_successfully": "Kalender erfolgreich aktualisiert",
"check_here": "Hier auswählen",
"conferencing": "Videokonferenzen",
"calendar": "Kalender",
"payments": "Zahlungen",
@@ -1032,6 +1074,9 @@
"user_impersonation_heading": "Benutzeridentitätswechsel",
"user_impersonation_description": "Erlaubt es unserem Support-Team, sich vorübergehend mit Ihrem Konto anzumelden, um jegliche Probleme, die Sie gemeldet haben, schneller zu lösen.",
"team_impersonation_description": "Ermöglicht den Team-Administratoren, sich vorübergehend als Sie einzuloggen.",
"cal_signup_description": "Für Einzelpersonen kostenlos. Teampläne für kollaborative Funktionen.",
"make_org_private": "Organisation privat stellen",
"make_org_private_description": "Wenn diese Einstellung aktiv ist, können Ihre Organisationsmitglieder keine anderen Organisationsmitglieder sehen.",
"make_team_private": "Team privat stellen",
"make_team_private_description": "Wenn diese Einstellung aktiv ist, können deine Teammitglieder keine anderen Teammitglieder sehen.",
"you_cannot_see_team_members": "Sie können nicht alle Teammitglieder eines privaten Teams sehen.",
@@ -1091,6 +1136,7 @@
"developer_documentation": "Entwickler-Dokumentation",
"get_in_touch": "Kontakt aufnehmen",
"contact_support": "Support kontaktieren",
"premium_support": "Premium-Support",
"community_support": "Community Support",
"feedback": "Feedback",
"submitted_feedback": "Vielen Dank für Ihr Feedback!",
@@ -1254,6 +1300,7 @@
"default_calendar_selected": "Standardkalender",
"hide_from_profile": "Im Profil ausblenden",
"event_setup_tab_title": "Termin-Einrichtung",
"availability_not_found_in_schedule_error": "Keine Verfügbarkeit im Zeitplan gefunden",
"event_limit_tab_title": "Limits",
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
"event_advanced_tab_description": "Kalendereinstellungen & mehr...",
@@ -1274,6 +1321,7 @@
"do_this": "Mache dies",
"turn_off": "Abschalten",
"turn_on": "Einschalten",
"cancelled_bookings_cannot_be_rescheduled": "Stornierte Buchungen können nicht verschoben werden",
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
"personal_cal_url": "Meine persönliche {{appName}}-URL",
@@ -1297,6 +1345,7 @@
"customize_your_brand_colors": "Passen Sie Ihre eigene Markenfarbe auf Ihrer Buchungsseite an.",
"pro": "Pro",
"removes_cal_branding": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z. B. „Powered by {{appName}}.“",
"instant_meeting_with_title": "Sofortiges Meeting mit {{name}}",
"profile_picture": "Profilbild",
"upload": "Upload",
"add_profile_photo": "Profilbild hinzufügen",
@@ -1352,6 +1401,7 @@
"event_name_info": "Name des Ereignistyps",
"event_date_info": "Das Datum der Veranstaltung",
"event_time_info": "Die Startzeit des Termins",
"event_type_not_found": "Ereignistyp nicht gefunden",
"location_variable": "Ort",
"location_info": "Der Ort des Events",
"additional_notes_variable": "Zusätzliche Notizen",
@@ -1394,6 +1444,7 @@
"slot_length": "Zeitfensterlänge",
"booking_appearance": "Darstellung der Buchungsseite",
"appearance_team_description": "Einstellungen für die Buchungsdarstellung Ihres Teams verwalten",
"appearance_org_description": "Einstellungen für die Buchungsdarstellung Ihrer Organisation verwalten",
"only_owner_change": "Nur der Besitzer dieses Teams kann Änderungen an den Buchungen des Teams vornehmen ",
"team_disable_cal_branding_description": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z. B. „Powered by {{appName}}“",
"invited_by_team": "{{teamName}} hat Sie eingeladen, dem Team als {{role}} beizutreten",
@@ -1447,6 +1498,7 @@
"password_updated": "Passwort aktualisiert!",
"pending_payment": "Zahlung ausstehend",
"pending_invites": "Offene Einladungen",
"pending_organization_invites": "Ausstehende Organisationseinladungen",
"not_on_cal": "Nicht auf {{appName}}",
"no_calendar_installed": "Kein Kalender installiert",
"no_calendar_installed_description": "Sie haben noch keinen Ihrer Kalender verbunden",
@@ -1458,6 +1510,8 @@
"report_app": "App melden",
"limit_booking_frequency": "Buchungsfrequenz begrenzen",
"limit_booking_frequency_description": "Begrenzen, wie oft dieses Ereignis gebucht werden kann",
"limit_booking_only_first_slot": "Buchun auf den ersten Platz beschränken",
"limit_booking_only_first_slot_description": "Nur die Buchung des ersten Platzes am Tag erlauben",
"limit_total_booking_duration": "Gesamtbuchungsdauer beschränken",
"limit_total_booking_duration_description": "Begrenzt die Gesamtanzahl, die dieser Termin gebucht werden kann",
"add_limit": "Begrenzung hinzufügen",
@@ -1525,10 +1579,14 @@
"your_org_disbanded_successfully": "Ihre Organization wurde erfolgreich aufgelöst",
"error_creating_team": "Fehler beim Erstellen des Teams",
"you": "Sie",
"or_continue_with": "Oder fortfahren mit",
"resend_email": "E-Mail erneut senden",
"member_already_invited": "Mitglied wurde bereits eingeladen",
"already_in_use_error": "Benutzername wird bereits verwendet",
"enter_email_or_username": "E-Mail oder Benutzername eingeben",
"enter_email": "E-Mail-Adresse eingeben",
"enter_emails": "E-Mail-Adressen eingeben",
"too_many_invites": "Sie sind darauf beschränkt, maximal {{nbUsers}} Benutzer gleichzeitig einzuladen.",
"team_name_taken": "Dieser Name ist bereits vergeben",
"must_enter_team_name": "Team-Name muss eingegeben werden",
"team_url_required": "Team-URL muss eingegeben werden",
@@ -1608,6 +1666,7 @@
"individual": "Person",
"all_bookings_filter_label": "Alle Buchungen",
"all_users_filter_label": "Alle Benutzer",
"all_event_types_filter_label": "Alle Ereignistypen",
"your_bookings_filter_label": "Ihre Buchungen",
"meeting_url_variable": "Meeting-URL",
"meeting_url_info": "Die URL der Meeting-Konferenz",
@@ -1704,9 +1763,11 @@
"organizer_timezone": "Zeitzone des Veranstalters",
"email_user_cta": "Einladung anzeigen",
"email_no_user_invite_heading_team": "Sie wurden eingeladen, einem {{appName}}-Team beizutreten",
"email_no_user_invite_heading_subteam": "Sie wurden eingeladen, einem Team der Organisation {{parentTeamName}} beizutreten",
"email_no_user_invite_heading_org": "Sie wurden eingeladen, einer {{appName}}-Organisation beizutreten",
"email_no_user_invite_subheading": "{{invitedBy}} hat Sie eingeladen, dem Team in {{appName}} beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne ständiges hin und her zu planen.",
"email_user_invite_subheading_team": "{{invitedBy}} hat Sie eingeladen, dem Team „{{teamName}}“ beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne Hin und Her zu planen.",
"email_user_invite_subheading_subteam": "{{invitedBy}} hat Sie eingeladen, dem Team „{{teamName}}“ in seiner bzw. ihrer Organisation {{parentTeamName}} bei {{appName}} beizutreten. {{appName}} ist der Terminplaner, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne Hin und Her zu planen.",
"email_user_invite_subheading_org": "{{invitedBy}} hat Sie eingeladen, ihrer Organisation „{{teamName}}“ in {{appName}} beizutreten. {{appName}} ist der Terminplaner, der es Ihnen und Ihrer Organisation ermöglicht, Meetings ohne ständiges hin und her zu planen.",
"email_no_user_invite_steps_intro": "Wir begleiten Sie durch ein paar Schritte, danach können Sie in kürzester Zeit stressfreie Planung mit Ihrer {{entity}} genießen.",
"email_no_user_step_one": "Wählen Sie Ihren Benutzernamen",
@@ -1839,7 +1900,9 @@
"review_event_type": "Ereignistyp überprüfen",
"looking_for_more_analytics": "Suchen Sie nach weiteren Analysedaten?",
"looking_for_more_insights": "Möchten Sie mehr Insights?",
"filters": "Filter",
"add_filter": "Filter hinzufügen",
"remove_filters": "Alle Filter löschen",
"select_user": "Benutzer auswählen",
"select_event_type": "Ereignistyp auswählen",
"select_date_range": "Datumsbereich auswählen",
@@ -1930,6 +1993,7 @@
"connect_google_workspace": "Google Workspace verbinden",
"google_workspace_admin_tooltip": "Sie müssen ein Workspace-Admin sein, um diese Funktion nutzen zu können",
"first_event_type_webhook_description": "Erstellen Sie Ihren ersten Webhook für diesen Termintypen",
"create_instant_meeting_webhook_description": "Erstellen Sie Ihren ersten Webhook mit „Sofortiges Meeting erstellt“ als Auslöser für diesen Ereignistyp",
"install_app_on": "App installieren auf",
"create_for": "Erstellen für",
"currency": "Währung",
@@ -1994,6 +2058,8 @@
"add_times_to_your_email": "Zeiten zur E-Mail hinzufügen",
"select_time": "Zeit auswählen",
"select_date": "Datum auswählen",
"connecting_you_to_someone": "Wir verbinden Sie mit jemandem.",
"please_do_not_close_this_tab": "Diesen Tab bitte nicht schließen",
"see_all_available_times": "Alle verfügbaren Zeiten ansehen",
"org_team_names_example_1": "z.B. Marketing-Team",
"org_team_names_example_2": "z.B. Vertriebsteam",
@@ -2011,8 +2077,12 @@
"description_requires_booker_email_verification": "Um sicherzustellen, dass die E-Mail des Buchenden vor der Terminplanung verifiziert ist",
"requires_confirmation_mandatory": "Textnachrichten können nur an Teilnehmer gesendet werden, wenn der Ereignistyp bestätigt werden muss.",
"organizations": "Organisationen",
"upload_cal_video_logo": "Cal Video Logo hochladen",
"update_cal_video_logo": "Cal Video Logo aktualisieren",
"cal_video_logo_upload_instruction": "Um sicherzustellen, dass Ihr Logo vor dem dunklen Hintergrund von Cal Video gut sichtbar ist, laden Sie bitte ein helles Bild im PNG- oder SVG-Format hoch, um die Transparenz zu erhalten.",
"org_admin_other_teams": "Weitere Teams",
"org_admin_other_teams_description": "Hier können Sie Teams innerhalb Ihrer Organisation sehen, zu denen Sie nicht gehören. Sie können sich bei Bedarf selbst hinzufügen.",
"not_part_of_org": "Sie gehören keiner Organisation an",
"no_other_teams_found": "Keine weiteren Teams gefunden",
"no_other_teams_found_description": "Es gibt keine weiteren Teams in dieser Organisation.",
"attendee_first_name_variable": "Vorname des Teilnehmers",
@@ -2048,7 +2118,9 @@
"org_error_processing": "Beim Verarbeiten dieser Organisation ist ein Fehler aufgetreten",
"orgs_page_description": "Eine Liste aller Organisationen. Wenn Sie eine Organisation akzeptieren, können sich alle Benutzer mit dieser E-Mail-Domain OHNE E-Mail-Überprüfung registrieren.",
"unverified": "Nicht verifiziert",
"verified": "Verifiziert",
"dns_missing": "DNS fehlt",
"dns_configured": "DNS konfiguriert",
"mark_dns_configured": "Als DNS konfiguriert kennzeichnen",
"value": "Wert",
"your_organization_updated_sucessfully": "Ihre Organisation wurde erfolgreich aktualisiert",
@@ -2058,8 +2130,11 @@
"oAuth": "OAuth",
"recently_added": "Kürzlich hinzugefügt",
"connect_all_calendars": "Verbinden Sie alle Ihre Kalender",
"connect_all_calendars_description": "{{appName}} liest die Verfügbarkeit aus allen Ihren vorhandenen Kalendern.",
"workflow_automation": "Workflow-Automatisierung",
"workflow_automation_description": "Personalisieren Sie Ihre Planungserfahrung mit Workflows",
"scheduling_for_your_team": "Workflow-Automatisierung",
"scheduling_for_your_team_description": "Planen Sie Ihr Team mit kollektiver Terminplanung und Round-Robin-Terminplanung",
"no_members_found": "Keine Mitglieder gefunden",
"event_setup_length_error": "Ereignis-Einrichtung: Die Dauer muss mindestens 1 Minute betragen.",
"availability_schedules": "Verfügbarkeitspläne",
@@ -2092,10 +2167,100 @@
"overlay_my_calendar": "Meinen Kalender überlagern",
"overlay_my_calendar_toc": "Durch das Verbinden mit Ihrem Kalender akzeptieren Sie unsere Datenschutzerklärung und Nutzungsbedingungen. Sie können den Zugriff jederzeit widerrufen.",
"view_overlay_calendar_events": "Sehen Sie sich Ihre Kalendertermine an, um Buchungskonflikte zu vermeiden.",
"join_event_location": "{{eventLocationType}} beitreten",
"troubleshooting": "Problembehandlung",
"calendars_were_checking_for_conflicts": "Kalender, die wir auf Konflikten überprüfen",
"availabilty_schedules": "Verfügbarkeitspläne",
"manage_calendars": "Kalender verwalten",
"manage_availability_schedules": "Verfügbarkeitspläne verwalten",
"lock_timezone_toggle_on_booking_page": "Zeitzone auf der Buchungsseite sperren",
"description_lock_timezone_toggle_on_booking_page": "Um die Zeitzone auf der Buchungsseite zu sperren, nützlich für Termine in Person.",
"event_setup_multiple_payment_apps_error": "Sie können nur eine Zahlungs-App pro Ereignistyp aktiv haben.",
"number_in_international_format": "Bitte geben Sie die Nummer im internationalen Format ein.",
"install_calendar": "Kalender installieren",
"branded_subdomain": "Marken-Subdomain",
"branded_subdomain_description": "Holen Sie sich Ihre eigene Marken-Subdomain, wie z.B. acme.cal.com",
"org_insights": "Organisationsweite Insights",
"org_insights_description": "Verstehen Sie, wie Ihre gesamte Organisation Zeit verbringt",
"extensive_whitelabeling": "Dedizierte Onboarding- und Engineeringsupport",
"extensive_whitelabeling_description": "Passen Sie Ihre Planungserfahrung mit Ihrem eigenen Logo, eigenen Farben und mehr an",
"unlimited_teams": "Unbegrenzte Teams",
"unlimited_teams_description": "Fügen Sie Ihrer Organisation so viele Unterteams hinzu, wie notwendig",
"unified_billing": "Einheitliche Abrechnung",
"unified_billing_description": "Fügen Sie eine einzige Kreditkarte hinzu, um alle Abos Ihres Teams zu bezahlen",
"advanced_managed_events": "Erweiterte verwaltete Ereignistypen",
"advanced_managed_events_description": "Fügen Sie eine einzige Kreditkarte hinzu, um alle Abonnements Ihres Teams zu bezahlen",
"enterprise_description": "Rüsten Sie auf Enterprise auf, um Ihre Organisation zu erstellen",
"create_your_org": "Erstellen Sie Ihre Organisation",
"create_your_org_description": "Rüsten Sie auf Enterprise auf und erhalten Sie eine Subdomain, einheitliche Abrechnung, Insights, umfangreiches Whitelabeling und mehr",
"other_payment_app_enabled": "Sie können nur eine Zahlungs-App pro Ereignistyp aktivieren",
"admin_delete_organization_description": "<ul><li>Teams, die Mitglieder dieser Organisation sind, werden ebenfalls zusammen mit ihren Ereignistypen gelöscht</li><li>Benutzer, die Teil der Organisation waren, werden nicht gelöscht und ihre Ereignistypen bleiben ebenfalls intakt.</li><li>Benutzernamen würden geändert werden, um zu ermöglichen, dass sie außerhalb der Organisation existieren können</li></ul>",
"admin_delete_organization_title": "{{organizationName}} löschen?",
"published": "Veröffentlicht",
"unpublished": "Unveröffentlicht",
"publish": "Veröffentlichen",
"org_publish_error": "Organisation konnte nicht veröffentlicht werden",
"troubleshooter_tooltip": "Öffnen Sie die Problembehandlung und finden Sie heraus, was mit Ihrem Zeitplan nicht stimmt",
"need_help": "Brauchst du Hilfe?",
"troubleshooter": "Problembehandlung",
"please_install_a_calendar": "Bitte installieren Sie einen Kalender",
"instant_tab_title": "Sofortige Buchung",
"instant_event_tab_description": "Personen sofort buchen lassen",
"uprade_to_create_instant_bookings": "Rüsten Sie auf Enterprise auf und lassen Sie Ihre Gäste an einem Sofortanruf teilnehmen, an dem die Teilnehmer direkt teilnehmen können. Dies ist nur für Team-Ereignistypen verfügbar",
"dont_want_to_wait": "Sie möchten nicht warten?",
"meeting_started": "Meeting gestartet",
"pay_and_book": "Zum Buchen bezahlen",
"booking_not_found_error": "Buchung konnte nicht gefunden werden",
"booking_seats_full_error": "Die Buchungsplätze sind voll",
"missing_payment_credential_error": "Fehlende Zahlungsinformationen",
"missing_payment_app_id_error": "Fehlende Zahlungsapp-ID",
"not_enough_available_seats_error": "Die Buchung hat nicht genügend Plätze verfügbar",
"user_redirect_title": "{{username}} ist derzeit kurzzeitig abwesend.",
"user_redirect_description": "In der Zwischenzeit wird {{profile.username}} für alle neuen geplanten Meetings im Namen von {{username}} verantwortlich sein.",
"out_of_office": "Abwesend",
"out_of_office_description": "Konfigurieren Sie Aktionen in Ihrem Profil während Sie abwesend sind.",
"send_request": "Anfrage senden",
"start_date_and_end_date_required": "Startdatum und Enddatum sind erforderlich",
"start_date_must_be_before_end_date": "Das Startdatum muss vor dem Enddatum liegen",
"start_date_must_be_in_the_future": "Das Startdatum muss in der Zukunft liegen",
"user_not_found": "Benutzer nicht gefunden",
"out_of_office_entry_already_exists": "Abwesenheitseintrag existiert bereits",
"out_of_office_id_required": "Abwesenheitseintrags-ID erforderlich",
"booking_redirect_infinite_not_allowed": "Es besteht bereits eine Buchungsumleitung von diesem Benutzer zu Ihnen.",
"success_entry_created": "Neuer Eintrag wurde erfolgreich erstellt",
"booking_redirect_email_subject": "Buchungsumleitungsbenachrichtigung",
"booking_redirect_email_title": "Buchungsumleitungsbenachrichtigung",
"booking_redirect_email_description": "Sie haben eine Umleitung der Buchung von {{toName}} erhalten, so dass deren Profillinks für das Zeitfenster an Sie umgeleitet werden: ",
"success_accept_booking_redirect": "Sie haben diese Buchungsumleitungsanfrage akzeptiert.",
"success_reject_booking_redirect": "Sie haben diese Buchungsumleitungsanfrage abgelehnt.",
"copy_link_booking_redirect_request": "Link zum Teilen der Anfrage kopieren",
"booking_redirect_request_title": "Buchungsumleitungsanfrage",
"select_team_member": "Teammitglied auswählen",
"going_away_title": "Verreisen Sie? Markieren Sie einfach Ihren Profillink für eine gewisse Zeit als nicht verfügbar.",
"redirect_team_enabled": "Leiten Sie Ihr Profil an ein anderes Teammitglied um",
"redirect_team_disabled": "Leiten Sie Ihr Profil an ein anderes Teammitglied um (Teamplan erforderlich)",
"out_of_office_unavailable_list": "Abwesenheits-Unverfügbarkeitsliste",
"success_deleted_entry_out_of_office": "Eintrag erfolgreich gelöscht",
"temporarily_out_of_office": "Temporär abwesend?",
"add_a_redirect": "Umleitung hinzufügen",
"create_entry": "Eintrag erstellen",
"time_range": "Zeitspanne",
"automatically_add_all_team_members": "Alle Teammitglieder hinzufügen, einschließlich zukünftiger Mitglieder",
"redirect_to": "Umleiten zu",
"having_trouble_finding_time": "Haben Sie Schwierigkeiten, ein Zeitfenster zu finden?",
"show_more": "Mehr anzeigen",
"assignment_description": "Planen Sie Meetings ein, wenn jeder verfügbar ist, oder rotieren Sie durch die Mitglieder Ihres Team",
"lowest": "Niedrigste",
"low": "Niedrig",
"medium": "Mittel",
"high": "Hoch",
"Highest": "Höchste",
"send_booker_to": "Bucher senden an",
"set_priority": "Priorität festlegen",
"priority_for_user": "Priorität von {{userName}}",
"change_priority": "Priorität ändern",
"field_identifiers_as_variables": "Verwenden Sie Feldbezeichner als Variablen für Ihre benutzerdefinierte Ereignisumleitung",
"field_identifiers_as_variables_with_example": "Verwenden Sie Feldbezeichner als Variablen für Ihre benutzerdefinierte Ereignisumleitung (z. B. {{variable}})",
"account_already_linked": "Konto ist bereits verknüpft",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -807,6 +807,8 @@
"additional_input_description": "Require scheduler to input additional inputs prior the booking is confirmed",
"label": "Label",
"placeholder": "Placeholder",
"display_add_to_calendar_organizer":"Display 'Add to calendar' email as the organizer",
"reconnect_calendar_to_use": "Please note that you may need to disconnect and then reconnect your 'Add to Calendar' account to use this feature.",
"type": "Type",
"edit": "Edit",
"add_input": "Add an Input",
@@ -2194,7 +2196,7 @@
"create_your_org_description": "Upgrade to Enterprise and receive a subdomain, unified billing, Insights, extensive whitelabeling and more",
"other_payment_app_enabled": "You can only enable one payment app per event type",
"admin_delete_organization_description": "<ul><li>Teams that are member of this organization will also be deleted along with their event-types</li><li>Users that were part of the organization will not be deleted and their event-types will also remain intact.</li><li>Usernames would be changed to allow them to exist outside the organization</li></ul>",
"admin_delete_organization_title": "Delete organization?",
"admin_delete_organization_title": "Delete {{organizationName}}?",
"published": "Published",
"unpublished": "Unpublished",
"publish": "Publish",
@@ -2258,6 +2260,8 @@
"set_priority": "Set Priority",
"priority_for_user": "Priority for {{userName}}",
"change_priority": "change priority",
"field_identifiers_as_variables": "Use field identifiers as variables for your custom event redirect",
"field_identifiers_as_variables_with_example": "Use field identifiers as variables for your custom event redirect (e.g. {{variable}})",
"account_already_linked": "Account is already linked",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -13,10 +13,17 @@
"reset_password_subject": "{{appName}} : Instructions de réinitialisation de mot de passe",
"verify_email_subject": "{{appName}} : Vérifiez votre compte",
"check_your_email": "Vérifiez votre e-mail",
"old_email_address": "Ancienne adresse e-mail",
"new_email_address": "Nouvelle adresse e-mail",
"verify_email_page_body": "Nous avons envoyé un e-mail à {{email}}. Il est important de vérifier votre adresse e-mail pour garantir la meilleure délivrabilité des e-mails et des calendriers de {{appName}}.",
"verify_email_banner_body": "Vérifiez votre adresse e-mail pour garantir la meilleure délivrabilité des e-mails et des calendriers",
"verify_email_email_header": "Vérifiez votre adresse e-mail",
"verify_email_email_button": "Vérifier l'adresse e-mail",
"verify_email_change_description": "Vous avez récemment demandé à changer l'adresse e-mail que vous utilisez pour vous connecter à votre compte {{appName}}. Veuillez cliquer sur le bouton ci-dessous pour confirmer votre nouvelle adresse e-mail.",
"verify_email_change_success_toast": "Mise à jour de votre adresse e-mail. Nouvelle : {{email}}",
"verify_email_change_failure_toast": "Échec de la mise à jour de l'adresse e-mail.",
"change_of_email": "Faites vérifier votre nouvelle adresse e-mail {{appName}}",
"change_of_email_toast": "Nous avons envoyé un lien de vérification à {{email}}. Nous mettrons à jour votre adresse e-mail une fois que vous aurez cliqué sur ce lien.",
"copy_somewhere_safe": "Sauvegardez cette clé API dans un endroit sûr. Vous ne pourrez plus la revoir.",
"verify_email_email_body": "Veuillez vérifier votre adresse e-mail en cliquant sur le bouton ci-dessous.",
"verify_email_by_code_email_body": "Veuillez vérifier votre adresse e-mail en utilisant le code ci-dessous.",
@@ -56,6 +63,17 @@
"a_refund_failed": "Un remboursement a échoué",
"awaiting_payment_subject": "En attente de paiement : {{title}} le {{date}}",
"meeting_awaiting_payment": "Votre rendez-vous est en attente de paiement",
"dark_theme_contrast_error": "La couleur du thème sombre ne passe pas la vérification du contraste. Nous vous recommandons de modifier cette couleur afin que vos boutons soient plus visibles.",
"light_theme_contrast_error": "La couleur du thème clair ne passe pas la vérification du contraste. Nous vous recommandons de modifier cette couleur afin que vos boutons soient plus visibles.",
"payment_not_created_error": "Le paiement n'a pas pu être créé",
"couldnt_charge_card_error": "Impossible de débiter la carte pour le Paiement",
"no_available_users_found_error": "Aucun utilisateur disponible n'a été trouvé. Pourriez-vous essayer un autre créneau horaire ?",
"request_body_end_time_internal_error": "Erreur interne. Le corps de la demande ne contient pas l'heure de fin",
"create_calendar_event_error": "Impossible de créer un événement de Calendrier dans le calendrier de l'Organisateur",
"update_calendar_event_error": "Impossible de mettre à jour l'événement de Calendrier.",
"delete_calendar_event_error": "Impossible de supprimer l'événement de Calendrier.",
"already_signed_up_for_this_booking_error": "Vous êtes déjà inscrit(e) à cette réservation.",
"hosts_unavailable_for_booking": "Certains hôtes sont indisponibles pour la réservation.",
"help": "Aide",
"price": "Prix",
"paid": "Payé",
@@ -101,6 +119,7 @@
"requested_to_reschedule_subject_attendee": "Replanification requise : veuillez réserver un nouveau créneau pour {{eventType}} avec {{name}}",
"hi_user_name": "Bonjour {{name}}",
"ics_event_title": "{{eventType}} avec {{name}}",
"please_book_a_time_sometime_later": "Oups, nous n'avons pas pu vous connecter cette fois-ci. Veuillez plutôt planifier un appel ultérieur",
"new_event_subject": "Nouvel événement : {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "Rejoindre par {{entryPoint}}",
"notes": "Notes",
@@ -117,15 +136,20 @@
"meeting_id": "ID du rendez-vous",
"meeting_password": "Mot de passe du rendez-vous",
"meeting_url": "Lien du rendez-vous",
"meeting_url_not_found": "L'URL du rendez-vous est introuvable",
"token_not_found": "Jeton introuvable",
"some_other_host_already_accepted_the_meeting": "D'autres hôtes ont déjà accepté le rendez-vous. Voulez-vous quand même participer ? <1>Aller quand même dans Rendez-vous</1>",
"meeting_request_rejected": "Votre demande de rendez-vous a été refusée",
"rejected_event_type_with_organizer": "Refusé : {{eventType}} avec {{organizer}} le {{date}}",
"hi": "Bonjour",
"join_team": "Rejoindre l'équipe",
"manage_this_team": "Gérer cette équipe",
"team_info": "Informations sur l'équipe",
"join_meeting": "Rejoindre le rendez-vous",
"request_another_invitation_email": "Si vous préférez ne pas utiliser {{toEmail}} comme votre adresse e-mail {{appName}} ou si vous avez déjà un compte {{appName}}, veuillez demander une autre invitation à cette adresse.",
"you_have_been_invited": "Vous avez été invité à rejoindre l'équipe {{teamName}}",
"user_invited_you": "{{user}} vous a invité à rejoindre l'{{entity}} {{team}} sur {{appName}}",
"user_invited_you_to_subteam": "{{user}} vous a invité(e) à rejoindre l'équipe {{team}} de l'organisation {{parentTeamName}} sur {{appName}}",
"hidden_team_member_title": "Vous êtes masqué dans cette équipe",
"hidden_team_member_message": "Votre place n'est pas payée, passez au plan Pro ou faites savoir au propriétaire de l'équipe qu'il peut payer votre place.",
"hidden_team_owner_message": "Vous avez besoin d'un compte Pro pour utiliser les équipes, vous êtes masqué jusqu'à ce que vous mettiez à niveau.",
@@ -285,6 +309,7 @@
"welcome_to_calcom": "Bienvenue sur {{appName}}",
"welcome_instructions": "Dites-nous comment vous appeler et indiquez-nous dans quel fuseau horaire vous vous trouvez. Vous pourrez modifier cela plus tard. ",
"connect_caldav": "Se connecter au serveur CalDAV (Bêta)",
"connect_ics_feed": "Connexion à un flux ICS",
"connect": "Connecter",
"try_for_free": "Essayez gratuitement",
"create_booking_link_with_calcom": "Créez votre propre lien de réservation avec {{appName}}",
@@ -423,12 +448,14 @@
"browse_api_documentation": "Parcourez la documentation de l'API",
"leverage_our_api": "Profitez de notre API pour tout personnaliser et contrôler.",
"create_webhook": "Créer un webhook",
"instant_meeting": "Rendez-vous instantané créé",
"booking_cancelled": "Réservation annulée",
"booking_rescheduled": "Réservation replanifiée",
"recording_ready": "Lien de téléchargement de l'enregistrement prêt",
"booking_created": "Réservation créée",
"booking_rejected": "Réservation rejetée",
"booking_requested": "Réservation demandée",
"booking_payment_initiated": "Paiement de la réservation initiée",
"meeting_ended": "Rendez-vous terminé",
"form_submitted": "Formulaire envoyé",
"booking_paid": "Réservation payée",
@@ -536,6 +563,7 @@
"enter_number_between_range": "Veuillez entrer un nombre entre 1 et {{maxOccurences}}",
"email_address": "Adresse e-mail",
"enter_valid_email": "Veuillez saisir une adresse e-mail valide",
"please_schedule_future_call": " Veuillez programmer un appel ultérieur si nous ne sommes pas disponibles dans {{seconds}} secondes",
"location": "Lieu",
"address": "Adresse",
"enter_address": "Entrer une adresse",
@@ -595,6 +623,7 @@
"new_member": "Nouveau membre",
"invite": "Inviter",
"add_team_members": "Ajouter des membres d'équipe",
"add_org_members": "Ajouter des membres",
"add_team_members_description": "Invitez d'autres personnes à rejoindre votre équipe.",
"add_team_member": "Ajouter un membre d'équipe",
"invite_new_member": "Inviter un nouveau membre d'équipe",
@@ -609,6 +638,7 @@
"hide_book_a_team_member_description": "Masquez le bouton Réserver un membre d'équipe de vos pages publiques.",
"danger_zone": "Zone de danger",
"account_deletion_cannot_be_undone": "Attention, la suppression de compte est irréversible.",
"team_deletion_cannot_be_undone": "Attention. La suppression de l'équipe est irréversible",
"back": "Retour",
"cancel": "Annuler",
"cancel_all_remaining": "Annuler tous les événements restants",
@@ -672,6 +702,7 @@
"add_members": "Ajouter des membres...",
"no_assigned_members": "Aucun membre assigné",
"assigned_to": "Assigné à",
"you_must_be_logged_in_to": "Vous devez être connecté(e) à {{url}}",
"start_assigning_members_above": "Commencez à assigner des membres ci-dessus",
"locked_fields_admin_description": "Les membres ne pourront pas modifier ceci.",
"locked_fields_member_description": "Cette option a été verrouillée par l'administrateur de l'équipe.",
@@ -776,6 +807,7 @@
"additional_input_description": "Exiger que le planificateur saisisse des champs supplémentaires avant la confirmation de la réservation.",
"label": "Libellé",
"placeholder": "Texte de substitution",
"display_add_to_calendar_organizer": "Afficher l'e-mail « Ajouter au calendrier » en tant qu'organisateur",
"type": "Type",
"edit": "Modifier",
"add_input": "Ajouter un champ",
@@ -784,6 +816,9 @@
"requires_confirmation_description": "La réservation doit être confirmée manuellement avant d'être poussée vers les intégrations et qu'un e-mail de confirmation soit envoyé.",
"recurring_event": "Événement récurrent",
"recurring_event_description": "Les personnes peuvent s'inscrire à des événements récurrents.",
"cannot_be_used_with_paid_event_types": "À ne pas utiliser avec les types d'événement payants",
"warning_payment_instant_meeting_event": "Les Rendez-vous instantanés ne sont pas encore pris en charge par les événements récurrents et les applications de paiement",
"warning_instant_meeting_experimental": "Expérimental : Les événements de Rendez-vous instantané sont actuellement expérimentaux.",
"starting": "Début",
"disable_guests": "Désactiver les invités",
"disable_guests_description": "Désactivez l'ajout d'invités supplémentaires lors de la réservation.",
@@ -792,6 +827,7 @@
"private_link_label": "Lien privé",
"private_link_hint": "Votre lien privé sera régénéré après chaque utilisation",
"copy_private_link": "Copier le lien privé",
"copy_private_link_to_event": "Copier le lien privé de l'événement",
"private_link_description": "Générez un lien privé à partager sans révéler votre nom d'utilisateur {{appName}}.",
"invitees_can_schedule": "Les invités peuvent réserver",
"date_range": "Plage de dates",
@@ -866,6 +902,7 @@
"toggle_calendars_conflict": "Activez/désactivez les calendriers pour lesquels vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
"connect_additional_calendar": "Connecter un calendrier supplémentaire",
"calendar_updated_successfully": "Calendrier mis à jour avec succès",
"check_here": "Vérifier ici",
"conferencing": "Visioconférence",
"calendar": "Calendrier",
"payments": "Paiements",
@@ -1038,6 +1075,8 @@
"user_impersonation_description": "Autorisez notre équipe d'assistance à se connecter temporairement à votre place pour nous aider à résoudre les problèmes que vous pourriez nous signaler.",
"team_impersonation_description": "Autorisez les propriétaires/administrateurs de votre équipe à se connecter temporairement à votre place.",
"cal_signup_description": "Gratuit pour les particuliers. Plans Équipes pour les fonctionnalités collaboratives.",
"make_org_private": "Rendre l'organisation privée",
"make_org_private_description": "Les membres de votre organisation ne pourront pas voir les autres membres de l'organisation lorsque cette option est activée.",
"make_team_private": "Rendre l'équipe privée",
"make_team_private_description": "Les membres de votre équipe ne pourront pas voir les autres membres de l'équipe si cette option est activée.",
"you_cannot_see_team_members": "Vous ne pouvez pas voir tous les membres d'une équipe privée.",
@@ -1222,6 +1261,7 @@
"organizer_name_variable": "Nom de l'organisateur",
"app_upgrade_description": "Pour pouvoir utiliser cette fonctionnalité, vous devez passer à un compte Pro.",
"invalid_number": "Numéro de téléphone invalide",
"invalid_url_error_message": "URL non valide pour {{label}}. Exemple d'URL : {{sampleUrl}}",
"navigate": "Naviguer",
"open": "Ouvrir",
"close": "Fermer",
@@ -1260,6 +1300,7 @@
"default_calendar_selected": "Calendrier par défaut",
"hide_from_profile": "Masquer du profil",
"event_setup_tab_title": "Configuration",
"availability_not_found_in_schedule_error": "Aucune disponibilité trouvée dans le planning",
"event_limit_tab_title": "Limites",
"event_limit_tab_description": "Fréquence de réservation",
"event_advanced_tab_description": "Paramètres du calendrier, etc.",
@@ -1280,6 +1321,7 @@
"do_this": "Effectuer ceci",
"turn_off": "Désactiver",
"turn_on": "Activer",
"cancelled_bookings_cannot_be_rescheduled": "Les réservations annulées ne peuvent pas être reprogrammées",
"settings_updated_successfully": "Paramètres mis à jour avec succès",
"error_updating_settings": "Erreur lors de la mise à jour des paramètres",
"personal_cal_url": "Mon lien {{appName}} personnel",
@@ -1296,12 +1338,14 @@
"select_calendars": "Sélectionnez les calendriers dont vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
"check_for_conflicts": "Vérifier les conflits",
"view_recordings": "Voir les enregistrements",
"check_for_recordings": "Chercher des enregistrements",
"adding_events_to": "Ajout d'événements",
"follow_system_preferences": "Suivre les préférences système",
"custom_brand_colors": "Couleurs de marque personnalisées",
"customize_your_brand_colors": "Personnalisez la couleur de votre page de réservation à l'image de votre marque.",
"pro": "Pro",
"removes_cal_branding": "Supprimez toutes les références à la marque {{appName}}, par exemple « Propulsé par {{appName}} ».",
"instant_meeting_with_title": "Rendez-vous instantané avec {{name}}",
"profile_picture": "Photo de profil",
"upload": "Télécharger",
"add_profile_photo": "Ajouter une photo de profil",
@@ -1357,6 +1401,7 @@
"event_name_info": "Nom du type d'événement",
"event_date_info": "Date de l'événement",
"event_time_info": "Heure de début de l'événement",
"event_type_not_found": "Type d'événement introuvable",
"location_variable": "Lieu",
"location_info": "Lieu de l'événement",
"additional_notes_variable": "Notes supplémentaires",
@@ -1399,6 +1444,7 @@
"slot_length": "Durée du créneau",
"booking_appearance": "Apparence",
"appearance_team_description": "Gérez les paramètres d'apparence de vos réservations d'équipe.",
"appearance_org_description": "Gérez les paramètres d'apparence de vos réservations d'organisation",
"only_owner_change": "Seul le propriétaire de cette équipe peut apporter des modifications à la réservation de l'équipe ",
"team_disable_cal_branding_description": "Supprimez toutes les références à la marque {{appName}}, par exemple « Propulsé par {{appName}} ».",
"invited_by_team": "{{teamName}} vous a invité à rejoindre son équipe en tant que {{role}}",
@@ -1452,6 +1498,7 @@
"password_updated": "Mot de passe mis à jour !",
"pending_payment": "Paiement en attente",
"pending_invites": "Invitations en attente",
"pending_organization_invites": "Invitations de l'organisation en attente",
"not_on_cal": "Pas sur {{appName}}",
"no_calendar_installed": "Aucun calendrier installé",
"no_calendar_installed_description": "Vous n'avez pas encore connecté vos calendriers.",
@@ -1463,6 +1510,8 @@
"report_app": "Signaler l'application",
"limit_booking_frequency": "Limiter la fréquence de réservation",
"limit_booking_frequency_description": "Limitez le nombre de réservations possibles pour cet événement.",
"limit_booking_only_first_slot": "Limiter la réservation à un seul créneau",
"limit_booking_only_first_slot_description": "N'autoriser la réservation que du premier créneau de chaque jour",
"limit_total_booking_duration": "Limiter la durée totale de réservation",
"limit_total_booking_duration_description": "Limitez la durée totale pendant laquelle cet événement peut être réservé.",
"add_limit": "Ajouter une limite",
@@ -1535,6 +1584,9 @@
"member_already_invited": "Le membre a déjà été invité",
"already_in_use_error": "Nom d'utilisateur déjà utilisé",
"enter_email_or_username": "Saisissez une adresse e-mail ou un nom d'utilisateur",
"enter_email": "Saisissez une adresse e-mail",
"enter_emails": "Saisissez des adresses e-mail",
"too_many_invites": "Vous ne pouvez inviter qu'un maximum de {{nbUsers}} utilisateurs à la fois.",
"team_name_taken": "Ce nom est déjà pris",
"must_enter_team_name": "Vous devez saisir un nom d'équipe",
"team_url_required": "Vous devez saisir un lien d'équipe",
@@ -1614,6 +1666,7 @@
"individual": "Particulier",
"all_bookings_filter_label": "Toutes les réservations",
"all_users_filter_label": "Tous les utilisateurs",
"all_event_types_filter_label": "Tous les types d'événement",
"your_bookings_filter_label": "Vos réservations",
"meeting_url_variable": "Lien du rendez-vous",
"meeting_url_info": "Lien de la visioconférence du rendez-vous",
@@ -1628,6 +1681,7 @@
"date_overrides_mark_all_day_unavailable_other": "Marquer comme indisponible aux dates sélectionnées",
"date_overrides_add_btn": "Ajouter la superposition",
"date_overrides_update_btn": "Mettre à jour la superposition",
"date_successfully_added": "Ajout du remplacement de date réussi",
"event_type_duplicate_copy_text": "{{slug}}-copie",
"set_as_default": "Définir par défaut",
"hide_eventtype_details": "Masquer les détails du type d'événement",
@@ -1654,6 +1708,7 @@
"minimum_round_robin_hosts_count": "Nombre d'hôtes requis pour participer",
"hosts": "Hôtes",
"upgrade_to_enable_feature": "Vous devez créer une équipe pour activer cette fonctionnalité. Cliquez pour créer une équipe.",
"orgs_upgrade_to_enable_feature": "Vous devez passer sur notre formule entreprise pour activer cette fonctionnalité.",
"new_attendee": "Nouveau participant",
"awaiting_approval": "En attente de validation",
"requires_google_calendar": "Cette application nécessite une connexion à Google Agenda",
@@ -1708,9 +1763,11 @@
"organizer_timezone": "Fuseau horaire de l'organisateur",
"email_user_cta": "Voir l'invitation",
"email_no_user_invite_heading_team": "Vous avez été invité à rejoindre une équipe {{appName}}",
"email_no_user_invite_heading_subteam": "Vous avez été invité(e) à rejoindre une équipe de l'organisation {{parentTeamName}}",
"email_no_user_invite_heading_org": "Vous avez été invité à rejoindre une organisation {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} vous a invité à rejoindre son équipe sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
"email_user_invite_subheading_team": "{{invitedBy}} vous a invité à rejoindre son équipe « {{teamName}} » sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
"email_user_invite_subheading_subteam": "{{invitedBy}} vous a invité(e) à rejoindre son équipe {{teamName}} dans son organisation {{parentTeamName}} sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
"email_user_invite_subheading_org": "{{invitedBy}} vous a invité à rejoindre son organisation « {{teamName}} » sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre organisation d'organiser des rendez-vous sans échanges d'e-mails.",
"email_no_user_invite_steps_intro": "Nous vous guiderons à travers quelques étapes courtes et vous profiterez d'une planification sans stress avec votre {{entity}} en un rien de temps.",
"email_no_user_step_one": "Choisissez votre nom d'utilisateur",
@@ -1843,6 +1900,7 @@
"review_event_type": "Examiner le type d'événement",
"looking_for_more_analytics": "Vous cherchez plus d'analyses ?",
"looking_for_more_insights": "Vous cherchez plus de statistiques ?",
"filters": "Filtres",
"add_filter": "Ajouter un filtre",
"remove_filters": "Effacer les filtres",
"select_user": "Sélectionner un utilisateur",
@@ -1935,6 +1993,7 @@
"connect_google_workspace": "Connecter Google Workspace",
"google_workspace_admin_tooltip": "Vous devez être un administrateur d'espace de travail pour utiliser cette fonctionnalité",
"first_event_type_webhook_description": "Créez votre premier webhook pour ce type d'événement",
"create_instant_meeting_webhook_description": "Créez votre premier webhook avec « Rendez-vous instantané créé » comme déclencheur pour ce type d'événement",
"install_app_on": "Installer l’application sur",
"create_for": "Créer pour",
"currency": "Devise",
@@ -1999,6 +2058,8 @@
"add_times_to_your_email": "Sélectionnez quelques créneaux disponibles et intégrez-les dans votre e-mail.",
"select_time": "Sélectionner un créneau",
"select_date": "Sélectionner une date",
"connecting_you_to_someone": "Nous vous mettons en relation avec quelqu'un.",
"please_do_not_close_this_tab": "Veuillez ne pas fermer cet onglet",
"see_all_available_times": "Voir tous les créneaux disponibles",
"org_team_names_example_1": "p. ex. Équipe marketing",
"org_team_names_example_2": "p. ex. Équipe de vente",
@@ -2016,8 +2077,12 @@
"description_requires_booker_email_verification": "Pour assurer la vérification par e-mail du participant avant la planification des événements.",
"requires_confirmation_mandatory": "Les messages ne peuvent être envoyés aux participants que lorsque le type d'événement nécessite une confirmation.",
"organizations": "Organisations",
"upload_cal_video_logo": "Téléverser un logo vidéo Cal",
"update_cal_video_logo": "Mettre à jour un logo vidéo Cal",
"cal_video_logo_upload_instruction": "Pour assurer que votre logo reste visible sur le fond sombre de la vidéo Cal, veuillez téléverser une image de couleur claire au format PNG ou SVG afin de maintenir la transparence.",
"org_admin_other_teams": "Autres équipes",
"org_admin_other_teams_description": "Ici, vous pouvez voir les équipes au sein de votre organisation dont vous ne faites pas partie. Vous pouvez vous y ajouter si nécessaire.",
"not_part_of_org": "Vous ne faites partie d'aucune organisation",
"no_other_teams_found": "Aucune autre équipe trouvée",
"no_other_teams_found_description": "Il n'y a pas d'autres équipes dans cette organisation.",
"attendee_first_name_variable": "Prénom du participant",
@@ -2053,52 +2118,149 @@
"org_error_processing": "Une erreur s'est produite lors du traitement de cette organisation",
"orgs_page_description": "Une liste de toutes les organisations. Accepter une organisation permettra à tous les utilisateurs avec ce domaine de messagerie de s'inscrire SANS vérification d'e-mail.",
"unverified": "Non vérifié",
"verified": "Vérifié",
"dns_missing": "DNS manquant",
"dns_configured": "DNS configuré",
"mark_dns_configured": "Marquer comme DNS configuré",
"value": "Valeur",
"your_organization_updated_sucessfully": "Votre organisation a été mise à jour avec succès",
"team_no_event_types": "Cette équipe n'a aucun type d'événement",
"seat_options_doesnt_multiple_durations": "L'option par place ne prend pas en charge les durées multiples",
"include_calendar_event": "Inclure l'événement du calendrier",
"oAuth": "OAuth",
"recently_added": "Ajoutées récemment",
"connect_all_calendars": "Connectez vos calendriers",
"connect_all_calendars_description": "{{appName}} relève la disponibilité de tous vos calendriers existants.",
"workflow_automation": "Automatisation de workflows",
"workflow_automation_description": "Personnalisez votre expérience de planification avec les workflows",
"scheduling_for_your_team": "Automatisation de workflows",
"scheduling_for_your_team_description": "Planifiez pour votre équipe grâce à la planification collective et par permutation circulaire",
"no_members_found": "Aucun membre trouvé",
"event_setup_length_error": "Configuration de l'événement : la durée doit être d'au moins 1 minute.",
"availability_schedules": "Horaires de disponibilité",
"unauthorized": "Non autorisé",
"access_cal_account": "{{clientName}} aimerait accéder à votre compte {{appName}}",
"select_account_team": "Sélectionner un compte ou une équipe",
"allow_client_to": "Cela permettra au {{clientName}} de",
"associate_with_cal_account": "Vous associer à vos informations personnelles de {{clientName}}",
"see_personal_info": "Voir vos informations personnelles, y compris celles que vous avez rendues publiques",
"see_primary_email_address": "Voir votre adresse e-mail principale",
"connect_installed_apps": "Vous connecter à vos applications installées",
"access_event_type": "Lire, modifier, supprimer vos types d'événements",
"access_availability": "Lire, modifier, supprimer vos disponibilités",
"access_bookings": "Lire, modifier, supprimer vos réservations",
"allow_client_to_do": "Autoriser {{clientName}} à faire cela ?",
"oauth_access_information": "En cliquant sur autoriser, vous autorisez cette application à utiliser vos informations conformément à ses conditions de service et à sa politique de confidentialité. Vous pouvez supprimer l'accès dans l'App Store de {{appName}}.",
"allow": "Autoriser",
"view_only_edit_availability_not_onboarded": "Cet utilisateur n'a pas terminé son intégration. Vous ne pourrez pas définir sa disponibilité tant qu'il n'aura pas terminé son intégration.",
"view_only_edit_availability": "Vous consultez les disponibilités de cet utilisateur. Vous ne pouvez modifier que vos propres disponibilités.",
"you_can_override_calendar_in_advanced_tab": "Vous pouvez modifier ceci pour chaque événement dans les paramètres avancés de chaque type d'événement.",
"edit_users_availability": "Modifier la disponibilité de l'utilisateur : {{username}}",
"resend_invitation": "Renvoyer l'invitation",
"invitation_resent": "L'invitation a été renvoyée.",
"saml_sso": "SAML",
"add_client": "Ajouter un client",
"copy_client_secret_info": "Après avoir copié le secret, vous ne pourrez plus le voir",
"add_new_client": "Ajouter un nouveau client",
"this_app_is_not_setup_already": "Cette application n'a pas encore été configurée",
"as_csv": "au format CSV",
"overlay_my_calendar": "Superposer mon calendrier",
"overlay_my_calendar_toc": "En vous connectant à votre calendrier, vous acceptez notre politique de confidentialité et nos conditions d'utilisation. Vous pouvez révoquer cet accès à tout moment.",
"view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.",
"join_event_location": "Rejoignez {{eventLocationType}}",
"troubleshooting": "Dépannage",
"calendars_were_checking_for_conflicts": "Calendriers dont nous vérifions les conflits",
"availabilty_schedules": "Horaires de disponibilité",
"manage_calendars": "Gérer les calendriers",
"manage_availability_schedules": "Gérer les horaires de disponibilité",
"lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation",
"description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.",
"event_setup_multiple_payment_apps_error": "Vous ne pouvez activer qu'une seule application de paiement par type d'événement.",
"number_in_international_format": "Veuillez entrer le numéro au format international.",
"install_calendar": "Installer Calendrier",
"branded_subdomain": "Sous-domaine de marque",
"branded_subdomain_description": "Obtenez votre propre sous-domaine de marque, tel que acme.cal.com",
"org_insights": "Statistiques à l'échelle de l'organisation",
"org_insights_description": "Comprenez comment l'ensemble de votre organisation passe son temps",
"extensive_whitelabeling": "Marque blanche étendue",
"extensive_whitelabeling_description": "Personnalisez votre expérience de planification avec votre propre logo, vos couleurs, etc.",
"unlimited_teams": "Équipes illimitées",
"unlimited_teams_description": "Ajoutez autant de sous-équipes que nécessaire à votre organisation",
"unified_billing": "Facturation unifiée",
"unified_billing_description": "Ajoutez une seule carte de crédit pour payer tous les abonnements de votre équipe",
"advanced_managed_events": "Types d'événements gérés avancés",
"advanced_managed_events_description": "Ajoutez une seule carte de crédit pour payer tous les abonnements de votre équipe",
"enterprise_description": "Passez en Entreprise pour créer votre organisation",
"create_your_org": "Créer votre organisation",
"create_your_org_description": "Passez en Entreprise et recevez un sous-domaine, une facturation unifiée, des statistiques, un marquage blanc extensif et plus encore",
"other_payment_app_enabled": "Vous ne pouvez activer qu'une seule application de paiement par type d'événement",
"admin_delete_organization_description": "<ul><li>Les équipes qui sont membres de cette organisation seront également supprimées, ainsi que leurs types d'événements</li><li>Les utilisateurs qui faisaient partie de l'organisation ne seront pas supprimés et leurs types d'événements resteront intacts.</li><li>Les noms d'utilisateur seront modifiés pour leur permettre d'exister en dehors de l'organisation</li></ul>",
"admin_delete_organization_title": "Supprimer {{organizationName}} ?",
"published": "Publiée",
"unpublished": "Non publiée",
"publish": "Publier",
"org_publish_error": "L'organisation n'a pas pu être publiée",
"troubleshooter_tooltip": "Ouvrez l'outil de dépannage et déterminez ce qui ne va pas avec votre planning",
"need_help": "Besoin d'aide ?",
"troubleshooter": "Dépannage",
"please_install_a_calendar": "Veuillez installer un publier",
"instant_tab_title": "Réservation instantanée",
"instant_event_tab_description": "Autoriser les gens à réserver immédiatement",
"uprade_to_create_instant_bookings": "Passez en Entreprise et autorisez les invités à rejoindre un appel instantané auquel les participants peuvent accéder directement. Ceci n'est valable que pour les types d'événements d'équipe",
"dont_want_to_wait": "Vous ne voulez pas attendre ?",
"meeting_started": "Le rendez-vous a commencé",
"pay_and_book": "Payer pour réserver",
"booking_not_found_error": "Impossible de trouver la réservation",
"booking_seats_full_error": "Les places de réservation sont complètes",
"missing_payment_credential_error": "Identifiants de paiement manquants",
"missing_payment_app_id_error": "Id de l'application de paiement manquant",
"not_enough_available_seats_error": "La réservation n'a pas assez de places disponibles",
"user_redirect_title": "{{username}} est actuellement en absence de courte durée.",
"user_redirect_description": "Pendant ce temps, {{profile.username}} sera responsable de tous les rendez-vous programmés de la part de {{username}}.",
"out_of_office": "Pas au bureau",
"out_of_office_description": "Configurez des actions dans votre profil pendant votre absence.",
"send_request": "Envoyer une demande",
"start_date_and_end_date_required": "Les dates de début et de fin sont requises",
"start_date_must_be_before_end_date": "La date de début doit être antérieure à la date de fin",
"start_date_must_be_in_the_future": "La date de début doit être dans le futur",
"user_not_found": "Utilisateur introuvable",
"out_of_office_entry_already_exists": "L'entrée Pas au bureau existe déjà",
"out_of_office_id_required": "L'id de l'entrée Pas au bureau est requis",
"booking_redirect_infinite_not_allowed": "Une redirection de réservation de cet utilisateur vers vous existe déjà.",
"success_entry_created": "Création d'une nouvelle entrée réussie",
"booking_redirect_email_subject": "Notification de redirection de réservation",
"booking_redirect_email_title": "Notification de redirection de réservation",
"booking_redirect_email_description": "Vous avez reçu une redirection de réservation de {{toName}} pour que les liens vers son profil soient redirigés vers vous pendant l'intervalle de temps : ",
"success_accept_booking_redirect": "Vous avez accepté cette demande de redirection de réservation.",
"success_reject_booking_redirect": "Vous avez refusé cette demande de redirection de réservation.",
"copy_link_booking_redirect_request": "Copier le lien pour partager la demande",
"booking_redirect_request_title": "Demande de redirection de réservation",
"select_team_member": "Sélectionner un membre de l'équipe",
"going_away_title": "Vous partez ? Marquez simplement le lien de votre profil comme étant indisponible pendant un certain temps.",
"redirect_team_enabled": "Redirigez votre profil vers un autre membre de l'équipe",
"redirect_team_disabled": "Redirigez votre profil vers un autre membre de l'équipe (formule Équipe requise)",
"out_of_office_unavailable_list": "Liste des indisponibilités Pas au bureau",
"success_deleted_entry_out_of_office": "Suppression de l'entrée réussie",
"temporarily_out_of_office": "Temporairement Pas au bureau ?",
"add_a_redirect": "Ajouter une redirection",
"create_entry": "Créer une entrée",
"time_range": "Plage temporelle",
"automatically_add_all_team_members": "Ajouter tous les membres de l'équipe, y compris les futurs membres",
"redirect_to": "Redirection vers",
"having_trouble_finding_time": "Vous avez du mal à trouver une heure ?",
"show_more": "En voir plus",
"assignment_description": "Planifiez des rendez-vous lorsque tout le monde est disponible ou faites tourner les membres de votre équipe",
"lowest": "le plus bas",
"low": "bas",
"medium": "moyen",
"high": "élevé",
"Highest": "le plus élevé",
"send_booker_to": "Envoyer un utilisateur vers",
"set_priority": "Définir la priorité",
"priority_for_user": "Priorité pour {{userName}}",
"change_priority": "changer la priorité",
"field_identifiers_as_variables": "Utiliser les identifiants de champs comme variables pour la redirection de votre événement personnalisé",
"field_identifiers_as_variables_with_example": "Utiliser les identifiants de champs comme variables pour la redirection de votre événement personnalisé (par ex. : {{variable}})",
"account_already_linked": "Le compte est déjà associé",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -807,6 +807,8 @@
"additional_input_description": "דרוש מהמתזמן להזין נתונים נוספים לפני אישור התזמון",
"label": "תווית",
"placeholder": "מציין מיקום",
"display_add_to_calendar_organizer": "הצגת דוא״ל ‚הוספה ללוח השנה’ כגוף מארגן",
"reconnect_calendar_to_use": "נא לשים לב שכנראה צריך להתנתק ואז להתחבר ללוח השנה שלך מחדש כדי להשתמש ביכולת הזאת.",
"type": "סוג",
"edit": "עריכה",
"add_input": "הוספת קלט",
@@ -2258,6 +2260,8 @@
"set_priority": "הגדרת עדיפות",
"priority_for_user": "העדיפות של {{userName}}",
"change_priority": "שינוי עדיפות",
"field_identifiers_as_variables": "להשתמש במזהי השדות כמשתנים להפניות האירועים בהתאמה אישית",
"field_identifiers_as_variables_with_example": "להשתמש במזהי השדות כמשתנים להפניות האירועים בהתאמה אישית (למשל: {{variable}})",
"account_already_linked": "החשבון כבר מקושר",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -13,10 +13,17 @@
"reset_password_subject": "{{appName}}: istruzioni per reimpostare la password",
"verify_email_subject": "{{appName}}: verifica il tuo account",
"check_your_email": "Controlla la tua e-mail",
"old_email_address": "Vecchio indirizzo email",
"new_email_address": "Nuovo indirizzo email",
"verify_email_page_body": "Abbiamo inviato un'e-mail a {{email}}. È importante verificare il tuo indirizzo e-mail per garantire la consegna delle e-mail e delle notifiche del calendario {{appName}}.",
"verify_email_banner_body": "Verifica il tuo indirizzo e-mail per garantire la consegna delle e-mail e delle notifiche del calendario",
"verify_email_email_header": "Verifica il tuo indirizzo e-mail",
"verify_email_email_button": "Verifica l'e-mail",
"verify_email_change_description": "Hai recentemente chiesto di cambiare l'indirizzo email che utilizzi per accedere al tuo account {{appName}}. Fai clic sul pulsante qui sotto pe confermare il tuo nuovo indirizzo email.",
"verify_email_change_success_toast": "Indirizzo email aggiornato: {{email}}",
"verify_email_change_failure_toast": "Aggiornamento email non riuscito.",
"change_of_email": "Verifica il tuo nuovo indirizzo email per {{appName}}",
"change_of_email_toast": "Abbiamo inviato un link di verifica a {{email}}. Aggiorneremo il tuo indirizzo email quando farai clic sul link.",
"copy_somewhere_safe": "Salva questa chiave API in un posto sicuro. Non potrai visualizzarla in seguito.",
"verify_email_email_body": "Verifica il tuo indirizzo e-mail facendo clic sul pulsante qui sotto.",
"verify_email_by_code_email_body": "Verifica il tuo indirizzo e-mail utilizzando il codice sottostante.",
@@ -56,6 +63,17 @@
"a_refund_failed": "Un rimborso non è riuscito",
"awaiting_payment_subject": "In attesa del pagamento: {{title}} il {{date}}",
"meeting_awaiting_payment": "La tua riunione è in attesa di pagamento",
"dark_theme_contrast_error": "Il colore del tema scuro non supera il controllo del contrasto. Ti consigliamo di cambiare questo colore in modo che i pulsanti siano più visibili.",
"light_theme_contrast_error": "Il colore del tema chiaro non supera il controllo del contrasto. Ti consigliamo di cambiare questo colore in modo che i pulsanti siano più visibili.",
"payment_not_created_error": "Non è stato possibile creare il pagamento",
"couldnt_charge_card_error": "Non è stato possibile addebitare la carta per il pagamento",
"no_available_users_found_error": "Nessun utente disponibile. Puoi provare un altro intervallo temporale?",
"request_body_end_time_internal_error": "Errore interno. La richiesta non contiene l'ora finale",
"create_calendar_event_error": "Impossibile creare l'evento nel calendario dell'organizzatore",
"update_calendar_event_error": "Impossibile aggiornare l'evento nel calendario.",
"delete_calendar_event_error": "Impossibile eliminare l'evento dal calendario.",
"already_signed_up_for_this_booking_error": "Ti sei già registrato per questa prenotazione.",
"hosts_unavailable_for_booking": "Alcuni degli organizzatori non sono disponibili per la prenotazione.",
"help": "Guida",
"price": "Prezzo",
"paid": "Pagato",
@@ -101,6 +119,7 @@
"requested_to_reschedule_subject_attendee": "Azione richiesta per la riprogrammazione: prenota un nuovo orario per {{eventType}} con {{name}}",
"hi_user_name": "Ciao {{name}}",
"ics_event_title": "{{eventType}} con {{name}}",
"please_book_a_time_sometime_later": "Oops, non siamo riusciti a collegarti. Pianifica una chiamata futura",
"new_event_subject": "Richiesta di nuovo evento: {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "Entra da {{entryPoint}}",
"notes": "Osservazioni",
@@ -117,15 +136,20 @@
"meeting_id": "Meeting ID",
"meeting_password": "Password del meeting",
"meeting_url": "URL del Meeting",
"meeting_url_not_found": "URL della riunione non trovato",
"token_not_found": "Token non trovato",
"some_other_host_already_accepted_the_meeting": "Qualche altro host ha già accettato la riunione. Vuoi comunque partecipare? <1>Prosegui con la riunione</1>",
"meeting_request_rejected": "La tua richiesta di riunione è stata rifiutata",
"rejected_event_type_with_organizer": "Rifiutato: {{eventType}} con {{organizer}} il {{date}}",
"hi": "Ciao",
"join_team": "Unisciti al team",
"manage_this_team": "Gestisci questo team",
"team_info": "Informazioni sul team",
"join_meeting": "Partecipa alla riunione",
"request_another_invitation_email": "Se preferisci non usare {{toEmail}} come email {{appName}} o già hai un account {{appName}}, per favore richiedi un altro invito a quella email.",
"you_have_been_invited": "Sei stato invitato ad unirti al team {{teamName}}",
"user_invited_you": "{{user}} ti ha invitato a unirti a {{entity}} {{team}} su {{appName}}",
"user_invited_you_to_subteam": "{{user}} ti ha invitato a entrare nel team {{team}} dell'organizzazione {{parentTeamName}} su {{appName}}",
"hidden_team_member_title": "Sei nascosto in questo team",
"hidden_team_member_message": "Il tuo posto non è pagato. Passa a Pro oppure informa il proprietario del team che può pagare il tuo posto.",
"hidden_team_owner_message": "Per utilizzare i team ti occorre un account Pro. Sarai nascosto finché non cambierai piano.",
@@ -229,6 +253,7 @@
"reset_your_password": "Imposta la nuova password seguendo le istruzioni che ti sono state inviate via e-mail.",
"email_change": "Accedi di nuovo con il nuovo indirizzo e-mail e la nuova password.",
"create_your_account": "Crea il tuo account",
"create_your_calcom_account": "Crea il tuo accout Cal.com",
"sign_up": "Registrati",
"youve_been_logged_out": "Hai fatto il log out",
"hope_to_see_you_soon": "Speriamo di rivederti presto!",
@@ -266,6 +291,9 @@
"nearly_there_instructions": "Infine: qualche informazione su di te e una foto sono utili per ottenere prenotazioni e per far capire alle persone con chi stanno prenotando.",
"set_availability_instructions": "Definisci intervalli di tempo di quando sei disponibile su una base ricorrente. Puoi crearne di più in seguito e assegnarli a calendari diversi.",
"set_availability": "Imposta la tua disponibilità",
"set_availbility_description": "Programma gli orari in cui vuoi essere prenotato.",
"share_a_link_or_embed": "Condividi o pubblica un link",
"share_a_link_or_embed_description": "Condividi il tuo link {{appName}} o inseriscilo nel tuo sito.",
"availability_settings": "Impostazioni di disponibilità",
"continue_without_calendar": "Continua senza calendario",
"continue_with": "Continua con {{appName}}",
@@ -281,6 +309,7 @@
"welcome_to_calcom": "Benvenuto in {{appName}}",
"welcome_instructions": "Dimmi come chiamarti e facci sapere in che timezone sei. Potrai cambiarlo dopo.",
"connect_caldav": "Connetti al server CalDav",
"connect_ics_feed": "Collega a un feed ICS",
"connect": "Connetti",
"try_for_free": "Provalo gratuitamente",
"create_booking_link_with_calcom": "Crea il tuo link di prenotazione con {{appName}}",
@@ -419,6 +448,7 @@
"browse_api_documentation": "Sfoglia la nostra documentazione API",
"leverage_our_api": "Sfrutta la nostra API per il pieno controllo e la personalizzabilità.",
"create_webhook": "Crea Webhook",
"instant_meeting": "Riunione immediata creata",
"booking_cancelled": "Prenotazione cancellata",
"booking_rescheduled": "Prenotazione Riprogrammata",
"recording_ready": "Il link per scaricare la registrazione è pronto",
@@ -533,11 +563,13 @@
"enter_number_between_range": "Insrisci un numero compreso tra 1 e {{maxOccurences}}",
"email_address": "Indirizzo email",
"enter_valid_email": "Immettere un indirizzo e-mail valido",
"please_schedule_future_call": " Pianifica una chiamata futura se non siamo disponibili entro {{seconds}} secondi",
"location": "Luogo",
"address": "Indirizzo",
"enter_address": "Immettere l'indirizzo",
"in_person_attendee_address": "Di persona (indirizzo del partecipante)",
"yes": "Sì",
"no": "No",
"additional_notes": "Note aggiuntive",
"booking_fail": "Impossibile prenotare la riunione.",
"reschedule_fail": "Impossibile riprogrammare la riunione.",
@@ -591,6 +623,7 @@
"new_member": "Nuovo Membro",
"invite": "Invita",
"add_team_members": "Aggiungi membri del team",
"add_org_members": "Aggiungi membri",
"add_team_members_description": "Invita altri utenti a unirsi al tuo team",
"add_team_member": "Aggiungi un membro del team",
"invite_new_member": "Invita un nuovo membro",
@@ -605,6 +638,7 @@
"hide_book_a_team_member_description": "Nasconde il pulsante Prenota un membro del team nelle pagine pubbliche.",
"danger_zone": "Zona pericolosa",
"account_deletion_cannot_be_undone": "Attenzione. Non è possibile annullare l'eliminazione dell'account.",
"team_deletion_cannot_be_undone": "Attenzione. Non è possibile annullare l'eliminazione del team",
"back": "Indietro",
"cancel": "Annulla",
"cancel_all_remaining": "Annulla tutti i rimanenti",
@@ -655,6 +689,7 @@
"default_duration": "Durata predefinita",
"default_duration_no_options": "Scegliere prima delle durate disponibili",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"minutes": "Minuti",
"round_robin": "Round Robin",
"round_robin_description": "Ciclo di riunioni tra più membri del team.",
@@ -667,6 +702,7 @@
"add_members": "Aggiungi membri...",
"no_assigned_members": "Nessun membro assegnato",
"assigned_to": "Assegnato a",
"you_must_be_logged_in_to": "Devi effettuare l'accesso a {{url}}",
"start_assigning_members_above": "Inizia ad assegnare membri in alto",
"locked_fields_admin_description": "I membri non potranno fare modifiche",
"locked_fields_member_description": "Questa opzione è stata bloccata dall'amministratore del team",
@@ -771,6 +807,7 @@
"additional_input_description": "Chiedi al creatore del programma di inserire dei campi di input aggiuntivi prima che la prenotazione sia confermata",
"label": "Etichetta",
"placeholder": "Segnaposto",
"display_add_to_calendar_organizer": "Visualizza l'email \"Aggiungi al calendario\" come organizzatore",
"type": "Tipo",
"edit": "Modifica",
"add_input": "Aggiungi un input",
@@ -779,6 +816,9 @@
"requires_confirmation_description": "La prenotazione deve essere confermata manualmente prima di essere inviata alle integrazioni e che venga inviata una e-mail di conferma.",
"recurring_event": "Evento ricorrente",
"recurring_event_description": "Le persone possono iscriversi a eventi ricorrenti",
"cannot_be_used_with_paid_event_types": "Non può essere utilizzato con i tipi di evento a pagamento",
"warning_payment_instant_meeting_event": "Le riunioni immediate non sono ancora disponibili con gli eventi ricorrenti e le app di pagamento",
"warning_instant_meeting_experimental": "Sperimentale: gli eventi Riunione immediata al momento sono in fase di sperimentazione.",
"starting": "Inizio",
"disable_guests": "Disabilita Ospiti",
"disable_guests_description": "Disabilita l'aggiunta di ulteriori ospiti durante la prenotazione.",
@@ -787,6 +827,7 @@
"private_link_label": "Link privato",
"private_link_hint": "Il tuo link privato verrà rigenerato dopo ogni utilizzo",
"copy_private_link": "Copia link privato",
"copy_private_link_to_event": "Copia link privato nell'evento",
"private_link_description": "Genera un URL privato da condividere senza mostrare il tuo nome utente {{appName}}",
"invitees_can_schedule": "Gli inviti possono programmare",
"date_range": "Range di date",
@@ -846,6 +887,7 @@
"next_step": "Salta passo",
"prev_step": "Passo precedente",
"install": "Installa",
"start_paid_trial": "Inizia la prova gratuita",
"installed": "Installato",
"active_install_one": "{{count}} installazione attiva",
"active_install_other": "{{count}} installazioni attive",
@@ -860,6 +902,7 @@
"toggle_calendars_conflict": "Attiva/disattiva i calendari in cui desideri controllare i conflitti per evitare doppie prenotazioni.",
"connect_additional_calendar": "Collega calendario aggiuntivo",
"calendar_updated_successfully": "Calendario aggiornato correttamente",
"check_here": "Controlla qui",
"conferencing": "Conferenze",
"calendar": "Calendario",
"payments": "Pagamenti",
@@ -1031,6 +1074,9 @@
"user_impersonation_heading": "Accesso con altra identità",
"user_impersonation_description": "Consente al nostro team di supporto di effettuare l'accesso temporaneo a tuo nome per risolvere rapidamente gli eventuali problemi da te segnalati.",
"team_impersonation_description": "Consente ai proprietari/amministratori del tuo team di accedere temporaneamente con il tuo nome.",
"cal_signup_description": "Gratuito per le singole pesone. Piani collettivi per le funzionalità di collaborazione.",
"make_org_private": "Rendi l'organizzazione privata",
"make_org_private_description": "Quando questa opzione è attivata, i membri della tua organizzazione non possono vedere gli altri membri.",
"make_team_private": "Rendi il team privato",
"make_team_private_description": "Quando questa opzione è attivata, i membri del tuo team non potranno vedere gli altri membri del team.",
"you_cannot_see_team_members": "Non è possibile vedere tutti i membri di un team privato.",
@@ -1090,6 +1136,7 @@
"developer_documentation": "Documentazione sviluppatore",
"get_in_touch": "Contattaci",
"contact_support": "Contatta il supporto",
"premium_support": "Assistenza Premium",
"community_support": "Supporto della community",
"feedback": "Feedback",
"submitted_feedback": "Grazie per il tuo feedback!",
@@ -1253,6 +1300,7 @@
"default_calendar_selected": "Calendario predefinito",
"hide_from_profile": "Nascondi nel profilo",
"event_setup_tab_title": "Impostazione evento",
"availability_not_found_in_schedule_error": "Nessuna disponibilità trovata nel programma",
"event_limit_tab_title": "Limiti",
"event_limit_tab_description": "Con quale frequenza è possibile fare una prenotazione",
"event_advanced_tab_description": "Impostazioni calendario e altro...",
@@ -1273,6 +1321,7 @@
"do_this": "Esegui questa operazione",
"turn_off": "Disattiva",
"turn_on": "Attiva",
"cancelled_bookings_cannot_be_rescheduled": "Le prenotazioni annullate non possono essere riprogrammate",
"settings_updated_successfully": "Impostazioni aggiornate correttamente",
"error_updating_settings": "Errore durante l'aggiornamento delle impostazioni",
"personal_cal_url": "Il mio URL personale di {{appName}}",
@@ -1296,6 +1345,7 @@
"customize_your_brand_colors": "Personalizza la tua pagina di prenotazione con i colori del tuo marchio.",
"pro": "Pro",
"removes_cal_branding": "Rimuove tutte le scritte con il marchio {{appName}}, ad es., 'Powered by {{appName}}.'",
"instant_meeting_with_title": "Riunione immediata con {{name}}",
"profile_picture": "Immagine del profilo",
"upload": "Carica",
"add_profile_photo": "Aggiungi foto del profilo",
@@ -1351,6 +1401,7 @@
"event_name_info": "Nome del tipo di evento",
"event_date_info": "Data dell'evento",
"event_time_info": "Ora di inizio dell'evento",
"event_type_not_found": "EventType non trovato",
"location_variable": "Luogo",
"location_info": "Luogo dell'evento",
"additional_notes_variable": "Note aggiuntive",
@@ -1393,6 +1444,7 @@
"slot_length": "Durata della fascia oraria",
"booking_appearance": "Aspetto prenotazione",
"appearance_team_description": "Gestisci le impostazioni di aspetto delle prenotazioni del tuo team",
"appearance_org_description": "Imposta l'aspetto delle prenotazioni per la tua organizzazione",
"only_owner_change": "Solo il proprietario del team può apportare modifiche alla prenotazione del team ",
"team_disable_cal_branding_description": "Rimuove tutte le scritte con il marchio {{appName}}, ad es., 'Powered by {{appName}}'",
"invited_by_team": "{{teamName}} ti ha invitato a unirti al team come {{role}}",
@@ -1446,6 +1498,7 @@
"password_updated": "Password aggiornata!",
"pending_payment": "Pagamento in sospeso",
"pending_invites": "Inviti in sospeso",
"pending_organization_invites": "Inviti in sospeso dell'organizzazione",
"not_on_cal": "Non presente su {{appName}}",
"no_calendar_installed": "Nessun calendario installato",
"no_calendar_installed_description": "Non hai ancora collegato alcun calendario",
@@ -1457,6 +1510,8 @@
"report_app": "Segnala app",
"limit_booking_frequency": "Limita frequenza di prenotazione",
"limit_booking_frequency_description": "Indica quante volte è possibile prenotare questo evento",
"limit_booking_only_first_slot": "Limita prenotazione solo prima fascia",
"limit_booking_only_first_slot_description": "Consenti la prenotazione solo nella prima fascia oraria di ogni giorno",
"limit_total_booking_duration": "Limita durata totale prenotazione",
"limit_total_booking_duration_description": "Indicare la quantità di tempo totale disponibile per prenotare questo evento",
"add_limit": "Aggiungi limite",
@@ -1524,10 +1579,14 @@
"your_org_disbanded_successfully": "La tua organizzazione è stata sciolta",
"error_creating_team": "Errore durante la creazione del team",
"you": "Tu",
"or_continue_with": "Oppure continua con",
"resend_email": "Invia di nuovo l'e-mail",
"member_already_invited": "Membro già invitato",
"already_in_use_error": "Nome utente già in uso",
"enter_email_or_username": "Immettere un indirizzo e-mail o un nome utente",
"enter_email": "Inserisci un'email",
"enter_emails": "Inserisci email",
"too_many_invites": "Puoi invitare un massimo di {{nbUsers}} utenti alla volta.",
"team_name_taken": "Nome già utilizzato",
"must_enter_team_name": "Necessario immettere un nome per il team",
"team_url_required": "Immettere un URL per il team",
@@ -1607,6 +1666,7 @@
"individual": "Persona",
"all_bookings_filter_label": "Tutte le prenotazioni",
"all_users_filter_label": "Tutti gli utenti",
"all_event_types_filter_label": "Tutti i tipi di evento",
"your_bookings_filter_label": "Le tue prenotazioni",
"meeting_url_variable": "URL della riunione",
"meeting_url_info": "URL dell'evento, dell'incontro o della conferenza",
@@ -1703,9 +1763,11 @@
"organizer_timezone": "Fuso orario organizzatore",
"email_user_cta": "Visualizza invito",
"email_no_user_invite_heading_team": "Hai ricevuto un invito a entrare a far parte del team di {{appName}}",
"email_no_user_invite_heading_subteam": "Sei stato invitato a partecipare a un team dell'organizzazione {{parentTeamName}}",
"email_no_user_invite_heading_org": "Hai ricevuto un invito a entrare a far parte dell'organizzazione {{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_team": "{{invitedBy}} ti ha invitato a unirti al suo team `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette a te e al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
"email_user_invite_subheading_subteam": "{{invitedBy}} ti ha invitato a entrare nel team {{teamName}} della sua organizzazione {{parentTeamName}} su {{appName}}. {{appName}} è lo strumento di programmazione degli eventi che consente a te e al tuo team di programmare riunioni senza lo scambio di infinite email.",
"email_user_invite_subheading_org": "{{invitedBy}} ti ha invitato a unirti alla sua organizzazione `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette a te e alla tua organizzazione di pianificare le riunioni senza scambiare decine di e-mail.",
"email_no_user_invite_steps_intro": "Ti assisteremo nei passaggi iniziali e potrai rapidamente pianificare eventi per il/la {{entity}} senza fatica.",
"email_no_user_step_one": "Scegli il tuo nome utente",
@@ -1838,7 +1900,9 @@
"review_event_type": "Rivedi tipo di evento",
"looking_for_more_analytics": "Hai bisogno di più dati analitici?",
"looking_for_more_insights": "Ti servono più Insights?",
"filters": "Filtri",
"add_filter": "Aggiungi filtro",
"remove_filters": "Cancella tutti i filtri",
"select_user": "Seleziona utente",
"select_event_type": "Seleziona tipo di evento",
"select_date_range": "Seleziona intervallo di date",
@@ -1929,6 +1993,7 @@
"connect_google_workspace": "Collega Google Workspace",
"google_workspace_admin_tooltip": "Per usare questa funzione è necessario essere amministratori di Workspace",
"first_event_type_webhook_description": "Crea il primo webhook per questo tipo di evento",
"create_instant_meeting_webhook_description": "Crea il tuo primo webhook con \"Riunione immediata creata\" come attivatore per questo tipo di evento",
"install_app_on": "Installa l'app su",
"create_for": "Crea per",
"currency": "Valuta",
@@ -1993,6 +2058,8 @@
"add_times_to_your_email": "Seleziona alcuni orari disponibili e incorporali nella tua e-mail",
"select_time": "Seleziona l'ora",
"select_date": "Seleziona la data",
"connecting_you_to_someone": "Ti stiamo collegando con qualcuno.",
"please_do_not_close_this_tab": "Non chiudere questa scheda",
"see_all_available_times": "Vedi tutti gli orari disponibili",
"org_team_names_example_1": "ad es., team di marketing",
"org_team_names_example_2": "ad es., team vendite",
@@ -2010,8 +2077,12 @@
"description_requires_booker_email_verification": "Per garantire la verifica dell'indirizzo e-mail della persona che prenota prima di panificare eventi",
"requires_confirmation_mandatory": "I messaggi di testo possono solo essere inviati ai partecipanti quando il tipo di evento richiede una conferma.",
"organizations": "Organizzazioni",
"upload_cal_video_logo": "Carica il logo Cal Video",
"update_cal_video_logo": "Aggiorna il logo Cal Video",
"cal_video_logo_upload_instruction": "Per fare in modo che il tuo logo sia visibile sullo sfondo scuro del video Cal, carica un'immagine di colore chiaro in formato PNG o SVG per mantenere la trasparenza.",
"org_admin_other_teams": "Atri team",
"org_admin_other_teams_description": "Qui puoi vedere i team di cui non fai parte all'interno della tua organizzazione. Puoi entrare a far parte di tali team se necessario.",
"not_part_of_org": "Non fai parte di alcuna organizzazione",
"no_other_teams_found": "Nessun altro team trovato",
"no_other_teams_found_description": "Non ci sono altri team in questa organizzazione.",
"attendee_first_name_variable": "Nome del partecipante",
@@ -2047,7 +2118,9 @@
"org_error_processing": "Si è verificato un errore durante l'elaborazione di questa organizzazione",
"orgs_page_description": "Un elenco di tutte le organizzazioni. Accettando un'organizzazione, tutti gli utenti con il rispettivo dominio e-mail potranno registrarsi SENZA verificare l'indirizzo e-mail.",
"unverified": "Non verificato",
"verified": "Verificato",
"dns_missing": "DNS mancante",
"dns_configured": "DNS Configurato",
"mark_dns_configured": "Contrassegna DNS come configurato",
"value": "Valore",
"your_organization_updated_sucessfully": "L'organizzazione è stata aggiornata correttamente",
@@ -2057,8 +2130,11 @@
"oAuth": "OAuth",
"recently_added": "Aggiunti di recente",
"connect_all_calendars": "Collega tutti i tuoi calendari",
"connect_all_calendars_description": "{{appName}} legge la disponibilità da tutti i tuoi calendari esistenti.",
"workflow_automation": "Automazione dei flussi di lavoro",
"workflow_automation_description": "Personalizza la tua esperienza di programmazione con i flussi di lavoro",
"scheduling_for_your_team": "Automazione dei flussi di lavoro",
"scheduling_for_your_team_description": "Usa la programmazione collettiva e round robin per il tuo team",
"no_members_found": "Nessun membro trovato",
"event_setup_length_error": "Impostazione evento: la durata deve essere di almeno 1 minuto.",
"availability_schedules": "Calendario disponibilità",
@@ -2091,10 +2167,100 @@
"overlay_my_calendar": "Sovrapponi il mio calendario",
"overlay_my_calendar_toc": "Collegando il tuo calendario, accetti la nostra informativa sulla privacy e i termini di servizio. Puoi revocare l'accesso in qualsiasi momento.",
"view_overlay_calendar_events": "Visualizza gli eventi del tuo calendario per prevenire prenotazioni in conflitto.",
"join_event_location": "Partecipa a {{eventLocationType}}",
"troubleshooting": "Risoluzione problemi",
"calendars_were_checking_for_conflicts": "Calendari in cui stiamo cercando possibili conflitti",
"availabilty_schedules": "Programmi di disponibilità",
"manage_calendars": "Gestisci calendari",
"manage_availability_schedules": "Gestisci programmi di disponibilità",
"lock_timezone_toggle_on_booking_page": "Blocca fuso orario nella pagina di prenotazione",
"description_lock_timezone_toggle_on_booking_page": "Per bloccare il fuso orario nella pagina di prenotazione, utile per gli eventi di persona.",
"event_setup_multiple_payment_apps_error": "Puoi avere solo un'app di pagamento abilitata per ciascun tipo di evento.",
"number_in_international_format": "Inserisci il numero in formato internazionale.",
"install_calendar": "Installa Calendario",
"branded_subdomain": "Sottodominio del marchio",
"branded_subdomain_description": "Crea un sottodominio per il tuo marchio, come acme.cal.com",
"org_insights": "Approfondimenti a livello di organizzazione",
"org_insights_description": "Comprendi come la tua organizzazione utilizza il tempo",
"extensive_whitelabeling": "Assistenza per l'onboarding e supporto tecnico dedicati",
"extensive_whitelabeling_description": "Personalizza la tua esperienza di programmazione con il tuo logo, colori e altro ancora",
"unlimited_teams": "Numero illimitato di team",
"unlimited_teams_description": "Aggiungi alla tua organizzazione tutti i sotto-team necessari",
"unified_billing": "Fatturazione unificata",
"unified_billing_description": "Indica una singola carta di credito per pagare tutti gli abbonamenti del tuo team",
"advanced_managed_events": "Tipi di evento gestiti avanzati",
"advanced_managed_events_description": "Indica una singola carta di credito per pagare tutti gli abbonamenti del tuo team",
"enterprise_description": "Passa a Enterprise per creare la tua organizzazione",
"create_your_org": "Crea la tua organizzazione",
"create_your_org_description": "Passa a Enterprise per avere sottodominio, fatturazione unificata, approfondimenti, personalizzazione completa e altro ancora",
"other_payment_app_enabled": "Puoi attivare solo un'app di pagamento per ciascun tipo di evento",
"admin_delete_organization_description": "<ul><li>Verranno eliminati anche i team che sono membri di questa organizzazione, insieme ai rispettivi tipi di evento</li><li>Gli utenti che facevano parte dell'organizzazione non verranno eliminati e anche i loro tipi di evento rimarranno inalterati.</li><li>I nomi utente saranno modificati in modo che possano esistere al di fuori dell'organizzazione</li></ul>",
"admin_delete_organization_title": "Eliminare {{organizationName}}?",
"published": "Pubblicato",
"unpublished": "Pubblicazione annullata",
"publish": "Pubblica",
"org_publish_error": "Non è stato possibile pubblicare l'organizzazione",
"troubleshooter_tooltip": "Apri lo strumento di soluzione dei problemi per individuare il problema del tuo programma",
"need_help": "Hai bisogno di aiuto?",
"troubleshooter": "Risoluzione problemi",
"please_install_a_calendar": "Installa un calendario",
"instant_tab_title": "Prenotazione immediata",
"instant_event_tab_description": "Consenti alle persone di prenotare immediatamente",
"uprade_to_create_instant_bookings": "Passa a Enterprise e permetti agli ospiti di partecipare a una chiamata immediata a cui i partecipanti possono accedere direttamente. Ciò è riservato agli eventi per team",
"dont_want_to_wait": "Non vuoi aspettare?",
"meeting_started": "Riunione iniziata",
"pay_and_book": "Pagare per prenotare",
"booking_not_found_error": "Prenotazione non trovata",
"booking_seats_full_error": "I posti sono esauriti",
"missing_payment_credential_error": "Credenziali di pagamento mancanti",
"missing_payment_app_id_error": "ID dell'app di pagamento mancante",
"not_enough_available_seats_error": "La prenotazione non ha abbastanza posti disponibili",
"user_redirect_title": "{{username}} è attualmente assente per un breve periodo.",
"user_redirect_description": "Nel frattempo, {{profile.username}} sarà responsabile di tutte le nuove riunioni programmate per conto di {{username}}.",
"out_of_office": "Assente",
"out_of_office_description": "Configura le azioni nel tuo profilo mentre sei assente.",
"send_request": "Invia richiesta",
"start_date_and_end_date_required": "Data iniziale e data finale sono obbligatorie",
"start_date_must_be_before_end_date": "La data iniziale deve precedere quella finale",
"start_date_must_be_in_the_future": "La data iniziale deve essere nel futuro",
"user_not_found": "Utente non trovato",
"out_of_office_entry_already_exists": "La voce per l'assenza esiste già",
"out_of_office_id_required": "L'ID della voce per l'assenza è obbligatorio",
"booking_redirect_infinite_not_allowed": "Esiste già un reindirizzamento della prenotazione dall'utente a te.",
"success_entry_created": "Nuova voce creata correttamente",
"booking_redirect_email_subject": "Notifica di reindirizzamento prenotazione",
"booking_redirect_email_title": "Notifica di reindirizzamento prenotazione",
"booking_redirect_email_description": "Hai ricevuto un reindirizzamento delle prenotazioni da {{toName}}. I link del suo profilo rimanderanno ai tuoi, nel periodo: ",
"success_accept_booking_redirect": "Hai accettato questa richiesta di reindirizzamento delle prenotazioni.",
"success_reject_booking_redirect": "Hai rifiutato questa richiesta di reindirizzamento delle prenotazioni.",
"copy_link_booking_redirect_request": "Copia link per condividere la richiesta",
"booking_redirect_request_title": "Richiesta di reindirizzamento delle prenotazioni",
"select_team_member": "Seleziona un membro del team",
"going_away_title": "Devi assentarti? Segnala che il link al tuo profilo non è disponibile per un periodo.",
"redirect_team_enabled": "Rendirizza il tuo profilo a un altro membro del team",
"redirect_team_disabled": "Reindirizza il tuo profilo a un altro membro del team (è necessario il piano Team)",
"out_of_office_unavailable_list": "Elenco di indisponibilità per assenza",
"success_deleted_entry_out_of_office": "Voce eliminata",
"temporarily_out_of_office": "Temporaneamente assente?",
"add_a_redirect": "Aggiungi un reindirizzamento",
"create_entry": "Crea una voce",
"time_range": "Intervallo temporale",
"automatically_add_all_team_members": "Aggiungi tutti i membri del team, compresi quelli futuri",
"redirect_to": "Reindirizza a",
"having_trouble_finding_time": "Non riesci a trovare un orario?",
"show_more": "Mostra altro",
"assignment_description": "Pianifica le riunioni quando tutti sono disponibili, oppure ruota tra i membri del tuo team",
"lowest": "più basso",
"low": "basso",
"medium": "medio",
"high": "alto",
"Highest": "più alto",
"send_booker_to": "Rimanda la persona che prenota a",
"set_priority": "Imposta priorità",
"priority_for_user": "Priorità per {{userName}}",
"change_priority": "cambia priorità",
"field_identifiers_as_variables": "Usa gli identificatori di campo come variabili per il reindirizzamento dell'evento personalizzato",
"field_identifiers_as_variables_with_example": "Usa gli identificatori di campo come variabili per il reindirizzamento dell'evento personalizzato (es: {{variable}})",
"account_already_linked": "L'account è già collegato",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Aggiungi le tue nuove stringhe qui sopra ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -13,10 +13,17 @@
"reset_password_subject": "{{appName}}: Instruções para redefinir sua senha",
"verify_email_subject": "{{appName}}: Verificar sua conta",
"check_your_email": "Verifique seu e-mail",
"old_email_address": "E-mail antigo",
"new_email_address": "Novo e-mail",
"verify_email_page_body": "Enviamos um e-mail para {{email}}. É importante verificar seu endereço de e-mail para garantir o melhor envio de e-mail e calendário do {{appName}}.",
"verify_email_banner_body": "Verifique seu endereço de e-mail para garantir o fornecimento do melhor e-mail e calendário",
"verify_email_email_header": "Verifique seu endereço de e-mail",
"verify_email_email_button": "Verificar e-mail",
"verify_email_change_description": "Você solicitou recentemente a alteração do endereço de e-mail que você usa para entrar em sua conta {{appName}}. Por favor, clique no botão abaixo para confirmar o seu novo endereço de e-mail.",
"verify_email_change_success_toast": "Seu e-mail foi atualizado para {{email}}",
"verify_email_change_failure_toast": "Não foi possível atualizar o e-mail.",
"change_of_email": "Verifique seu novo endereço de e-mail da sua conta {{appName}}",
"change_of_email_toast": "Enviamos um link de verificação para {{email}}. Atualizaremos seu endereço de e-mail quando você clicar neste link.",
"copy_somewhere_safe": "Salve esta chave de API em um lugar seguro. Você não será capaz de vê-la novamente.",
"verify_email_email_body": "Por favor, verifique seu endereço de e-mail clicando no botão abaixo.",
"verify_email_by_code_email_body": "Por favor, verifique seu endereço de e-mail usando o código abaixo.",
@@ -56,6 +63,17 @@
"a_refund_failed": "Um reembolso falhou",
"awaiting_payment_subject": "Aguardando pagamento: {{title}} em {{date}}",
"meeting_awaiting_payment": "Sua reunião está aguardando o pagamento",
"dark_theme_contrast_error": "A cor do tema escuro não foi aprovada pela verificação de contraste. Recomendamos que você altere esta cor para que seus botões fiquem mais visíveis.",
"light_theme_contrast_error": "A cor do tema claro não foi aprovada pela verificação de contraste. Recomendamos que você altere esta cor para que seus botões fiquem mais visíveis.",
"payment_not_created_error": "O pagamento não pôde ser criado",
"couldnt_charge_card_error": "Não foi possível cobrar o cartão para o pagamento",
"no_available_users_found_error": "Nenhum usuário disponível foi encontrado. Você poderia tentar outra faixa horária?",
"request_body_end_time_internal_error": "Erro interno. O corpo da solicitação não contém a hora final",
"create_calendar_event_error": "Não foi possível criar um evento no calendário do organizador",
"update_calendar_event_error": "Não foi possível atualizar o evento.",
"delete_calendar_event_error": "Não foi possível deletar o evento.",
"already_signed_up_for_this_booking_error": "Você já está inscrito para esta reserva.",
"hosts_unavailable_for_booking": "Alguns dos anfitriões não estão disponíveis para reserva.",
"help": "Ajuda",
"price": "Preço",
"paid": "Pago",
@@ -101,6 +119,7 @@
"requested_to_reschedule_subject_attendee": "Ação de reagendamento necessária: reserve outro horário para {{eventType}} com {{name}}",
"hi_user_name": "Olá {{name}}",
"ics_event_title": "{{eventType}} com {{name}}",
"please_book_a_time_sometime_later": "Ops, não foi possível conectá-lo desta vez. Por favor, agende uma chamada mais tarde",
"new_event_subject": "Novo evento: {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "Junte-se através de {{entryPoint}}",
"notes": "Notas",
@@ -117,15 +136,20 @@
"meeting_id": "ID da reunião",
"meeting_password": "Senha da reunião",
"meeting_url": "URL da reunião",
"meeting_url_not_found": "URL da reunião não encontrada",
"token_not_found": "Token não encontrado",
"some_other_host_already_accepted_the_meeting": "Algum outro anfitrião já aceitou a reunião. Ainda deseja entrar? <1>Entrar na reunião</1>",
"meeting_request_rejected": "A sua solicitação de reunião foi rejeitada",
"rejected_event_type_with_organizer": "Rejeitado: {{eventType}} com {{organizer}} em {{date}}",
"hi": "Olá",
"join_team": "Entrar no time",
"manage_this_team": "Gerenciar esta equipe",
"team_info": "Informações da equipe",
"join_meeting": "Entrar na reunião",
"request_another_invitation_email": "Se não quiser utilizar {{toEmail}} como email do {{appName}}, ou já tem uma conta em {{appName}}, por favor peça outro convite para esse email.",
"you_have_been_invited": "Você recebeu um convite para se juntar ao time {{teamName}}",
"user_invited_you": "{{user}} convidou para ingressar na equipe {{team}} de {{entity}} em {{appName}}",
"user_invited_you_to_subteam": "{{user}} convidou você para se juntar à equipe {{team}} da organização {{parentTeamName}} no {{appName}}",
"hidden_team_member_title": "Você está oculto nesta equipe",
"hidden_team_member_message": "Seu assento não é pago, atualize para a versão Pro ou avise o proprietário da equipe de que é possível pagar pelo seu assento.",
"hidden_team_owner_message": "É preciso ter uma conta Pro para usar equipes, você permanecerá oculto até a atualização.",
@@ -229,6 +253,7 @@
"reset_your_password": "Defina sua nova senha com as instruções enviadas para seu endereço de e-mail.",
"email_change": "Volte a entrar com seu novo endereço de e-mail e senha.",
"create_your_account": "Criar sua conta",
"create_your_calcom_account": "Crie sua conta Cal.com",
"sign_up": "Cadastro",
"youve_been_logged_out": "A sessão foi encerrada",
"hope_to_see_you_soon": "Esperamos te ver novamente em breve!",
@@ -266,6 +291,9 @@
"nearly_there_instructions": "Por último, uma breve descrição de você mesmo e uma foto ajudam a conseguir agendamentos e a mostrar às pessoas com quem elas estão agendando.",
"set_availability_instructions": "Definir períodos em que você está disponível de forma recorrente. Mais tarde você poderá criar outros períodos e atribuí-los a diferentes calendários.",
"set_availability": "Definir disponibilidade",
"set_availbility_description": "Defina os horários que você quer que estejam disponíveis para reservas.",
"share_a_link_or_embed": "Compartilhe um link ou incorpore",
"share_a_link_or_embed_description": "Compartilhe o link da sua conta {{appName}} ou incorpore no seu site.",
"availability_settings": "Configurações de disponibilidade",
"continue_without_calendar": "Continuar sem calendário",
"continue_with": "Continuar com {{appName}}",
@@ -281,6 +309,7 @@
"welcome_to_calcom": "Bem vindo ao {{appName}}",
"welcome_instructions": "Diga como devemos te chamar e qual o seu fuso horário. Você poderá editar isto mais tarde.",
"connect_caldav": "Conectar a um servidor CalDav",
"connect_ics_feed": "Conectar a um feed ICS",
"connect": "Conectar",
"try_for_free": "Experimente gratuitamente",
"create_booking_link_with_calcom": "Crie o seu próprio link para reservas com {{appName}}",
@@ -419,6 +448,7 @@
"browse_api_documentation": "Consulte a nossa documentação da API",
"leverage_our_api": "Utilize nossa API para controle e personalização completa.",
"create_webhook": "Criar Webhook",
"instant_meeting": "Reunião instantânea criada",
"booking_cancelled": "Reserva cancelada",
"booking_rescheduled": "Reserva Reagendada",
"recording_ready": "Link para download de gravação pronto",
@@ -533,6 +563,7 @@
"enter_number_between_range": "Por favor, insira um número entre 1 e {{maxOccurences}}",
"email_address": "Endereço de e-mail",
"enter_valid_email": "Insira um e-mail válido",
"please_schedule_future_call": " Por favor, agende uma chamada futura se não estivermos disponíveis em {{seconds}} segundos",
"location": "Local",
"address": "Local",
"enter_address": "Insira o endereço",
@@ -592,6 +623,7 @@
"new_member": "Novo Membro",
"invite": "Convidar",
"add_team_members": "Adicionar membros da equipe",
"add_org_members": "Adicionar membros",
"add_team_members_description": "Convidar outros para se juntar à sua equipe",
"add_team_member": "Adicionar membro da equipe",
"invite_new_member": "Convidar um novo membro",
@@ -606,6 +638,7 @@
"hide_book_a_team_member_description": "Oculte o botão de agendar um membro da equipe em suas páginas públicas.",
"danger_zone": "Zona de perigo",
"account_deletion_cannot_be_undone": "Cuidado. A exclusão da conta não pode ser desfeita.",
"team_deletion_cannot_be_undone": "Cuidado. A exclusão de uma equipe não pode ser desfeita",
"back": "Voltar",
"cancel": "Cancelar",
"cancel_all_remaining": "Cancelar todos os restantes",
@@ -668,6 +701,7 @@
"add_members": "Adicionar membros...",
"no_assigned_members": "Nenhum membro atribuído",
"assigned_to": "Atribuído a",
"you_must_be_logged_in_to": "Você deve estar logado em {{url}}",
"start_assigning_members_above": "Começar a atribuir os membros acima",
"locked_fields_admin_description": "Os membros não poderão editar isto",
"locked_fields_member_description": "Esta opção foi bloqueada pelo administrador da equipe",
@@ -772,6 +806,7 @@
"additional_input_description": "Solicitar que o agendador insira entradas adicionais antes que o pedido seja confirmado",
"label": "Etiqueta",
"placeholder": "Preenchimento",
"display_add_to_calendar_organizer": "Exibir o e-mail 'Adicionar ao calendário' como organizador",
"type": "Tipo",
"edit": "Editar",
"add_input": "Adicionar um campo",
@@ -13,10 +13,17 @@
"reset_password_subject": "{{appName}}: Instruções de redefinição da senha",
"verify_email_subject": "{{appName}}: Verifique a sua conta",
"check_your_email": "Verifique o seu e-mail",
"old_email_address": "E-mail antigo",
"new_email_address": "Novo e-mail",
"verify_email_page_body": "Enviámos um e-mail para {{email}}. É importante verificar o seu endereço de e-mail para garantir que receberá as comunicações de {{appName}}.",
"verify_email_banner_body": "Confirme o seu endereço de e-mail para garantir a melhor entrega possível de e-mail e de agenda",
"verify_email_email_header": "Confirme o seu endereço de e-mail",
"verify_email_email_button": "Confirmar o seu e-mail",
"verify_email_change_description": "Solicitou recentemente a alteração do endereço de e-mail que utiliza para entrar na sua conta {{appName}}. Clique no botão abaixo para confirmar o seu novo endereço de e-mail.",
"verify_email_change_success_toast": "Atualizou o seu e-mail para {{email}}",
"verify_email_change_failure_toast": "Falha ao atualizar o e-mail.",
"change_of_email": "Confirme o seu novo endereço de e-mail para {{appName}}",
"change_of_email_toast": "Enviámos uma ligação de confirmação para {{email}}. Atualizaremos o seu endereço de e-mail após clicar nessa ligação.",
"copy_somewhere_safe": "Guarde esta chave da API num local seguro. Não poderá consultar a mesma novamente.",
"verify_email_email_body": "Confirme o seu endereço de e-mail clicando no botão abaixo.",
"verify_email_by_code_email_body": "Confirme o seu endereço de e-mail utilizando o código abaixo.",
@@ -142,6 +149,7 @@
"request_another_invitation_email": "Se não quiser utilizar {{toEmail}} como email do {{appName}}, ou já tem uma conta em {{appName}}, por favor peça outro convite para esse email.",
"you_have_been_invited": "Recebeu um convite para juntar-se à equipa {{teamName}}",
"user_invited_you": "{{user}} enviou um convite para se juntar a {{team}} de {{entity}} em {{appName}}",
"user_invited_you_to_subteam": "{{user}} enviou um convite para se juntar à equipa {{team}} da organização {{parentTeamName}} em {{appName}}",
"hidden_team_member_title": "Está oculto nesta equipa",
"hidden_team_member_message": "O seu lugar não está pago, faça upgrade para PRO ou informe ao proprietário da equipa que pode pagar pelo seu lugar.",
"hidden_team_owner_message": "Precisa de uma conta Pro para usar equipas, ficará oculto até que actualize.",
@@ -245,6 +253,7 @@
"reset_your_password": "Defina a sua nova palavra-passe seguindo as instruções enviadas para o seu endereço de e-mail.",
"email_change": "Inicie sessão novamente com o seu novo endereço de e-mail e palavra-passe.",
"create_your_account": "Crie a sua conta",
"create_your_calcom_account": "Crie a sua conta Cal.com",
"sign_up": "Registar",
"youve_been_logged_out": "A sessão foi encerrada",
"hope_to_see_you_soon": "Esperamos ver-nos em breve de novo!",
@@ -282,6 +291,9 @@
"nearly_there_instructions": "Por último, uma breve descrição sobre si e uma fotografia ajudam a obter reservas e a mostrar às pessoas com quem estão a reservar.",
"set_availability_instructions": "Definir períodos em que está disponível numa base recorrente. Mais tarde poderá criar mais destes, e atribuí-los a diferentes calendários.",
"set_availability": "Definir disponibilidade",
"set_availbility_description": "Defina agendamentos para os horários que pretende que sejam reservados.",
"share_a_link_or_embed": "Partilhe uma ligação ou incorpore",
"share_a_link_or_embed_description": "Partilhe a sua ligação {{appName}} ou incorpore no seu site.",
"availability_settings": "Configurações de disponibilidade",
"continue_without_calendar": "Continuar sem calendário",
"continue_with": "Continuar com {{appName}}",
@@ -297,6 +309,7 @@
"welcome_to_calcom": "Boas-vindas ao {{appName}}",
"welcome_instructions": "Diga-nos como lhe devemos chamar e qual o seu fuso horário. Poderá editar isto mais tarde.",
"connect_caldav": "Ligar a servidor CalDav",
"connect_ics_feed": "Ligue-se a uma Feed ICS",
"connect": "Ligar",
"try_for_free": "Experimente gratuitamente",
"create_booking_link_with_calcom": "Crie a sua própria ligação de reservas com {{appName}}",
@@ -435,6 +448,7 @@
"browse_api_documentation": "Consulte a nossa documentação da API",
"leverage_our_api": "Tire partido da nossa API para controlo e personalização total.",
"create_webhook": "Criar Webhook",
"instant_meeting": "Reunião instantânea criada",
"booking_cancelled": "Reserva cancelada",
"booking_rescheduled": "Reserva Reagendada",
"recording_ready": "Ligação de transferência da gravação pronta",
@@ -549,6 +563,7 @@
"enter_number_between_range": "Introduza um número entre 1 e {{maxOccurences}}",
"email_address": "Endereço de email",
"enter_valid_email": "Insira um e-mail válido",
"please_schedule_future_call": " Se não estivermos disponíveis em {{seconds}} segundos, por favor, agende uma chamada para mais tarde",
"location": "Localização",
"address": "Local",
"enter_address": "Especifique o endereço",
@@ -608,6 +623,7 @@
"new_member": "Novo Membro",
"invite": "Convidar",
"add_team_members": "Adicionar membros da equipa",
"add_org_members": "Adicionar membros",
"add_team_members_description": "Convidar outros a juntarem-se à sua equipa",
"add_team_member": "Adicionar membro da equipa",
"invite_new_member": "Convidar um Novo Membro",
@@ -622,6 +638,7 @@
"hide_book_a_team_member_description": "Ocultar o botão Reservar um Membro da equipa das suas páginas públicas.",
"danger_zone": "Zona de perigo",
"account_deletion_cannot_be_undone": "Cuidado. A eliminação da conta não pode ser anulada.",
"team_deletion_cannot_be_undone": "Cuidado. A eliminação de uma equipa não pode ser anulada",
"back": "Anterior",
"cancel": "Cancelar",
"cancel_all_remaining": "Cancelar todos os restantes",
@@ -672,6 +689,7 @@
"default_duration": "Duração predefinida",
"default_duration_no_options": "Por favor, selecione primeiro as durações disponíveis",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"minutes": "Minutos",
"round_robin": "Round Robin",
"round_robin_description": "Reuniões de ciclo entre vários membros da equipa.",
@@ -684,6 +702,7 @@
"add_members": "Adicionar membros...",
"no_assigned_members": "Sem membros atribuídos",
"assigned_to": "Atribuído a",
"you_must_be_logged_in_to": "Deve ter sessão iniciada para {{url}}",
"start_assigning_members_above": "Comece a atribuir membros acima",
"locked_fields_admin_description": "Os membros não poderão editar isto",
"locked_fields_member_description": "Esta opção foi bloqueada pelo administrador da equipa",
@@ -788,6 +807,7 @@
"additional_input_description": "Requerer que o responsável pelo agendamento especifique entradas complementares antes do pedido ser confirmado",
"label": "Etiqueta",
"placeholder": "Preenchimento",
"display_add_to_calendar_organizer": "Mostrar o e-mail 'Adicionar ao calendário' como organizador",
"type": "Tipo",
"edit": "Editar",
"add_input": "Adicionar um Campo",
@@ -796,6 +816,9 @@
"requires_confirmation_description": "A reserva precisa ser confirmada manualmente antes de ser enviada para as integrações e ser enviada uma mensagem de confirmação.",
"recurring_event": "Evento recorrente",
"recurring_event_description": "As pessoas podem subscrever para eventos recorrentes",
"cannot_be_used_with_paid_event_types": "Não pode ser utilizado com tipos de eventos pagos",
"warning_payment_instant_meeting_event": "As Reuniões Instantâneas ainda não são suportadas com eventos recorrentes e aplicações de pagamento",
"warning_instant_meeting_experimental": "Experimental: atualmente os Eventos de Reuniões Instantâneas são experimentais.",
"starting": "A iniciar",
"disable_guests": "Desativar Convidados",
"disable_guests_description": "Desativar a adição de convidados adicionais durante a reserva.",
@@ -804,6 +827,7 @@
"private_link_label": "Ligação privada",
"private_link_hint": "A sua ligação será renovada após cada utilização",
"copy_private_link": "Copiar ligação privada",
"copy_private_link_to_event": "Copiar ligação privada para o evento",
"private_link_description": "Crie um URL privado para partilhar sem expor o seu nome de utilizador do {{appName}}",
"invitees_can_schedule": "Os convidados podem agendar",
"date_range": "Intervalo de Datas",
@@ -863,6 +887,7 @@
"next_step": "Ignorar passo",
"prev_step": "Passo anterior",
"install": "Instalar",
"start_paid_trial": "Iniciar avaliação gratuita",
"installed": "Instalado",
"active_install_one": "{{count}} instalação activa",
"active_install_other": "{{count}} instalações activas",
@@ -877,6 +902,7 @@
"toggle_calendars_conflict": "Escolha os calendários que deseja verificar para evitar conflitos e duplicação de reservas.",
"connect_additional_calendar": "Ligar um calendário adicional",
"calendar_updated_successfully": "Calendário atualizado com sucesso",
"check_here": "Verificar aqui",
"conferencing": "Conferência",
"calendar": "Calendário",
"payments": "Pagamentos",
@@ -1048,6 +1074,9 @@
"user_impersonation_heading": "Representação de utilizador",
"user_impersonation_description": "Permite à nossa equipa de suporte iniciar sessão temporariamente em seu nome para rapidamente resolver quaisquer erros que nos reporte.",
"team_impersonation_description": "Permite que os administradores da sua equipa iniciem a sessão temporariamente em seu nome.",
"cal_signup_description": "Gratuito para pessoas singulares. Planos de equipa para funcionalidades colaborativas.",
"make_org_private": "Tornar a organização privada",
"make_org_private_description": "Os membros da sua organização não poderão ver outros membros da organização quando esta opção estiver ativada.",
"make_team_private": "Tornar a equipa privada",
"make_team_private_description": "Os membros da sua equipa não poderão ver outros membros da equipa quando esta opção estiver ativada.",
"you_cannot_see_team_members": "Não pode ver todos os membros da equipa de uma equipa privada.",
@@ -1107,6 +1136,7 @@
"developer_documentation": "Documentação para programadores",
"get_in_touch": "Entre em contacto",
"contact_support": "Contacte o suporte",
"premium_support": "Suporte Premium",
"community_support": "Apoio da Comunidade",
"feedback": "Feedback",
"submitted_feedback": "Obrigado pelo seu feedback!",
@@ -1270,6 +1300,7 @@
"default_calendar_selected": "Calendário predefinido",
"hide_from_profile": "Ocultar do perfil",
"event_setup_tab_title": "Configuração do evento",
"availability_not_found_in_schedule_error": "Não foi encontrada nenhuma disponibilidade na agenda",
"event_limit_tab_title": "Limites",
"event_limit_tab_description": "Com que frequência pode ser reservado",
"event_advanced_tab_description": "Definições do calendário e muito mais...",
@@ -1290,6 +1321,7 @@
"do_this": "Fazer isto",
"turn_off": "Desligar",
"turn_on": "Ativar",
"cancelled_bookings_cannot_be_rescheduled": "As reservas canceladas não podem ser reagendadas",
"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}}",
@@ -1313,6 +1345,7 @@
"customize_your_brand_colors": "Personalize a cor da sua marca na sua página de reservas.",
"pro": "Pro",
"removes_cal_branding": "Remove qualquer identificação de marca relacionada o {{appName}}, por exemplo, 'Com tecnologia {{appName}}.'",
"instant_meeting_with_title": "Reunião instantânea com {{name}}",
"profile_picture": "Imagem do perfil",
"upload": "Carregar",
"add_profile_photo": "Adicionar imagem de perfil",
@@ -1368,6 +1401,7 @@
"event_name_info": "O nome do tipo de evento",
"event_date_info": "A data do evento",
"event_time_info": "A hora de início do evento",
"event_type_not_found": "Tipo de evento não encontrado",
"location_variable": "Localização",
"location_info": "O local do evento",
"additional_notes_variable": "Notas Adicionais",
@@ -1410,6 +1444,7 @@
"slot_length": "Comprimento do espaço",
"booking_appearance": "Aparência da reserva",
"appearance_team_description": "Faça a gestão das definições de aparência da reserva da sua equipa",
"appearance_org_description": "Faça a gestão das definições de aparência da reserva da sua organização",
"only_owner_change": "Só o proprietário desta equipa pode fazer alterações na reserva da equipa ",
"team_disable_cal_branding_description": "Remove qualquer identificação de marca relacionada o {{appName}}, por exemplo, 'Com tecnologia {{appName}}'",
"invited_by_team": "{{teamName}} enviou um convite para se juntar à sua equipa como {{role}}",
@@ -1463,6 +1498,7 @@
"password_updated": "Palavra-passe atualizada!",
"pending_payment": "Pagamento pendente",
"pending_invites": "Convites pendentes",
"pending_organization_invites": "Convites de organização pendentes",
"not_on_cal": "Não existe em {{appName}}",
"no_calendar_installed": "Nenhum calendário instalado",
"no_calendar_installed_description": "Ainda não associou nenhum dos seus calendários",
@@ -1474,6 +1510,8 @@
"report_app": "Reportar aplicação",
"limit_booking_frequency": "Limitar a frequência das reservas",
"limit_booking_frequency_description": "Limitar o número de vezes que este evento pode ser reservado",
"limit_booking_only_first_slot": "Limitar a reserva apenas à primeira vaga",
"limit_booking_only_first_slot_description": "Permitir que apenas a primeira vaga de todos os dias seja reservada",
"limit_total_booking_duration": "Limitar a duração total da reserva",
"limit_total_booking_duration_description": "Limitar o tempo total que pode ser reservado para este evento",
"add_limit": "Adicionar limite",
@@ -1541,10 +1579,14 @@
"your_org_disbanded_successfully": "A sua organização foi dissolvida com sucesso",
"error_creating_team": "Erro ao criar a equipa",
"you": "Você",
"or_continue_with": "Ou continuar com",
"resend_email": "Reenviar e-mail",
"member_already_invited": "O membro já foi convidado",
"already_in_use_error": "O nome de utilizador já está a ser utilizado",
"enter_email_or_username": "Especifique um e-mail ou nome de utilizador",
"enter_email": "Inserir um e-mail",
"enter_emails": "Inserir e-mails",
"too_many_invites": "Está limitado a convidar um máximo de {{nbUsers}} utilizadores de cada vez.",
"team_name_taken": "Este nome já está a ser utilizado",
"must_enter_team_name": "Deve especificar um nome para a equipa",
"team_url_required": "Deve especificar um endereço para a equipa",
@@ -1624,6 +1666,7 @@
"individual": "Individual",
"all_bookings_filter_label": "Todas as reservas",
"all_users_filter_label": "Todos os utilizadores",
"all_event_types_filter_label": "Todos os tipos de evento",
"your_bookings_filter_label": "As suas reservas",
"meeting_url_variable": "URL da reunião",
"meeting_url_info": "URL da reunião de conferência",
@@ -1720,9 +1763,11 @@
"organizer_timezone": "Fuso horário do organizador",
"email_user_cta": "Ver convite",
"email_no_user_invite_heading_team": "Recebeu um convite para fazer parte de uma equipa {{appName}}",
"email_no_user_invite_heading_subteam": "Recebeu um convite para fazer parte de uma equipa da organização {{parentTeamName}}",
"email_no_user_invite_heading_org": "Recebeu um convite para fazer parte de uma organização {{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_team": "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_user_invite_subheading_subteam": "Recebeu um convite de {{invitedBy}} para fazer parte da equipa {{teamName}} da organização {{parentTeamName}} 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_user_invite_subheading_org": "Recebeu um convite de {{invitedBy}} para fazer parte de {{teamName}} da sua organização em {{appName}}. {{appName}} é a solução de agendamento de eventos que lhe permite a si e à sua organização agendar reuniões sem o pingue-pongue de e-mails.",
"email_no_user_invite_steps_intro": "Vamos ajudar com alguns pequenos passos para que você e a sua {{entity}} possam desfrutar rapidamente de uma gestão de agendamentos sem complicações.",
"email_no_user_step_one": "Escolha o seu nome de utilizador",
@@ -1855,7 +1900,9 @@
"review_event_type": "Rever Tipo de Evento",
"looking_for_more_analytics": "À procura de mais análises?",
"looking_for_more_insights": "À procura de mais Insights?",
"filters": "Filtros",
"add_filter": "Adicionar filtro",
"remove_filters": "Limpar todos os filtros",
"select_user": "Selecionar utilizador",
"select_event_type": "Selecionar tipo de evento",
"select_date_range": "Selecionar intervalo de datas",
@@ -1946,6 +1993,7 @@
"connect_google_workspace": "Associar o Google Workspace",
"google_workspace_admin_tooltip": "Tem de 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_instant_meeting_webhook_description": "Crie o seu primeiro webhook com 'Reunião instantânea criada' como acionador para este tipo de evento",
"install_app_on": "Instalar aplicação em",
"create_for": "Criar para",
"currency": "Moeda",
@@ -2010,6 +2058,8 @@
"add_times_to_your_email": "Selecione alguns horários disponíveis e incorpore-os no seu E-mail",
"select_time": "Selecionar horário",
"select_date": "Selecionar data",
"connecting_you_to_someone": "Estamos a conectá-lo a alguém.",
"please_do_not_close_this_tab": "Não feche este separador",
"see_all_available_times": "Ver todos os horários disponíveis",
"org_team_names_example_1": "Por exemplo, Equipa de Marketing",
"org_team_names_example_2": "Por exemplo, Equipa de Vendas",
@@ -2027,8 +2077,12 @@
"description_requires_booker_email_verification": "Para garantir a confirmação do e-mail do responsável pela reserva antes de agendar eventos",
"requires_confirmation_mandatory": "As mensagens de texto só podem ser enviadas aos participantes quando o tipo de evento requerer confirmação.",
"organizations": "Organizações",
"upload_cal_video_logo": "Carregar logótipo de Cal Video",
"update_cal_video_logo": "Atualizar logótipo de Cal Video",
"cal_video_logo_upload_instruction": "Para garantir que o seu logótipo seja visível contra o fundo escuro do Cal Video, carregue uma imagem de cores claras em formato PNG ou SVG, para manter a transparência.",
"org_admin_other_teams": "Outras equipas",
"org_admin_other_teams_description": "Aqui pode ver equipas dentro da sua organização e das quais não faz parte. Pode adicionar-se às mesmas, se necessário.",
"not_part_of_org": "Você não faz parte de nenhuma organização",
"no_other_teams_found": "Não foram encontradas outras equipas",
"no_other_teams_found_description": "Não há outras equipas nesta organização.",
"attendee_first_name_variable": "Primeiro nome do participante",
@@ -2064,7 +2118,9 @@
"org_error_processing": "Ocorreu um erro ao processar esta organização",
"orgs_page_description": "Uma lista de todas as organizações. Aceitar uma organização permitirá que todos os utilizadores com esse domínio de e-mail se registem SEM a verificação de e-mail.",
"unverified": "Não verificada",
"verified": "Confirmado",
"dns_missing": "DNS em falta",
"dns_configured": "DNS configurado",
"mark_dns_configured": "Marcar como o DNS configurado",
"value": "Valor",
"your_organization_updated_sucessfully": "A sua organização foi atualizada com sucesso",
@@ -2074,8 +2130,11 @@
"oAuth": "OAuth",
"recently_added": "Adicionados recentemente",
"connect_all_calendars": "Associe todos os seus calendários",
"connect_all_calendars_description": "{{appName}} lê a disponibilidade de todos os seus calendários existentes.",
"workflow_automation": "Automação de fluxo de trabalho",
"workflow_automation_description": "Personalize a sua experiência de agendamento com fluxos de trabalho",
"scheduling_for_your_team": "Automação de fluxo de trabalho",
"scheduling_for_your_team_description": "Faça o agendamento para a sua equipa, com agendamento coletivo e distribuição equilibrada",
"no_members_found": "Nenhum membro encontrado",
"event_setup_length_error": "Configuração do Evento: a duração deve ser de, pelo menos, 1 minuto.",
"availability_schedules": "Horários de Disponibilidade",
@@ -2108,10 +2167,100 @@
"overlay_my_calendar": "Sobrepor o meu calendário",
"overlay_my_calendar_toc": "Ao ligar-se ao seu calendário, você aceita a nossa política de privacidade e termos de utilização. Pode revogar o acesso a qualquer momento.",
"view_overlay_calendar_events": "Consulte os eventos do seu calendário para evitar agendamentos conflituantes.",
"join_event_location": "Junte-se a {{eventLocationType}}",
"troubleshooting": "Resolução de problemas",
"calendars_were_checking_for_conflicts": "Calendários que estamos a verificar por conflitos",
"availabilty_schedules": "Horários de disponibilidade",
"manage_calendars": "Gerir calendários",
"manage_availability_schedules": "Gerir horários de disponibilidade",
"lock_timezone_toggle_on_booking_page": "Bloquear fuso horário na página de reserva",
"description_lock_timezone_toggle_on_booking_page": "Para bloquear o fuso horário na página de reservas. Útil para eventos presenciais.",
"event_setup_multiple_payment_apps_error": "Só pode ter uma aplicação de pagamentos ativada por tipo de evento.",
"number_in_international_format": "Insira o número no formato internacional.",
"install_calendar": "Instalar Calendário",
"branded_subdomain": "Subdomínio de marca",
"branded_subdomain_description": "Obtenha o seu próprio subdomínio de marca. Por exemplo, acme.cal.com",
"org_insights": "Insights de toda a organização",
"org_insights_description": "Compreenda como toda a sua organização está a utilizar o tempo",
"extensive_whitelabeling": "Apoio dedicado à integração e engenharia",
"extensive_whitelabeling_description": "Personalize a sua experiência de agendamento com o seu próprio logótipo, cores e muito mais",
"unlimited_teams": "Equipas ilimitadas",
"unlimited_teams_description": "Adicione à sua organização tantas subequipas quanto for necessário",
"unified_billing": "Faturação unificada",
"unified_billing_description": "Adicione um único cartão de crédito para pagar todas as subscrições da sua equipa",
"advanced_managed_events": "Tipos de evento geridos avançados",
"advanced_managed_events_description": "Adicione um único cartão de crédito para pagar todas as subscrições da sua equipa",
"enterprise_description": "Atualize para Enterprise para criar a sua Organização",
"create_your_org": "Crie a sua Organização",
"create_your_org_description": "Atualize para Enterprise e receba um subdomínio, faturação unificada, Insights, personalização abrangente da imagem corporativa e muito mais",
"other_payment_app_enabled": "Só pode ativar uma aplicação de pagamento por tipo de evento",
"admin_delete_organization_description": "<ul><li>As equipas que são membros desta organização também serão eliminadas, em conjunto com os respetivos tipos de evento</li><li>Utilizadores que faziam parte da organização não serão eliminados e os seus tipos de evento também permanecerão intactos.</li><li>Os nomes de utilizador serão alterados para permitir que possam existir fora da organização</li></ul>",
"admin_delete_organization_title": "Eliminar {{organizationName}}?",
"published": "Publicada",
"unpublished": "Não publicada",
"publish": "Publicar",
"org_publish_error": "Não foi possível publicar a organização",
"troubleshooter_tooltip": "Abra o solucionador de problemas e descubra o que está errado com o seu agendamento",
"need_help": "Precisa de ajuda?",
"troubleshooter": "Solucionador de problemas",
"please_install_a_calendar": "Por favor, instale um calendário",
"instant_tab_title": "Reserva imediata",
"instant_event_tab_description": "Permita que as pessoas reservem imediatamente",
"uprade_to_create_instant_bookings": "Atualize para Enterprise e permita que os convidados se juntem a uma chamada instantânea em que os participantes podem entrar diretamente. Isto é apenas para tipos de evento de equipa",
"dont_want_to_wait": "Não quer esperar?",
"meeting_started": "Reunião iniciada",
"pay_and_book": "Pagar para reservar",
"booking_not_found_error": "Não foi possível encontrar a reserva",
"booking_seats_full_error": "Os lugares para reserva estão cheios",
"missing_payment_credential_error": "Credenciais de pagamento em falta",
"missing_payment_app_id_error": "ID da aplicação de pagamento em falta",
"not_enough_available_seats_error": "A reserva não tem lugares disponíveis suficientes",
"user_redirect_title": "{{username}} está ausente por um breve período de tempo.",
"user_redirect_description": "Entretanto, {{profile.username}} ficará responsável por todas as novas reuniões agendadas em nome de {{username}}.",
"out_of_office": "Fora do escritório",
"out_of_office_description": "Configure as ações no seu perfil enquanto estiver ausente.",
"send_request": "Enviar pedido",
"start_date_and_end_date_required": "Data de início e data de fim são necessárias",
"start_date_must_be_before_end_date": "Data de início deve ser anterior à data de fim",
"start_date_must_be_in_the_future": "Data de início deve ser no futuro",
"user_not_found": "Utilizador não encontrado",
"out_of_office_entry_already_exists": "Já existe uma entrada de fora do escritório",
"out_of_office_id_required": "É necessária a ID da entrada de fora do escritório",
"booking_redirect_infinite_not_allowed": "Já existe um redirecionamento de reservas desse utilizador para si.",
"success_entry_created": "Criou uma nova entrada com sucesso",
"booking_redirect_email_subject": "Notificação de redirecionamento de reserva",
"booking_redirect_email_title": "Notificação de redirecionamento de reserva",
"booking_redirect_email_description": "Recebeu um redirecionamento de reservas de {{toName}}, pelo que as ligações do respetivo perfil serão redirecionados para o seu perfil durante o intervalo de tempo: ",
"success_accept_booking_redirect": "Aceitou este pedido de redirecionamento de reservas.",
"success_reject_booking_redirect": "Rejeitou este pedido de redirecionamento de reservas.",
"copy_link_booking_redirect_request": "Copiar ligação para partilhar o pedido",
"booking_redirect_request_title": "Pedido de redirecionamento de reservas",
"select_team_member": "Selecionar o membro da equipa",
"going_away_title": "Vai ausentar-se? Simplesmente marque a ligação do seu perfil como indisponível por um determinado período de tempo.",
"redirect_team_enabled": "Redirecionar o seu perfil para outro membro da equipa",
"redirect_team_disabled": "Redirecionar o seu perfil para outro membro da equipa (é necessário um plano Team)",
"out_of_office_unavailable_list": "Lista de indisponibilidades de fora do escritório",
"success_deleted_entry_out_of_office": "Entrada eliminada com sucesso",
"temporarily_out_of_office": "Temporariamente fora do escritório?",
"add_a_redirect": "Adicionar um redirecionamento",
"create_entry": "Criar entrada",
"time_range": "Intervalo de tempo",
"automatically_add_all_team_members": "Adicionar todos os membros da equipa, incluindo membros futuros",
"redirect_to": "Redirecionar para",
"having_trouble_finding_time": "Com dificuldades em encontrar um horário?",
"show_more": "Mostrar mais",
"assignment_description": "Agende reuniões quando todos estiverem disponíveis ou rode entre os membros da sua equipa",
"lowest": "a mais baixa",
"low": "baixa",
"medium": "média",
"high": "alta",
"Highest": "a mais alta",
"send_booker_to": "Enviar responsável pela reserva para",
"set_priority": "Definir prioridade",
"priority_for_user": "Prioridade para {{userName}}",
"change_priority": "alterar prioridade",
"field_identifiers_as_variables": "Utilize identificadores de campo como variáveis para o seu redirecionamento de evento personalizado",
"field_identifiers_as_variables_with_example": "Utilize identificadores de campo como variáveis para o seu redirecionamento de evento personalizado (ex.: {{variable}})",
"account_already_linked": "A conta já está vinculada",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -42,6 +42,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(result.newUserIds).toEqual(undefined);
expect(result.oldUserIds).toEqual(undefined);
@@ -62,6 +63,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(result.newUserIds).toEqual(undefined);
expect(result.oldUserIds).toEqual(undefined);
@@ -88,6 +90,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(result.newUserIds).toEqual(undefined);
expect(result.oldUserIds).toEqual(undefined);
@@ -108,6 +111,7 @@ describe("handleChildrenEventTypes", () => {
timeZone,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
id: 123,
@@ -124,6 +128,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
data: {
@@ -156,6 +161,7 @@ describe("handleChildrenEventTypes", () => {
scheduleId,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -171,13 +177,14 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {
bookingLimits: undefined,
},
});
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
data: {
...evType,
hidden: false,
bookingLimits: undefined,
durationLimits: undefined,
recurringEvent: undefined,
hashedLink: { create: { link: expect.any(String) } },
},
where: {
@@ -205,6 +212,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(result.newUserIds).toEqual([]);
expect(result.oldUserIds).toEqual([]);
@@ -230,6 +238,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
// Have been called
expect(result.newUserIds).toEqual([5]);
@@ -250,6 +259,7 @@ describe("handleChildrenEventTypes", () => {
timeZone,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
id: 123,
@@ -267,6 +277,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
data: {
@@ -298,6 +309,7 @@ describe("handleChildrenEventTypes", () => {
userId,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -314,14 +326,15 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {
length: 30,
},
});
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
data: {
...evType,
bookingLimits: undefined,
durationLimits: undefined,
recurringEvent: undefined,
scheduleId: undefined,
hidden: false,
length: 30,
hashedLink: undefined,
},
where: {
userId_parentId: {
@@ -350,6 +363,7 @@ describe("handleChildrenEventTypes", () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -374,6 +388,7 @@ describe("handleChildrenEventTypes", () => {
connectedLink: null,
prisma: prismaMock,
profileId: null,
updatedValues: {},
});
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
data: {
@@ -399,13 +414,8 @@ describe("handleChildrenEventTypes", () => {
});
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
data: {
...evType,
bookingLimits: undefined,
durationLimits: undefined,
recurringEvent: undefined,
hidden: false,
hashedLink: undefined,
workflows: undefined,
scheduleId: undefined,
},
where: {
userId_parentId: {
+10 -6
View File
@@ -8,7 +8,7 @@ import { expect, vi } from "vitest";
import "vitest-fetch-mock";
import dayjs from "@calcom/dayjs";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { BookingStatus } from "@calcom/prisma/enums";
@@ -307,16 +307,18 @@ export function expectWebhookToHaveBeenCalledWith(
export function expectWorkflowToBeTriggered({
emails,
organizer,
destinationEmail,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
destinationEmail?: string;
}) {
const subjectPattern = /^Reminder: /i;
expect(emails.get()).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: organizer.email,
to: destinationEmail ?? organizer.email,
}),
])
);
@@ -370,6 +372,7 @@ export function expectSuccessfulBookingCreationEmails({
recurrence,
bookingTimeRange,
booking,
destinationEmail,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
@@ -381,8 +384,9 @@ export function expectSuccessfulBookingCreationEmails({
eventDomain?: string;
bookingTimeRange?: { start: Date; end: Date };
booking: { uid: string; urlOrigin?: string };
destinationEmail?: string;
}) {
const bookingUrlOrigin = booking.urlOrigin || CAL_URL;
const bookingUrlOrigin = booking.urlOrigin || WEBSITE_URL;
expect(emails).toHaveEmail(
{
titleTag: "confirmed_event_type_subject",
@@ -409,7 +413,7 @@ export function expectSuccessfulBookingCreationEmails({
},
}
: null),
to: `${organizer.email}`,
to: `${destinationEmail ?? organizer.email}`,
ics: {
filename: "event.ics",
iCalUID: `${iCalUID}`,
@@ -417,7 +421,7 @@ export function expectSuccessfulBookingCreationEmails({
method: "REQUEST",
},
},
`${organizer.email}`
`${destinationEmail ?? organizer.email}`
);
expect(emails).toHaveEmail(
@@ -742,7 +746,7 @@ export function expectBookingRequestRescheduledEmails({
booking: { uid: string; urlOrigin?: string };
bookNewTimePath: string;
}) {
const bookingUrlOrigin = booking.urlOrigin || CAL_URL;
const bookingUrlOrigin = booking.urlOrigin || WEBSITE_URL;
expect(emails).toHaveEmail(
{
@@ -34,6 +34,7 @@ import ics_feedcalendar_config_json from "./ics-feedcalendar/config.json";
import intercom_config_json from "./intercom/config.json";
import { metadata as jitsivideo__metadata_ts } from "./jitsivideo/_metadata";
import { metadata as larkcalendar__metadata_ts } from "./larkcalendar/_metadata";
import linear_config_json from "./linear/config.json";
import make_config_json from "./make/config.json";
import matomo_config_json from "./matomo/config.json";
import metapixel_config_json from "./metapixel/config.json";
@@ -58,7 +59,6 @@ import sendgrid_config_json from "./sendgrid/config.json";
import shimmervideo_config_json from "./shimmervideo/config.json";
import signal_config_json from "./signal/config.json";
import sirius_video_config_json from "./sirius_video/config.json";
import skiff_config_json from "./skiff/config.json";
import { metadata as stripepayment__metadata_ts } from "./stripepayment/_metadata";
import sylapsvideo_config_json from "./sylapsvideo/config.json";
import { metadata as tandemvideo__metadata_ts } from "./tandemvideo/_metadata";
@@ -117,6 +117,7 @@ export const appStoreMetadata = {
intercom: intercom_config_json,
jitsivideo: jitsivideo__metadata_ts,
larkcalendar: larkcalendar__metadata_ts,
linear: linear_config_json,
make: make_config_json,
matomo: matomo_config_json,
metapixel: metapixel_config_json,
@@ -141,7 +142,6 @@ export const appStoreMetadata = {
shimmervideo: shimmervideo_config_json,
signal: signal_config_json,
sirius_video: sirius_video_config_json,
skiff: skiff_config_json,
stripepayment: stripepayment__metadata_ts,
sylapsvideo: sylapsvideo_config_json,
tandemvideo: tandemvideo__metadata_ts,
+1 -1
View File
@@ -34,6 +34,7 @@ export const apiHandlers = {
intercom: import("./intercom/api"),
jitsivideo: import("./jitsivideo/api"),
larkcalendar: import("./larkcalendar/api"),
linear: import("./linear/api"),
make: import("./make/api"),
matomo: import("./matomo/api"),
metapixel: import("./metapixel/api"),
@@ -58,7 +59,6 @@ export const apiHandlers = {
shimmervideo: import("./shimmervideo/api"),
signal: import("./signal/api"),
sirius_video: import("./sirius_video/api"),
skiff: import("./skiff/api"),
stripepayment: import("./stripepayment/api"),
sylapsvideo: import("./sylapsvideo/api"),
tandemvideo: import("./tandemvideo/api"),
+1 -2
View File
@@ -5,7 +5,6 @@ import { useEffect, useRef } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import classNames from "@calcom/lib/classNames";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { CAL_URL } from "@calcom/lib/constants";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -156,7 +155,7 @@ export const AppDependencyComponent = ({
<div>
<>
<Link
href={`${CAL_URL}/apps/${dependency.slug}`}
href={`${WEBAPP_URL}/apps/${dependency.slug}`}
className="text-info flex items-center underline">
<span className="mr-1">
{t("connect_app", { dependencyName: dependency.name })}
@@ -11,6 +11,7 @@ import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
export const scopes = [
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/calendar.events",
"https://www.googleapis.com/auth/userinfo.profile",
];
let client_id = "";
@@ -1,9 +1,11 @@
import type { Auth } from "googleapis";
import { google } from "googleapis";
import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL_FOR_OAUTH, WEBAPP_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
@@ -91,6 +93,10 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
throw new HttpError({ message: "Internal Error", statusCode: 500 });
}
// Update the user's profile photo with google profile photo if it's null
// Since we don't want to block the user from using the app if this fails, we don't await this
updateProfilePhoto(oAuth2Client, req.session.user.id);
const credential = await prisma.credential.create({
data: {
type: "google_calendar",
@@ -156,6 +162,23 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
);
}
async function updateProfilePhoto(oAuth2Client: Auth.OAuth2Client, userId: number) {
try {
const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
const userDetails = await oauth2.userinfo.get();
if (userDetails.data?.picture) {
await prisma.user.update({
where: { id: userId, avatarUrl: null, avatar: null },
data: {
avatarUrl: userDetails.data.picture,
},
});
}
} catch (error) {
logger.error("Error updating avatarUrl from google calendar connect", error);
}
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
});
@@ -1,6 +1,6 @@
import Link from "next/link";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { AlertCircle, ArrowRight, Check } from "@calcom/ui/components/icon";
@@ -38,7 +38,7 @@ const ExistingGoogleCal = ({ gCalInstalled, appName }: { gCalInstalled?: boolean
<div>
<>
<Link
href={`${CAL_URL}/apps/google-calendar`}
href={`${WEBAPP_URL}/apps/google-calendar`}
className="text-info flex items-center underline">
<span className="mr-1">{t("connect_google_calendar")}</span>
<ArrowRight />
+2 -2
View File
@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
@@ -86,7 +86,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
);
res.redirect(
getSafeRedirectUrl(`${CAL_URL}/apps/installed/automation?hl=intercom`) ??
getSafeRedirectUrl(`${WEBAPP_URL}/apps/installed/automation?hl=intercom`) ??
getInstalledAppPath({ variant: "automation", slug: "intercom" })
);
}
+2 -2
View File
@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import type {
@@ -94,7 +94,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
return {
id: `${CAL_URL}/${slug}/${eventType.slug}`,
id: `${WEBSITE_URL}/${slug}/${eventType.slug}`,
type: "item",
title: eventType.title,
subtitle: `${slug}/${eventType.slug}`,
@@ -1,4 +1,4 @@
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import type { TextComponent } from "../lib";
@@ -9,11 +9,11 @@ import type { TextComponent } from "../lib";
* @returns boolean
*/
export async function isValidCalURL(url: string) {
const regex = new RegExp(`^${CAL_URL}/`, `i`);
const regex = new RegExp(`^${WEBSITE_URL}/`, `i`);
const error: TextComponent = {
type: "text",
text: `This is not a valid ${CAL_URL.replace("https://", "")} link`,
text: `This is not a valid ${WEBSITE_URL.replace("https://", "")} link`,
style: "error",
align: "left",
};
+19
View File
@@ -0,0 +1,19 @@
---
items:
- 1.jpg
---
{DESCRIPTION}
### Overview
The Linear integration with Google calendar allows you to connect your calendar and automate your status. If your teammates need to know whether you’re out of office, now they will.
### How it works
Once connected, your Linear status will detect out of office events in Google calendar and update your status when those events are upcoming or active.
### To Configure
Install Google Calendar from your Linear settings under My Account > Linked accounts.
Connect your account and sign into your Google account to allow access.
Success! Your calendar is connected and will update your Linear status with out of office notices.
@@ -11,7 +11,7 @@ const handler: AppDeclarativeHandler = {
handlerType: "add",
redirect: {
newTab: true,
url: "https://skiff.com/blog/skiff-cal-com-launch",
url: "https://linear.app/settings/integrations/google-calendar",
},
createCredential: ({ appType, user, slug, teamId }) =>
createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }),
+17
View File
@@ -0,0 +1,17 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Linear",
"slug": "linear",
"type": "linear_other",
"logo": "icon.svg",
"url": "https://linear.app",
"variant": "other",
"categories": ["other"],
"publisher": "Linear",
"email": "support@linear.app",
"description": "Linear is a better way to build products. Connect your calendar to automate your status",
"isTemplate": false,
"__createdUsingCli": true,
"dependencies": ["google-calendar"],
"__template": "link-as-an-app"
}
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/skiff",
"name": "@calcom/linear",
"version": "0.0.0",
"main": "./index.ts",
"dependencies": {
@@ -10,5 +10,5 @@
"devDependencies": {
"@calcom/types": "*"
},
"description": "End-to-end encrypted, private calendar, email, notes, and drive."
"description": "Connect your calendar to automate your status"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+50
View File
@@ -0,0 +1,50 @@
<svg width="2484" height="2484" viewBox="0 0 2484 2484" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_981_40)">
<path d="M0 0H2484V2484H0V0Z" fill="url(#paint0_linear_981_40)"/>
<g filter="url(#filter0_dd_981_40)">
<path d="M636.219 1385.11C633.456 1373.33 647.489 1365.91 656.048 1374.47L1109.53 1827.95C1118.09 1836.51 1110.67 1850.54 1098.89 1847.78C870.04 1794.1 689.904 1613.96 636.219 1385.11Z" fill="url(#paint1_linear_981_40)"/>
<path d="M621.024 1203.36C620.804 1206.88 622.128 1210.32 624.619 1212.81L1271.19 1859.38C1273.68 1861.87 1277.12 1863.2 1280.64 1862.98C1310.06 1861.14 1338.93 1857.26 1367.11 1851.48C1376.6 1849.53 1379.9 1837.86 1373.05 1831.01L652.994 1110.95C646.138 1104.1 634.473 1107.4 632.522 1116.89C626.737 1145.07 622.856 1173.94 621.024 1203.36Z" fill="url(#paint2_linear_981_40)"/>
<path d="M673.3 989.942C671.233 994.585 672.286 1000.01 675.879 1003.6L1480.4 1808.12C1483.99 1811.71 1489.42 1812.77 1494.06 1810.7C1516.24 1800.82 1537.74 1789.68 1558.46 1777.36C1565.32 1773.29 1566.38 1763.87 1560.74 1758.23L725.772 923.262C720.132 917.622 710.71 918.68 706.635 925.537C694.322 946.258 683.18 967.758 673.3 989.942Z" fill="url(#paint3_linear_981_40)"/>
<path d="M778.222 845.479C773.625 840.882 773.339 833.508 777.671 828.661C891.501 701.225 1057.08 621 1241.4 621C1584.7 621 1863 899.298 1863 1242.6C1863 1426.92 1782.77 1592.5 1655.34 1706.33C1650.49 1710.66 1643.12 1710.37 1638.52 1705.78L778.222 845.479Z" fill="url(#paint4_linear_981_40)"/>
</g>
</g>
<defs>
<filter id="filter0_dd_981_40" x="585.357" y="590.926" width="1313.29" height="1313.29" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5.5693"/>
<feGaussianBlur stdDeviation="17.8218"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.118924 0 0 0 0 0.158031 0 0 0 0 0.570833 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_981_40"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.94926"/>
<feGaussianBlur stdDeviation="7.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_981_40" result="effect2_dropShadow_981_40"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_981_40" result="shape"/>
</filter>
<linearGradient id="paint0_linear_981_40" x1="1242" y1="0" x2="1242" y2="2484" gradientUnits="userSpaceOnUse">
<stop stop-color="#5C6BF1"/>
<stop offset="1" stop-color="#283188"/>
</linearGradient>
<linearGradient id="paint1_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
</linearGradient>
<linearGradient id="paint2_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
</linearGradient>
<linearGradient id="paint3_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
</linearGradient>
<linearGradient id="paint4_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
</linearGradient>
<clipPath id="clip0_981_40">
<rect width="2484" height="2484" rx="200" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -8,7 +8,7 @@ import { z } from "zod";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { RoutingFormEmbedButton, RoutingFormEmbedDialog } from "@calcom/features/embed/RoutingFormEmbed";
import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
@@ -405,8 +405,8 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
const embedLink = `forms/${routingForm?.id}`;
const orgBranding = useOrgBranding();
const formLink = `${orgBranding?.fullDomain ?? CAL_URL}/${embedLink}`;
let redirectUrl = `${orgBranding?.fullDomain ?? CAL_URL}/router?form=${routingForm?.id}`;
const formLink = `${orgBranding?.fullDomain ?? WEBSITE_URL}/${embedLink}`;
let redirectUrl = `${orgBranding?.fullDomain ?? WEBSITE_URL}/router?form=${routingForm?.id}`;
routingForm?.fields?.forEach((field) => {
redirectUrl += `&${getFieldIdentifier(field)}={Recalled_Response_For_This_Field}`;
@@ -2,7 +2,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Link from "next/link";
import React, { useCallback, useState } from "react";
import React, { useCallback, useState, useEffect } from "react";
import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
// types
import type { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
@@ -80,6 +80,7 @@ const Route = ({
moveDown,
appUrl,
disabled = false,
fieldIdentifiers,
}: {
form: inferSSRProps<typeof getServerSideProps>["form"];
route: Route;
@@ -87,6 +88,7 @@ const Route = ({
setRoute: (id: string, route: Partial<Route>) => void;
config: QueryBuilderUpdatedConfig;
setRoutes: React.Dispatch<React.SetStateAction<Route[]>>;
fieldIdentifiers: string[];
moveUp?: { fn: () => void; check: () => boolean } | null;
moveDown?: { fn: () => void; check: () => boolean } | null;
appUrl: string;
@@ -96,7 +98,7 @@ const Route = ({
const index = routes.indexOf(route);
const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery({
const { data: eventTypesByGroup, isLoading } = trpc.viewer.eventTypes.getByViewer.useQuery({
forRoutingForms: true,
});
@@ -136,9 +138,15 @@ const Route = ({
? eventOptions[0].value.substring(0, eventOptions[0].value.lastIndexOf("/") + 1)
: "";
const [customEventTypeSlug, setCustomEventTypeSlug] = useState(
!isRouter(route) ? route.action.value.split("/").pop() : ""
);
const [customEventTypeSlug, setCustomEventTypeSlug] = useState<string>("");
useEffect(() => {
if (!isLoading) {
const isCustom =
!isRouter(route) && !eventOptions.find((eventOption) => eventOption.value === route.action.value);
setCustomEventTypeSlug(isCustom && !isRouter(route) ? route.action.value.split("/").pop() ?? "" : "");
}
}, [isLoading]);
const onChange = (route: Route, immutableTree: ImmutableTree, config: QueryBuilderUpdatedConfig) => {
const jsonTree = QbUtils.getTree(immutableTree);
@@ -271,7 +279,7 @@ const Route = ({
isDisabled={disabled}
options={
eventOptions.length !== 0
? eventOptions.concat({ label: t("Custom"), value: "custom" })
? [{ label: t("custom"), value: "custom" }].concat(eventOptions)
: []
}
onChange={(option) => {
@@ -280,6 +288,7 @@ const Route = ({
}
if (option.value !== "custom") {
setRoute(route.id, { action: { ...route.action, value: option.value } });
setCustomEventTypeSlug("");
} else {
setRoute(route.id, { action: { ...route.action, value: "custom" } });
setCustomEventTypeSlug("");
@@ -287,7 +296,10 @@ const Route = ({
}}
value={
eventOptions.length !== 0 && route.action.value !== ""
? eventOptions.find((eventOption) => eventOption.value === route.action.value) || {
? eventOptions.find(
(eventOption) =>
eventOption.value === route.action.value && !customEventTypeSlug.length
) || {
label: t("custom"),
value: "custom",
}
@@ -295,8 +307,10 @@ const Route = ({
}
/>
{eventOptions.length !== 0 &&
route.action.value !== "" &&
!eventOptions.find((eventOption) => eventOption.value === route.action.value) && (
route.action.value !== "" &&
(!eventOptions.find((eventOption) => eventOption.value === route.action.value) ||
customEventTypeSlug.length) ? (
<>
<TextField
disabled={disabled}
className="border-default flex w-full flex-grow text-sm"
@@ -312,7 +326,19 @@ const Route = ({
}}
placeholder="event-url"
/>
)}
<div className="mt-2 ">
<p className="text-subtle text-xs">
{fieldIdentifiers.length
? t("field_identifiers_as_variables_with_example", {
variable: `{${fieldIdentifiers[0]}}`,
})
: t("field_identifiers_as_variables")}
</p>
</div>
</>
) : (
<></>
)}
</div>
)
) : null}
@@ -504,6 +530,10 @@ const Routes = ({
hookForm.setValue("routes", routesToSave);
const fields = hookForm.getValues("fields");
const fieldIdentifiers = fields ? fields.map((field) => field.identifier ?? field.label) : [];
return (
<div className="bg-default border-subtle flex flex-col-reverse rounded-md border p-8 md:flex-row">
<div ref={animationRef} className="w-full ltr:mr-2 rtl:ml-2">
@@ -515,6 +545,7 @@ const Routes = ({
key={route.id}
config={config}
route={route}
fieldIdentifiers={fieldIdentifiers}
moveUp={{
check: () => key !== 0,
fn: () => {
@@ -587,6 +618,7 @@ const Routes = ({
setRoute={setRoute}
setRoutes={setRoutes}
appUrl={appUrl}
fieldIdentifiers={fieldIdentifiers}
/>
</div>
</div>
-21
View File
@@ -1,21 +0,0 @@
---
items:
- 1.png
- 2.png
- 3.png
- 4.png
- 5.png
---
{DESCRIPTION}
Skiff is a privacy-first, end-to-end encrypted workspace with Mail, Pages, Drive, and Calendar applications. Skiff Calendar integrates with Cal.com, enabling you to schedule appointments, organize events, and use your preferred video conferencing product.
## Features
- **Full workspace** — beyond email and calendar, Skiff enables writing and collaborating on end-to-end encrypted notes or wikis. You can also upload, preview, and share files.
- **Native applications** — Skiff has native macOS, Windows, iOS, and Android applications, ensuring you can stay on top of your schedule from any device.
- **Multiple accounts** — all Skiff products support multiple accounts, or sharing your entire team to collaborate inside one workspace.
- **Custom domains** - you can use your own domain name for free on Skiff to send and receive mail or calendar invites.
- **End-to-end encryption** — all data is encrypted on your device so that only you can access it. Skiff Mail is also open-source to ensure the highest level of transparency.
- **Complete privacy** - no trackers or ads, and your data is never sold.
-17
View File
@@ -1,17 +0,0 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Skiff",
"slug": "skiff",
"type": "skiff_calendar",
"logo": "icon.svg",
"url": "https://skiff.com/blog/skiff-cal-com-launch",
"variant": "calendar",
"categories": ["calendar"],
"publisher": "Skiff World, Inc.",
"email": "hello@skiff.org",
"description": "End-to-end encrypted, private calendar, email, notes, and drive.",
"isTemplate": false,
"__createdUsingCli": true,
"__template": "link-as-an-app",
"dirName": "skiff"
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

-13
View File
@@ -1,13 +0,0 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 399.586C0 258.244 0 187.573 27.9428 133.781C51.49 88.4505 88.4505 51.49 133.781 27.9428C187.573 0 258.244 0 399.586 0H624.414C765.756 0 836.427 0 890.219 27.9428C935.549 51.49 972.51 88.4505 996.057 133.781C1024 187.573 1024 258.244 1024 399.586V624.414C1024 765.756 1024 836.427 996.057 890.219C972.51 935.549 935.549 972.51 890.219 996.057C836.427 1024 765.756 1024 624.414 1024H399.586C258.244 1024 187.573 1024 133.781 996.057C88.4505 972.51 51.49 935.549 27.9428 890.219C0 836.427 0 765.756 0 624.414V399.586Z" fill="#EF5A3C"/>
<g clip-path="url(#clip0_1851_83221)">
<path d="M634.311 128.949C671.289 128.001 708.267 135.586 742.4 150.756C716.8 134.638 689.304 121.364 660.859 110.934C518.637 58.7861 358.4 85.3342 241.778 181.097C237.985 183.942 228.504 192.475 224.711 196.268C220.918 200.06 212.385 207.645 208.592 211.438C203.852 216.179 198.163 222.816 193.422 227.556C189.629 232.297 182.044 240.831 179.2 244.623C175.407 250.312 170.666 256.001 167.822 260.742C164.978 264.534 158.341 274.964 154.548 279.705C153.6 281.601 149.807 287.29 148.859 289.186C147.911 290.134 145.066 295.823 144.118 296.771C142.222 299.616 135.585 311.942 133.689 314.786C131.792 319.527 126.104 329.956 124.207 334.697C122.311 339.438 118.518 347.023 116.622 351.764C115.674 353.66 110.933 366.934 109.985 368.831C108.089 372.623 106.192 379.26 105.244 383.053C102.4 390.638 100.504 400.119 97.6591 408.653C96.7109 413.393 93.8665 425.719 92.9183 430.46L91.9702 434.253C73.0072 533.808 91.9702 639.053 143.17 727.23C148.859 735.764 155.496 747.142 161.185 755.675C164.029 759.467 167.822 765.156 170.666 768.001L180.148 780.327C182.992 784.119 191.526 793.601 194.37 797.393C199.111 802.134 204.8 808.771 209.541 813.512C212.385 816.356 217.126 821.097 219.97 823.942L225.659 828.682L226.607 829.63L228.504 830.579L231.348 832.475C235.141 835.319 239.881 838.164 243.674 841.008L246.518 842.905C247.467 843.853 248.415 843.853 249.363 844.801C253.155 846.697 258.844 850.49 261.689 852.386C263.585 853.334 266.429 855.23 268.326 856.179C270.222 857.127 275.911 859.971 278.755 860.919C281.6 862.816 286.341 863.764 289.185 865.66C296.77 868.505 304.355 871.349 311.941 874.193C315.733 875.142 320.474 877.038 324.267 877.986C327.111 878.934 332.8 879.882 335.644 880.83C338.489 881.779 343.229 881.779 347.022 882.727C360.296 884.623 372.622 885.571 385.896 885.571C417.185 884.623 448.474 877.986 476.918 865.66C537.6 840.06 587.852 788.86 611.555 726.282L614.4 719.645C615.348 717.749 617.244 711.112 618.193 709.216C620.089 703.527 621.985 696.89 622.933 691.201C622.933 689.305 623.881 685.512 624.83 683.616C626.726 673.186 628.622 660.86 628.622 650.43C628.622 647.586 628.622 641.897 628.622 639.053C628.622 639.053 628.622 638.105 628.622 637.156C620.089 637.156 612.504 636.208 603.97 635.26C474.074 621.986 375.467 509.156 379.259 377.364C382.104 237.986 497.778 128.001 634.311 128.949ZM310.044 792.653C293.926 783.171 277.807 771.793 264.533 758.519C205.748 701.63 165.926 625.779 152.652 544.238C128 400.119 187.733 252.208 306.252 165.927C372.622 117.571 456.059 91.0231 538.548 93.8676C440.889 98.6083 345.126 139.379 275.911 209.542C189.629 295.823 148.859 420.979 168.77 541.393C182.044 624.83 223.763 704.475 286.341 761.364C350.815 818.253 447.526 831.527 525.274 794.549C459.852 832.475 375.467 830.579 310.044 792.653ZM322.37 689.305C169.718 536.653 197.215 277.808 379.259 160.238C402.963 145.068 428.563 132.742 456.059 123.26C498.726 109.038 544.237 103.349 588.8 107.142C499.674 106.193 411.496 140.327 347.97 201.008C214.281 326.164 204.8 536.653 327.111 674.134C329.955 676.979 334.696 682.668 338.489 686.46C396.326 746.193 492.089 763.26 565.096 721.542C489.244 772.742 384 754.727 322.37 689.305ZM364.089 576.475L371.674 587.853L380.207 599.23L389.689 610.608C399.17 621.038 408.652 631.468 420.03 640.001C464.592 675.082 527.17 689.305 582.163 670.342C546.133 686.46 504.415 687.408 466.489 676.03C432.355 665.601 402.963 645.69 380.207 620.09C335.644 572.682 308.148 511.053 302.459 446.579C283.496 256.949 447.526 95.7639 637.155 124.208C593.541 122.312 549.926 128.001 510.104 143.171C499.674 146.964 487.348 152.653 477.867 158.342C475.97 159.29 470.281 162.134 468.385 163.082C466.489 164.031 460.8 167.823 458.904 168.771C455.111 170.668 450.37 174.46 446.578 176.356C442.785 179.201 438.044 182.045 435.2 184.89C311.941 274.964 280.652 447.527 364.089 576.475Z" fill="black"/>
<path d="M777.481 841.956C781.274 839.111 790.755 830.578 794.548 826.785C798.34 822.993 806.874 815.407 810.666 811.615C815.407 806.874 821.096 800.237 825.836 795.496C829.629 790.756 837.214 782.222 840.059 778.43C843.851 773.689 848.592 768 851.437 763.259C854.281 759.467 860.918 749.037 864.711 744.296C865.659 742.4 869.451 736.711 870.399 734.815C871.348 733.867 874.192 728.178 875.14 727.23C877.037 724.385 883.674 712.059 885.57 709.215C887.466 704.474 893.155 694.044 895.051 689.304C896.948 684.563 900.74 676.978 902.636 672.237C903.585 670.341 908.325 657.067 909.274 655.17C911.17 651.378 913.066 644.741 914.014 640.948C916.859 633.363 918.755 623.882 921.599 615.348C922.548 610.607 925.392 598.281 926.34 593.541L927.288 589.748C946.251 490.193 927.288 384.948 876.088 296.77C870.399 288.237 863.762 276.859 858.074 268.326C856.177 264.533 851.436 259.793 849.54 256L840.059 243.674C837.214 239.881 828.681 230.4 825.836 226.607C821.096 221.867 815.407 215.23 810.666 210.489C807.822 207.644 803.081 202.904 800.236 200.059L794.548 195.318L793.599 194.37L791.703 193.422L786.962 190.578C783.17 187.733 778.429 184.889 774.636 182.044L771.792 180.148C770.844 179.2 769.896 179.2 768.948 178.252C765.155 176.356 759.466 172.563 756.622 170.667C754.725 169.718 751.881 167.822 749.985 166.874C748.088 165.926 742.399 163.081 739.555 162.133C736.711 160.237 731.97 159.289 729.125 157.393C721.54 154.548 713.955 151.704 706.37 148.859C702.577 147.911 697.836 146.015 694.044 145.067C691.199 144.118 685.511 143.17 682.666 142.222C679.822 141.274 675.081 141.274 671.288 140.326C658.014 138.43 645.688 137.481 632.414 137.481C601.125 138.43 569.836 145.067 541.392 157.393C480.711 182.993 430.459 234.193 406.755 296.77L403.911 303.407C402.962 305.304 401.066 311.941 400.118 313.837C398.222 319.526 396.325 326.163 395.377 331.852C395.377 333.748 394.429 337.541 393.481 339.437C391.585 349.867 389.688 362.193 389.688 372.622C389.688 375.467 389.688 381.156 389.688 384C389.688 384 389.688 384.948 389.688 386.844C530.014 387.793 641.896 505.363 638.103 645.689C634.311 785.067 519.585 894.104 383.051 894.104C346.074 895.052 309.096 887.467 274.962 872.296C300.562 888.415 328.059 901.689 356.503 912.119C500.622 964.267 659.911 937.719 777.481 841.956ZM709.214 231.348C725.333 240.83 741.451 252.207 754.725 265.481C813.511 322.37 853.333 398.222 866.607 479.763C891.259 623.881 830.577 772.741 712.059 858.074C645.688 906.43 562.251 932.978 479.762 930.133C578.37 926.341 674.133 885.57 742.399 815.407C829.629 729.126 869.451 603.97 849.54 482.607C836.266 399.17 794.548 319.526 731.97 262.637C667.496 205.748 570.785 192.474 493.036 229.452C559.407 191.526 643.792 193.422 709.214 231.348ZM696.888 334.696C849.54 487.348 822.044 746.193 639.999 863.763C616.296 878.933 590.696 891.259 563.199 900.741C520.533 914.963 475.022 920.652 430.459 916.859C519.585 917.807 607.762 883.674 671.288 822.993C805.925 696.889 814.459 486.4 693.096 349.867C690.251 347.022 685.511 341.333 681.718 337.541C623.881 277.807 528.118 260.741 455.111 302.459C530.014 251.259 635.259 269.274 696.888 334.696ZM511.051 882.726C521.481 878.933 533.807 873.244 543.288 867.556C545.185 866.607 550.874 863.763 552.77 862.815C554.666 861.867 560.355 858.074 562.251 857.126C566.044 855.23 570.785 851.437 574.577 849.541C578.37 846.696 583.111 843.852 585.955 841.007C708.266 749.037 738.607 576.474 656.118 447.526L647.585 436.148L639.051 424.77L629.57 413.393C620.088 402.963 610.607 392.533 599.229 384C554.666 348.919 492.088 334.696 437.096 353.659C473.125 337.541 514.844 336.593 552.77 347.97C586.903 358.4 616.296 378.311 639.051 403.911C683.614 451.319 711.111 512.948 716.799 577.422C735.762 767.052 571.733 928.237 382.103 899.793C426.666 904.533 470.281 897.896 511.051 882.726Z" fill="black"/>
<path d="M401.066 388.741C398.221 388.741 395.377 387.793 392.532 387.793C392.532 387.793 392.532 387.793 392.532 388.741C399.169 388.741 405.806 389.689 412.443 390.637C408.651 389.689 404.858 389.689 401.066 388.741Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_1851_83221">
<rect width="853.333" height="853.333" fill="white" transform="translate(85.3333 85.3333)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.5 KiB

@@ -21,7 +21,7 @@
"@stripe/stripe-js": "^1.35.0",
"stripe": "^9.16.0",
"uuid": "^8.3.2",
"zod": "^3.22.2"
"zod": "^3.22.4"
},
"devDependencies": {
"@calcom/types": "*",
@@ -5,7 +5,7 @@ import {
addForm as addRoutingForm,
addOneFieldAndDescriptionAndSaveForm,
} from "@calcom/app-store/routing-forms/playwright/tests/testUtils";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL } from "@calcom/lib/constants";
import type { Fixtures } from "@calcom/web/playwright/lib/fixtures";
import { test } from "@calcom/web/playwright/lib/fixtures";
@@ -46,7 +46,7 @@ test.describe("Typeform App", () => {
const text = await page.evaluate(async () => {
return navigator.clipboard.readText();
});
expect(text).toBe(`${CAL_URL}/router?form=${formId}&test={Recalled_Response_For_This_Field}`);
expect(text).toBe(`${WEBAPP_URL}/router?form=${formId}&test={Recalled_Response_For_This_Field}`);
});
test("should copy link in RoutingForms list", async ({ page, context, users }) => {
@@ -66,7 +66,7 @@ test.describe("Typeform App", () => {
const text = await page.evaluate(async () => {
return navigator.clipboard.readText();
});
expect(text).toBe(`${CAL_URL}/router?form=${formId}&test={Recalled_Response_For_This_Field}`);
expect(text).toBe(`${WEBAPP_URL}/router?form=${formId}&test={Recalled_Response_For_This_Field}`);
});
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
import { Meta, Canvas, ArgsTable } from "@storybook/blocks";
import { Title } from "@calcom/storybook/components";
import { Booker } from "./Booker";
import { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
import * as BookerStories from "./Booker.stories";
<Meta of={BookerStories} />
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Booker } from "./Booker";
import { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
const meta: Meta<typeof Booker> = {
component: Booker,
-13
View File
@@ -1,13 +0,0 @@
import type { BookerProps } from "@calcom/features/bookings/Booker";
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
import type { AtomsGlobalConfigProps } from "../types";
type BookerAtomProps = BookerProps & AtomsGlobalConfigProps;
/**
* @TODO Before we can turn this into a reusable atom
* * Use the webAppUrl coming from AtomsGlobalConfigProps to make url dynamic
* * Find a solution for translations
*/
export const Booker = (props: BookerAtomProps) => <BookerComponent {...props} />;
+1 -1
View File
@@ -1,5 +1,5 @@
/** Export file is only used for building the dist version of this Atom. */
// import "../globals.css";
export { Booker } from "./Booker";
export { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
export * from "../types";
+1 -1
View File
@@ -1 +1 @@
export { Booker } from "./Booker";
export { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
@@ -0,0 +1,195 @@
import { useSession } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import { useMemo, useCallback } from "react";
import { shallow } from "zustand/shallow";
import dayjs from "@calcom/dayjs";
import type { BookerProps } from "@calcom/features/bookings/Booker";
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
import { useBookerLayout } from "@calcom/features/bookings/Booker/components/hooks/useBookerLayout";
import { useBookingForm } from "@calcom/features/bookings/Booker/components/hooks/useBookingForm";
import { useBookings } from "@calcom/features/bookings/Booker/components/hooks/useBookings";
import { useCalendars } from "@calcom/features/bookings/Booker/components/hooks/useCalendars";
import { useSlots } from "@calcom/features/bookings/Booker/components/hooks/useSlots";
import { useVerifyCode } from "@calcom/features/bookings/Booker/components/hooks/useVerifyCode";
import { useVerifyEmail } from "@calcom/features/bookings/Booker/components/hooks/useVerifyEmail";
import { useBookerStore, useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import type { AtomsGlobalConfigProps } from "../../types";
type BookerWebWrapperAtomProps = BookerProps & AtomsGlobalConfigProps;
export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const event = useEvent();
const bookerLayout = useBookerLayout(event.data);
const selectedDate = searchParams?.get("date");
const isRedirect = searchParams?.get("redirected") === "true" || false;
const fromUserNameRedirected = searchParams?.get("username") || "";
const rescheduleUid =
typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("rescheduleUid") : null;
const bookingUid =
typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("bookingUid") : null;
const date = dayjs(selectedDate).format("YYYY-MM-DD");
useInitializeBookerStore({
...props,
eventId: event?.data?.id,
rescheduleUid,
bookingUid: bookingUid,
layout: bookerLayout.defaultLayout,
org: props.entity.orgSlug,
});
const [bookerState, _] = useBookerStore((state) => [state.state, state.setState], shallow);
const [dayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
const { data: session } = useSession();
const routerQuery = useRouterQuery();
const hasSession = !!session;
const firstNameQueryParam = searchParams?.get("firstName");
const lastNameQueryParam = searchParams?.get("lastName");
const metadata = Object.keys(routerQuery)
.filter((key) => key.startsWith("metadata"))
.reduce(
(metadata, key) => ({
...metadata,
[key.substring("metadata[".length, key.length - 1)]: searchParams?.get(key),
}),
{}
);
const prefillFormParams = useMemo(() => {
return {
name:
searchParams?.get("name") ||
(firstNameQueryParam ? `${firstNameQueryParam} ${lastNameQueryParam}` : null),
guests: searchParams?.getAll("guests") || searchParams?.getAll("guest"),
};
}, [searchParams, firstNameQueryParam, lastNameQueryParam]);
const bookerForm = useBookingForm({
event,
sessionEmail: session?.user.email,
sessionUsername: session?.user.username,
sessionName: session?.user.name,
hasSession,
extraOptions: routerQuery,
prefillFormParams,
});
const bookings = useBookings({
event,
hashedLink: props.hashedLink,
bookingForm: bookerForm.bookingForm,
metadata: metadata ?? {},
});
const calendars = useCalendars({ hasSession });
const verifyEmail = useVerifyEmail({
email: bookerForm.formEmail,
name: bookerForm.formName,
requiresBookerEmailVerification: event?.data?.requiresBookerEmailVerification,
onVerifyEmail: bookerForm.beforeVerifyEmail,
});
const slots = useSlots(event);
const prefetchNextMonth =
(bookerLayout.layout === BookerLayouts.WEEK_VIEW &&
!!bookerLayout.extraDays &&
dayjs(date).month() !== dayjs(date).add(bookerLayout.extraDays, "day").month()) ||
(bookerLayout.layout === BookerLayouts.COLUMN_VIEW &&
dayjs(date).month() !== dayjs(date).add(bookerLayout.columnViewExtraDays.current, "day").month());
const monthCount =
((bookerLayout.layout !== BookerLayouts.WEEK_VIEW && bookerState === "selecting_time") ||
bookerLayout.layout === BookerLayouts.COLUMN_VIEW) &&
dayjs(date).add(1, "month").month() !==
dayjs(date).add(bookerLayout.columnViewExtraDays.current, "day").month()
? 2
: undefined;
/**
* Prioritize dateSchedule load
* Component will render but use data already fetched from here, and no duplicate requests will be made
* */
const schedule = useScheduleForEvent({
prefetchNextMonth,
username: props.username,
monthCount,
dayCount,
eventSlug: props.eventSlug,
month: props.month,
duration: props.duration,
selectedDate,
});
const verifyCode = useVerifyCode({
onSuccess: () => {
verifyEmail.setVerifiedEmail(bookerForm.formEmail);
verifyEmail.setEmailVerificationModalVisible(false);
bookings.handleBookEvent();
},
});
// Toggle query param for overlay calendar
const onOverlaySwitchStateChange = useCallback(
(state: boolean) => {
const current = new URLSearchParams(Array.from(searchParams?.entries() ?? []));
if (state) {
current.set("overlayCalendar", "true");
localStorage.setItem("overlayCalendarSwitchDefault", "true");
} else {
current.delete("overlayCalendar");
localStorage.removeItem("overlayCalendarSwitchDefault");
}
// cast to string
const value = current.toString();
const query = value ? `?${value}` : "";
router.push(`${pathname}${query}`);
},
[searchParams, pathname, router]
);
return (
<BookerComponent
{...props}
onGoBackInstantMeeting={() => {
if (pathname) window.location.href = pathname;
}}
onConnectNowInstantMeeting={() => {
const newPath = `${pathname}?isInstantMeeting=true`;
router.push(newPath);
}}
onOverlayClickNoCalendar={() => {
router.push("/apps/categories/calendar");
}}
onClickOverlayContinue={() => {
const currentUrl = new URL(window.location.href);
currentUrl.pathname = "/login/";
currentUrl.searchParams.set("callbackUrl", window.location.pathname);
currentUrl.searchParams.set("overlayCalendar", "true");
router.push(currentUrl.toString());
}}
onOverlaySwitchStateChange={onOverlaySwitchStateChange}
sessionUsername={session?.user.username}
isRedirect={isRedirect}
fromUserNameRedirected={fromUserNameRedirected}
rescheduleUid={rescheduleUid}
bookingUid={bookingUid}
hasSession={hasSession}
extraOptions={routerQuery}
bookings={bookings}
calendars={calendars}
slots={slots}
verifyEmail={verifyEmail}
bookerForm={bookerForm}
event={event}
bookerLayout={bookerLayout}
schedule={schedule}
verifyCode={verifyCode}
/>
);
};
+1 -1
View File
@@ -1,2 +1,2 @@
export { Booker } from "./booker/Booker";
export { Booker } from "./booker";
export { CalProvider } from "./cal-provider/index";

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