Compare commits

...
100 Commits
Author SHA1 Message Date
Keith Williams f179c23cc5 v2.8.13
release.yml / release (push) Canceled after 0s
2023-05-10 11:26:52 -03:00
Jeroen Reumkens 9f83885e8c CAL-1577: Improved input addon styles. (#8803) 2023-05-10 08:59:32 +00:00
Syed Ali Shahbaz 8ffb5529e6 Use user profile timezone from DB as fallback for CalDAV all-day events (#8755)
* adds timezone pull from user profile in DB

* minor fixes

* fixes promise return type

* comments and minor fixes

* minor improvement

* fix return type

* changes for help in debugging
2023-05-10 08:44:15 +00:00
Jon@1599 2e686d03fa feat: allow only team users to record daily meetings (#8799) 2023-05-10 08:28:49 +00:00
Jeroen Reumkens cc3be0a6ac CAL-1623 CAL-1620: Improve animation for booker, and push mini calendar down in fullscreen views (#8773)
* CAL-1623 CAL-1620: Improve animation for booker, and push mini calendar down in fullscreen views

* Updated yarn.lock
2023-05-10 08:26:59 +00:00
ec3a3268c9 Resolved[#8732] Adding Trim to remove extra space (#8736)
* Resolved[#8732] Adding Trim to remove extra space

* fix: improve code

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

* chore: add lowercase

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

* chore: use lowercase

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: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-05-10 07:45:15 +00:00
Udit Takkar 77eed5611e fix: use useTeamsPlan hook (#8789)
* fix: use useTeamsPlan hook

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

* fix: z index bug on dialog

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-05-10 07:11:01 +00:00
Crowdin Bot a827d1defb New Crowdin translations by Github Action 2023-05-10 04:27:42 +00:00
Hariom Balhara b08a62d30b Allow booking responses to have the data from variantsConfig (#8718) 2023-05-10 03:59:51 +00:00
d6fb0df64f perf: tRPC procedures and middleware refactor (#8419)
* trpc procedures an middleware refactor

* allow use sessionMiddleware without a req object

* sync with the new tRPC structure

* tRPC refactor on routing form app

* import Prisma from @prisma/client

* Lazy load apps from appstore

* remove unrelated changes

* Add types for PaymentService

* type fixes

* Merge branch 'main' into roae85/cal-1514-set-the-user-session-only-on-the

* fix typo

* remove console.log

* remove explicit types from apstore object

* linter fixes

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-09 19:27:05 +00:00
Crowdin Bot 03548d8381 New Crowdin translations by Github Action 2023-05-09 17:41:16 +00:00
Carina WollendorferandCarinaWolli 36d8554fd5 disable standard confirmation emails if workflow exists (#8747)
* add switches to advanced even type settings

* only show switches when workflow is enabled

* check in event type update handler if user is allowed to disable standard emails

* don't send emails if disabled

* make sure emails can't be disabled if no workflow exist

* send workflow emails to all attendees

* code clean up

* add translations

* always send to all attendees when scheduling reminders

* change text

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-05-09 17:08:14 +00:00
sean-brydonandUdit Takkar 6fc9e9315d Bug/checkbox calendar sync state (#8677)
* switch wip

* Implement calendar switch component

* Checked default

* Add spinner to improve UX

* Add connected calendar reivaluate

* Remove local state

* Move back to middleware

* Fix spacing issue

* fix: spacing

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: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-05-09 17:53:23 +01:00
Ashray ShettyandBailey Pumfleet 6eb8119a82 Added the 'Add' button (#8777)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-05-09 17:45:53 +01:00
Keith Williams f4c613e6d1 Enable workflows on merge_group (#8781)
* Enable workflows on merge_group

* Reverting back to pull_request_target
2023-05-09 17:43:07 +01:00
Nafees NazikandHariom Balhara 082507aea5 fix: location select logo in dark mode (#8633)
* fix: logo

* fix: icon on booker

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-09 15:27:09 +00:00
Leo GiovanettiandJoe Au-Yeung 90c1270e3d Fixing availability for MET (#8662)
* Fixing availability for MET

* Removing unneeded code

* Fixing unit tests

* Lint fixes

---------

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2023-05-09 11:15:13 -03:00
Joe Au-Yeungandalannnc 94917dbb1e Disable reserve slot on seated events (#8761)
* Disable reserve slot on seated events

* Move seats and reserve logic to backend

* Pass booking attendees

* Type fix

* Create booking when two users choose a slot before booking is created

* Update packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts

---------

Co-authored-by: alannnc <alannnc@gmail.com>
2023-05-09 11:02:19 +01:00
Carina WollendorferandCarinaWolli fbbcc401cb fix START_TIME variable + allow sms for self hosted (#8762)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-05-09 07:50:17 +00:00
swaraj bachu af7d7ec678 fixed ui bug (#8741) 2023-05-09 08:45:45 +01:00
Reckson Zirsangzela Khiangte 3b7d85852a Bug: Change color variable for dark mode (#8758) 2023-05-09 08:44:17 +01:00
Peer RichelsenandUdit Takkar 5362cd66b4 added gradients to dark dashboard and dark booking page (#8459)
* added gradients to dark dashboard and dark booking page

* removed bg of timezone dropdown for dak

* added gradient to confirmation screen

* only gradient in dashboard

* fix: prettier

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: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-05-08 16:03:24 -03:00
Zoheb AhmedandAlex van Andel 2907405d4e Fixes Bold/Italics Issue (#8613)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-08 14:11:25 +02:00
3c46c39114 feat: catch emails sent locally using Mailhog (#8470)
* feat: added mailhog to catch emails sent locally

* fix: added new line at the end of packages/emails/docker-compose.yml

* chore: removed EMAIL_SERVER_USER and EMAIL_SERVER_PASSWORD for local usage with Mailhog

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-06 19:08:04 -03:00
Manish Roy c6ab889b98 handles scenario where defaultschedule is nil (#8714) 2023-05-06 08:52:54 +01:00
Hariom Balhara a2e2b0ed34 Fix broken appsRouter (#8719) 2023-05-06 08:50:52 +01:00
bc38b3bc55 enhance Timezone Component with Box Shadow (#8582)
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-05 21:38:19 +00:00
Jeroen Reumkens 7611d5c24e CAL-1555: Show correct team avatars (#8665) 2023-05-05 21:44:21 +01:00
5d2fd7aafb [Symbolic Link Issues] Added instructions for setup on windows (#8574)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2023-05-05 21:43:40 +01:00
b9ade55ac1 Update README.md (#8679)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-05 21:41:47 +01:00
Jeroen Reumkens d7051c4a3f Centered small_calendar view vertically (#8712) 2023-05-05 21:39:00 +01:00
Jeroen Reumkens ab50067194 Set selecteddate to today when switching layout. Before we were getting the current layout from the store, which would actually be the OLD layout you were switching from 😅 (#8711) 2023-05-05 16:42:47 +00:00
cdba1920fc refactor: split up routers to separate lambdas (#8041)
* split up routers to separate lambdas

* fix responsemeta

* move

* add typeguards to make sure all endpoints are covered in the approuter

* prettier

* move slotsrouter

* split ssg/ssr

* make sure correct headers are sent on viewer.public

* make sure correct headers are sent + use ctx.prisma

* Fixed new prefetch broken by merge

* Fixes after merge

* Created separate API route for all tRPC routers

* More fixes from refactor

* Fixed tRPC query for slots

* Put back extra line

* Fixed type checks

* Removed Endpoint type check since it loads from client

* Reverted change in getSchedule test

* Fix trpc routes in expectations

* Fix one more route test

---------

Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-05 13:19:10 -03:00
Jeroen Reumkens 98a3508b8f CAL-1552: Add newline in front of time for selected timeslot in booker meta. (#8669) 2023-05-05 16:55:54 +01:00
Alex van Andel 446f384405 Add publishHandler user.id (#8703) 2023-05-05 15:36:33 +00:00
Jeroen Reumkensandsean-brydon be79b8ecd6 CAL-1554: remove gap between form and event meta on now booker due to css grid alignment (#8667)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-05 12:38:21 +00:00
371e7b024a add "reset to default" color option for ColorPicker (#8643)
* add reset to default color option

* Do not show reset button in DOM if resetDefaultValue is undefined

* handle edge case: if invalid hexcode is set for resetDefaultValue

* Do not show the reset button if the color is already set to the default value

* Remove data-testid from Button

---------

Co-authored-by: Praneeth Bhogaraju <praneeth.bhogaraju@ni.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-05 13:25:18 +01:00
sean-brydon 76807a7e70 Fix switch label hover (#8696) 2023-05-05 12:47:00 +01:00
Hariom BalharaandPeer Richelsen d3f77bf292 Fix: Embed Theme switching when appearance is using System Theme (#8655)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-05 16:54:06 +05:30
Nafees Nazik 2f175eacf5 fix: load date picker on client side (#8690) 2023-05-05 13:04:58 +02:00
Nafees Nazik b45aea3f26 feat: add search for members (#8659)
* feat: add search team members

* fix: add default value

* fix: add placeholder
2023-05-05 10:35:27 +01:00
Jeroen Reumkens c401a58bb1 CAL-1589: Added utm link to powered by in booker. Also added new colors to powered by component, so theyre also used in current booker form now on. (#8670) 2023-05-05 08:36:23 +01:00
05b42c001f feat: Add Zoho Bigin app to appstore (#8158)
* feat: add zoho bigin app to appstore

* Handle appKeys save and read correctly

* Fixes

* Remove env variables and seeding

* Create README for zoho-bigin and link to that in main README

* Remove imageSrc, thats deprecated

* Improved logs

* Remove stray beecepter fetch

* Add Zoho Bigin package.json description

* Load constants from config.json

* Refactor bigin auth tokens management

* Remove try catch

* fix refresh token not getting stored properly

* load api domain dynamically

* Fix reschedule not updating zoho bigin event

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-04 20:19:48 +00:00
Mahesh Bansodeandsean-brydon 20f7a5841e Added validation for duration field in the event types (#8649)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-04 12:05:52 +00:00
zomars dbf6bb991f Revert "perf: fix multiple versions of emotion/react loaded warning (#8549)"
This reverts commit 1cea7f9760.
2023-05-04 13:01:16 +01:00
Zeeshan BhatiandEfraín Rochín 75cd5a05b8 Fix Hover Effect in booking page (#8650)
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-05-04 11:58:27 +00:00
Joeandsean-brydon ca6afed27c fix: changed assign to field as typeable input (#8637)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-04 11:39:43 +00:00
Jeroen Reumkens 45250f6f3e CAL-1558: Fixed responsiveness of booker for small calendar layout (#8660)
* CAL-1558: Fixed responsiveness of booker for small calendar layout

* Remove commented code
2023-05-04 12:32:22 +01:00
Jeroen Reumkens bd5af9e821 Fix/prevent duplicate avatars (#8664)
* Prevent duplicate avatars in new booker by also checking on profile url.

* Don't filter out profile without profile url

* Use webapp_url for avatars since that's what is used in the rest of the platform, that way the unique filter on avatar works
2023-05-04 11:07:37 +00:00
Jeroen Reumkens 4267c9063b CAL-1556: Alignment of current day dot in datepicker. (#8663) 2023-05-04 11:53:50 +01:00
Crowdin Bot 2b49d96e4a New Crowdin translations by Github Action 2023-05-03 18:37:57 +00:00
127c96b46c Update README.md (#8632)
* Update README.md

* Update README.md

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-03 15:36:55 -03:00
Hariom Balhara 3595ba9824 Fix tests (#8640) 2023-05-03 12:30:26 -03:00
Keith Williams 56acfa38f1 v2.8.12
release.yml / release (push) Canceled after 0s
2023-05-03 10:12:34 -03:00
Syed Ali Shahbaz e78fb22451 [URGENT] Hotfix: re-adds the password and username set check in signup (#8638)
* Check for existing password and username to ensure it isn't an invite

* added check with verified to allow SAML linking

* wrong verified check lol

* updated comment

* unintended
2023-05-03 10:10:28 -03:00
Babis DigosandKeith Williams 99f736b317 fix: Use correct values on append DayRange (#8597)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 19:43:32 -03:00
ee9f80ae3b Fix form inputs height to 36px (#8541)
* fix form inputs height to 36px

* fix: remove h-9 from preset

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

* fix input addOns container height

* replace minHeight class with height in input addOn

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 21:49:17 +00:00
a3c283da5e Updated the CSS classes for the user name and profile (#8571)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 18:20:01 -03:00
Zoheb Ahmed 0ef678b2cf Booking page bug fixed (#8614) 2023-05-02 18:10:47 -03:00
4f466fd95d [CAL-1095] Embed modal (floating pop up button) - UI/layout/spacing issues (#8217)
* [CAL-1095] Embed modal (floating pop up button) - UI/layout/spacing issues

* requested changes

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 15:57:01 -03:00
Keith Williams 5672a721d9 v2.8.11
release.yml / release (push) Canceled after 0s
2023-05-02 15:21:58 -03:00
Mohd Afzal KhanandKeith Williams 5ad45900ea fix the auto-focus issue in teams setting page (#8577)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 15:20:03 -03:00
Hariom Balhara ada88a2f7e Fix: Show smsReminderNumber on booking success page (#8596) 2023-05-02 16:58:39 +00:00
Srijan MukherjeeandAlex van Andel 9ba90e18de resolves #3565 (#8581)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-02 16:56:29 +00:00
Carina WollendorferandCarinaWolli a88eb0fdc5 Allow booking date overrides outside of working hours (#8538)
* allow booking on date overrides outside of working hours

* remove comment

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-05-02 11:38:56 -03:00
4b4c1200cd Add HistoryPlugin to the Editor UI component (#8603)
* Add HistoryPlugin to the Editor UI component

* Fix ESLint errors

---------

Co-authored-by: Praneeth Bhogaraju <praneeth.bhogaraju@ni.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-02 11:29:52 -03:00
Nafees NazikandUdit Takkar 1cea7f9760 perf: fix multiple versions of emotion/react loaded warning (#8549)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-05-02 18:31:54 +05:30
28dcf69d18 use named imports instead to avoid polluting the global namespace. in… (#8616)
* use named imports instead to avoid polluting the global namespace. in nodemailer

* Update packages/emails/templates/_base-email.ts

Lint fix.

* Revert yarn.lock changes

---------

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-02 12:58:47 +00:00
Zeeshan Bhatiandnafees nazik 83d5176ddb Remove redirection after app uninstalltion (#8556)
Co-authored-by: nafees nazik <nafeesnazik21@gmail.com>
2023-05-02 18:14:09 +05:30
Alex van Andel 3815a4c4bf Fixed yarn.lock 2023-05-02 14:14:42 +02:00
Julius MarmingeandAlex van Andel 53748eb380 add an enum generator, stop importing from @prisma/client (#8548)
* add an enum generator and start importing from it

* keep moving imports

* fix remaining

* Header simplified

* Removed generated file from repo

* Updated .gitignore to exclude enums directory

* Add eslint rule to check for @prisma/client Prisma enum import

* Added another enum import + exclude PrismaClient

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-02 13:44:05 +02:00
hiteshbedre 28af11f90e fix: corrected discord link pattern (#8612) 2023-05-02 15:44:26 +05:30
Peer Richelsen ea676907b2 fixed border of status badge (#8568) 2023-04-30 13:26:09 +05:30
hallidayo 221c8b75bc added alignment on items (#8544) 2023-04-28 16:56:31 +05:30
GitStart-Cal.comandgitstart-calcom 5b9b7ab22d [CAL-1530] Assigned members to managed event types missing preview URL (#8552)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-04-27 14:51:20 -03:00
668ea088ef fix: password not updatable after an api error (#8514)
* fix: error clearance

* changes

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-04-27 11:43:23 +00:00
Nafees NazikandKeith Williams 8a6c420268 perf: prevent refetching i18n on route change (#8535)
* fix: prevent refetching i18n on route change

* remove additional properties

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-04-27 10:55:26 +00:00
Crowdin Bot d2f8a5c076 New Crowdin translations by Github Action 2023-04-27 08:08:34 +00:00
Abdurrahman RajabandPeer Richelsen 9c0f7bbfc7 Chore: fix RTL style in Color picker, Return arrow, Two factor page (#8317)
* chore: UI arrow left add RTL.

* chore: fix appearence page RTL

* chore: add margins two-factor RTL

* chore: update due to review

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-27 08:07:32 +00:00
Joe 8297824051 fix: edit mode set to true by default (#8468) 2023-04-26 22:06:04 +05:30
Joe Au-YeungandHariom Balhara 7ff5b66f46 Bookings with no-show fee to set proper location & prevent broken payments from blocking payment page (#8508)
* Better error logging on Stripe payment service

* Update booking

* If missing payment, load rest of bookings

* Address feedback

* Move requires confirmation threshold to webhook

* Use requiresConfirmation

* Revert unnecessary prettier changes

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-04-26 10:12:14 -04:00
Kirat 55570ed4bf Fixed discord validation (#8536) 2023-04-26 13:20:18 +00:00
Hariom Balhara 51695ee79d chore/AppStore: Remove deprecated imageSrc and make the paths relative in logo (#8479)
* Remove depreated imageSrc and make the paths relative in logo

* Do logo replacement at a single place

* Make logo relative at other places

* Fix campfire page not working in dev environment
2023-04-26 12:03:34 +01:00
Crowdin Bot 543466f1b9 New Crowdin translations by Github Action 2023-04-26 08:30:45 +00:00
Peer Richelsen 4abf2d67d9 removed devcontainer (#8521) 2023-04-26 10:29:54 +02:00
1eeb91a793 perf: lazy load tRPC routes (#8167)
* experiment: cold start perf

* fix: update failing test

* chore: add database indexes

* chore: use json protocol and add query batching back

* Update [status].tsx

* Update [trpc].ts

* Delete getSlimSession.ts

* Update createContext.ts

* remove trpc caller

* correctly import Prisma

* lazy ethRouter

* replace crypto with md5

* import fixes

* public event endpoint refactor

* Update yarn.lock

* Update yarn.lock

* Using yarn.lock from main

---------

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-04-25 19:39:47 -03:00
Nafees Nazik a4725920ff fix: font not loaded in cal video (#8517) 2023-04-25 20:59:39 +02:00
Richard Poelderl 4cc7766f88 add noindex tags for apps setup pages (#8518) 2023-04-25 20:59:15 +02:00
Crowdin Bot 0c78715e9a New Crowdin translations by Github Action 2023-04-25 14:30:39 +00:00
Crowdin Bot 671d9b4903 New Crowdin translations by Github Action 2023-04-25 14:29:36 +00:00
Jeroen ReumkensandPeer Richelsen 524cefcbad UI Improvements for new booker (#8483)
CAL-1578: No layout shift when switching months (because of timeslots disappearing again)
CAL-1576: Font weight tweaks
Bigger sidebar on fullscreen booker layouts
CAL-1573: Slightly smaller padding around booker sections
CAL-1563: Prevent timezone select from resizing when typing in it
CAL-1561: Spacing improvements timeslots + button height
CAL-1560: Improve resize animation when changing from time slot picker to form
CAL-1549: Added overflow scroll to event description, removed icon from event description so we have more space, and added sexy scrollbars

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-25 14:28:32 +00:00
Crowdin Bot 72f72d86b2 New Crowdin translations by Github Action 2023-04-25 14:08:18 +00:00
Crowdin Bot c5c29322d3 New Crowdin translations by Github Action 2023-04-25 14:07:30 +00:00
Crowdin Bot 863b04e9c1 New Crowdin translations by Github Action 2023-04-25 14:06:34 +00:00
Crowdin Bot c2833f1b44 New Crowdin translations by Github Action 2023-04-25 14:05:43 +00:00
Hariom Balhara 25df264823 Remove reviews/rating/trending/verified props (#8482) 2023-04-25 16:04:40 +02:00
Crowdin Bot e870312ebe New Crowdin translations by Github Action 2023-04-25 14:04:23 +00:00
Crowdin Bot 5371bff6ff New Crowdin translations by Github Action 2023-04-25 14:03:32 +00:00
Jeroen Reumkens c791344c8b Switch back to small calendar layout when switching from mobile to desktop view. Also fixed rerender bug triggerd by ToggleGroup constantly updating because it got a new instance for the onValueChange function. (#8515) 2023-04-25 15:02:34 +01:00
Crowdin Bot 5e149d9f5f New Crowdin translations by Github Action 2023-04-25 12:22:07 +00:00
662 changed files with 15994 additions and 10567 deletions
-42
View File
@@ -1,42 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker
{
"name": "Docker outside of Docker",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
"features": {
"ghcr.io/devcontainers/features/docker-from-docker:1": {
"version": "latest",
"enableNonRootDocker": "true",
"moby": "true"
},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers-contrib/features/npm-package:1": {},
"ghcr.io/devcontainers-contrib/features/jest:2": {},
"ghcr.io/devcontainers-contrib/features/prisma:2": {},
"ghcr.io/guiyomh/features/vim:0": {}
},
// Use this environment variable if you need to bind mount your local source code into a new container.
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},
"hostRequirements": {
"cpus": 4,
"memory": "8gb"
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "./deploy/install.sh"
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
+2 -1
View File
@@ -16,11 +16,12 @@
# - WEB3
# - SALESFORCE
# - ZOHOCRM
# - ZOHO_BIGIN
# - APP STORE **********************************************************************************************
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
# - DAILY.CO VIDEO
# Enables Cal Video. to get your key
# Enables Cal Video. to get your key
# 1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
# 2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
# @see https://github.com/calcom/cal.com#obtaining-daily-api-credentials
+8 -4
View File
@@ -138,12 +138,16 @@ API_KEY_PREFIX=cal_
EMAIL_FROM='notifications@yourselfhostedcal.com'
# Configure SMTP settings (@see https://nodemailer.com/smtp/).
# Configuration to receive emails locally (mailhog)
EMAIL_SERVER_HOST='localhost'
EMAIL_SERVER_PORT=1025
# Note: The below configuration for Office 365 has been verified to work.
EMAIL_SERVER_HOST='smtp.office365.com'
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER='<office365_emailAddress>'
# EMAIL_SERVER_HOST='smtp.office365.com'
# EMAIL_SERVER_PORT=587
# EMAIL_SERVER_USER='<office365_emailAddress>'
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
EMAIL_SERVER_PASSWORD='<office365_password>'
# EMAIL_SERVER_PASSWORD='<office365_password>'
# The following configuration for Gmail has been verified to work.
# EMAIL_SERVER_HOST='smtp.gmail.com'
+1
View File
@@ -7,6 +7,7 @@ on:
paths-ignore:
- "**.md"
- ".github/CODEOWNERS"
merge_group:
workflow_dispatch:
concurrency:
+2 -1
View File
@@ -65,8 +65,9 @@ tsconfig.tsbuildinfo
# turbo
.turbo
# Prisma-Zod
# Prisma generated files
packages/prisma/zod/*.ts
packages/prisma/enums
# Builds
dist
+1
View File
@@ -14,4 +14,5 @@ public
.DS_Store
.eslintignore
packages/prisma/zod
packages/prisma/enums
apps/web/public/embed
+1 -1
View File
@@ -6,6 +6,6 @@
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"],
"spellright.documentTypes": ["markdown", "typescript"],
"spellright.documentTypes": ["markdown", "typescript", "typescriptreact"],
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
}
+55 -44
View File
@@ -24,7 +24,7 @@
<p align="center">
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://status.cal.com"><img height="20px" src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
@@ -38,13 +38,13 @@
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
<a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
<a href="https://contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
<a href="https://console.algora.io/org/cal/bounties?status=open"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcal%2Fbounties%3Fstatus%3Dopen" /></a>
</p>
<!-- ABOUT THE PROJECT -->
## About The Project
## About the Project
<img width="100%" alt="booking-screen" src="https://user-images.githubusercontent.com/8019099/176390354-f1bc7069-0341-437a-9bb8-eb41092b4016.gif">
@@ -53,13 +53,13 @@
The open source Calendly alternative. You are in charge
of your own data, workflow and appearance.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customisations.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customizations.
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
## Recognition
#### Hacker News
#### [Hacker News](https://news.ycombinator.com/from?site=cal.com)
<a href="https://news.ycombinator.com/item?id=34507672">
<img
@@ -77,22 +77,22 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
/>
</a>
#### [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)
#### [Product Hunt](https://producthunt.com/products/cal-com?utm_source=badge-top-post-badge&utm_medium=badge)
<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
### Built With
- [Next.js](https://nextjs.org/?ref=cal.com)
- [tRPC](https://trpc.io/?ref=cal.com)
- [React](https://reactjs.org/?ref=cal.com)
- [Tailwind](https://tailwindcss.com/?ref=cal.com)
- [Prisma](https://prisma.io/?ref=cal.com)
- [React.js](https://reactjs.org/?ref=cal.com)
- [Tailwind CSS](https://tailwindcss.com/?ref=cal.com)
- [Prisma.io](https://prisma.io/?ref=cal.com)
- [Daily.co](https://go.cal.com/daily)
## Stay Up-to-Date
Cal officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
Cal.com officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
![cal-star-github](https://user-images.githubusercontent.com/8019099/154853944-a9e3c999-3da3-4048-b149-b4f73893c6fb.gif)
@@ -104,7 +104,7 @@ To get a local copy up and running, please follow these simple steps.
### Prerequisites
Here is what you need to be able to run Cal.
Here is what you need to be able to run Cal.com.
- Node.js (Version: >=15.x <17)
- PostgreSQL
@@ -121,6 +121,9 @@ Here is what you need to be able to run Cal.
```sh
git clone https://github.com/calcom/cal.com.git
```
> If you are on windows, run the following command on `gitbash` with admin privileges: <br>
```git clone -c core.symlinks=true https://github.com/calcom/cal.com.git``` <br>
See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
1. Go to the project folder
@@ -134,10 +137,10 @@ Here is what you need to be able to run Cal.
yarn
```
1. Set up your .env file
1. Set up your `.env` file
- Duplicate `.env.example` to `.env`
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the .env file.
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the .env file.
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
#### Quick start with `yarn dx`
@@ -150,7 +153,7 @@ yarn dx
#### Development tip
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **trpc**.
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **tRPC**.
```sh
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
@@ -158,16 +161,16 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
#### Manual setup
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
```
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
```
<details>
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB using Heroku</summary>
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB using Heroku.</summary>
1. Create a free account with [Heroku](https://www.heroku.com/).
1. Create a free account with [Heroku](https://heroku.com).
2. Create a new app.
<img width="306" alt="Create an App" src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png">
@@ -184,19 +187,25 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`.
![image](https://user-images.githubusercontent.com/16905768/115323367-e92ea980-a17f-11eb-9ff4-dec95f2ec349.png)
7. In `Settings`, copy your URI to your Cal.com .env file and replace the `postgresql://<user>:<pass>@<db-host>:<db-port>` with it.
7. In `Settings`, copy your URI to your Cal.com `.env` file and replace the `postgresql://<user>:<pass>@<db-host>:<db-port>` with it.
![image](https://user-images.githubusercontent.com/16905768/115323556-4591c900-a180-11eb-9808-2f55d2aa3995.png)
![image](https://user-images.githubusercontent.com/16905768/115323697-7a9e1b80-a180-11eb-9f08-a742b1037f90.png)
8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com/).
8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com).
</details>
1. Set a 32 character random string in your .env file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set a 32 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
```sh
yarn workspace @calcom/prisma db-deploy
```
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
```sh
docker pull mailhog/mailhog
docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
```
1. Run (in development mode)
@@ -206,7 +215,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
#### Setting up your first user
1. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content:
1. Open [Prisma Studio](https://prisma.io/studio) to look at or modify the database content:
```sh
yarn db-studio
@@ -251,7 +260,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
yarn workspace @calcom/prisma db-migrate
```
(this can clear your development database in some cases)
(This can clear your development database in some cases)
In a production environment, run:
@@ -285,7 +294,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
### Docker
The Docker configuration for Cal is an effort powered by people within the community.
The Docker configuration for Cal.com is an effort powered by people within the community.
If you want to contribute to the Docker repository, [reply here](https://github.com/calcom/docker/discussions/32).
@@ -298,14 +307,14 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
### Heroku
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
<img width="185px" height="auto" src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
<img width="185px" height="auto" src="https://herokucdn.com/deploy/button.svg" alt="Deploy">
</a>
### Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/cal)
You can deploy Cal on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal on their platform.
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
### Vercel
@@ -386,7 +395,7 @@ Don't code but still want to contribute? Join our [slack](https://cal.com/slack)
8. Select Web Application as the Application Type.
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
10. The key will be created and you will be redirected back to the Credentials page. Select the newly generated client ID under OAuth 2.0 Client IDs.
11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the .env file as the value for GOOGLE_API_CREDENTIALS key.
11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the `.env` file as the value for `GOOGLE_API_CREDENTIALS` key.
#### _Adding google calendar to Cal.com App Store_
@@ -423,7 +432,7 @@ following
5. Choose "User-managed app" as the app type.
6. De-select the option to publish the app on the Zoom App Marketplace.
7. Click "Create".
8. Now copy the Client ID and Client Secret to your .env file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields.
8. Now copy the Client ID and Client Secret to your `.env` file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields.
9. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zoomvideo/callback` replacing Cal.com URL with the URI at which your application runs.
10. Also add the redirect URL given above as a allow list URL and enable "Subdomain check". Make sure, it says "saved" below the form.
11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`.
@@ -435,8 +444,8 @@ following
1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
3. Copy your API key.
4. Now paste the API key to your .env file into the `DAILY_API_KEY` field in your .env file.
5. If you have the [Daily Scale Plan](https://www.daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
4. Now paste the API key to your `.env` file into the `DAILY_API_KEY` field in your `.env` file.
5. If you have the [Daily Scale Plan](https://daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
### Obtaining HubSpot Client ID and Secret
@@ -445,7 +454,7 @@ following
3. Click "Create app" button top right.
4. Fill in any information you want in the "App info" tab
5. Go to tab "Auth"
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
6. Now copy the Client ID and Client Secret to your `.env` file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot/callback` replacing Cal.com URL with the URI at which your application runs.
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
9. Click the "Save" button at the bottom footer.
@@ -458,31 +467,33 @@ following
3. Click "ADD CLIENT" button top right and select "Server-based Applications".
4. Fill in any information you want in the "Client Details" tab
5. Go to tab "Client Secret" tab.
6. Now copy the Client ID and Client Secret to your .env file into the `ZOHOCRM_CLIENT_ID` and `ZOHOCRM_CLIENT_SECRET` fields.
6. Now copy the Client ID and Client Secret to your `.env` file into the `ZOHOCRM_CLIENT_ID` and `ZOHOCRM_CLIENT_SECRET` fields.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zohocrm/callback` replacing Cal.com URL with the URI at which your application runs.
8. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
9. Click the "Save"/ "UPDATE" button at the bottom footer.
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
### Obtaining Zoho Bigin Client ID and Secret
[Follow these steps](./packages/app-store/zoho-bigin/)
## Workflows
### Setting up SendGrid for Email reminders
1. Create a SendGrid account (https://signup.sendgrid.com/)
2. Go to Settings -> API keys and create an API key
3. Copy API key to your .env file into the SENDGRID_API_KEY field
3. Copy API key to your `.env` file into the `SENDGRID_API_KEY` field
4. Go to Settings -> Sender Authentication and verify a single sender
5. Copy the verified E-Mail to your .env file into the SENDGRID_EMAIL field
6. Add your custom sender name to the .env file into the NEXT_PUBLIC_SENDGRID_SENDER_NAME field (fallback is Cal.com)
5. Copy the verified E-Mail to your `.env` file into the `SENDGRID_EMAIL` field
6. Add your custom sender name to the `.env` file into the `NEXT_PUBLIC_SENDGRID_SENDER_NAME` field (fallback is Cal.com)
### Setting up Twilio for SMS reminders
1. Create a Twilio account (https://www.twilio.com/try-twilio)
1. Create a Twilio account (https://twilio.com/try-twilio)
2. Click ‘Get a Twilio phone number’
3. Copy Account SID to your .env file into the TWILIO_SID field
4. Copy Auth Token to your .env file into the TWILIO_TOKEN field
5. Copy your Twilio phone number to your .env file into the TWILIO_PHONE_NUMBER field
6. Add your own sender id to the .env file into the NEXT_PUBLIC_SENDER_ID field (fallback is Cal)
3. Copy Account SID to your `.env` file into the `TWILIO_SID` field
4. Copy Auth Token to your `.env` file into the `TWILIO_TOKEN` field
5. Copy your Twilio phone number to your `.env` file into the `TWILIO_PHONE_NUMBER` field
6. Add your own sender id to the `.env` file into the `NEXT_PUBLIC_SENDER_ID` field (fallback is Cal.com)
7. Create a messaging service (Develop -> Messaging -> Services)
8. Choose any name for the messaging service
9. Click 'Add Senders'
@@ -490,15 +501,15 @@ following
11. Add the listed phone number
12. Leave all other fields as they are
13. Complete setup and click ‘View my new Messaging Service’
14. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field
14. Copy Messaging Service SID to your `.env` file into the `TWILIO_MESSAGING_SID` field
15. Create a verify service
16. Copy Verify Service SID to your .env file into the TWILIO_VERIFY_SID field
16. Copy Verify Service SID to your `.env` file into the `TWILIO_VERIFY_SID` field
<!-- LICENSE -->
## License
Distributed under the AGPLv3 License. See `LICENSE` for more information.
Distributed under the [AGPLv3 License](https://github.com/calcom/cal.com/blob/main/LICENSE). See `LICENSE` for more information.
<!-- ACKNOWLEDGEMENTS -->
+1 -1
View File
@@ -71,5 +71,5 @@ module.exports = {
return config;
},
typescript: { reactDocgen: 'react-docgen' }
typescript: { reactDocgen: "react-docgen" },
};
+264 -273
View File
@@ -1,5 +1,6 @@
import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
import classNames from "classnames";
import type { TFunction } from "next-i18next";
import type { NextRouter } from "next/router";
import { useRouter } from "next/router";
import type { MutableRefObject, RefObject } from "react";
@@ -21,11 +22,10 @@ import {
TextArea,
TextField,
ColorPicker,
Select,
} from "@calcom/ui";
import { Code, Trello, Sun, ArrowLeft } from "@calcom/ui/components/icon";
import Select from "@components/ui/form/Select";
type EmbedType = "inline" | "floating-popup" | "element-click";
type EmbedFramework = "react" | "HTML";
@@ -305,190 +305,189 @@ const getEmbedTypeSpecificString = ({
return "";
};
const embeds: {
illustration: React.ReactElement;
title: string;
subtitle: string;
type: EmbedType;
}[] = [
{
title: "Inline Embed",
subtitle: "Loads your event type directly inline with your other website content.",
type: "inline",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="24.5" y="51" width="139" height="163" rx="1.5" fill="#F8F8F8" />
<rect opacity="0.8" x="48" y="74.5" width="80" height="8" rx="6" fill="#F3F4F6" />
<rect x="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
<rect x="49" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="99.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="73" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="125.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<path
d="M61 124.5H67V122.5H61V124.5ZM68 125.5V131.5H70V125.5H68ZM67 132.5H61V134.5H67V132.5ZM60 131.5V125.5H58V131.5H60ZM61 132.5C60.4477 132.5 60 132.052 60 131.5H58C58 133.157 59.3431 134.5 61 134.5V132.5ZM68 131.5C68 132.052 67.5523 132.5 67 132.5V134.5C68.6569 134.5 70 133.157 70 131.5H68ZM67 124.5C67.5523 124.5 68 124.948 68 125.5H70C70 123.843 68.6569 122.5 67 122.5V124.5ZM61 122.5C59.3431 122.5 58 123.843 58 125.5H60C60 124.948 60.4477 124.5 61 124.5V122.5Z"
fill="#3E3E3E"
/>
<rect x="73" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="24.5" y="51" width="139" height="163" rx="6" stroke="#292929" />
<rect x="176" y="50.5" width="108" height="164" rx="6" fill="#F3F4F6" />
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
</svg>
),
},
{
title: "Floating pop-up button",
subtitle: "Adds a floating button on your site that launches Cal in a dialog.",
type: "floating-popup",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="24" y="50.5" width="120" height="76" rx="6" fill="#F3F4F6" />
<rect x="24" y="138.5" width="120" height="76" rx="6" fill="#F3F4F6" />
<rect x="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="226" y="223.5" width="66" height="26" rx="6" fill="#292929" />
<rect x="242" y="235.5" width="34" height="2" rx="1" fill="white" />
</svg>
),
},
{
title: "Pop up via element click",
subtitle: "Open your Cal dialog when someone clicks an element.",
type: "element-click",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
<g filter="url(#filter0_i_3223_14162)">
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
</g>
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
const embeds = (t: TFunction) =>
(() => {
return [
{
title: t("inline_embed"),
subtitle: t("load_inline_content"),
type: "inline",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="24.5" y="51" width="139" height="163" rx="1.5" fill="#F8F8F8" />
<rect opacity="0.8" x="48" y="74.5" width="80" height="8" rx="6" fill="#F3F4F6" />
<rect x="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
<rect x="49" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="99.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="73" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="125.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<path
d="M61 124.5H67V122.5H61V124.5ZM68 125.5V131.5H70V125.5H68ZM67 132.5H61V134.5H67V132.5ZM60 131.5V125.5H58V131.5H60ZM61 132.5C60.4477 132.5 60 132.052 60 131.5H58C58 133.157 59.3431 134.5 61 134.5V132.5ZM68 131.5C68 132.052 67.5523 132.5 67 132.5V134.5C68.6569 134.5 70 133.157 70 131.5H68ZM67 124.5C67.5523 124.5 68 124.948 68 125.5H70C70 123.843 68.6569 122.5 67 122.5V124.5ZM61 122.5C59.3431 122.5 58 123.843 58 125.5H60C60 124.948 60.4477 124.5 61 124.5V122.5Z"
fill="#3E3E3E"
/>
<rect x="73" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="24.5" y="51" width="139" height="163" rx="6" stroke="#292929" />
<rect x="176" y="50.5" width="108" height="164" rx="6" fill="#F3F4F6" />
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
</svg>
),
},
{
title: t("floating_pop_up_button"),
subtitle: t("floating_button_trigger_modal"),
type: "floating-popup",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="24" y="50.5" width="120" height="76" rx="6" fill="#F3F4F6" />
<rect x="24" y="138.5" width="120" height="76" rx="6" fill="#F3F4F6" />
<rect x="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="226" y="223.5" width="66" height="26" rx="6" fill="#292929" />
<rect x="242" y="235.5" width="34" height="2" rx="1" fill="white" />
</svg>
),
},
{
title: t("pop_up_element_click"),
subtitle: t("open_dialog_with_element_click"),
type: "element-click",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
<g filter="url(#filter0_i_3223_14162)">
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
</g>
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
<rect opacity="0.8" x="176" y="84" width="80" height="8" rx="2" fill="#E5E7EB" />
<rect x="176" y="96" width="48" height="4" rx="1" fill="#E5E7EB" />
<rect x="177" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="109" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="201" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="135" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="187.3" y="133.4" width="9" height="9" rx="1.5" stroke="#0D121D" />
<rect x="201" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="147" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="147" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="249" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="159" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="159" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="249" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="171" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="24" y="35" width="259" height="192" rx="5.5" stroke="#101010" />
<rect x="24" y="241.503" width="260" height="24" rx="6" fill="#F3F4F6" />
</svg>
),
},
];
})();
<rect opacity="0.8" x="176" y="84" width="80" height="8" rx="2" fill="#E5E7EB" />
<rect x="176" y="96" width="48" height="4" rx="1" fill="#E5E7EB" />
<rect x="177" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="109" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="201" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="135" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="187.3" y="133.4" width="9" height="9" rx="1.5" stroke="#0D121D" />
<rect x="201" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="237" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="249" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="147" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="147" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="249" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="159" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="159" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="249" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="261" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="177" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="189" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="201" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="213" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="225" y="171" width="6" height="6" rx="1" fill="#0D121D" />
<rect x="237" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
<rect x="24" y="35" width="259" height="192" rx="5.5" stroke="#101010" />
<rect x="24" y="241.503" width="260" height="24" rx="6" fill="#F3F4F6" />
</svg>
),
},
];
const tabs = [
{
name: "HTML",
@@ -534,11 +533,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
<!-- Cal ${embedType} embed code ends -->`
}
/>
<p className="text-subtle hidden text-sm">
{t(
"Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options."
)}
</p>
<p className="text-subtle hidden text-sm">{t("need_help_embedding")}</p>
</>
);
}),
@@ -622,7 +617,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
return (
<components.Control {...props}>
<Sun className="text-subtle ml-2 h-4 w-4" />
<Sun className="text-subtle mr-2 h-4 w-4" />
{children}
</components.Control>
);
@@ -642,7 +637,7 @@ const ChooseEmbedTypesDialogContent = () => {
</div>
</div>
<div className="items-start space-y-2 md:flex md:space-y-0">
{embeds.map((embed, index) => (
{embeds(t).map((embed, index) => (
<button
className="hover:bg-subtle bg-muted w-full rounded-md border border-transparent p-6 text-left hover:rounded-md ltr:mr-4 ltr:last:mr-0 rtl:ml-4 rtl:last:ml-0 lg:w-1/3"
key={index}
@@ -692,7 +687,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
});
const refOfEmbedCodesRefs = useRef(embedCodeRefs);
const embed = embeds.find((embed) => embed.type === embedType);
const embed = embeds(t).find((embed) => embed.type === embedType);
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
@@ -806,11 +801,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
const FloatingPopupPositionOptions = [
{
value: "bottom-right",
label: "Bottom Right",
label: "Bottom right",
},
{
value: "bottom-left",
label: "Bottom Left",
label: "Bottom left",
},
];
@@ -834,7 +829,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</button>
{embed.title}
</h3>
<h4 className="text-emphasis mb-6 text-sm font-normal">{embed.subtitle}</h4>
<h4 className="text-subtle mb-6 text-sm font-normal">{embed.subtitle}</h4>
<div className="flex flex-col">
<div className={classNames("font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
@@ -844,7 +839,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<div className={classNames(embedType === "inline" ? "block" : "hidden")}>
{/*TODO: Add Auto/Fixed toggle from Figma */}
<div className="text-default mb-[9px] text-sm">Window sizing</div>
<div className="justify-left flex items-center !font-normal">
<div className="justify-left mb-6 flex items-center !font-normal ">
<div className="mr-[9px]">
<TextField
labelProps={{ className: "hidden" }}
@@ -892,10 +887,10 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
"items-center justify-between",
embedType === "floating-popup" ? "text-emphasis" : "hidden"
)}>
<div className="mb-2 text-sm">Button Text</div>
<div className="mb-2 text-sm">Button text</div>
{/* Default Values should come from preview iframe */}
<TextField
labelProps={{ className: "hidden" }}
@@ -910,7 +905,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
};
});
}}
defaultValue="Book my Cal"
defaultValue={t("book_my_cal")}
required
/>
</div>
@@ -935,14 +930,14 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
});
}}
/>
<div className="text-default text-sm">Display Calendar Icon Button</div>
<div className="text-default my-2 text-sm">Display calendar icon</div>
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "text-emphasis" : "hidden"
)}>
<div className="mb-2">Position of Button</div>
<div className="mb-2">Position of button</div>
<Select
onChange={(position) => {
setPreviewState((previewState) => {
@@ -959,54 +954,50 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
options={FloatingPopupPositionOptions}
/>
</div>
<div
className={classNames(
"mt-4",
embedType === "floating-popup" ? "text-emphasis" : "hidden"
)}>
<div>Button Color</div>
<div className="w-full">
<ColorPicker
popoverAlign="start"
container={dialogContentRef?.current ?? undefined}
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonColor: color,
},
};
});
}}
/>
<div className="mt-3 flex flex-col xl:flex-row xl:justify-between">
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div className="whitespace-nowrap">Button color</div>
<div className="mt-2 w-40 xl:mt-0 xl:w-full">
<ColorPicker
className="w-[130px]"
popoverAlign="start"
container={dialogContentRef?.current ?? undefined}
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonColor: color,
},
};
});
}}
/>
</div>
</div>
</div>
<div
className={classNames(
"mt-4",
embedType === "floating-popup" ? "text-emphasis" : "hidden"
)}>
<div>Text Color</div>
<div className="w-full">
<ColorPicker
popoverAlign="start"
container={dialogContentRef?.current ?? undefined}
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonTextColor: color,
},
};
});
}}
/>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div className="whitespace-nowrap">Text color</div>
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
<ColorPicker
className="w-[130px]"
popoverAlign="start"
container={dialogContentRef?.current ?? undefined}
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonTextColor: color,
},
};
});
}}
/>
</div>
</div>
</div>
</CollapsibleContent>
@@ -1017,7 +1008,30 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
open={isBookingCustomizationOpen}
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleContent>
<div className="mt-6 text-sm">
<div className="text-sm">
<Label className="mb-6">
<div className="mb-2">Theme</div>
<Select
className="w-full"
defaultValue={ThemeOptions[0]}
components={{
Control: ThemeSelectControl,
IndicatorSeparator: () => null,
}}
onChange={(option) => {
if (!option) {
return;
}
setPreviewState((previewState) => {
return {
...previewState,
theme: option.value,
};
});
}}
options={ThemeOptions}
/>
</Label>
<div className="mb-6 flex items-center justify-start space-x-2 rtl:space-x-reverse">
<Switch
checked={previewState.hideEventTypeDetails}
@@ -1056,29 +1070,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
</Label>
))}
<Label>
<div className="mb-2">Theme</div>
<Select
className="w-full"
defaultValue={ThemeOptions[0]}
components={{
Control: ThemeSelectControl,
IndicatorSeparator: () => null,
}}
onChange={(option) => {
if (!option) {
return;
}
setPreviewState((previewState) => {
return {
...previewState,
theme: option.value,
};
});
}}
options={ThemeOptions}
/>
</Label>
</div>
</CollapsibleContent>
</Collapsible>
+1 -3
View File
@@ -1,5 +1,4 @@
import Link from "next/link";
import { useRouter } from "next/router";
import type { IframeHTMLAttributes } from "react";
import React, { useState } from "react";
@@ -43,7 +42,6 @@ const Component = ({
}: Parameters<typeof App>[0]) => {
const { t, i18n } = useLocale();
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
@@ -186,7 +184,7 @@ const Component = ({
label={t("disconnect")}
credentialId={existingCredentials[0]}
onSuccess={() => {
router.replace("/apps/installed");
appCredentials.refetch();
}}
/>
) : (
@@ -1,9 +1,9 @@
import { useMutation } from "@tanstack/react-query";
import Link from "next/link";
import { Fragment } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -12,12 +12,10 @@ import {
Button,
EmptyScreen,
List,
showToast,
AppSkeletonLoader as SkeletonLoader,
Switch,
ShellSubHeading,
} from "@calcom/ui";
import { ArrowLeft, Calendar, Plus } from "@calcom/ui/components/icon";
import { Calendar, Plus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -31,83 +29,6 @@ type Props = {
destinationCalendarId?: string;
};
function CalendarSwitch(props: {
type: string;
externalId: string;
title: string;
defaultSelected: boolean;
destination?: boolean;
}) {
const { t } = useLocale();
const utils = trpc.useContext();
const mutation = useMutation<
unknown,
unknown,
{
isOn: boolean;
}
>(
async ({ isOn }) => {
const body = {
integration: props.type,
externalId: props.externalId,
};
if (isOn) {
const res = await fetch("/api/availability/calendar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
} else {
const res = await fetch("/api/availability/calendar", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
}
},
{
async onSettled() {
await utils.viewer.integrations.invalidate();
},
onError() {
showToast(`Something went wrong when toggling "${props.title}""`, "error");
},
}
);
return (
<div className="flex flex-col py-1 sm:flex-row">
<Switch
key={props.externalId}
name="enabled"
label={props.title}
defaultChecked={props.defaultSelected}
onCheckedChange={(isOn: boolean) => {
mutation.mutate({ isOn });
}}
/>
{!!props.destination && (
<span className="bg-subtle text-default ml-8 inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm font-normal sm:ml-4">
<ArrowLeft className="h-4 w-4" />
{t("adding_events_to")}
</span>
)}
</div>
);
}
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
@@ -185,14 +106,15 @@ function ConnectedCalendarsList(props: Props) {
{!fromOnboarding && (
<>
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 px-5 py-4">
<ul className="space-y-4 px-5 py-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
defaultSelected={cal.isSelected}
isChecked={cal.isSelected}
destination={cal.externalId === props.destinationCalendarId}
/>
))}
@@ -1,4 +1,3 @@
import { UserPermissionRole } from "@prisma/client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import type { ComponentProps } from "react";
@@ -6,6 +5,7 @@ import React, { useEffect } from "react";
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
import type Shell from "@calcom/features/shell/Shell";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { ErrorBoundary } from "@calcom/ui";
export default function AdminLayout({
@@ -47,7 +47,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
src={eventLocationType.iconUrl}
className={classNames(
"ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert-[.65]" : ""
!eventLocationType.iconUrl?.startsWith("/app-store") ? "dark:invert-[.65]" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
@@ -11,7 +11,7 @@ import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { nameOfDay } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -72,6 +72,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
slotUtcStartDate: slot.time,
eventTypeId,
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
bookingAttendees: bookingAttendees || undefined,
});
};
@@ -1,10 +1,10 @@
import { SchedulingType } from "@prisma/client";
import type { FC, ReactNode } from "react";
import { useEffect } from "react";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums";
import { Badge } from "@calcom/ui";
import { CheckSquare, Clock } from "@calcom/ui/components/icon";
+14 -10
View File
@@ -1,4 +1,3 @@
import { BookingStatus } from "@prisma/client";
import { useRouter } from "next/router";
import { useState } from "react";
@@ -13,6 +12,7 @@ import { formatTime } from "@calcom/lib/date-fns";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { BookingStatus } from "@calcom/prisma/enums";
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { ActionType } from "@calcom/ui";
@@ -280,13 +280,13 @@ function BookingListItem(booking: BookingItemProps) {
isOpenDialog={isOpenSetLocationDialog}
setShowLocationModal={setIsOpenLocationDialog}
/>
{booking.paid && (
{booking.paid && booking.payment[0] && (
<ChargeCardDialog
isOpenDialog={chargeCardDialogIsOpen}
setIsOpenDialog={setChargeCardDialogIsOpen}
bookingId={booking.id}
paymentAmount={booking?.payment[0].amount}
paymentCurrency={booking?.payment[0].currency}
paymentAmount={booking.payment[0].amount}
paymentCurrency={booking.payment[0].currency}
/>
)}
{showRecordingsButtons && (
@@ -354,11 +354,15 @@ function BookingListItem(booking: BookingItemProps) {
{booking.eventType.team.name}
</Badge>
)}
{booking.paid && (
{booking.paid && !booking.payment[0] ? (
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
{t("error_collecting_card")}
</Badge>
) : booking.paid ? (
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
</Badge>
)}
) : null}
{recurringDates !== undefined && (
<div className="text-muted mt-2 text-sm">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
@@ -458,7 +462,7 @@ function BookingListItem(booking: BookingItemProps) {
<RequestSentMessage />
</div>
)}
{booking.status === "ACCEPTED" && booking.paid && booking?.payment[0]?.paymentOption === "HOLD" && (
{booking.status === "ACCEPTED" && booking.paid && booking.payment[0]?.paymentOption === "HOLD" && (
<div className="ml-2">
<TableActions actions={chargeCardActions} />
</div>
@@ -483,12 +487,12 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
i18n: { language },
} = useLocale();
const now = new Date();
const recurringCount = recurringDates.filter((date) => {
const recurringCount = recurringDates.filter((recurringDate) => {
return (
date >= now &&
recurringDate >= now &&
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(date.toDateString())
.includes(recurringDate.toDateString())
);
}).length;
@@ -17,7 +17,7 @@ export function TimezoneDropdown({
return (
<>
<div className="dark:focus-within:bg-darkgray-200 dark:bg-darkgray-100 dark:hover:bg-darkgray-200 !mt-3 flex w-full max-w-[20rem] items-center rounded-[4px] px-1 text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 lg:max-w-[12rem] [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white">
<div className="dark:focus-within:bg-darkgray-200 dark:hover:bg-darkgray-200 !mt-3 flex w-full max-w-[20rem] items-center rounded-[4px] px-1 text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 lg:max-w-[12rem] [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white">
<Globe className="dark:text-darkgray-600 flex h-4 w-4 text-gray-600 ltr:mr-2 rtl:ml-2" />
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
</div>
@@ -230,12 +230,14 @@ const BookingPage = ({
}),
{}
);
const reserveSlot = () => {
if (queryDuration) {
reserveSlotMutation.mutate({
eventTypeId: eventType.id,
slotUtcStartDate: dayjs(queryDate).utc().format(),
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
bookingAttendees: currentSlotBooking ? currentSlotBooking.attendees.length : undefined,
});
}
};
@@ -550,11 +552,11 @@ const BookingPage = ({
)}>
<div className="sm:flex">
{showEventTypeDetails && (
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="dark:text-inverted text-default items-start text-sm font-medium">
<div className="text-default items-start text-sm font-medium">
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
@@ -11,6 +11,10 @@ import { getEventName } from "@calcom/core/event";
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import {
allowDisablingAttendeeConfirmationEmails,
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
@@ -43,6 +47,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const bookingFields: Prisma.JsonObject = {};
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
eventType.bookingFields.forEach(({ name }) => {
bookingFields[name] = name + " input";
});
@@ -338,7 +344,51 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</>
)}
/>
{allowDisablingAttendeeConfirmationEmails(workflows) && (
<>
<hr className="border-subtle" />
<Controller
name="metadata.disableStandardEmails.confirmation.attendee"
control={formMethods.control}
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
title={t("disable_attendees_confirmation_emails")}
description={t("disable_attendees_confirmation_emails_description")}
checked={value || false}
onCheckedChange={(e) => {
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e);
onChange(e);
}}
/>
</>
)}
/>
</>
)}
{allowDisablingHostConfirmationEmails(workflows) && (
<>
<hr className="border-subtle" />
<Controller
name="metadata.disableStandardEmails.confirmation.host"
control={formMethods.control}
defaultValue={!!eventType.seatsPerTimeSlot}
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
title={t("disable_host_confirmation_emails")}
description={t("disable_host_confirmation_emails_description")}
checked={value || false}
onCheckedChange={(e) => {
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e);
onChange(e);
}}
/>
</>
)}
/>
</>
)}
{showEventNameTip && (
<CustomEventTypeModal
close={closeEventNameTip}
@@ -1,4 +1,3 @@
import { SchedulingType } from "@prisma/client";
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
@@ -10,6 +9,7 @@ import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hook
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
@@ -154,12 +154,11 @@ const EventTypeScheduleDetails = ({
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
const { t } = useLocale();
const { shouldLockIndicator, shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } =
useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { shouldLockIndicator, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { watch } = useFormContext<FormValues>();
const watchSchedule = watch("schedule");
const formMethods = useFormContext<FormValues>();
@@ -205,6 +204,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
const value = options.find((option) =>
scheduleId
? option.value === scheduleId
: isManagedEventType
? option.value === 0
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
);
@@ -13,7 +13,7 @@ import type { DurationType } from "@calcom/lib/convertToNewDurationType";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { PeriodType } from "@calcom/prisma/client";
import type { PeriodType } from "@calcom/prisma/enums";
import type { IntervalLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
import { Plus, Trash } from "@calcom/ui/components/icon";
@@ -276,7 +276,7 @@ export const EventSetupTab = (
return (
<li
key={`${location.type}${index}`}
className="border-default text-default mb-2 rounded-md border py-1.5 px-2 hover:cursor-pointer">
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
@@ -285,7 +285,7 @@ export const EventSetupTab = (
"h-4 w-4",
// invert all the icons except app icons
eventLocationType.iconUrl &&
!eventLocationType.iconUrl.startsWith("/api") &&
!eventLocationType.iconUrl.startsWith("/app-store") &&
"dark:invert"
)}
alt={`${eventLocationType.label} logo`}
@@ -466,6 +466,7 @@ export const EventSetupTab = (
defaultValue={eventType.length ?? 15}
{...formMethods.register("length")}
addOnSuffix={<>{t("minutes")}</>}
min={1}
/>
)}
{!lengthLockedProps.disabled && (
@@ -1,4 +1,3 @@
import { SchedulingType } from "@prisma/client";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef } from "react";
import type { ComponentProps } from "react";
@@ -10,6 +9,7 @@ import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTea
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums";
import { Label, Select } from "@calcom/ui";
interface IUserToValue {
@@ -1,4 +1,3 @@
import { SchedulingType } from "@prisma/client";
import { Webhook as TbWebhook } from "lucide-react";
import type { TFunction } from "next-i18next";
import { Trans } from "next-i18next";
@@ -13,6 +12,7 @@ import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import {
Button,
@@ -238,7 +238,7 @@ function EventTypeSingleLayout({
<div className="flex items-center justify-end">
{!eventType.metadata.managedEventConfig && (
<>
<div className="sm:hover:bg-subtle hidden items-center rounded-md px-2 lg:flex">
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 lg:flex">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
@@ -1,4 +1,4 @@
import { CalendarSwitch } from "./CalendarSwitch";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
interface IConnectedCalendarItem {
name: string;
@@ -1,85 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, showToast, Switch } from "@calcom/ui";
import { ArrowLeft } from "@calcom/ui/components/icon";
export function CalendarSwitch(props: {
type: string;
externalId: string;
title: string;
defaultSelected: boolean;
isSelected: boolean;
}) {
const { t } = useLocale();
const utils = trpc.useContext();
const mutation = useMutation<
unknown,
unknown,
{
isOn: boolean;
}
>(
async ({ isOn }) => {
const body = {
integration: props.type,
externalId: props.externalId,
};
if (isOn) {
const res = await fetch("/api/availability/calendar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
} else {
const res = await fetch("/api/availability/calendar", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
}
},
{
async onSettled() {
await utils.viewer.integrations.invalidate();
},
onError() {
showToast(`Something went wrong when toggling "${props.title}""`, "error");
},
}
);
return (
<div className="flex space-x-2 py-1 rtl:space-x-reverse">
<Switch
key={props.externalId}
name="enabled"
label={props.title}
defaultChecked={props.isSelected}
onCheckedChange={(isOn: boolean) => {
mutation.mutate({ isOn });
}}
/>
{props.defaultSelected && (
<Badge variant="gray">
<div className="flex items-center">
<ArrowLeft className="text-default mr-1 h-4 w-4" /> {t("adding_events_to")}
</div>
</Badge>
)}
</div>
);
}
@@ -25,7 +25,7 @@ const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
src={icon}
alt="cover"
// invert all the icons except app icons
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/api") && "dark:invert")}
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/app-store") && "dark:invert")}
/>
)}
<span className={classNames("text-sm font-medium")}>{label}</span>
+3 -3
View File
@@ -19,6 +19,8 @@ import { MetaProvider } from "@calcom/ui";
import usePublicPage from "@lib/hooks/usePublicPage";
import type { WithNonceProps } from "@lib/withNonce";
import { useViewerI18n } from "@components/I18nLanguageHandler";
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
({ children }) => <>{children}</>
);
@@ -46,9 +48,7 @@ const CustomI18nextProvider = (props: AppPropsWithChildren) => {
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
* We intend to not cache i18n query
**/
const { i18n, locale } = trpc.viewer.public.i18n.useQuery(undefined, {
trpc: { context: { skipBatch: true } },
}).data ?? {
const { i18n, locale } = useViewerI18n().data ?? {
locale: "en",
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.8.10",
"version": "2.8.13",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
+1
View File
@@ -23,6 +23,7 @@ class MyDocument extends Document<Props> {
// If x-csp not set by gSSP, then it's initialPropsOnly
setHeader(ctx, "x-csp", "initialPropsOnly");
}
const asPath = ctx.asPath || "";
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
const parsedUrl = new URL(asPath, "https://dummyurl");
+1 -1
View File
@@ -1,4 +1,3 @@
import { IdentityProvider } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
@@ -6,6 +5,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession({ req, res });
+1 -1
View File
@@ -1,4 +1,3 @@
import { IdentityProvider } from "@prisma/client";
import type { NextApiRequest } from "next";
import z from "zod";
@@ -8,6 +7,7 @@ import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
const querySchema = z.object({
username: z
+15 -4
View File
@@ -1,10 +1,10 @@
import { IdentityProvider } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
@@ -36,14 +36,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
// There is actually an existingUser if username matches
// OR if email matches and both username and password are set
// There is an existingUser if the username matches
// OR if the email matches AND either the email is verified
// or both username and password are set
const existingUser = await prisma.user.findFirst({
where: {
OR: [
{ username },
{
AND: [{ email: userEmail }],
AND: [
{ email: userEmail },
{
OR: [
{ emailVerified: { not: null } },
{
AND: [{ password: { not: null } }, { username: { not: null } }],
},
],
},
],
},
],
},
@@ -1,4 +1,3 @@
import { IdentityProvider } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { authenticator } from "otplib";
import qrcode from "qrcode";
@@ -8,6 +7,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import { symmetricEncrypt } from "@calcom/lib/crypto";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
+1 -1
View File
@@ -1,4 +1,3 @@
import { BookingStatus, ReminderType } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
@@ -7,6 +6,7 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus, ReminderType } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+4 -18
View File
@@ -1,4 +1,3 @@
import { UserPermissionRole } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
@@ -7,7 +6,7 @@ import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { TRPCError } from "@calcom/trpc/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerRouter } from "@calcom/trpc/server/routers/viewer";
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
enum DirectAction {
ACCEPT = "accept",
@@ -27,31 +26,18 @@ const decryptedSchema = z.object({
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
const { action, token, reason } = querySchema.parse(req.query);
const { bookingUid, userId } = decryptedSchema.parse(
const { bookingUid } = decryptedSchema.parse(
JSON.parse(symmetricDecrypt(decodeURIComponent(token), process.env.CALENDSO_ENCRYPTION_KEY || ""))
);
/** We shape the session as required by tRPC router */
async function sessionGetter() {
return {
user: {
id: userId,
username: "" /* Not used in this context */,
role: UserPermissionRole.USER,
},
hasValidLicense: true,
expires: "" /* Not used in this context */,
};
}
const booking = await prisma.booking.findUniqueOrThrow({
where: { uid: bookingUid },
});
try {
/** @see https://trpc.io/docs/server-side-calls */
const ctx = await createContext({ req, res }, sessionGetter);
const caller = viewerRouter.createCaller(ctx);
const ctx = await createContext({ req, res });
const caller = viewerRouter.createCaller({ ...ctx, req, res });
await caller.bookings.confirm({
bookingId: booking.id,
recurringEventId: booking.recurringEventId || undefined,
-83
View File
@@ -1,83 +0,0 @@
/**
* This file contains tRPC's HTTP response handler
*/
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import * as trpcNext from "@calcom/trpc/server/adapters/next";
import { createContext as createTrpcContext } from "@calcom/trpc/server/createContext";
import { appRouter } from "@calcom/trpc/server/routers/_app";
export default trpcNext.createNextApiHandler({
router: appRouter,
/**
* @link https://trpc.io/docs/context
*/
createContext: ({ req, res }) => {
const sessionGetter = () => getServerSession({ req, res });
return createTrpcContext({ req, res }, sessionGetter);
},
/**
* @link https://trpc.io/docs/error-handling
*/
onError({ error }) {
if (error.code === "INTERNAL_SERVER_ERROR") {
// send to bug reporting
console.error("Something went wrong", error);
}
},
/**
* Enable query batching
*/
batching: {
enabled: true,
},
/**
* @link https://trpc.io/docs/caching#api-response-caching
*/
responseMeta({ ctx, paths, type, errors }) {
// Some helpers relevant to this function only
const ONE_DAY_IN_SECONDS = 60 * 60 * 24;
// assuming you have all your public routes with the keyword `public` in them
const allPublic = paths && paths.every((path) => path.startsWith("viewer.public."));
// checking that no procedures errored
const allOk = errors.length === 0;
// checking we're doing a query request
const isQuery = type === "query";
const noHeaders = {};
// We cannot set headers on SSG queries
if (!ctx?.res) return noHeaders;
const defaultHeaders: Record<"headers", Record<string, string>> = {
headers: {},
};
const timezone = z.string().safeParse(ctx.req?.headers["x-vercel-ip-timezone"]);
if (timezone.success) defaultHeaders.headers["x-cal-timezone"] = timezone.data;
// We need all these conditions to be true to set cache headers
if (!(allPublic && allOk && isQuery)) return defaultHeaders;
// No cache by default
defaultHeaders.headers["cache-control"] = `no-cache`;
// Our cache can change depending on our current paths value. Since paths is an array,
// we want to create a map that can match potential paths with their desired cache value
const cacheRules = {
"viewer.public.session": `no-cache`,
"viewer.public.i18n": `no-cache`,
// Revalidation time here should be 1 second, per https://github.com/calcom/cal.com/pull/6823#issuecomment-1423215321
"viewer.public.slots.getSchedule": `no-cache`, // FIXME
"viewer.public.cityTimezones": `max-age=${ONE_DAY_IN_SECONDS}, stale-while-revalidate`,
} as const;
// Find which element above is an exact match for this group of paths
const matchedPath = paths.find((v) => v in cacheRules) as keyof typeof cacheRules;
if (matchedPath) defaultHeaders.headers["cache-control"] = cacheRules[matchedPath];
return defaultHeaders;
},
});
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
export default createNextApiHandler(apiKeysRouter);
@@ -0,0 +1,4 @@
import appRoutingForms from "@calcom/app-store/routing-forms/trpc-router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(appRoutingForms);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
export default createNextApiHandler(appsRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
export default createNextApiHandler(appsRouter);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { authRouter } from "@calcom/trpc/server/routers/viewer/auth/_router";
export default createNextApiHandler(authRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { availabilityRouter } from "@calcom/trpc/server/routers/viewer/availability/_router";
export default createNextApiHandler(availabilityRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { bookingsRouter } from "@calcom/trpc/server/routers/viewer/bookings/_router";
export default createNextApiHandler(bookingsRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { deploymentSetupRouter } from "@calcom/trpc/server/routers/viewer/deploymentSetup/_router";
export default createNextApiHandler(deploymentSetupRouter);
+4
View File
@@ -0,0 +1,4 @@
import ethRouter from "@calcom/app-store/rainbow/trpc/router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(ethRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { eventTypesRouter } from "@calcom/trpc/server/routers/viewer/eventTypes/_router";
export default createNextApiHandler(eventTypesRouter);
@@ -0,0 +1,4 @@
import { featureFlagRouter } from "@calcom/features/flags/server/router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(featureFlagRouter);
@@ -0,0 +1,4 @@
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(insightsRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { paymentsRouter } from "@calcom/trpc/server/routers/viewer/payments/_router";
export default createNextApiHandler(paymentsRouter);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { publicViewerRouter } from "@calcom/trpc/server/routers/publicViewer/_router";
export default createNextApiHandler(publicViewerRouter, true);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { ssoRouter } from "@calcom/trpc/server/routers/viewer/sso/_router";
export default createNextApiHandler(ssoRouter);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { slotsRouter } from "@calcom/trpc/server/routers/viewer/slots/_router";
export default createNextApiHandler(slotsRouter);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
export default createNextApiHandler(viewerTeamsRouter);
+4
View File
@@ -0,0 +1,4 @@
import { userAdminRouter } from "@calcom/features/ee/users/server/trpc-router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(userAdminRouter);
+4
View File
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { loggedInViewerRouter } from "@calcom/trpc/server/routers/loggedInViewer/_router";
export default createNextApiHandler(loggedInViewerRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { webhookRouter } from "@calcom/trpc/server/routers/viewer/webhook/_router";
export default createNextApiHandler(webhookRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { workflowsRouter } from "@calcom/trpc/server/routers/viewer/workflows/_router";
export default createNextApiHandler(workflowsRouter);
+6 -3
View File
@@ -6,6 +6,7 @@ import path from "path";
import { z } from "zod";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -108,9 +109,11 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
if (data.items) {
data.items = data.items.map((item) => {
if (typeof item === "string" && !item.includes("/api/app-store")) {
// Make relative paths absolute
return `/api/app-store/${appDirname}/${item}`;
if (typeof item === "string") {
return getAppAssetFullPath(item, {
dirName: singleApp.dirName,
isTemplate: singleApp.isTemplate,
});
}
return item;
});
+8 -1
View File
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import { HeadSeo } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
@@ -25,7 +26,13 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
});
}
return <AppSetupPage slug={slug} {...props} />;
return (
<>
{/* So that the set up page does not get indexed by search engines */}
<HeadSeo nextSeoProps={{ noindex: true, nofollow: true }} title={`${slug} | Cal.com`} description="" />
<AppSetupPage slug={slug} {...props} />
</>
);
}
SetupInformation.PageWrapper = PageWrapper;
@@ -1,4 +1,3 @@
import { AppCategories } from "@prisma/client";
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -7,6 +6,7 @@ import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { AppCategories } from "@calcom/prisma/enums";
import { AppCard, SkeletonText } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
+2 -2
View File
@@ -6,7 +6,7 @@ import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/client";
import type { AppCategories } from "@calcom/prisma/enums";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import type { HorizontalTabItemProps } from "@calcom/ui";
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
@@ -40,7 +40,7 @@ function AppsSearch({
className="bg-subtle !border-muted !pl-0 focus:!ring-offset-0"
addOnLeading={<Search className="text-subtle h-4 w-4" />}
addOnClassname="!border-muted"
containerClassName={classNames("focus:!ring-offset-0", className)}
containerClassName={classNames("focus:!ring-offset-0 m-1", className)}
type="search"
autoComplete="false"
onChange={onChange}
+1 -1
View File
@@ -1,4 +1,3 @@
import { UserPermissionRole } from "@prisma/client";
import type { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
@@ -8,6 +7,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Meta, WizardForm } from "@calcom/ui";
+1 -1
View File
@@ -188,7 +188,7 @@ export default function Availability() {
}
CTA={
<div className="flex items-center justify-end">
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 sm:flex">
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
{t("set_to_default")}
</Skeleton>
+5 -3
View File
@@ -1,4 +1,3 @@
import { BookingStatus } from "@prisma/client";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import classNames from "classnames";
import { createEvent } from "ics";
@@ -28,6 +27,7 @@ import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booki
import {
SystemField,
getBookingFieldsWithSystemFields,
SMS_REMINDER_NUMBER_FIELD,
} from "@calcom/features/bookings/lib/getBookingFields";
import { parseRecurringEvent } from "@calcom/lib";
import { APP_NAME } from "@calcom/lib/constants";
@@ -46,6 +46,7 @@ import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/t
import { localStorage } from "@calcom/lib/webstorage";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme } from "@calcom/ui";
@@ -308,7 +309,7 @@ export default function Success(props: SuccessProps) {
<div className="mt-2 ml-4 -mb-4">
<Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="hover:bg-subtle text-subtle dark:hover:text-inverted hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
<ChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</Link>
</div>
@@ -487,7 +488,8 @@ export default function Success(props: SuccessProps) {
// We show rescheduleReason at the top
if (!field) return null;
const isSystemField = SystemField.safeParse(field.name);
if (isSystemField.success) return null;
// SMS_REMINDER_NUMBER_FIELD is a system field but doesn't have a dedicated place in the UI. So, it would be shown through the following responses list
if (isSystemField.success && field.name !== SMS_REMINDER_NUMBER_FIELD) return null;
const label = field.label || t(field.defaultLabel || "");
+8 -1
View File
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import type { PeriodType, SchedulingType } from "@prisma/client";
import type { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useEffect, useState } from "react";
@@ -19,6 +18,7 @@ import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { Prisma } from "@calcom/prisma/client";
import type { PeriodType, SchedulingType } from "@calcom/prisma/enums";
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { RouterOutputs } from "@calcom/trpc/react";
@@ -136,6 +136,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
onSuccess: async () => {
formMethods.setValue(
"children",
formMethods.getValues().children.map((child) => ({
...child,
created: true,
}))
);
showToast(
t("event_type_updated_successfully", {
eventTypeTitle: eventType.title,
+82 -77
View File
@@ -1,6 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import type { User } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import { Trans } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -18,6 +17,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { SchedulingType } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import {
@@ -352,6 +352,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
const firstItem = types[0];
const lastItem = types[types.length - 1];
const isManagedEventPrefix = () => {
return deleteDialogTypeSchedulingType === SchedulingType.MANAGED ? "_managed" : "";
};
return (
<div className="bg-default border-subtle mb-16 flex overflow-hidden rounded-md border">
<ul ref={parent} className="divide-subtle !static w-full divide-y" data-testid="event-types">
@@ -501,7 +504,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
)}
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
isManagedEventType && (
!isChildrenManagedEventType && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem>
@@ -533,27 +536,31 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem className="outline-none">
<DropdownItem
href={calLink}
target="_blank"
StartIcon={ExternalLink}
className="w-full rounded-none">
{t("preview")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
<DropdownItem
data-testid={"event-type-duplicate-" + type.id}
onClick={() => {
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
}}
StartIcon={Clipboard}
className="w-full rounded-none text-left">
{t("copy_link")}
</DropdownItem>
</DropdownMenuItem>
{!isManagedEventType && (
<>
<DropdownMenuItem className="outline-none">
<DropdownItem
href={calLink}
target="_blank"
StartIcon={ExternalLink}
className="w-full rounded-none">
{t("preview")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
<DropdownItem
data-testid={"event-type-duplicate-" + type.id}
onClick={() => {
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
}}
StartIcon={Clipboard}
className="w-full rounded-none text-left">
{t("copy_link")}
</DropdownItem>
</DropdownMenuItem>
</>
)}
{isNativeShare ? (
<DropdownMenuItem className="outline-none">
<DropdownItem
@@ -574,36 +581,46 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</DropdownItem>
</DropdownMenuItem>
) : null}
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => router.push("/event-types/" + type.id)}
StartIcon={Edit}
className="w-full rounded-none">
{t("edit")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => openDuplicateModal(type, group)}
StartIcon={Copy}
data-testid={"event-type-duplicate-" + type.id}>
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="outline-none">
<DropdownItem
color="destructive"
onClick={() => {
setDeleteDialogOpen(true);
setDeleteDialogTypeId(type.id);
setDeleteDialogSchedulingType(type.schedulingType);
}}
StartIcon={Trash}
className="w-full rounded-none">
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
{!readOnly && (
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => router.push("/event-types/" + type.id)}
StartIcon={Edit}
className="w-full rounded-none">
{t("edit")}
</DropdownItem>
</DropdownMenuItem>
)}
{!isManagedEventType && !isChildrenManagedEventType && (
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => openDuplicateModal(type, group)}
StartIcon={Copy}
data-testid={"event-type-duplicate-" + type.id}>
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
)}
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
!isChildrenManagedEventType && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem className="outline-none">
<DropdownItem
color="destructive"
onClick={() => {
setDeleteDialogOpen(true);
setDeleteDialogTypeId(type.id);
setDeleteDialogSchedulingType(type.schedulingType);
}}
StartIcon={Trash}
className="w-full rounded-none">
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenuPortal>
</Dropdown>
@@ -616,36 +633,24 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
variety="danger"
title={t(
`delete_${deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"}_event_type`
)}
confirmBtnText={t(
`confirm_delete_${
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
}_event_type`
)}
loadingText={t(
`confirm_delete_${
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
}_event_type`
)}
title={t(`delete${isManagedEventPrefix()}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => {
e.preventDefault();
deleteEventTypeHandler(deleteDialogTypeId);
}}>
<p className="mt-5">
{t(
`delete_${
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
}_event_type_description`
)}
</p>
<p className="mt-5">
<Trans
i18nKey={`delete_${
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
}_event_type_warning`}
/>
i18nKey={`delete${isManagedEventPrefix()}_event_type_description`}
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
<ul>
<li>Members assigned to this event type will also have their event types deleted.</li>
<li>
Anyone who they&apos;ve shared their link with will no longer be able to book using it.
</li>
</ul>
</Trans>
</p>
</ConfirmationDialogContent>
</Dialog>
+1 -1
View File
@@ -15,7 +15,7 @@ type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away }: PageProps) {
return (
<main className="flex justify-center">
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
</main>
);
@@ -14,7 +14,7 @@ type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away }: PageProps) {
return (
<main className="flex justify-center">
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
</main>
);
@@ -146,6 +146,7 @@ const AppearanceView = () => {
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
<ColorPicker
defaultValue={user.brandColor}
resetDefaultValue="#292929"
onChange={(value) => {
if (!checkWCAGContrastColor("#ffffff", value)) {
setLightModeError(true);
@@ -167,6 +168,7 @@ const AppearanceView = () => {
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
<ColorPicker
defaultValue={user.darkBrandColor}
resetDefaultValue="#fafafa"
onChange={(value) => {
if (!checkWCAGContrastColor("#101010", value)) {
setDarkModeError(true);
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { Fragment } from "react";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { classNames } from "@calcom/lib";
@@ -29,7 +30,6 @@ import { Plus, Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import PageWrapper from "@components/PageWrapper";
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
const SkeletonLoader = () => {
return (
@@ -178,15 +178,17 @@ const CalendarsView = () => {
</div>
<div className="border-subtle w-full border-t">
<p className="text-subtle px-2 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 p-4">
<ul className="space-y-4 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isSelected={cal.isSelected}
defaultSelected={cal.externalId === data?.destinationCalendar?.externalId}
isChecked={
cal.isSelected || cal.externalId === data?.destinationCalendar?.externalId
}
/>
))}
</ul>
@@ -24,7 +24,7 @@ import {
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import { AlertCircle, MoreHorizontal, Trash, Video } from "@calcom/ui/components/icon";
import { AlertCircle, MoreHorizontal, Trash, Video, Plus } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
import PageWrapper from "@components/PageWrapper";
@@ -41,6 +41,18 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
);
};
const AddConferencingButton = () => {
const { t } = useLocale();
return (
<>
<Button color="secondary" StartIcon={Plus} href="/apps/categories/video">
{t("add_conferencing_app")}
</Button>
</>
);
};
const ConferencingLayout = () => {
const { t } = useLocale();
const utils = trpc.useContext();
@@ -91,7 +103,11 @@ const ConferencingLayout = () => {
return (
<div className="bg-default w-full sm:mx-0 xl:mt-0">
<Meta title={t("conferencing")} description={t("conferencing_description")} />
<Meta
title={t("conferencing")}
description={t("conferencing_description")}
CTA={<AddConferencingButton />}
/>
<List>
{apps?.items &&
apps.items
@@ -1,5 +1,4 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { IdentityProvider } from "@prisma/client";
import { signOut } from "next-auth/react";
import type { BaseSyntheticEvent } from "react";
import { useRef, useState } from "react";
@@ -13,6 +12,7 @@ import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import turndown from "@calcom/lib/turndownService";
import { IdentityProvider } from "@calcom/prisma/enums";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
@@ -1,10 +1,10 @@
import { IdentityProvider } from "@prisma/client";
import { signOut, useSession } from "next-auth/react";
import { useForm } from "react-hook-form";
import { identityProviderNameMap } from "@calcom/features/auth/lib/identityProviderNameMap";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { IdentityProvider } from "@calcom/prisma/enums";
import { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Alert, Button, Form, Meta, PasswordField, Select, SettingsToggle, showToast } from "@calcom/ui";
@@ -199,6 +199,7 @@ const PasswordView = () => {
color="primary"
className="mt-8"
type="submit"
onClick={() => formMethods.clearErrors("apiError")}
disabled={isDisabled || passwordMutation.isLoading || sessionMutation.isLoading}>
{t("update")}
</Button>
@@ -46,10 +46,10 @@ const TwoFactorAuthView = () => {
user?.twoFactorEnabled ? setDisableModalOpen(true) : setEnableModalOpen(true)
}
/>
<div>
<div className="!mx-4">
<div className="flex">
<p className="text-default font-semibold">{t("two_factor_auth")}</p>
<Badge className="ml-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
<Badge className="mx-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
</Badge>
</div>
+1 -1
View File
@@ -263,7 +263,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
);
}
VideoMeetingInfo.PageWrapper = PageWrapper;
JoinCall.PageWrapper = PageWrapper;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res } = context;
+1
View File
@@ -12,6 +12,7 @@ test.describe("App Store - Authed", () => {
await pro.login();
await page.goto("/apps/categories/calendar");
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
await page.waitForURL("/apps/apple-calendar");
await page.click('[data-testid="install-app-button"]');
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
});
@@ -19,7 +19,10 @@ test("Can delete user account", async ({ page, users }) => {
const $passwordField = page.locator("[data-testid=password]");
await $passwordField.fill(user.username);
await Promise.all([page.waitForNavigation({ url: "/auth/logout" }), page.click("text=Delete my account")]);
await Promise.all([
page.waitForURL((url) => url.pathname === "/auth/logout"),
page.click("text=Delete my account"),
]);
await expect(page.locator(`[id="modal-title"]`)).toHaveText("You've been logged out");
});
@@ -17,9 +17,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// Press Enter
await Promise.all([
page.waitForNavigation({
url: (u) => u.pathname.startsWith("/auth/forgot-password/"),
}),
page.waitForURL((u) => u.pathname.startsWith("/auth/forgot-password/")),
page.press('input[name="email"]', "Enter"),
]);
@@ -38,7 +36,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
await expect(page.locator(`text=Password updated`)).toBeVisible();
// Click button:has-text("Login")
await Promise.all([
page.waitForNavigation({ url: (u) => u.pathname.startsWith("/auth/login") }),
page.waitForURL((u) => u.pathname.startsWith("/auth/login")),
page.click('a:has-text("Login")'),
]);
+1 -1
View File
@@ -33,7 +33,7 @@ test.describe("Availablity tests", () => {
});
await test.step("Date override is displayed in troubleshooter", async () => {
const response = await page.waitForResponse("**/api/trpc/viewer.availability.schedule.update?batch=1");
const response = await page.waitForResponse("**/api/trpc/availability/schedule.update?batch=1");
const json = await response.json();
// @ts-expect-error trust me bro
const date = json[0].result.data.json.schedule.availability.find((a) => !!a.date);
+16 -19
View File
@@ -31,10 +31,8 @@ testBothBookers.describe("free user", (bookerVariant) => {
// eslint-disable-next-line playwright/no-conditional-in-test
if (bookerVariant !== "new-booker") {
// Navigate to book page
await page.waitForNavigation({
url(url) {
return url.pathname.endsWith("/book");
},
await page.waitForURL((url) => {
return url.pathname.endsWith("/book");
});
}
@@ -84,23 +82,24 @@ testBothBookers.describe("pro user", () => {
await page.waitForSelector('[data-testid="bookings"]');
await page.locator('[data-testid="edit_booking"]').nth(0).click();
await page.locator('[data-testid="reschedule"]').click();
await page.waitForNavigation({
url: (url) => {
const bookingId = url.searchParams.get("rescheduleUid");
return !!bookingId;
},
await page.waitForURL((url) => {
const bookingId = url.searchParams.get("rescheduleUid");
return !!bookingId;
});
await selectSecondAvailableTimeSlotNextMonth(page);
// --- fill form
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({
url(url) {
return url.pathname.startsWith("/booking");
},
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking");
});
});
test("Can cancel the recently created booking and rebook the same timeslot", async ({ page, users }) => {
test("Can cancel the recently created booking and rebook the same timeslot", async ({
page,
users,
}, testInfo) => {
// Because it tests the entire booking flow + the cancellation + rebooking
test.setTimeout(testInfo.timeout * 3);
await bookFirstEvent(page);
const [pro] = users.get();
@@ -108,10 +107,8 @@ testBothBookers.describe("pro user", () => {
await page.goto("/bookings/upcoming");
await page.locator('[data-testid="cancel"]').first().click();
await page.waitForNavigation({
url: (url) => {
return url.pathname.startsWith("/booking");
},
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking/");
});
await page.locator('[data-testid="cancel"]').click();
@@ -134,7 +131,7 @@ testBothBookers.describe("pro user", () => {
await page.goto("/bookings/unconfirmed");
await Promise.all([
page.click('[data-testid="confirm"]'),
page.waitForResponse((response) => response.url().includes("/api/trpc/viewer.bookings.confirm")),
page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm")),
]);
// This is the only booking in there that needed confirmation and now it should be empty screen
await expect(page.locator('[data-testid="empty-screen"]')).toBeVisible();
+5 -6
View File
@@ -1,9 +1,9 @@
import { expect } from "@playwright/test";
import type { Prisma } from "@prisma/client";
import { BookingStatus } from "@prisma/client";
import { v4 as uuidv4 } from "uuid";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import type { Fixtures } from "./lib/fixtures";
import { test } from "./lib/fixtures";
@@ -71,10 +71,8 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
// to change the test as little as possible.
// eslint-disable-next-line playwright/no-conditional-in-test
if (bookerVariant === "old-booker") {
await page.waitForNavigation({
url(url) {
return url.pathname.endsWith("/book");
},
await page.waitForURL((url) => {
return url.pathname.endsWith("/book");
});
}
@@ -192,7 +190,7 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({ url: /.*booking/ });
await page.waitForURL(/.*booking/);
await page.goto(`/reschedule/${references[1].referenceUid}`);
@@ -200,6 +198,7 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled
await page.waitForNavigation({ url: /.*booking/ });
// Should expect old booking to be cancelled
@@ -31,19 +31,15 @@ test.skip("dynamic booking", async ({ page, users }) => {
await page.goto("/bookings/upcoming");
await page.locator('[data-testid="edit_booking"]').nth(0).click();
await page.locator('[data-testid="reschedule"]').click();
await page.waitForNavigation({
url: (url) => {
const bookingId = url.searchParams.get("rescheduleUid");
return !!bookingId;
},
await page.waitForURL((url) => {
const bookingId = url.searchParams.get("rescheduleUid");
return !!bookingId;
});
await selectSecondAvailableTimeSlotNextMonth(page);
// --- fill form
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({
url(url) {
return url.pathname.startsWith("/booking");
},
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking");
});
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
});
@@ -51,10 +47,8 @@ test.skip("dynamic booking", async ({ page, users }) => {
await test.step("Can cancel the recently created booking", async () => {
await page.goto("/bookings/upcoming");
await page.locator('[data-testid="cancel"]').first().click();
await page.waitForNavigation({
url: (url) => {
return url.pathname.startsWith("/booking");
},
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking");
});
await page.locator('[data-testid="cancel"]').click();
+15 -21
View File
@@ -36,14 +36,12 @@ async function expectToBeNavigatingToEmbedTypesDialog(
if (!embedUrl) {
throw new Error("Couldn't find embedUrl");
}
await page.waitForNavigation({
url: (url) => {
return (
url.pathname === basePage &&
url.searchParams.get("dialog") === "embed" &&
url.searchParams.get("embedUrl") === embedUrl
);
},
await page.waitForURL((url) => {
return (
url.pathname === basePage &&
url.searchParams.get("dialog") === "embed" &&
url.searchParams.get("embedUrl") === embedUrl
);
});
}
@@ -54,16 +52,14 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
if (!embedUrl) {
throw new Error("Couldn't find embedUrl");
}
await page.waitForNavigation({
url: (url) => {
return (
url.pathname === basePage &&
url.searchParams.get("dialog") === "embed" &&
url.searchParams.get("embedUrl") === embedUrl &&
url.searchParams.get("embedType") === embedType &&
url.searchParams.get("embedTabName") === "embed-code"
);
},
await page.waitForURL((url) => {
return (
url.pathname === basePage &&
url.searchParams.get("dialog") === "embed" &&
url.searchParams.get("embedUrl") === embedUrl &&
url.searchParams.get("embedType") === embedType &&
url.searchParams.get("embedTabName") === "embed-code"
);
});
}
@@ -183,9 +179,7 @@ test.describe("Embed Code Generator Tests", () => {
await page.goto(`/event-types`);
await Promise.all([
page.locator('[href*="/event-types/"]').first().click(),
page.waitForNavigation({
url: (url) => url.pathname.startsWith("/event-types/"),
}),
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
]);
});
+6 -12
View File
@@ -94,10 +94,8 @@ test.describe("Event Types tests", () => {
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.waitForURL((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']");
@@ -108,10 +106,8 @@ test.describe("Event Types tests", () => {
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.waitForURL((url) => {
return !!url.pathname.match(/\/event-types\/.+/);
});
const locationData = ["location 1", "location 2", "location 3"];
@@ -153,10 +149,8 @@ test.describe("Event Types tests", () => {
// to change the test as little as possible.
// eslint-disable-next-line playwright/no-conditional-in-test
if (bookerVariant === "old-booker") {
await page.waitForNavigation({
url(url) {
return url.pathname.endsWith("/book");
},
await page.waitForURL((url) => {
return url.pathname.endsWith("/book");
});
}
+4 -3
View File
@@ -1,11 +1,12 @@
import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { MembershipRole, Prisma as PrismaType } from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client";
import { hashSync as hash } from "bcryptjs";
import dayjs from "@calcom/dayjs";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TimeZoneEnum } from "./types";
@@ -345,12 +346,12 @@ export async function getPaymentCredential(page: Page) {
/** We start the Stripe flow */
await Promise.all([
page.waitForNavigation({ url: "https://connect.stripe.com/oauth/v2/authorize?*" }),
page.waitForURL("https://connect.stripe.com/oauth/v2/authorize?*"),
page.click('[data-testid="install-app-button"]'),
]);
await Promise.all([
page.waitForNavigation({ url: "/apps/installed/payment?hl=stripe" }),
page.waitForURL("/apps/installed/payment?hl=stripe"),
/** We skip filling Stripe forms (testing mode only) */
page.click('[id="skip-account-app"]'),
]);
@@ -49,7 +49,7 @@ test.describe("Stripe integration", () => {
await page.fill('[name="name"]', "Stripe Stripeson");
await page.fill('[name="email"]', "test@example.com");
await Promise.all([page.waitForNavigation({ url: "/payment/*" }), page.press('[name="email"]', "Enter")]);
await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]);
const stripeFrame = page
.frameLocator('iframe[src^="https://js.stripe.com/v3/elements-inner-card-"]')
+4 -8
View File
@@ -114,10 +114,8 @@ async function bookEventOnThisPage(page: Page) {
await bookTimeSlot(page);
// Make sure we're navigated to the success page
await page.waitForNavigation({
url(url) {
return url.pathname.startsWith("/booking");
},
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking");
});
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
}
@@ -156,10 +154,8 @@ export const createNewEventType = async (page: Page, args: { eventTitle: string
await page.fill("[name=length]", "10");
await page.click("[type=submit]");
await page.waitForNavigation({
url(url) {
return url.pathname !== "/event-types";
},
await page.waitForURL((url) => {
return url.pathname !== "/event-types";
});
};
@@ -1,10 +1,10 @@
import type { Locator, Page, PlaywrightTestArgs } from "@playwright/test";
import { expect } from "@playwright/test";
import { WebhookTriggerEvents } from "@prisma/client";
import type { createUsersFixture } from "playwright/fixtures/users";
import { uuid } from "short-uuid";
import prisma from "@calcom/prisma";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { test } from "./lib/fixtures";
import { testBothBookers } from "./lib/new-booker";
@@ -362,8 +362,8 @@ async function rescheduleFromTheLinkOnPage({
await page.waitForLoadState();
await selectFirstAvailableTimeSlotNextMonth(page);
if (bookerVariant === "old-booker") {
await page.waitForNavigation({
url: (url) => url.pathname.endsWith("/book"),
await page.waitForURL((url) => {
return url.pathname.endsWith("/book");
});
}
await page.click('[data-testid="confirm-reschedule-button"]');
@@ -380,8 +380,8 @@ async function openBookingFormInPreviewTab(
await previewTabPage.waitForLoadState();
await selectFirstAvailableTimeSlotNextMonth(previewTabPage);
if (bookerVariant === "old-booker") {
await previewTabPage.waitForNavigation({
url: (url) => url.pathname.endsWith("/book"),
await previewTabPage.waitForURL((url) => {
return url.pathname.endsWith("/book");
});
}
return previewTabPage;
+3 -5
View File
@@ -1,7 +1,7 @@
import { expect } from "@playwright/test";
import { BookingStatus } from "@prisma/client";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { test } from "./lib/fixtures";
import { testBothBookers } from "./lib/new-booker";
@@ -149,10 +149,8 @@ testBothBookers.describe("Reschedule Tests", async () => {
await page.locator('[data-testid="confirm-reschedule-button"]').click();
await page.waitForNavigation({
url(url) {
return url.pathname.indexOf("/payment") > -1;
},
await page.waitForURL((url) => {
return url.pathname.indexOf("/payment") > -1;
});
await expect(page).toHaveURL(/.*payment/);
+1 -3
View File
@@ -24,9 +24,7 @@ test("add webhook & test that creating an event triggers a webhook call", async
await Promise.all([
page.click("[type=submit]"),
page.waitForNavigation({
url: (url) => url.pathname.endsWith("/settings/developer/webhooks"),
}),
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
]);
// page contains the url
+135 -27
View File
@@ -1,4 +1,5 @@
{
"identity_provider": "موفر الهوية",
"trial_days_left": "يتبقى لديك $t(day, {\"count\": {{days}} }) في الفترة التجريبية من PRO",
"day_one": "{{count}} يوم",
"day_other": "{{count}} يوم",
@@ -70,6 +71,7 @@
"event_still_awaiting_approval": "الحدث لا يزال في انتظار موافقتك",
"booking_submitted_subject": "تم إرسال الحجز: {{title}} في {{date}}",
"download_recording_subject": "تنزيل التسجيل: {{title}} في {{date}}",
"download_your_recording": "تنزيل تسجيلك",
"your_meeting_has_been_booked": "لقد تم حجز الاجتماع الخاص بك",
"event_type_has_been_rescheduled_on_time_date": "تم إعادة جدولة {{title}} إلى {{date}}.",
"event_has_been_rescheduled": "تم التحديث - تم إعادة جدولة الحدث الخاص بك",
@@ -112,7 +114,7 @@
"hidden_team_owner_message": "تحتاج إلى حساب Pro لاستخدام خدمة الفرق، وأنت حاليًا في الوضع الخفي حتى تقوم بالترقية.",
"link_expires": "ملاحظة: تنتهي الصلاحية في غضون {{expiresIn}} من الساعات.",
"upgrade_to_per_seat": "الترقية إلى \"كل مقعد بشكل منفرد\"",
"seat_options_doesnt_support_confirmation": "لا يدعم خيار المقاعد متطلبات التأكيد",
"seat_options_doesnt_support_confirmation": "الحجز الذي يتطلب التأكيد لا يدعم خيار حجز المقاعد",
"team_upgrade_seats_details": "من بين {{memberCount}} من الأعضاء في فريقك، لم يتم الدفع مقابل {{unpaidCount}} من المقاعد. بما أن سعر المقعد ${{seatPrice}} شهريًا، تبلغ التكلفة الإجمالية المقدرة لعضويتك ${{totalCost}} شهريًا.",
"team_upgrade_banner_description": "شكراً لك على تجربة خطة الفريق الجديدة التي نقدمها. لقد لاحظنا أن فريقك \"{{teamName}}\" بحاجة إلى الترقية.",
"team_upgrade_banner_action": "قم بالترقية هنا",
@@ -225,6 +227,7 @@
"done": "تم",
"all_done": "اكتمل كل شيء!",
"all_apps": "الكل",
"available_apps": "التطبيقات المتوفرة",
"check_email_reset_password": "تحقق من البريد الإلكتروني. لقد أرسلنا رابطًا لإعادة تعيين كلمة المرور.",
"finish": "إنهاء",
"few_sentences_about_yourself": "اكتب بضع جمل عن نفسك. سيظهر هذا على صفحة عنوان URL الشخصية لديك.",
@@ -405,6 +408,7 @@
"add_time_availability": "إضافة فتحة زمنية جديدة",
"add_an_extra_layer_of_security": "أضف إلى حسابك طبقة أمان إضافية للاحتياط من سرقة كلمة المرور الخاصة بك.",
"2fa": "المصادقة من عاملين",
"2fa_disabled": "يمكن تمكين المصادقة الثنائية فقط لمصادقة البريد الإلكتروني وكلمة المرور",
"enable_2fa": "تمكين المصادقة من عاملين",
"disable_2fa": "تعطيل المصادقة من عاملين",
"disable_2fa_recommendation": "إذا كنت بحاجة إلى تعطيل المصادقة من عاملين، فنوصيك بإعادة تمكينها في أقرب وقت ممكن.",
@@ -434,7 +438,7 @@
"password_hint_min": "7 أحرف كحد أدنى",
"password_hint_admin_min": "15 محرفًا كحد أدنى",
"password_hint_num": "يحتوي على رقم واحد على الأقل",
"max_limit_allowed_hint": "يجب أن يكون بعدد محارف هو {{limit}} أو أقل",
"max_limit_allowed_hint": "يجب أن يكون عدد الأحرف {{limit}} أو أقل",
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من {{passwordLength}} أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
"incorrect_password": "كلمة المرور غير صحيحة.",
"incorrect_username_password": "اسم المستخدم أو كلمة المرور غير صحيحة.",
@@ -461,7 +465,6 @@
"friday": "الجمعة",
"saturday": "السبت",
"sunday": "الأحد",
"all_booked_today": "تم حجز الكل اليوم.",
"slots_load_fail": "تعذر تحميل الفترات الزمنية المتاحة.",
"additional_guests": "إضافة ضيوف",
"your_name": "اسمك",
@@ -588,9 +591,18 @@
"round_robin_description": "نقل الاجتماعات بشكل دوري بين أعضاء الفريق المتعددين.",
"managed_event": "الحدث المدار",
"username_placeholder": "اسم المستخدم",
"managed_event_description": "إنشاء وتوزيع أنواع الأحداث بكميات كبيرة على أعضاء الفريق",
"managed": "مدارة",
"managed_event_url_clarification": "سيتم ملء \"اسم المستخدم\" باسم المستخدم للأعضاء المعينين",
"assign_to": "تعيين إلى",
"add_members": "إضافة أعضاء...",
"count_members_one": "{{count}} عضو",
"count_members_other": "{{count}} أعضاء",
"no_assigned_members": "لا يوجد أعضاء معينين",
"assigned_to": "تم تعيينه إلى",
"start_assigning_members_above": "بدء تعيين الأعضاء أعلاه",
"locked_fields_admin_description": "لن يتمكن الأعضاء من تعديل هذا",
"locked_fields_member_description": "قام مشرف الفريق بقفل هذا الخيار",
"url": "URL",
"hidden": "مخفي",
"readonly": "للقراءة فقط",
@@ -742,6 +754,11 @@
"minimum_booking_notice": "الحد الأدنى من الوقت للحجز",
"slot_interval": "الفترات الزمنية بين عمليات الحجز",
"slot_interval_default": "استخدام طول الحدث (الوضع الافتراضي)",
"delete_event_type": "حذف نوع الحدث؟",
"delete_managed_event_type": "حذف نوع الحدث المُدار؟",
"delete_event_type_description": "لن يكون بإمكان أي شخص قمت بمشاركة الرابط معه الوصول إلى الرابط.",
"delete_managed_event_type_description": "<ul><li>سيتم أيضًا حذف أنواع الأحداث الخاصة بالأعضاء المعينين في هذا النوع من الأحداث.</li><li>لن يتمكن أي شخص شاركوا رابطهم معه من الحجز باستخدامه.</li></ul>",
"confirm_delete_event_type": "نعم، قم بالحذف",
"delete_account": "حذف الحساب",
"confirm_delete_account": "نعم، احذف الحساب",
"delete_account_confirmation_message": "هل تريد بالتأكيد حذف حسابك على {{appName}}؟ أي شخص شاركت رابط حسابك معه لن يستطيع بعد الآن الحجز باستخدامه، وستفقد أي تفضيلات حفظتها.",
@@ -866,7 +883,7 @@
"add_new_calendar": "إضافة تقويم جديد",
"set_calendar": "حدد أين سيتم إضافة الأحداث الجديدة عند الحجز.",
"delete_schedule": "حذف الجدول",
"delete_schedule_description": "سيؤدي حذف الجدول الزمني إلى إزالته من جميع أنواع الفعاليات، ولا يمكن التراجع عن هذا الإجراء.",
"delete_schedule_description": "سيؤدي حذف الجدول الزمني إلى إزالته من جميع أنواع الأحداث، ولا يمكن التراجع عن هذا الإجراء.",
"schedule_created_successfully": "تم إنشاء جدول {{scheduleName}} بنجاح",
"availability_updated_successfully": "تم تحديث جدول {{scheduleName}} بنجاح",
"schedule_deleted_successfully": "تم حذف الجدول بنجاح",
@@ -1021,7 +1038,6 @@
"event_cancelled_trigger": "متى تم إلغاء هذا الحدث",
"new_event_trigger": "متى تم حجز الحدث الجديد",
"email_host_action": "إرسال رسالة إلكترونية إلى المضيف",
"email_attendee_action": "إرسال رسالة إلكترونية إلى واحد من الحضور",
"sms_attendee_action": "إرسال رسالة نصية قصيرة إلى واحد من الحضور",
"sms_number_action": "إرسال رسالة نصية قصيرة إلى رقم محدد",
"workflows": "سير العمل",
@@ -1138,6 +1154,7 @@
"variable_format": "تنسيق المتغير",
"webhook_subscriber_url_reserved": "رابط مشترك Webhook معرّف بالفعل",
"custom_input_as_variable_info": "تجاهل جميع الأحرف الخاصة من تسمية الإدخال الإضافي (استخدم الأحرف والأرقام فقط)، واستخدم الحروف الكبيرة لجميع الأحرف واستبدل المساحات البيضاء بالشرط.",
"using_booking_questions_as_variables": "كيف يمكنني استخدام أسئلة الحجز كمتغيرات؟",
"download_desktop_app": "تحميل تطبيق سطح المكتب",
"set_ping_link": "تعيين رابط Ping",
"rate_limit_exceeded": "تم تجاوز حد السعر",
@@ -1161,7 +1178,7 @@
"event_setup_multiple_duration_error": "إعداد الحدث: تستلزم فترات متعددة خيارًا واحدًا على الأقل.",
"event_setup_multiple_duration_default_error": "إعداد الحدث: الرجاء تحديد مدة افتراضية صالحة.",
"event_setup_booking_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم،أسبوع،شهر،سنة]",
"event_setup_duration_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]",
"event_setup_duration_limits_error": "يجب أن تكون حدود المدة بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]",
"select_which_cal": "اختر أي رزنامة تريد إضافة الحجز إليها",
"custom_event_name": "اسم حدث مخصص",
"custom_event_name_description": "إنشاء أسماء أحداث مخصصة لعرضها في حدث الرزنامة",
@@ -1183,6 +1200,7 @@
"start_of_week": "بداية الأسبوع",
"recordings_title": "التسجيلات",
"recording": "التسجيل",
"happy_scheduling": "جدولة سعيدة",
"select_calendars": "حدد الرزنامات التي تريد التحقق منها بحثًا عن تضاربات لمنع الحجوزات المزدوجة.",
"check_for_conflicts": "التحقق من وجود تضاربات",
"view_recordings": "عرض التسجيلات",
@@ -1220,6 +1238,7 @@
"impersonation": "انتحال الشخصية",
"impersonation_description": "إعدادات إدارة انتحال شخصية مستخدم",
"users": "المستخدمون",
"user": "المستخدم",
"profile_description": "إدارة الإعدادات لملفك الشخصي على {{appName}}",
"users_description": "هنا يمكنك العثور على قائمة بجميع المستخدمين",
"users_listing": "قائمة المستخدمين",
@@ -1260,6 +1279,8 @@
"download_responses": "تنزيل الردود",
"download_responses_description": "تنزيل جميع الردود إلى النموذج بتنسيق CSV.",
"download": "التنزيل",
"download_recording": "تنزيل التسجيل",
"recording_from_your_recent_call": "تسجيل من مكالمتك الأخيرة على Cal.com جاهز للتنزيل",
"create_your_first_form": "إنشاء أول استمارة",
"create_your_first_form_description": "باستخدام نماذج المسارات، يمكنك طرح أسئلة التأهيل والتوجيه إلى الشخص/نوع الحدث المناسب.",
"create_your_first_webhook": "إنشاء أول Webhook",
@@ -1297,6 +1318,14 @@
"exchange_authentication_ntlm": "مصادقة NTLM",
"exchange_compression": "ضغط Gzip",
"exchange_version": "نسخة التبادل",
"exchange_version_2007_SP1": "2007 SP1",
"exchange_version_2010": "2010",
"exchange_version_2010_SP1": "2010 SP1",
"exchange_version_2010_SP2": "2010 SP2",
"exchange_version_2013": "2013",
"exchange_version_2013_SP1": "2013 SP1",
"exchange_version_2015": "2015",
"exchange_version_2016": "2016",
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
"routing_forms_send_email_owner": "إرسال رسالة إلكترونية إلى المالك",
"routing_forms_send_email_owner_description": "يرسل رسالة بريد إلكتروني إلى المالك عند إرسال النموذج",
@@ -1347,10 +1376,11 @@
"limit_booking_frequency": "الحد من تواتر الحجز",
"limit_booking_frequency_description": "الحد من عدد مرات حجز هذا الحدث",
"limit_total_booking_duration": "الحد من مدة الحجز الإجمالية",
"limit_total_booking_duration_description": "الحد من الوقت المخصص الذي يمكن حجزه لهذه الفعالية",
"limit_total_booking_duration_description": "الحد من إجمالي الوقت المخصص الذي يمكن حجزه لهذا الحدث",
"add_limit": "إضافة حد",
"team_name_required": "اسم الفريق مطلوب",
"show_attendees": "مشاركة معلومات الحضور مع الضيوف",
"how_booking_questions_as_variables": "كيف يمكن استخدام أسئلة الحجز كمتغيرات؟",
"format": "التنسيق",
"uppercase_for_letters": "استخدم الحروف الكبيرة لجميع الأحرف",
"replace_whitespaces_underscores": "استبدل المساحات البيضاء بالشرطات السفلية",
@@ -1365,6 +1395,7 @@
"billing_help_title": "هل تحتاج إلى أي شيء آخر؟",
"billing_help_description": "إذا كنت تحتاج إلى أي مساعدة إضافية بخصوص الفوترة، فإن فريق الدعم لدينا في انتظارك لتقديم المساعدة.",
"billing_help_cta": "الاتصال بالدعم",
"ignore_special_characters_booking_questions": "تجاهل المحارف الخاصة في معرّف سؤال الحجز لديك. استخدم فقط الأحرف والأرقام",
"retry": "إعادة المحاولة",
"fetching_calendars_error": "حدثت مشكلة أثناء جلب التقويمات الخاصة بك. يرجى <1>إعادة المحاولة</1> أو الاتصال بدعم العملاء.",
"calendar_connection_fail": "فشل الاتصال بالتقويم",
@@ -1447,12 +1478,17 @@
"fixed_round_robin": "جدولة دوارة",
"add_one_fixed_attendee": "أضف حاضرًا ثابتًا وجدولة دوارة عبر عدد من الحاضرين.",
"calcom_is_better_with_team": "يعمل Cal.com أفضل مع الفرق",
"the_calcom_team": "فريق Cal.com",
"add_your_team_members": "أضف أعضاء فريقك إلى أنواع الأحداث الخاصة بك. استخدم الجدولة الجماعية لإدراج الجميع أو العثور على أنسب شخص باستخدام الجدولة الدوارة.",
"booking_limit_reached": "تم الوصول إلى الحد الأقصى للحجز لهذا النوع من الأحداث",
"duration_limit_reached": "تم الوصول إلى الحد الأقصى للحجز لهذا النوع من الفعاليات",
"duration_limit_reached": "تم الوصول إلى الحد الأقصى لمدة الحجز لهذا النوع من الأحداث",
"admin_has_disabled": "قام مشرفًا بتعطيل {{appName}}",
"disabled_app_affects_event_type": "قام مشرفًا بتعطيل {{appName}} مما يؤثر على نوع الحدث {{eventType}} لديك",
"event_replaced_notice": "استبدل المشرف أحد أنواع الأحداث لديك",
"email_subject_slug_replacement": "استبدل مسؤول فريق الحدث لديك /{{slug}}",
"email_body_slug_replacement_notice": "استبدل مسؤول في فريق <strong>{{teamName}}</strong> نوع الحدث لديك<strong>/{{slug}}</strong> بنوع الحدث المُدار الذي يتحكم فيه.",
"email_body_slug_replacement_info": "سيستمر رابطك في العمل، ولكن بعض إعداداته قد تغيرت. يمكنك مراجعتها في أنواع الأحداث.",
"email_body_slug_replacement_suggestion": "يرجى الاتصال بالمسؤول إذا كان لديك أي أسئلة حول نوع الحدث.<br /><br />جدولة سعيدة، <br />فريق Cal.com",
"disable_payment_app": "قام المشرف بتعطيل {{appName}} الذي يؤثر على نوع الحدث {{title}} لديك. لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.",
"payment_disabled_still_able_to_book": "لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.",
"app_disabled_with_event_type": "عطّل المشرف {{appName}} مما يؤثر على نوع الحدث {{title}} لديك.",
@@ -1501,6 +1537,7 @@
"date_overrides_update_btn": "تحديث التجاوز",
"event_type_duplicate_copy_text": "{{slug}}-نسخ",
"set_as_default": "تعيين كافتراضي",
"hide_eventtype_details": "إخفاء تفاصيل نوع الحدث",
"show_navigation": "إظهار التنقل",
"hide_navigation": "إخفاء التنقل",
"verification_code_sent": "تم إرسال رمز التحقق",
@@ -1514,6 +1551,7 @@
"create_your_first_team_webhook_description": "قم بإنشاء أول شبكة ويب هوك لهذا النوع من فعاليات الفريق",
"create_webhook_team_event_type": "قم بإنشاء ويب هوك لهذا النوع من فعاليات الفريق",
"disable_success_page": "تعطيل صفحة النجاح (يعمل فقط إذا كان لديك رابط إعادة توجيه)",
"invalid_admin_password": "تم تعيينك كمشرف ولكن ليس لديك كلمة مرور بطول 15 مِحرَفاً على الأقل",
"change_password_admin": "قم بتغيير كلمة المرور للحصول على وصول المشرف",
"username_already_taken": "اسم المستخدم هذا مستخدم مسبقًا",
"assignment": "تعيين",
@@ -1592,27 +1630,28 @@
"available_variables": "المتغيرات المتوفرة",
"scheduler": "{Scheduler}",
"no_workflows": "لا يوجد أي سير عمل",
"change_filter": "قم بتغيير عامل الفرز لمشاهدة سير عملك وفريقك.",
"change_filter": "قم بتغيير عامل التصفية لمشاهدة سير العمل الشخصي وفريقك.",
"recommended_next_steps": "الخطوات التالية الموصى بها",
"create_a_managed_event": "إنشاء نوع من الأحداث المُدارة",
"meetings_are_better_with_the_right": "الاجتماعات أفضل مع وجود أعضاء الفريق المناسبين. قم بدعوتهم الآن.",
"create_a_one_one_template": "إنشاء قالب فردي واحد لنوع الفعالية وتوزيعه على عدة أعضاء.",
"create_a_one_one_template": "إنشاء قالب فردي واحد لنوع الحدث وتوزيعه على عدة أعضاء.",
"collective_or_roundrobin": "جماعية أو دوارة",
"book_your_team_members": "احجز أعضاء فريقك والأحداث الجماعية أو تمكن من الوصول إلى الشخص المناسب باستخدام الجدولة الدوارة.",
"event_no_longer_attending_subject": "لن يتم حضور {{title}} في {{date}}",
"no_longer_attending": "لن تحضر هذا الحدث بعد الآن",
"no_longer_attending": "لن تحضر هذا الحدث",
"attendee_no_longer_attending_subject": "أحد الحضور لن يحضر {{title}} في {{date}}",
"attendee_no_longer_attending": "أحد الحضور لن يحضر فعاليتك بعد الآن",
"attendee_no_longer_attending_subtitle": "تم إلغاء الفعالية {{name}}، وهو ما يعني توافر معقد شاغر في هذا التوقيت",
"create_event_on": "إنشاء أحداث في",
"attendee_no_longer_attending": "أحد الحضور لن يحضر الحدث الخاص بك",
"attendee_no_longer_attending_subtitle": "تم إلغاء الحدث {{name}}، وهو ما يعني توافر معقد شاغر في هذا التوقيت",
"create_event_on": "إنشاء حدث في",
"default_app_link_title": "تعيين رابط تطبيق افتراضي",
"default_app_link_description": "يسمح تعيين رابط التطبيق الافتراضي لجميع أنواع الأحداث التي تم إنشاؤها حديثًا باستخدام رابط التطبيق الذي عينته.",
"change_default_conferencing_app": "تعيين كافتراضي",
"organizer_default_conferencing_app": "تطبيق المنظمة الافتراضي",
"under_maintenance": "معطل للصيانة",
"under_maintenance_description": "يجري فريق {{appName}} صيانة مجدولة. إذا كان لديك أي أسئلة، يرجى الاتصال بالدعم.",
"event_type_seats": "{{numberOfSeats}} من المقاعد",
"booking_questions_title": "أسئلة حول الحجز",
"booking_questions_description": "اعمل على تخصيص الأسئلة المطروحة في صفحة الحجز",
"booking_questions_description": "يمكنك تخصيص الأسئلة المطروحة في صفحة الحجز",
"add_a_booking_question": "اطرح سؤالاً",
"duplicate_email": "البريد الإلكتروني مكرر",
"booking_with_payment_cancelled": "لم يعد الدفع لهذا الحدث ممكنًا",
@@ -1620,20 +1659,21 @@
"booking_with_payment_cancelled_refunded": "تم استرداد مبلغ هذا الحجز.",
"booking_confirmation_failed": "فشل تأكيد الحجز",
"not_enough_seats": "لا توجد مقاعد كافية",
"form_builder_field_already_exists": "ثمة حقل بهذا الاسم موجود بالفعل",
"form_builder_field_add_subtitle": "اعمل على تخصيص الأسئلة المطروحة في صفحة الحجز",
"form_builder_field_already_exists": "يوجد حقل بهذا الاسم بالفعل",
"form_builder_field_add_subtitle": "يمكنك تخصيص الأسئلة المطروحة في صفحة الحجز",
"show_on_booking_page": "إظهار في صفحة الحجز",
"get_started_zapier_templates": "البدء في استخدام قوالب Zapier",
"team_is_unpublished": "لم يُنشر {{team}}",
"team_is_unpublished_description": "رابط هذا الفريق غير متوفر حالياً. يرجى الاتصال بمالك الفريق أو الطلب منهم نشره.",
"team_is_unpublished_description": "رابط هذا الفريق غير متاح حاليًا. يرجى الاتصال بمالك الفريق أو طلب نشره منه.",
"team_member": "عضو الفريق",
"a_routing_form": "نموذج توجيه",
"form_description_placeholder": "وصف النموذج",
"keep_me_connected_with_form": "ابقني متصلاً بالنموذج",
"fields_in_form_duplicated": "أي تغييرات في جهاز التوجيه والحقول الخاصة بالنموذج المكرر، ستنعكس في النسخة المكررة.",
"form_deleted": "تم حذف النموذج",
"delete_form": "حذف النموذج",
"delete_form": "هل أنت متأكد من أنك تريد حذف هذا القالب؟",
"delete_form_action": "نعم، حذف النموذج",
"delete_form_confirmation": "هل أنت متأكد من أنك تريد حذف هذا النموذج؟ لن يتمكن أي شخص تشارك الرابط معه من الحجز بعد الآن. كما ستحذف جميع الردود المرتبطة بذلك.",
"delete_form_confirmation": "أي شخص قمت بمشاركة الرابط معه لن يكون قادرًا على الوصول إليه.",
"delete_form_confirmation_2": "سيتم حذف جميع الردود ذات الصلة.",
"typeform_redirect_url_copied": "تم نسخ رابط Typeform Redirect! يمكنك الانتقال وتعيين الرابط في نموذج Typeform.",
"modifications_in_fields_warning": "ستنعكس التعديلات في الحقول والمسارات للنماذج التالية في هذا النموذج.",
@@ -1647,32 +1687,100 @@
"add_a_new_route": "إضافة مسار جديد",
"make_informed_decisions": "اتخاذ قرارات على اطّلاع باستخدام الرؤى",
"make_informed_decisions_description": "تقدّم لوحة الرؤى لدينا جميع الأنشطة لفريقك وتظهر لك الاتجاهات السائدة التي تمكّنك من جدولة الفريق واتخاذ القرار بشكل أفضل.",
"view_bookings_across": "عرض الحجز أمام جميع الأعضاء",
"view_bookings_across_description": "شاهد من يتلقى معظم الحجوزات وتأكد من إجراء أفضل عملية توزيع عبر فريقك",
"identify_booking_trends": "تحديد الاتجاهات السائدة للحجز",
"identify_booking_trends_description": "شاهد أي الأوقات في الأسبوع وخلال اليوم تحظى بشعبية لدى من يقومون بالحجز لديك",
"spot_popular_event_types": "أنواع الأحداث الشائعة",
"spot_popular_event_types_description": "شاهد أي من نوع من الأحداث لديك يتلقى معظم النقرات والحجز",
"no_responses_yet": "لا توجد ردود بعد",
"this_will_be_the_placeholder": "سيكون هذا هو العنصر النائب",
"this_meeting_has_not_started_yet": "لم يبدأ هذا الاجتماع بعد",
"this_app_requires_connected_account": "{{appName}} يتطلب حساب {{dependencyName}} متصل",
"connect_app": "الاتصال {{dependencyName}}",
"connect_app": "اتصال {{dependencyName}}",
"app_is_connected": "{{dependencyName}} متصل",
"requires_app": "يتطلب {{dependencyName}}",
"verification_code": "رمز التحقق",
"can_you_try_again": "هلّا جربت المحاولة ثانيةً في وقت مختلف؟",
"can_you_try_again": "هل يمكنك المحاولة مرة أخرى في وقت مختلف؟",
"verify": "التحقق",
"timezone_variable": "المنطقة الزمنية",
"timezone_info": "المنطقة الزمنية للشخص المتلقي",
"invalid_event_name_variables": "ثمة متغيّر غير غير صالح في اسم فعاليتك",
"select_all": "اختيار الكل",
"default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الفعاليات الحالية",
"default_conferencing_bulk_description": "تحديث المواقع لأنواع الفعاليات المحددة",
"event_end_time_variable": "وقت انتهاء الحدث",
"event_end_time_info": "زمن نهاية الحدث",
"cancel_url_variable": "إلغاء عنوان URL",
"cancel_url_info": "عنوان URL لإلغاء الحجز",
"reschedule_url_variable": "إعادة جدولة عنوان URL",
"reschedule_url_info": "عنوان URL لإعادة جدولة الحجز",
"invalid_event_name_variables": "هناك متغيّر غير صالح في اسم الحدث",
"select_all": "تحديد الكل",
"default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الأحداث الحالية",
"members_default_schedule": "الجدول الزمني الافتراضي للعضو",
"set_by_admin": "تعيين بواسطة مشرف الفريق",
"members_default_location": "الموقع الافتراضي للعضو",
"members_default_schedule_description": "سنستخدم جدول الإتاحة الافتراضي لكل عضو، وسيكون بإمكانه تعديله أو تغييره.",
"requires_at_least_one_schedule": "يجب أن يكون لديك جدول واحد على الأقل",
"default_conferencing_bulk_description": "تحديث المواقع لأنواع الأحداث المحددة",
"locked_for_members": "مقفل أمام الأعضاء",
"locked_apps_description": "سيكون الأعضاء قادرين على رؤية التطبيقات النشطة، ولكن من دون القدرة على تعديل أي إعدادات للتطبيق",
"locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية قوالب الويب النشطة، ولكن من دون القدرة على تعديل أي إعدادات للقوالب",
"locked_workflows_description": "سيكون الأعضاء قادرين على رؤية مسارات العمل النشطة، ولكن من دون القدرة على تعديل أي إعدادات لمسار العمل",
"locked_by_admin": "مقفل من قبل المشرف",
"app_not_connected": "أنت غير متصل بحساب {{appName}}.",
"connect_now": "اتصل الآن",
"managed_event_dialog_confirm_button_one": "استبدال وإشعار العضو {{count}}",
"managed_event_dialog_confirm_button_other": "استبدال وإشعار الأعضاء {{count}}",
"managed_event_dialog_title_one": "عنوان url /{{slug}} موجود بالفعل من أجل {{count}} عضو. هل تريد استبداله؟",
"managed_event_dialog_title_other": "عنوان url /{{slug}} موجود بالفعل لـ {{count}} أعضاء. هل تريد استبداله؟",
"managed_event_dialog_information_one": "<strong>{{names}}</strong> يستخدم بالفعل عنوان url <strong>/{{slug}}</strong>.",
"managed_event_dialog_information_other": "<strong>{{names}}</strong> يستخدم بالفعل عنوان url <string>/{{slug}}</strong>.",
"managed_event_dialog_clarification": "سنقوم بإعلامهم إذا اخترت استبداله. قم بالعودة وإزالتهم إذا كنت لا تريد الكتابة فوقها.",
"review_event_type": "مراجعة نوع الحدث",
"looking_for_more_analytics": "هل تبحث عن تحليلات إضافية؟",
"looking_for_more_insights": "هل تبحث عن رؤى أكثر؟",
"add_filter": "إضافة عامل فرز",
"select_user": "اختر مستخدم",
"select_event_type": "اختر نوع الحدث",
"select_date_range": "اختر نطاق التاريخ",
"popular_events": "الأحداث الشائعة",
"no_event_types_found": "لم يتم العثور على أنواع الأحداث",
"average_event_duration": "متوسط مدة الحدث",
"most_booked_members": "أكثر الأعضاء حجزاً",
"least_booked_members": "أقل الأعضاء حجزاً",
"events_created": "تم إنشاء الأحداث",
"events_completed": "تم اكتمال الأحداث",
"events_cancelled": "تم إلغاء الأحداث",
"events_rescheduled": "تم إعادة جدولة الأحداث",
"from_last_period": "من آخر فترة",
"from_to_date_period": "من: {{startDate}} إلى: {{endDate}}",
"analytics_for_organisation": "الرؤى",
"subtitle_analytics": "اعرف المزيد عن نشاط فريقك",
"redirect_url_warning": "سيؤدي إضافة إعادة توجيه إلى تعطيل صفحة النجاح. تأكد من ذكر \"تأكيد الحجز\" في صفحة النجاح المخصصة.",
"event_trends": "الاتجاهات السائدة للحدث",
"clear_filters": "مسح عوامل الفرز",
"hold": "تعليق",
"on_booking_option": "تحصيل الدفعة عن الحجز",
"hold_option": "فرض رسوم لقاء عدم الحضور",
"card_held": "تم تعليق البطاقة",
"charge_card": "الدفع باستخدام البطاقة",
"card_charged": "تم استقطاع الدفعة من البطاقة",
"no_show_fee_amount": "رسم عدم الحضور {{amount, currency}}",
"no_show_fee": "رسم عدم الحضور",
"submit_card": "تقديم بطاقة",
"submit_payment_information": "تقديم معلومات الدفع",
"meeting_awaiting_payment_method": "اجتماعك في انتظار طريقة الدفع",
"no_show_fee_charged_email_subject": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل {{title}} في {{date}}",
"no_show_fee_charged_text_body": "تم تحصيل رسم عدم حضور",
"no_show_fee_charged_subtitle": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل الحدث التالي",
"error_charging_card": "حدثت مشكلة في استقطاع رسم عدم الحضور. يرجى المحاولة مرة أخرى لاحقاً.",
"collect_no_show_fee": "تحصيل رسم عدم الحضور",
"no_show_fee_charged": "لم يتم تحصيل رسوم عدم حضور",
"insights": "الرؤى",
"acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فإنه لن يتم استخدام بطاقتي للدقع لرسوم الحدث {{amount, currency}}."
"testing_workflow_info_message": "عند اختبار مسار العمل هذا، كن على علم بأنه يمكن جدولة رسائل البريد الإلكتروني والرسائل القصيرة فقط قبل ساعة واحدة على الأقل",
"insights_no_data_found_for_filter": "لم يتم العثور على بيانات لعامل الفرز المحدد أو التواريخ المحددة.",
"acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فإنه لن يتم استخدام بطاقتي للدقع لرسوم الحدث {{amount, currency}}.",
"card_details": "تفاصيل البطاقة",
"seats_and_no_show_fee_error": "لا يمكن حالياً تمكين المقاعد وفرض رسوم عدم الحضور",
"complete_your_booking": "أكمل الحجز",
"complete_your_booking_subject": "أكمل الحجز: {{title}} في {{date}}",
"email_invite_team": "تم دعوة {{email}}"
}
@@ -465,7 +465,6 @@
"friday": "pátek",
"saturday": "sobota",
"sunday": "neděle",
"all_booked_today": "Všechen čas dnes rezervován.",
"slots_load_fail": "Nepodařilo se načíst dostupné časové sloty.",
"additional_guests": "Přidat hosty",
"your_name": "Vaše jméno",
@@ -1039,7 +1038,6 @@
"event_cancelled_trigger": "při zrušení události",
"new_event_trigger": "při rezervaci nové události",
"email_host_action": "odeslání e-mailu hostiteli",
"email_attendee_action": "odeslání e-mailu účastníkovi",
"sms_attendee_action": "odeslání SMS účastníkovi",
"sms_number_action": "odeslání SMS na konkrétní číslo",
"workflows": "Pracovní postupy",
@@ -457,7 +457,6 @@
"friday": "Fredag",
"saturday": "Lørdag",
"sunday": "Søndag",
"all_booked_today": "Alle booket i dag.",
"slots_load_fail": "Kunne ikke indlæse de tilgængelige tidsrum.",
"additional_guests": "Tilføj gæster",
"your_name": "Dit navn",
@@ -1011,7 +1010,6 @@
"event_cancelled_trigger": "når begivenheden er aflyst",
"new_event_trigger": "når ny begivenhed er booket",
"email_host_action": "send e-mail til vært",
"email_attendee_action": "send e-mail til deltager",
"sms_attendee_action": "Send SMS til deltager",
"sms_number_action": "send SMS til et specifikt nummer",
"workflows": "Workflows",
@@ -465,7 +465,6 @@
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag",
"all_booked_today": "Ausgebucht für heute.",
"slots_load_fail": "Die verfügbaren Zeitfenster konnten nicht geladen werden.",
"additional_guests": "+ Weitere Gäste",
"your_name": "Ihr Name",
@@ -1039,7 +1038,6 @@
"event_cancelled_trigger": "wenn das Ereignis abgesagt 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 Teilnehmende senden",
"sms_attendee_action": "SMS an Teilnehmende senden",
"sms_number_action": "SMS an eine bestimmte Nummer senden",
"workflows": "Workflows",
+17 -3
View File
@@ -465,7 +465,7 @@
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"all_booked_today": "All booked today.",
"all_booked_today": "All booked.",
"slots_load_fail": "Could not load the available time slots.",
"additional_guests": "Add guests",
"your_name": "Your name",
@@ -1039,7 +1039,7 @@
"event_cancelled_trigger": "when event is cancelled",
"new_event_trigger": "when new event is booked",
"email_host_action": "send email to host",
"email_attendee_action": "send email to attendee",
"email_attendee_action": "send email to attendees",
"sms_attendee_action": "send SMS to attendee",
"sms_number_action": "send SMS to a specific number",
"workflows": "Workflows",
@@ -1248,6 +1248,7 @@
"calendars_description": "Configure how your event types interact with your calendars",
"appearance_description": "Manage settings for your booking appearance",
"conferencing_description": "Add your favourite video conferencing apps for your meetings",
"add_conferencing_app": "Add Conferencing App",
"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.",
@@ -1799,5 +1800,18 @@
"charge_attendee": "Charge attendee {{amount, currency}}",
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
"email_invite_team": "{{email}} has been invited",
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
"error_collecting_card": "Error collecting card",
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit",
"inline_embed": "Inline Embed",
"load_inline_content": "Loads your event type directly inline with your other website content.",
"floating_pop_up_button": "Floating pop-up button",
"floating_button_trigger_modal": "Puts a floating button on your site that triggers a modal with your event type.",
"pop_up_element_click": "Pop up via element click",
"open_dialog_with_element_click": "Open your Cal dialog when someone clicks an element.",
"need_help_embedding": "Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options.",
"book_my_cal": "Book my Cal",
"disable_attendees_confirmation_emails": "Disable default confirmation emails for attendees",
"disable_attendees_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the attendees when the event is booked.",
"disable_host_confirmation_emails": "Disable default confirmation emails for host",
"disable_host_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the host when the event is booked."
}
@@ -465,7 +465,6 @@
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo",
"all_booked_today": "Todo Reservado Hoy.",
"slots_load_fail": "No se pudo cargar el intervalo de tiempo disponible.",
"additional_guests": "Añadir invitados",
"your_name": "Tu Nombre",
@@ -1039,7 +1038,6 @@
"event_cancelled_trigger": "cuando se cancela el evento",
"new_event_trigger": "cuando se reserva un nuevo evento",
"email_host_action": "enviar correo electrónico al anfitrión",
"email_attendee_action": "enviar correo electrónico al asistente",
"sms_attendee_action": "enviar SMS al asistente",
"sms_number_action": "enviar SMS a un número específico",
"workflows": "Flujos de trabajo",
+13 -3
View File
@@ -465,7 +465,7 @@
"friday": "Vendredi",
"saturday": "Samedi",
"sunday": "Dimanche",
"all_booked_today": "Tout est réservé pour aujourd'hui.",
"all_booked_today": "Tout est réservé.",
"slots_load_fail": "Impossible de charger les créneaux disponibles.",
"additional_guests": "Ajouter des invités",
"your_name": "Votre nom",
@@ -1039,7 +1039,7 @@
"event_cancelled_trigger": "lorsque l'événement est annulé",
"new_event_trigger": "lorsqu'un nouvel événement est réservé",
"email_host_action": "envoyer un e-mail à l'hôte",
"email_attendee_action": "envoyer un e-mail au participant",
"email_attendee_action": "envoyer un e-mail aux participants",
"sms_attendee_action": "envoyer un SMS au participant",
"sms_number_action": "envoyer un SMS à un numéro spécifique",
"workflows": "Workflows",
@@ -1248,6 +1248,7 @@
"calendars_description": "Configurez la manière dont vos types d'événements interagissent avec vos calendriers.",
"appearance_description": "Gérez les paramètres d'apparence de vos réservations.",
"conferencing_description": "Ajoutez vos applications de visioconférence préférées pour vos rendez-vous.",
"add_conferencing_app": "Ajouter une application de conférence",
"password_description": "Gérez les paramètres pour les mots de passe de votre compte.",
"2fa_description": "Gérez les paramètres pour les mots de passe de votre compte.",
"we_just_need_basic_info": "Nous avons juste besoin de quelques informations de base pour configurer votre profil.",
@@ -1793,10 +1794,19 @@
"seats_and_no_show_fee_error": "Il n'est pas possible d'activer les places et de facturer des frais d'absence pour le moment",
"complete_your_booking": "Terminer votre réservation",
"complete_your_booking_subject": "Terminer votre réservation : {{title}} le {{date}}",
"confirm_your_details": "Confirmez vos coordonnées",
"currency_string": "{{amount, currency}}",
"charge_card_dialog_body": "Vous êtes sur le point de facturer {{amount, currency}} au participant. Voulez-vous vraiment continuer ?",
"charge_attendee": "Facturer {{amount, currency}} au participant",
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
"email_invite_team": "{{email}} a été invité",
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo"
"error_collecting_card": "Erreur lors de la collecte de la carte",
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo",
"inline_embed": "Intégration en ligne",
"load_inline_content": "Charge votre type d'événement directement en ligne avec le contenu de votre site web.",
"floating_pop_up_button": "Bouton pop-up flottant",
"disable_attendees_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour les participants",
"disable_attendees_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail aux participants lorsque l'événement est réservé.",
"disable_host_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour l'hôte",
"disable_host_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail à l'hôte lorsque l'événement est réservé."
}
@@ -460,7 +460,6 @@
"friday": "יום שישי",
"saturday": "יום שבת",
"sunday": "יום ראשון",
"all_booked_today": "כל חלונות הזמן להיום הוזמנו.",
"slots_load_fail": "לא ניתן לטעון את חלונות הזמן הזמינים.",
"additional_guests": "הוספת אורחים",
"your_name": "שמך",
@@ -1015,7 +1014,6 @@
"event_cancelled_trigger": "כאשר אירוע מבוטל",
"new_event_trigger": "כאשר אירוע חדש מוזמן",
"email_host_action": "שלח מייל למארח",
"email_attendee_action": "שלח מייל למשתתף",
"sms_attendee_action": "שלח SMS למשתתף",
"sms_number_action": "שלח SMS למספר מסוים",
"workflows": "תהליכים",

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