Compare commits

...
110 Commits
Author SHA1 Message Date
Crowdin Bot 0d45cfb7ea New Crowdin translations by Github Action
release.yml / release (push) Canceled after 0s
2023-05-15 16:13:08 +00:00
Alex van Andel a4fc25e260 v2.9.0 2023-05-15 18:12:07 +02:00
Jeroen ReumkensandAlex van Andel 796c932311 CAL-1550: Align form submit button of new booker to bottom (#8896)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-15 15:02:16 +00:00
Jeroen Reumkens 4137f63ec7 CAL-1664: Improve flash of toggle group when rerendering (#8900)
* CAL-1664: Improve flash of toggle group when rerendering, because active element moves into position.

* Added back newline
2023-05-15 14:38:02 +00:00
Jeroen Reumkens 2147cc82a5 CAL-1557: Improve timeformat selector alignment in new booker (#8898) 2023-05-15 14:37:23 +00:00
Crowdin Bot 40e646e9a4 New Crowdin translations by Github Action 2023-05-15 14:22:54 +00:00
cb2225259c Enabling workflows for managed events (#8446)
* Enabling workflows for managed events

* Disabling fields for workflow when readonly

* Disabling event type workflows if readonly

* Installing auth app

* Reverting yarn.lock

* Again reverting yarn

* Showing user workflows in managed event type

* Reusing existing code to show all workflows

* Further extending workflow support

* Added unit test

* Fixing workflow assignment

* Fixing locked workflows for children MET

* Update packages/features/ee/workflows/components/EventWorkflowsTab.tsx

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

* Personal workflows fix, matching count numbers

* Fixing menu items for managed event types

* Fixing type checks

* Fixing empty activeOn select

* Fixing unit test

* handling active workflows for MET

* Fixing MET slug nit

* Embed option in action restored for non-MET

* Correcting mobile view when hidden

* More adjustments

* fix sms reminder field for children

* remove console.log

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-05-15 13:56:26 +00:00
372cd94d9f Gitpod setup And Bio Detail Overflow Bug Fixed (#8824)
* feat: Add Gitpod setup and update README.md

* fix: Fix overflow issue in bio details with word-wrap property

* minor changes in gitpod script

* updated gitpod url for cal

* gitpod url fixed in markdown

* fix: lint errors

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

* added vscode extensions and  enabled prebuilds to reduce waiting time

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-05-15 13:18:31 +00:00
Vedant Jainandsean-brydon c7c282f30f [fix] : select options marginal space (#8892)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-15 10:25:33 +00:00
959488910f fix: strip markdown from meta description tag in users page (#8769)
* fix: remove markdown validation

* feat: use markdown stripped bio

* fix: use remove markdown

* fix: type errors

---------

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-15 07:44:23 +00:00
Hariom Balhara 0ca0cc3265 Add eventTypeSelected event (#8894) 2023-05-15 06:30:08 +00:00
69bc12187e Fixed dark mode bugs in the availability section (#8630)
* Fixed dark mode bugs in the availability section

* Reverted the tooltip change for delete button

* Updated tooltip of delete button conditionally based on isLastSchedule flag

* fix: disabled button state

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>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-05-14 17:46:19 +00:00
Abdurrahman RajabandCarina Wollendorfer 5e772530b2 Feat: select all days in schedule (#8796)
* feat: add select all

* fix: fix deleted days

* chore: add i18n select all

* chore: check all days when select all

* chore: use calcom checkbox

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-05-14 16:00:55 +00:00
Peer Richelsen 3088b1174a only border top for bottom nav (#8882) 2023-05-14 11:06:10 +00:00
Santosh Kuchetti fadf12b2bd Star not centered issue #8519 (#8872)
* modifications for the issues #8159 are done

* modifications for the issue #8519 are made
2023-05-13 09:51:16 +00:00
sean-brydon c81f30b092 Fix hidden label breaking layout (#8867) 2023-05-13 09:50:00 +00:00
Hariom Balharaandzomars 476119d627 Add changesets/cli to release embed (#8126)
* Add dep versions

* Release new version for embeds

* Add prepublish

* Use prepare script which works with both npm and yarn

* Add changesets correctly

* Add embed publishing command

---------

Co-authored-by: zomars <zomars@me.com>
2023-05-13 08:22:31 +00:00
Crowdin Bot 040f03a9b5 New Crowdin translations by Github Action 2023-05-13 04:31:26 +00:00
Shivang SharmaandOmar López b552bf6956 Fix: #8528 Moved the data checks to superRefine (#8749)
* Fix: #8528 Moved the data checks to superRefine

* Resolved lint isseu

* Updated to parse from safeParse

* added new line

---------

Co-authored-by: Omar López <zomars@me.com>
2023-05-13 04:11:12 +00:00
82d3c4370d cancel and schedule reminders when workflow activated/deactivated on event type (#8757)
* 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

* remove  reminders when workflow is deactivated on event type

* 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

* schedule reminders if event type gets activated on workflow

* schedule reminders (commented code)

* change text

* schedule reminders for all attendees not just first one

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2023-05-12 17:31:01 +00:00
492b4d3e97 Refactor/readme.md (#8723)
* Update README.md 

Removed 
- Heroku DB setup section (since it's no longer free). 
Added
- Section for setting up local DB.
- Some other minor details.

* Minor grammer fixes

---------

Co-authored-by: vaibhav bisht <vaibhav@aus.co.in>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-12 10:39:03 -06:00
0ae319d206 a11y: Exit the lexical Editor and button a11y on tab index (#8139)
* a11y: Exit the lexical Editor and button a11y on tab index

Signed-off-by: GurkiranSingh <gurkiransinghk@gmail.com>

* chore: Updated lexical to v0.9.0

Signed-off-by: GurkiranSingh <gurkiransinghk@gmail.com>

* chore: Added ErrorBoundary for Lexical RichTextPlugin

Signed-off-by: GurkiranSingh <gurkiransinghk@gmail.com>

* chore: yarn install changes

Signed-off-by: GurkiranSingh <gurkiransinghk@gmail.com>

* fix: merge conflicts

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

* Update yarn.lock

---------

Signed-off-by: GurkiranSingh <gurkiransinghk@gmail.com>
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: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-05-12 10:28:14 -06:00
Hariom Balhara 749311be2a Pull latest main in every submodule (#8537)
* Update to latest main so that yarn.lock is always correct

* Update git-setup.sh
2023-05-12 10:20:32 -06:00
Crowdin Bot 3cce4e5009 New Crowdin translations by Github Action 2023-05-12 15:46:20 +00:00
Mendy Landa 341f82ccb9 chore: removed duplicate value in className @ Tooltip.tsx (#8865) 2023-05-12 09:45:32 -06:00
Pranjal Goyal e028a339f5 Adding icon.svg and renaming config file (#8859) 2023-05-12 12:57:09 +00:00
sean-brydonandBailey Pumfleet c55e432bc3 Feat/team invite flow (#8804)
* completeOnboarding drives invite flow changes

* Username prefil - force getting started when acc doesnt exist

* Change subtitle text and unique error on multiple invites

* User exists and no password set - generate signup link still

* Fix callback param

* Update packages/trpc/server/routers/viewer/teams/inviteMember.handler.ts

* Fix spacing

* Add password hints

* Fix translations

---------

Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-05-12 12:52:09 +00:00
Hariom BalharaandJoe Au-Yeung f47c3cd538 Fix/ Routing Form Routes (#8827)
* Reuse split routes

* Add cache

* Update formMutation.handler.ts

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-05-12 12:01:05 +00:00
Nafees Nazik 4b70a20941 fix: overflow (#8858) 2023-05-12 10:55:11 +00:00
Hariom Balhara c873b83965 Unstage enums file (#8836) 2023-05-12 05:02:42 +00:00
Hariom Balhara ff859737ec Improvement/AppStore: Remove seeding from app-store-cli. (#8486)
* Remove seeding from cli

* Self review fixes

* Fix TS error
2023-05-12 04:59:15 +00:00
AnsariandPeer Richelsen 607ef71f9f fix: teams upgrade banner on smaller screens (#8786)
* background image fix 📲

* object image position change 🖼

* Duplicate Deleted Field 🎭

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-12 00:23:11 +00:00
24dd4002fa fixed color of status bar for pwa (#8583)
* fixed color of status bar for pwa

* Apply suggestions from code review

* Apply suggestions from code review

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-05-11 23:50:11 +00:00
Praveen DiasandJoe Au-Yeung 078c9ddca5 Improved API key error message for SendGrid setup page (#8847)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-05-11 23:04:10 +00:00
alannnc b44e5a7aae Fix no video reference on google-meet (#8849) 2023-05-11 22:49:10 +00:00
4374da08e2 #8701 CAL-1621 new booker dialog grow in height (#8774)
* increase scrollable height

* Fix react-select version - import esm error

* Increase booking form modal height

* Update packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx

* Match design specs

* nit change

* Trying to fix type errors

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-11 17:31:51 +00:00
Crowdin Bot 98b531e6b5 New Crowdin translations by Github Action 2023-05-11 16:26:01 +00:00
74414db760 feat: add meta and seo tags for new booker component (#8697)
* feat: add hidden to select

* feat: add hide branding to store

* fix: remove branding from store

* feat: prefetch data

* feat: use meta tags

* fix: remove branding from store

* fix: prefetching

* refactor: useEvent hook

* fix: type

* fix :booker seo

* fix: type error

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
2023-05-11 16:02:13 +00:00
sean-brydon bdf3e34ea1 Feat/onboarding video step connection (#8838)
* UI work - WIP scrollable area

* Add translations - refactor some components

* Add installed text

* Disable if there is no currently installed

* Extract loader

* Fix conditional

* Fix E2E

* fix typo
2023-05-11 13:20:39 +00:00
Jeroen Reumkens 738e0f3e50 CAL-1553: Show dates for multi occurence event instead of input to change occurences, during booking step. Also make form less wide and meta a bit wider during booker step (in small view only), so form looks better and dates fit on a single line for the multiple occurences. When split over multiple lines they would otherwise take up a lot of unneccesary space. (#8834) 2023-05-11 11:51:41 +00:00
Jeroen Reumkens 16ae19abdb CAL-1624: Timezone select layout improvements when typing (#8832) 2023-05-11 08:19:26 +00:00
a2e18ae9ba deleted fields in duplicate form 🎭 (#8808)
* deleted fields in duplicate form  🎭

* Fixed duplicate deleted fields

* Update packages/app-store/routing-forms/trpc/_router.ts

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-05-11 07:42:37 +00:00
e2cfd0d1d1 Webex integration (#7651)
* start webex app creation

* webex integration wip

* fix lint errors

* fix lint errors

* add webex env vars in appStore.example

* webex app wip

* fix lint errors

* edit webex oauth scopes

* add location in webex app config

* add site url placeholder and regex in webex config location

* debug translateEvent

* fix utc formatting for event start time, add test boilerplate for webex, add envs

* fix location and datetime formatting

* get correct videoCredentials for deleteMeeting

* Move webex specific readme content to webex README

* Fix app not visible in app-store

* Delete setup route

* add webex icon

* delete prev icon

* webex api fix

* add app screenshots

* Revert tests changes as they dont run

* Use config instead of hardcoding vales

* Update README

* Remove all env variables related to WEBEX app. They can be added through settings->admin->apps interface

* update from origin

* fix icon path

* update webex readme

* Update yarn.lock

* update webex readme

* Remove unnecessary URL from webex

* revert changes in cancel booking handler

* simply webex zod schemas, logs for debugging

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-11 07:14:32 +00:00
Crowdin Bot 067b77faf1 New Crowdin translations by Github Action 2023-05-11 04:07:21 +00:00
Ashray ShettyandAlex van Andel 740c01ac29 align text "Date overrides" with corresponding svg (#8819)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-05-11 03:45:14 +00:00
asadath1395andBailey Pumfleet 97cfd1ffa7 Fix delete icon background color in availability (#8810)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-05-10 15:15:13 +00:00
Jithil P Ponnan a7b2cfc90f fix: cal-1647 alignment issues (#8817) 2023-05-10 15:05:46 +00:00
d067b0782a feat: add RECORDING_READY webhook (#8277)
* feat: add RECORDING_READY webhook

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

* fix: type errors and improvements

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: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-10 14:56:31 +00:00
203a36e2b5 Make Jitsi Host configurable (#8636)
* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Replace Tab with spaces

* Add JITSI_HOST_URL env

* Remote Tabs for Spaces

* Support jitsi_host key

* Remove ENV, add Interface jitsi host URL and fallback

* Fix linting issues

* Clean code

* add missing await

* Add configurable meeting slug

* Refactor to jitsiHost, remove slug generator

* zod default doesnt work right now. So remove it

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-05-10 14:46:14 +00:00
Hariom Balhara f5a38c3f89 Add missing zoho-bigin import (#8798) 2023-05-10 14:37:16 +00:00
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
333 changed files with 6392 additions and 2829 deletions
+8
View File
@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
+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:
+43
View File
@@ -0,0 +1,43 @@
tasks:
- init: |
yarn &&
cp .env.example .env &&
next_auth_secret=$(openssl rand -base64 32) &&
calendso_encryption_key=$(openssl rand -base64 24) &&
sed -i -e "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$next_auth_secret|" \
-e "s|^CALENDSO_ENCRYPTION_KEY=.*|CALENDSO_ENCRYPTION_KEY=$calendso_encryption_key|" .env
command: yarn dx
ports:
- port: 3000
visibility: public
onOpen: open-preview
- port: 5420
visibility: private
onOpen: ignore
- port: 1025
visibility: private
onOpen: ignore
- port: 8025
visibility: private
onOpen: ignore
github:
prebuilds:
master: true
pullRequests: true
pullRequestsFromForks: true
addCheck: true
addComment: true
addBadge: true
vscode:
extensions:
- DavidAnson.vscode-markdownlint
- yzhang.markdown-all-in-one
- esbenp.prettier-vscode
- dbaeumer.vscode-eslint
- bradlc.vscode-tailwindcss
- ban.spellright
- stripe.vscode-stripe
- Prisma.prisma
File diff suppressed because one or more lines are too long
+2
View File
@@ -3,5 +3,7 @@ nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.4.1.cjs
+78 -59
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,53 +153,63 @@ 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
```
#### Gitpod Setup
1. Click the button below to open this project in Gitpod.
2. This will open a fully configured workspace in your browser with all the necessary dependencies already installed.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/calcom/cal.com)
#### 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 local DB</summary>
1. Create a free account with [Heroku](https://www.heroku.com/).
1. [Download](https://www.postgresql.org/download/) and install postgres in your local (if you don't have it already).
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">
2. Create your own local db by executing `createDB <DB name>`
3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database.
![image](https://user-images.githubusercontent.com/16905768/115323232-a53ba480-a17f-11eb-98db-58e2f8c52426.png)
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
4. Once you clicked on `Configure Add-ons`, click on `Find more add-ons` and search for `postgres`. One of the options will be `Heroku Postgres` - click on that option.
![image](https://user-images.githubusercontent.com/16905768/115323126-5beb5500-a17f-11eb-8030-7380310807a9.png)
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
![image](https://user-images.githubusercontent.com/39329182/236612291-51d87f69-6dc1-4a23-bf4d-1ca1754e0a35.png)
5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`.
<img width="512" alt="Submit Order Form" src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png">
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
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.
![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/).
</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).
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
- [Setup postgres DB with render](https://render.com/docs/databases)
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
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 +219,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 +264,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 +298,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 +311,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 +399,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 +436,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 +448,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,12 +458,16 @@ 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.
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
### Obtaining Webex Client ID and Secret
[See Webex Readme](./packages/app-store/webex/)
### Obtaining ZohoCRM Client ID and Secret
1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
@@ -458,31 +475,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 +509,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
View File
@@ -1,6 +1,7 @@
{
"name": "@calcom/docs",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
+10
View File
@@ -0,0 +1,10 @@
# @calcom/web
## 2.7.16
### Patch Changes
- Updated dependencies
- @calcom/embed-snippet@1.0.7
- @calcom/embed-react@1.0.12
- @calcom/embed-core@1.1.5
+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,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}
/>
))}
@@ -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`}
/>
@@ -72,6 +72,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
slotUtcStartDate: slot.time,
eventTypeId,
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
bookingAttendees: bookingAttendees || undefined,
});
};
@@ -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}
@@ -119,7 +119,7 @@ const EventTypeScheduleDetails = ({
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ms-4">-</span>
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
</div>
@@ -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
);
@@ -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`}
@@ -390,7 +390,12 @@ export const EventSetupTab = (
addOnLeading={
<>
{CAL_URL?.replace(/^(https?:|)\/\//, "")}/
{team ? "team/" + team.slug : eventType.users[0].username}/
{!isManagedEventType
? team
? "team/" + team.slug
: eventType.users[0].username
: t("username_placeholder")}
/
</>
}
{...formMethods.register("slug", {
@@ -466,6 +471,7 @@ export const EventSetupTab = (
defaultValue={eventType.length ?? 15}
{...formMethods.register("length")}
addOnSuffix={<>{t("minutes")}</>}
min={1}
/>
)}
{!lengthLockedProps.disabled && (
@@ -170,7 +170,7 @@ export const EventTeamWebhooksTab = ({
</Dialog>
{/* Edit webhook dialog */}
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
<DialogContent title={t("edit_webhook")}>
<DialogContent enableOverflow title={t("edit_webhook")}>
<WebhookForm
webhook={webhookToEdit}
apps={installedApps?.items.map((app) => app.slug)}
@@ -170,7 +170,7 @@ function EventTypeSingleLayout({
// Define tab navigation here
const EventTypeTabs = useMemo(() => {
let navigation = getNavigation({
const navigation = getNavigation({
t,
eventType,
enabledAppsNumber,
@@ -210,7 +210,7 @@ function EventTypeSingleLayout({
}
if (isManagedEventType || isChildrenManagedEventType) {
// Removing apps and workflows for manageg event types by admins v1
navigation = navigation.slice(0, -2);
navigation.splice(-2, 1);
} else {
navigation.push({
name: "webhooks",
@@ -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"
@@ -3,15 +3,16 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
interface ICalendarItem {
interface IAppConnectionItem {
title: string;
description?: string;
logo: string;
type: App["type"];
installed?: boolean;
}
const CalendarItem = (props: ICalendarItem) => {
const { title, logo, type } = props;
const AppConnectionItem = (props: IAppConnectionItem) => {
const { title, logo, type, installed } = props;
const { t } = useLocale();
return (
<div className="flex flex-row items-center justify-between p-5">
@@ -25,13 +26,14 @@ const CalendarItem = (props: ICalendarItem) => {
<Button
{...buttonProps}
color="secondary"
disabled={installed}
type="button"
onClick={(event) => {
// Save cookie key to return url step
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
buttonProps && buttonProps.onClick && buttonProps?.onClick(event);
}}>
{t("connect")}
{installed ? t("installed") : t("connect")}
</Button>
)}
/>
@@ -39,4 +41,4 @@ const CalendarItem = (props: ICalendarItem) => {
);
};
export { CalendarItem };
export { AppConnectionItem };
@@ -1,4 +1,4 @@
import { CalendarSwitch } from "./CalendarSwitch";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
interface IConnectedCalendarItem {
name: string;
@@ -0,0 +1,17 @@
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui";
export function StepConnectionLoader() {
return (
<ul className="bg-default divide-subtle border-subtle divide-y rounded-md border p-0 dark:bg-black">
{Array.from({ length: 4 }).map((_item, index) => {
return (
<li className="flex w-full flex-row justify-center border-b-0 py-6" key={index}>
<SkeletonAvatar className="mx-6 h-8 w-8 px-4" />
<SkeletonText className="ml-1 mr-4 mt-3 h-5 w-full" />
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
</li>
);
})}
</ul>
);
}
@@ -3,11 +3,12 @@ import { ArrowRightIcon } from "@heroicons/react/solid";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { SkeletonAvatar, SkeletonText, SkeletonButton, List } from "@calcom/ui";
import { List } from "@calcom/ui";
import { CalendarItem } from "../components/CalendarItem";
import { AppConnectionItem } from "../components/AppConnectionItem";
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
import { CreateEventsOnCalendarSelect } from "../components/CreateEventsOnCalendarSelect";
import { StepConnectionLoader } from "../components/StepConnectionLoader";
interface IConnectCalendarsProps {
nextStep: () => void;
@@ -33,7 +34,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
firstCalendar.integration.title &&
firstCalendar.integration.logo && (
<>
<List className="bg-default border-subtle rounded-md border p-0 dark:bg-black">
<List className="bg-default border-subtle rounded-md border p-0 dark:bg-black ">
<ConnectedCalendarItem
key={firstCalendar.integration.title}
name={firstCalendar.integration.title}
@@ -60,7 +61,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
queryIntegrations.data.items.map((item) => (
<li key={item.title}>
{item.title && item.logo && (
<CalendarItem
<AppConnectionItem
type={item.type}
title={item.title}
description={item.description}
@@ -72,19 +73,8 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
</List>
)}
{queryIntegrations.isLoading && (
<ul className="bg-default divide-subtle border-subtle divide-y rounded-md border p-0 dark:bg-black">
{[0, 0, 0, 0].map((_item, index) => {
return (
<li className="flex w-full flex-row justify-center border-b-0 py-6" key={index}>
<SkeletonAvatar className="mx-6 h-8 w-8 px-4" />
<SkeletonText className="ml-1 mr-4 mt-3 h-5 w-full" />
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
</li>
);
})}
</ul>
)}
{queryIntegrations.isLoading && <StepConnectionLoader />}
<button
type="button"
data-testid="save-calendar-button"
@@ -0,0 +1,68 @@
import { ArrowRightIcon } from "@heroicons/react/solid";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { List } from "@calcom/ui";
import { AppConnectionItem } from "../components/AppConnectionItem";
import { StepConnectionLoader } from "../components/StepConnectionLoader";
interface ConnectedAppStepProps {
nextStep: () => void;
}
const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
const { nextStep } = props;
const { data: queryConnectedVideoApps, isLoading } = trpc.viewer.integrations.useQuery({
variant: "conferencing",
onlyInstalled: false,
});
const { t } = useLocale();
const hasAnyInstalledVideoApps = queryConnectedVideoApps?.items.some(
(item) => item.credentialIds.length > 0
);
return (
<>
{!isLoading && (
<List className="bg-default border-subtle divide-subtle scroll-bar mx-1 max-h-[45vh] divide-y !overflow-y-scroll rounded-md border p-0 sm:mx-0">
{queryConnectedVideoApps?.items &&
queryConnectedVideoApps?.items.map((item) => {
if (item.slug === "daily-video") return null; // we dont want to show daily here as it is installed by default
return (
<li key={item.name}>
{item.name && item.logo && (
<AppConnectionItem
type={item.type}
title={item.name}
description={item.description}
logo={item.logo}
installed={item.credentialIds.length > 0}
/>
)}
</li>
);
})}
</List>
)}
{isLoading && <StepConnectionLoader />}
<button
type="button"
data-testid="save-video-button"
className={classNames(
"text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm",
!hasAnyInstalledVideoApps ? "cursor-not-allowed opacity-20" : ""
)}
disabled={!hasAnyInstalledVideoApps}
onClick={() => nextStep()}>
{t("next_step_text")}
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</button>
</>
);
};
export { ConnectedVideoStep };
@@ -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>
);
}
+1 -1
View File
@@ -31,7 +31,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
{!isBioEmpty ? (
<>
<div
className=" text-subtle text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
/>
</>
@@ -230,7 +230,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="absolute top-0 right-2 flex flex-row">
<span
className={classNames(
"mx-2 py-1",
"mx-2 py-2",
isInputUsernamePremium ? "text-orange-400" : "",
usernameIsAvailable ? "" : ""
)}>
@@ -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>
-1
View File
@@ -105,7 +105,6 @@ const nextConfig = {
"@calcom/trpc",
"@calcom/ui",
"lucide-react",
"react-timezone-select",
],
modularizeImports: {
"@calcom/ui/components/icon": {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.8.11",
"version": "2.9.0",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -113,14 +113,13 @@
"react-phone-number-input": "^3.2.7",
"react-schemaorg": "^2.0.0",
"react-select": "^5.7.0",
"react-timezone-select": "^1.5.1",
"react-timezone-select": "^1.4.0",
"react-use-intercom": "1.5.1",
"remark": "^14.0.2",
"remove-markdown": "^0.5.0",
"rrule": "^2.7.1",
"sanitize-html": "^2.10.0",
"schema-dts": "^1.1.0",
"short-uuid": "^4.2.0",
"strip-markdown": "^5.0.0",
"stripe": "^9.16.0",
"superjson": "1.9.1",
"tailwindcss-radix": "^2.6.0",
@@ -153,6 +152,7 @@
"@types/qrcode": "^1.4.3",
"@types/react": "18.0.26",
"@types/react-phone-number-input": "^3.0.14",
"@types/remove-markdown": "^0.3.1",
"@types/sanitize-html": "^2.9.0",
"@types/stripe": "^8.0.417",
"@types/uuid": "8.3.1",
+16 -6
View File
@@ -23,6 +23,7 @@ import defaultEvents, {
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
import prisma from "@calcom/prisma";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@@ -37,7 +38,16 @@ import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
const {
users,
profile,
eventTypes,
isDynamicGroup,
dynamicNames,
dynamicUsernames,
isSingleUser,
markdownStrippedBio,
} = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(user.theme);
const { t } = useLocale();
@@ -107,11 +117,9 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<>
<HeadSeo
title={isDynamicGroup ? dynamicNames.join(", ") : nameOrUsername}
description={
isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : (user.bio as string) || ""
}
description={isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : markdownStrippedBio}
meeting={{
title: isDynamicGroup ? "" : `${user.bio}`,
title: isDynamicGroup ? "" : markdownStrippedBio,
profile: { name: `${profile.name}`, image: null },
users: isDynamicGroup
? dynamicUsernames.map((username, index) => ({ username, name: dynamicNames[index] }))
@@ -136,7 +144,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{!isBioEmpty && (
<>
<div
className=" text-subtle text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: props.safeBio }}
/>
</>
@@ -342,6 +350,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const safeBio = markdownToSafeHTML(user.bio) || "";
const markdownStrippedBio = stripMarkdown(user?.bio || "");
return {
props: {
users,
@@ -361,6 +370,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
dynamicNames,
dynamicUsernames,
isSingleUser,
markdownStrippedBio,
},
};
};
+3 -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");
@@ -45,7 +46,8 @@ class MyDocument extends Document<Props> {
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#ff0000" />
<meta name="theme-color" content="var(--cal-bg)" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1C1C1C" />
</Head>
<body
+14 -3
View File
@@ -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 } }],
},
],
},
],
},
],
},
+2 -17
View File
@@ -4,7 +4,6 @@ import { z } from "zod";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { TRPCError } from "@calcom/trpc/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
@@ -27,32 +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 ctx = await createContext({ req, res });
const caller = viewerRouter.createCaller({ ...ctx, req, res });
await caller.bookings.confirm({
bookingId: booking.id,
recurringEventId: booking.recurringEventId || undefined,
+62 -21
View File
@@ -1,3 +1,4 @@
import type { WebhookTriggerEvents } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
@@ -5,6 +6,8 @@ import { DailyLocationType } from "@calcom/app-store/locations";
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
import { sendDailyVideoRecordingEmails } from "@calcom/emails";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import { defaultHandler } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
@@ -20,6 +23,38 @@ const downloadLinkSchema = z.object({
download_link: z.string(),
});
const triggerWebhook = async ({
evt,
downloadLink,
booking,
}: {
evt: CalendarEvent;
downloadLink: string;
booking: {
userId: number | undefined;
eventTypeId: number | null;
};
}) => {
const eventTrigger: WebhookTriggerEvents = "RECORDING_READY";
// Send Webhook call if hooked to BOOKING.RECORDING_READY
const subscriberOptions = {
userId: booking.userId ?? 0,
eventTypeId: booking.eventTypeId ?? 0,
triggerEvent: eventTrigger,
};
const webhooks = await getWebhooks(subscriberOptions);
const promises = webhooks.map((webhook) =>
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
...evt,
downloadLink,
}).catch((e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
})
);
await Promise.all(promises);
};
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
return res.status(405).json({ message: "No SendGrid API key or email" });
@@ -51,6 +86,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
uid: true,
location: true,
isRecorded: true,
eventTypeId: true,
user: {
select: {
id: true,
@@ -105,31 +141,36 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
},
});
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId);
const downloadLinkResponse = downloadLinkSchema.parse(response);
const downloadLink = downloadLinkResponse.download_link;
const evt: CalendarEvent = {
type: booking.title,
title: booking.title,
description: booking.description || undefined,
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: booking.user?.email || "Email-less",
name: booking.user?.name || "Nameless",
timeZone: booking.user?.timeZone || "Europe/London",
language: { translate: t, locale: booking?.user?.locale ?? "en" },
},
attendees: attendeesList,
uid: booking.uid,
};
await triggerWebhook({
evt,
downloadLink,
booking: { userId: booking?.user?.id, eventTypeId: booking.eventTypeId },
});
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
// send emails to all attendees only when user has team plan
if (isSendingEmailsAllowed) {
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId);
const downloadLinkResponse = downloadLinkSchema.parse(response);
const downloadLink = downloadLinkResponse.download_link;
const evt: CalendarEvent = {
type: booking.title,
title: booking.title,
description: booking.description || undefined,
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: booking.user?.email || "Email-less",
name: booking.user?.name || "Nameless",
timeZone: booking.user?.timeZone || "Europe/London",
language: { translate: t, locale: booking?.user?.locale ?? "en" },
},
attendees: attendeesList,
uid: booking.uid,
};
await sendDailyVideoRecordingEmails(evt, downloadLink);
return res.status(200).json({ message: "Success" });
}
+1 -4
View File
@@ -4,7 +4,6 @@ import type { SatoriOptions } from "satori";
import { z } from "zod";
import { Meeting, App, Generic } from "@calcom/lib/OgImages";
import { md } from "@calcom/lib/markdownIt";
const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
@@ -75,12 +74,10 @@ export default async function handler(req: NextApiRequest) {
imageType,
});
const title_ = md.render(title).replace(/(<([^>]+)>)/gi, "");
const img = new ImageResponse(
(
<Meeting
title={title_}
title={title}
profile={{ name: meetingProfileName, image: meetingImage }}
users={names.map((name, index) => ({ name, username: usernames[index] }))}
/>
-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);
+47 -12
View File
@@ -2,6 +2,7 @@ import fs from "fs";
import matter from "gray-matter";
import MarkdownIt from "markdown-it";
import type { GetStaticPaths, GetStaticPropsContext } from "next";
import Link from "next/link";
import path from "path";
import { z } from "zod";
@@ -33,7 +34,26 @@ const sourceSchema = z.object({
}),
});
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
// If it's not production environment, it would be a better idea to inform that the App is disabled.
if (props.isAppDisabled) {
if (process.env.NODE_ENV !== "production") {
// TODO: Improve disabled App UI. This is just a placeholder.
return (
<div className="p-2">
This App seems to be disabled. If you are an admin, you can enable this app from{" "}
<Link href="/settings/admin/apps" className="cursor-pointer text-blue-500 underline">
here
</Link>
</div>
);
}
// Disabled App should give 404 any ways in production.
return null;
}
const { source, data } = props;
return (
<App
name={data.name}
@@ -80,29 +100,43 @@ export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true };
const app = await prisma.app.findUnique({
const appMeta = await getAppWithMetadata({
slug: ctx.params?.slug,
});
const appFromDb = await prisma.app.findUnique({
where: { slug: ctx.params.slug.toLowerCase() },
});
if (!app) return { notFound: true };
const isAppAvailableInFileSystem = appMeta;
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
const singleApp = await getAppWithMetadata(app);
if (process.env.NODE_ENV !== "production" && isAppDisabled) {
return {
props: {
isAppDisabled: true as const,
data: {
...appMeta,
},
},
};
}
if (!singleApp) return { notFound: true };
if (!appFromDb || !appMeta || isAppDisabled) return { notFound: true };
const isTemplate = singleApp.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", app.dirName);
const isTemplate = appMeta.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH);
let source = "";
try {
source = fs.readFileSync(postFilePath).toString();
source = source.replace(/{DESCRIPTION}/g, singleApp.description);
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
} catch (error) {
/* If the app doesn't have a README we fallback to the package description */
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = singleApp.description;
source = appMeta.description;
}
const result = matter(source);
@@ -111,8 +145,8 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
data.items = data.items.map((item) => {
if (typeof item === "string") {
return getAppAssetFullPath(item, {
dirName: singleApp.dirName,
isTemplate: singleApp.isTemplate,
dirName: appMeta.dirName,
isTemplate: appMeta.isTemplate,
});
}
return item;
@@ -120,8 +154,9 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
}
return {
props: {
isAppDisabled: false as const,
source: { content, data },
data: singleApp,
data: appMeta,
},
};
};
+1 -1
View File
@@ -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}
+6 -4
View File
@@ -157,10 +157,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
};
}
let deploymentKey = await getDeploymentKey(prisma);
const deploymentKey = await prisma.deployment.findUnique({
where: { id: 1 },
select: { licenseKey: true },
});
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey) {
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) {
await prisma.deployment.upsert({
where: { id: 1 },
update: {
@@ -172,10 +175,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
agreedLicenseAt: new Date(),
},
});
deploymentKey = await getDeploymentKey(prisma);
}
const isFreeLicense = deploymentKey === "";
const isFreeLicense = (await getDeploymentKey(prisma)) === "";
return {
props: {
+3 -3
View File
@@ -62,7 +62,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
<h3 className="text-emphasis font-medium leading-6">
{t("date_overrides")}{" "}
<Tooltip content={t("date_overrides_info")}>
<span className="inline-block">
<span className="inline-block align-middle">
<Info className="h-4 w-4" />
</span>
</Tooltip>
@@ -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>
@@ -212,7 +212,7 @@ export default function Availability() {
aria-label={t("delete")}
className="hidden sm:inline"
disabled={schedule?.isLastSchedule}
tooltip={t("requires_at_least_one_schedule")}
tooltip={schedule?.isLastSchedule ? t("requires_at_least_one_schedule") : t("delete")}
/>
</DialogTrigger>
<ConfirmationDialogContent
+1 -1
View File
@@ -309,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>
+93 -86
View File
@@ -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">
@@ -487,21 +490,23 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
<EmbedButton
as={DropdownItem}
type="button"
StartIcon={Code}
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}>
{t("embed")}
</EmbedButton>
</DropdownMenuItem>
</>
)}
{!isManagedEventType && (
<DropdownMenuItem className="outline-none">
<EmbedButton
as={DropdownItem}
type="button"
StartIcon={Code}
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}>
{t("embed")}
</EmbedButton>
</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) &&
isManagedEventType && (
!isChildrenManagedEventType && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem>
@@ -533,27 +538,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 +583,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 +635,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>
+42 -3
View File
@@ -15,6 +15,7 @@ import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
import { ConnectedVideoStep } from "@components/getting-started/steps-views/ConnectedVideoStep";
import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability";
import UserProfile from "@components/getting-started/steps-views/UserProfile";
import { UserSettings } from "@components/getting-started/steps-views/UserSettings";
@@ -22,7 +23,13 @@ import { UserSettings } from "@components/getting-started/steps-views/UserSettin
export type IOnboardingPageProps = inferSSRProps<typeof getServerSideProps>;
const INITIAL_STEP = "user-settings";
const steps = ["user-settings", "connected-calendar", "setup-availability", "user-profile"] as const;
const steps = [
"user-settings",
"connected-calendar",
"connected-video",
"setup-availability",
"user-profile",
] as const;
const stepTransform = (step: (typeof steps)[number]) => {
const stepIndex = steps.indexOf(step);
@@ -36,9 +43,9 @@ const stepRouteSchema = z.object({
step: z.array(z.enum(steps)).default([INITIAL_STEP]),
});
// TODO: Refactor how steps work to be contained in one array/object. Currently we have steps,initalsteps,headers etc. These can all be in one place
const OnboardingPage = (props: IOnboardingPageProps) => {
const router = useRouter();
const { user } = props;
const { t } = useLocale();
@@ -55,6 +62,11 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
subtitle: [`${t("connect_your_calendar_instructions")}`],
skipText: `${t("connect_calendar_later")}`,
},
{
title: `${t("connect_your_video_app")}`,
subtitle: [`${t("connect_your_video_app_instructions")}`],
skipText: `${t("set_up_later")}`,
},
{
title: `${t("set_availability")}`,
subtitle: [
@@ -68,6 +80,17 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
},
];
// TODO: Add this in when we have solved the ability to move to tokens accept invite and note invitedto
// Ability to accept other pending invites if any (low priority)
// if (props.hasPendingInvites) {
// headers.unshift(
// props.hasPendingInvites && {
// title: `${t("email_no_user_invite_heading", { appName: APP_NAME })}`,
// subtitle: [], // TODO: come up with some subtitle text here
// }
// );
// }
const goToIndex = (index: number) => {
const newStep = steps[index];
router.push(
@@ -122,12 +145,15 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
{currentStep === "connected-calendar" && <ConnectedCalendars nextStep={() => goToIndex(2)} />}
{currentStep === "connected-video" && <ConnectedVideoStep nextStep={() => goToIndex(3)} />}
{currentStep === "setup-availability" && (
<SetupAvailability nextStep={() => goToIndex(3)} defaultScheduleId={user.defaultScheduleId} />
<SetupAvailability nextStep={() => goToIndex(4)} defaultScheduleId={user.defaultScheduleId} />
)}
{currentStep === "user-profile" && <UserProfile user={user} />}
</StepCard>
{headers[currentStepIndex]?.skipText && (
<div className="flex w-full flex-row justify-center">
<Button
@@ -181,6 +207,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
allowDynamicBooking: true,
defaultScheduleId: true,
completedOnboarding: true,
teams: {
select: {
accepted: true,
team: {
select: {
id: true,
name: true,
logo: true,
},
},
},
},
},
});
@@ -199,6 +237,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
...user,
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
},
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
},
};
};
+14 -7
View File
@@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getUsernameList } from "@calcom/lib/defaultEvents";
@@ -15,7 +16,8 @@ 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">
<BookerSeo username={user} eventSlug={slug} rescheduleUid={booking?.uid} />
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
</main>
);
@@ -27,8 +29,8 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const { user, type: slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { ssgInit } = await import("@server/lib/ssg");
const ssg = await ssgInit(context);
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const usernameList = getUsernameList(user);
const users = await prisma.user.findMany({
@@ -53,13 +55,15 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
await ssr.viewer.public.event.prefetch({ username: user, eventSlug: slug });
return {
props: {
booking,
user,
slug,
away: false,
trpcState: ssg.dehydrate(),
trpcState: ssr.dehydrate(),
},
};
}
@@ -67,8 +71,9 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
async function getUserPageProps(context: GetServerSidePropsContext) {
const { user: username, type: slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { ssgInit } = await import("@server/lib/ssg");
const ssg = await ssgInit(context);
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const user = await prisma.user.findUnique({
where: {
username,
@@ -89,13 +94,15 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
await ssr.viewer.public.event.prefetch({ username, eventSlug: slug });
return {
props: {
booking,
away: user?.away,
user: username,
slug,
trpcState: ssg.dehydrate(),
trpcState: ssr.dehydrate(),
},
};
}
@@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import prisma from "@calcom/prisma";
@@ -14,7 +15,8 @@ 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">
<BookerSeo username={user} eventSlug={slug} rescheduleUid={booking?.uid} />
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
</main>
);
@@ -30,8 +32,8 @@ const paramsSchema = z.object({ type: z.string(), slug: z.string() });
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { ssgInit } = await import("@server/lib/ssg");
const ssg = await ssgInit(context);
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const team = await prisma.team.findFirst({
where: {
@@ -52,14 +54,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (rescheduleUid) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
await ssr.viewer.public.event.prefetch({ username: teamSlug, eventSlug: meetingSlug });
return {
props: {
booking,
away: false,
user: teamSlug,
slug: meetingSlug,
trpcState: ssg.dehydrate(),
trpcState: ssr.dehydrate(),
},
};
};
@@ -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
+40 -28
View File
@@ -4,10 +4,12 @@ import { useRouter } from "next/router";
import type { CSSProperties } from "react";
import type { SubmitHandler } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { z } from "zod";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
@@ -16,7 +18,6 @@ import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@c
import PageWrapper from "@components/PageWrapper";
import { asStringOrNull } from "../lib/asStringOrNull";
import { IS_GOOGLE_LOGIN_ENABLED } from "../server/lib/constants";
import { ssrInit } from "../server/lib/ssr";
@@ -24,11 +25,10 @@ type FormValues = {
username: string;
email: string;
password: string;
passwordcheck: string;
apiError: string;
};
export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof getServerSideProps>) {
export default function Signup({ prepopulateFormValues, token }: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const router = useRouter();
const telemetry = useTelemetry();
@@ -76,7 +76,7 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
return (
<LicenseRequired>
<div
className="bg-muted flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8"
className="bg-muted flex min-h-screen flex-col justify-center "
style={
{
"--cal-brand": "#111827",
@@ -95,7 +95,7 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-default mx-2 px-4 py-8 shadow sm:rounded-lg sm:px-10">
<div className="bg-default mx-2 p-6 shadow sm:rounded-lg lg:p-8">
<FormProvider {...methods}>
<form
onSubmit={(event) => {
@@ -109,7 +109,7 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
}}
className="bg-default space-y-6">
{errors.apiError && <Alert severity="error" message={errors.apiError?.message} />}
<div className="space-y-2">
<div className="space-y-4">
<TextField
addOnLeading={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/`}
{...register("username")}
@@ -125,32 +125,28 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
className: "block text-sm font-medium text-default",
}}
{...register("password")}
hintErrors={["caplow", "min", "num"]}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:border-black focus:outline-none focus:ring-black sm:text-sm"
/>
<PasswordField
label={t("confirm_password")}
{...register("passwordcheck", {
validate: (value) =>
value === methods.watch("password") || (t("error_password_mismatch") as string),
})}
/>
</div>
<div className="flex space-x-2 rtl:space-x-reverse">
<Button type="submit" loading={isSubmitting} className="w-7/12 justify-center">
<Button type="submit" loading={isSubmitting} className="w-full justify-center">
{t("create_account")}
</Button>
<Button
color="secondary"
className="w-5/12 justify-center"
onClick={() =>
signIn("Cal.com", {
callbackUrl: router.query.callbackUrl
? `${WEBAPP_URL}/${router.query.callbackUrl}`
: `${WEBAPP_URL}/getting-started`,
})
}>
{t("login_instead")}
</Button>
{!token && (
<Button
color="secondary"
className="w-full justify-center"
onClick={() =>
signIn("Cal.com", {
callbackUrl: router.query.callbackUrl
? `${WEBAPP_URL}/${router.query.callbackUrl}`
: `${WEBAPP_URL}/getting-started`,
})
}>
{t("login_instead")}
</Button>
)}
</div>
</form>
</FormProvider>
@@ -163,7 +159,7 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const ssr = await ssrInit(ctx);
const token = asStringOrNull(ctx.query.token);
const token = z.string().optional().parse(ctx.query.token);
const props = {
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
@@ -221,11 +217,27 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
};
}
const guessUsernameFromEmail = (email: string) => {
const [username] = email.split("@");
return username;
};
let username = guessUsernameFromEmail(verificationToken.identifier);
if (!IS_SELF_HOSTED) {
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
const { available, suggestion } = await checkPremiumUsername(username);
username = available ? username : suggestion || username;
}
return {
props: {
...props,
token,
prepopulateFormValues: {
email: verificationToken.identifier,
username,
},
},
};
+13 -4
View File
@@ -4,7 +4,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
import { CAL_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
@@ -12,6 +12,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Avatar, AvatarGroup, Button, EmptyScreen, HeadSeo } from "@calcom/ui";
@@ -26,7 +27,7 @@ import Team from "@components/team/screens/Team";
import { ssrInit } from "@server/lib/ssr";
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
function TeamPage({ team, isUnpublished }: TeamPageProps) {
function TeamPage({ team, isUnpublished, markdownStrippedBio }: TeamPageProps) {
useTheme(team.theme);
const showMembers = useToggleQuery("members");
const { t } = useLocale();
@@ -67,6 +68,11 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) {
<div className="px-6 py-4 ">
<Link
href={`/team/${team.slug}/${type.slug}`}
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
data-testid="event-type-link"
className="flex justify-between">
<div className="flex-shrink">
@@ -100,7 +106,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) {
title={teamName}
description={teamName}
meeting={{
title: team?.bio || "",
title: markdownStrippedBio,
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
}}
/>
@@ -111,7 +117,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) {
{!isBioEmpty && (
<>
<div
className=" text-subtle text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: team.safeBio }}
/>
</>
@@ -196,10 +202,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { ...member, safeBio: markdownToSafeHTML(member.bio || "") };
});
const markdownStrippedBio = stripMarkdown(team?.bio || "");
return {
props: {
team: { ...team, safeBio, members },
trpcState: ssr.dehydrate(),
markdownStrippedBio,
},
} as const;
};
+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();
});
+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);
+8 -3
View File
@@ -94,7 +94,12 @@ testBothBookers.describe("pro user", () => {
});
});
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();
@@ -103,7 +108,7 @@ testBothBookers.describe("pro user", () => {
await page.goto("/bookings/upcoming");
await page.locator('[data-testid="cancel"]').first().click();
await page.waitForURL((url) => {
return url.pathname.startsWith("/booking");
return url.pathname.startsWith("/booking/");
});
await page.locator('[data-testid="cancel"]').click();
@@ -126,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();
+11 -2
View File
@@ -47,10 +47,19 @@ test.describe("Onboarding", () => {
// tests skip button, we don't want to test entire flow.
await page.locator("button[data-testid=skip-step]").click();
await expect(page).toHaveURL(/.*setup-availability/);
await expect(page).toHaveURL(/.*connected-video/);
});
await test.step("step 3", async () => {
const isDisabled = await page.locator("button[data-testid=save-video-button]").isDisabled();
await expect(isDisabled).toBe(true);
// tests skip button, we don't want to test entire flow.
await page.locator("button[data-testid=skip-step]").click();
await expect(page).toHaveURL(/.*setup-availability/);
});
await test.step("step 4", async () => {
const isDisabled = await page.locator("button[data-testid=save-availability]").isDisabled();
await expect(isDisabled).toBe(false);
// same here, skip this step.
@@ -59,7 +68,7 @@ test.describe("Onboarding", () => {
await expect(page).toHaveURL(/.*user-profile/);
});
await test.step("step 4", async () => {
await test.step("step 5", async () => {
await page.locator("button[type=submit]").click();
// should redirect to /event-types after onboarding
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "متى تم إلغاء هذا الحدث",
"new_event_trigger": "متى تم حجز الحدث الجديد",
"email_host_action": "إرسال رسالة إلكترونية إلى المضيف",
"email_attendee_action": "إرسال رسالة إلكترونية إلى واحد من الحضور",
"sms_attendee_action": "إرسال رسالة نصية قصيرة إلى واحد من الحضور",
"sms_number_action": "إرسال رسالة نصية قصيرة إلى رقم محدد",
"workflows": "سير العمل",
@@ -1725,7 +1724,6 @@
"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}}",
@@ -1038,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",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Členové budou vidět aktivní aplikace, ale nastavení aplikací upravovat moci nebudou",
"locked_webhooks_description": "Členové budou vidět aktivní webhooky, ale nastavení webhooků upravovat moci nebudou",
"locked_workflows_description": "Členové budou vidět aktivní pracovní postupy, ale nastavení pracovních postupů upravovat moci nebudou",
"locked_by_admin": "Uzamčeno správcem",
"app_not_connected": "Nemáte připojený účet {{appName}}.",
"connect_now": "Připojte se",
"managed_event_dialog_confirm_button_one": "Nahradit a upozornit {{count}} člena",
@@ -1010,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",
@@ -1038,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",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Mitglieder können die aktiven Apps sehen, können aber keine App-Einstellungen anpassen",
"locked_webhooks_description": "Mitglieder können die aktiven Webhooks sehen, können aber keine Webhook-Einstellungen anpassen",
"locked_workflows_description": "Mitglieder können die aktiven Workflows sehen, können aber keine Workflow-Einstellungen anpassen",
"locked_by_admin": "Von Admin gesperrt",
"app_not_connected": "Sie haben kein {{appName}}-Konto verbunden.",
"connect_now": "Jetzt verbinden",
"managed_event_dialog_confirm_button_one": "{{count}} Mitglied ersetzen & benachrichtigen",
+22 -3
View File
@@ -237,6 +237,8 @@
"set_availability": "Set your availability",
"continue_without_calendar": "Continue without calendar",
"connect_your_calendar": "Connect your calendar",
"connect_your_video_app":"Connect your video apps",
"connect_your_video_app_instructions":"Connect your video apps to use them on your event types.",
"connect_your_calendar_instructions": "Connect your calendar to automatically check for busy times and new events as they’re scheduled.",
"set_up_later": "Set up later",
"current_time": "Current time",
@@ -367,6 +369,7 @@
"create_webhook": "Create Webhook",
"booking_cancelled": "Booking Cancelled",
"booking_rescheduled": "Booking Rescheduled",
"recording_ready":"Recording Download Link Ready",
"booking_created": "Booking Created",
"meeting_ended": "Meeting Ended",
"form_submitted": "Form Submitted",
@@ -1039,7 +1042,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",
@@ -1192,6 +1195,7 @@
"create_workflow": "Create a workflow",
"do_this": "Do this",
"turn_off": "Turn off",
"turn_on": "Turn on",
"settings_updated_successfully": "Settings updated successfully",
"error_updating_settings": "Error updating settings",
"personal_cal_url": "My personal {{appName}} URL",
@@ -1248,6 +1252,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.",
@@ -1622,6 +1627,7 @@
"email_user_cta": "View Invitation",
"email_no_user_invite_heading": "You’ve been invited to join a team on {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} has invited you to join their team on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.",
"email_user_invite_subheading": "{{invitedBy}} has invited you to join their team `{{teamName}}` on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.",
"email_no_user_invite_steps_intro": "We’ll walk you through a few short steps and you’ll be enjoying stress free scheduling with your team in no time.",
"email_no_user_step_one": "Choose your username",
"email_no_user_step_two": "Connect your calendar account",
@@ -1735,7 +1741,7 @@
"locked_apps_description": "Members will be able to see the active apps but will not be able to edit any app settings",
"locked_webhooks_description": "Members will be able to see the active webhooks but will not be able to edit any webhook settings",
"locked_workflows_description": "Members will be able to see the active workflows but will not be able to edit any workflow settings",
"locked_by_admin": "Locked by admin",
"locked_by_admin": "Locked by team admin",
"app_not_connected": "You have not connected a {{appName}} account.",
"connect_now": "Connect now",
"managed_event_dialog_confirm_button_one": "Replace & notify {{count}} member",
@@ -1800,5 +1806,18 @@
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
"email_invite_team": "{{email}} has been invited",
"error_collecting_card": "Error collecting card",
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
"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",
"email_not_cal_member_cta": "Join your team",
"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."
}
@@ -1038,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",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Los miembros podrán ver las aplicaciones activas pero no podrán editar ninguna configuración de la aplicación",
"locked_webhooks_description": "Los miembros podrán ver los webhooks activos, pero no podrán editar ninguna configuración de webhooks",
"locked_workflows_description": "Los miembros podrán ver los flujos de trabajo activos, pero no podrán editar ninguna configuración de flujo de trabajo",
"locked_by_admin": "Bloqueado por el administrador",
"app_not_connected": "No ha conectado una cuenta de {{appName}}.",
"connect_now": "Conectar ahora",
"managed_event_dialog_confirm_button_one": "Reemplazar y notificar a {{count}} miembro",
+17 -3
View File
@@ -237,6 +237,8 @@
"set_availability": "Définissez vos disponibilités",
"continue_without_calendar": "Continuer sans calendrier",
"connect_your_calendar": "Connectez votre calendrier",
"connect_your_video_app": "Connectez vos applications vidéo",
"connect_your_video_app_instructions": "Connectez vos applications vidéo pour les utiliser sur vos types d'événements.",
"connect_your_calendar_instructions": "Connectez votre calendrier pour vérifier automatiquement vos disponibilités ainsi que les nouveaux événements lorsqu'ils sont planifiés.",
"set_up_later": "Configurer plus tard",
"current_time": "Heure actuelle",
@@ -367,6 +369,7 @@
"create_webhook": "Créer un Webhook",
"booking_cancelled": "Réservation annulée",
"booking_rescheduled": "Réservation replanifiée",
"recording_ready": "Lien de téléchargement d'enregistrement prêt",
"booking_created": "Réservation créée",
"meeting_ended": "Rendez-vous terminé",
"form_submitted": "Formulaire envoyé",
@@ -1039,7 +1042,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",
@@ -1192,6 +1195,7 @@
"create_workflow": "Créer un workflow",
"do_this": "Effectuer ceci",
"turn_off": "Désactiver",
"turn_on": "Activer",
"settings_updated_successfully": "Paramètres mis à jour avec succès",
"error_updating_settings": "Erreur lors de la mise à jour des paramètres",
"personal_cal_url": "Mon lien {{appName}} personnel",
@@ -1248,6 +1252,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.",
@@ -1622,6 +1627,7 @@
"email_user_cta": "Voir l'invitation",
"email_no_user_invite_heading": "Vous avez été invité(e) à rejoindre une équipe sur {{appName}}",
"email_no_user_invite_subheading": "{{invitedBy}} a été invité à rejoindre son équipe sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet, à vous et à votre équipe, de planifier des rendez-vous sans les allers-retours par e-mail.",
"email_user_invite_subheading": "{{invitedBy}} vous a invité à rejoindre son équipe « {{teamName}} » sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet, à vous et à votre équipe, de planifier des réunions sans les allers-retours par e-mail.",
"email_no_user_invite_steps_intro": "Nous vous guiderons à travers quelques étapes courtes et vous profiterez d'une planification sans stress avec votre équipe en un rien de temps.",
"email_no_user_step_one": "Choisissez votre nom d'utilisateur",
"email_no_user_step_two": "Connectez votre compte de calendrier",
@@ -1735,7 +1741,7 @@
"locked_apps_description": "Les membres pourront voir les applications actives, mais ne pourront pas modifier leurs paramètres.",
"locked_webhooks_description": "Les membres pourront voir les webhooks actifs, mais ne pourront pas modifier leurs paramètres.",
"locked_workflows_description": "Les membres pourront voir les workflows actifs, mais ne pourront pas modifier leurs paramètres.",
"locked_by_admin": "Verrouillé par l'administrateur",
"locked_by_admin": "Verrouillé par l'administrateur de l'équipe",
"app_not_connected": "Vous n'avez pas connecté de compte {{appName}}.",
"connect_now": "Connecter maintenant",
"managed_event_dialog_confirm_button_one": "Remplacer et notifier {{count}} membre",
@@ -1800,5 +1806,13 @@
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
"email_invite_team": "{{email}} a été invité",
"error_collecting_card": "Erreur lors de la collecte de la carte",
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo"
"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",
"email_not_cal_member_cta": "Rejoignez votre équipe",
"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é."
}
@@ -1014,7 +1014,6 @@
"event_cancelled_trigger": "כאשר אירוע מבוטל",
"new_event_trigger": "כאשר אירוע חדש מוזמן",
"email_host_action": "שלח מייל למארח",
"email_attendee_action": "שלח מייל למשתתף",
"sms_attendee_action": "שלח SMS למשתתף",
"sms_number_action": "שלח SMS למספר מסוים",
"workflows": "תהליכים",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "quando l'evento viene annullato",
"new_event_trigger": "quando viene prenotato un nuovo evento",
"email_host_action": "invia e-mail all'organizzatore",
"email_attendee_action": "invia e-mail al partecipante",
"sms_attendee_action": "invia SMS al partecipante",
"sms_number_action": "invia SMS a un numero specifico",
"workflows": "Flussi di lavoro",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "I membri saranno in grado di vedere le app attive, ma non saranno in grado di modificare le impostazioni delle app",
"locked_webhooks_description": "I membri saranno in grado di vedere i webhook attivi, ma non saranno in grado di modificare le impostazioni dei webhook",
"locked_workflows_description": "I membri saranno in grado di vedere i flussi di lavoro attivi, ma non saranno in grado di modificare le impostazioni dei flussi di lavoro",
"locked_by_admin": "Bloccato dall'amministratore",
"app_not_connected": "Non è stato connesso un account di {{appName}}.",
"connect_now": "Connetti ora",
"managed_event_dialog_confirm_button_one": "Sostituisci e invia notifica a {{count}} membro",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "イベントキャンセル時に",
"new_event_trigger": "新しいイベント予約時に",
"email_host_action": "ホストにメールを送信",
"email_attendee_action": "出席者にメールを送信",
"sms_attendee_action": "出席者に SMS を送信",
"sms_number_action": "特定の番号に SMS を送信",
"workflows": "ワークフロー",
@@ -1725,7 +1724,6 @@
"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}} 人のメンバーを置き換えて通知する",
+24 -3
View File
@@ -237,6 +237,7 @@
"set_availability": "언제 여유가 있는지 설정하세요.",
"continue_without_calendar": "캘린더 없이 계속하기",
"connect_your_calendar": "캘린더 연결하기",
"connect_your_video_app_instructions": "이벤트 타입에서 사용하기 위한 비디오 앱을 연결해주세요.",
"connect_your_calendar_instructions": "바쁜 시간과 새 이벤트 일정을 자동으로 확인할 수 있도록 캘린더 연결",
"set_up_later": "나중에 하기",
"current_time": "현재 시각",
@@ -465,6 +466,7 @@
"friday": "금요일",
"saturday": "토요일",
"sunday": "일요일",
"all_booked_today": "모두 예약됨.",
"slots_load_fail": "사용 가능한 시간 슬롯을 로드할 수 없습니다.",
"additional_guests": "게스트 추가",
"your_name": "당신의 이름",
@@ -1038,7 +1040,7 @@
"event_cancelled_trigger": "이벤트 취소 시",
"new_event_trigger": "새 이벤트 예약 시",
"email_host_action": "호스트에게 이메일 발송",
"email_attendee_action": "참석자에게 이메일 발송",
"email_attendee_action": "참석자들에게 이메일 전송",
"sms_attendee_action": "참석자에게 SMS 발송",
"sms_number_action": "특정 번호로 SMS 발송",
"workflows": "워크플로",
@@ -1247,6 +1249,7 @@
"calendars_description": "이벤트 타입이 캘린더와 상호 작용하는 방식 구성",
"appearance_description": "예약 모양 설정 관리",
"conferencing_description": "회의에 즐겨 사용하는 화상 회의 앱 추가",
"add_conferencing_app": "회의 앱 추가",
"password_description": "계정 비밀번호 설정 관리",
"2fa_description": "계정 비밀번호 설정 관리",
"we_just_need_basic_info": "프로필 설정을 위해 몇 가지 기본 정보만 있으면 됩니다.",
@@ -1621,6 +1624,7 @@
"email_user_cta": "초대 보기",
"email_no_user_invite_heading": "{{appName}} 팀에 초대되었습니다",
"email_no_user_invite_subheading": "{{invitedBy}} 님이 귀하를 {{appName}}에서 자신의 팀에 초대했습니다. {{appName}} 앱은 귀하와 귀하의 팀이 이메일을 주고 받지 않고 회의 일정을 잡을 수 있게 해주는 이벤트 정리 스케줄러입니다.",
"email_user_invite_subheading": "{{invitedBy}}님이 {{appName}}에서 자신의 `{{teamName}}` 팀에 가입하도록 당신을 초대했습니다. {{appName}}은 유저와 팀이 이메일을 주고 받지 않고도 회의 일정을 잡을 수 있게 하는 이벤트 조율 스케줄러입니다.",
"email_no_user_invite_steps_intro": "안내해 드릴 몇 가지 간단한 단계만 거치면 곧 팀과 함께 스트레스 없이 편리하게 일정을 관리할 수 있게 될 것입니다.",
"email_no_user_step_one": "사용자 이름 선택",
"email_no_user_step_two": "캘린더 계정 연결",
@@ -1696,6 +1700,14 @@
"spot_popular_event_types_description": "어떤 이벤트 타입이 가장 많은 클릭을 받고 예약이 되고 있는지 확인해보세요",
"no_responses_yet": "아직 응답 없음",
"this_will_be_the_placeholder": "이것은 자리 표시자가 됩니다",
"error_booking_event": "이벤트를 예약하는 도중 오류가 발생했습니다. 페이지를 새로고침한 후 다시 시도해주세요.",
"timeslot_missing_title": "시간대가 선택되지 않았습니다",
"timeslot_missing_description": "이벤트를 예약 할 시간대를 선택해주세요.",
"timeslot_missing_cta": "시간대 선택",
"switch_monthly": "월별 보기로 전환",
"switch_weekly": "주간 보기로 전환",
"switch_multiday": "일간 보기로 전환",
"num_locations": "{{num}}개의 위치 옵션",
"this_meeting_has_not_started_yet": "이 회의는 아직 시작되지 않았습니다",
"this_app_requires_connected_account": "{{appName}}에 연결된 {{dependencyName}} 계정이 필요합니다",
"connect_app": "{{dependencyName}} 연결",
@@ -1725,7 +1737,6 @@
"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}}명 바꾸기 및 알림",
@@ -1783,5 +1794,15 @@
"seats_and_no_show_fee_error": "현재 좌석을 활성화할 수 없으므로 노쇼 수수료를 부과할 수 없습니다",
"complete_your_booking": "예약을 완료하세요",
"complete_your_booking_subject": "예약 완료: {{title}} 날짜 {{date}}",
"email_invite_team": "{{email}} 이 초대되었습니다."
"currency_string": "{{amount, currency}}",
"charge_card_dialog_body": "참석자에게 {{amount, currency}}을 청구하려고 합니다. 계속 진행하시겠습니까?",
"charge_attendee": "참석자에게 {{amount, currency}}을 청구합니다.",
"payment_app_commission": "결제가 필요합니다 (건당 수수료 {{paymentFeePercentage}}% + {{fee, currency}})",
"email_invite_team": "{{email}} 이 초대되었습니다.",
"image_size_limit_exceed": "업로드되는 이미지의 크기는 5MB를 초과하면 안 됩니다.",
"need_help_embedding": "도움이 필요하신가요? Wix, Squarespace, 또는 WordPress에서 Cal을 임베드하는 가이드를 참조하거나, 자주 묻는 질문을 확인하거나, 고급 임베드 옵션을 살펴보세요.",
"disable_attendees_confirmation_emails": "참석자에 대한 기본 확인 이메일 비활성화",
"disable_attendees_confirmation_emails_description": "이 이벤트 유형에는 이벤트가 예약되었을 때 참석자들에게 이메일을 보내는 최소한 하나의 워크플로가 활성화되어 있습니다.",
"disable_host_confirmation_emails": "호스트에 대한 기본 확인 이메일 비활성화",
"disable_host_confirmation_emails_description": "이 이벤트 유형에는 이벤트가 예약될 때 호스트에게 이메일을 보내는 최소한 하나의 워크플로가 활성화되어 있습니다."
}
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "wanneer de gebeurtenis is geannuleerd",
"new_event_trigger": "wanneer een nieuwe gebeurtenis is geboekt",
"email_host_action": "e-mail naar organisator versturen",
"email_attendee_action": "e-mail naar deelnemer versturen",
"sms_attendee_action": "sms naar deelnemer versturen",
"sms_number_action": "sms naar een specifiek nummer sturen",
"workflows": "Werkstromen",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Leden kunnen de actieve apps zien, maar kunnen geen appinstellingen bewerken",
"locked_webhooks_description": "Leden kunnen de actieve webhooks zien, maar kunnen geen webhookinstellingen bewerken",
"locked_workflows_description": "Leden kunnen de actieve werkstromen zien, maar kunnen geen werkstroominstellingen bewerken",
"locked_by_admin": "Vergrendeld door beheerder",
"app_not_connected": "U heeft geen {{appName}}-account gekoppeld.",
"connect_now": "Nu koppelen",
"managed_event_dialog_confirm_button_one": "{{count}} lid vervangen en informeren",
@@ -990,7 +990,6 @@
"event_cancelled_trigger": "når hendelsen er avlyst",
"new_event_trigger": "når en ny hendelse er booket",
"email_host_action": "send e-post til vert",
"email_attendee_action": "send e-post til deltakeren",
"sms_attendee_action": "send SMS til deltakeren",
"sms_number_action": "send SMS til et bestemt nummer",
"workflows": "Arbeidsflyter",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "gdy wydarzenie zostanie odwołane",
"new_event_trigger": "gdy nowe wydarzenie zostanie zarezerwowane",
"email_host_action": "wyślij wiadomość e-mail do hosta",
"email_attendee_action": "wyślij wiadomość e-mail do uczestnika",
"sms_attendee_action": "wyślij SMS do uczestnika",
"sms_number_action": "wyślij wiadomość SMS na określony numer",
"workflows": "Przepływy pracy",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Członkowie będą mogli zobaczyć aktywne aplikacje, ale nie będą mogli zmieniać ich ustawień.",
"locked_webhooks_description": "Członkowie będą mogli zobaczyć aktywne webhooki, ale nie będą mogli zmieniać ich ustawień.",
"locked_workflows_description": "Członkowie będą mogli zobaczyć aktywne przepływy pracy, ale nie będą mogli zmieniać ich ustawień.",
"locked_by_admin": "Zablokowane przez administratora",
"app_not_connected": "Nie połączono konta aplikacji {{appName}}.",
"connect_now": "Połącz teraz",
"managed_event_dialog_confirm_button_one": "Zastąp i poinformuj {{count}} członka",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "quando o evento é cancelado",
"new_event_trigger": "quando um novo evento for reservado",
"email_host_action": "enviar e-mail para o host",
"email_attendee_action": "enviar e-mail para o participante",
"sms_attendee_action": "enviar SMS para participante",
"sms_number_action": "enviar SMS para um número específico",
"workflows": "Fluxos de trabalho",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Os membros poderão ver os aplicativos ativos, mas não poderão editar as configurações",
"locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar as configurações",
"locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar as configurações",
"locked_by_admin": "Bloqueado pelo administrador",
"app_not_connected": "Você não conectou uma conta do {{appName}}.",
"connect_now": "Conectar agora",
"managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro",
@@ -599,7 +599,7 @@
"count_members_one": "{{count}} membro",
"count_members_other": "{{count}} membros",
"no_assigned_members": "Sem membros atribuídos",
"assigned_to": "Atribuído a",
"assigned_to": "Asignado a",
"start_assigning_members_above": "Comece a atribuir membros acima",
"locked_fields_admin_description": "Os membros não poderão editar isto",
"locked_fields_member_description": "Esta opção foi bloqueada pelo administrador da equipa",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "quando o evento for cancelado",
"new_event_trigger": "quando um novo evento for reservado",
"email_host_action": "enviar email ao anfitrião",
"email_attendee_action": "enviar email ao participante",
"sms_attendee_action": "enviar SMS ao participante",
"sms_number_action": "enviar SMS para um número específico",
"workflows": "Fluxos de trabalho",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Os membros poderão ver as aplicações ativas, mas não poderão editar quaisquer configurações das aplicações",
"locked_webhooks_description": "Os membros poderão ver os webhooks ativos, mas não poderão editar quaisquer configurações dos webhooks",
"locked_workflows_description": "Os membros poderão ver os fluxos de trabalho ativos, mas não poderão editar quaisquer configurações dos fluxos de trabalho",
"locked_by_admin": "Bloqueado pelo administrador",
"app_not_connected": "Não associou uma conta {{appName}}.",
"connect_now": "Associar agora",
"managed_event_dialog_confirm_button_one": "Substituir e notificar {{count}} membro",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "când se anulează evenimentul",
"new_event_trigger": "când este rezervat noul eveniment",
"email_host_action": "trimiteți e-mail la gazdă",
"email_attendee_action": "trimiteți e-mail la participant",
"sms_attendee_action": "trimiteți SMS la participant",
"sms_number_action": "trimiteți SMS la un anumit număr",
"workflows": "Fluxuri de lucru",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Membrii vor putea vedea aplicațiile active, însă nu le vor putea modifica deloc setările",
"locked_webhooks_description": "Membrii vor putea vedea webhook-urile active, însă nu le vor putea modifica deloc setările",
"locked_workflows_description": "Membrii vor putea vedea fluxurile de lucru active, însă nu le vor putea modifica deloc setările",
"locked_by_admin": "Blocat de administrator",
"app_not_connected": "Nu ați conectat un cont {{appName}}.",
"connect_now": "Conectați-l acum",
"managed_event_dialog_confirm_button_one": "Înlocuiți și anunțați {{count}} membru",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "когда событие отменено",
"new_event_trigger": "когда будет забронировано новое событие",
"email_host_action": "отправить письмо организатору",
"email_attendee_action": "отправить письмо участнику",
"sms_attendee_action": "отправить SMS-сообщение участнику",
"sms_number_action": "отправка SMS-сообщение на указанный номер",
"workflows": "Рабочие процессы",
@@ -1725,7 +1724,6 @@
"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}} участника",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "kada je događaj otkazan",
"new_event_trigger": "kada je zakazan novi događaj",
"email_host_action": "pošalji imejl domaćinu",
"email_attendee_action": "pošalji imejl polazniku",
"sms_attendee_action": "pošalji SMS polazniku",
"sms_number_action": "pošalji SMS na određeni broj",
"workflows": "Radni tokovi",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju bilo kakva podešavanja aplikacije",
"locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju bilo kakva podešavanja webhook-ova",
"locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju bilo kakva podešavanja radnih tokova",
"locked_by_admin": "Zaključao administrator",
"app_not_connected": "Niste povezali {{appName}} nalog.",
"connect_now": "Povežite odmah",
"managed_event_dialog_confirm_button_one": "Zameni i obavesti {{count}} člana",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "när händelsen ställs in",
"new_event_trigger": "när ny händelse bokas",
"email_host_action": "skicka e-post till värd",
"email_attendee_action": "skicka e-post till deltagare",
"sms_attendee_action": "skicka SMS till deltagare",
"sms_number_action": "skicka SMS till ett specifikt nummer",
"workflows": "Arbetsflöden",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Medlemmarna kommer att kunna se de aktiva apparna, men de kommer inte att kunna ändra appinställningarna",
"locked_webhooks_description": "Medlemmarna kommer att kunna se aktiva webhooks men inte kunna redigera inställningar för webhooks",
"locked_workflows_description": "Medlemmarna kommer att kunna se de aktiva arbetsflödena, men de kommer inte att kunna ändra några arbetsflödesinställningar",
"locked_by_admin": "Låst av administratör",
"app_not_connected": "Du har inte anslutit ett {{appName}}-konto.",
"connect_now": "Anslut nu",
"managed_event_dialog_confirm_button_one": "Ersätt och meddela {{count}} medlem",
@@ -1038,7 +1038,6 @@
"event_cancelled_trigger": "etkinlik iptal edildiğinde",
"new_event_trigger": "yeni bir etkinlik için rezervasyon yapıldığında",
"email_host_action": "organizatöre e-posta gönder",
"email_attendee_action": "katılımcıya e-posta gönder",
"sms_attendee_action": "katılımcıya SMS gönder",
"sms_number_action": "belirli bir numaraya SMS gönder",
"workflows": "İş akışları",
@@ -1725,7 +1724,6 @@
"locked_apps_description": "Üyeler aktif uygulamaları görebilir, ancak hiçbir uygulama ayarını düzenleyemez",
"locked_webhooks_description": "Üyeler aktif web kancalarını görebilir, ancak hiçbir web kancası ayarını düzenleyemez",
"locked_workflows_description": "Üyeler aktif iş akışlarını görebilir, ancak hiçbir iş akışı ayarını düzenleyemez",
"locked_by_admin": "Yönetici tarafından kilitlendi",
"app_not_connected": "Bir {{appName}} hesabı bağlamadınız.",
"connect_now": "Hemen bağlan",
"managed_event_dialog_confirm_button_one": "{{count}} üyeyi değiştirin ve bilgilendirin",

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