Compare commits

...
66 Commits
Author SHA1 Message Date
Omar López 3a67e5617e v2.3.7
release.yml / release (push) Canceled after 0s
2022-12-08 22:44:49 -07:00
f401aa7669 Harsh/fix avail time order (#5900)
* fix: 404 page for undefined events

* remove prev code merge

* add: sorting in available durations

* fix: sort only once

* remove: console logs

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-09 00:16:08 +00:00
a6e0678e59 feat: use next/script tag for inline scripts (#5938)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-08 23:39:15 +00:00
Omar López e832015f26 Deprecates user plan (#5942)
* Remove isMissingSeat

* Removes user plan

* Deprecates User Plan

* Updates website

* Update eventTypes.tsx
2022-12-08 16:20:24 -07:00
zomars 5e86881e15 Adds missing package 2022-12-08 16:17:04 -07:00
zomars 3b71f98460 Submodule sync 2022-12-08 15:00:29 -07:00
144d57adb2 fix: make banner responsive and cover entire app (#5934)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-08 18:42:55 +00:00
github-actions[bot]andCrowdin Bot e1f1c15318 New Crowdin translations by Github Action (#5932)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-08 19:31:16 +01:00
d88b1b2f8f remove {{appName}} in translation (#5924)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-08 18:28:22 +00:00
2e61c15f2c fix: check input is not undefined (#5927)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-08 18:16:04 +00:00
Hariom Balharaandkodiakhq[bot] df73473c7f Avoid redirect in cancellation and visit from Bookings list (#5928)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-08 17:32:44 +00:00
github-actions[bot]andCrowdin Bot 7167973afd New Crowdin translations by Github Action (#5920)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-08 15:12:24 +01:00
Omar López 3c17bc2d1a Fixes for avatar payloads (#5921) 2022-12-08 09:50:03 +00:00
a9a295dc54 Admin apps UI (#5494)
* Abstract app category navigation

* Send key schema to frontend

Co-authored-by: Omar López <zomars@users.noreply.github.com>

* Render keys for apps on admin

* Add enabled col for apps

* Save app keys to DB

* Add checks for admin role

* Abstract setup components

* Add AdminAppsList to setup wizard

* Migrate to v10 tRPC

* Default hide keys

* Display enabled apps

* Merge branch 'main' into admin-apps-ui

* Toggle calendars

* WIP

* Add params and include AppCategoryNavigation

* Refactor getEnabledApps

* Add warning for disabling apps

* Fallback to cal video when a video app is disabled

* WIP send disabled email

* Send email to all users of  event types with payment app

* Disable Stripe when app is disabled

* Disable apps in event types

* Send email to users on disabled apps

* Send email based on what app was disabled

* WIP type fix

* Disable navigation to apps list if already setup

* UI import fixes

* Waits for session data before redirecting

* Updates admin seeded password

To comply with admin password requirements

* Update yarn.lock

* Flex fixes

* Adds admin middleware

* Clean up

* WIP

* WIP

* NTS

* Add dirName to app metadata

* Upsert app if not in db

* Upsert app if not in db

* Add dirName to app metadata

* Add keys to app packages w/ keys

* Merge with main

* Toggle show keys & on enable

* Fix empty keys

* Fix lark calendar metadata

* Fix some type errors

* Fix Lark metadata & check for category when upserting

* More type fixes

* Fix types & add keys to google cal

* WIP

* WIP

* WIP

* More type fixes

* Fix type errors

* Fix type errors

* More type fixes

* More type fixes

* More type fixes

* Feedback

* Fixes default value

* Feedback

* Migrate credential invalid col default value "false"

* Upsert app on saving keys

* Clean up

* Validate app keys on frontend

* Add nonempty to app keys schemas

* Add web3

* Listlocale filter on categories / category

* Grab app metadata via category or categories

* Show empty screen if no apps are enabled

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Replace .nonempty() w/ .min(1)

* Fix type error

* Address feedback

* Added migration to keep current apps enabled

* Update apps.tsx

* Fix bug

* Add keys schema to Plausible app

* Add appKeysSchema to zod.ts template

* Update AdminAppsList.tsx

Co-authored-by: Omar López <zomars@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-12-07 14:47:02 -07:00
+2 21dd1f4e95 [CAL-462] Team settings dropdown menu should be open when you navigate from Teams (#5868)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart_bot <gitstart_bot@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart_bot <gitstart_bot@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
2022-12-07 21:35:41 +00:00
zomars 5d775bd4a3 v2.3.6
release.yml / release (push) Canceled after 0s
2022-12-07 14:02:24 -07:00
Nafees Nazik 4e02b22472 fix: add optimistic update for disabling forms (#5547) 2022-12-07 13:55:41 -07:00
4686d3ff48 Improve multilingualism and fix search routes (#5334)
* fixed search routes

* made static text multilingual and fixed german translations

# Conflicts:
#	apps/web/components/availability/Schedule.tsx

* delete empty file created by fixing merge conflicts

* fixing TextField placeholder by passing attendeeName and fixed json variable

* Using useLocal to modify static text to make it multilingual, and passing the correct variables for brand and mail

* seperated whitelabel and improved translations

* added missing translation

* added missing translation for webhooks

* Updated AdminAppsView with dynamic translations.
Added translations for german and english files only.

* changed back to one liner

* updated german and english translations for impersonation.tsx

* updated german and english translations for impersonation.tsx and users.tsx. added missing german translation for timeformat_profile_hint

* updated german and english translations for team-billing-view.tsx

* updated german and english translations for LicenseRequired.tsx

* updated routes for profile and avatar

* yarn.lock updated from newer changes

* Revert " yarn.lock updated from newer changes"

This reverts commit efd9a90bf774371a331861e4fb441ab4f4d0b7fd.

* sanitize dangerouslySetInnerHTML to prevent xss attacks

* tried to fix window title flicker

* changed ssdInit to ssrInit for getServerSideProps. Serverside translation works but current route still set as a window title

* flicker with route in window title is caused here. It is not necessary to check if isPublic and session is false because it already gets checked in useRedirectToLoginIfUnauthenticated hook.

* fixed window title translation flicker for availability page

* fixed window title translation flicker for teams page

* fixed window title translation flicker for workflow page

* fixed error that div may not be rendered within p element

* fixed window title translation flicker for booking page

* fixed window title translation flicker by adding getServerSideProps

* Only set HeadSeo if an page title exists. If window title is set by the Meta component, shell is causing a flicker because it overwrites the title which is set by Meta. It is a problem especially for settings pages.

* fixed window title translation flicker by adding the Meta component to the skeleton

* fixed condition

* removed condition and added withoutSeo for settings pages

* using translations for create team page further fixed flicker for window title

* fixed flicker for window title for event-type creation page

* fixed flicker for window title for availability creation page

* fixed flicker for window title for sso page

* updated conferencing en translation

* added meta.CTA back it was mistakenly removed

* fixed flicker for workflows page

* fixed missing variable

* Update packages/ui/v2/core/Shell.tsx

* Delete index.tsx

* Update sso.tsx

* Updates subdmoules

Co-authored-by: maxi <maximilian.oehrlein@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-07 13:53:44 -07:00
zomars 02e9d8d45f Sync submodules 2022-12-07 13:40:37 -07:00
Udit Takkar 7ce5096f13 fix: resetting input on cancel and refactoring input fiedl (#5522)
* fix: resetting input on cancel and refactoring input fiedl

* feat: add username field in /usersettings

* chore: remove unused

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:15:15 -07:00
Udit Takkar 83e51e9b9d fix: use trim in zod (#5915)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:12:53 -07:00
Udit TakkarandPeer Richelsen dca3278c40 fix: zoom video (#5910)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 13:12:26 -07:00
Udit Takkar 7501635fbf fix: delete use when 2fa is disabled (#5899)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:12:14 -07:00
Peer Richelsen 80c96ea5ac remove "Add bugs to Bug Hunting" workflow (#5901)
not needed anymore
2022-12-07 13:09:01 -07:00
Hariom BalharaandPeer Richelsen 8c001e0d34 Fix reschedule embed (#5861)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 13:08:34 -07:00
Joe Au-Yeungandalannnc bf275497a2 MS Teams use joinWebUrl from response body (#5886)
* Add joinWebUrl to event

* Remove console.log

Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-07 12:56:15 -07:00
6e351a4b20 fix/premium-username-flow (#5815)
* Removes user plan logic for stripe sub

* Update hardcoded price

* Removed checkoutSessionId metadata and fix for trpc query

* Remove or comment team billing unused code

* Add todo note

* Fix type checks

* Change function that was renamed

* Remove duplicated test

* remove plan when creating user on test

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 12:55:47 -07:00
github-actions[bot]andCrowdin Bot 5360cea3bf New Crowdin translations by Github Action (#5919)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-07 20:49:45 +01:00
+2 2f36985462 /availability - Add ability to set as default in the dropdown (#5717)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gGh00sTt <coolmagnas@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gGh00sTt <coolmagnas@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-07 12:35:55 -07:00
6b26e9a7de Harsh/fix timeformat hint (#5918)
* fix: 404 page for undefined events

* remove prev code merge

* fix: timeformat profile hint

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <root@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 18:22:43 +00:00
github-actions[bot]andCrowdin Bot d7655f0521 New Crowdin translations by Github Action (#5914)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-07 19:10:15 +01:00
sean-brydonandLeo Giovanetti f4ed345a87 Fixes disable branding for teams and users (#5913)
* Adding new nextauth object

* Use correct brand hidden values

* Move check to getprops instead of a function

* Update apps/web/components/booking/pages/AvailabilityPage.tsx

* Update apps/web/components/booking/pages/AvailabilityPage.tsx

* Update apps/web/pages/[user]/[type].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

* Update apps/web/pages/api/auth/[...nextauth].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

* Update apps/web/pages/api/auth/[...nextauth].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

Co-authored-by: Leo Giovanetti <hello@leog.me>
2022-12-07 15:04:04 +00:00
SumedhandPeer Richelsen c3122c82e4 fix/cant-create-team-name-taken (#5911)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 14:50:54 +00:00
Udit Takkar cc9b49131e fix: add toast and fix delete action (#5912)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 15:37:20 +01:00
github-actions[bot]andCrowdin Bot 190db1a0f6 New Crowdin translations by Github Action (#5903)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-06 23:25:53 +01:00
Cory Dransfeldt bd48a72e99 [Feature] Adds support for Plausible analytics to app store (#5906) 2022-12-06 21:19:16 +00:00
Nafees Nazik a6836a1488 fix: use the new docker compose command if it exists (#5852) 2022-12-06 11:41:50 -07:00
2b67ea9635 Harsh/fix 404 page for undefined events (#5700)
* fix: 404 page for undefined events

* Update apps/web/pages/[user]/[type].tsx

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-12-06 15:04:34 +00:00
Udit Takkar a9117c2d36 fix: use tooltip portal (#5889)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-06 13:57:02 +00:00
+1 10d3b5f1b9 Remove the back arrow (#5402)
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
2022-12-06 13:02:58 +00:00
Peer Richelsen b757cb6ba1 added new teams empty screen (#5847)
* added new teams empty screen

* replaced svg background with smaller jpg

* nit

* nit

* nit

* nit

* show old emptyscreen for self-hosters

* nit
2022-12-05 15:20:33 -07:00
5968e0aa19 added EventReservationSchema to success page (#5777)
* added EventReservationSchema to success page

* removed duplicated of yarn.lock

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 21:35:44 +00:00
zomars a06fffc4ec Adds missing translation 2022-12-05 14:24:09 -07:00
zomars 3312c84e7c Update turbo.json 2022-12-05 14:17:35 -07:00
Nafees NazikandPeer Richelsen d86710d5e9 fix: remove unnecessary title (#5853)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 21:11:10 +00:00
Udit Takkar 2d8e65c160 fix: ui of calendar item (#5879)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-05 20:39:52 +00:00
Udit TakkarandPeer Richelsen f74eaf7cb3 fix: increase edit location height (#5882)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 20:18:45 +00:00
bcfd57c953 Bugfix/payment options (#5805)
* removed locale to use automatic detection in teams/lib/payments.ts

* removed payment_method_types to manage payment methods in the Dashboard

* added customer_update and automatic_tax to apply tax rates to payments

* added allow_promotion_codes to allow promotion codes

* removed multiple usages of `allow_promotion_codes` in payments.ts

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-05 13:02:59 -07:00
c38588b93d New Crowdin translations by Github Action (#5842)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 20:59:12 +01:00
alannnc 32ed43574d Fix input parse to number (#5884) 2022-12-05 20:57:33 +01:00
38a5a22b9e Harsh/fix bookings name (#5848)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <root@pop-os.localdomain>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 17:41:40 +00:00
Sascha FoersterandPeer Richelsen 2120e301c3 Update german translations (#5854)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 17:29:19 +00:00
Bailey Pumfleet dd0aec4e97 Fix Kbar translation (#5867) 2022-12-05 14:43:31 +00:00
Peer Richelsen 3815b86258 added norwegian to i18n (#5866) 2022-12-05 13:58:00 +00:00
Hariom BalharaandPeer Richelsen 7a6545bd03 Wordpress App Files cleanup (#5858)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 13:42:38 +00:00
Udit TakkarandPeer Richelsen 11f467af5d fix: toggle (#5865)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 13:28:21 +00:00
0930cf50b6 BugFix: Link based Apps installation - Opens 2 tabs instead of 1 (#5859)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-05 14:13:01 +01:00
c37cddd0b7 feat: add each app from the app store to KBar (#5724)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-05 12:13:31 +00:00
09253f83bf [CAL-463] Clicking Members from event types screen should take you to the members page (#5857)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-05 12:12:37 +00:00
Leo GiovanettiandBailey Pumfleet 1cdcebac01 Requires Confirmation Threshold (#5825)
* WIP + react errors fixed

* Booking and Availability page treatment

* Update BookingPage.tsx

Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-05 12:12:14 +00:00
sean-brydon 58270fa653 Team impersonation (#5863)
* Re Implement frontend logic to impersonate team members

* Refactor dialog + correct disable toggle.

* fix translation

* Update packages/features/ee/impersonation/lib/ImpersonationProvider.ts
2022-12-05 12:03:36 +00:00
Peer Richelsen 1a26a254ab added team subdomain to whereby validation (#5850) 2022-12-03 19:32:40 +00:00
Udit Takkarandalannnc a36d15894d fix: user infer props and remove avatarSSr from onboarding (#5549)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-03 19:20:18 +00:00
Sumedh cf077fd33c fix/sidebar-profiledropdown-wired-hoverstate (#5832) 2022-12-03 15:12:12 +00:00
sean-brydonandPeer Richelsen 0c979d3604 Fix imports + buttons (#5831)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-03 14:53:07 +00:00
zomars 33bc786bb3 API scripts 2022-12-02 15:25:13 -07:00
302 changed files with 3858 additions and 2656 deletions
-22
View File
@@ -1,22 +0,0 @@
name: Add bugs to Bug Hunting
on:
issues:
types:
- opened
- labeled
pull_request:
types:
- opened
- labeled
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.1.0
with:
project-url: https://github.com/orgs/calcom/projects/9
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: 🙋🏻‍♂️help wanted
+2 -2
View File
@@ -42,7 +42,7 @@ const Component = ({
const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
if (data.setupPending) return;
if (data?.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -195,7 +195,7 @@ const Component = ({
<h4 className="mt-8 font-semibold text-gray-900 ">{t("pricing")}</h4>
<span>
{price === 0 ? (
"Free"
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat("en-US", {
@@ -1,6 +1,7 @@
import { SchedulingType } from "@prisma/client";
import { FC, ReactNode, useEffect } from "react";
import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Badge } from "@calcom/ui";
@@ -41,6 +42,7 @@ interface Props {
const BookingDescription: FC<Props> = (props) => {
const { profile, eventType, isBookingPage = false, children } = props;
const { date: bookingDate } = useRouterQuery("date");
const { t } = useLocale();
const { duration, setQuery: setDuration } = useRouterQuery("duration");
useEffect(() => {
@@ -54,6 +56,21 @@ const BookingDescription: FC<Props> = (props) => {
}
}
}, []);
let requiresConfirmation = eventType?.requiresConfirmation;
let requiresConfirmationText = t("requires_confirmation");
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold;
if (rcThreshold) {
if (isBookingPage) {
if (dayjs(bookingDate).diff(dayjs(), rcThreshold.unit) > rcThreshold.time) {
requiresConfirmation = false;
}
} else {
requiresConfirmationText = t("requires_confirmation_threshold", {
...rcThreshold,
unit: rcThreshold.unit.slice(0, -1),
});
}
}
return (
<>
<UserAvatars
@@ -89,16 +106,16 @@ const BookingDescription: FC<Props> = (props) => {
</div>
</div>
)}
{eventType?.requiresConfirmation && (
{requiresConfirmation && (
<div
className={classNames(
"flex items-center",
"items-top flex",
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}>
<div>
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 " />
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] inline-block h-4 w-4 " />
</div>
{t("requires_confirmation")}
{requiresConfirmationText}
</div>
)}
<AvailableEventLocations
@@ -1,7 +1,6 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventType } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { useReducer, useEffect, useMemo, useState } from "react";
import { Toaster } from "react-hot-toast";
@@ -19,7 +18,6 @@ import {
} from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { WEBSITE_URL } from "@calcom/lib/constants";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -27,15 +25,11 @@ import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Icon, DatePicker } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import Gates, { Gate, GateState } from "@components/Gates";
import AvailableTimes from "@components/booking/AvailableTimes";
@@ -48,23 +42,6 @@ import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
// Get router variables
const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
const router = useRouter();
const path = router.asPath.split("/");
path.pop(); // Remove the last item (where we currently are)
path.shift(); // Removes first item e.g. if we were visitng "/teams/test/30mins" the array will new look like ["teams","test"]
const slug = path.join("/");
return (
<div className="flex h-full flex-col justify-end">
<button title={t("profile")} onClick={() => router.replace(`${WEBSITE_URL}/${slug}`)}>
<Icon.FiArrowLeft className="dark:text-darkgray-600 h-4 w-4 text-black transition-opacity hover:cursor-pointer" />
<p className="sr-only">Go Back</p>
</button>
</div>
);
};
const useSlots = ({
eventTypeId,
eventTypeSlug,
@@ -237,7 +214,7 @@ function TimezoneDropdown({
return (
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600">
<p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 mr-[10px] ml-[2px] -mt-[2px] inline-block" />
{timeZone}
@@ -300,9 +277,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
// TODO: Improve this;
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
const telemetry = useTelemetry();
@@ -423,12 +397,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
{timezoneDropdown}
</BookingDescription>
{!isEmbed && (
<div className="mt-auto hidden md:block">
<GoBackToPreviousPage t={t} />
</div>
)}
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
@@ -468,7 +436,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
/>
</div>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
{(!restProps.isBrandingHidden || isEmbed) && <PoweredByCal />}
</div>
</main>
</div>
@@ -240,7 +240,7 @@ const BookingPage = ({
const bookingFormSchema = z
.object({
name: z.string().min(1),
email: z.string().email(),
email: z.string().trim().email(),
phone: z
.string()
.refine((val) => isValidPhoneNumber(val))
@@ -297,7 +297,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}
@@ -20,12 +20,13 @@ type AvailabilityOption = {
const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.Option {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.Option>
@@ -34,12 +35,13 @@ const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.SingleValue {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.SingleValue>
@@ -1,371 +0,0 @@
import { ErrorMessage } from "@hookform/error-message";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
EventLocationType,
getEventLocationType,
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, Form, Icon, Label, PhoneInput, Select } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { LinkText } from "@components/ui/LinkText";
import CheckboxField from "@components/ui/form/CheckboxField";
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
};
interface ISetLocationDialog {
saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void;
selection?: OptionTypeBase;
booking?: BookingItem;
defaultValues?: LocationObject[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
}
const LocationInput = (props: {
eventLocationType: EventLocationType;
locationFormMethods: ReturnType<typeof useForm>;
id: string;
required: boolean;
placeholder: string;
className?: string;
defaultValue?: string;
}): JSX.Element | null => {
const { eventLocationType, locationFormMethods, ...remainingProps } = props;
if (eventLocationType?.organizerInputType === "text") {
return (
<input {...locationFormMethods.register(eventLocationType.variable)} type="text" {...remainingProps} />
);
} else if (eventLocationType?.organizerInputType === "phone") {
return (
<PhoneInput
name={eventLocationType.variable}
control={locationFormMethods.control}
{...remainingProps}
/>
);
}
return null;
};
export const EditLocationDialog = (props: ISetLocationDialog) => {
const {
saveLocation,
selection,
booking,
setShowLocationModal,
isOpenDialog,
defaultValues,
setSelectedLocation,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.viewer.locationOptions.useQuery();
useEffect(() => {
if (selection) {
locationFormMethods.setValue("locationType", selection?.value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selection]);
const locationFormSchema = z.object({
locationType: z.string(),
phone: z.string().optional(),
locationAddress: z.string().optional(),
locationLink: z
.string()
.optional()
.superRefine((val, ctx) => {
if (
eventLocationType &&
!eventLocationType.default &&
eventLocationType.linkType === "static" &&
eventLocationType.urlRegExp
) {
const valid = z
.string()
.regex(new RegExp(eventLocationType.urlRegExp || ""))
.safeParse(val).success;
if (!valid) {
const sampleUrl = eventLocationType.organizerInputPlaceholder;
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid URL for ${eventLocationType.label}. ${
sampleUrl ? "Sample URL: " + sampleUrl : ""
}`,
});
}
return;
}
const valid = z.string().url().optional().safeParse(val).success;
if (!valid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid URL`,
});
}
return;
}),
displayLocationPublicly: z.boolean().optional(),
locationPhoneNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
});
const locationFormMethods = useForm({
mode: "onSubmit",
resolver: zodResolver(locationFormSchema),
});
const selectedLocation = useWatch({
control: locationFormMethods.control,
name: "locationType",
});
const eventLocationType = getEventLocationType(selectedLocation);
const defaultLocation = defaultValues?.find(
(location: { type: EventLocationType["type"] }) => location.type === eventLocationType?.type
);
const LocationOptions = (() => {
if (eventLocationType && eventLocationType.organizerInputType && LocationInput) {
if (!eventLocationType.variable) {
console.error("eventLocationType.variable can't be undefined");
return null;
}
return (
<div>
<div>
<Label htmlFor="locationInput">{t(eventLocationType.messageForOrganizer || "")}</Label>
<LocationInput
locationFormMethods={locationFormMethods}
eventLocationType={eventLocationType}
id="locationInput"
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
required
className="block w-full rounded-md border-gray-300 text-sm"
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
defaultValue={
(defaultLocation && defaultLocation[eventLocationType.defaultValueVariable]) || ""
}
/>
<ErrorMessage
errors={locationFormMethods.formState.errors}
name={eventLocationType.variable}
className="mt-1 text-sm text-red-500"
as="p"
/>
</div>
{!booking && (
<div className="mt-3">
<Controller
name="displayLocationPublicly"
control={locationFormMethods.control}
render={() => (
<CheckboxField
defaultChecked={defaultLocation?.displayLocationPublicly}
description={t("display_location_label")}
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
)}
</div>
);
} else {
return <p className="text-sm">{getMessageForOrganizer(selectedLocation, t)}</p>;
}
})();
return (
<Dialog open={isOpenDialog}>
<DialogContent
type="creation"
Icon={Icon.FiMapPin}
title={t("edit_location")}
description={!booking ? t("this_input_will_shown_booking_this_event") : undefined}>
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
className="space-y-4"
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
let details = {};
if (newLocation === LocationType.AttendeeInPerson) {
details = {
address: values.locationAddress,
};
}
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
if (booking) {
locationOptions.forEach((location) => {
if (location.label === "phone") {
location.options.filter((l) => l.value !== "phone");
}
});
}
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-2.5">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
isSearchable={false}
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
/>
{selectedLocation && LocationOptions}
<div className="mt-5">
<p className="text-sm text-gray-400">
<Trans i18nKey="cant_find_the_right_video_app_visit_our_app_store">
Can&apos;t find the right video app? Visit our
<LinkText href="/apps/categories/video" classNameChildren="text-blue-400" target="_blank">
App Store
</LinkText>
.
</Trans>
</p>
</div>
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister("locationType");
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</Form>
</DialogContent>
</Dialog>
);
};
@@ -27,6 +27,8 @@ import {
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
import RequiresConfirmationController from "./RequiresConfirmationController";
const generateHashedLink = (id: number) => {
const translator = short();
const seed = `${id}:${new Date().getTime()}`;
@@ -47,6 +49,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
);
const [selectedCustomInput, setSelectedCustomInput] = useState<CustomInputParsed | undefined>(undefined);
const [selectedCustomInputModalOpen, setSelectedCustomInputModalOpen] = useState(false);
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const seatsEnabled = formMethods.getValues("seatsPerTimeSlotEnabled");
@@ -100,7 +103,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
addOnSuffix={
@@ -159,18 +162,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
</SettingsToggle>
</div>
<hr />
<Controller
name="requiresConfirmation"
defaultValue={eventType.requiresConfirmation}
render={({ field: { value, onChange } }) => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={value}
onCheckedChange={(e) => onChange(e)}
disabled={seatsEnabled}
/>
)}
<RequiresConfirmationController
seatsEnabled={seatsEnabled}
metadata={eventType.metadata}
requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
<hr />
<Controller
@@ -306,6 +302,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
if (e) {
formMethods.setValue("disableGuests", true);
formMethods.setValue("requiresConfirmation", false);
setRequiresConfirmation(false);
formMethods.setValue("seatsPerTimeSlot", 2);
} else {
formMethods.setValue("seatsPerTimeSlot", null);
@@ -355,7 +352,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
/>
+18 -28
View File
@@ -33,22 +33,10 @@ export const EventSetupTab = (
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [
{ value: 5, label: t("multiple_duration_mins", { count: 5 }) },
{ value: 10, label: t("multiple_duration_mins", { count: 10 }) },
{ value: 15, label: t("multiple_duration_mins", { count: 15 }) },
{ value: 20, label: t("multiple_duration_mins", { count: 20 }) },
{ value: 25, label: t("multiple_duration_mins", { count: 25 }) },
{ value: 30, label: t("multiple_duration_mins", { count: 30 }) },
{ value: 45, label: t("multiple_duration_mins", { count: 45 }) },
{ value: 50, label: t("multiple_duration_mins", { count: 50 }) },
{ value: 60, label: t("multiple_duration_mins", { count: 60 }) },
{ value: 75, label: t("multiple_duration_mins", { count: 75 }) },
{ value: 80, label: t("multiple_duration_mins", { count: 80 }) },
{ value: 90, label: t("multiple_duration_mins", { count: 90 }) },
{ value: 120, label: t("multiple_duration_mins", { count: 120 }) },
{ value: 180, label: t("multiple_duration_mins", { count: 180 }) },
];
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
value: mins,
label: t("multiple_duration_mins", { count: mins }),
}));
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
MultiValue<{
@@ -142,6 +130,7 @@ export const EventSetupTab = (
{validLocations.length === 0 && (
<div className="flex">
<Select
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
@@ -224,7 +213,7 @@ export const EventSetupTab = (
<div className="space-y-8">
<TextField
required
label={t("Title")}
label={t("title")}
defaultValue={eventType.title}
{...formMethods.register("title")}
/>
@@ -261,23 +250,24 @@ export const EventSetupTab = (
isSearchable={false}
className="h-auto !min-h-[36px] text-sm"
options={multipleDurationOptions}
value={selectedMultipleDuration}
onChange={(options) => {
const values = options
.map((opt) => opt.value)
.sort(function (a, b) {
return a - b;
});
let newOptions = [...options];
newOptions = newOptions.sort((a, b) => {
return a?.value - b?.value;
});
const values = newOptions.map((opt) => opt.value);
setMultipleDuration(values);
setSelectedMultipleDuration(options);
if (!options.find((opt) => opt.value === defaultDuration?.value)) {
if (options.length > 0) {
setDefaultDuration(options[0]);
setSelectedMultipleDuration(newOptions);
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
if (newOptions.length > 0) {
setDefaultDuration(newOptions[0]);
} else {
setDefaultDuration(null);
}
}
if (options.length === 1 && defaultDuration === null) {
setDefaultDuration(options[0]);
if (newOptions.length === 1 && defaultDuration === null) {
setDefaultDuration(newOptions[0]);
}
formMethods.setValue("metadata.multipleDuration", values);
}}
@@ -67,7 +67,7 @@ function getNavigation(props: {
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
info: `default_schedule_name`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
@@ -0,0 +1,156 @@
import * as RadioGroup from "@radix-ui/react-radio-group";
import { UnitTypeLongPlural } from "dayjs";
import { Trans } from "next-i18next";
import type { FormValues } from "pages/event-types/[type]";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Alert, Input, Label, SettingsToggle } from "@calcom/ui";
type RequiresConfirmationControllerProps = {
metadata: z.infer<typeof EventTypeMetaDataSchema>;
requiresConfirmation: boolean;
onRequiresConfirmation: Dispatch<SetStateAction<boolean>>;
seatsEnabled: boolean;
};
export default function RequiresConfirmationController({
metadata,
requiresConfirmation,
onRequiresConfirmation,
seatsEnabled,
}: RequiresConfirmationControllerProps) {
const { t } = useLocale();
const [requiresConfirmationSetup, setRequiresConfirmationSetup] = useState(
metadata?.requiresConfirmationThreshold
);
const defaultRequiresConfirmationSetup = { time: 30, unit: "minutes" as UnitTypeLongPlural };
const formMethods = useFormContext<FormValues>();
useEffect(() => {
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
}
}, [requiresConfirmation]);
return (
<div className="block items-start sm:flex">
<div className={!seatsEnabled ? "w-full" : ""}>
{seatsEnabled ? (
<Alert severity="warning" title="Seats option doesn't support confirmation requirement" />
) : (
<Controller
name="requiresConfirmation"
control={formMethods.control}
render={() => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={requiresConfirmation}
onCheckedChange={(val) => {
formMethods.setValue("requiresConfirmation", val);
onRequiresConfirmation(val);
}}>
<RadioGroup.Root
defaultValue={
requiresConfirmation
? requiresConfirmationSetup === undefined
? "always"
: "notice"
: undefined
}
onValueChange={(val) => {
if (val === "always") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
setRequiresConfirmationSetup(undefined);
} else if (val === "notice") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue(
"metadata.requiresConfirmationThreshold",
requiresConfirmationSetup || defaultRequiresConfirmationSetup
);
}
}}>
<div className="-ml-2 flex flex-col flex-wrap justify-start gap-y-2 space-y-2">
<div className="flex items-center">
<RadioGroup.Item
id="always"
value="always"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="always" className="!m-0 flex items-center">
{t("always_requires_confirmation")}
</Label>
</div>
<div className="flex items-center">
<RadioGroup.Item
id="notice"
value="notice"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="notice" className="!m-0 flex items-center">
<Trans
i18nKey="when_booked_with_less_than_notice"
defaults="When booked with less than <time></time> notice"
components={{
time: (
<div className="mx-2 flex">
<Input
type="number"
min={1}
onChange={(evt) => {
const val = Number(evt.target?.value);
setRequiresConfirmationSetup({
unit:
requiresConfirmationSetup?.unit ??
defaultRequiresConfirmationSetup.unit,
time: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.time", val);
}}
className="!m-0 block w-16 rounded-md border-gray-300 text-sm [appearance:textfield]"
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
/>
<select
onChange={(evt) => {
const val = evt.target.value as UnitTypeLongPlural;
setRequiresConfirmationSetup({
time:
requiresConfirmationSetup?.time ??
defaultRequiresConfirmationSetup.time,
unit: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.unit", val);
}}
className="ml-2 block h-9 rounded-md border-gray-300 py-2 pl-3 pr-10 text-sm focus:outline-none"
defaultValue={
metadata?.requiresConfirmationThreshold?.unit ||
defaultRequiresConfirmationSetup.unit
}>
<option value="minutes">{t("minute_timeUnit")}</option>
<option value="hours">{t("hour_timeUnit")}</option>
</select>
</div>
),
}}
/>
</Label>
</div>
</div>
</RadioGroup.Root>
</SettingsToggle>
)}
/>
)}
</div>
</div>
);
}
@@ -4,21 +4,20 @@ import { FormEvent, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import { AvatarSSR } from "@components/ui/AvatarSSR";
interface IUserProfile {
user?: User;
}
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
type FormData = {
bio: string;
};
interface IUserProfileProps {
user: IOnboardingPageProps["user"];
}
const UserProfile = (props: IUserProfile) => {
const UserProfile = (props: IUserProfileProps) => {
const { user } = props;
const { t } = useLocale();
const avatarRef = useRef<HTMLInputElement>(null!);
@@ -100,7 +99,14 @@ const UserProfile = (props: IUserProfile) => {
return (
<form onSubmit={onSubmit}>
<div className="flex flex-row items-center justify-start rtl:justify-end">
{user && <AvatarSSR user={user} alt="Profile picture" className="h-16 w-16" />}
{user && (
<Avatar
alt={user.username || "user avatar"}
gravatarFallbackMd5={user.emailMd5}
size="lg"
imageSrc={imageSrc}
/>
)}
<input
ref={avatarRef}
type="hidden"
@@ -1,17 +1,18 @@
import { ArrowRightIcon } from "@heroicons/react/outline";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button, TimezoneSelect } from "@calcom/ui";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
interface IUserSettingsProps {
user: User;
user: IOnboardingPageProps["user"];
nextStep: () => void;
}
@@ -23,11 +24,9 @@ const UserSettings = (props: IUserSettingsProps) => {
register,
handleSubmit,
formState: { errors },
control,
} = useForm({
defaultValues: {
name: user?.name || "",
username: user?.username || "",
},
reValidateMode: "onChange",
});
@@ -41,39 +40,19 @@ const UserSettings = (props: IUserSettingsProps) => {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: onSuccess,
});
const { data: stripeCustomer } = trpc.viewer.stripeCustomer.useQuery();
const paymentRequired = stripeCustomer?.isPremium ? !stripeCustomer?.paidForPremium : false;
const onSubmit = handleSubmit((data) => {
if (paymentRequired) {
return;
}
mutation.mutate({
name: data.name,
timeZone: selectedTimeZone,
});
});
const [currentUsername, setCurrentUsername] = useState(user.username || "");
return (
<form onSubmit={onSubmit}>
<div className="space-y-6">
{/* Username textfield */}
<Controller
control={control}
name="username"
render={({ field: { value, ref, onChange } }) => (
<UsernameAvailability
readonly={true}
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
user={user}
/>
)}
/>
<UsernameAvailabilityField user={user} />
{/* Full name textfield */}
<div className="w-full">
@@ -0,0 +1,73 @@
import type { Attendee, Booking, User } from "@prisma/client";
import type { FC } from "react";
import { useMemo } from "react";
import { JsonLd } from "react-schemaorg";
import type { EventReservation, Person, ReservationStatusType } from "schema-dts";
type EventSchemaUser = Pick<User, "name" | "email">;
type EventSchemaAttendee = Pick<Attendee, "name" | "email">;
interface EventReservationSchemaInterface {
reservationId: Booking["uid"];
eventName: Booking["title"];
startTime: Booking["startTime"];
endTime: Booking["endTime"];
organizer: EventSchemaUser | null;
attendees: EventSchemaAttendee[];
location: Booking["location"];
description: Booking["description"];
status: Booking["status"];
}
const EventReservationSchema: FC<EventReservationSchemaInterface> = ({
reservationId,
eventName,
startTime,
endTime,
organizer,
attendees,
location,
description,
status,
}) => {
const reservationStatus = useMemo<ReservationStatusType>(() => {
switch (status) {
case "ACCEPTED":
return "ReservationConfirmed";
case "REJECTED":
case "CANCELLED":
return "ReservationCancelled";
case "PENDING":
return "ReservationPending";
default:
return "ReservationHold";
}
}, [status]);
return (
<JsonLd<EventReservation>
item={{
"@context": "https://schema.org",
"@type": "EventReservation",
reservationId,
reservationStatus,
reservationFor: {
"@type": "Event",
name: eventName,
startDate: startTime.toString(),
endDate: endTime.toString(),
organizer: organizer
? ({ "@type": "Person", name: organizer.name, email: organizer.email } as Person)
: undefined,
attendee: attendees?.map(
(person) => ({ "@type": "Person", name: person.name, email: person.email } as Person)
),
location: location || undefined,
description: description || undefined,
},
}}
/>
);
};
export default EventReservationSchema;
@@ -3,7 +3,7 @@ import { debounce, noop } from "lodash";
import { useRouter } from "next/router";
import { RefCallback, useEffect, useMemo, useState } from "react";
import { getPremiumPlanMode, getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { fetchUsername } from "@calcom/lib/fetchUsername";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -24,9 +24,7 @@ import {
} from "@calcom/ui";
export enum UsernameChangeStatusEnum {
NORMAL = "NORMAL",
UPGRADE = "UPGRADE",
DOWNGRADE = "DOWNGRADE",
}
interface ICustomUsernameProps {
@@ -37,43 +35,21 @@ interface ICustomUsernameProps {
setInputUsernameValue: (value: string) => void;
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<
User,
| "username"
| "name"
| "email"
| "bio"
| "avatar"
| "timeZone"
| "weekStart"
| "hideBranding"
| "theme"
| "plan"
| "brandColor"
| "darkBrandColor"
| "timeFormat"
| "metadata"
>;
user: Pick<User, "username" | "metadata">;
readonly?: boolean;
}
const obtainNewUsernameChangeCondition = ({
userIsPremium,
isNewUsernamePremium,
stripeCustomer,
}: {
userIsPremium: boolean;
isNewUsernamePremium: boolean;
stripeCustomer: RouterOutputs["viewer"]["stripeCustomer"] | undefined;
}) => {
if (!userIsPremium && isNewUsernamePremium && !stripeCustomer?.paidForPremium) {
if (!userIsPremium && isNewUsernamePremium) {
return UsernameChangeStatusEnum.UPGRADE;
}
if (userIsPremium && !isNewUsernamePremium && getPremiumPlanMode() === "subscription") {
return UsernameChangeStatusEnum.DOWNGRADE;
}
return UsernameChangeStatusEnum.NORMAL;
};
const PremiumTextfield = (props: ICustomUsernameProps) => {
@@ -106,7 +82,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
setIsInputUsernamePremium(data.premium);
setUsernameIsAvailable(data.available);
}, 150),
[]
[currentUsername]
);
useEffect(() => {
@@ -137,7 +113,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
});
// when current username isn't set - Go to stripe to check what username he wanted to buy and was it a premium and was it paid for
const paymentRequired = !currentUsername && stripeCustomer?.isPremium && !stripeCustomer?.paidForPremium;
const paymentRequired = !currentUsername && stripeCustomer?.isPremium;
const usernameChangeCondition = obtainNewUsernameChangeCondition({
userIsPremium: isCurrentUsernamePremium,
@@ -194,7 +170,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
};
const saveUsername = () => {
if (usernameChangeCondition === UsernameChangeStatusEnum.NORMAL) {
if (usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE) {
updateUsername.mutate({
username: inputUsernameValue,
});
@@ -279,14 +255,6 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
{paymentMsg}
{markAsError && <p className="mt-1 text-xs text-red-500">Username is already taken</p>}
{usernameIsAvailable && (
<p className={classNames("mt-1 text-xs text-gray-900")}>
{usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE && (
<>{t("premium_to_standard_username_description")}</>
)}
</p>
)}
<Dialog open={openDialogSaveUsername}>
<DialogContent>
<div className="flex flex-row">
@@ -295,13 +263,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
</div>
<div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} />
{usernameChangeCondition && usernameChangeCondition !== UsernameChangeStatusEnum.NORMAL && (
<p className="-mt-4 mb-4 text-sm text-gray-800">
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE &&
t("change_username_standard_to_premium")}
{usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE &&
t("change_username_premium_to_standard")}
</p>
{usernameChangeCondition && usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
<p className="mb-4 text-sm text-gray-800">{t("change_username_standard_to_premium")}</p>
)}
<div className="flex w-full flex-wrap rounded-sm bg-gray-100 py-3 text-sm">
@@ -323,8 +286,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="mt-4 flex flex-row-reverse gap-x-2">
{/* redirect to checkout */}
{(usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE ||
usernameChangeCondition === UsernameChangeStatusEnum.DOWNGRADE) && (
{usernameChangeCondition === UsernameChangeStatusEnum.UPGRADE && (
<Button
type="button"
loading={updateUsername.isLoading}
@@ -336,7 +298,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
</Button>
)}
{/* Normal save */}
{usernameChangeCondition === UsernameChangeStatusEnum.NORMAL && (
{usernameChangeCondition !== UsernameChangeStatusEnum.UPGRADE && (
<Button
type="button"
loading={updateUsername.isLoading}
@@ -124,6 +124,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
<Input
ref={usernameRef}
name="username"
value={inputUsernameValue}
autoComplete="none"
autoCapitalize="none"
autoCorrect="none"
@@ -133,7 +134,6 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
: ""
)}
defaultValue={currentUsername}
onChange={(event) => {
event.preventDefault();
setInputUsernameValue(event.target.value);
@@ -1,6 +1,51 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import { User } from "@calcom/prisma/client";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { PremiumTextfield } from "./PremiumTextfield";
import { UsernameTextfield } from "./UsernameTextfield";
export const UsernameAvailability = IS_SELF_HOSTED ? UsernameTextfield : PremiumTextfield;
interface UsernameAvailabilityFieldProps {
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<User, "username" | "metadata">;
}
export const UsernameAvailabilityField = ({
onSuccessMutation,
onErrorMutation,
user,
}: UsernameAvailabilityFieldProps) => {
const [currentUsername, setCurrentUsername] = useState(user.username ?? "");
const formMethods = useForm({
defaultValues: {
username: currentUsername,
},
});
return (
<Controller
control={formMethods.control}
name="username"
render={({ field: { ref, onChange, value } }) => {
return (
<UsernameAvailability
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
user={user}
/>
);
}}
/>
);
};
@@ -1,12 +0,0 @@
import { UserPlan } from "@prisma/client";
/**
* TODO: It should be exposed at a single place.
*/
export function useExposePlanGlobally(plan: UserPlan) {
// Don't wait for component to mount. Do it ASAP. Delaying it would delay UI Configuration.
if (typeof window !== "undefined") {
// This variable is used by embed-iframe to determine if we should allow UI configuration
window.CalComPlan = plan;
}
}
+2 -4
View File
@@ -1,5 +1,3 @@
import { User } from "@prisma/client";
export function isBrandingHidden<TUser extends Pick<User, "hideBranding" | "plan">>(user: TUser) {
return user.hideBranding && user.plan !== "FREE";
export function isBrandingHidden(hideBrandingSetting: boolean, belongsToActiveTeam: boolean) {
return belongsToActiveTeam && hideBrandingSetting;
}
+23
View File
@@ -7,6 +7,29 @@ export type EmbedProps = {
export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
const ssrResponse = await getServerSideProps(context);
const embed = context.query.embed;
if ("redirect" in ssrResponse) {
// Use a dummy URL https://base as the fallback base URL so that URL parsing works for relative URLs as well.
const destinationUrlObj = new URL(ssrResponse.redirect.destination, "https://base");
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl =
destinationUrlObj.pathname +
"/embed?" +
destinationUrlObj.searchParams.toString() +
"&embed=" +
embed;
return {
...ssrResponse,
redirect: {
...ssrResponse.redirect,
destination: newDestinationUrl,
},
};
}
if (!("props" in ssrResponse)) {
return ssrResponse;
}
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.3.5",
"version": "2.3.7",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -107,12 +107,14 @@
"react-live-chat-loader": "^2.7.3",
"react-multi-email": "^0.5.3",
"react-phone-number-input": "^3.2.7",
"react-schemaorg": "^2.0.0",
"react-select": "^5.4.0",
"react-timezone-select": "^1.3.2",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
"rrule": "^2.7.1",
"schema-dts": "^1.1.0",
"short-uuid": "^4.2.0",
"stripe": "^9.16.0",
"superjson": "1.9.1",
+4 -1
View File
@@ -1,9 +1,12 @@
import Head from "next/head";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui";
export default function Error500() {
const { t } = useLocale();
return (
<div className="flex h-screen">
<Head>
@@ -24,7 +27,7 @@ export default function Error500() {
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
away for you.
</p>
<Button href={`${WEBSITE_URL}/support`}>Contact support</Button>
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
<Button color="secondary" href="javascript:history.back()" className="ml-2">
Go back
</Button>
-3
View File
@@ -28,7 +28,6 @@ import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@@ -92,7 +91,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const query = { ...router.query };
delete query.user; // So it doesn't display in the Link (and make tests fail)
useExposePlanGlobally("PRO");
const nameOrUsername = user.name || user.username || "";
const telemetry = useTelemetry();
@@ -272,7 +270,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
darkBrandColor: true,
avatar: true,
theme: true,
plan: true,
away: true,
verified: true,
allowDynamicBooking: true,
+18 -7
View File
@@ -10,8 +10,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
import prisma from "@calcom/prisma";
import { User } from "@calcom/prisma/client";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@@ -71,7 +72,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
id: true,
username: true,
away: true,
plan: true,
name: true,
hideBranding: true,
timeZone: true,
@@ -110,6 +110,11 @@ async function getUserPageProps(context: GetStaticPropsContext) {
},
],
},
teams: {
include: {
team: true,
},
},
},
});
@@ -121,10 +126,10 @@ async function getUserPageProps(context: GetStaticPropsContext) {
"strikethrough",
]);
if (!user || !user.eventTypes) return { notFound: true };
if (!user || !user.eventTypes.length) return { notFound: true };
const [eventType]: (typeof user.eventTypes[number] & {
users: Pick<User, "name" | "username" | "hideBranding" | "plan" | "timeZone">[];
users: Pick<User, "name" | "username" | "hideBranding" | "timeZone">[];
})[] = [
{
...user.eventTypes[0],
@@ -133,7 +138,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
},
],
@@ -150,6 +154,13 @@ async function getUserPageProps(context: GetStaticPropsContext) {
locations: privacyFilteredLocations(locations),
descriptionAsSafeHTML: eventType.description ? md.render(eventType.description) : null,
});
// Check if the user you are logging into has any active teams
const hasActiveTeam =
user.teams.filter((m) => {
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
}).length > 0;
return {
props: {
@@ -167,6 +178,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
away: user?.away,
isDynamic: false,
trpcState: ssg.dehydrate(),
isBrandingHidden: isBrandingHidden(user.hideBranding, hasActiveTeam),
},
revalidate: 10, // seconds
};
@@ -212,7 +224,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
},
},
theme: true,
plan: true,
},
});
@@ -232,7 +243,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
};
}),
@@ -262,6 +272,7 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
isDynamic: true,
away: false,
trpcState: ssg.dehydrate(),
isBrandingHidden: false, // I think we should always show branding for dynamic groups - saves us checking every single user
},
revalidate: 10, // seconds
};
+5 -1
View File
@@ -1,5 +1,6 @@
import { DefaultSeo } from "next-seo";
import Head from "next/head";
import Script from "next/script";
import superjson from "superjson";
import "@calcom/embed-core/src/embed-iframe";
@@ -36,8 +37,11 @@ function MyApp(props: AppProps) {
<AppProviders {...props}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Script
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
<Head>
<script dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }} />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head>
{getLayout(
+7 -5
View File
@@ -1,12 +1,12 @@
import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps } from "next/document";
import Script from "next/script";
type Props = Record<string, unknown> & DocumentProps;
function toRunBeforeReactOnClient() {
const calEmbedMode =
location.search.includes("embed=") ||
/* Iframe Name */
window.name.includes("cal-embed");
const calEmbedMode = typeof new URL(document.URL).searchParams.get("embed") === "string";
/* Iframe Name */
window.name.includes("cal-embed");
window.isEmbed = () => {
// Once an embed mode always an embed mode
@@ -70,7 +70,9 @@ class MyDocument extends Document<Props> {
{/* Define isEmbed here so that it can be shared with App(embed-iframe) as well as the following code to change background and hide body
Persist the embed mode in sessionStorage because query param might get lost during browsing.
*/}
<script
<Script
id="run-before-client"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `(${toRunBeforeReactOnClient.toString()})()`,
}}
+18
View File
@@ -20,6 +20,7 @@ import { defaultCookies } from "@calcom/lib/default-cookies";
import rateLimit from "@calcom/lib/rateLimit";
import { serverConfig } from "@calcom/lib/serverConfig";
import prisma from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
import { randomString } from "@lib/random";
@@ -63,6 +64,11 @@ const providers: Provider[] = [
password: true,
twoFactorEnabled: true,
twoFactorSecret: true,
teams: {
include: {
team: true,
},
},
},
});
@@ -116,6 +122,13 @@ const providers: Provider[] = [
intervalInMs: 60 * 1000, // 1 minute
});
await limiter.check(10, user.email); // 10 requests per minute
// Check if the user you are logging into has any active teams
const hasActiveTeams =
user.teams.filter((m) => {
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
}).length > 0;
// authentication success- but does it meet the minimum password requirements?
if (user.role === "ADMIN" && !isPasswordValid(credentials.password, false, true)) {
@@ -125,6 +138,7 @@ const providers: Provider[] = [
email: user.email,
name: user.name,
role: "USER",
belongsToActiveTeam: hasActiveTeams,
};
}
@@ -134,6 +148,7 @@ const providers: Provider[] = [
email: user.email,
name: user.name,
role: user.role,
belongsToActiveTeam: hasActiveTeams,
};
},
}),
@@ -272,6 +287,7 @@ export default NextAuth({
email: user.email,
role: user.role,
impersonatedByUID: user?.impersonatedByUID,
belongsToActiveTeam: user?.belongsToActiveTeam,
};
}
@@ -307,6 +323,7 @@ export default NextAuth({
email: existingUser.email,
role: existingUser.role,
impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
};
}
@@ -324,6 +341,7 @@ export default NextAuth({
username: token.username as string,
role: token.role as UserPermissionRole,
impersonatedByUID: token.impersonatedByUID as number,
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
},
};
return calendsoSession;
-1
View File
@@ -46,7 +46,6 @@ async function handler(req: NextApiRequest) {
name: parsedQuery.data.full_name,
emailVerified: new Date(),
locale: "en", // TODO: We should revisit this
plan: "PRO",
identityProvider: IdentityProvider.CAL,
},
});
+1 -33
View File
@@ -1,10 +1,5 @@
import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.headers.authorization || req.query.apiKey;
if (process.env.CRON_API_KEY !== apiKey) {
@@ -18,35 +13,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/**
* TODO:
* We should add and extra check for non-paying customers in Stripe so we can
* downgrade them here.
* Remove this endpoint
*/
await prisma.user.updateMany({
data: {
plan: "FREE",
},
where: {
plan: "TRIAL",
OR: [
/**
* If the user doesn't have a trial end date,
* use the default 14 day trial from creation.
*/
{
createdDate: {
lt: dayjs().subtract(TRIAL_LIMIT_DAYS, "day").toDate(),
},
trialEndsAt: null,
},
/** If it does, then honor the trial end date. */
{
trialEndsAt: {
lt: dayjs().toDate(),
},
},
],
},
});
res.json({ ok: true });
}
+4 -3
View File
@@ -60,9 +60,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("AttendeeScheduledEmail", {
attendee: evt.attendees[0],
calEvent: evt,
renderEmail("DisabledAppEmail", {
appName: "Stripe",
appType: ["payment"],
t,
})
);
res.end();
@@ -47,7 +47,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
username: true,
id: true,
plan: true,
createdDate: true,
},
});
@@ -81,7 +80,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
? new Date(lastBooking.booking.createdAt).toLocaleDateString("en-US")
: "No info"
}</li>
<li><b>Plan:</b>&nbsp;${user.plan}</li>
<li><b>Account created:</b>&nbsp;${new Date(user.createdDate).toLocaleDateString("en-US")}</li>
</ul>
`,
-68
View File
@@ -1,68 +0,0 @@
import { MembershipRole } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import { getSession } from "@lib/auth";
import slugify from "@lib/slugify";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
if (!session?.user?.id) {
res.status(401).json({ message: "Not authenticated" });
return;
}
const ownerUser = await prisma.user.findFirst({
where: {
id: session.user.id,
},
select: {
id: true,
username: true,
createdDate: true,
name: true,
plan: true,
email: true,
},
});
if (req.method === "POST") {
const slug = slugify(req.body.name);
const nameCollisions = await prisma.team.count({
where: {
OR: [{ name: req.body.name }, { slug: slug }],
},
});
if (nameCollisions > 0) {
return res.status(409).json({ errorCode: "TeamNameCollision", message: "Team name already taken." });
}
const createTeam = await prisma.team.create({
data: {
name: req.body.name,
slug: slug,
},
});
await prisma.membership.create({
data: {
teamId: createTeam.id,
userId: session.user.id,
role: MembershipRole.OWNER,
accepted: true,
},
});
// Sync Services: Close.com
closeComUpsertTeamUser(createTeam, ownerUser, MembershipRole.OWNER);
return res.status(201).json({ message: "Team created" });
}
res.status(404).json({ message: "Team not found" });
}
+53 -67
View File
@@ -1,86 +1,72 @@
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import prisma from "@calcom/prisma";
import { defaultAvatarSrc } from "@lib/profile";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// const username = req.url?.substring(1, req.url.lastIndexOf("/"));
const username = req.query.username as string;
const teamname = req.query.teamname as string;
let identity;
let linksToThisRoute = false;
const querySchema = z
.object({
username: z.string(),
teamname: z.string(),
})
.partial();
async function getIdentityData(req: NextApiRequest) {
const { username, teamname } = querySchema.parse(req.query);
if (username) {
const user = await prisma.user.findUnique({
where: {
username: username,
},
select: {
avatar: true,
email: true,
},
where: { username },
select: { avatar: true, email: true },
});
identity = {
return {
name: username,
email: user?.email,
avatar: user?.avatar,
};
linksToThisRoute =
identity.avatar === `${CAL_URL}/${username}/avatar.png` ||
identity.avatar === `${WEBAPP_URL}/${username}/avatar.png`;
} else if (teamname) {
const team = await prisma.team.findUnique({
where: {
slug: teamname,
},
select: {
logo: true,
},
});
identity = {
name: teamname,
shouldDefaultBeNameBased: true,
avatar: team?.logo,
};
linksToThisRoute =
identity.avatar === `${CAL_URL}/team/${teamname}/avatar.png` ||
identity.avatar === `${WEBAPP_URL}/team/${teamname}/avatar.png`;
}
const emailMd5 = crypto
.createHash("md5")
.update((identity?.email as string) || "guest@example.com")
.digest("hex");
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img || linksToThisRoute) {
let defaultSrc = defaultAvatarSrc({ md5: emailMd5 });
if (identity?.shouldDefaultBeNameBased) {
defaultSrc = getPlaceholderAvatar(null, identity.name);
}
res.writeHead(302, {
Location: defaultSrc,
if (teamname) {
const team = await prisma.team.findUnique({
where: { slug: teamname },
select: { logo: true },
});
res.end();
} else if (!img.includes("data:image")) {
res.writeHead(302, {
Location: img,
});
res.end();
} else {
const decoded = img
.toString()
.replace("data:image/png;base64,", "")
.replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
return {
name: teamname,
email: null,
avatar: team?.logo || getPlaceholderAvatar(null, teamname),
};
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const identity = await getIdentityData(req);
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img) {
res.writeHead(302, {
Location: defaultAvatarSrc({
md5: crypto
.createHash("md5")
.update(identity?.email || "guest@example.com")
.digest("hex"),
}),
});
return res.end();
}
if (!img.includes("data:image")) {
res.writeHead(302, { Location: img });
return res.end();
}
const decoded = img.toString().replace("data:image/png;base64,", "").replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
}
+9 -6
View File
@@ -1,5 +1,4 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { InferGetStaticPropsType, NextPageContext } from "next";
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import { ChangeEventHandler, useState } from "react";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
@@ -31,7 +30,10 @@ function AppsSearch({
);
}
export default function Apps({ appStore, categories }: InferGetStaticPropsType<typeof getServerSideProps>) {
export default function Apps({
categories,
appStore,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { t } = useLocale();
const [searchText, setSearchText] = useState<string | undefined>(undefined);
@@ -42,7 +44,8 @@ export default function Apps({ appStore, categories }: InferGetStaticPropsType<t
subtitle={t("app_store_description")}
actions={(className) => (
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
)}>
)}
emptyStore={!appStore.length}>
{!searchText && (
<>
<AppStoreCategories categories={categories} />
@@ -54,7 +57,7 @@ export default function Apps({ appStore, categories }: InferGetStaticPropsType<t
);
}
export const getServerSideProps = async (context: NextPageContext) => {
export const getServerSideProps: GetServerSideProps = async (context) => {
const ssg = await ssgInit(context);
const session = await getSession(context);
@@ -77,7 +80,6 @@ export const getServerSideProps = async (context: NextPageContext) => {
}, {} as Record<string, number>);
return {
props: {
trpcState: ssg.dehydrate(),
categories: Object.entries(categories)
.map(([name, count]): { name: AppCategories; count: number } => ({
name: name as AppCategories,
@@ -87,6 +89,7 @@ export const getServerSideProps = async (context: NextPageContext) => {
return b.count - a.count;
}),
appStore,
trpcState: ssg.dehydrate(),
},
};
};
+48
View File
@@ -0,0 +1,48 @@
import { useState } from "react";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { WizardForm } from "@calcom/ui";
import SetupFormStep1 from "./steps/SetupFormStep1";
import StepDone from "./steps/StepDone";
export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const [isLoadingStep1, setIsLoadingStep1] = useState(false);
const shouldDisable = props.userCount !== 0;
const steps = [
{
title: t("administrator_user"),
description: t("lets_create_first_administrator_user"),
content: shouldDisable ? <StepDone /> : <SetupFormStep1 setIsLoading={setIsLoadingStep1} />,
isLoading: isLoadingStep1,
},
{
title: t("enable_apps"),
description: t("enable_apps_description"),
content: <AdminAppsList baseURL="/auth/setup" />,
isLoading: false,
},
];
return (
<>
<main className="flex items-center bg-gray-100 print:h-full">
<WizardForm href="/auth/setup" steps={steps} disableNavigation={shouldDisable} />
</main>
</>
);
}
export const getServerSideProps = async () => {
const userCount = await prisma.user.count();
return {
props: {
userCount,
},
};
};
@@ -1,34 +1,14 @@
import { CheckIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import * as z from "zod";
import { isPasswordValid } from "@calcom/lib/auth";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { EmailField, Label, PasswordField, TextField, WizardForm } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const StepDone = () => {
const { t } = useLocale();
return (
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<CheckIcon className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
);
};
import { EmailField, Label, PasswordField, TextField } from "@calcom/ui";
const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
const router = useRouter();
@@ -83,13 +63,19 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
},
});
if (response.status === 200) {
router.replace(`/auth/login?email=${data.email_address.toLowerCase()}`);
await signIn("credentials", {
redirect: false,
callbackUrl: "/",
email: data.email_address.toLowerCase(),
password: data.password,
});
router.replace(`/auth/setup?step=2&category=calendar`);
} else {
router.replace("/auth/setup");
}
}, onError);
const longWebsiteUrl = process.env.NEXT_PUBLIC_WEBSITE_URL.length > 30;
const longWebsiteUrl = WEBSITE_URL.length > 30;
return (
<FormProvider {...formMethods}>
@@ -201,37 +187,4 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
);
};
export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const [isLoadingStep1, setIsLoadingStep1] = useState(false);
const steps = [
{
title: t("administrator_user"),
description: t("lets_create_first_administrator_user"),
content: props.userCount !== 0 ? <StepDone /> : <SetupFormStep1 setIsLoading={setIsLoadingStep1} />,
enabled: props.userCount === 0, // to check if the wizard should show buttons to navigate through more steps
isLoading: isLoadingStep1,
},
];
return (
<>
<main className="flex h-screen items-center bg-gray-100 print:h-full">
<WizardForm href="/auth/setup" steps={steps} containerClassname="max-w-sm" />
</main>
</>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const userCount = await prisma.user.count();
return {
props: {
trpcState: ssr.dehydrate(),
userCount,
},
};
};
export default SetupFormStep1;
@@ -0,0 +1,19 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
const StepDone = () => {
const { t } = useLocale();
return (
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<Icon.FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
);
};
export default StepDone;
+2 -2
View File
@@ -3,7 +3,7 @@ import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { getPremiumPlanPrice } from "@calcom/app-store/stripepayment/lib/utils";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import {
hostedCal,
@@ -161,7 +161,7 @@ const getStripePremiumUsernameUrl = async ({
customer: customer.id,
line_items: [
{
price: getPremiumPlanPrice(),
price: getPremiumMonthlyPlanPriceId(),
quantity: 1,
},
],
+6 -2
View File
@@ -29,6 +29,8 @@ import { HttpError } from "@lib/core/http/error";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
import { ssgInit } from "@server/lib/ssg";
const querySchema = z.object({
schedule: stringOrNumber,
});
@@ -91,7 +93,7 @@ export default function Availability({ schedule }: { schedule: number }) {
return (
<Shell
backPath="/availability"
title={data?.schedule.name && data.schedule.name + " | " + t("availability")}
title={data?.schedule.name ? data.schedule.name + " | " + t("availability") : t("availability")}
heading={
<Controller
control={form.control}
@@ -209,14 +211,16 @@ export default function Availability({ schedule }: { schedule: number }) {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
schedule: params.data.schedule,
trpcState: ssg.dehydrate(),
},
revalidate: 10, // seconds
};
+32
View File
@@ -1,4 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetServerSidePropsContext } from "next";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -10,6 +11,8 @@ import { HttpError } from "@lib/core/http/error";
import SkeletonLoader from "@components/availability/SkeletonLoader";
import { ssrInit } from "@server/lib/ssr";
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
const { t } = useLocale();
const utils = trpc.useContext();
@@ -45,6 +48,24 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
},
});
const updateMutation = trpc.viewer.availability.schedule.update.useMutation({
onSuccess: async ({ schedule }) => {
await utils.viewer.availability.list.invalidate();
showToast(
t("availability_updated_successfully", {
scheduleName: schedule.name,
}),
"success"
);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
// Adds smooth delete button - item fades and old item slides into place
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
@@ -71,6 +92,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
}}
key={schedule.id}
schedule={schedule}
updateDefault={updateMutation.mutate}
deleteFunction={deleteMutation.mutate}
/>
))}
@@ -93,3 +115,13 @@ export default function AvailabilityPage() {
</div>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
+56 -23
View File
@@ -41,6 +41,7 @@ import { timeZone } from "@lib/clock";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
import EventReservationSchema from "@components/schemas/EventReservationSchema";
import { HeadSeo } from "@components/seo/head-seo";
import { ssrInit } from "@server/lib/ssr";
@@ -56,26 +57,41 @@ function redirectToExternalUrl(url: string) {
function RedirectionToast({ url }: { url: string }) {
const [timeRemaining, setTimeRemaining] = useState(10);
const [isToastVisible, setIsToastVisible] = useState(true);
const parsedSuccessUrl = new URL(document.URL);
const parsedExternalUrl = new URL(url);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
for (const [name, value] of parsedExternalUrl.searchParams.entries()) {
parsedSuccessUrl.searchParams.set(name, value);
}
const urlWithSuccessParams =
parsedExternalUrl.origin +
parsedExternalUrl.pathname +
"?" +
parsedSuccessUrl.searchParams.toString() +
parsedExternalUrl.hash;
const { t } = useLocale();
const timerRef = useRef<number | null>(null);
const router = useRouter();
const { cancel: isCancellationMode } = querySchema.parse(router.query);
const urlWithSuccessParamsRef = useRef<string | null>();
if (isCancellationMode && timerRef.current) {
setIsToastVisible(false);
}
useEffect(() => {
if (!isToastVisible && timerRef.current) {
window.clearInterval(timerRef.current);
}
}, [isToastVisible]);
useEffect(() => {
const parsedExternalUrl = new URL(url);
const parsedSuccessUrl = new URL(document.URL);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
for (const [name, value] of parsedExternalUrl.searchParams.entries()) {
parsedSuccessUrl.searchParams.set(name, value);
}
const urlWithSuccessParams =
parsedExternalUrl.origin +
parsedExternalUrl.pathname +
"?" +
parsedSuccessUrl.searchParams.toString() +
parsedExternalUrl.hash;
urlWithSuccessParamsRef.current = urlWithSuccessParams;
timerRef.current = window.setInterval(() => {
if (timeRemaining > 0) {
setTimeRemaining((timeRemaining) => {
@@ -89,7 +105,7 @@ function RedirectionToast({ url }: { url: string }) {
return () => {
window.clearInterval(timerRef.current as number);
};
}, [timeRemaining, urlWithSuccessParams]);
}, [timeRemaining, url]);
if (!isToastVisible) {
return null;
@@ -112,7 +128,9 @@ function RedirectionToast({ url }: { url: string }) {
<div className="order-3 mt-2 w-full flex-shrink-0 sm:order-2 sm:mt-0 sm:w-auto">
<button
onClick={() => {
redirectToExternalUrl(urlWithSuccessParams);
if (urlWithSuccessParamsRef.current) {
redirectToExternalUrl(urlWithSuccessParamsRef.current);
}
}}
className="flex w-full items-center justify-center rounded-sm border border-transparent bg-white px-4 py-2 text-sm font-medium text-green-600 hover:bg-green-50">
{t("continue")}
@@ -123,7 +141,6 @@ function RedirectionToast({ url }: { url: string }) {
type="button"
onClick={() => {
setIsToastVisible(false);
window.clearInterval(timerRef.current as number);
}}
className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white">
<Icon.FiX className="h-6 w-6 text-white" />
@@ -178,12 +195,16 @@ export default function Success(props: SuccessProps) {
console.error(`No location found `);
}
const name = props.bookingInfo?.user?.name;
const email = props.bookingInfo?.user?.email;
const status = props.bookingInfo?.status;
const reschedule = props.bookingInfo.status === BookingStatus.ACCEPTED;
const cancellationReason = props.bookingInfo.cancellationReason;
const attendeeName =
typeof props?.bookingInfo?.attendees?.[0]?.name === "string"
? props?.bookingInfo?.attendees?.[0]?.name
: "Nameless";
const [is24h, setIs24h] = useState(isBrowserLocale24h());
const { data: session } = useSession();
@@ -205,8 +226,6 @@ export default function Success(props: SuccessProps) {
});
}
const attendeeName = typeof name === "string" ? name : "Nameless";
const eventNameObject = {
attendeeName,
eventType: props.eventType.title,
@@ -319,6 +338,19 @@ export default function Success(props: SuccessProps) {
return (
<div className={isEmbed ? "" : "h-screen"} data-testid="success-page">
{!isEmbed && (
<EventReservationSchema
reservationId={bookingInfo.uid}
eventName={eventName}
startTime={bookingInfo.startTime}
endTime={bookingInfo.endTime}
organizer={bookingInfo.user}
attendees={bookingInfo.attendees}
location={locationToDisplay}
description={bookingInfo.description}
status={status}
/>
)}
{userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4">
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}>
@@ -333,7 +365,9 @@ export default function Success(props: SuccessProps) {
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
{eventType.successRedirectUrl ? <RedirectionToast url={eventType.successRedirectUrl} /> : null}{" "}
{isSuccessBookingPage && !isCancellationMode && eventType.successRedirectUrl ? (
<RedirectionToast url={eventType.successRedirectUrl} />
) : null}{" "}
<div
className={classNames(
shouldAlignCentrally ? "text-center" : "",
@@ -824,7 +858,6 @@ const getEventTypesFromDB = async (id: number) => {
name: true,
username: true,
hideBranding: true,
plan: true,
theme: true,
brandColor: true,
darkBrandColor: true,
+5 -1
View File
@@ -14,6 +14,8 @@ import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";
import { ssgInit } from "@server/lib/ssg";
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
@@ -179,14 +181,16 @@ export default function Bookings() {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
status: params.data.status,
trpcState: ssg.dehydrate(),
},
};
};
+1 -3
View File
@@ -90,7 +90,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
},
theme: true,
plan: true,
},
});
@@ -117,7 +116,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: u.name,
username: u.username,
hideBranding: u.hideBranding,
plan: u.plan,
timeZone: u.timeZone,
})),
});
@@ -158,7 +156,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
away: user.away,
isDynamicGroup: false,
profile,
plan: user.plan,
date,
eventType: eventTypeObject,
workingHours,
@@ -166,6 +163,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
previousPage: context.req.headers.referer ?? null,
booking,
users: [user.username],
isBrandingHidden: user.hideBranding,
},
};
};
+11 -4
View File
@@ -8,9 +8,10 @@ import { useForm } from "react-hook-form";
import { z } from "zod";
import { StripeData } from "@calcom/app-store/stripepayment/lib/server";
import getApps, { getEventTypeAppData, getLocationOptions } from "@calcom/app-store/utils";
import { getEventTypeAppData, getLocationOptions } from "@calcom/app-store/utils";
import { EventLocationType, LocationObject } from "@calcom/core/location";
import { parseBookingLimit, parseRecurringEvent, validateBookingLimitOrder } from "@calcom/lib";
import getEnabledApps from "@calcom/lib/apps/getEnabledApps";
import { CAL_URL } from "@calcom/lib/constants";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
@@ -39,6 +40,7 @@ import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayo
import EventWorkflowsTab from "@components/eventtype/EventWorkfowsTab";
import { getTranslation } from "@server/lib/i18n";
import { ssrInit } from "@server/lib/ssr";
export type FormValues = {
title: string;
@@ -281,7 +283,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
} else {
if (!metadata?.multipleDuration?.includes(input.length)) {
if (input.length && !metadata?.multipleDuration?.includes(input.length)) {
throw new Error(t("event_setup_multiple_duration_default_error"));
}
}
@@ -316,6 +318,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const { req, query } = context;
const session = await getSession({ req });
const typeParam = parseInt(asStringOrThrow(query.type));
const ssr = await ssrInit(context);
if (Number.isNaN(typeParam)) {
return {
@@ -338,7 +341,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
id: true,
avatar: true,
email: true,
plan: true,
locale: true,
defaultScheduleId: true,
});
@@ -465,6 +467,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const credentials = await prisma.credential.findMany({
where: {
userId: session.user.id,
app: {
enabled: true,
},
},
select: {
id: true,
@@ -472,6 +477,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
key: true,
userId: true,
appId: true,
invalid: true,
},
});
@@ -524,7 +530,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
}
const currentUser = eventType.users.find((u) => u.id === session.user.id);
const t = await getTranslation(currentUser?.locale ?? "en", "common");
const integrations = getApps(credentials);
const integrations = await getEnabledApps(credentials);
const locationOptions = getLocationOptions(integrations, t);
const eventTypeObject = Object.assign({}, eventType, {
@@ -553,6 +559,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
team: eventTypeObject.team || null,
teamMembers,
currentUserMembership,
trpcState: ssr.dehydrate(),
},
};
};
+16 -7
View File
@@ -1,5 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Head from "next/head";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useEffect, useState } from "react";
@@ -38,6 +38,8 @@ import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import Avatar from "@components/ui/Avatar";
import AvatarGroup from "@components/ui/AvatarGroup";
import { ssrInit } from "@server/lib/ssr";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
@@ -531,9 +533,9 @@ const EventTypeListHeading = ({
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a className="font-bold">{profile?.name || ""}</a>
</Link>
{membershipCount && (
{membershipCount && teamId && (
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 rtl:mr-2">
<Link href="/settings/teams">
<Link href={`/settings/teams/${teamId}/members`}>
<a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
@@ -579,12 +581,9 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const EventTypesPage = () => {
const { t } = useLocale();
return (
<div>
<Head>
<title>Home | {APP_NAME}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading={t("event_types_page_title") as string}
subtitle={t("event_types_page_subtitle") as string}
@@ -622,4 +621,14 @@ const EventTypesPage = () => {
);
};
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default EventTypesPage;
@@ -11,15 +11,14 @@ import { User } from "@calcom/prisma/client";
import { Button, StepCard, Steps } from "@calcom/ui";
import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability";
import UserProfile from "@components/getting-started/steps-views/UserProfile";
import { UserSettings } from "@components/getting-started/steps-views/UserSettings";
interface IOnboardingPageProps {
user: User;
}
export type IOnboardingPageProps = inferSSRProps<typeof getServerSideProps>;
const INITIAL_STEP = "user-settings";
const steps = ["user-settings", "connected-calendar", "setup-availability", "user-profile"] as const;
@@ -165,7 +164,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
weekStart: true,
hideBranding: true,
theme: true,
plan: true,
brandColor: true,
darkBrandColor: true,
metadata: true,
+4 -1
View File
@@ -42,6 +42,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
if (!booking) {
return {
notFound: true,
} as {
notFound: true;
};
}
@@ -49,6 +51,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
// TODO: Show something in UI to let user know that this booking is not rescheduleable.
return {
notFound: true,
} as {
notFound: true;
};
}
@@ -62,7 +66,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
: booking.user?.username || "rick") /* This shouldn't happen */ +
"/" +
eventType?.slug;
return {
redirect: {
destination: "/" + eventPage + "?rescheduleUid=" + context.query.uid,
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[uid]";
export { default } from "../[uid]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
-14
View File
@@ -1,14 +0,0 @@
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
function AdminAppsView() {
return (
<>
<Meta title="Apps" description="apps_description" />
<h1>App listing</h1>
</>
);
}
AdminAppsView.getLayout = getLayout;
export default AdminAppsView;
@@ -0,0 +1,31 @@
import { GetServerSidePropsContext } from "next";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
const { t } = useLocale();
return (
<>
<Meta title={t("apps")} description={t("apps_description")} />
<AdminAppsList baseURL="/settings/admin/apps" className="w-0" />
</>
);
}
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;
@@ -1,16 +1,19 @@
import { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, getAdminLayout as getLayout, Meta, TextField } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminView() {
const { t } = useLocale();
const usernameRef = useRef<HTMLInputElement>(null);
return (
<>
<Meta title="Admin" description="Impersonation" />
<Meta title={t("admin")} description={t("impersonation")} />
<form
className="mb-6 w-full sm:w-1/2"
onSubmit={(e) => {
@@ -21,7 +24,7 @@ function AdminView() {
<div className="flex items-center space-x-2">
<TextField
containerClassName="w-full"
name="Impersonate User"
name={t("user_impersonation_heading")}
addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
ref={usernameRef}
hint={t("impersonate_user_tip")}
@@ -36,4 +39,14 @@ function AdminView() {
AdminView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminView;
+14
View File
@@ -1,5 +1,9 @@
import { GetServerSidePropsContext } from "next";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
return (
<>
@@ -11,4 +15,14 @@ function AdminAppsView() {
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;
+18 -2
View File
@@ -1,14 +1,30 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminUsersView() {
const { t } = useLocale();
return (
<>
<Meta title="Users" description="users_description" />
<h1>Users listing</h1>
<Meta title={t("users")} description={t("users_description")} />
<h1>{t("users_listing")}</h1>
</>
);
}
AdminUsersView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminUsersView;
+15 -8
View File
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import { HelpScout, useChat } from "react-live-chat-loader";
@@ -8,6 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, getSettingsLayout as getLayout, Icon, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
interface CtaRowProps {
title: string;
description: string;
@@ -33,7 +36,6 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
const BillingView = () => {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const isPro = user?.plan === "PRO";
const [, loadChat] = useChat();
const [showChat, setShowChat] = useState(false);
const router = useRouter();
@@ -50,21 +52,16 @@ const BillingView = () => {
<Meta title={t("billing")} description={t("manage_billing_description")} />
<div className="space-y-6 text-sm sm:space-y-8">
<CtaRow
className={classNames(!isPro && "pointer-events-none opacity-30")}
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<Button
color={isPro ? "primary" : "secondary"}
href={billingHref}
target="_blank"
EndIcon={Icon.FiExternalLink}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={Icon.FiExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
<Button color="secondary" onClick={onContactSupportClick}>
{t("billing_help_cta")}
{t("contact_support")}
</Button>
</CtaRow>
{showChat && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
@@ -75,4 +72,14 @@ const BillingView = () => {
BillingView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default BillingView;
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
@@ -18,6 +19,8 @@ import {
SkeletonLoader,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ApiKeysView = () => {
const { t } = useLocale();
@@ -93,4 +96,14 @@ const ApiKeysView = () => {
ApiKeysView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ApiKeysView;
@@ -1,3 +1,5 @@
import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import { APP_NAME } from "@calcom/lib/constants";
@@ -17,9 +19,12 @@ import {
Switch,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
@@ -41,7 +46,7 @@ const SkeletonLoader = () => {
const AppearanceView = () => {
const { t } = useLocale();
const session = useSession();
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
@@ -68,7 +73,7 @@ const AppearanceView = () => {
},
});
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null;
@@ -85,7 +90,7 @@ const AppearanceView = () => {
theme: values.theme || null,
});
}}>
<Meta title="Appearance" description="Manage settings for your booking appearance" />
<Meta title={t("appearance")} description={t("appearance_description")} />
<div className="mb-6 flex items-center text-sm">
<div>
<p className="font-semibold">{t("theme")}</p>
@@ -180,6 +185,7 @@ const AppearanceView = () => {
<div className="flex-none">
<Switch
id="hideBranding"
disabled={!session.data?.user.belongsToActiveTeam}
onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
}
@@ -204,6 +210,16 @@ const AppearanceView = () => {
AppearanceView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AppearanceView;
interface ThemeLabelProps {
variant: "light" | "dark" | "system";
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -22,12 +23,15 @@ import {
SkeletonButton,
SkeletonContainer,
SkeletonText,
showToast,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
@@ -66,15 +70,17 @@ const CalendarsView = () => {
async onSettled() {
await utils.viewer.connectedCalendars.invalidate();
},
onSuccess: async () => {
showToast(t("calendar_updated_successfully"), "success");
},
onError: () => {
showToast(t("unexpected_error_try_again"), "error");
},
});
return (
<>
<Meta
title="Calendars"
description="Configure how your event types interact with your calendars"
CTA={<AddCalendarButton />}
/>
<Meta title={t("calendars")} description={t("calendars_description")} CTA={<AddCalendarButton />} />
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
@@ -110,7 +116,7 @@ const CalendarsView = () => {
{t("check_for_conflicts")}
</h4>
<p className="pb-2 text-sm leading-5 text-gray-600">{t("select_calendars")}</p>
<List>
<List className="flex flex-col gap-6" noBorderTreatment>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.error && item.error.message && (
@@ -137,7 +143,7 @@ const CalendarsView = () => {
/>
)}
{item?.error === undefined && item.calendars && (
<ListItem className="flex-col">
<ListItem className="flex-col rounded-md">
<div className="flex w-full flex-1 items-center space-x-3 p-4 rtl:space-x-reverse">
{
// eslint-disable-next-line @next/next/no-img-element
@@ -164,7 +170,7 @@ const CalendarsView = () => {
<DisconnectIntegration
trashIcon
credentialId={item.credentialId}
buttonProps={{ size: "icon", color: "secondary" }}
buttonProps={{ className: "border border-gray-300" }}
/>
</div>
</div>
@@ -172,7 +178,7 @@ const CalendarsView = () => {
<p className="px-2 pt-4 text-sm text-neutral-500">
{t("toggle_calendars_conflict")}
</p>
<ul className="space-y-2 px-2 pt-4">
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
@@ -224,4 +230,14 @@ const CalendarsView = () => {
CalendarsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CalendarsView;
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -24,9 +25,12 @@ import {
SkeletonText,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@@ -60,11 +64,12 @@ const ConferencingLayout = () => {
const [deleteAppModal, setDeleteAppModal] = useState(false);
const [deleteCredentialId, setDeleteCredentialId] = useState<number>(0);
if (isLoading) return <SkeletonLoader />;
if (isLoading)
return <SkeletonLoader title={t("conferencing")} description={t("conferencing_description")} />;
return (
<div className="w-full bg-white sm:mx-0 xl:mt-0">
<Meta title="Conferencing" description="Add your favourite video conferencing apps for your meetings" />
<Meta title={t("conferencing")} description={t("conferencing_description")} />
<List>
{apps?.items &&
apps.items
@@ -128,4 +133,14 @@ const ConferencingLayout = () => {
ConferencingLayout.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ConferencingLayout;
+18 -4
View File
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
@@ -21,9 +22,12 @@ import {
import { withQuery } from "@lib/QueryCell";
import { nameOfDay } from "@lib/core/i18n/weekday";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@@ -47,14 +51,14 @@ const GeneralQueryView = () => {
const { t } = useLocale();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
if (!user) {
throw new Error(t("something_went_wrong"));
}
return (
<WithQuery
success={({ data }) => <GeneralView user={user} localeProp={data.locale} />}
customLoader={<SkeletonLoader />}
customLoader={<SkeletonLoader title={t("general")} description={t("general_description")} />}
/>
);
};
@@ -127,7 +131,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
weekStart: values.weekStart.value,
});
}}>
<Meta title="General" description="Manage settings for your language and timezone" />
<Meta title={t("general")} description={t("general_description")} />
<Controller
name="locale"
render={({ field: { value, onChange } }) => (
@@ -210,4 +214,14 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
GeneralQueryView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default GeneralQueryView;
+182 -168
View File
@@ -1,7 +1,8 @@
import { IdentityProvider } from "@prisma/client";
import crypto from "crypto";
import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { BaseSyntheticEvent, useEffect, useRef, useState } from "react";
import { BaseSyntheticEvent, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
@@ -35,11 +36,14 @@ import {
} from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonAvatar className=" h-12 w-12 px-4" />
@@ -59,14 +63,25 @@ interface DeleteAccountValues {
totpCode: string;
}
type FormValues = {
username: string;
avatar: string;
name: string;
email: string;
bio: string;
};
const ProfileView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: avatar, isLoading: isLoadingAvatar } = trpc.viewer.avatar.useQuery();
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: () => {
showToast(t("settings_updated_successfully"), "success");
utils.viewer.me.invalidate();
utils.viewer.avatar.invalidate();
setTempFormValues(null);
},
onError: () => {
showToast(t("error_updating_settings"), "error");
@@ -74,18 +89,14 @@ const ProfileView = () => {
});
const [confirmPasswordOpen, setConfirmPasswordOpen] = useState(false);
const [tempFormValues, setTempFormValues] = useState<FormValues | null>(null);
const [confirmPasswordErrorMessage, setConfirmPasswordDeleteErrorMessage] = useState("");
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
const [hasDeleteErrors, setHasDeleteErrors] = useState(false);
const [deleteErrorMessage, setDeleteErrorMessage] = useState("");
const form = useForm<DeleteAccountValues>();
const emailMd5 = crypto
.createHash("md5")
.update(user?.email || "example@example.com")
.digest("hex");
const onDeleteMeSuccessMutation = async () => {
await utils.viewer.me.invalidate();
showToast(t("Your account was deleted"), "success");
@@ -100,7 +111,7 @@ const ProfileView = () => {
const confirmPasswordMutation = trpc.viewer.auth.verifyPassword.useMutation({
onSuccess() {
mutation.mutate(formMethods.getValues());
if (tempFormValues) mutation.mutate(tempFormValues);
setConfirmPasswordOpen(false);
},
onError() {
@@ -146,6 +157,7 @@ const ProfileView = () => {
deleteMeWithoutPasswordMutation.mutate();
}
};
const onConfirm = ({ totpCode }: DeleteAccountValues, e: BaseSyntheticEvent | undefined) => {
e?.preventDefault();
if (isCALIdentityProviver) {
@@ -156,38 +168,6 @@ const ProfileView = () => {
}
};
const formMethods = useForm({
defaultValues: {
username: "",
avatar: "",
name: "",
email: "",
bio: "",
},
});
const {
reset,
formState: { isSubmitting, isDirty },
} = formMethods;
useEffect(() => {
if (user) {
reset(
{
avatar: user?.avatar || "",
username: user?.username || "",
name: user?.name || "",
email: user?.email || "",
bio: user?.bio || "",
},
{
keepDirtyValues: true,
}
);
}
}, [reset, user]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const passwordRef = useRef<HTMLInputElement>(null!);
@@ -199,143 +179,100 @@ const ProfileView = () => {
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
const onSuccessfulUsernameUpdate = async () => {
showToast(t("settings_updated_successfully"), "success");
await utils.viewer.me.invalidate();
};
const onErrorInUsernameUpdate = () => {
showToast(t("error_updating_settings"), "error");
};
if (isLoading || !user || isLoadingAvatar || !avatar)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />;
if (isLoading || !user) return <SkeletonLoader />;
const isDisabled = isSubmitting || !isDirty;
const defaultValues = {
username: user.username || "",
avatar: avatar.avatar || "",
name: user.name || "",
email: user.email || "",
bio: user.bio || "",
};
return (
<>
<Form
form={formMethods}
handleSubmit={(values) => {
if (values.email !== user?.email && isCALIdentityProviver) {
<Meta title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
<ProfileForm
key={JSON.stringify(defaultValues)}
defaultValues={defaultValues}
onSubmit={(values) => {
if (values.email !== user.email && isCALIdentityProviver) {
setTempFormValues(values);
setConfirmPasswordOpen(true);
} else {
mutation.mutate(values);
}
}}>
<Meta title="Profile" description={t("profile_description", { appName: APP_NAME })} />
<div className="flex items-center">
<Controller
control={formMethods.control}
name="avatar"
render={({ field: { value } }) => (
<>
<Avatar alt="" imageSrc={value} gravatarFallbackMd5={emailMd5} size="lg" />
<div className="ml-4">
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
handleAvatarChange={(newAvatar) => {
formMethods.setValue("avatar", newAvatar, { shouldDirty: true });
}}
imageSrc={value}
/>
</div>
</>
}}
extraField={
<div className="mt-8">
<UsernameAvailabilityField
user={user}
onSuccessMutation={async () => {
showToast(t("settings_updated_successfully"), "success");
await utils.viewer.me.invalidate();
}}
onErrorMutation={() => {
showToast(t("error_updating_settings"), "error");
}}
/>
</div>
}
/>
<hr className="my-6 border-neutral-200" />
<Label>{t("danger_zone")}</Label>
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button
data-testid="delete-account"
color="destructive"
className="mt-1 border-2"
StartIcon={Icon.FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
<DialogContent
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation"
Icon={Icon.FiAlertTriangle}>
<>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && (
<PasswordField
data-testid="password"
name="password"
id="password"
autoComplete="current-password"
required
label="Password"
ref={passwordRef}
/>
)}
/>
</div>
<div className="mt-8">
<Controller
control={formMethods.control}
name="username"
render={({ field: { ref, onChange, value } }) => {
return (
<UsernameAvailability
currentUsername={user?.username || ""}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessfulUsernameUpdate}
onErrorMutation={onErrorInUsernameUpdate}
user={user}
/>
);
}}
/>
</div>
<div className="mt-8">
<TextField label={t("full_name")} {...formMethods.register("name")} />
</div>
<div className="mt-8">
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
</div>
<div className="mt-8">
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
</div>
<Button
disabled={isDisabled}
color="primary"
className="mt-8"
type="submit"
loading={mutation.isLoading}>
{t("update")}
</Button>
{user?.twoFactorEnabled && isCALIdentityProviver && (
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
<TwoFactor center={false} />
</Form>
)}
<hr className="my-6 border-neutral-200" />
<Label>{t("danger_zone")}</Label>
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button
data-testid="delete-account"
color="destructive"
className="mt-1 border-2"
StartIcon={Icon.FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
<DialogContent
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation"
Icon={Icon.FiAlertTriangle}>
<>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && (
<PasswordField
data-testid="password"
name="password"
id="password"
autoComplete="current-password"
required
label="Password"
ref={passwordRef}
/>
)}
{user?.twoFactorEnabled && isCALIdentityProviver && (
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
<TwoFactor center={false} />
</Form>
)}
{hasDeleteErrors && <Alert severity="error" title={deleteErrorMessage} />}
<DialogFooter>
<Button
color="primary"
data-testid="delete-account-confirm"
onClick={(e) => onConfirmButton(e)}>
{t("delete_my_account")}
</Button>
<DialogClose />
</DialogFooter>
</>
</DialogContent>
</Dialog>
</Form>
{hasDeleteErrors && <Alert severity="error" title={deleteErrorMessage} />}
<DialogFooter>
<Button
color="primary"
data-testid="delete-account-confirm"
onClick={(e) => onConfirmButton(e)}>
{t("delete_my_account")}
</Button>
<DialogClose />
</DialogFooter>
</>
</DialogContent>
</Dialog>
{/* If changing email, confirm password */}
<Dialog open={confirmPasswordOpen} onOpenChange={setConfirmPasswordOpen}>
@@ -369,6 +306,83 @@ const ProfileView = () => {
);
};
const ProfileForm = ({
defaultValues,
onSubmit,
extraField,
}: {
defaultValues: FormValues;
onSubmit: (values: FormValues) => void;
extraField?: React.ReactNode;
}) => {
const { t } = useLocale();
const emailMd5 = crypto
.createHash("md5")
.update(defaultValues.email || "example@example.com")
.digest("hex");
const formMethods = useForm<FormValues>({
defaultValues,
});
const {
formState: { isSubmitting, isDirty },
} = formMethods;
const isDisabled = isSubmitting || !isDirty;
return (
<Form form={formMethods} handleSubmit={onSubmit}>
<div className="flex items-center">
<Controller
control={formMethods.control}
name="avatar"
render={({ field: { value } }) => (
<>
<Avatar alt="" imageSrc={value} gravatarFallbackMd5={emailMd5} size="lg" />
<div className="ml-4">
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
handleAvatarChange={(newAvatar) => {
formMethods.setValue("avatar", newAvatar, { shouldDirty: true });
}}
imageSrc={value || undefined}
/>
</div>
</>
)}
/>
</div>
{extraField}
<div className="mt-8">
<TextField label={t("full_name")} {...formMethods.register("name")} />
</div>
<div className="mt-8">
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
</div>
<div className="mt-8">
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
</div>
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
{t("update")}
</Button>
</Form>
);
};
ProfileView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileView;
@@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -13,6 +14,8 @@ import {
Switch,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ProfileImpersonationView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
@@ -39,7 +42,7 @@ const ProfileImpersonationView = () => {
return (
<>
<Meta title="Impersonation Settings" description="" />
<Meta title={t("impersonation")} description={t("impersonation_description")} />
<Form
form={formMethods}
handleSubmit={({ disableImpersonation }) => {
@@ -74,4 +77,14 @@ const ProfileImpersonationView = () => {
ProfileImpersonationView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileImpersonationView;
@@ -1,4 +1,5 @@
import { IdentityProvider } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useForm } from "react-hook-form";
@@ -7,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Form, getSettingsLayout as getLayout, Meta, PasswordField, showToast } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
type ChangePasswordFormValues = {
oldPassword: string;
newPassword: string;
@@ -92,4 +95,14 @@ const PasswordView = () => {
PasswordView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default PasswordView;
+14
View File
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/user-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
@@ -1,12 +1,37 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, getSettingsLayout as getLayout, Loader, Meta, Switch } from "@calcom/ui";
import {
Badge,
getSettingsLayout as getLayout,
Meta,
Switch,
SkeletonButton,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import DisableTwoFactorModal from "@components/settings/DisableTwoFactorModal";
import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
<SkeletonText className="h-8 w-full" />
</div>
</div>
</SkeletonContainer>
);
};
const TwoFactorAuthView = () => {
const utils = trpc.useContext();
@@ -16,11 +41,11 @@ const TwoFactorAuthView = () => {
const [enableModalOpen, setEnableModalOpen] = useState(false);
const [disableModalOpen, setDisableModalOpen] = useState(false);
if (isLoading) return <Loader />;
if (isLoading) return <SkeletonLoader title={t("2fa")} description={t("2fa_description")} />;
return (
<>
<Meta title="Two-Factor Authentication" description="Manage settings for your account passwords" />
<Meta title={t("2fa")} description={t("2fa_description")} />
<div className="mt-6 flex items-start space-x-4">
<Switch
checked={user?.twoFactorEnabled}
@@ -35,7 +60,7 @@ const TwoFactorAuthView = () => {
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="text-sm text-gray-600">Add an extra layer of security to your account.</p>
<p className="text-sm text-gray-600">{t("add_an_extra_layer_of_security")}</p>
</div>
</div>
@@ -68,4 +93,14 @@ const TwoFactorAuthView = () => {
TwoFactorAuthView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default TwoFactorAuthView;
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-appearance-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-billing-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-members-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-profile-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/teams-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
+17 -2
View File
@@ -1,14 +1,19 @@
import { GetServerSidePropsContext } from "next";
import Head from "next/head";
import { CreateANewTeamForm } from "@calcom/features/ee/teams/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getWizardLayout as getLayout } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const CreateNewTeamPage = () => {
const { t } = useLocale();
return (
<>
<Head>
<title>Create a new Team</title>
<meta name="description" content="Create a new team to ease your organisational booking" />
<title>{t("create_new_team")}</title>
<meta name="description" content={t("create_new_team_description")} />
</Head>
<CreateANewTeamForm />
</>
@@ -17,4 +22,14 @@ const CreateNewTeamPage = () => {
CreateNewTeamPage.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CreateNewTeamPage;
-7
View File
@@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
@@ -14,7 +13,6 @@ import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -27,7 +25,6 @@ function TeamPage({ team }: TeamPageProps) {
useTheme();
const showMembers = useToggleQuery("members");
const { t } = useLocale();
useExposePlanGlobally("PRO");
const isEmbed = useIsEmbed();
const telemetry = useTelemetry();
const router = useRouter();
@@ -137,10 +134,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team) return { notFound: true } as { notFound: true };
const members = team.members.filter((member) => member.plan !== UserPlan.FREE);
team.members = members ?? [];
team.eventTypes = team.eventTypes.map((type) => ({
...type,
users: type.users.map((user) => ({
+2 -5
View File
@@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { privacyFilteredLocations, LocationObject } from "@calcom/core/location";
@@ -43,6 +42,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: true,
slug: true,
logo: true,
hideBranding: true,
eventTypes: {
where: {
slug: typeParam,
@@ -59,7 +59,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
username: true,
timeZone: true,
hideBranding: true,
plan: true,
brandColor: true,
darkBrandColor: true,
},
@@ -129,7 +128,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
})),
});
@@ -143,8 +141,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
// Team is always pro
plan: "PRO" as UserPlan,
profile: {
name: team.name || team.slug,
slug: team.slug,
@@ -160,6 +156,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
previousPage: context.req.headers.referer ?? null,
booking,
trpcState: ssg.dehydrate(),
isBrandingHidden: team.hideBranding,
},
};
};
+14
View File
@@ -1,8 +1,12 @@
import { GetServerSidePropsContext } from "next";
import { TeamsListing } from "@calcom/features/ee/teams/components";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Shell } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function Teams() {
const { t } = useLocale();
return (
@@ -22,4 +26,14 @@ function Teams() {
Teams.requiresLicense = false;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default Teams;
+14
View File
@@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/workflows/pages/index";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
+1 -1
View File
@@ -13,7 +13,7 @@ test.describe.configure({ mode: "parallel" });
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
const free = await users.create();
await page.goto(`/${free.username}`);
});
test.afterEach(async ({ users }) => {
+4 -68
View File
@@ -1,8 +1,5 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { getFreePlanPrice, getProPlanPrice } from "@calcom/app-store/stripepayment/lib/utils";
import dayjs from "@calcom/dayjs";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -27,7 +24,7 @@ test.describe("Change username on settings", () => {
/** TODO: Find out why it's timing out */
test.fixme("User can change username", async ({ page, users, prisma }) => {
const user = await users.create({ plan: UserPlan.TRIAL });
const user = await users.create();
await user.login();
// Try to go homepage
@@ -50,28 +47,13 @@ test.describe("Change username on settings", () => {
expect(newUpdatedUser.username).toBe("demousernamex");
});
test("User trial can update to PREMIUM username", async ({ page, users }, testInfo) => {
test("User can update to PREMIUM username", async ({ page, users }, testInfo) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
test.skip(IS_SELF_HOSTED, "It shouldn't run on self hosted");
const user = await users.create({ plan: UserPlan.TRIAL });
const customer = await stripe.customers.create({ email: `${user?.username}@example.com` });
await stripe.subscriptionSchedules.create({
customer: customer.id,
start_date: "now",
end_behavior: "release",
phases: [
{
items: [{ price: getProPlanPrice() }],
trial_end: dayjs().add(14, "day").unix(),
end_date: dayjs().add(14, "day").unix(),
},
{
items: [{ price: getFreePlanPrice() }],
},
],
});
const user = await users.create();
await stripe.customers.create({ email: `${user?.username}@example.com` });
await user.login();
await page.goto("/settings/my-account/profile");
@@ -97,50 +79,4 @@ test.describe("Change username on settings", () => {
await expect(page).toHaveURL(/.*checkout.stripe.com/);
});
test("User PRO can update to PREMIUM username", async ({ page, users }, testInfo) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_STRIPE_ENABLED, "It should only run if Stripe is installed");
test.skip(IS_SELF_HOSTED, "It shouldn't run on self hosted");
const user = await users.create({ plan: UserPlan.PRO });
const customer = await stripe.customers.create({ email: `${user?.username}@example.com` });
const paymentMethod = await stripe.paymentMethods.create({
type: "card",
card: {
number: "4242424242424242",
cvc: "123",
exp_month: 12,
exp_year: 2040,
},
});
await stripe.paymentMethods.attach(paymentMethod.id, { customer: customer.id });
await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: getProPlanPrice() }],
});
await user.login();
await page.goto("/settings/profile");
// Change username from normal to premium
const usernameInput = page.locator("[data-testid=username-input]");
await usernameInput.fill(`xx${testInfo.workerIndex}`);
// Click on save button
await page.click('button[type="submit"]');
// Validate modal text fields
const currentUsernameText = page.locator("[data-testid=current-username]").innerText();
const newUsernameText = page.locator("[data-testid=new-username]").innerText();
expect(currentUsernameText).not.toBe(newUsernameText);
// Click on Go to billing
await page.click("[data-testid=go-to-billing]", { timeout: 300 });
await page.waitForLoadState();
await expect(page).toHaveURL(/.*billing.stripe.com/);
});
});
@@ -10,7 +10,7 @@ import {
test("dynamic booking", async ({ page, users }) => {
const pro = await users.create();
await pro.login();
const free = await users.create({ plan: "FREE" });
const free = await users.create({ username: "free" });
await page.goto(`/${pro.username}+${free.username}`);
await test.step("book an event first day in next month", async () => {
+1 -31
View File
@@ -120,37 +120,7 @@ test.describe("Event Types tests", () => {
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
});
});
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
await free.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test("has at least 2 events where first is enabled", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const count = await $eventTypes.count();
expect(count).toBeGreaterThanOrEqual(2);
});
test("edit first event", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const firstEventTypeElement = $eventTypes.first();
await firstEventTypeElement.click();
await page.waitForNavigation({
url: (url) => {
return !!url.pathname.match(/\/event-types\/.+/);
},
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
expect(toast).toBeTruthy();
});
});
});
+4 -7
View File
@@ -1,6 +1,6 @@
import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client";
import { hash } from "bcryptjs";
import dayjs from "@calcom/dayjs";
@@ -238,7 +238,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
};
};
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale" | "name";
type CustomUserOptsKeys = "username" | "password" | "completedOnboarding" | "locale" | "name";
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneEnum };
// creates the actual user in the db.
@@ -247,13 +247,10 @@ const createUser = async (
opts?: CustomUserOpts | null
): Promise<PrismaType.UserCreateInput> => {
// build a unique name for our user
const uname = `${opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()}-${
workerInfo.workerIndex
}-${Date.now()}`;
const uname = `${opts?.username}-${workerInfo.workerIndex}-${Date.now()}`;
return {
username: uname,
name: opts?.name === undefined ? (opts?.plan ?? UserPlan.PRO).toUpperCase() : opts?.name,
plan: opts?.plan ?? UserPlan.PRO,
name: opts?.name,
email: `${uname}@example.com`,
password: await hashPassword(uname),
emailVerified: new Date(),
+2 -29
View File
@@ -1,5 +1,4 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { login } from "./fixtures/users";
import { test } from "./lib/fixtures";
@@ -13,10 +12,10 @@ test.describe("user can login & logout succesfully", async () => {
test.afterAll(async ({ users }) => {
await users.deleteAll();
});
test("login flow TRAIL user & logout using dashboard", async ({ page, users }) => {
test("login flow user & logout using dashboard", async ({ page, users }) => {
// log in trail user
await test.step("Log in", async () => {
const user = await users.create({ plan: UserPlan.TRIAL });
const user = await users.create();
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
@@ -24,11 +23,6 @@ test.describe("user can login & logout succesfully", async () => {
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-trial]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText("-TRIAL");
});
//
@@ -64,27 +58,6 @@ test.describe("Login and logout tests", () => {
await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
});
// Test login with all plans
const plans = [UserPlan.PRO, UserPlan.FREE];
plans.forEach((plan) => {
test(`Should login with a ${plan} account`, async ({ page, users }) => {
// Create user and login
const user = await users.create({ plan });
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-${plan.toLowerCase()}]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText(`-${plan}`);
});
});
test.describe("Login flow validations", async () => {
test("Should warn when user does not exist", async ({ page }) => {
const alertMessage = (await localize("en"))("no_account_exists");
+1 -2
View File
@@ -1,6 +1,5 @@
/* eslint-disable playwright/no-skipped-test */
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { test } from "./lib/fixtures";
@@ -9,7 +8,7 @@ test.describe.configure({ mode: "serial" });
test.describe("Onboarding", () => {
test.describe("Onboarding v2", () => {
test("Onboarding Flow", async ({ page, users }) => {
const user = await users.create({ plan: UserPlan.TRIAL, completedOnboarding: false, name: null });
const user = await users.create({ completedOnboarding: false, name: null });
await user.login();
// tests whether the user makes it to /getting-started
@@ -1 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
+18 -17
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "تم إلغاء حدثك المُجَدْوَل",
"organizer": "المنظِّم",
"need_to_reschedule_or_cancel": "هل تحتاج إلى إعادة جدولة أو إلغاء؟",
"cancellation_reason": "سبب الإلغاء",
"cancellation_reason_placeholder": "ما سبب الإلغاء؟ (اختياري)",
"cancellation_reason": "سبب الإلغاء (اختياري)",
"cancellation_reason_placeholder": "لماذا تلغي؟",
"rejection_reason": "سبب الرفض",
"rejection_reason_title": "رفض طلب الحجز؟",
"rejection_reason_description": "هل أنت متأكد من رغبتك في رفض الحجز؟ سنقوم بإبلاغ الشخص الذي حاول الحجز. يمكنك تقديم سبب أدناه.",
@@ -109,7 +109,7 @@
"upgrade_to_per_seat": "الترقية إلى \"كل مقعد بشكل منفرد\"",
"team_upgrade_seats_details": "من بين {{memberCount}} من الأعضاء في فريقك، لم يتم الدفع مقابل {{unpaidCount}} من المقاعد. بما أن سعر المقعد ${{seatPrice}} شهريًا، تبلغ التكلفة الإجمالية المقدرة لعضويتك ${{totalCost}} شهريًا.",
"team_upgrade_banner_description": "شكراً لك على تجربة خطة الفريق الجديدة التي نقدمها. لقد لاحظنا أن فريقك \"{{teamName}}\" بحاجة إلى الترقية.",
"team_upgrade_banner_action": "الترقية هنا",
"team_upgrade_banner_action": "قم بالترقية هنا",
"team_upgraded_successfully": "تمت ترقية فريقك بنجاح!",
"use_link_to_reset_password": "استخدم الرابط أدناه لإعادة تعيين كلمة المرور",
"hey_there": "مرحبًا،",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "إذا فقدت هذا السر أو نسيته، يمكنك تغييره، ولكن ضع في اعتبارك أن جميع التكاملات التي تستخدم هذا السر ستحتاج إلى تحديث",
"current_incorrect_password": "كلمة المرور الحالية غير صحيحة",
"password_hint_caplow": "مزيج من الحروف الكبيرة والحروف الصغيرة",
"password_hint_min": "7 أحرف كحد أدنى",
"password_hint_min": "8 أحرف كحد أدنى",
"password_hint_num": "يحتوي على رقم واحد على الأقل",
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من 7 أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
"incorrect_password": "كلمة المرور غير صحيحة.",
@@ -653,7 +653,7 @@
"show_advanced_settings": "عرض الإعدادات المتقدمة",
"event_name": "اسم الحدث",
"event_name_tooltip": "الاسم الذي سيظهر في التقاويم",
"meeting_with_user": "الاجتماع مع {ATTENDEE}",
"meeting_with_user": "الاجتماع مع {{attendeeName}}",
"additional_inputs": "مدخلات إضافية",
"additional_input_description": "يتطلب من المجدول إدخال مدخلات إضافية قبل تأكيد الحجز",
"label": "العلامة",
@@ -784,7 +784,7 @@
"analytics": "التحليلات",
"empty_installed_apps_headline": "لم يتم تثبيت أي تطبيق",
"empty_installed_apps_description": "تعطيك التطبيقات القدرة على تعزيز سير عملك وتحسين الجدولة في حياتك بشدة.",
"empty_installed_apps_button": "استكشف App Store أو قم بالتثبيت من التطبيقات أدناه",
"empty_installed_apps_button": "تصفح متجر التطبيقات",
"manage_your_connected_apps": "إدارة تطبيقاتك المثبتة أو تغيير الإعدادات",
"browse_apps": "استعراض التطبيقات",
"features": "الميزات",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "رمز المصادقة الثنائية غير صحيح",
"which_event_type_apply": "ما نوع الحدث الذي سينطبق عليه هذا؟",
"no_workflows_description": "تعمل سير العمل على تمكين أتمتة إرسال الإشعارات والتذكيرات التي تمكنك من بناء عمليات حول الأحداث الخاصة بك.",
"timeformat_profile_hint": "هذا إعداد داخلي، ولن يؤثر على كيفية عرض الأوقات على صفحات الحجز العامة لديك أو لدى أي شخص يقوم بحجزك.",
"create_workflow": "إنشاء سير عمل",
"do_this": "افعل هذا",
"turn_off": "إيقاف التشغيل",
@@ -1129,7 +1130,7 @@
"removes_cal_branding": "إزالة أي علامات تجارية ذات صلة بـ{{appName}} ، أي 'تم تشغيلها من قبل {{appName}}.'",
"profile_picture": "صورة الملف الشخصي",
"upload": "تحميل",
"add_profile_photo": "إضافة صورة ملف تعريفي",
"add_profile_photo": "إضافة صورة الملف الشخصي",
"web3": "Web3",
"rainbow_token_gated": "يتم فتح هذا النوع من الأحداث برمز مميز.",
"rainbow_connect_wallet_gate": "قم بربط محفظتك إذا كنت تملك <1>{{name}}</1> (<3>{{symbol}}</3>).",
@@ -1228,7 +1229,7 @@
"exchange_compression": "ضغط Gzip",
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
"routing_forms_send_email_owner": "إرسال رسالة إلكترونية إلى المالك",
"routing_forms_send_email_owner_description": "يرسل رسالة إلكترونية إلى المالك عند يتم تقديم الاستمارة",
"routing_forms_send_email_owner_description": "يرسل رسالة بريد إلكتروني إلى المالك عند إرسال النموذج",
"add_new_form": "إضافة استمارة جديدة",
"form_description": "قم بإنشاء النموذج الخاص بك لتوجيه صاحب حجز",
"copy_link_to_form": "نسخ الرابط إلى النموذج",
@@ -1341,15 +1342,15 @@
"limit_future_bookings_description": "الحد من عدد مرات حجز هذا الحدث في المستقبل",
"no_event_types": "لا يوجد إعداد لأنواع الأحداث",
"no_event_types_description": "لم يتم إعداد أي نوع من الأحداث لك من قِبل {{name}}.",
"billing_frequency": "تواتر الفواتير",
"monthly": "شهرياً",
"yearly": "سنوياً",
"billing_frequency": "تواتر إصدار الفواتير",
"monthly": "شهريًا",
"yearly": "سنويًا",
"checkout": "الدفع",
"your_team_disbanded_successfully": "تم حل فريقك بنجاح",
"error_creating_team": "خطأ في إنشاء فريق",
"you": "أنت",
"send_email": "إرسال رسالة إلكترونية",
"member_already_invited": "تم بالفعل دعوة العضو",
"send_email": "إرسال بريد إلكتروني",
"member_already_invited": "تمت دعوة العضو بالفعل",
"enter_email_or_username": "أدخل بريد إلكتروني أو اسم مستخدم",
"team_name_taken": "هذا الاسم مأخوذ بالفعل",
"must_enter_team_name": "يجب إدخال اسم فريق",
@@ -1359,15 +1360,15 @@
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
"attendee_email_workflow": "اسم الحاضر",
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
"invalid_credential": "أوه لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. الرجاء إعادة التثبيت مرة أخرى.",
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
"choose_common_schedule_team_event": "اختر جدولاً زمنياً مشتركاً",
"choose_common_schedule_team_event_description": "قم بتمكين هذا إذا كنت ترغب في استخدام جدول زمني مشترك بين المضيفين. عند تعطيل هذا الخيار، سيتم حجز كل مضيف على أساس جدوله الافتراضي.",
"choose_common_schedule_team_event_description": "قم بتمكين هذا الخيار إذا كنت ترغب في استخدام جدول زمني مشترك بين المضيفين. عند تعطيل هذا الخيار، سيتم حجز كل مضيف على أساس جدوله الافتراضي.",
"reason": "السبب",
"sender_id": "معرّف المرسل",
"sender_id_error_message": "فقط الحروف والأرقام والمسافات مسموح بها (بحد أقصى 11 مِحرَفاً)",
"sender_id_error_message": "يسمح بالحروف والأرقام والمسافات فقط (بحد أقصى 11 حرفًا)",
"test_routing_form": "اختبار استمارة إعادة التوجيه",
"test_preview": "اختبار المعاينة",
"route_to": "التوجيه إلى",
"test_preview_description": "اختبار نموذج التوجيه الخاص بك من دون تقديم أي بيانات",
"test_preview_description": "اختبار نموذج التوجيه الخاص بك دون تقديم أي بيانات",
"test_routing": "اختبار التوجيه"
}
@@ -17,7 +17,7 @@
"organizer": "Organizátor",
"need_to_reschedule_or_cancel": "Přeplánovat nebo zrušit?",
"cancellation_reason": "Důvod zrušení",
"cancellation_reason_placeholder": "Proč rušíte? (nepovinné)",
"cancellation_reason_placeholder": "Proč rušíte?\n",
"rejection_reason": "Důvod odmítnutí",
"rejection_reason_title": "Odmítnout žádost o rezervaci?",
"rejection_reason_description": "Opravdu chcete rezervaci odmítnout? Osobě, která se váš čas pokusila rezervovat, dáme vědět. Níž můžete uvést důvod.",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Zobrazit pokročilá nastavení",
"event_name": "Název události",
"event_name_tooltip": "Název, který se zobrazí v kalendářích",
"meeting_with_user": "Schůzka s {ATTENDEE}",
"meeting_with_user": "Schůzka s {{attendeeName}}",
"additional_inputs": "Další pole",
"additional_input_description": "Před potvrzením rezervace vyžadovat od plánující osoby zadání dalších vstupů",
"label": "Popisek",
@@ -736,6 +736,7 @@
"toggle_calendars_conflict": "Zapněte kalendáře, u kterých chcete mít přehled o konfliktech a zabránit dvojí rezervaci.",
"select_destination_calendar": "Vytvořit události v",
"connect_additional_calendar": "Připojit další kalendář",
"calendar_updated_successfully": "Kalendář byl úspěšně aktualizován",
"conferencing": "Konference",
"calendar": "Kalendář",
"payments": "Platby",
@@ -1108,6 +1109,7 @@
"incorrect_2fa": "Nesprávný kód dvoufázového ověření",
"which_event_type_apply": "Na jaký typ události se to bude vztahovat?",
"no_workflows_description": "Pracovní postupy umožňují jednoduchou automatizaci zasílání oznámení a připomínek, což vám umožní vytvářet procesy v souvislosti s vašimi událostmi.",
"timeformat_profile_hint": "Jedná se o interní nastavení, které nemá vliv na to, jak se vám nebo komukoli, kdo vás rezervuje, na veřejných rezervačních stránkách zobrazují časy.",
"create_workflow": "Vytvoření pracovního postupu",
"do_this": "Provést",
"turn_off": "Vypnout",
@@ -1349,17 +1351,17 @@
"error_creating_team": "Chyba při vytváření týmu",
"you": "Vy",
"send_email": "Odeslat e-mail",
"member_already_invited": "Člen byl již pozván",
"member_already_invited": "Člen byl už pozván",
"enter_email_or_username": "Zadejte e-mail nebo uživatelské jméno",
"team_name_taken": "Toto jméno je již obsazeno",
"team_name_taken": "Toto jméno je už obsazeno",
"must_enter_team_name": "Musíte zadat název týmu",
"team_url_required": "Musíte zadat adresu URL týmu",
"team_url_taken": "Tato adresa URL je již obsazená",
"team_url_taken": "Tato adresa URL je už obsazená",
"team_publish": "Zveřejnit tým",
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
"attendee_email_workflow": "E-mail účastníka",
"attendee_email_info": "E-mail osoby provádějící rezervaci",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte prosím novou instalaci.",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.",
"choose_common_schedule_team_event": "Vyberte společný plán",
"choose_common_schedule_team_event_description": "Tuto možnost povolte, pokud chcete používat společný plán mezi hostiteli. Při zakázání této možnosti bude každý hostitel rezervován na základě svého výchozího plánu.",
"reason": "Důvod",
@@ -1367,7 +1369,7 @@
"sender_id_error_message": "Povolena jsou pouze písmena, číslice a mezery (max. 11 znaků)",
"test_routing_form": "Test směrovacího formuláře",
"test_preview": "Testovací náhled",
"route_to": "Směrovat kam:",
"route_to": "Směrovat na",
"test_preview_description": "Otestujte svůj směrovací formulář bez odeslání jakýchkoli dat",
"test_routing": "Test směrování"
}
+80 -67
View File
@@ -14,7 +14,7 @@
"event_request_cancelled": "Ihr geplanter Termin wurde abgesagt",
"organizer": "Organisator",
"need_to_reschedule_or_cancel": "Möchten Sie einen neuen Termin finden oder absagen?",
"cancellation_reason": "Grund für Ihre Absage",
"cancellation_reason": "Grund für Ihre Absage (optional)",
"cancellation_reason_placeholder": "Warum sagen Sie ab? (optional)",
"rejection_reason": "Grund für die Ablehnung",
"rejection_reason_title": "Die Buchungsanfrage ablehnen?",
@@ -53,20 +53,20 @@
"load_more_results": "Weitere Ergebnisse laden",
"integration_meeting_id": "{{integrationName}} Termin-ID: {{meetingId}}",
"confirmed_event_type_subject": "Bestätigt: {{eventType}} mit {{name}} am {{date}}",
"new_event_request": "Neue Event-Anfrage: {{attendeeName}} - {{date}} - {{eventType}}",
"new_event_request": "Neue Termin-Anfrage: {{attendeeName}} - {{date}} - {{eventType}}",
"confirm_or_reject_request": "Anfrage bestätigen oder ablehnen",
"check_bookings_page_to_confirm_or_reject": "Überprüfen Sie Ihre Buchungsseite, um die Buchung zu bestätigen oder abzulehnen.",
"event_awaiting_approval": "Ein neues Event wartet auf Ihre Bestätigung",
"event_awaiting_approval_recurring": "Ein wiederkehrender Termin wartet auf Ihre Genehmigung",
"event_awaiting_approval": "Ein neuer Termin wartet auf Ihre Bestätigung",
"event_awaiting_approval_recurring": "Ein wiederkehrender Termin wartet auf Ihre Bestätigung",
"someone_requested_an_event": "Jemand hat eine Buchungsanfrage gestellt.",
"someone_requested_password_reset": "Jemand hat einen Link angefordert, um Ihr Passwort zu ändern.",
"password_reset_instructions": "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail sicher ignorieren und Ihr Passwort wird nicht geändert.",
"event_awaiting_approval_subject": "Warte auf Bestätigung: {{eventType}} mit {{name}} um {{date}}",
"event_still_awaiting_approval": "Ein Event wartet noch auf Ihre Bestätigung",
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde verschoben auf {{date}}.",
"event_has_been_rescheduled": "Ihr Event wurde verschoben.",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.",
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
"request_reschedule_title_organizer": "Sie haben {{attendee}} zur Neuplanung aufgefordert",
@@ -74,7 +74,7 @@
"rescheduled_event_type_subject": "Anfrage für Neuplanung gesendet: {{eventType}} mit {{name}} am {{date}}",
"requested_to_reschedule_subject_attendee": "Aktion erforderlich - Neuplanung: Bitte buchen Sie eine neue Zeit für „{{eventType}}“ mit {{name}}",
"reschedule_reason": "Grund für die Neuplanung",
"hi_user_name": "Hi {{userName}}",
"hi_user_name": "Hallo {{userName}}",
"ics_event_title": "{{eventType}} mit {{name}}",
"new_event_subject": "Neuer Termin: {{attendeeName}} - {{date}} - {{eventType}}",
"join_by_entrypoint": "{{entryPoint}} beitreten",
@@ -118,7 +118,7 @@
"webhook_response": "Webhook-Antwort",
"webhook_test": "Webhook-Test",
"manage_your_webhook": "Webhook verwalten",
"webhook_created_successfully": "Webhook erfolgreich aktualisiert!",
"webhook_created_successfully": "Webhook erfolgreich erstellt!",
"webhook_updated_successfully": "Webhook erfolgreich aktualisiert!",
"webhook_removed_successfully": "Webhook erfolgreich entfernt!",
"payload_template": "Payload Vorlage",
@@ -142,7 +142,7 @@
"install_another": "Weitere installieren",
"until": "bis",
"powered_by": "betrieben von",
"unavailable": "Unverfügbar",
"unavailable": "Nicht verfügbar",
"set_work_schedule": "Arbeitszeiten festlegen",
"change_bookings_availability": "Verfügbarkeit für Buchungen ändern",
"select": "Auswählen...",
@@ -178,14 +178,14 @@
"register": "Registrieren",
"page_doesnt_exist": "Diese Seite existiert nicht.",
"check_spelling_mistakes_or_go_back": "Prüfen Sie auf Rechtschreibfehler oder gehen Sie zur vorherigen Seite zurück.",
"404_page_not_found": "404: Diese Seite konnte nicht gefunden werden.",
"404_page_not_found": "Fehler 404: Diese Seite konnte nicht gefunden werden.",
"getting_started": "Los geht's",
"15min_meeting": "15 Minuten Termin",
"30min_meeting": "30 Minuten Termin",
"secret": "Passwort",
"leave_blank_to_remove_secret": "Leer lassen, um Passwort zu entfernen",
"secret_meeting": "Geheimer Termin",
"login_instead": "Stattdessen anmelden",
"login_instead": "Stattdessen einloggen",
"already_have_an_account": "Haben Sie bereits ein Konto?",
"create_account": "Konto erstellen",
"confirm_password": "Passwort bestätigen",
@@ -240,7 +240,7 @@
"add_another_calendar": "Einen weiteren Kalender hinzufügen",
"other": "Sonstige",
"emailed_you_and_attendees": "Wir haben Ihnen und den anderen Teilnehmern eine Einladung zum Kalender mit allen Details zugeschickt.",
"emailed_you_and_attendees_recurring": "Wir haben Ihnn und den anderen Teilnemern eine Kalendereinladung für den ersten Termin dieses wiederkehrendem Ereignisses gesendet.",
"emailed_you_and_attendees_recurring": "Wir haben Ihnn und den anderen Teilnemern eine Kalendereinladung für den ersten Termin dieser wiederkehrenden Termins gesendet.",
"emailed_you_and_any_other_attendees": "Wir haben Ihnen und allen anderen Teilnehmer diesen Informationen per E-Mail geschickt.",
"needs_to_be_confirmed_or_rejected": "Ihre Buchung muss noch bestätigt oder abgelehnt werden.",
"needs_to_be_confirmed_or_rejected_recurring": "Ihr wiederkehrender Termin muss noch bestätigt oder abgelehnt werden.",
@@ -318,8 +318,8 @@
"start_time": "Beginn",
"end_time": "Ende",
"buffer_time": "Pufferzeit",
"before_event": "Vor dem Ereignis",
"after_event": "Nach dem Event",
"before_event": "Vor dem Termin",
"after_event": "Nach dem Termin",
"event_buffer_default": "Keine Pufferzeit",
"buffer": "Puffer",
"your_day_starts_at": "Ihr Tag beginnt um",
@@ -338,31 +338,31 @@
"automatically_adjust_theme": "Theme automatisch basierend auf den Einstellungen des Teilnehmers anpassen",
"user_dynamic_booking_disabled": "Einige der Benutzer in der Gruppe haben derzeit dynamische Gruppenbuchungen deaktiviert",
"allow_dynamic_booking": "Ermöglichen Sie es den Teilnehmenden, Sie über dynamische Gruppenbuchungen zu buchen",
"email": "Email",
"email_placeholder": "jdoe@example.com",
"email": "E-Mail",
"email_placeholder": "max@example.com",
"full_name": "Vollständiger Name",
"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",
"booking_cancelled": "Termin abgesagt",
"booking_rescheduled": "Termin verlegt",
"booking_created": "Buchung erstellt",
"booking_created": "Termin erstellt",
"meeting_ended": "Meeting beendet",
"form_submitted": "Formular gesendet",
"event_triggers": "Ereignis-Auslöser",
"event_triggers": "Event-Auslöser",
"subscriber_url": "Subscriber-URL",
"create_new_webhook": "Einen neuen Webhook erstellen",
"webhooks": "Webhooks",
"team_webhooks": "Team Webhooks",
"team_webhooks": "Team-Webhooks",
"create_new_webhook_to_account": "Erstellen Sie einen neuen Webhook in Ihrem Account",
"new_webhook": "Neuer Webhook",
"responsive_fullscreen_iframe": "Responsives Vollbild-iframe",
"responsive_fullscreen_iframe": "Responsives Vollbild-iFrame",
"loading": "Wird geladen...",
"deleting": "Lösche...",
"standard_iframe": "Standard iframe",
"standard_iframe": "Standard iFrame",
"developer": "Entwickler",
"manage_developer_settings": "Verwalten Sie Ihre Entwicklereinstellungen.",
"iframe_embed": "iframe Embed",
"iframe_embed": "iFrame-Einbettung",
"integrate_using_embed_or_webhooks": "Integrieren Sie Cal.com in Ihre Website mit unseren Einbettungs-Optionen oder erhalten Sie Buchungsdaten mit Webhooks.",
"schedule_a_meeting": "Termin planen",
"view_and_manage_billing_details": "Ihre Rechnungsdetails ansehen und verwalten",
@@ -406,7 +406,7 @@
"forgotten_secret_description": "Wenn Sie dieses Passwort verloren oder vergessen haben, können Sie es ändern. Bitte beachten Sie, dass alle Integrationen, die dieses Geheimnis nutzen, aktualisiert werden müssen",
"current_incorrect_password": "Aktuelles Passwort ist falsch",
"password_hint_caplow": "Mischung aus Groß- und Kleinbuchstaben",
"password_hint_min": "Mindestens 7 Zeichen lang",
"password_hint_min": "Mindestens 8 Zeichen lang",
"password_hint_num": "Enthält mindestens 1 Zahl",
"invalid_password_hint": "Das Passwort muss mindestens 7 Zeichen lang sein und mindestens eine Nummer beinhalten, sowie eine Mischung aus Groß- und Kleinbuchstaben sein",
"incorrect_password": "Passwort ist falsch",
@@ -482,7 +482,7 @@
"members": "Mitglieder",
"member": "Mitglied",
"owner": "Inhaber",
"admin": "Admin",
"admin": "Administrator",
"administrator_user": "Administrator Benutzer",
"lets_create_first_administrator_user": "Lass uns als erstes den Administrator Benutzer anlegen.",
"new_member": "Neues Mitglied",
@@ -567,7 +567,7 @@
"web3_metamask_added": "Metamask erfolgreich hinzugefügt",
"web3_metamask_disconnected": "Metamask erfolgreich getrennt",
"hours": "Stunden",
"your_email": "Ihre Emailadresse",
"your_email": "Ihre E-Mail-Adresse",
"change_avatar": "Profilbild ändern",
"language": "Sprache",
"timezone": "Zeitzone",
@@ -590,8 +590,8 @@
"max": "Maximal",
"single_theme": "Einzelnes Theme",
"brand_color": "Markenfarbe",
"light_brand_color": "Brand Farbe (Helles Theme)",
"dark_brand_color": "Brand Farbe (Dunkles Theme)",
"light_brand_color": "Markenfarbe (Helles Theme)",
"dark_brand_color": "Markenfarbe (Dunkles Theme)",
"file_not_named": "Datei heißt nicht [idOrSlug]/[user]",
"create_team": "Team anlegen",
"name": "Name",
@@ -604,6 +604,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Abrechnung der Teams",
"team_billing_description": "Verwalte die Abrechungen deines Teams",
"upgrade_to_flexible_pro_title": "Wir haben die Abrechnung für Teams geändert",
"upgrade_to_flexible_pro_message": "Es gibt Mitglieder in Ihrem Team ohne Lizenz. Upgraden Sie Ihren Pro-Plan um fehlende Lizenzen abzudecken.",
"changed_team_billing_info": "Ab Januar 2022 berechnen wir pro Sitzplatz für Teammitglieder. Mitglieder deines Teams, die Pro kostenlos hatten, sind jetzt in einer 14 Tage Testphase. Sobald die Testversion abläuft, werden diese Mitglieder vor Ihrem Team verborgen, es sei denn, Sie upgraden jetzt.",
@@ -614,7 +615,7 @@
"little_something_about": "Ein wenig über sich selbst.",
"profile_updated_successfully": "Profil erfolgreich aktualisiert",
"your_user_profile_updated_successfully": "Ihr Benutzerprofil wurde erfolgreich aktualisiert.",
"user_cannot_found_db": "Benutzer scheint eingeloggt zu sein, kann aber nicht in db gefunden werden",
"user_cannot_found_db": "Benutzer scheint eingeloggt zu sein, kann aber nicht in der Datenbank gefunden werden",
"embed_and_webhooks": "Einbetten & Webhooks",
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
@@ -630,18 +631,18 @@
"error": "Fehler",
"at_least_characters_one": "Bitte geben Sie wenigstens ein Zeichen ein",
"at_least_characters_other": "Bitte geben Sie mindestens {{count}} Zeichen ein",
"team_logo": "Team Logo",
"team_logo": "Team-Logo",
"add_location": "Standort hinzufügen",
"attendees": "TeilnehmerInnen",
"add_attendees": "TeilnehmerInnen hinzufügen",
"attendees": "Teilnehmende",
"add_attendees": "Teilnehmende hinzufügen",
"show_advanced_settings": "Erweiterte Einstellungen anzeigen",
"event_name": "Termin Name",
"event_name": "Termin-Name",
"event_name_tooltip": "Der Name, der in Kalendern angezeigt wird",
"meeting_with_user": "Meeting mit {ATTENDEE}",
"meeting_with_user": "Meeting mit {{attendeeName}}",
"additional_inputs": "Zusätzliche Notizen",
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Buchungsbestätigung",
"label": "Bezeichnung",
"placeholder": "Placeholder",
"placeholder": "Platzhalter",
"type": "Typ",
"edit": "Bearbeiten",
"add_input": "Eingabe hinzufügen",
@@ -657,7 +658,7 @@
"private_link_label": "Privater Link",
"private_link_hint": "Ihr privater Link wird nach jeder Nutzung neu generiert",
"copy_private_link": "Privaten Link kopieren",
"invitees_can_schedule": "Teilnehmer können folgendes buchen",
"invitees_can_schedule": "Teilnehmende können folgendes buchen",
"date_range": "Datumszeitraum",
"calendar_days": "Kalendertage",
"business_days": "Werktage",
@@ -673,7 +674,7 @@
"within_date_range": "Innerhalb eines Zeitraums",
"indefinitely_into_future": "Auf unbestimmte Zeit in die Zukunft",
"add_new_custom_input_field": "Neues individuelles Eingabefeld hinzufügen",
"quick_chat": "Quick Chat",
"quick_chat": "Schneller Chat",
"add_new_team_event_type": "Neuen Termintyp hinzufügen",
"add_new_event_type": "Neuen Termintyp hinzufügen",
"new_event_type_to_book_description": "Erstellen Sie einen neuen Termintyp, mit dem Personen Zeiten buchen können.",
@@ -688,6 +689,8 @@
"confirm_delete_account": "Ja, Account löschen",
"integrations": "Integrationen",
"apps": "Apps",
"apps_description": "Hier findest du eine Auflistung deiner Apps",
"apps_listing": "App-Liste",
"category_apps": "{{category}} Apps",
"app_store": "App Store",
"app_store_description": "Verbindet Menschen, Technologie und den Arbeitsplatz.",
@@ -707,7 +710,7 @@
"embed_your_calendar": "Fügen Sie Ihren Kalender in Ihre Webseite ein",
"connect_your_favourite_apps": "Verbinden Sie Ihre Lieblings-Apps.",
"automation": "Automatisierung",
"configure_how_your_event_types_interact": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen.",
"configure_how_your_event_types_interact": "Konfigurieren Sie, wie Ihre Termin-Typen mit Ihren Kalendern interagieren sollen.",
"toggle_calendars_conflict": "Wechseln Sie die Kalender in denen nach Konflikten gesucht werden sollen, um Doppelbuchungen zu vermeiden.",
"select_destination_calendar": "Erstelle Termine im",
"connect_additional_calendar": "Zusätzlichen Kalender verbinden",
@@ -723,7 +726,7 @@
"set_as_away": "Stelle deinen Status auf Abwesend",
"set_as_free": "Abwesenheitsstatus deaktivieren",
"user_away": "Dieser Benutzer ist derzeit abwesend.",
"user_away_description": "Die Person, mi der Sie einen Termin buchen wollen, ist zur Zeit abwesend und akzeptiert daher keine neuen Buchungen.",
"user_away_description": "Die Person, mit der Sie einen Termin buchen wollen, ist zur Zeit abwesend und akzeptiert daher keine neuen Buchungen.",
"meet_people_with_the_same_tokens": "Triff Leute mit den selben Token",
"only_book_people_and_allow": "Nur Buchungen von Personen erlauben, die die gleichen Token, DAOs oder NFTs teilen.",
"account_created_with_identity_provider": "Ihr Konto wurde mit einem Identitäts-Anbieter erstellt.",
@@ -743,6 +746,7 @@
"trending_apps": "Angesagte Apps",
"explore_apps": "{{category}} Apps",
"installed_apps": "Installierte Apps",
"free_to_use_apps": "Kostenlos",
"no_category_apps": "Keine {{category}} Apps",
"no_category_apps_description_calendar": "Fügen Sie eine Kalender-App hinzu, in der nach Konflikten gesucht werden soll, um Doppelbuchungen zu vermeiden",
"no_category_apps_description_conferencing": "Versuchen Sie, eine Konferenz-App hinzuzufügen, um Videoanrufe mit Ihren Kunden zu integrieren",
@@ -759,7 +763,7 @@
"analytics": "Analyse",
"empty_installed_apps_headline": "Keine Apps installiert",
"empty_installed_apps_description": "Apps erlauben es Ihnen, Ihren Arbeitsfluss zu verbessern und Ihre Planung viel besser zu gestalten.",
"empty_installed_apps_button": "Entdecken Sie den App Store",
"empty_installed_apps_button": "Durchsuchen Sie den App Store",
"manage_your_connected_apps": "Verwalten Sie Ihre installierten Apps oder ändern Sie die Einstellungen",
"browse_apps": "Apps durchsuchen",
"features": "Funktionen",
@@ -781,6 +785,8 @@
"verify_wallet": "Wallet verifizieren",
"connect_metamask": "Metamask verbinden",
"create_events_on": "Erstelle Termine in:",
"enterprise_license": "Das ist eine Enterprise-Funktion",
"enterprise_license_description": "Um diese Funktion zu aktivieren, holen Sie sich einen Deployment-Schlüssel von der {{consoleUrl}}-Konsole und fügen Sie ihn als CALCOM_LICENSE_KEY zu Ihrer .env hinzu. Wenn Ihr Team bereits eine Lizenz hat, wenden Sie sich bitte an {{supportMail}} für Hilfe.",
"missing_license": "Lizenz fehlt",
"signup_requires": "Kommerzielle Lizenz erforderlich",
"next_steps": "Nächste Schritte",
@@ -798,11 +804,11 @@
"set_calendar": "Legen Sie fest, wo neue Events hinzugefügt werden sollen, wenn Sie ausgebucht sind.",
"delete_schedule": "Verfügbarkeitsplan löschen",
"schedule_created_successfully": "{{scheduleName}} -Verfügbarkeitsplan erfolgreich erstellt",
"availability_updated_successfully": "Verfügbarkeit erfolgreich aktualisiert",
"availability_updated_successfully": "Verfügbarkeitsplan erfolgreich aktualisiert",
"schedule_deleted_successfully": "Verfügbarkeitsplan erfolgreich gelöscht",
"default_schedule_name": "Arbeitszeiten",
"new_schedule_heading": "Erstelle einen Verfügbarkeitsplan",
"new_schedule_description": "Das Erstellen von Verfügbarkeitsplan sorgt dafür, dass Ihre Verfügbarkeit bei mehreren Termintypen genutzt werden kann.",
"new_schedule_description": "Das Erstellen eines Verfügbarkeitsplan sorgt dafür, dass Ihre Verfügbarkeit bei mehreren Termintypen genutzt werden kann.",
"requires_ownership_of_a_token": "Erfordert Besitz eines Tokens, das zu der folgenden Adresse gehört:",
"example_name": "Max Mustermann",
"time_format": "Zeitformat",
@@ -872,9 +878,9 @@
"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.",
"impersonate_user_tip": "Alle Verwendungen dieser Funktion werden geprüft.",
"impersonating_user_warning": "Imitiere den User {{user}}.",
"impersonating_user_warning": "Imitiere den Account {{user}}.",
"impersonating_stop_instructions": "<0>Klicke hier um zu stoppen </0>.",
"event_location_changed": "Aktualisiert - Ihr Ereignis hat den Standort geändert",
"event_location_changed": "Aktualisiert - Ihr Termin hat den Standort geändert",
"location_changed_event_type_subject": "Ort geändert: {{eventType}} mit {{name}} am {{date}}",
"current_location": "Aktueller Ort",
"user_phone": "Ihre Telefonnummer",
@@ -890,7 +896,8 @@
"setting_up_zapier": "Einrichtung Ihrer Zapier-Integration",
"generate_api_key": "Api Key generieren",
"your_unique_api_key": "Ihr eindeutiger API-Key",
"copy_safe_api_key": "Kopieren Sie diesen API-Key und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Key verlieren, müssen Sie einen neuen generieren.",
"copy_safe_api_key": "Kopieren Sie diesen API-Schlüssel und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Schlüssel verlieren, müssen Sie einen neuen generieren.",
"zapier_setup_instructions": "<0>Gehen Sie zu: <1>Zapier-Einladungs-Link</1></0><1>Logge Sie sich in Ihr Zapier-Konto ein und erstelle einen neuen Zap.</1><2>Wählen Sie Cal.com als ihre Trigger-App aus. Wählen Sie auch ein Auslöserereignis.</2><3>Wähle Sie ihr Konto aus und geben dann ihren eindeutigen API-Key ein.</3><4>Teste Sie den Trigger.</4><5>Alles fertig!</5>",
"install_zapier_app": "Bitte installieren Sie zuerst die Zapier-App im App Store.",
"connect_apple_server": "Mit Apple-Server verbinden",
"connect_caldav_server": "Mit CalDav-Server verbinden",
@@ -911,7 +918,7 @@
"share_feedback": "Feedback teilen",
"resources": "Ressourcen",
"support_documentation": "Support Dokumentation",
"developer_documentation": "Entwickler Dokumentation",
"developer_documentation": "Entwickler-Dokumentation",
"get_in_touch": "Kontakt aufnehmen",
"contact_support": "Support kontaktieren",
"feedback": "Feedback",
@@ -923,7 +930,7 @@
"nevermind": "Egal",
"go_to": "Gehe zu: ",
"zapier_invite_link": "Zapier Einladungs-Link",
"meeting_url_provided_after_confirmed": "Eine Temrin-URL wird angelegt, sobald die Buchung bestätigt wurde.",
"meeting_url_provided_after_confirmed": "Eine Termin-URL wird angelegt, sobald der Termin bestätigt wurde.",
"attendees_name": "Teilnehmername",
"dynamically_display_attendee_or_organizer": "Zeigt dynamisch entweder Ihnen den Namen Ihres Teilnehmers bzw. Ihrer Teilnehmerin an oder zeigt Ihrem/Ihrer Teilnehmerin Ihren Namen an",
"event_location": "Ort des Ereignisses",
@@ -937,7 +944,7 @@
"number_provided": "Telefonnummer wird angegeben",
"before_event_trigger": "vor Beginn des Termins",
"event_cancelled_trigger": "wenn das Ereignis abgesagt wird",
"new_event_trigger": "wenn ein neues Ereignis gebucht wird",
"new_event_trigger": "wenn ein neer Termin gebucht wird",
"email_host_action": "E-Mail an den Veranstalter senden",
"email_attendee_action": "E-Mail an Teilnehmer senden",
"sms_attendee_action": "SMS an Teilnehmer senden",
@@ -1012,7 +1019,7 @@
"invalid_input": "Ungültige Eingabe",
"broken_video_action": "Wir konnten den <1>{{location}}</1> Meeting-Link nicht zu Ihrem geplanten Ereignis hinzufügen. Kontaktieren Sie Ihre Eingeladenen oder aktualisieren Sie Ihr Kalenderereignis, um die Einzelheiten hinzuzufügen. Sie können entweder <3> Ihren Standort im Ereignistyp ändern</3> oder <5>die App entfernen und erneut hinzufügen.</5>",
"broken_calendar_action": "Wir konnten Ihren <1>{{calendar}}</1> nicht aktualisieren. <2> Bitte überprüfen Sie die Kalendereinstellungen oder entfernen Sie Ihren Kalender und fügen Sie Ihren Kalender erneut hinzu </2>",
"attendee_name": "Teilnehmername",
"attendee_name": "Name des Teilnehmenden",
"broken_integration": "Defekte Integration",
"problem_adding_video_link": "Beim Hinzufügen eines Video-Links ist ein Problem aufgetreten",
"problem_updating_calendar": "Es gab ein Problem beim Aktualisieren Ihres Kalenders",
@@ -1064,30 +1071,31 @@
"connect_your_calendar_and_link": "Sie können Ihren Kalender von <1>hier</1> verbinden.",
"default_calendar_selected": "Standardkalender",
"hide_from_profile": "Im Profil ausblenden",
"event_setup_tab_title": "Event-Einrichtung",
"event_setup_tab_title": "Termin-Einrichtung",
"event_limit_tab_title": "Limits",
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
"event_advanced_tab_description": "Kalendereinstellungen & mehr...",
"event_advanced_tab_title": "Erweitert",
"select_which_cal": "Wählen Sie den Kalender aus, zu dem Buchungen hinzugefügt werden sollen",
"custom_event_name": "Eigener Ereignisname",
"custom_event_name_description": "Erstellen Sie benutzerdefinierte Ereignisnamen, die im Kalenderereignis angezeigt werden sollen",
"select_which_cal": "Wählen Sie den Kalender aus, zu dem Termine hinzugefügt werden sollen",
"custom_event_name": "Eigener Terminname",
"custom_event_name_description": "Erstellen Sie benutzerdefinierte Terminnamen, die im Kalendereintrag angezeigt werden sollen",
"2fa_required": "Zwei-Faktor-Authentifizierung erforderlich",
"incorrect_2fa": "Falscher Zwei-Faktor-Authentifizierungscode",
"which_event_type_apply": "Auf welchen Ereignistyp wird dies angewandt?",
"which_event_type_apply": "Auf welchen Termintyp wird dies angewandt?",
"no_workflows_description": "Workflows ermöglichen die einfache Automatisierung des Versands von Benachrichtigungen und Erinnerungen, so dass Sie Prozesse rund um Ihre Ereignisse erstellen können.",
"timeformat_profile_hint": "Dies ist eine interne Einstellung und hat keinen Einfluss darauf, wie die Zeiten auf den öffentlichen Buchungsseiten für Sie oder jemanden, der Sie buchen möchte, angezeigt werden.",
"create_workflow": "Einen Workflow erstellen",
"do_this": "Mache dies",
"turn_off": "Abschalten",
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Url-Seite erscheinen wird.",
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Profil-Seite erscheinen wird.",
"delete_account_modal_title": "Account löschen",
"delete_my_account": "Meinen Account löschen",
"start_of_week": "Wochenstart",
"select_calendars": "Wählen Sie aus, in welchen Kalendern Sie nach Konflikten suchen wollen, um Doppelbuchungen zu vermeiden.",
"check_for_conflicts": "Auf Konflikte prüfen",
"adding_events_to": "Ereignisse hinzufügen zu",
"adding_events_to": "Termine hinzufügen zu",
"follow_system_preferences": "Systemeinstellungen folgen",
"custom_brand_colors": "Eigene Marken-Farben",
"customize_your_brand_colors": "Passen Sie Ihre eigene Markenfarbe auf Ihrer Buchungsseite an.",
@@ -1118,7 +1126,11 @@
"invoices": "Rechnungen",
"embeds": "Embeds",
"impersonation": "Benutzeridentitätswechsel",
"impersonation_description": "Einstellungen um Benutzeridentitätswechsel zu verwalten",
"users": "Benutzer",
"profile_description": "Einstellungen für Ihr {{appName}} Profil verwalten",
"users_description": "Hier findest du eine Auflistung aller Benutzer",
"users_listing": "Benutzerliste",
"general_description": "Einstellungen für Ihre Sprache und Zeitzone verwalten",
"calendars_description": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen",
"appearance_description": "Einstellungen für Ihre Buchungsdarstellung verwalten",
@@ -1188,7 +1200,7 @@
"exchange_authentication_ntlm": "NTLM-Authentifizierung",
"exchange_compression": "GZip-Kompression",
"routing_forms_description": "Hier sehen Sie alle Formulare und Weiterleitungen, die Sie erstellt haben.",
"routing_forms_send_email_owner": "E-Mail an Besitzer senden",
"routing_forms_send_email_owner": "E-Mail an Eigentümer senden",
"routing_forms_send_email_owner_description": "Sendet eine E-Mail an den Eigentümer, wenn das Formular abgeschickt wird",
"add_new_form": "Neues Formular hinzufügen",
"form_description": "Erstellen Sie Ihr Formular, um einen Bucher zu anzuleiten",
@@ -1293,10 +1305,10 @@
"saml_sp_entity_id_copied": "SP-Entitäts-ID kopiert!",
"saml_btn_configure": "Konfigurieren",
"add_calendar": "Kalender hinzufügen",
"limit_future_bookings": "Zukünftige Buchungen begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieses Ereignis gebucht werden kann",
"no_event_types": "Keine Ereignistypen eingerichtet",
"no_event_types_description": "{{name}} hat keine für Sie buchbaren Ereignistypen eingerichtet.",
"limit_future_bookings": "Zukünftige Termine begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieser Termin gebucht werden kann",
"no_event_types": "Keine Termintypen eingerichtet",
"no_event_types_description": "{{name}} hat keine für Sie buchbaren Termintypen eingerichtet.",
"billing_frequency": "Rechnungshäufigkeit",
"monthly": "Monatlich",
"yearly": "Jährlich",
@@ -1308,22 +1320,23 @@
"member_already_invited": "Mitglied wurde bereits eingeladen",
"enter_email_or_username": "E-Mail oder Benutzername eingeben",
"team_name_taken": "Dieser Name ist bereits vergeben",
"must_enter_team_name": "Teamname muss eingegeben werden",
"must_enter_team_name": "Team-Name muss eingegeben werden",
"team_url_required": "Team-URL muss eingegeben werden",
"team_url_taken": "Diese URL ist bereits vergeben",
"team_publish": "Team veröffentlichen",
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
"attendee_email_workflow": "Teilnehmer E-Mail",
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen sind oder entzogen wurden. Bitte erneut installieren.",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.",
"choose_common_schedule_team_event": "Gemeinsamen Zeitplan auswählen",
"choose_common_schedule_team_event_description": "Aktivieren Sie dies, wenn Sie einen gemeinsamen Zeitplan zwischen Hosts verwenden möchten. Wenn deaktiviert, ist jeder Host basierend auf seinen Standardplan buchbar.",
"reason": "Grund",
"sender_id": "Absender-ID",
"sender_id_error_message": "Nur Buchstaben, Zahlen und Leerzeichen erlaubt (max. 11 Zeichen)",
"test_routing_form": "Testleitungsformular",
"test_preview": "Testvorschau",
"route_to": "Route zu",
"test_preview_description": "Testen Sie Ihr Leitungsformular, ohne Daten zu senden",
"test_routing_form": "Testweiterleitungsformular",
"test_preview": "Vorschau testen",
"route_to": "Weiterleiten zu",
"test_preview_description": "Testen Sie Ihr Weiterleitungsformular, ohne Daten zu senden",
"test_routing": "Testweiterleitung"
}
+58 -9
View File
@@ -466,14 +466,14 @@
"booking_confirmation": "Confirm your {{eventTypeTitle}} with {{profileName}}",
"booking_reschedule_confirmation": "Reschedule your {{eventTypeTitle}} with {{profileName}}",
"in_person_meeting": "In-person meeting",
"attendeeInPerson":"In Person (Attendee Address)",
"inPerson":"In Person (Organizer Address)",
"attendeeInPerson": "In Person (Attendee Address)",
"inPerson": "In Person (Organizer Address)",
"link_meeting": "Link meeting",
"phone_call": "Attendee Phone Number",
"your_number": "Your phone number",
"phone_number": "Phone Number",
"attendee_phone_number": "Attendee Phone Number",
"organizer_phone_number" :"Organizer Phone Number",
"organizer_phone_number": "Organizer Phone Number",
"host_phone_number": "Your Phone Number",
"enter_phone_number": "Enter phone number",
"reschedule": "Reschedule",
@@ -629,6 +629,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Team Billing",
"team_billing_description": "Manage billing for your team",
"upgrade_to_flexible_pro_title": "We've changed billing for teams",
"upgrade_to_flexible_pro_message": "There are members in your team without a seat. Upgrade your pro plan to cover missing seats.",
"changed_team_billing_info": "As of January 2022 we charge on a per-seat basis for team members. Members of your team who had PRO for free are now on a 14 day trial. Once their trial expires these members will be hidden from your team unless you upgrade now.",
@@ -663,7 +664,7 @@
"show_advanced_settings": "Show advanced settings",
"event_name": "Event Name",
"event_name_tooltip": "The name that will appear in calendars",
"meeting_with_user": "Meeting with {ATTENDEE}",
"meeting_with_user": "Meeting with {{attendeeName}}",
"additional_inputs": "Additional Inputs",
"additional_input_description": "Require scheduler to input additional inputs prior the booking is confirmed",
"label": "Label",
@@ -704,6 +705,7 @@
"hide_event_type": "Hide event type",
"edit_location": "Edit location",
"into_the_future": "into the future",
"when_booked_with_less_than_notice": "When booked with less than <time></time> notice",
"within_date_range": "Within a date range",
"indefinitely_into_future": "Indefinitely into the future",
"add_new_custom_input_field": "Add new custom input field",
@@ -723,6 +725,8 @@
"delete_account_confirmation_message": "Are you sure you want to delete your {{appName}} account? Anyone who you've shared your account link with will no longer be able to book using it and any preferences you have saved will be lost.",
"integrations": "Integrations",
"apps": "Apps",
"apps_description": "Here you can find a list of your apps",
"apps_listing": "App listing",
"category_apps": "{{category}} apps",
"app_store": "App Store",
"app_store_description": "Connecting people, technology and the workplace.",
@@ -746,6 +750,7 @@
"toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.",
"select_destination_calendar": "Create events on",
"connect_additional_calendar": "Connect additional calendar",
"calendar_updated_successfully":"Calendar updated successfully",
"conferencing": "Conferencing",
"calendar": "Calendar",
"payments": "Payments",
@@ -909,7 +914,7 @@
"impersonate": "Impersonate",
"user_impersonation_heading": "User Impersonation",
"user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.",
"team_impersonation_description": "Allows your team members to temporarily sign in as you.",
"team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.",
"allow_booker_to_select_duration": "Allow booker to select duration",
"impersonate_user_tip": "All uses of this feature is audited.",
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
@@ -935,7 +940,7 @@
"generate_api_key_description": "Generate an API key to use with {{appName}} at",
"your_unique_api_key": "Your unique API key",
"copy_safe_api_key": "Copy this API key and save it somewhere safe. If you lose this key you have to generate a new one.",
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select {{appName}} as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select Cal.com as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
"install_zapier_app": "Please first install the Zapier App in the app store.",
"connect_apple_server": "Connect to Apple Server",
"connect_caldav_server": "Connect to CalDav (Beta)",
@@ -1028,6 +1033,9 @@
"error_removing_app": "Error removing app",
"web_conference": "Web conference",
"requires_confirmation": "Requires confirmation",
"always_requires_confirmation": "Always",
"requires_confirmation_threshold": "Requires confirmation if booked with < {{time}} $t({{unit}}_timeUnit) notice",
"may_require_confirmation": "May require confirmation",
"nr_event_type_one": "{{count}} event type",
"nr_event_type_other": "{{count}} event types",
"add_action": "Add action",
@@ -1125,6 +1133,7 @@
"incorrect_2fa": "Incorrect two factor authentication code",
"which_event_type_apply": "Which event type will this apply to?",
"no_workflows_description": "Workflows enable simple automation to send notifications & reminders enabling you to build processes around your events.",
"timeformat_profile_hint": "This is an internal setting and will not affect how times are displayed on public booking pages for you or anyone booking you.",
"create_workflow": "Create a workflow",
"do_this": "Do this",
"turn_off": "Turn off",
@@ -1170,12 +1179,15 @@
"invoices": "Invoices",
"embeds": "Embeds",
"impersonation": "Impersonation",
"impersonation_description": "Settings to manage user impersonation",
"users": "Users",
"profile_description": "Manage settings for your {{appName}} profile",
"users_description": "Here you can find a list of all users",
"users_listing": "User listing",
"general_description": "Manage settings for your language and timezone",
"calendars_description": "Configure how your event types interact with your calendars",
"appearance_description": "Manage settings for your booking appearance",
"conferencing_description": "Manage your video conferencing apps for your meetings",
"conferencing_description": "Add your favourite video conferencing apps for your meetings",
"password_description": "Manage settings for your account passwords",
"2fa_description": "Manage settings for your account passwords",
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
@@ -1376,21 +1388,58 @@
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_info": "The person booking's email",
"kbar_search_placeholder": "Type a command or search...",
"free_to_use_apps": "Free",
"enterprise_license": "This is an enterprise feature",
"enterprise_license_description": "To enable this feature, get a deployment key at {{consoleUrl}} console and add it to your .env as CALCOM_LICENSE_KEY. If your team already has a license, please contact {{supportMail}} for help.",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"choose_common_schedule_team_event": "Choose a common schedule",
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule.",
"reason": "Reason",
"sender_id": "Sender ID",
"sender_id_error_message":"Only letters, numbers and spaces allowed (max. 11 characters)",
"sender_id_error_message": "Only letters, numbers and spaces allowed (max. 11 characters)",
"test_routing_form": "Test Routing Form",
"test_preview": "Test Preview",
"route_to": "Route to",
"test_preview_description": "Test your routing form without submitting any data",
"test_routing": "Test Routing",
"payment_app_disabled": "An admin has disabled a payment app",
"edit_event_type": "Edit event type",
"collective_scheduling": "Collective Scheduling",
"make_it_easy_to_book": "Make it easy to book your team when everyone is available.",
"find_the_best_person": "Find the best person available and cycle through your team.",
"fixed_round_robin": "Fixed round robin",
"add_one_fixed_attendee": "Add one fixed attendee and round robin through a number of attendees.",
"calcom_is_better_with_team": "Cal.com is better with teams",
"add_your_team_members": "Add your team members to your event types. Use collective scheduling to include everyone or find the most suitable person with round robin scheduling.",
"booking_limit_reached":"Booking Limit for this event type has been reached",
"admin_has_disabled": "An admin has disabled {{appName}}",
"disabled_app_affects_event_type": "An admin has disabled {{appName}} which affects your event type {{eventType}}",
"disable_payment_app": "The admin has disabled {{appName}} which affects your event type {{title}}. Attendees are still able to book this type of event but will not be prompted to pay. You may hide hide the event type to prevent this until your admin renables your payment method.",
"payment_disabled_still_able_to_book": "Attendees are still able to book this type of event but will not be prompted to pay. You may hide hide the event type to prevent this until your admin reenables your payment method.",
"app_disabled_with_event_type": "The admin has disabled {{appName}} which affects your event type {{title}}.",
"app_disabled_video": "The admin has disabled {{appName}} which may affect your event types. If you have event types with {{appName}} as the location then it will default to Cal Video.",
"app_disabled_subject": "{{appName}} has been disabled",
"navigate_installed_apps": "Go to installed apps",
"disabled_calendar": "If you have another calendar installed new bookings will be added to it. If not then connect a new calendar so you do not miss any new bookings.",
"enable_apps": "Enable Apps",
"enable_apps_description": "Enable apps that users can integrate with Cal.com",
"app_is_enabled": "{{appName}} is enabled",
"app_is_disabled": "{{appName}} is disabled",
"keys_have_been_saved": "Keys have been saved",
"disable_app": "Disable App",
"disable_app_description": "Disabling this app could cause problems with how your users interact with Cal",
"edit_keys": "Edit Keys",
"apps_description": "Enable apps for your instance of Cal",
"no_available_apps": "There are no available apps",
"no_available_apps_description": "Please ensure there are apps in your deployment under 'packages/app-store'",
"no_apps": "There are no apps enabled in this instance of Cal",
"apps_settings": "Apps settings",
"fill_this_field": "Please fill in this field",
"options": "Options",
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio"
"radio": "Radio",
"kbar_search_placeholder" : "Start typing to search",
"set_as_default": "Set as default"
}
@@ -16,8 +16,8 @@
"event_request_cancelled": "Tu evento programado fue cancelado",
"organizer": "Organizador",
"need_to_reschedule_or_cancel": "¿Necesita reprogramar o cancelar?",
"cancellation_reason": "Motivo de la cancelación",
"cancellation_reason_placeholder": "¿Por qué razón estás cancelando? (opcional)",
"cancellation_reason": "Motivo de la cancelación (opcional)",
"cancellation_reason_placeholder": "¿Por qué razón está cancelando?",
"rejection_reason": "Motivo del rechazo",
"rejection_reason_title": "¿Rechazar la solicitud de reserva?",
"rejection_reason_description": "¿Estás seguro de que deseas rechazar la reserva? Le haremos saber a la persona que ha intentado reservar. Puedes indicar una razón a continuación.",
@@ -504,7 +504,7 @@
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
"add_team_member": "Añadir miembro del equipo",
"invite_new_member": "Invitar a un nuevo miembro",
"invite_new_member_description": "Nota: Esto costará <1>un asiento adicional ($15/m)</1> en su suscripción.",
"invite_new_member_description": "Nota: Esto costará <1>un cupo adicional ($15/m)</1> en su suscripción.",
"invite_new_team_member": "Invita a alguien a tu equipo.",
"change_member_role": "Cambiar rol del miembro del equipo",
"disable_cal_branding": "Desactivar marca de {{appName}}",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Opciones Avanzadas",
"event_name": "Nombre del Evento",
"event_name_tooltip": "Nombre que aparecerá en los calendarios",
"meeting_with_user": "Reunión con {ATTENDEE}",
"meeting_with_user": "Reunión con {{attendeeName}}",
"additional_inputs": "Campos Adicionales",
"additional_input_description": "Requerir que el planificador introduzca entradas adicionales antes de confirmar la reserva",
"label": "Etiqueta",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Código de autenticación de dos factores incorrecto",
"which_event_type_apply": "¿A qué tipo de evento se aplicará esto?",
"no_workflows_description": "Los flujos de trabajo permiten una automatización simple para enviar notificaciones y recordatorios que le permiten crear procesos para sus eventos.",
"timeformat_profile_hint": "Esta es una configuración interna y no afectará la manera en que se muestran los horarios en las páginas de reservas públicas para usted o para cualquier persona que lo solicite.",
"create_workflow": "Crear un flujo de trabajo",
"do_this": "Hacer esto",
"turn_off": "Apagar",
@@ -1227,8 +1228,8 @@
"exchange_authentication_ntlm": "Autenticación NTLM",
"exchange_compression": "Compresión GZip",
"routing_forms_description": "Aquí puedes ver todos los formularios y rutas que has creado.",
"routing_forms_send_email_owner": "Enviar email al propietario",
"routing_forms_send_email_owner_description": "Envía un correo electrónico al propietario cuando se envía el formulario",
"routing_forms_send_email_owner": "Enviar correo electrónico al propietario",
"routing_forms_send_email_owner_description": "Mande un correo electrónico al propietario cuando se envía el formulario",
"add_new_form": "Añadir formulario nuevo",
"form_description": "Crea tu formulario para dirigirlo a un agente de reservas",
"copy_link_to_form": "Copiar enlace al formulario",
@@ -1360,8 +1361,8 @@
"attendee_email_workflow": "Correo electrónico del asistente",
"attendee_email_info": "El correo electrónico de la persona que reserva",
"invalid_credential": "¡Oh no! Parece que el permiso ha caducado o se ha revocado. Vuelva a instalarlo.",
"choose_common_schedule_team_event": "Elija un horario común",
"choose_common_schedule_team_event_description": "Habilite esto si desea utilizar un cronograma común entre anfitriones. Cuando esté desactivado, cada anfitrión se reservará con base en su horario predeterminado.",
"choose_common_schedule_team_event": "Elige un horario común para todos",
"choose_common_schedule_team_event_description": "Habilite esto si desea utilizar un cronograma común entre anfitriones. Cuando esté desactivado, cada anfitrión se reservará en relación a su horario predeterminado.",
"reason": "Motivo",
"sender_id": "ID del remitente",
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
@@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Un événement attend toujours votre approbation",
"booking_submitted_subject": "Réservation confirmée : {{eventType}} avec {{name}} le {{date}}",
"your_meeting_has_been_booked": "Votre réunion a été réservée",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté au {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté à {{time}} ({{timeZone}}) le {{date}}.",
"event_has_been_rescheduled": "Votre évènement a été reprogrammé.",
"request_reschedule_title_attendee": "Demande de report de votre réservation",
"request_reschedule_subtitle": "{{organizer}} a annulé la réservation et vous a demandé de choisir un autre moment.",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Afficher les paramètres avancés",
"event_name": "Nom de l'événement",
"event_name_tooltip": "Le nom qui apparaîtra dans les calendriers",
"meeting_with_user": "Rendez-vous avec {ATTENDEE}",
"meeting_with_user": "Rendez-vous avec {{attendeeName}}",
"additional_inputs": "Entrées additionnelles",
"additional_input_description": "Exiger que le planificateur saisisse des entrées supplémentaires avant que la réservation soit confirmée",
"label": "Libellé",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Code d'authentification à deux facteurs incorrect",
"which_event_type_apply": "À quel type d'événement s'appliquera-t-il ?",
"no_workflows_description": "Les workflows permettent une automatisation simple pour envoyer des notifications et des rappels vous permettant de construire des processus autour de vos événements.",
"timeformat_profile_hint": "Ceci est un paramètre interne et n'affectera pas la façon dont les heures sont affichées sur les pages publiques de réservation pour vous ou toute personne qui réserve votre temps.",
"create_workflow": "Créer un workflow",
"do_this": "Effectuer ceci",
"turn_off": "Désactiver",
+13 -12
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "האירוע המתוכנן בוטל",
"organizer": "מארגן",
"need_to_reschedule_or_cancel": "רוצה לקבוע מועד חדש או לבטל?",
"cancellation_reason": "הסיבה לביטול",
"cancellation_reason_placeholder": "מדוע בחרת לבטל? (אופציונלי)",
"cancellation_reason": "הסיבה לביטול (לא חובה)",
"cancellation_reason_placeholder": "מדוע בחרת לבטל? (לא חובה)",
"rejection_reason": "הסיבה לדחייה",
"rejection_reason_title": "לדחות את בקשת ההזמנה?",
"rejection_reason_description": "בטוח שברצונך לדחות את ההזמנה? אנחנו ניידע את האדם שניסה להזמין. יש לך אפשרות להוסיף את הסיבה לדחייה למטה.",
@@ -108,7 +108,7 @@
"link_expires": "נ.ב. תוקף הקישור יפוג תוך {{expiresIn}} שעות.",
"upgrade_to_per_seat": "שדרג למינוי לפי מקום",
"team_upgrade_seats_details": "לא התבצע תשלום עבור {{unpaidCount}} מקומות מתוך {{memberCount}} חברי הצוות שלך. תמורת ${{seatPrice}} לחודש למקום, עלות המינוי הכוללת המשוערת שלך היא ${{totalCost}} לחודש.",
"team_upgrade_banner_description": "אנחנו מודים לך על שניסית את החבילה החדשה שלנו לצוותים. שמנו לב שהצוות שלך, \"{{teamName}}\", זקוק לשדרוג.",
"team_upgrade_banner_description": "אנחנו מודים לך על כך שניסית את החבילה החדשה שלנו לצוותים. שמנו לב שהצוות שלך, \"{{teamName}}\", זקוק לשדרוג.",
"team_upgrade_banner_action": "כאן משדרגים",
"team_upgraded_successfully": "הצוות שלך שודרג בהצלחה!",
"use_link_to_reset_password": "נא להשתמש בקישור הבא כדי לאפס את הסיסמה",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "אם שכחת או איבדת את הסוד, יש לך אפשרות לשנות אותו, אבל חשוב לזכור שיהיה צורך לעדכן את כל השילובים שבהם נעשה שימוש בסוד זה",
"current_incorrect_password": "הסיסמה הנוכחית שגויה",
"password_hint_caplow": "שילוב של אותיות רישיות וקטנות",
"password_hint_min": "באורך של 7 תווים לפחות",
"password_hint_min": "באורך 8 תווים לפחות",
"password_hint_num": "לכלול לפחות ספרה אחת",
"invalid_password_hint": "הסיסמה חייבת להיות באורך של 7 תווים לפחות ולכלול לפחות ספרה אחת ושילוב של אותיות רישיות וקטנות",
"incorrect_password": "הסיסמה שגויה.",
@@ -653,7 +653,7 @@
"show_advanced_settings": "הצגת הגדרות מתקדמות",
"event_name": "שם האירוע",
"event_name_tooltip": "השם שיופיע בלוחות השנה",
"meeting_with_user": "פגישה עם {ATTENDEE}",
"meeting_with_user": "פגישה עם {{attendeeName}}",
"additional_inputs": "אפשרויות קלט נוספות",
"additional_input_description": "לדרוש מכלי התזמון להוסיף פרטים לפני אישור ההזמנה",
"label": "תווית",
@@ -784,7 +784,7 @@
"analytics": "ניתוח נתונים",
"empty_installed_apps_headline": "אין אפליקציות מותקנות",
"empty_installed_apps_description": "אפליקציות מאפשרות לך לשפר משמעותית את תהליכי העבודה שלך ואת תהליך קביעת המועדים.",
"empty_installed_apps_button": "נא לעיין ב-App Store או להתקין מהאפליקציות שלהלן",
"empty_installed_apps_button": "נא לעיין ב-App Store",
"manage_your_connected_apps": "ניהול האפליקציות המותקנות שלך או שינוי הגדרות",
"browse_apps": "עיון באפליקציות",
"features": "תכונות",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "קוד שגוי של אימות בשני גורמים",
"which_event_type_apply": "על איזה סוג אירוע זה יחול?",
"no_workflows_description": "תהליכי עבודה מאפשרים שימוש באוטומציה פשוטה לשליחת עדכונים ותזכורות, כך שאת/ה יכול/ה לתכנן תהליכים שקשורים לאירועים שלך.",
"timeformat_profile_hint": "זוהי הגדרה פנימית שלא תשפיע על אופן התצוגה של תאריכים ושעות בדפים של הזמנות ציבוריות עבורך או עבור מי שמבצע הזמנות בשמך.",
"create_workflow": "יצירת תהליך עבודה",
"do_this": "יש לבצע את הפעולה הזו",
"turn_off": "השבתה",
@@ -1228,7 +1229,7 @@
"exchange_compression": "דחיסת GZip",
"routing_forms_description": "כאן ניתן לצפות בכל הטפסים והנתיבים שיצרת.",
"routing_forms_send_email_owner": "שליחת דוא\"ל לבעלים",
"routing_forms_send_email_owner_description": "שליחת דוא\"ל לבעלים כשהטופס נשלח",
"routing_forms_send_email_owner_description": "דוא\"ל נשלח לבעלים לאחר שליחת הטופס",
"add_new_form": "הוספת טופס חדש",
"form_description": "צור/י טופס משלך כדי לנתב אל מזמין/ה",
"copy_link_to_form": "להעתיק את הקישור לטופס",
@@ -1340,11 +1341,11 @@
"limit_future_bookings": "הגבלת הזמנות עתידיות",
"limit_future_bookings_description": "הגבלת התאריך הרחוק ביותר בעתיד שעד אליו ניתן להזמין את האירוע הזה",
"no_event_types": "לא הוגדר אף סוג אירוע",
"no_event_types_description": "{{name}} לא הגדיר/ה אף סוג אירוע שבאפשרותך להזמין.",
"no_event_types_description": "לא הוגדר על ידי {{name}} אף סוג אירוע שבאפשרותך להזמין.",
"billing_frequency": "תדירות חיוב",
"monthly": "אחת לחודש",
"yearly": "אחת לשנה",
"checkout": "קופה",
"checkout": "תשלום",
"your_team_disbanded_successfully": "פיזור הצוות שלך בוצע בהצלחה",
"error_creating_team": "אירעה שגיאה במהלך יצירת הצוות",
"you": "את/ה",
@@ -1363,11 +1364,11 @@
"choose_common_schedule_team_event": "בחירת לוח זמנים משותף",
"choose_common_schedule_team_event_description": "יש להפעיל אפשרות זו אם רוצים להשתמש בלוח זמנים משותף בין המארחים. אם האפשרות הזו מבוטלת, כל מארח יוזמן לפי לוח הזמנים המשמש כברירת המחדל שלו.",
"reason": "סיבה",
"sender_id": "מזהה שולח/ת",
"sender_id": "מזהה השולח/ת",
"sender_id_error_message": "מותר להשתמש רק באותיות, במספרים וברווחים (לכל היותר 11 תווים)",
"test_routing_form": "בדיקת טופס ניתוב",
"test_preview": "תצוגה מקדימה של הבדיקה",
"test_preview": "בדיקת התצוגה המקדימה",
"route_to": "ניתוב אל",
"test_preview_description": "בדיקת טופס הניתוב שלך בלי לשלוח נתונים",
"test_preview_description": "לבדוק את טופס הניתוב שלך בלי לשלוח נתונים",
"test_routing": "בדיקת ניתוב"
}
+12 -11
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "Il tuo evento programmato è stato cancellato",
"organizer": "Organizzatore",
"need_to_reschedule_or_cancel": "È necessario riprogrammare o annullare?",
"cancellation_reason": "Motivo dell'annullamento",
"cancellation_reason_placeholder": "Perché stai annullando? (facoltativo)",
"cancellation_reason": "Motivo dell'annullamento (facoltativo)",
"cancellation_reason_placeholder": "Perché stai annullando?",
"rejection_reason": "Motivo del rifiuto",
"rejection_reason_title": "Rifiutare la richiesta di prenotazione?",
"rejection_reason_description": "Sei sicuro di voler rifiutare la prenotazione? Informeremo la persona che ha cercato di prenotare. Puoi indicare il motivo qui sotto.",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "Nel caso la parola segreta venga persa o dimenticata, è possibile cambiarla, ma tieni presente che tutte le integrazioni che la utilizzano dovranno essere aggiornate",
"current_incorrect_password": "La password attuale non è corretta",
"password_hint_caplow": "Combinazione di caratteri maiuscoli e minuscoli",
"password_hint_min": "La lunghezza minima è di 7 caratteri",
"password_hint_min": "La lunghezza minima è di 8 caratteri",
"password_hint_num": "Deve contenere almeno 1 numero",
"invalid_password_hint": "La password deve avere una lunghezza minima di 7 caratteri e deve contenere almeno 1 numero e una combinazione di caratteri maiuscoli e minuscoli",
"incorrect_password": "La password non è corretta.",
@@ -504,7 +504,7 @@
"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",
"invite_new_member_description": "Nota: il <1>costo di un posto aggiuntivo (15$/m)</1> verrà addebitato sul tuo abbonamento.",
"invite_new_member_description": "Nota: Sul tuo abbonamento verrà addebitato <1>un posto aggiuntivo (15$/m)</1>.",
"invite_new_team_member": "Invita qualcuno nel tuo team.",
"change_member_role": "Cambia il ruolo del membro del team",
"disable_cal_branding": "Disabilita il branding {{appName}}",
@@ -601,7 +601,7 @@
"monthly_other": "mesi",
"yearly_one": "anno",
"yearly_other": "anni",
"plus_more": "altri {{count}}",
"plus_more": "Altri {{count}}",
"max": "Max",
"single_theme": "Tema Singolo",
"brand_color": "Colore del Marchio",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Mostra impostazioni avanzate",
"event_name": "Nome Dell'Evento",
"event_name_tooltip": "Il nome che apparirà nei calendari",
"meeting_with_user": "Riunione con {ATTENDEE}",
"meeting_with_user": "Riunione con {{attendeeName}}",
"additional_inputs": "Input Aggiuntivi",
"additional_input_description": "Chiedi al creatore del programma di inserire dei campi di input aggiuntivi prima che la prenotazione sia confermata",
"label": "Etichetta",
@@ -784,7 +784,7 @@
"analytics": "Analisi",
"empty_installed_apps_headline": "Nessuna app installata",
"empty_installed_apps_description": "Le app consentono di ottimizzare il flusso di lavoro e di migliorare significativamente la pianificazione.",
"empty_installed_apps_button": "Esplora l'App Store o installa dalle app sottostanti",
"empty_installed_apps_button": "Esplora l'App Store",
"manage_your_connected_apps": "Gestisci le app installate o modifica le impostazioni",
"browse_apps": "Sfoglia app",
"features": "Funzionalità",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Codice di autenticazione a due fattori errato",
"which_event_type_apply": "A quale tipo di evento si applicherà?",
"no_workflows_description": "I flussi di lavoro attivano una semplice automazione per inviare notifiche e promemoria che ti consentono di creare processi per i tuoi eventi.",
"timeformat_profile_hint": "Questa è un'impostazione interna e non influenzerà il modo in cui gli orari sono visualizzati nelle pagine di prenotazione pubbliche a te e a chiunque faccia una prenotazione.",
"create_workflow": "Crea un flusso di lavoro",
"do_this": "Esegui questa operazione",
"turn_off": "Disattiva",
@@ -1344,16 +1345,16 @@
"billing_frequency": "Frequenza di fatturazione",
"monthly": "Mensile",
"yearly": "Annuale",
"checkout": "Checkout",
"checkout": "Pagamento",
"your_team_disbanded_successfully": "Il tuo team è stato sciolto correttamente",
"error_creating_team": "Errore durante la creazione del team",
"you": "Tu",
"send_email": "Invia e-mail",
"member_already_invited": "Membro già invitato",
"enter_email_or_username": "Immettere un indirizzo e-mail o un nome utente",
"team_name_taken": "Nome già occupato",
"must_enter_team_name": "Immettere un nome del team",
"team_url_required": "Immettere un URL del team",
"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",
"team_url_taken": "URL già occupato",
"team_publish": "Pubblica team",
"number_sms_notifications": "Numero di telefono (notifiche SMS)",
+23 -22
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "スケジュールされていた予定がキャンセルされました",
"organizer": "主催者",
"need_to_reschedule_or_cancel": "再スケジュールまたはキャンセルが必要ですか?",
"cancellation_reason": "キャンセルの理由",
"cancellation_reason_placeholder": "キャンセルが必要な理由を教えてください (任意)",
"cancellation_reason": "キャンセルの理由 (オプション)",
"cancellation_reason_placeholder": "キャンセルが必要な理由をお教えください",
"rejection_reason": "拒否の理由",
"rejection_reason_title": "予約のリクエストを拒否しますか?",
"rejection_reason_description": "この予約を拒否してよろしいですか?予約を試みたユーザーに通知を送ります。拒否の理由は、下にご記入いただけます。",
@@ -108,7 +108,7 @@
"link_expires": "追記: 有効期限は {{expiresIn}} 時間後に切れます。",
"upgrade_to_per_seat": "座席ごとにアップグレードする",
"team_upgrade_seats_details": "あなたのチームに所属する {{memberCount}} 人のメンバーのうち、{{unpaidCount}} 名分のライセンスが未払いとなっています。ライセンスごとの料金は、${{seatPrice}} / 月ですので、あなたのメンバーシップの合計金額は、${{totalCost}} / 月になると予想されます。",
"team_upgrade_banner_description": "新しいチームプランをお試しいただきありがとうございます。あなたのチーム「{{teamName}}」はアップグレードが必要なようです。",
"team_upgrade_banner_description": "新しいチームプランをお試しいただき、ありがとうございます。あなたのチーム「{{teamName}}」には、アップグレードが必要なようです。",
"team_upgrade_banner_action": "アップグレードはこちら",
"team_upgraded_successfully": "チームのアップグレードが正常に完了しました!",
"use_link_to_reset_password": "以下のリンクを使用してパスワードをリセットしてください",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "このシークレットを紛失または忘れた場合は変更が可能です。しかしこのシークレットを使っている連携はすべて最新のものにする必要があります",
"current_incorrect_password": "現在のパスワードが正しくありません",
"password_hint_caplow": "アルファベットの大文字と小文字を両方使用してください",
"password_hint_min": "7 文字以上入力してください",
"password_hint_min": "8 文字以上入力してください",
"password_hint_num": "少なくとも 1 文字は数字を含めてください",
"invalid_password_hint": "パスワードには少なくとも 1 文字以上数字を含め、アルファベットの大文字と小文字を両方使用した上で 7 文字以上の長さに設定してください",
"incorrect_password": "パスワードが正しくありません。",
@@ -504,7 +504,7 @@
"add_team_members_description": "他のユーザーをチームに招待する",
"add_team_member": "チームメンバーを追加",
"invite_new_member": "新しいチームメンバーを招待",
"invite_new_member_description": "注意:これによりサブスクリプションに<1>追加座席分(月15ドル)の費用が加算されます。</1>",
"invite_new_member_description": "注: これにより、サブスクリプションに<1>追加の座席料金 (15 米ドル/メンバー) が加算されます。</1>",
"invite_new_team_member": "チームに誰かを招待する。",
"change_member_role": "チームメンバーの役割を変更する",
"disable_cal_branding": "{{appName}} のブランディングを無効化",
@@ -601,7 +601,7 @@
"monthly_other": "月",
"yearly_one": "年",
"yearly_other": "年",
"plus_more": "{{count}} 回以上",
"plus_more": "あと {{count}} 回",
"max": "最大",
"single_theme": "シングルテーマ",
"brand_color": "ブランドカラー",
@@ -653,7 +653,7 @@
"show_advanced_settings": "詳細設定を表示",
"event_name": "イベント名",
"event_name_tooltip": "カレンダーに表示される名前",
"meeting_with_user": "{ATTENDEE} とのミーティング",
"meeting_with_user": "{{attendeeName}} とのミーティング",
"additional_inputs": "追加入力",
"additional_input_description": "予約確認前にスケジュール設定者の追加入力が必要です",
"label": "ラベル",
@@ -784,7 +784,7 @@
"analytics": "分析",
"empty_installed_apps_headline": "アプリがインストールされていません",
"empty_installed_apps_description": "アプリを使えば、ワークフローの質を高め、スケジュールライフを大幅に改善することができます。",
"empty_installed_apps_button": "App Store で探す",
"empty_installed_apps_button": "App Store を見る",
"manage_your_connected_apps": "インストール済みアプリの管理や設定の変更を行う",
"browse_apps": "アプリを閲覧",
"features": "特徴",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "不正な二要素認証コード",
"which_event_type_apply": "これはどのイベントタイプに適用されますか?",
"no_workflows_description": "ワークフローを使用すると通知やリマインダーの送信を簡単に自動化することができ、イベントに関するプロセスを構築することができます。",
"timeformat_profile_hint": "これは内部設定であり、あなたやあなたを予約した人の公開予約ページの時間表示には影響しません。",
"create_workflow": "ワークフローを作成",
"do_this": "これを実行する",
"turn_off": "オフにする",
@@ -1228,7 +1229,7 @@
"exchange_compression": "GZip 圧縮",
"routing_forms_description": "作成したすべてのフォームとルートをここで確認できます。",
"routing_forms_send_email_owner": "所有者にメールを送信",
"routing_forms_send_email_owner_description": "フォーム送信時に所有者にメールを送信します",
"routing_forms_send_email_owner_description": "フォームの送信時に所有者にメールを送信します",
"add_new_form": "新しいフォームを追加",
"form_description": "予約者をルーティングするフォームを作成します",
"copy_link_to_form": "フォームへのリンクをコピー",
@@ -1295,7 +1296,7 @@
"fetching_calendars_error": "カレンダーの取得中にエラーが発生しました。<1>もう一度お試しいただく</1>か、カスタマーサポートまでお問い合わせください。",
"calendar_connection_fail": "カレンダーの接続に失敗しました",
"booking_confirmation_success": "正常に予約を確認しました",
"booking_rejection_success": "正常に予約を拒否しました",
"booking_rejection_success": "予約を正常に拒否しました",
"booking_confirmation_fail": "予約の確認に失敗しました",
"we_wont_show_again": "次回から表示しない",
"couldnt_update_timezone": "タイムゾーンを更新できませんでした",
@@ -1341,33 +1342,33 @@
"limit_future_bookings_description": "今後いつまでこのイベントを予約できるかを設定します",
"no_event_types": "イベントタイプは設定されていません",
"no_event_types_description": "{{name}}はあなたが予約するイベントのタイプを設定していません。",
"billing_frequency": "請求頻度",
"monthly": "月ごと",
"yearly": "年ごと",
"checkout": "精算",
"your_team_disbanded_successfully": "チームは正常に解散しました",
"billing_frequency": "請求の頻度",
"monthly": "毎月",
"yearly": "毎年",
"checkout": "チェックアウト",
"your_team_disbanded_successfully": "チームは正常に解散されました",
"error_creating_team": "チームの作成中にエラーが発生しました",
"you": "あなた",
"send_email": "メールを送信",
"member_already_invited": "メンバーは既に招待されています",
"enter_email_or_username": "メールアドレスまたはユーザー名を入力してください",
"team_name_taken": "この名前は既に使用されています",
"must_enter_team_name": "チーム名を入力する必要があります",
"team_url_required": "チームの URL を入力する必要があります",
"must_enter_team_name": "チーム名の入力は必須です",
"team_url_required": "チームの URL の入力は必須です",
"team_url_taken": "この URL は既に使用されています",
"team_publish": "チームを公開",
"number_sms_notifications": "電話番号(SMS 通知)",
"attendee_email_workflow": "出席者のメールアドレス",
"attendee_email_info": "予約者のメールアドレス",
"invalid_credential": "おっと!許可が期限切れになったか、取り消されたようです。もう一度インストールし直してください。",
"invalid_credential": "アクセス許可の有効期限が切れたか、取り消されたようです。もう一度インストールしてください。",
"choose_common_schedule_team_event": "共通のスケジュールを選択",
"choose_common_schedule_team_event_description": "ホスト間で共通のスケジュールを使用する場合は、これを有効にします。無効にすると、各ホストはデフォルトのスケジュールに基づいて予約されます。",
"choose_common_schedule_team_event_description": "ホスト間で共通のスケジュールを使用する場合には、これを有効にしてください。無効にすると、各ホストに対してはデフォルトのスケジュールに基づいて予約が行われます。",
"reason": "理由",
"sender_id": "送信者 ID",
"sender_id_error_message": "文字、数字、スペースのみ使用できます(最大 11 文字)",
"sender_id_error_message": "文字、数字、スペースのみが使用可能です (最大 11 文字)",
"test_routing_form": "ルーティングフォームをテスト",
"test_preview": "テストプレビュー",
"route_to": "ルート先",
"test_preview": "プレビューをテスト",
"route_to": "ルーティング先",
"test_preview_description": "データを送信せずにルーティングフォームをテストします",
"test_routing": "ルーティングをテスト"
}
+16 -15
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "회의 일정이 취소되었습니다.",
"organizer": "주최자",
"need_to_reschedule_or_cancel": "다시 일정을 잡거나 취소하시겠습니까?",
"cancellation_reason": "취소 사유",
"cancellation_reason_placeholder": "왜 취소를 선택하셨나요? (선택사항)",
"cancellation_reason": "취소 사유(선택사항)",
"cancellation_reason_placeholder": "취소하는 이유가 무엇인가요?",
"rejection_reason": "거부 사유",
"rejection_reason_title": "예약 요청을 거부하시겠습니까?",
"rejection_reason_description": "예약을 거부하시겠습니까? 예약하려고 시도하셨던 다른 분께 알려드릴 것입니다. 해당 사유는 아래에서 제공하실 수 있습니다.",
@@ -108,8 +108,8 @@
"link_expires": "{{expiresIn}} 시간 안에 만료됩니다.",
"upgrade_to_per_seat": "시트 단위로 업그레이드",
"team_upgrade_seats_details": "귀하의 팀원 {{memberCount}}명 중 {{unpaidCount}}명의 시트 비용이 미지급되었습니다. 시트당 ${{seatPrice}}/m로 계산시, 예상 총 멤버십 비용은 ${{totalCost}}/m입니다.",
"team_upgrade_banner_description": "당사의 새로운 팀 플랜을 사용해 주셔서 감사합니다. \"{{teamName}}\" 팀을 업그레이드해야 합니다.",
"team_upgrade_banner_action": "여기에서 업그레이드",
"team_upgrade_banner_description": "당사의 새로운 팀 플랜을 사용해 주셔서 감사합니다. 귀하의 \"{{teamName}}\" 팀에 업그레이드가 필요한 것으로 확인되었습니다.",
"team_upgrade_banner_action": "여기서 업그레이드",
"team_upgraded_successfully": "팀이 성공적으로 업그레이드되었습니다!",
"use_link_to_reset_password": "비밀번호를 재설정하시려면 아래 링크를 사용하세요.",
"hey_there": "안녕하세요.",
@@ -504,7 +504,7 @@
"add_team_members_description": "다른 사람을 팀에 초대",
"add_team_member": "팀원 추가",
"invite_new_member": "새 팀원 초대",
"invite_new_member_description": "참고: 이렇게 하면 해당 구독에 <1>추가 좌석 비용($15/m)</1>이 발생합니다.",
"invite_new_member_description": "참고: 이로 인해 해당 구독에 <1>추가 시트 비용($15/m)</1>이 발생합니다.",
"invite_new_team_member": "누군가를 팀에 초대하기",
"change_member_role": "팀원 역할 변경",
"disable_cal_branding": "{{appName}} 브랜딩 비활성화",
@@ -653,7 +653,7 @@
"show_advanced_settings": "고급 설정 표시",
"event_name": "이벤트 이름",
"event_name_tooltip": "캘린더에 표시될 이름",
"meeting_with_user": "{ATTENDEE} 님과의 회의",
"meeting_with_user": "{{attendeeName}} 님과의 회의",
"additional_inputs": "추가 입력",
"additional_input_description": "예약이 확정되기 전 스케줄러가 추가 입력을 해야 합니다",
"label": "레이블",
@@ -784,7 +784,7 @@
"analytics": "분석",
"empty_installed_apps_headline": "설치된 앱 없음",
"empty_installed_apps_description": "앱을 통해 워크플로를 강화하고 일정 관리 수명을 대폭 향상할 수 있습니다.",
"empty_installed_apps_button": "앱 스토어를 둘러보거나 아래 앱 중에서 설치",
"empty_installed_apps_button": "앱 스토어 둘러보기",
"manage_your_connected_apps": "설치된 앱 관리 또는 설정 변경",
"browse_apps": "앱 찾아보기",
"features": "기능",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "잘못된 2단계 인증 코드",
"which_event_type_apply": "이것은 어떤 이벤트 유형에 적용될까요?",
"no_workflows_description": "워크플로를 사용하면 이벤트와 관련된 프로세스를 구축할 수 있도록 알림 및 미리 알림을 보내는 간단한 자동화가 가능합니다.",
"timeformat_profile_hint": "이것은 내부 설정이며 귀하 또는 귀하를 예약하는 모든 사람의 공개 예약 페이지에 시간이 표시되는 방식에는 영향을 미치지 않습니다.",
"create_workflow": "워크플로를 만듭니다",
"do_this": "이렇게 하세요",
"turn_off": "끄기",
@@ -1295,7 +1296,7 @@
"fetching_calendars_error": "캘린더를 가져오는 중 문제가 발생했습니다. <1>다시 시도</1>하거나 고객 지원에 문의하십시오.",
"calendar_connection_fail": "캘린더 연결 실패",
"booking_confirmation_success": "예약 확인 성공",
"booking_rejection_success": "예약 거부 성공",
"booking_rejection_success": "예약이 거부됨",
"booking_confirmation_fail": "예약 확인 실패",
"we_wont_show_again": "다시 표시 안 함",
"couldnt_update_timezone": "시간대를 업데이트할 수 없습니다",
@@ -1346,27 +1347,27 @@
"yearly": "연간",
"checkout": "체크아웃",
"your_team_disbanded_successfully": "귀하의 팀이 성공적으로 해산되었습니다",
"error_creating_team": "팀을 만드는 중 오류가 발생했습니다",
"you": "나",
"error_creating_team": "팀을 만드는 중 오류 발생",
"you": "귀하",
"send_email": "이메일 보내기",
"member_already_invited": "구성원이 이미 초대되었습니다",
"enter_email_or_username": "이메일 또는 사용자 이름을 입력하세요",
"team_name_taken": "이미 사용 중인 이름입니다",
"must_enter_team_name": "팀 이름을 입력하세요",
"team_url_required": "팀 URL을 입력해야 합니다",
"team_url_required": "팀 URL을 입력하세요",
"team_url_taken": "이미 사용 중인 URL입니다",
"team_publish": "팀 게시",
"number_sms_notifications": "전화번호 (SMS notifications)",
"attendee_email_workflow": "참석자 이메일",
"attendee_email_info": "예약자 이메일",
"invalid_credential": "죄송합니다! 권한이 만료되었거나 취소된 것 같습니다. 다시 설치하십시오.",
"choose_common_schedule_team_event": "공통 일정을 선택하세요",
"choose_common_schedule_team_event_description": "호스트 간에 공통 일정을 사용하려면 이 옵션을 활성화하십시오. 비활성화되면 각 호스트는 기본 일정에 따라 예약됩니다.",
"reason": "이유",
"choose_common_schedule_team_event": "공통 일정 선택",
"choose_common_schedule_team_event_description": "호스트 간에 공통 일정을 사용하려면 이 옵션을 활성화하세요. 비활성화되면 각 호스트는 기본 일정에 따라 예약됩니다.",
"reason": "사유",
"sender_id": "발신자 ID",
"sender_id_error_message": "영문, 숫자, 공백만 허용(최대 11자)",
"test_routing_form": "라우팅 양식 테스트",
"test_preview": "테스트 미리보기",
"test_preview": "미리보기 테스트",
"route_to": "목표 경로",
"test_preview_description": "데이터를 제출하지 않고 라우팅 양식 테스트",
"test_routing": "라우팅 테스트"
@@ -653,7 +653,7 @@
"show_advanced_settings": "Toon geavanceerde instellingen",
"event_name": "Naam van evenement",
"event_name_tooltip": "De naam die in kalenders wordt weergegeven",
"meeting_with_user": "Afspraak met {ATTENDEE}",
"meeting_with_user": "Afspraak met {{attendeeName}}",
"additional_inputs": "Extra invoer",
"additional_input_description": "Planner vereist om extra gegevens in te voeren voordat de boeking wordt bevestigd",
"label": "Omschrijving",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Onjuiste tweestapsverificatiecode",
"which_event_type_apply": "Op welk type gebeurtenis is dit van toepassing?",
"no_workflows_description": "Werkstromen maken eenvoudige automatisering van het versturen van meldingen en herinneringen mogelijk, zodat u processen rond uw gebeurtenissen kunt opbouwen.",
"timeformat_profile_hint": "Dit is een interne instelling en heeft geen invloed op hoe de tijden worden weergegeven op de openbare boekingspagina's voor u of voor iemand die u boekt.",
"create_workflow": "Maak een werkstroom",
"do_this": "Doe dit",
"turn_off": "Uitschakelen",
+55 -45
View File
@@ -6,9 +6,9 @@
"second_other": "{{count}} sekunder",
"upgrade_now": "Oppgrader nå",
"accept_invitation": "Aksepter invitasjon",
"calcom_explained": "Cal.com er en åpen kildekodeversjon av Calendly som gir deg kontroll over dine egne data, arbeidsflyt og utseende.",
"calcom_explained": "{{appName}} er en åpen kildekodeversjon av Calendly som gir deg kontroll over dine egne data, arbeidsflyt og utseende.",
"have_any_questions": "Har du spørsmål? Vi er her for å hjelpe.",
"reset_password_subject": "Cal.com: Instruksjoner for tilbakestilling av passord",
"reset_password_subject": "{{appName}}: Instruksjoner for tilbakestilling av passord",
"event_declined_subject": "Avslått: {{eventType}} med {{name}} kl. {{date}}",
"event_cancelled_subject": "Avbrutt: {{eventType}} med {{name}} kl. {{date}}",
"event_request_declined": "Din hendelsesforespørsel har blitt avvist",
@@ -52,6 +52,7 @@
"still_waiting_for_approval": "En hendelse venter fremdeles på godkjenning",
"event_is_still_waiting": "Hendelsesforespørsel venter fortsatt: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Ingen flere resultater",
"no_results": "Ingen resultater",
"load_more_results": "Last inn flere resultater",
"integration_meeting_id": "{{integrationName}} møte-ID: {{meetingId}}",
"confirmed_event_type_subject": "Bekreftet: {{eventType}} med {{name}} kl. {{date}}",
@@ -99,9 +100,9 @@
"join_team": "Bli med i teamet",
"manage_this_team": "Administrer dette teamet",
"team_info": "Team Info",
"request_another_invitation_email": "Hvis du foretrekker å ikke bruke {{toEmail}} som Cal.com-e-post eller allerede har en Cal.com-konto, vennligst be om en annen invitasjon til denne e-posten.",
"request_another_invitation_email": "Hvis du foretrekker å ikke bruke {{toEmail}} som {{appName}}-e-post eller allerede har en {{appName}}-konto, vennligst be om en annen invitasjon til denne e-posten.",
"you_have_been_invited": "Du har blitt invitert til å bli med i teamet {{teamName}}",
"user_invited_you": "{{user}} inviterte deg til å bli med i teamet {{team}} på Cal.com",
"user_invited_you": "{{user}} inviterte deg til å bli med i teamet {{team}} på {{appName}}",
"hidden_team_member_title": "Du er skjult i dette teamet",
"hidden_team_member_message": "Setet ditt er ikke betalt for, enten oppgrader til PRO eller la teameieren få vite at de kan betale for setet ditt.",
"hidden_team_owner_message": "Du trenger en pro-konto for å bruke team, du er skjult inntil du oppgraderer.",
@@ -113,8 +114,8 @@
"team_upgraded_successfully": "Teamet ditt ble oppgradert!",
"use_link_to_reset_password": "Bruk lenken nedenfor for å tilbakestille passordet ditt",
"hey_there": "Hei der,",
"forgot_your_password_calcom": "Glemt passordet? - Cal.com",
"delete_webhook_confirmation_message": "Er du sikker på at du vil slette denne webhooken? Du vil ikke lenger motta Cal.com møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller avlyst.",
"forgot_your_password_calcom": "Glemt passordet? - {{appName}}",
"delete_webhook_confirmation_message": "Er du sikker på at du vil slette denne webhooken? Du vil ikke lenger motta {{appName}} møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller avlyst.",
"confirm_delete_webhook": "Ja, slett webhook",
"edit_webhook": "Rediger Webhook",
"delete_webhook": "Slett Webhook",
@@ -190,7 +191,7 @@
"30min_meeting": "30 Min Møte",
"secret": "Hemmelighet",
"leave_blank_to_remove_secret": "La stå tomt for å fjerne hemmeligheten",
"webhook_secret_key_description": "Sørg for at serveren din kun mottar de forventede Cal.com-forespørslene av sikkerhetsgrunner",
"webhook_secret_key_description": "Sørg for at serveren din kun mottar de forventede {{appName}}-forespørslene av sikkerhetsgrunner",
"secret_meeting": "Hemmelig Møte",
"login_instead": "Logg på i stedet",
"already_have_an_account": "Har du allerede en bruker?",
@@ -234,13 +235,13 @@
"details": "Detaljer",
"welcome": "Velkommen",
"welcome_back": "Velkommen tilbake",
"welcome_to_calcom": "Velkommen til Cal.com",
"welcome_to_calcom": "Velkommen til {{appName}}",
"welcome_instructions": "Fortell oss hva vi skal kalle deg og gi oss beskjed om hvilken tidssone du er i. Du kan redigere dette senere.",
"connect_caldav": "Koble til CalDav (Beta)",
"credentials_stored_and_encrypted": "Din legitimasjon vil bli lagret og kryptert.",
"connect": "Tilkoble",
"try_for_free": "Prøv det gratis",
"create_booking_link_with_calcom": "Lag din egen booking lenke med Cal.com",
"create_booking_link_with_calcom": "Lag din egen booking lenke med {{appName}}",
"who": "Hvem",
"what": "Hva",
"when": "Når",
@@ -248,6 +249,7 @@
"add_to_calendar": "Legg til i kalender",
"add_another_calendar": "Legg til en annen kalender",
"other": "Annet",
"email_sign_in_subject": "Påloggings-lenken din for {{appName}}",
"emailed_you_and_attendees": "Vi sendte deg og de andre deltakerne en kalenderinvitasjon med alle detaljene.",
"emailed_you_and_attendees_recurring": "Vi sendte deg og de andre deltakerne en kalenderinvitasjon på e-post for den første av disse gjentakende hendelsene.",
"emailed_you_and_any_other_attendees": "Du og eventuelle andre deltakere har fått e-post med denne informasjonen.",
@@ -346,7 +348,7 @@
"dark": "Mørkt",
"automatically_adjust_theme": "Juster tema automatisk basert på den invitertes innstillinger",
"user_dynamic_booking_disabled": "Noen av brukerne i gruppen har for øyeblikket deaktivert dynamiske gruppebookinger",
"allow_dynamic_booking_tooltip": "Gruppebooking-lenker som kan opprettes dynamisk ved å legge til flere brukernavn med en '+'. eksempel: 'cal.com/bailey+peer'",
"allow_dynamic_booking_tooltip": "Gruppebooking-lenker som kan opprettes dynamisk ved å legge til flere brukernavn med en '+'. eksempel: '{{appName}}/bailey+peer'",
"allow_dynamic_booking": "Tillat deltakere å booke deg gjennom dynamiske gruppebookinger",
"email": "E-post",
"email_placeholder": "ola.nordman@eksempel.no",
@@ -366,8 +368,8 @@
"team_webhooks": "Team Webhooker",
"create_new_webhook_to_account": "Opprett en ny webhook til kontoen din",
"new_webhook": "Ny Webhook",
"receive_cal_meeting_data": "Motta Cal-møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller kansellert.",
"receive_cal_event_meeting_data": "Motta Cal-møtedata på en spesifisert URL, i sanntid, når denne hendelsen er planlagt eller kansellert.",
"receive_cal_meeting_data": "Motta {{appName}}-møtedata på en spesifisert URL, i sanntid, når en hendelse er planlagt eller kansellert.",
"receive_cal_event_meeting_data": "Motta {{appName}}-møtedata på en spesifisert URL, i sanntid, når denne hendelsen er planlagt eller kansellert.",
"responsive_fullscreen_iframe": "Responsiv fullskjerms iFrame",
"loading": "Laster inn...",
"deleting": "Sletter...",
@@ -375,7 +377,7 @@
"developer": "Utvikler",
"manage_developer_settings": "Administrer utviklerinnstillingene dine.",
"iframe_embed": "iframe Bygg inn",
"embed_calcom": "Den enkleste måten å bygge inn Cal.com på nettstedet ditt.",
"embed_calcom": "Den enkleste måten å bygge inn {{appName}} på nettstedet ditt.",
"integrate_using_embed_or_webhooks": "Integrer med nettstedet ditt ved å bruke våre innebyggings-alternativer, eller få bookinginformasjon i sanntid ved å bruke egendefinerte webhooks.",
"schedule_a_meeting": "Planlegg et møte",
"view_and_manage_billing_details": "Se og administrer betalingsdetaljene dine",
@@ -511,8 +513,8 @@
"invite_new_member_description": "Merk: Dette vil <1>koste et ekstra sete ($15/m)</1> på abonnementet ditt.",
"invite_new_team_member": "Inviter noen til teamet ditt.",
"change_member_role": "Endre team-medlems-rolle",
"disable_cal_branding": "Deaktiver Cal merkeprofilering",
"disable_cal_branding_description": "Skjul all Cal.com-merkevareprofilering fra de offentlige sidene dine.",
"disable_cal_branding": "Deaktiver {{appName}} merkeprofilering",
"disable_cal_branding_description": "Skjul all {{appName}}-merkevareprofilering fra de offentlige sidene dine.",
"danger_zone": "Faresone",
"back": "Tilbake",
"cancel": "Avbryt",
@@ -632,7 +634,7 @@
"changed_team_billing_info": "Fra januar 2022 tar vi betalt per sete for teammedlemmer. Medlemmer av teamet ditt som hadde PRO gratis, har nå en 14 dagers prøveversjon. Når prøveperioden deres utløper, vil disse medlemmene bli skjult for teamet ditt med mindre du oppgraderer nå.",
"create_manage_teams_collaborative": "Opprett og administrer team for å bruke samarbeidsfunksjoner.",
"only_available_on_pro_plan": "Denne funksjonen er bare tilgjengelig i Pro-planen",
"remove_cal_branding_description": "For å fjerne Cal-merket fra bookingsidene dine, må du oppgradere til en Pro-konto.",
"remove_cal_branding_description": "For å fjerne {{appName}}-merket fra bookingsidene dine, må du oppgradere til en Pro-konto.",
"edit_profile_info_description": "Rediger profilinformasjonen din, som viser på planleggingslenken.",
"change_email_tip": "Du må kanskje logge ut og inn igjen for å se endringen tre i kraft.",
"little_something_about": "Litt om deg selv.",
@@ -681,21 +683,21 @@
"private_link_label": "Privat lenke",
"private_link_hint": "Din private lenke vil regenereres etter hver bruk",
"copy_private_link": "Kopier privat lenke",
"private_link_description": "Generer en privat URL for å dele uten å avsløre Cal-brukernavnet ditt",
"private_link_description": "Generer en privat URL for å dele uten å avsløre {{appName}}-brukernavnet ditt",
"invitees_can_schedule": "Inviterte kan velge tidspunkt",
"date_range": "Datointervall",
"calendar_days": "kalenderdager",
"business_days": "virkedager",
"set_address_place": "Angi en adresse eller sted",
"set_link_meeting": "Angi en lenke til møtet",
"cal_invitee_phone_number_scheduling": "Cal vil be den inviterte om å skrive inn et telefonnummer før valg av tidspunkt.",
"cal_provide_google_meet_location": "Cal vil oppgi en Google Meet-lokasjon.",
"cal_provide_zoom_meeting_url": "Cal vil angi en URL-adresse for Zoom-møte.",
"cal_provide_tandem_meeting_url": "Cal vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "Cal vil angi en videomøte-URL.",
"cal_invitee_phone_number_scheduling": "{{appName}} vil be den inviterte om å skrive inn et telefonnummer før valg av tidspunkt.",
"cal_provide_google_meet_location": "{{appName}} vil oppgi en Google Meet-lokasjon.",
"cal_provide_zoom_meeting_url": "{{appName}} vil angi en URL-adresse for Zoom-møte.",
"cal_provide_tandem_meeting_url": "{{appName}} vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "{{appName}} vil angi en videomøte-URL.",
"cal_provide_jitsi_meeting_url": "Vi vil generere en Jitsi Meet URL for deg.",
"cal_provide_huddle01_meeting_url": "Cal vil angi en Huddle01 web3 videomøte-URL.",
"cal_provide_teams_meeting_url": "Cal vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 web3 videomøte-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"require_payment": "Krev Betaling",
"commission_per_transaction": "provisjon per transaksjon",
"event_type_updated_successfully_description": "Din hendelsestype har blitt oppdatert.",
@@ -718,7 +720,7 @@
"confirm_delete_event_type": "Ja, slett hendelsestype",
"delete_account": "Slett konto",
"confirm_delete_account": "Ja, slett konto",
"delete_account_confirmation_message": "Er du sikker på at du vil slette Cal.com-kontoen din? Alle du har delt konto-lenken med, vil ikke lenger kunne booke ved å bruke den, og eventuelle innstillinger du har lagret vil gå tapt.",
"delete_account_confirmation_message": "Er du sikker på at du vil slette {{appName}}-kontoen din? Alle du har delt konto-lenken med, vil ikke lenger kunne booke ved å bruke den, og eventuelle innstillinger du har lagret vil gå tapt.",
"integrations": "Integrasjoner",
"apps": "Apper",
"category_apps": "{{category}} apper",
@@ -776,6 +778,7 @@
"trending_apps": "Populære Apper",
"explore_apps": "{{category}} apper",
"installed_apps": "Installerte Apper",
"free_to_use_apps": "Gratis",
"no_category_apps": "Ingen {{category}} apper",
"no_category_apps_description_calendar": "Legg til en kalenderapp for å se etter konflikter for å forhindre dobbelt-bookinger",
"no_category_apps_description_conferencing": "Prøv å legge til en konferanseapp for å integrere videosamtaler med klientene dine",
@@ -814,9 +817,11 @@
"verify_wallet": "Verifiser Lommebok",
"connect_metamask": "Koble til Metamask",
"create_events_on": "Opprett hendelser på",
"enterprise_license": "Dette er en bedriftsfunksjon",
"enterprise_license_description": "For å aktivere denne funksjonen, skaff deg en distribusjonsnøkkel på {{consoleUrl}}-konsollen og legg den til i .env som CALCOM_LICENSE_KEY. Hvis teamet ditt allerede har en lisens, vennligst kontakt {{supportMail}} for å få hjelp.",
"missing_license": "Mangler Lisens",
"signup_requires": "Kommersiell lisens kreves",
"signup_requires_description": "Cal.com, Inc. tilbyr for øyeblikket ikke en gratis åpen kildekode-versjon av registreringssiden. For å få full tilgang til registreringskomponentene må du anskaffe en kommersiell lisens. For personlig bruk anbefaler vi Prisma Data Platform eller et annet Postgres-grensesnitt for å opprette kontoer.",
"signup_requires_description": "{{companyName}} tilbyr for øyeblikket ikke en gratis åpen kildekode-versjon av registreringssiden. For å få full tilgang til registreringskomponentene må du anskaffe en kommersiell lisens. For personlig bruk anbefaler vi Prisma Data Platform eller et annet Postgres-grensesnitt for å opprette kontoer.",
"next_steps": "Neste Skritt",
"acquire_commercial_license": "Skaff en kommersiell lisens",
"the_infrastructure_plan": "Infrastruktur-planen er bruksbasert og har oppstartsvennlige rabatter.",
@@ -919,23 +924,23 @@
"update_location": "Oppdater Sted",
"location_updated": "Sted oppdatert",
"email_validation_error": "Det ser ikke ut som en e-postadresse",
"place_where_cal_widget_appear": "Plasser denne koden i din HTML der du vil at Cal-widgeten skal vises.",
"place_where_cal_widget_appear": "Plasser denne koden i din HTML der du vil at {{appName}}-widgeten skal vises.",
"create_update_react_component": "Opprett eller oppdater en eksisterende React-komponent som vist nedenfor.",
"copy_code": "Kopier Kode",
"code_copied": "Kode kopiert!",
"how_you_want_add_cal_site": "Hvordan vil du legge til Cal på nettstedet ditt?",
"choose_ways_put_cal_site": "Velg en av følgende måter å plassere Cal på nettstedet ditt.",
"how_you_want_add_cal_site": "Hvordan vil du legge til {{appName}} på nettstedet ditt?",
"choose_ways_put_cal_site": "Velg en av følgende måter å plassere {{appName}} på nettstedet ditt.",
"setting_up_zapier": "Sette opp din Zapier-integrasjon",
"generate_api_key": "Generer API nøkkel",
"generate_api_key_description": "Generer en API nøkkel til bruk med Cal.com på",
"generate_api_key_description": "Generer en API nøkkel til bruk med {{appName}} på",
"your_unique_api_key": "Din unike API nøkkel",
"copy_safe_api_key": "Kopier denne API nøkkelen og lagre den et trygt sted. Hvis du mister denne nøkkelen, må du generere en ny.",
"zapier_setup_instructions": "<0>Logg på Zapier-kontoen din og opprett en ny Zap.</0><1>Velg Cal.com som din Trigger-app. Velg også en utløserhendelse.</1><2>Velg kontoen din og skriv deretter inn din unike API nøkkel.</2><3>Test utløseren.</3><4>Du er klar!</4 >",
"zapier_setup_instructions": "<0>Logg på Zapier-kontoen din og opprett en ny Zap.</0><1>Velg {{appName}} som din Trigger-app. Velg også en utløserhendelse.</1><2>Velg kontoen din og skriv deretter inn din unike API nøkkel.</2><3>Test utløseren.</3><4>Du er klar!</4 >",
"install_zapier_app": "Installer først Zapier-appen i app butikken.",
"connect_apple_server": "Koble til Apple Server",
"connect_caldav_server": "Koble til CalDav (Beta)",
"calendar_url": "Kalender URL",
"apple_server_generate_password": "Generer et app-spesifikt passord for bruk med Cal.com på",
"apple_server_generate_password": "Generer et app-spesifikt passord for bruk med {{appName}} på",
"credentials_stored_encrypted": "Din legitimasjon vil bli lagret og kryptert.",
"it_stored_encrypted": "Det vil bli lagret og kryptert.",
"go_to_app_store": "Gå til App Butikken",
@@ -1120,15 +1125,16 @@
"incorrect_2fa": "Feil to-faktor autentiseringskode",
"which_event_type_apply": "Hvilken hendelsestype vil dette gjelde for?",
"no_workflows_description": "Arbeidsflyter muliggjør enkel automatisering for å sende varsler og påminnelser slik at du kan bygge prosesser rundt hendelsene dine.",
"timeformat_profile_hint": "Dette er en intern innstilling og vil ikke påvirke hvordan tidspunkt vises på offentlige bookingsider for deg eller noen som booker deg.",
"create_workflow": "Lag en arbeidsflyt",
"do_this": "Gjør dette",
"turn_off": "Slå av",
"settings_updated_successfully": "Innstillingene ble oppdatert",
"error_updating_settings": "Feil under oppdatering av innstillinger",
"personal_cal_url": "Min personlige Cal URL",
"personal_cal_url": "Min personlige {{appName}} URL",
"bio_hint": "Noen setninger om deg selv. Dette vil vises på din personlige url-side.",
"delete_account_modal_title": "Slett Konto",
"confirm_delete_account_modal": "Er du sikker på at du vil slette Cal.com-kontoen din?",
"confirm_delete_account_modal": "Er du sikker på at du vil slette {{appName}}-kontoen din?",
"delete_my_account": "Slett kontoen min",
"start_of_week": "Begynnelsen av uken",
"select_calendars": "Velg hvilke kalendere du vil sjekke for konflikter for å forhindre dobbeltbookinger.",
@@ -1138,7 +1144,7 @@
"custom_brand_colors": "Egendefinerte merkevare-farger",
"customize_your_brand_colors": "Tilpass din egen merkevare-farge på bookingsiden din.",
"pro": "Pro",
"removes_cal_branding": "Fjerner alt av Cal relatert merkeprofilering, f.eks. \"Powered by Cal.\"",
"removes_cal_branding": "Fjerner alt av {{appName}} relatert merkeprofilering, f.eks. \"Powered by {{appName}}.\"",
"profile_picture": "Profilbilde",
"upload": "Last opp",
"add_profile_photo": "Legg til profilbilde",
@@ -1166,7 +1172,7 @@
"embeds": "Innbygginger",
"impersonation": "Etterligning",
"users": "Brukere",
"profile_description": "Administrer innstillinger for Cal profilen din",
"profile_description": "Administrer innstillinger for {{appName}} profilen din",
"general_description": "Administrer innstillinger for språk og tidssone",
"calendars_description": "Konfigurer hvordan hendelsestypene dine samhandler med kalenderne dine",
"appearance_description": "Administrer innstillinger for booking-utseendet",
@@ -1204,7 +1210,7 @@
"create_your_first_form": "Lag ditt første skjema",
"create_your_first_form_description": "Med Viderekoblings-skjema kan du stille kvalifiserende spørsmål og videresende til riktig person eller hendelsestype.",
"create_your_first_webhook": "Lag din første Webhook",
"create_your_first_webhook_description": "Med Webhooks kan du motta møtedata i sanntid når noe skjer i Cal.com.",
"create_your_first_webhook_description": "Med Webhooks kan du motta møtedata i sanntid når noe skjer i {{appName}}.",
"for_a_maximum_of": "For maksimalt",
"event_one": "hendelse",
"event_other": "hendelser",
@@ -1220,7 +1226,7 @@
"workflow_example_4": "Send deltakeren en e-post-påminnelse 1 time før hendelser starter",
"workflow_example_5": "Send verten en tilpasset e-post når en hendelse får nytt tidspunkt",
"workflow_example_6": "Send verten en tilpasset SMS når en ny hendelse er booket",
"welcome_to_cal_header": "Velkommen til Cal.com!",
"welcome_to_cal_header": "Velkommen til {{appName}}!",
"edit_form_later_subtitle": "Du kan redigere dette senere.",
"connect_calendar_later": "Jeg kobler til kalenderen min senere",
"set_my_availability_later": "Jeg setter opp min tilgjengelighet senere",
@@ -1230,7 +1236,7 @@
"booking_appearance": "Booking Utseende",
"appearance_team_description": "Administrer innstillinger for teamets booking-utseende",
"only_owner_change": "Kun eieren av dette teamet kan gjøre endringer i teamets booking ",
"team_disable_cal_branding_description": "Fjerner alle Cal relaterte varemerker, f.eks. \"Powered by Cal\"",
"team_disable_cal_branding_description": "Fjerner alle {{appName}} relaterte varemerker, f.eks. \"Powered by {{appName}}\"",
"invited_by_team": "{{teamName}} har invitert deg til å bli med i teamet deres som en {{role}}",
"token_invalid_expired": "Token er enten ugyldig eller utløpt.",
"exchange_add": "Koble til Microsoft Exchange",
@@ -1250,13 +1256,13 @@
"theme_dark": "Mørkt",
"theme_system": "System standard",
"add_a_team": "Legg til et team",
"add_webhook_description": "Motta møtedata i sanntid når noe skjer i Cal.com",
"add_webhook_description": "Motta møtedata i sanntid når noe skjer i {{appName}}",
"triggers_when": "Utløses når",
"test_webhook": "Vennligst ping-test før du oppretter.",
"enable_webhook": "Aktiver Webhook",
"add_webhook": "Legg til Webhook",
"webhook_edited_successfully": "Webhook lagret",
"webhooks_description": "Motta møtedata i sanntid når noe skjer i Cal.com",
"webhooks_description": "Motta møtedata i sanntid når noe skjer i {{appName}}",
"api_keys_description": "Generer API nøkler for å få tilgang til din egen konto",
"new_api_key": "Ny API nøkkel",
"active": "aktiv",
@@ -1265,7 +1271,7 @@
"embeds_title": "HTML iframe-innbygging",
"embeds_description": "Bygg inn alle hendelsestypene dine på nettstedet ditt",
"create_first_api_key": "Lag din første API nøkkel",
"create_first_api_key_description": "API nøkler lar andre apper kommunisere med Cal.com",
"create_first_api_key_description": "API nøkler lar andre apper kommunisere med {{appName}}",
"back_to_signin": "Tilbake til innlogging",
"reset_link_sent": "Lenke for tilbakestilling er sendt",
"password_reset_email": "En e-post er på vei til {{email}} med instruksjoner for å tilbakestille passordet.",
@@ -1273,14 +1279,14 @@
"password_updated": "Passord oppdatert!",
"pending_payment": "Venter på betaling",
"confirmation_page_rainbow": "Token vokter hendelsen din med tokens eller NFT-er på Ethereum, Polygon og mer.",
"not_on_cal": "Ikke på Cal.com",
"not_on_cal": "Ikke på {{appName}}",
"no_calendar_installed": "Ingen kalender installert",
"no_calendar_installed_description": "Du har ikke koblet til noen av kalenderne dine ennå",
"add_a_calendar": "Legg til en kalender",
"change_email_hint": "Det kan hende du må logge ut og inn igjen for å se at endringer trer i kraft",
"confirm_password_change_email": "Vennligst bekreft passordet ditt før du endrer e-postadressen din",
"seats": "seter",
"every_app_published": "Hver app publisert på Cal.com App Butikken er åpen kildekode og grundig testet via fagfellevurderinger. Cal.com, Inc. godkjenner eller sertifiserer likevel ikke disse appene med mindre de er publisert av Cal.com. Hvis du møter upassende innhold eller oppførsel, vennligst rapporter det.",
"every_app_published": "Hver app publisert på {{appName}} App Butikken er åpen kildekode og grundig testet via fagfellevurderinger. Uansett, {{companyName}} godkjenner eller sertifiserer likevel ikke disse appene med mindre de er publisert av {{appName}}. Hvis du møter upassende innhold eller oppførsel, vennligst rapporter det.",
"report_app": "Rapporter app",
"limit_booking_frequency": "Begrens bookingfrekvens",
"limit_booking_frequency_description": "Begrens hvor mange ganger denne hendelsen kan bookes",
@@ -1383,5 +1389,9 @@
"test_preview_description": "Test viderekoblings-skjemaet ditt uten å sende noen data",
"test_routing": "Test Viderekobling",
"booking_limit_reached": "Booking-grensen for denne hendelsestypen er nådd",
"fill_this_field": "Vennligst fyll ut dette feltet"
"fill_this_field": "Vennligst fyll ut dette feltet",
"options": "Alternativer",
"enter_option": "Angi alternativ {{index}}",
"add_an_option": "Legg til et alternativ",
"radio": "Radio"
}
+17 -16
View File
@@ -16,8 +16,8 @@
"event_request_cancelled": "Twoje zaplanowane wydarzenie zostało anulowane",
"organizer": "Organizator",
"need_to_reschedule_or_cancel": "Potrzebujesz zmienić harmonogram lub anulować?",
"cancellation_reason": "Powód anulowania",
"cancellation_reason_placeholder": "Dlaczego anulujesz? (opcjonalne)",
"cancellation_reason": "Powód anulowania (opcjonalnie)",
"cancellation_reason_placeholder": "Dlaczego anulujesz?",
"rejection_reason": "Powód odrzucenia",
"rejection_reason_title": "Odrzucić prośbę o rezerwację?",
"rejection_reason_description": "Czy jesteś pewien, że chcesz odrzucić prośbę o rezerwację? Damy znać osobie, która próbowała jej dokonać. Poniżej możesz podać powód.",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "Jeśli nie pamiętasz tego wpisu tajnego lub nie masz do niego dostępu, możesz go zmienić, ale pamiętaj, że wszystkie integracje korzystające z tego wpisu tajnego będą musiały zostać zaktualizowane.",
"current_incorrect_password": "Bieżące hasło jest nieprawidłowe",
"password_hint_caplow": "Kombinacja wielkich i małych liter",
"password_hint_min": "Długość co najmniej 7 znaków",
"password_hint_min": "Długość co najmniej 8 znaków",
"password_hint_num": "Zawrzyj co najmniej 1 cyfrę",
"invalid_password_hint": "Hasło musi składać się z co najmniej 7 znaków, zawierać co najmniej jedną cyfrę oraz kombinację wielkich i małych liter",
"incorrect_password": "Hasło jest nieprawidłowe.",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Pokaż ustawienia zaawansowane",
"event_name": "Nazwa wydarzenia",
"event_name_tooltip": "Nazwa, która pojawi się w kalendarzach",
"meeting_with_user": "Spotkanie z {ATTENDEE}",
"meeting_with_user": "Spotkanie z {{attendeeName}}",
"additional_inputs": "Dodatkowe Wejścia",
"additional_input_description": "Wymagaj od osoby układającej harmonogram wprowadzania dodatkowych danych przed potwierdzeniem rezerwacji",
"label": "Etykieta",
@@ -784,7 +784,7 @@
"analytics": "Analityka",
"empty_installed_apps_headline": "Brak zainstalowanych aplikacji",
"empty_installed_apps_description": "Aplikacje umożliwiają znaczne usprawnienie przebiegu pracy i uproszczenie planowania.",
"empty_installed_apps_button": "Przeglądaj sklep App Store lub zainstaluj z poniższych aplikacji",
"empty_installed_apps_button": "Przeglądaj sklep App Store",
"manage_your_connected_apps": "Zarządzaj zainstalowanymi aplikacjami lub zmień ustawienia",
"browse_apps": "Przeglądaj aplikacje",
"features": "Funkcje",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Nieprawidłowy kod uwierzytelnienia dwuskładnikowego",
"which_event_type_apply": "Do jakiego rodzaju wydarzenia będzie się to odnosić?",
"no_workflows_description": "Przepływy pracy umożliwiają prostą automatyzację wysyłania powiadomień i przypomnień, dzięki czemu możesz tworzyć procesy dotyczące Twoich wydarzeń.",
"timeformat_profile_hint": "To jest ustawienie wewnętrzne, które nie będzie miało wpływu na to, jak godziny będą wyświetlane Tobie lub komuś, kto dokonuje u Ciebie rezerwacji, na publicznych stronach rezerwacji.",
"create_workflow": "Utwórz przepływ pracy",
"do_this": "Zrób to",
"turn_off": "Wyłącz",
@@ -1344,17 +1345,17 @@
"billing_frequency": "Częstotliwość rozliczania",
"monthly": "Co miesiąc",
"yearly": "Co rok",
"checkout": "Kasa",
"checkout": "Do kasy",
"your_team_disbanded_successfully": "Twój zespół został rozwiązany.",
"error_creating_team": "Podczas tworzenia zespołu wystąpił błąd.",
"error_creating_team": "Podczas tworzenia zespołu wystąpił błąd",
"you": "Ty",
"send_email": "Wyślij wiadomość e-mail",
"member_already_invited": "Użytkownik został już zaproszony.",
"member_already_invited": "Członek został już zaproszony",
"enter_email_or_username": "Wprowadź adres e-mail lub nazwę użytkownika",
"team_name_taken": "Ta nazwa jest już zajęta.",
"must_enter_team_name": "Musisz podać nazwę zespołu.",
"team_url_required": "Musisz podać adres URL zespołu.",
"team_url_taken": "Ten adres URL jest już używany.",
"team_name_taken": "Ta nazwa jest już zajęta",
"must_enter_team_name": "Musisz wprowadzić nazwę zespołu",
"team_url_required": "Musisz wprowadzić adres URL zespołu",
"team_url_taken": "Ten adres URL jest już zajęty",
"team_publish": "Opublikuj zespół",
"number_sms_notifications": "Numer telefonu (powiadomienia SMS)",
"attendee_email_workflow": "Adres e-mail uczestnika",
@@ -1365,9 +1366,9 @@
"reason": "Powód",
"sender_id": "Identyfikator nadawcy",
"sender_id_error_message": "Dozwolone są tylko litery, cyfry i spacje (maksymalnie 11 znaków)",
"test_routing_form": "Testuj formularz do przekierowania",
"test_preview": "Podgląd testu",
"test_routing_form": "Przetestuj formularz przekierowania",
"test_preview": "Przetestuj podgląd",
"route_to": "Przekieruj do",
"test_preview_description": "Przetestuj swój formularz przekierowania bez przesyłania jakicholwiek danych",
"test_routing": "Testuj przekierowanie"
"test_preview_description": "Przetestuj swój formularz przekierowania bez przesyłania jakichkolwiek danych",
"test_routing": "Przetestuj przekierowanie"
}
@@ -17,7 +17,7 @@
"organizer": "Organizador",
"need_to_reschedule_or_cancel": "Precisa reagendar ou cancelar?",
"cancellation_reason": "Motivo do cancelamento",
"cancellation_reason_placeholder": "Por que você está cancelando? (opcional)",
"cancellation_reason_placeholder": "Por que você está cancelando?",
"rejection_reason": "Motivo da recusa",
"rejection_reason_title": "Recusar a solicitação de reserva?",
"rejection_reason_description": "Tem certeza de que deseja recusar a reserva? Avisaremos a pessoa que tentou reservar. Se quiser, informe um motivo abaixo.",
@@ -419,7 +419,7 @@
"forgotten_secret_description": "Caso tenha perdido ou esquecido o segredo, você poderá alterá-lo. Mas fique ciente de que todas as integrações com esse segredo precisarão ser atualizadas",
"current_incorrect_password": "Senha atual está incorreta",
"password_hint_caplow": "Mistura de letras maiúsculas e minúsculas",
"password_hint_min": "Mínimo de sete caracteres",
"password_hint_min": "Mínimo de oito caracteres",
"password_hint_admin_min": "No mínimo 15 caracteres",
"password_hint_num": "Contém pelo menos um número",
"invalid_password_hint": "A senha precisa ter pelo menos sete caracteres com pelo menos um número e ser uma combinação de letras maiúsculas e minúsculas",
@@ -654,7 +654,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que aparecerá nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Solicitar que o agendador insira entradas adicionais antes que o pedido seja confirmado",
"label": "Etiqueta",
@@ -1109,6 +1109,7 @@
"incorrect_2fa": "Código de autenticação de dois fatores incorreto",
"which_event_type_apply": "A que tipo de evento isto se aplica?",
"no_workflows_description": "Fluxo de trabalho permitem uma simples automação para enviar notificações e lembretes que permitem que você crie processos em torno de seus eventos.",
"timeformat_profile_hint": "Esta é uma configuração interna e não afetará a quantidade de exibições em páginas públicas de reservas para você ou outra pessoa que estiver fazendo reserva.",
"create_workflow": "Criar fluxo de trabalho",
"do_this": "Faça isso",
"turn_off": "Desligar",
@@ -1355,7 +1356,7 @@
"team_name_taken": "Este nome já está em uso",
"must_enter_team_name": "É preciso inserir um nome de equipe",
"team_url_required": "É preciso inserir uma URL de equipe",
"team_url_taken": "Esta URL já está em uso",
"team_url_taken": "Este URL já está em uso",
"team_publish": "Publicar equipe",
"number_sms_notifications": "Número de telefone (notificações de SMS)",
"attendee_email_workflow": "E-mail do participante",
@@ -1367,7 +1368,7 @@
"sender_id": "ID do remetente",
"sender_id_error_message": "Apenas letras, números e espaços permitidos (no máx. 11 caracteres)",
"test_routing_form": "Testar formulário de roteamento",
"test_preview": "Testar prévia",
"test_preview": "Visualizar teste",
"route_to": "Direcionar para",
"test_preview_description": "Teste seu formulário de roteamento sem enviar dados",
"test_routing": "Testar roteamento"
@@ -504,7 +504,7 @@
"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",
"invite_new_member_description": "Nota: isto irá ter o <1>custo de um lugar adicional ($15/m)</1> na sua subscrição.",
"invite_new_member_description": "Nota: isto vai ter o <1>custo de um lugar adicional ($15/m)</1> na sua subscrição.",
"invite_new_team_member": "Convide alguém para a sua equipa.",
"change_member_role": "Alterar função de membro da equipa",
"disable_cal_branding": "Desativar a marca {{appName}}",
@@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que será mostrado nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Requerer que o responsável pelo agendamento especifique entradas complementares antes do pedido ser confirmado",
"label": "Etiqueta",
@@ -1108,6 +1108,7 @@
"incorrect_2fa": "Código de autenticação de dois fatores incorreto",
"which_event_type_apply": "A que tipo de evento isto se aplica?",
"no_workflows_description": "Os fluxos de trabalho permitem automações simples para enviar notificações e lembretes, permitindo criar processos à volta dos seus eventos.",
"timeformat_profile_hint": "Esta é uma configuração interna e que não irá afetar como os horários serão apresentados nas páginas públicas de agendamento, seja para si ou para qualquer pessoa que esteja a fazer uma reserva.",
"create_workflow": "Criar um fluxo de trabalho",
"do_this": "Fazer isto",
"turn_off": "Desligar",

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