Compare commits

...
97 Commits
Author SHA1 Message Date
Alex van Andel 865663646a v2.5.5
release.yml / release (push) Canceled after 0s
2023-01-24 16:54:35 +00:00
Alex van Andel b7c92063c2 Revert "v2.5.4"
This reverts commit 4f293edb6b.
2023-01-24 16:54:05 +00:00
Alex van Andel 4f293edb6b v2.5.4 2023-01-24 16:52:33 +00:00
Ben HybertandAlex van Andel 0c88cf75e0 corrected avatars inside users block (#6667)
* corrected avatars inside users block

* ensured the team avatar didnt shrink too

* Update packages/ui/components/avatar/Avatar.tsx

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-24 16:10:43 +00:00
Nafees NazikandUdit Takkar 0b93647209 fix: double border issue in Location Select (#6646)
* fix: border issue

* fix: remove border from dropdown

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-01-24 17:05:35 +01:00
Alex van Andel b931b00714 Add env variables to turbo.json relevant to the website build (#6654) 2023-01-24 15:47:46 +00:00
Nafees Nazik 796a5f8d51 feat: use cal video as the default location (#6643)
* feat: use cal video as default while creating new events

* feat: use cal video as default value

* chore: remove default value

* fix: default value logic

* chore: remove console.log
2023-01-24 15:28:40 +00:00
sean-brydon 905a110377 Invites (#6665) 2023-01-24 15:27:05 +00:00
Alex van Andel 6ccc19985f Add sizePropsBySize to avatarPrimitive root (#6666) 2023-01-24 14:01:27 +00:00
Udit Takkar 8f71474303 fix: add allowOverflow property (#6636)
* fix: add allowOverlfow property

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

* fix: date override input dialog

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-24 14:00:45 +00:00
zomars 5f351230ff Ignores auth submodule 2023-01-23 16:50:17 -07:00
Alex van Andel 3e005919c1 Only return enabled apps in getEnabledApps (#6653) 2023-01-23 16:27:20 -07:00
cfa8fd8b67 Reduce bundle size by importing single icons at a time (#6644)
* Removed barrel import for icons to reduce bundle size.

* Fixed replacement mistakes

* Reverted unneccesary yarn.lock updates

* Added some missed Icon. import conversions

* Remove merge artifact import in @calcom/ui

* Don't import Icon in pages/[user]

* Update packages/ui/package.json

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-23 23:08:01 +00:00
Alex van Andel a888be6e04 Support both https/git by detection of origin root (#6651)
* Support both https/git by detection of origin root

* Remove unnecessary .

* Minor comment fixup
2023-01-23 23:05:53 +00:00
Joe Au-Yeung 6aa2cba3ce Msteams access token refresh (#6607)
* Add is valid call

* WIP

* Change logic for Microsoft refresh token

* remove async
2023-01-23 15:36:24 -07:00
sean-brydon 12ab615684 Fix weird avatar issue (#6645) 2023-01-23 15:48:40 +00:00
1d792a2c6c generic <UpgradeScreen> component (#6594)
* first attempt of <UpgradeScreen>

* changes to icons

* reverted changes back to initial state, needs fix: teams not showing

* WIP

* Fix weird reactnode error

* Fix loading text

* added upgradeTip to routing forms

* icon colors

* create and use hook to check if user has team plan

* use useTeamPlan for upgradeTeamsBadge

* replace huge svg with compressed jpeg

* responsive fixes

* Update packages/ui/components/badge/UpgradeTeamsBadge.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Give team plan features to E2E tests

* Allow option to make a user part of team int ests

* Remove flash of paywall for team user

* Add team user for typeform tests as well

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-01-23 10:58:41 +01:00
Hariom Balhara 9602ac29fb Fix Ga4 tracking (#6630) 2023-01-23 09:09:16 +00:00
Leo Giovanetti 5714471b9a Temporary fix (#6626)
release.yml / release (push) Canceled after 0s
2023-01-22 17:27:27 +00:00
Mohammed CherfaouiandAlex van Andel a99e93a4bd fix tooltip and modal stacking issues (#6491)
* fix tooltip and modal stacking issues

* use z-index in larger screens and less

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-22 20:31:25 +05:30
Udit Takkar 7f044ff92f fix: use items-center (#6618) 2023-01-22 13:36:07 +01:00
Alex van Andel 17a0a61313 v2.5.3
release.yml / release (push) Canceled after 0s
2023-01-22 00:06:41 +00:00
Alex van Andel 12b7739c9a Bugfix/dropdown menu trigger as child remove class names (#6614)
* Fix UsernameTextfield to take right height

* Remove className side-effect

* Incorrect resolution version fixed

* Converted mobile DropdownMenuTrigger styles into Button
2023-01-21 17:03:59 -07:00
Alex van Andel ff283a25d2 Reduce the avatar size to 'sm' for now 2023-01-21 22:09:32 +00:00
sean-brydon 4b343f20a4 Update booking filters design (#6543)
* Update booking filters

* Add filter on YOUR bookings

* Fix pending members showing up in list
2023-01-21 18:15:59 +01:00
Udit Takkar 5bb6904ffd feat: add LocationSelect component (#6571)
* feat: add LocationSelect component

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

* fix: type error

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

* chore: type error

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-21 16:52:21 +00:00
sean-brydon 0e04f5d338 Fix FAB (#6611) 2023-01-21 16:14:08 +00:00
Nafees Nazik da0ea5d9d0 fix: case-sensitivity in apps path (#6552)
* fix: lowercase slug

* fix: make fallback blocking
2023-01-21 16:13:55 +00:00
Peer Richelsen 1a19261b8f remove 1 on 1 icon (#6609)
* removed 1-on-1 badge

* changed user to users for group events
2023-01-21 16:07:45 +00:00
Luis Cadillo 55a6c65db6 fix: remove annoying outline when <Button /> clicked (#6537)
* fix: remove annoying outline when <Button /> clicked

* Delete yarn.lock
2023-01-21 16:07:30 +00:00
Alex van Andel c9ed3388ae Regenerated lockfile without upgrade (#6610) 2023-01-21 15:49:35 +00:00
2588b7ef84 Harsh/add member invite (#6598)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>
2023-01-21 16:32:00 +01:00
7ce83542e2 [CAL-811] Avatar icon not redirecting user back to the main page (#6586)
* Remove cursor-pointer, remove old Avatar* files

* Fixed styling for checkedSelect + some cleanup

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-21 11:41:49 +00:00
Alex van Andel 0bb0883e64 Fix string equality check in git-setup.sh 2023-01-21 06:24:29 +00:00
zomars d3be62fbdd Yarn lock fixes 2023-01-20 16:28:22 -07:00
zomars 736308e43a Test fixes 2023-01-20 15:10:09 -07:00
Peer Richelsen 791927e45e Update package.json
release.yml / release (push) Canceled after 0s
2023-01-20 23:06:43 +01:00
Alex van Andel fa984e1568 Changed a neutral- classes to gray (#6603)
* Changed a neutral- classes to gray

* Changed all border-1 to border
2023-01-20 23:04:58 +01:00
Peer Richelsen 9b197da5be Update README.md
release.yml / release (push) Canceled after 0s
2023-01-20 21:58:11 +01:00
Peer Richelsen b7c316b01b Update README.md 2023-01-20 21:57:52 +01:00
Udit Takkar 5ab56310bf fix: add delete in dropdown (#6599)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-20 14:57:14 -05:00
Udit Takkar 52e318ca18 fix: show button on empty string (#6601)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-20 12:27:58 -07:00
Omar López 8d078564dd Bye bye submodules (#6585)
* WIP

* Uses ssh instead

* Update .gitignore

* Update .gitignore

* Update Makefile

* Update git-setup.sh

* Update git-setup.sh

* Replaced Makefile with bash script

* Update package.json
2023-01-20 12:17:51 -07:00
7b5482e83d [CAL-808] /availability/single - UI issue on buttons beside time inputs (#6561)
* [CAL-808] /availability/single - UI issue on buttons beside time inputs

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

* Update packages/features/schedules/components/Schedule.tsx

* create new translation for tooltip

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-20 17:56:28 +00:00
sean-brydon 23086476b3 Tailwind Merge (#6596)
* Tailwind Merge

* Fix merge classNames
2023-01-20 15:19:12 +01:00
Nafees Nazik 00cab5c41f fix: react-select overflow issue in some modals. (#6587)
* feat: add a disable overflow prop

* feat: use the disable overflow prop
2023-01-20 13:29:37 +00:00
sean-brydon fbc27e1d52 Fix booking filters not working for admin (#6576) 2023-01-19 19:23:58 +01:00
Nafees Nazik e1c9a8cbfc fix: i18n placeholder in awaiting approval email (#6573) 2023-01-19 16:56:44 +01:00
Nafees Nazik 8ee109e975 fix: app list item ui issues in /settings/my-account/conferencing (#6565)
* fix: border color

* fix: padding and title line-height

* feat: add app-list card

* feat: use the AppListCard component

* nit: remove unused imports
2023-01-19 15:02:01 +00:00
sean-brydonandAlex van Andel cfaecc3e02 Icon small button variant (#6525)
* Icon small button variant

* Rename

* Update CVA + Update button WIP

* add ticket ref and fix padding

* Update button and all uses of Icon button

* SB deployment test

* Fixing type error

* Fix lint

* Add icon button to canvas

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-19 14:55:32 +00:00
Syed Ali Shahbaz 18246e0dc1 path.join causing issues on windows so we keep path like the other exports (#6568) 2023-01-19 13:21:51 +00:00
sean-brydon 6dcb16724c Fixing tabs again (#6566) 2023-01-19 12:05:00 +00:00
d4c5a906b5 App Store Templates (#5289)
* Start by moving what we have to _templates

* WIP

* WIP

* Add create/edit/delete template commands

* Type fixes cli

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-01-18 15:30:25 -07:00
Alex van Andel 27e8d73b82 Revert "experiment: disco border (#6531)"
This reverts commit b5f274d663.
2023-01-18 19:29:36 +00:00
Alex van Andel 25eae4a546 find indexes instead of just one (#6547) 2023-01-18 12:26:07 -07:00
Udit Takkar f663774fee feat: add most popular section in apps (#6539)
* feat: add most popular section in apps

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

* chore: remove margin

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

* fix: merge conflict

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-18 11:48:10 -07:00
Nafees NazikandLeo Giovanetti e2258fba27 Feat: webhook form UX improvement (#6448)
* feat: add type and required attribute

* feat: disable ping button when subscriberUrl is
not present

Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-01-18 16:02:20 +00:00
Carina WollendorferandCarinaWolli d0d8878f34 Adds ability to add custom sender names for workflow emails (#6463)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-18 15:32:39 +01:00
Udit Takkar 59ff6c35ee fix: limits tab in event types (#6545)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-18 12:58:13 +01:00
Alex van Andel 3c07e305ce Create local next.config.js, add storybook-static to .gitignore (#6544) 2023-01-18 11:54:03 +00:00
Alex van Andel b5f274d663 experiment: disco border (#6531)
* Had a play, looking good (now needs isLoading state or however we want to do that)

* Also add tailwind config..

* Don't show conic bg on lightmode

* Slow down animation, show on light mode

* Apply disco border both on light/dark mode & only on success page

* 5s->8s animation speed, remove border crisping up the corners
2023-01-18 10:40:06 +00:00
Peer Richelsen 6c3ae984fc add tracking URL to LicenseRequired (#6526) 2023-01-17 21:49:40 +00:00
Mohammed Cherfaoui a2e9bf0504 fix toast dimensions issues (#6522) 2023-01-18 02:45:04 +05:30
GitStart-Cal.comandgitstart-calcom 8aa089efe6 [CAL-760] Emails: Design Tokens are not update to 2.0 (#6530)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-17 22:14:16 +01:00
sean-brydonandzomars de64d3d9d4 Move exports up a folder (#6519)
* Move exports up a folder

* Fix imports

* Fix imports again

Co-authored-by: zomars <zomars@me.com>
2023-01-17 12:10:47 -07:00
Udit Takkar 71833c0737 fix: type error and border (#6520)
* fix: type error and border

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

* fix: sepeator

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-17 17:12:22 +00:00
Udit Takkar c78f4deaf7 fix: use suffix in text field (#6532)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-17 18:08:00 +01:00
Hariom Balhara 24fc7d611f Add routing-forms README (#6506) 2023-01-17 17:58:10 +01:00
sean-brydonandHariom Balhara 8afbc3b457 Fixing horizontal tabs (#6454)
* Fix spacing and height

* Testing if its timing out or something else

* So, timep=out is the problem. Increase the timeout to 1s for now

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-01-17 16:36:06 +00:00
sean-brydon da18aae21c Adding rtl support for SB (#6523) 2023-01-17 13:38:14 +01:00
Nafees Nazik 87a0d533ef fix: german translation (#6512) 2023-01-17 13:35:35 +01:00
Nafees Nazik 7cdd453953 fix: button shifting while focusing (#6521)
* fix: remove border on focus

* refactor: remove dark mode styles
2023-01-17 12:21:05 +00:00
Omar López 8143816c18 v2.5.0
release.yml / release (push) Canceled after 0s
2023-01-16 15:34:47 -07:00
Nafees Nazik 8243ac3c00 Fix: modal content hidden (#6508) 2023-01-16 18:08:34 +01:00
Alex van Andel 207e120d66 Bring back PublicShell (#6505) 2023-01-16 14:59:54 +01:00
Ronit PandaandPeer Richelsen b25d04da1c fix: tablet sidebar profile being too wide and not centered (#6490)
* feat: updated app list items styling issues inside settings/conferencing

* feat: fixed tablet sidebar width and not centered issue

* fix: removed un-related changes from this pr

* deleted .env

* fix: added the accidentaly deleted .env file

* fix: removed unwanted .env file from the pr

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-16 12:57:38 +00:00
Alex van Andel 7064acf756 Don't update the field when changing the select (#6486)
* Don't update the field when changing the select

* Fixing missing label

* Applied the correct y-8 vertical margin

* Changed the description vertical offset from title a bit more

* Add Trash icon on Availability list dropdown
2023-01-16 12:18:18 +00:00
Alex van Andel 716407ad90 Added fallback to hostsFixed too which is used in COLLECTIVE (#6500) 2023-01-16 10:46:06 +00:00
GitStart-Cal.comandgitstart-calcom af0b08bb2b [CAL-677] /event-type : Event Setup sidebar item doesn't reflect multiple durations (#6390)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-16 11:45:54 +01:00
Hariom Balhara 3e27735ccb Disable redirect for cancellation page (#6498) 2023-01-16 09:20:50 +00:00
Nafees Nazik 76c087f29c fix: wrong i18n placeholder in many locales (#6496)
* fix: czech translation

* fix: spanish translation

* fix: italian translation

* fix: japanese translation

* fix: korean translation

* fix: dutch translation

* fix: norwegian translation

* fix: polish translation

* fix: portuguese translation

* fix: portuguese translation

* fix: romanian translation

* fix: russian translation

* fix: serbian translation

* fix: swedish translation

* fix: turkish translation

* fix: ukrainian translation

* fix: vietnamese translation

* fix: chinese simplified translation

* fix: chinese traditional translation
2023-01-16 08:48:07 +00:00
Peer RichelsenandAlex van Andel e05bcf98c2 readding sidebar tips for everyone, added 3 more features to teams empty screen (#6475)
* readding sidebar tips for everyone

* added 3 more features to teams plan

* responsive fixes

* responsive fixes

* small responsive fix

* CTA alignment fixed

* Minor alignment fixes to Shell for event-types/[type]

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-15 18:54:52 +01:00
Udit TakkarandPeer Richelsen 636e88d467 fix: styling of dropdown component (#6440)
* fix: styling of dropdown component

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

* Apply suggestions from code review

* fix: use dropdown item in all pages

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

* chore: undo

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-15 11:40:40 +00:00
Nafees Nazik 34a61214d3 fix: app store spacing issues (#6481)
* feat: add equal spacing with gap

* fix: title style

* fix: title style

* fix: remove margin

* fix: remove margin

* fix: sub header spacing and slider button

* fix: category card text

* fix: app store spacing issues

* fix: tab in tablet

* fix: shell
2023-01-14 20:11:28 +01:00
Nafees Nazik b1af30e026 fix: german translation for reschedule booking email subject (#6483)
* fix: german translation for reschedule subject

* fix: add a full stop
2023-01-14 19:10:30 +01:00
Nafees Nazik 870b59d617 fix: darkmode in booking/[id] page (#6458)
* fix: text-area style

* fix: border styles
2023-01-14 16:49:15 +01:00
Peer Richelsen e25094ccab i18n udpate 2023-01-14 02:12:07 +01:00
Carina WollendorferandCarinaWolli 1376f0991f Reusable Upgrade component for teams only features (#6473)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-14 01:48:19 +01:00
443329b99f disables signups via env variable (#6212)
* disables signups via env variable

* Apply suggestions from code review

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-13 17:42:13 -07:00
Joe Au-Yeung a1fc4ec26d Disable email input if new user invited to team (#6471) 2023-01-13 17:21:57 -07:00
Udit TakkarandPeer Richelsen d462298e79 fix: remove website url in download recording (#6466)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-14 00:03:13 +00:00
Nafees NazikandPeer Richelsen 965cff38c2 fix: HOSTED_CAL_FEATURES env doesn't get passed to the frontend (#6392)
* fix: rename HOSTED_CAL_FEATURES into NEXT_PUBLIC_HOSTED_CAL_FEATURES

* chore: add the new env to example

* fix: docs link

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-13 23:51:37 +00:00
Joe Au-Yeungandkodiakhq[bot] f96e909dce Team invites - transform email or username to lower case (#6470)
* Fix string

* Use current hashPassword

* Transform email to lowercase

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-13 18:58:41 +00:00
Leo Giovanetti a4d6f7da62 Fixing GCal emailing notif and sequential deleting (#6460) 2023-01-13 11:29:42 -07:00
Nafees Nazikandkodiakhq[bot] ce9413e0dd fix: missing empty state in /apps/installed/web3 (#6377)
* refactor: turn enum into const assertion

* feat: add web3 to icon list

* fix: type

* fix: type cast query params and add category list

* fix: conditional rendering

* feat: add i18n keys

* fix: type errors

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-13 14:12:43 +00:00
Peer Richelsen 8fc0dfb763 booking page: only scroll to bottom on mobile (#6453)
* move useMediaQuery into packages

* revert logo.tsx
2023-01-13 17:40:02 +05:30
690edcf0a7 New Crowdin translations by Github Action (#6444)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-12 23:21:06 +00:00
460 changed files with 5869 additions and 5000 deletions

No files matched your search

+9 -1
View File
@@ -31,11 +31,14 @@ NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js' NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
# To enable SAML login, set both these variables # To enable SAML login, set both these variables
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login # @see https://github.com/calcom/cal.com/tree/main/packages/features/ee#setting-up-saml-login
# SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml" # SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml"
SAML_DATABASE_URL= SAML_DATABASE_URL=
# SAML_ADMINS='pro@example.com' # SAML_ADMINS='pro@example.com'
SAML_ADMINS= SAML_ADMINS=
# NEXT_PUBLIC_HOSTED_CAL_FEATURES=1
NEXT_PUBLIC_HOSTED_CAL_FEATURES=
# If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line. # If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line.
# @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js # @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
# PGSSLMODE='no-verify' # PGSSLMODE='no-verify'
@@ -79,6 +82,7 @@ SEND_FEEDBACK_EMAIL=
# Used for email reminders in workflows and internal sync services # Used for email reminders in workflows and internal sync services
SENDGRID_API_KEY= SENDGRID_API_KEY=
SENDGRID_EMAIL= SENDGRID_EMAIL=
NEXT_PUBLIC_SENDGRID_SENDER_NAME=
# Twilio # Twilio
# Used to send SMS reminders in workflows # Used to send SMS reminders in workflows
@@ -86,6 +90,7 @@ TWILIO_SID=
TWILIO_TOKEN= TWILIO_TOKEN=
TWILIO_MESSAGING_SID= TWILIO_MESSAGING_SID=
TWILIO_PHONE_NUMBER= TWILIO_PHONE_NUMBER=
# For NEXT_PUBLIC_SENDER_ID only letters, numbers and spaces are allowed (max. 11 characters)
NEXT_PUBLIC_SENDER_ID= NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID= TWILIO_VERIFY_SID=
@@ -149,3 +154,6 @@ SENTRY_IGNORE_API_RESOLUTION_ERROR=
NEXT_PUBLIC_APP_NAME="Cal.com" NEXT_PUBLIC_APP_NAME="Cal.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com" NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc." NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
# Set this to true in to disable new signups
# NEXT_PUBLIC_DISABLE_SIGNUP=true
NEXT_PUBLIC_DISABLE_SIGNUP=
+7
View File
@@ -80,3 +80,10 @@ apps/storybook/build-storybook.log
# Snaplet # Snaplet
.snaplet/snapshots .snaplet/snapshots
.snaplet/structure.d.ts .snaplet/structure.d.ts
# Submodules
.gitmodules
apps/api
apps/website
apps/console
apps/auth
-12
View File
@@ -1,12 +0,0 @@
[submodule "apps/console"]
path = apps/console
url = https://github.com/calcom/console.git
branch = main
[submodule "apps/api"]
path = apps/api
url = https://github.com/calcom/api.git
branch = main
[submodule "apps/website"]
path = apps/website
url = https://github.com/calcom/website.git
branch = main
+7 -11
View File
@@ -305,6 +305,12 @@ Currently Vercel Pro Plan is required to be able to Deploy this application with
See the [roadmap project](https://cal.com/roadmap) for a list of proposed features (and known issues). You can change the view to see planned tagged releases. See the [roadmap project](https://cal.com/roadmap) for a list of proposed features (and known issues). You can change the view to see planned tagged releases.
<!-- RORADMAP -->
## Repo Activity
<img width="100%" src="https://repobeats.axiom.co/api/embed/6bfca2f20f39738048b6e70ca205efde46352c3d.svg" />
<!-- CONTRIBUTING --> <!-- CONTRIBUTING -->
## Contributing ## Contributing
@@ -405,17 +411,6 @@ following
9. Click the "Save" button at the bottom footer. 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. 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 Vital API Keys
1. Open [Vital](https://tryvital.io/) and click Get API Keys.
1. Create a team with the team name you desire
1. Head to the configuration section on the sidebar of the dashboard
1. Click on API keys and you'll find your sandbox `api_key`.
1. Copy your `api_key` to `VITAL_API_KEY` in the .env.appStore file.
1. Open [Vital Webhooks](https://app.tryvital.io/team/{team_id}/webhooks) and add `<CALCOM BASE URL>/api/integrations/vital/webhook` as webhook for connected applications.
1. Select all events for the webhook you interested, e.g. `sleep_created`
1. Copy the webhook secret (`sec...`) to `VITAL_WEBHOOK_SECRET` in the .env.appStore file.
## Workflows ## Workflows
### Setting up SendGrid for Email reminders ### Setting up SendGrid for Email reminders
@@ -425,6 +420,7 @@ following
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 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 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 ### Setting up Twilio for SMS reminders
Submodule apps/api deleted from 7aebdb8c96.
Submodule apps/console deleted from 9aac72159e.
+1
View File
@@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
storybook-static
dist dist
dist-ssr dist-ssr
*.local *.local
+4 -2
View File
@@ -11,13 +11,15 @@ module.exports = {
"@storybook/addon-links", "@storybook/addon-links",
"@storybook/addon-essentials", "@storybook/addon-essentials",
"@storybook/addon-interactions", "@storybook/addon-interactions",
"storybook-addon-rtl-direction",
"storybook-react-i18next", "storybook-react-i18next",
{ "storybook-addon-next",
/*{
name: "storybook-addon-next", name: "storybook-addon-next",
options: { options: {
nextConfigPath: path.resolve(__dirname, "../../web/next.config.js"), nextConfigPath: path.resolve(__dirname, "../../web/next.config.js"),
}, },
}, },*/
], ],
framework: "@storybook/react", framework: "@storybook/react",
core: { core: {
+45
View File
@@ -0,0 +1,45 @@
const withBundleAnalyzer = require("@next/bundle-analyzer");
const withTM = require("next-transpile-modules")([
"@calcom/app-store",
"@calcom/dayjs",
"@calcom/emails",
"@calcom/trpc",
"@calcom/embed-core",
"@calcom/embed-react",
"@calcom/features",
"@calcom/lib",
"@calcom/prisma",
"@calcom/ui",
]);
const glob = require("glob");
const plugins = [];
plugins.push(withTM, withBundleAnalyzer({ enabled: process.env.ANALYZE === "true" }));
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ["www.datocms-assets.com"],
formats: ["image/avif", "image/webp"],
},
typescript: {
ignoreBuildErrors: true,
},
experimental: { images: { allowFutureImage: true } },
eslint: {
ignoreDuringBuilds: true,
},
webpack: (config, { isServer }) => {
if (!isServer) {
// don't resolve 'fs' module on the client to prevent this error on build --> Error: Can't resolve 'fs'
config.resolve.fallback = {
fs: false,
};
}
return config;
},
};
module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
+2 -1
View File
@@ -22,7 +22,8 @@
"@radix-ui/react-tooltip": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0",
"next": "^13.1.1", "next": "^13.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"storybook-addon-rtl-direction": "^0.0.19"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.19.6", "@babel/core": "^7.19.6",
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui"; import { FiX } from "@calcom/ui/components/icon";
export default function AddToHomescreen() { export default function AddToHomescreen() {
const { t } = useLocale(); const { t } = useLocale();
@@ -40,7 +40,7 @@ export default function AddToHomescreen() {
type="button" type="button"
className="-mr-1 flex rounded-md p-2 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white"> className="-mr-1 flex rounded-md p-2 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white">
<span className="sr-only">{t("dismiss")}</span> <span className="sr-only">{t("dismiss")}</span>
<Icon.FiX className="h-6 w-6 text-white" aria-hidden="true" /> <FiX className="h-6 w-6 text-white" aria-hidden="true" />
</button> </button>
</div> </div>
</div> </div>
+35
View File
@@ -0,0 +1,35 @@
import { ReactNode } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge, ListItemText } from "@calcom/ui";
interface AppListCardProps {
logo?: string;
title: string;
description: string;
actions?: ReactNode;
isDefault?: boolean;
}
export default function AppListCard(props: AppListCardProps) {
const { t } = useLocale();
const { logo, title, description, actions, isDefault } = props;
return (
<div className="p-4">
<div className="flex items-center gap-x-3">
{logo ? <img className="h-10 w-10" src={logo} alt={`${title} logo`} /> : null}
<div className="flex grow flex-col gap-y-1 truncate">
<div className="flex items-center gap-x-2">
<h3 className="truncate text-sm font-semibold text-gray-900">{title}</h3>
{isDefault ? <Badge variant="green">{t("default")}</Badge> : null}
</div>
<ListItemText component="p">{description}</ListItemText>
</div>
{actions}
</div>
</div>
);
}
+11 -11
View File
@@ -12,7 +12,6 @@ import {
DialogClose, DialogClose,
DialogContent, DialogContent,
HorizontalTabs, HorizontalTabs,
Icon,
InputLeading, InputLeading,
Label, Label,
showToast, showToast,
@@ -20,6 +19,7 @@ import {
TextArea, TextArea,
TextField, TextField,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiCode, FiTrello, FiSun, FiArrowLeft, FiChevronRight } from "@calcom/ui/components/icon";
import ColorPicker from "@components/ui/colorpicker"; import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select"; import Select from "@components/ui/form/Select";
@@ -488,7 +488,7 @@ const tabs = [
{ {
name: "HTML", name: "HTML",
href: "embedTabName=embed-code", href: "embedTabName=embed-code",
icon: Icon.FiCode, icon: FiCode,
type: "code", type: "code",
Component: forwardRef< Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null, HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -541,7 +541,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
{ {
name: "React", name: "React",
href: "embedTabName=embed-react", href: "embedTabName=embed-react",
icon: Icon.FiCode, icon: FiCode,
type: "code", type: "code",
Component: forwardRef< Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null, HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -581,7 +581,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
{ {
name: "Preview", name: "Preview",
href: "embedTabName=embed-preview", href: "embedTabName=embed-preview",
icon: Icon.FiTrello, icon: FiTrello,
type: "iframe", type: "iframe",
Component: forwardRef< Component: forwardRef<
HTMLIFrameElement | HTMLTextAreaElement | null, HTMLIFrameElement | HTMLTextAreaElement | null,
@@ -597,7 +597,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
<iframe <iframe
ref={ref as typeof ref & MutableRefObject<HTMLIFrameElement>} ref={ref as typeof ref & MutableRefObject<HTMLIFrameElement>}
data-testid="embed-preview" data-testid="embed-preview"
className="border-1 h-[100vh] border" className="h-[100vh] border"
width="100%" width="100%"
height="100%" height="100%"
src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`} src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`}
@@ -617,7 +617,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => { const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
return ( return (
<components.Control {...props}> <components.Control {...props}>
<Icon.FiSun className="ml-2 h-4 w-4 text-gray-500" /> <FiSun className="ml-2 h-4 w-4 text-gray-500" />
{children} {children}
</components.Control> </components.Control>
); );
@@ -639,7 +639,7 @@ const ChooseEmbedTypesDialogContent = () => {
<div className="flex items-start"> <div className="flex items-start">
{embeds.map((embed, index) => ( {embeds.map((embed, index) => (
<button <button
className="w-1/3 border border-transparent p-3 text-left hover:rounded-md hover:border-gray-200 hover:bg-neutral-100 ltr:mr-2 rtl:ml-2" className="w-1/3 border border-transparent p-3 text-left hover:rounded-md hover:border-gray-200 hover:bg-gray-100 ltr:mr-2 rtl:ml-2"
key={index} key={index}
data-testid={embed.type} data-testid={embed.type}
onClick={() => { onClick={() => {
@@ -815,7 +815,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onClick={() => { onClick={() => {
removeQueryParams(router, ["embedType", "embedTabName"]); removeQueryParams(router, ["embedType", "embedTabName"]);
}}> }}>
<Icon.FiArrowLeft className="mr-4 w-4" /> <FiArrowLeft className="mr-4 w-4" />
</button> </button>
{embed.title} {embed.title}
</h3> </h3>
@@ -835,7 +835,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
? "Floating Popup Customization" ? "Floating Popup Customization"
: "Element Click Customization"} : "Element Click Customization"}
</div> </div>
<Icon.FiChevronRight <FiChevronRight
className={`${ className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : "" isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-gray-500`} } ml-auto h-5 w-5 text-gray-500`}
@@ -1002,7 +1002,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}> onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleTrigger className="flex w-full" type="button"> <CollapsibleTrigger className="flex w-full" type="button">
<div className="text-base font-medium text-gray-900">Cal Booking Customization</div> <div className="text-base font-medium text-gray-900">Cal Booking Customization</div>
<Icon.FiChevronRight <FiChevronRight
className={`${ className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : "" isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-gray-500`} } ml-auto h-5 w-5 text-gray-500`}
@@ -1163,7 +1163,7 @@ export const EmbedButton = <T extends React.ElementType>({
...props ...props
}: EmbedButtonProps<T> & React.ComponentPropsWithoutRef<T>) => { }: EmbedButtonProps<T> & React.ComponentPropsWithoutRef<T>) => {
const router = useRouter(); const router = useRouter();
className = classNames(className, "hidden lg:inline-flex"); className = classNames("hidden lg:inline-flex", className);
const openEmbedModal = () => { const openEmbedModal = () => {
goto(router, { goto(router, {
dialog: "embed", dialog: "embed",
+1 -1
View File
@@ -142,7 +142,7 @@ export default function ImageUploader({
</div> </div>
)} )}
{result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />} {result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />}
<label className="mt-8 rounded-sm border border-gray-300 bg-white px-3 py-1 text-xs font-medium leading-4 text-gray-700 hover:bg-gray-50 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent dark:text-white dark:hover:bg-gray-900"> <label className="mt-8 rounded-sm border border-gray-300 bg-white px-3 py-1 text-xs font-medium leading-4 text-gray-700 hover:bg-gray-50 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent dark:text-white dark:hover:bg-gray-900">
<input <input
onInput={onInputFile} onInput={onInputFile}
type="file" type="file"
+1 -1
View File
@@ -67,7 +67,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
onClick={onClick} onClick={onClick}
className={classNames( className={classNames(
isCurrent isCurrent
? "border-neutral-900 text-gray-900" ? "border-gray-900 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700", : "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium", "group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
className className
+8 -7
View File
@@ -1,7 +1,8 @@
import React, { ComponentProps } from "react"; import React, { ComponentProps } from "react";
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { ErrorBoundary, Icon } from "@calcom/ui"; import { ErrorBoundary } from "@calcom/ui";
import { FiCreditCard, FiKey, FiLock, FiTerminal, FiUser, FiUsers } from "@calcom/ui/components/icon";
import NavTabs from "./NavTabs"; import NavTabs from "./NavTabs";
@@ -9,32 +10,32 @@ const tabs = [
{ {
name: "profile", name: "profile",
href: "/settings/profile", href: "/settings/profile",
icon: Icon.FiUser, icon: FiUser,
}, },
{ {
name: "teams", name: "teams",
href: "/settings/teams", href: "/settings/teams",
icon: Icon.FiUsers, icon: FiUsers,
}, },
{ {
name: "security", name: "security",
href: "/settings/security", href: "/settings/security",
icon: Icon.FiKey, icon: FiKey,
}, },
{ {
name: "developer", name: "developer",
href: "/settings/developer", href: "/settings/developer",
icon: Icon.FiTerminal, icon: FiTerminal,
}, },
{ {
name: "billing", name: "billing",
href: "/settings/billing", href: "/settings/billing",
icon: Icon.FiCreditCard, icon: FiCreditCard,
}, },
{ {
name: "admin", name: "admin",
href: "/settings/admin", href: "/settings/admin",
icon: Icon.FiLock, icon: FiLock,
adminRequired: true, adminRequired: true,
}, },
]; ];
@@ -4,7 +4,8 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App"; import type { App } from "@calcom/types/App";
import { Button, Icon, Select } from "@calcom/ui"; import { Button, Select } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
@@ -31,7 +32,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
/> />
) : ( ) : (
<Button className="w-full" color="minimal" href="/apps/categories/calendar"> <Button className="w-full" color="minimal" href="/apps/categories/calendar">
<Icon.FiPlus className="text-color mr-3 ml-1 h-4 w-4" /> <FiPlus className="text-color mr-3 ml-1 h-4 w-4" />
<p>{t("install_new_calendar_app")}</p> <p>{t("install_new_calendar_app")}</p>
</Button> </Button>
); );
@@ -61,7 +62,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
<Select <Select
name="additionalCalendar" name="additionalCalendar"
placeholder={ placeholder={
<Button StartIcon={Icon.FiPlus} color="secondary"> <Button StartIcon={FiPlus} color="secondary">
{t("add")} {t("add")}
</Button> </Button>
} }
+26 -9
View File
@@ -12,7 +12,17 @@ import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/consta
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App"; import { App as AppType } from "@calcom/types/App";
import { Button, Icon, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui"; import { Button, showToast, SkeletonButton, SkeletonText, HeadSeo, Badge } from "@calcom/ui";
import {
FiBookOpen,
FiCheck,
FiExternalLink,
FiFile,
FiFlag,
FiMail,
FiPlus,
FiShield,
} from "@calcom/ui/components/icon";
const Component = ({ const Component = ({
name, name,
@@ -34,6 +44,7 @@ const Component = ({
privacy, privacy,
isProOnly, isProOnly,
images, images,
isTemplate,
}: Parameters<typeof App>[0]) => { }: Parameters<typeof App>[0]) => {
const { t } = useLocale(); const { t } = useLocale();
const hasImages = images && images.length > 0; const hasImages = images && images.length > 0;
@@ -106,13 +117,18 @@ const Component = ({
</Link>{" "} </Link>{" "}
• {t("published_by", { author })} • {t("published_by", { author })}
</h2> </h2>
{isTemplate && (
<Badge variant="red" className="mt-4">
Template - Available in Dev Environment only for testing
</Badge>
)}
</header> </header>
</div> </div>
{!appCredentials.isLoading ? ( {!appCredentials.isLoading ? (
isGlobal || isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? ( (existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3"> <div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled> <Button StartIcon={FiCheck} color="secondary" disabled>
{existingCredentials.length > 0 {existingCredentials.length > 0
? t("active_install", { count: existingCredentials.length }) ? t("active_install", { count: existingCredentials.length })
: t("default")} : t("default")}
@@ -133,7 +149,7 @@ const Component = ({
} }
return ( return (
<Button <Button
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
{...props} {...props}
// @TODO: Overriding color and size prevent us from // @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now. // having to duplicate InstallAppButton for now.
@@ -220,7 +236,7 @@ const Component = ({
rel="noreferrer" rel="noreferrer"
className="text-sm font-normal text-black no-underline hover:underline" className="text-sm font-normal text-black no-underline hover:underline"
href={docs}> href={docs}>
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4 text-gray-500" /> <FiBookOpen className="mr-1 -mt-1 inline h-4 w-4 text-gray-500" />
{t("documentation")} {t("documentation")}
</a> </a>
</li> </li>
@@ -232,7 +248,7 @@ const Component = ({
rel="noreferrer" rel="noreferrer"
className="font-normal text-black no-underline hover:underline" className="font-normal text-black no-underline hover:underline"
href={website}> href={website}>
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4 text-gray-500" /> <FiExternalLink className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{website.replace("https://", "")} {website.replace("https://", "")}
</a> </a>
</li> </li>
@@ -244,7 +260,7 @@ const Component = ({
rel="noreferrer" rel="noreferrer"
className="font-normal text-black no-underline hover:underline" className="font-normal text-black no-underline hover:underline"
href={"mailto:" + email}> href={"mailto:" + email}>
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4 text-gray-500" /> <FiMail className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{email} {email}
</a> </a>
@@ -257,7 +273,7 @@ const Component = ({
rel="noreferrer" rel="noreferrer"
className="font-normal text-black no-underline hover:underline" className="font-normal text-black no-underline hover:underline"
href={tos}> href={tos}>
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4 text-gray-500" /> <FiFile className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{t("terms_of_service")} {t("terms_of_service")}
</a> </a>
</li> </li>
@@ -269,7 +285,7 @@ const Component = ({
rel="noreferrer" rel="noreferrer"
className="font-normal text-black no-underline hover:underline" className="font-normal text-black no-underline hover:underline"
href={privacy}> href={privacy}>
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4 text-gray-500" /> <FiShield className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{t("privacy_policy")} {t("privacy_policy")}
</a> </a>
</li> </li>
@@ -280,7 +296,7 @@ const Component = ({
{t("every_app_published", { appName: APP_NAME, companyName: COMPANY_NAME })} {t("every_app_published", { appName: APP_NAME, companyName: COMPANY_NAME })}
</span> </span>
<a className="mt-2 block text-xs text-red-500" href={`mailto:${SUPPORT_MAIL_ADDRESS}`}> <a className="mt-2 block text-xs text-red-500" href={`mailto:${SUPPORT_MAIL_ADDRESS}`}>
<Icon.FiFlag className="inline h-3 w-3" /> {t("report_app")} <FiFlag className="inline h-3 w-3" /> {t("report_app")}
</a> </a>
</div> </div>
</div> </div>
@@ -310,6 +326,7 @@ export default function App(props: {
licenseRequired: AppType["licenseRequired"]; licenseRequired: AppType["licenseRequired"];
isProOnly: AppType["isProOnly"]; isProOnly: AppType["isProOnly"];
images?: string[]; images?: string[];
isTemplate?: boolean;
}) { }) {
const { t } = useLocale(); const { t } = useLocale();
@@ -11,13 +11,13 @@ import {
Alert, Alert,
Button, Button,
EmptyScreen, EmptyScreen,
Icon,
List, List,
showToast, showToast,
AppSkeletonLoader as SkeletonLoader, AppSkeletonLoader as SkeletonLoader,
Switch, Switch,
ShellSubHeading, ShellSubHeading,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiArrowLeft, FiCalendar, FiPlus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
@@ -100,7 +100,7 @@ function CalendarSwitch(props: {
/> />
{!!props.destination && ( {!!props.destination && (
<span className="ml-4 inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-sm font-normal text-gray-800"> <span className="ml-4 inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-sm font-normal text-gray-800">
<Icon.FiArrowLeft className="h-4 w-4" /> <FiArrowLeft className="h-4 w-4" />
{t("adding_events_to")} {t("adding_events_to")}
</span> </span>
)} )}
@@ -282,8 +282,8 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
<div className="flex justify-between rounded-md border border-gray-200 bg-gray-50 p-4"> <div className="flex justify-between rounded-md border border-gray-200 bg-gray-50 p-4">
<div className="flex w-full flex-col items-start gap-4 md:flex-row md:items-center"> <div className="flex w-full flex-col items-start gap-4 md:flex-row md:items-center">
<div className="relative rounded-md border border-gray-200 bg-white p-1.5"> <div className="relative rounded-md border border-gray-200 bg-white p-1.5">
<Icon.FiCalendar className="h-8 w-8" strokeWidth="1" /> <FiCalendar className="h-8 w-8" strokeWidth="1" />
<Icon.FiPlus <FiPlus
className="absolute left-4 top-1/2 ml-0.5 mt-[1px] h-2 w-2 text-black" className="absolute left-4 top-1/2 ml-0.5 mt-[1px] h-2 w-2 text-black"
strokeWidth="4" strokeWidth="4"
/> />
@@ -322,7 +322,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
</> </>
) : ( ) : (
<EmptyScreen <EmptyScreen
Icon={Icon.FiCalendar} Icon={FiCalendar}
headline={t("no_category_apps", { headline={t("no_category_apps", {
category: t("calendar").toLowerCase(), category: t("calendar").toLowerCase(),
})} })}
@@ -3,7 +3,8 @@ import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react"; import { ReactNode, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui"; import { Badge, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui";
import { FiAlertCircle } from "@calcom/ui/components/icon";
import classNames from "@lib/classNames"; import classNames from "@lib/classNames";
@@ -19,6 +20,7 @@ function IntegrationListItem(props: {
destination?: boolean; destination?: boolean;
separate?: boolean; separate?: boolean;
invalidCredential?: boolean; invalidCredential?: boolean;
isTemplate?: boolean;
}): JSX.Element { }): JSX.Element {
const { t } = useLocale(); const { t } = useLocale();
const router = useRouter(); const router = useRouter();
@@ -50,14 +52,19 @@ function IntegrationListItem(props: {
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse")}> <div className={classNames("flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse")}>
{props.logo && <img className="h-11 w-11" src={props.logo} alt={title} />} {props.logo && <img className="h-11 w-11" src={props.logo} alt={title} />}
<div className="flex-grow truncate pl-2"> <div className="flex-grow truncate pl-2">
<ListItemTitle component="h3"> <ListItemTitle component="h3" className="flex ">
<Link href={"/apps/" + props.slug}>{props.name || title}</Link> <Link href={"/apps/" + props.slug}>{props.name || title}</Link>
{props.isTemplate && (
<Badge variant="red" className="ml-4">
Template
</Badge>
)}
</ListItemTitle> </ListItemTitle>
<ListItemText component="p">{props.description}</ListItemText> <ListItemText component="p">{props.description}</ListItemText>
{/* Alert error that key stopped working. */} {/* Alert error that key stopped working. */}
{props.invalidCredential && ( {props.invalidCredential && (
<div className="flex items-center space-x-2 rtl:space-x-reverse"> <div className="flex items-center space-x-2 rtl:space-x-reverse">
<Icon.FiAlertCircle className="w-8 text-red-500 sm:w-4" /> <FiAlertCircle className="w-8 text-red-500 sm:w-4" />
<ListItemText component="p" className="whitespace-pre-wrap text-red-500"> <ListItemText component="p" className="whitespace-pre-wrap text-red-500">
{t("invalid_credential")} {t("invalid_credential")}
</ListItemText> </ListItemText>
@@ -4,18 +4,8 @@ import React, { ComponentProps } from "react";
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen, Icon, HorizontalTabs, HorizontalTabItemProps } from "@calcom/ui"; import { EmptyScreen } from "@calcom/ui";
import { FiAlertCircle } from "@calcom/ui/components/icon";
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
type AppsLayoutProps = { type AppsLayoutProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -31,15 +21,12 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
if (session.status === "loading") return <></>; if (session.status === "loading") return <></>;
return ( return (
<Shell {...rest} actions={actions?.("hidden sm:block")}> <Shell {...rest} actions={actions?.("block")}>
<div className="flex flex-col xl:flex-row"> <div className="flex flex-col xl:flex-row">
<div className="block lg:hidden">
<HorizontalTabs tabs={tabs} actions={actions?.("ml-6 mr-4 mt-3 block sm:hidden")} />
</div>
<main className="w-full"> <main className="w-full">
{emptyStore ? ( {emptyStore ? (
<EmptyScreen <EmptyScreen
Icon={Icon.FiAlertCircle} Icon={FiAlertCircle}
headline={t("no_apps")} headline={t("no_apps")}
description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""} description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""}
buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""} buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""}
@@ -4,39 +4,39 @@ import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavi
import { InstalledAppVariants } from "@calcom/app-store/utils"; import { InstalledAppVariants } from "@calcom/app-store/utils";
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui"; import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
import { FiBarChart, FiCalendar, FiCreditCard, FiGrid, FiShare2, FiVideo } from "@calcom/ui/components/icon";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [ const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{ {
name: "calendar", name: "calendar",
href: "/apps/installed/calendar", href: "/apps/installed/calendar",
icon: Icon.FiCalendar, icon: FiCalendar,
}, },
{ {
name: "conferencing", name: "conferencing",
href: "/apps/installed/conferencing", href: "/apps/installed/conferencing",
icon: Icon.FiVideo, icon: FiVideo,
}, },
{ {
name: "payment", name: "payment",
href: "/apps/installed/payment", href: "/apps/installed/payment",
icon: Icon.FiCreditCard, icon: FiCreditCard,
}, },
{ {
name: "automation", name: "automation",
href: "/apps/installed/automation", href: "/apps/installed/automation",
icon: Icon.FiShare2, icon: FiShare2,
}, },
{ {
name: "analytics", name: "analytics",
href: "/apps/installed/analytics", href: "/apps/installed/analytics",
icon: Icon.FiBarChart, icon: FiBarChart,
}, },
{ {
name: "other", name: "other",
href: "/apps/installed/other", href: "/apps/installed/other",
icon: Icon.FiGrid, icon: FiGrid,
}, },
]; ];
@@ -44,13 +44,15 @@ export default function InstalledAppsLayout({
children, children,
...rest ...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) { }: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const variant: typeof InstalledAppVariants[number] = "payment";
const query = trpc.viewer.integrations.useQuery({ const query = trpc.viewer.integrations.useQuery({
variant: InstalledAppVariants.payment, variant,
onlyInstalled: true, onlyInstalled: true,
}); });
let actualTabs = tabs; let actualTabs = tabs;
if (query.data?.items.length === 0) { if (query.data?.items.length === 0) {
actualTabs = tabs.filter((tab) => tab.name !== InstalledAppVariants.payment); actualTabs = tabs.filter((tab) => tab.name !== variant);
} }
return ( return (
@@ -6,7 +6,7 @@ import classNames from "@lib/classNames";
function SkeletonLoader() { function SkeletonLoader() {
return ( return (
<ul className="animate-pulse divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden"> <ul className="animate-pulse divide-y divide-gray-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
@@ -1,7 +1,8 @@
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations"; import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Tooltip } from "@calcom/ui"; import { Tooltip } from "@calcom/ui";
import { FiLink } from "@calcom/ui/components/icon";
import { Props } from "./pages/AvailabilityPage"; import { Props } from "./pages/AvailabilityPage";
@@ -19,7 +20,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
return ( return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium"> <div key={location.type} className="flex flex-row items-center text-sm font-medium">
{eventLocationType.iconUrl === "/link.svg" ? ( {eventLocationType.iconUrl === "/link.svg" ? (
<Icon.FiLink className="dark:text-darkgray-600 ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 " /> <FiLink className="dark:text-darkgray-600 ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 " />
) : ( ) : (
<img <img
src={eventLocationType.iconUrl} src={eventLocationType.iconUrl}
@@ -4,7 +4,8 @@ import { FC, ReactNode, useEffect } from "react";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Badge } from "@calcom/ui"; import { Badge } from "@calcom/ui";
import { FiCheckSquare, FiClock, FiInfo } from "@calcom/ui/components/icon";
import useRouterQuery from "@lib/hooks/useRouterQuery"; import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -93,7 +94,7 @@ const BookingDescription: FC<Props> = (props) => {
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600" isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}> )}>
<div> <div>
<Icon.FiInfo <FiInfo
className={classNames( className={classNames(
"ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]", "ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "dark:text-darkgray-600 -mt-1 text-gray-500" isBookingPage && "dark:text-darkgray-600 -mt-1 text-gray-500"
@@ -112,7 +113,7 @@ const BookingDescription: FC<Props> = (props) => {
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600" isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}> )}>
<div> <div>
<Icon.FiCheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " /> <FiCheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
</div> </div>
{requiresConfirmationText} {requiresConfirmationText}
</div> </div>
@@ -123,9 +124,10 @@ const BookingDescription: FC<Props> = (props) => {
<div <div
className={classNames( className={classNames(
"flex flex-nowrap text-sm font-medium", "flex flex-nowrap text-sm font-medium",
isBookingPage && "dark:text-darkgray-600 text-gray-600" isBookingPage && "dark:text-darkgray-600 text-gray-600",
!eventType.metadata?.multipleDuration && "items-center"
)}> )}>
<Icon.FiClock <FiClock
className={classNames( className={classNames(
"min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]", "min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "mt-[2px]" isBookingPage && "mt-[2px]"
+12 -11
View File
@@ -18,7 +18,6 @@ import {
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
Icon,
MeetingTimeInTimezones, MeetingTimeInTimezones,
showToast, showToast,
TextArea, TextArea,
@@ -26,6 +25,7 @@ import {
ActionType, ActionType,
TableActions, TableActions,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiCheck, FiClock, FiMapPin, FiRefreshCcw, FiSend, FiSlash, FiX } from "@calcom/ui/components/icon";
import useMeQuery from "@lib/hooks/useMeQuery"; import useMeQuery from "@lib/hooks/useMeQuery";
@@ -100,7 +100,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => { onClick: () => {
setRejectionDialogIsOpen(true); setRejectionDialogIsOpen(true);
}, },
icon: Icon.FiSlash, icon: FiSlash,
disabled: mutation.isLoading, disabled: mutation.isLoading,
}, },
{ {
@@ -109,7 +109,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => { onClick: () => {
bookingConfirm(true); bookingConfirm(true);
}, },
icon: Icon.FiCheck, icon: FiCheck,
disabled: mutation.isLoading, disabled: mutation.isLoading,
color: "primary", color: "primary",
}, },
@@ -135,7 +135,7 @@ function BookingListItem(booking: BookingItemProps) {
href: `/booking/${booking.uid}?cancel=true${ href: `/booking/${booking.uid}?cancel=true${
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : "" isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}`, }`,
icon: Icon.FiX, icon: FiX,
}, },
{ {
id: "edit_booking", id: "edit_booking",
@@ -143,13 +143,13 @@ function BookingListItem(booking: BookingItemProps) {
actions: [ actions: [
{ {
id: "reschedule", id: "reschedule",
icon: Icon.FiClock, icon: FiClock,
label: t("reschedule_booking"), label: t("reschedule_booking"),
href: `/reschedule/${booking.uid}`, href: `/reschedule/${booking.uid}`,
}, },
{ {
id: "reschedule_request", id: "reschedule_request",
icon: Icon.FiSend, icon: FiSend,
iconClassName: "rotate-45 w-[16px] -translate-x-0.5 ", iconClassName: "rotate-45 w-[16px] -translate-x-0.5 ",
label: t("send_reschedule_request"), label: t("send_reschedule_request"),
onClick: () => { onClick: () => {
@@ -162,7 +162,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => { onClick: () => {
setIsOpenLocationDialog(true); setIsOpenLocationDialog(true);
}, },
icon: Icon.FiMapPin, icon: FiMapPin,
}, },
], ],
}, },
@@ -179,7 +179,7 @@ function BookingListItem(booking: BookingItemProps) {
const RequestSentMessage = () => { const RequestSentMessage = () => {
return ( return (
<div className="ml-1 mr-8 flex text-gray-500" data-testid="request_reschedule_sent"> <div className="ml-1 mr-8 flex text-gray-500" data-testid="request_reschedule_sent">
<Icon.FiSend className="-mt-[1px] w-4 rotate-45" /> <FiSend className="-mt-[1px] w-4 rotate-45" />
<p className="ml-2 ">{t("reschedule_request_sent")}</p> <p className="ml-2 ">{t("reschedule_request_sent")}</p>
</div> </div>
); );
@@ -220,7 +220,8 @@ function BookingListItem(booking: BookingItemProps) {
}, },
}); });
}; };
const showRecordingsButtons = booking.location === "integrations:daily" && isPast && isConfirmed; const showRecordingsButtons =
(booking.location === "integrations:daily" || booking?.location?.trim() === "") && isPast && isConfirmed;
return ( return (
<> <>
<RescheduleDialog <RescheduleDialog
@@ -273,7 +274,7 @@ function BookingListItem(booking: BookingItemProps) {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<tr className="group flex flex-col hover:bg-neutral-50 sm:flex-row"> <tr className="group flex flex-col hover:bg-gray-50 sm:flex-row">
<td <td
className="hidden align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:min-w-[12rem]" className="hidden align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:min-w-[12rem]"
onClick={onClickTableData}> onClick={onClickTableData}>
@@ -454,7 +455,7 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
); );
})}> })}>
<div className="text-gray-600 dark:text-white"> <div className="text-gray-600 dark:text-white">
<Icon.FiRefreshCcw <FiRefreshCcw
strokeWidth="3" strokeWidth="3"
className="float-left mr-1 mt-1.5 inline-block h-3 w-3 text-gray-400" className="float-left mr-1 mt-1.5 inline-block h-3 w-3 text-gray-400"
/> />
@@ -5,7 +5,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme"; import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar"; import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button, Icon, TextArea } from "@calcom/ui"; import { Button, TextArea } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
type Props = { type Props = {
booking: { booking: {
@@ -39,7 +40,7 @@ export default function CancelBooking(props: Props) {
{error && ( {error && (
<div className="mt-8"> <div className="mt-8">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" /> <FiX className="h-6 w-6 text-red-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
@@ -55,7 +56,7 @@ export default function CancelBooking(props: Props) {
placeholder={t("cancellation_reason_placeholder")} placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason} value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)} onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white " className="dark:bg-darkgray-100 dark:border-darkgray-400 mt-2 mb-4 w-full dark:text-white "
rows={3} rows={3}
/> />
<div className="flex flex-col-reverse rtl:space-x-reverse "> <div className="flex flex-col-reverse rtl:space-x-reverse ">
@@ -4,7 +4,7 @@ import { SkeletonText } from "@calcom/ui";
function SkeletonLoader() { function SkeletonLoader() {
return ( return (
<ul className="animate-pulse divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:overflow-hidden"> <ul className="animate-pulse divide-y divide-gray-200 rounded-md border border-gray-200 bg-white sm:overflow-hidden">
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
@@ -28,7 +28,8 @@ import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat"; import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Icon, HeadSeo } from "@calcom/ui"; import { HeadSeo } from "@calcom/ui";
import { FiChevronDown, FiChevronUp, FiCreditCard, FiGlobe, FiRefreshCcw } from "@calcom/ui/components/icon";
import { timeZone as localStorageTimeZone } from "@lib/clock"; import { timeZone as localStorageTimeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery"; import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -220,12 +221,12 @@ function TimezoneDropdown({
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}> <Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600"> <Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600">
<p className="flex items-center text-sm font-medium"> <p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 ml-[2px] -mt-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]" /> <FiGlobe className="min-h-4 min-w-4 ml-[2px] -mt-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]" />
{timeZone} {timeZone}
{isTimeOptionsOpen ? ( {isTimeOptionsOpen ? (
<Icon.FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" /> <FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
) : ( ) : (
<Icon.FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" /> <FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
)} )}
</p> </p>
</Popover.Trigger> </Popover.Trigger>
@@ -367,7 +368,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}> <BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && ( {!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium"> <div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " /> <FiRefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<div> <div>
<p className="mb-1 -ml-2 inline px-2 py-1"> <p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })} {getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
@@ -392,7 +393,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
)} )}
{stripeAppData.price > 0 && ( {stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium"> <p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" /> <FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en"> <IntlProvider locale="en">
<FormattedNumber <FormattedNumber
value={stripeAppData.price / 100.0} value={stripeAppData.price / 100.0}
@@ -39,8 +39,16 @@ import useTheme from "@calcom/lib/hooks/useTheme";
import { HttpError } from "@calcom/lib/http-error"; import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings"; import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui"; import { AddressInput, Button, EmailInput, Form, PhoneInput, Tooltip, Group, RadioField } from "@calcom/ui";
import { Group, RadioField } from "@calcom/ui"; import {
FiAlertTriangle,
FiCalendar,
FiCreditCard,
FiInfo,
FiRefreshCw,
FiUser,
FiUserPlus,
} from "@calcom/ui/components/icon";
import { asStringOrNull } from "@lib/asStringOrNull"; import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock"; import { timeZone } from "@lib/clock";
@@ -479,7 +487,7 @@ const BookingPage = ({
})}{" "} })}{" "}
| {APP_NAME} | {APP_NAME}
</title> </title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favico.ico" />
</Head> </Head>
<BookingPageTagManager eventType={eventType} /> <BookingPageTagManager eventType={eventType} />
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} /> <CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
@@ -492,7 +500,7 @@ const BookingPage = ({
<div <div
className={classNames( className={classNames(
"main overflow-hidden", "main overflow-hidden",
isBackgroundTransparent ? "" : "dark:border-1 dark:bg-darkgray-100 bg-white", isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-white dark:border",
"dark:border-darkgray-300 rounded-md sm:border" "dark:border-darkgray-300 rounded-md sm:border"
)}> )}>
<div className="sm:flex"> <div className="sm:flex">
@@ -501,7 +509,7 @@ const BookingPage = ({
<BookingDescription isBookingPage profile={profile} eventType={eventType}> <BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && ( {stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm "> <p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" /> <FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en"> <IntlProvider locale="en">
<FormattedNumber <FormattedNumber
value={stripeAppData.price / 100.0} value={stripeAppData.price / 100.0}
@@ -513,7 +521,7 @@ const BookingPage = ({
)} )}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && ( {!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white"> <div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" /> <FiRefreshCw 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"> <p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({ {getEveryFreqFor({
t, t,
@@ -524,7 +532,7 @@ const BookingPage = ({
</div> </div>
)} )}
<div className="text-bookinghighlight flex items-start text-sm"> <div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" /> <FiCalendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<div className="text-sm font-medium"> <div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`} {(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid && {!rescheduleUid &&
@@ -552,14 +560,14 @@ const BookingPage = ({
{t("former_time")} {t("former_time")}
</p> </p>
<p className="line-through "> <p className="line-through ">
<Icon.FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" /> <FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)} {typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p> </p>
</div> </div>
)} )}
{!!eventType.seatsPerTimeSlot && ( {!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm"> <div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser <FiUser
className={`ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] ${ className={`ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5 booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600" ? "text-rose-600"
@@ -623,7 +631,7 @@ const BookingPage = ({
/> />
{bookingForm.formState.errors.email && ( {bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 "> <div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" /> <FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("email_validation_error")}</p> <p>{t("email_validation_error")}</p>
</div> </div>
)} )}
@@ -716,7 +724,7 @@ const BookingPage = ({
</div> </div>
{bookingForm.formState.errors.phone && ( {bookingForm.formState.errors.phone && (
<div className="mt-2 flex items-center text-sm text-red-700 "> <div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" /> <FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p> <p>{t("invalid_number")}</p>
</div> </div>
)} )}
@@ -834,7 +842,7 @@ const BookingPage = ({
/> />
{bookingForm.formState.errors?.customInputs?.[input.id] && ( {bookingForm.formState.errors?.customInputs?.[input.id] && (
<div className="mt-2 flex items-center text-sm text-red-700 "> <div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" /> <FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p> <p>{t("invalid_number")}</p>
</div> </div>
)} )}
@@ -914,7 +922,7 @@ const BookingPage = ({
</div> </div>
{bookingForm.formState.errors.smsReminderNumber && ( {bookingForm.formState.errors.smsReminderNumber && (
<div className="mt-2 flex items-center text-sm text-red-700 "> <div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" /> <FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p> <p>{t("invalid_number")}</p>
</div> </div>
)} )}
@@ -954,19 +962,14 @@ const BookingPage = ({
<Button <Button
type="button" type="button"
color="minimal" color="minimal"
size="icon" variant="icon"
tooltip={t("additional_guests")} tooltip={t("additional_guests")}
StartIcon={Icon.FiUserPlus} StartIcon={FiUserPlus}
onClick={() => setGuestToggle(!guestToggle)} onClick={() => setGuestToggle(!guestToggle)}
className="mr-auto" className="mr-auto"
/> />
)} )}
<Button <Button color="minimal" type="button" onClick={() => router.back()}>
color="minimal"
type="button"
onClick={() => router.back()}
// We override this for this component only for now - as we don't support darkmode everywhere in the app
className="dark:hover:bg-darkgray-200 dark:border-none dark:text-white">
{t("cancel")} {t("cancel")}
</Button> </Button>
<Button <Button
@@ -998,7 +1001,7 @@ function ErrorMessage({ error }: { error: unknown }) {
<div data-testid="booking-fail" className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4"> <div data-testid="booking-fail" className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<Icon.FiAlertTriangle className="h-5 w-5 text-yellow-400" aria-hidden="true" /> <FiAlertTriangle className="h-5 w-5 text-yellow-400" aria-hidden="true" />
</div> </div>
<div className="ltr:ml-3 rtl:mr-3"> <div className="ltr:ml-3 rtl:mr-3">
<p className="text-sm text-yellow-700"> <p className="text-sm text-yellow-700">
@@ -3,7 +3,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js"; import { isValidPhoneNumber } from "libphonenumber-js";
import { useEffect } from "react"; import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod"; import { z } from "zod";
import { import {
@@ -14,32 +13,26 @@ import {
LocationObject, LocationObject,
LocationType, LocationType,
} from "@calcom/app-store/locations"; } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui"; import { Button, Dialog, DialogContent, DialogFooter, Form, PhoneInput } from "@calcom/ui";
import { FiMapPin } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
import CheckboxField from "@components/ui/form/CheckboxField"; import CheckboxField from "@components/ui/form/CheckboxField";
import Select from "@components/ui/form/Select"; import LocationSelect, { LocationOption } from "@components/ui/form/LocationSelect";
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number]; type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type OptionTypeBase = {
label: string;
value: EventLocationType["type"];
disabled?: boolean;
};
interface ISetLocationDialog { interface ISetLocationDialog {
saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void; saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void;
selection?: OptionTypeBase; selection?: LocationOption;
booking?: BookingItem; booking?: BookingItem;
defaultValues?: LocationObject[]; defaultValues?: LocationObject[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>; setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean; isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void; setSelectedLocation?: (param: LocationOption | undefined) => void;
setEditingLocationType?: (param: string) => void; setEditingLocationType?: (param: string) => void;
} }
@@ -211,11 +204,11 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
})(); })();
return ( return (
<Dialog open={isOpenDialog}> <Dialog open={isOpenDialog} onOpenChange={(open) => setShowLocationModal(open)}>
<DialogContent> <DialogContent>
<div className="flex flex-row space-x-3"> <div className="flex flex-row space-x-3">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10"> <div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" /> <FiMapPin className="text-primary-600 h-6 w-6" />
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="mt-3 text-center sm:mt-0 sm:text-left"> <div className="mt-3 text-center sm:mt-0 sm:text-left">
@@ -296,57 +289,31 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
name="locationType" name="locationType"
control={locationFormMethods.control} control={locationFormMethods.control}
render={() => ( render={() => (
<Select<{ label: string; value: string; icon?: string }> <div className="py-4">
maxMenuHeight={300} <LocationSelect
name="location" maxMenuHeight={300}
defaultValue={selection} name="location"
options={locationOptions} defaultValue={selection}
components={{ options={locationOptions}
Option: (props) => ( isSearchable
<components.Option {...props}> onChange={(val) => {
<div className="flex items-center gap-3"> if (val) {
{props.data.icon && ( locationFormMethods.setValue("locationType", val.value);
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" /> locationFormMethods.unregister([
)} "locationLink",
<span "locationAddress",
className={classNames( "locationPhoneNumber",
"text-sm font-medium", ]);
props.isSelected ? "text-white" : "text-gray-900" locationFormMethods.clearErrors([
)}> "locationLink",
{props.data.label} "locationPhoneNumber",
</span> "locationAddress",
</div> ]);
</components.Option> setSelectedLocation?.(val);
), }
}} }}
formatOptionLabel={(e) => ( />
<div className="flex items-center gap-3"> </div>
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => (
<p className="text-xs font-medium text-gray-600">{e.label}</p>
)}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)} )}
/> />
); );
@@ -9,10 +9,10 @@ import {
DialogContent, DialogContent,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
Icon,
showToast, showToast,
TextArea, TextArea,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiClock } from "@calcom/ui/components/icon";
interface IRescheduleDialog { interface IRescheduleDialog {
isOpenDialog: boolean; isOpenDialog: boolean;
@@ -43,7 +43,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<DialogContent> <DialogContent>
<div className="flex flex-row space-x-3"> <div className="flex flex-row space-x-3">
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]"> <div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiClock className="m-auto h-6 w-6" /> <FiClock className="m-auto h-6 w-6" />
</div> </div>
<div className="pt-1"> <div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} /> <DialogHeader title={t("send_reschedule_request")} />
@@ -8,7 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday"; import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui"; import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { FiExternalLink, FiGlobe } from "@calcom/ui/components/icon";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader"; import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
@@ -164,13 +165,13 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
<hr /> <hr />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between"> <div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start"> <span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start">
<Icon.FiGlobe className="ltr:mr-2 rtl:ml-2" /> <FiGlobe className="ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />} {schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span> </span>
<Button <Button
href={`/availability/${schedule?.schedule.id}`} href={`/availability/${schedule?.schedule.id}`}
color="minimal" color="minimal"
EndIcon={Icon.FiExternalLink} EndIcon={FiExternalLink}
target="_blank" target="_blank"
className="justify-center border sm:border-0" className="justify-center border sm:border-0"
rel="noopener noreferrer"> rel="noopener noreferrer">
@@ -5,7 +5,8 @@ import { FC } from "react";
import { Control, Controller, useFieldArray, useForm, UseFormRegister, useWatch } from "react-hook-form"; import { Control, Controller, useFieldArray, useForm, UseFormRegister, useWatch } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, TextField } from "@calcom/ui"; import { Button, Label, Select, TextField } from "@calcom/ui";
import { FiPlus, FiX } from "@calcom/ui/components/icon";
interface OptionTypeBase { interface OptionTypeBase {
label: string; label: string;
@@ -77,7 +78,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
defaultValue={selectedInputOption} defaultValue={selectedInputOption}
options={inputOptions} options={inputOptions}
isSearchable={false} isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm" className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
onChange={(option) => option && field.onChange(option.value)} onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption} value={selectedInputOption}
onBlur={field.onBlur} onBlur={field.onBlur}
@@ -183,9 +184,9 @@ function RadioInputHandler({
{...register(`options.${index}.label` as const, { required: true })} {...register(`options.${index}.label` as const, { required: true })}
addOnSuffix={ addOnSuffix={
<Button <Button
size="icon" variant="icon"
color="minimal" color="minimal"
StartIcon={Icon.FiX} StartIcon={FiX}
onClick={() => { onClick={() => {
remove(index); remove(index);
}} }}
@@ -196,7 +197,7 @@ function RadioInputHandler({
))} ))}
<Button <Button
color="minimal" color="minimal"
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
className="!text-sm !font-medium" className="!text-sm !font-medium"
onClick={() => { onClick={() => {
append({ label: "", type: "text" }); append({ label: "", type: "text" });
@@ -18,13 +18,13 @@ import {
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogFooter, DialogFooter,
Icon,
Label, Label,
SettingsToggle, SettingsToggle,
showToast, showToast,
TextField, TextField,
Tooltip, Tooltip,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiEdit, FiCopy, FiPlus } from "@calcom/ui/components/icon";
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm"; import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
@@ -128,8 +128,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
addOnSuffix={ addOnSuffix={
<Button <Button
type="button" type="button"
StartIcon={Icon.FiEdit} StartIcon={FiEdit}
size="icon" variant="icon"
color="minimal" color="minimal"
className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black" className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black"
onClick={() => setShowEventNameTip((old) => !old)} onClick={() => setShowEventNameTip((old) => !old)}
@@ -168,7 +168,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</ul> </ul>
{customInputs.length > 0 && ( {customInputs.length > 0 && (
<Button <Button
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
color="minimal" color="minimal"
type="button" type="button"
onClick={() => { onClick={() => {
@@ -314,7 +314,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}} }}
className="hover:stroke-3 hover:bg-transparent hover:text-black" className="hover:stroke-3 hover:bg-transparent hover:text-black"
type="button"> type="button">
<Icon.FiCopy /> <FiCopy />
</Button> </Button>
</Tooltip> </Tooltip>
} }
@@ -413,7 +413,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Dialog open={selectedCustomInputModalOpen} onOpenChange={setSelectedCustomInputModalOpen}> <Dialog open={selectedCustomInputModalOpen} onOpenChange={setSelectedCustomInputModalOpen}>
<DialogContent <DialogContent
type="creation" type="creation"
Icon={Icon.FiPlus} Icon={FiPlus}
title={t("add_new_custom_input_field")} title={t("add_new_custom_input_field")}
description={t("this_input_will_shown_booking_this_event")}> description={t("this_input_will_shown_booking_this_event")}>
<CustomInputTypeForm <CustomInputTypeForm
+9 -34
View File
@@ -1,43 +1,18 @@
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext"; import { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated"; import { EventTypeAppCard } from "@calcom/app-store/_components/EventTypeAppCardInterface";
import { EventTypeAppCardComponentProps } from "@calcom/app-store/types"; import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils"; import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, ErrorBoundary, Icon } from "@calcom/ui"; import { Button, EmptyScreen } from "@calcom/ui";
import { FiGrid } from "@calcom/ui/components/icon";
type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] & export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"]; EventTypeAppCardComponentProps["eventType"];
function AppCardWrapper({
app,
eventType,
getAppData,
setAppData,
}: {
app: RouterOutputs["viewer"]["apps"][number];
eventType: EventType;
getAppData: GetAppData;
setAppData: SetAppData;
}) {
const dirName = app.slug === "stripe" ? "stripepayment" : app.slug;
const Component = EventTypeAddonMap[dirName as keyof typeof EventTypeAddonMap];
if (!Component) {
throw new Error('No component found for "' + dirName + '"');
}
return (
<ErrorBoundary message={`There is some problem with ${app.name} App`}>
<EventTypeAppContext.Provider value={[getAppData, setAppData]}>
<Component key={app.slug} app={app} eventType={eventType} />
</EventTypeAppContext.Provider>
</ErrorBoundary>
);
}
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const { t } = useLocale(); const { t } = useLocale();
const { data: eventTypeApps, isLoading } = trpc.viewer.apps.useQuery({ const { data: eventTypeApps, isLoading } = trpc.viewer.apps.useQuery({
@@ -86,7 +61,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
<div className="before:border-0"> <div className="before:border-0">
{!isLoading && !installedApps?.length ? ( {!isLoading && !installedApps?.length ? (
<EmptyScreen <EmptyScreen
Icon={Icon.FiGrid} Icon={FiGrid}
headline={t("empty_installed_apps_headline")} headline={t("empty_installed_apps_headline")}
description={t("empty_installed_apps_description")} description={t("empty_installed_apps_description")}
buttonRaw={ buttonRaw={
@@ -97,7 +72,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
/> />
) : null} ) : null}
{installedApps?.map((app) => ( {installedApps?.map((app) => (
<AppCardWrapper <EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)} getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)} setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug} key={app.slug}
@@ -113,7 +88,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
) : null} ) : null}
<div className="before:border-0"> <div className="before:border-0">
{notInstalledApps?.map((app) => ( {notInstalledApps?.map((app) => (
<AppCardWrapper <EventTypeAppCard
getAppData={getAppDataGetter(app.slug as EventTypeAppsList)} getAppData={getAppDataGetter(app.slug as EventTypeAppsList)}
setAppData={getAppDataSetter(app.slug as EventTypeAppsList)} setAppData={getAppDataSetter(app.slug as EventTypeAppsList)}
key={app.slug} key={app.slug}
+196 -211
View File
@@ -1,8 +1,8 @@
import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group"; import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useRef, useState } from "react"; import React, { useEffect, useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext, UseFormRegisterReturn, useWatch } from "react-hook-form";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType"; import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
@@ -10,15 +10,98 @@ import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PeriodType } from "@calcom/prisma/client"; import { PeriodType } from "@calcom/prisma/client";
import type { BookingLimit } from "@calcom/types/Calendar"; import type { BookingLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui"; import { Button, DateRangePicker, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
import { FiPlus, FiTrash } from "@calcom/ui/components/icon";
const MinimumBookingNoticeInput = React.forwardRef<
HTMLInputElement,
Omit<UseFormRegisterReturn<"minimumBookingNotice">, "ref">
>(function MinimumBookingNoticeInput({ ...passThroughProps }, ref) {
const { t } = useLocale();
const { setValue, getValues } = useFormContext<FormValues>();
const durationTypeOptions: {
value: DurationType;
label: string;
}[] = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
const [minimumBookingNoticeDisplayValues, setMinimumBookingNoticeDisplayValues] = useState<{
type: DurationType;
value: number;
}>({
type: findDurationType(getValues(passThroughProps.name)),
value: convertToNewDurationType(
"minutes",
findDurationType(getValues(passThroughProps.name)),
getValues(passThroughProps.name)
),
});
// keep hidden field in sync with minimumBookingNoticeDisplayValues
useEffect(() => {
setValue(
passThroughProps.name,
convertToNewDurationType(
minimumBookingNoticeDisplayValues.type,
"minutes",
minimumBookingNoticeDisplayValues.value
)
);
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
return (
<div className="flex items-end justify-end">
<div className="w-1/2 md:w-full">
<InputField
required
defaultValue={minimumBookingNoticeDisplayValues.value}
onChange={(e) =>
setMinimumBookingNoticeDisplayValues({
...minimumBookingNoticeDisplayValues,
value: parseInt(e.target.value || "0", 10),
})
}
label={t("minimum_booking_notice")}
type="number"
placeholder="0"
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
/>
<input type="hidden" ref={ref} {...passThroughProps} />
</div>
<Select
isSearchable={false}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeDisplayValues.type
)}
onChange={(input) => {
if (input) {
setMinimumBookingNoticeDisplayValues({
...minimumBookingNoticeDisplayValues,
type: input.value,
});
}
}}
options={durationTypeOptions}
/>
</div>
);
});
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => { export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const { t } = useLocale(); const { t } = useLocale();
const formMethods = useFormContext<FormValues>(); const formMethods = useFormContext<FormValues>();
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
const PERIOD_TYPES = [ const PERIOD_TYPES = [
{ {
@@ -49,212 +132,115 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
defaultValue: periodType?.type, defaultValue: periodType?.type,
}); });
const onMinimumNoticeDurationTypeChange = useMemo(
() => (durationType?: DurationType) => {
if (typeof durationType === "undefined") return;
// Store current selected type in ref to use in previous run for comparrison.
minimumBookingNoticeType.current = durationType;
// Uses the previous selected type and converts the value to the new type.
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
const minimumBookingNoticeInDurationType = convertToNewDurationType(
prevBookingNoticeType.current,
durationType,
Number(minimumBookingNoticeInDurationTypeFormValue)
);
// Uses the round up value in the new type to calculate the value in minutes.
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
// would break in the case that the user enters 90 minutes, changes the type to
// hours (that will show 2 hours), but the value in minutes would then remain 90,
// which is something else than the user things they save.
const minimumBookingNoticeInMinutes = convertToNewDurationType(
durationType,
"minutes",
Number(minimumBookingNoticeInDurationType)
);
// Updates both form values as well as stores the current type as previous type in ref.
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
prevBookingNoticeType.current = durationType;
},
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
);
/**
* When the user inputs a new value for minimumBookingNoticeInDurationType,
* we calculate the value in minutes, and update this hidden field as well.
*/
const onMinimumNoticeChange = useMemo(
() => (duration: string) => {
const minimumBookingNoticeInMinutes = convertToNewDurationType(
minimumBookingNoticeType.current,
"minutes",
Number(duration)
);
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
},
[formMethods]
);
return ( return (
<div> <div className="space-y-8">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4"> <div className="space-y-4 lg:space-y-8">
<div className="w-full"> <div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<Label htmlFor="beforeBufferTime">{t("before_event")} </Label> <div className="w-full">
<Controller <Label htmlFor="beforeBufferTime">{t("before_event")} </Label>
name="beforeBufferTime" <Controller
control={formMethods.control} name="beforeBufferTime"
defaultValue={eventType.beforeEventBuffer || 0} control={formMethods.control}
render={({ field: { onChange, value } }) => { defaultValue={eventType.beforeEventBuffer || 0}
const beforeBufferOptions = [ render={({ field: { onChange, value } }) => {
{ const beforeBufferOptions = [
label: t("event_buffer_default"), {
value: 0, label: t("event_buffer_default"),
}, value: 0,
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({ },
label: minutes + " " + t("minutes"), ...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
value: minutes, label: minutes + " " + t("minutes"),
})), value: minutes,
]; })),
return ( ];
<Select return (
isSearchable={false}
onChange={(val) => {
if (val) onChange(val.value);
}}
defaultValue={
beforeBufferOptions.find((option) => option.value === value) || beforeBufferOptions[0]
}
options={beforeBufferOptions}
/>
);
}}
/>
</div>
<div className="w-full">
<Label htmlFor="afterBufferTime">{t("after_event")} </Label>
<Controller
name="afterBufferTime"
control={formMethods.control}
defaultValue={eventType.afterEventBuffer || 0}
render={({ field: { onChange, value } }) => {
const afterBufferOptions = [
{
label: t("event_buffer_default"),
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
onChange={(val) => {
if (val) onChange(val.value);
}}
defaultValue={
afterBufferOptions.find((option) => option.value === value) || afterBufferOptions[0]
}
options={afterBufferOptions}
/>
);
}}
/>
</div>
</div>
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex w-full items-end justify-end">
<Controller
name="minimumBookingNotice"
control={formMethods.control}
render={() => {
const durationTypeOptions = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
return (
<>
<div className="w-1/2 md:w-full">
<InputField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
{...formMethods.register("minimumBookingNoticeInDurationType", {
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
onMinimumNoticeChange(event.target.value),
})}
/>
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
</div>
<Select <Select
isSearchable={false} isSearchable={false}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]" onChange={(val) => {
defaultValue={durationTypeOptions.find( if (val) onChange(val.value);
(option) => option.value === minimumBookingNoticeType.current }}
)} defaultValue={
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)} beforeBufferOptions.find((option) => option.value === value) || beforeBufferOptions[0]
options={durationTypeOptions} }
options={beforeBufferOptions}
/> />
</> );
); }}
}} />
/> </div>
<div className="w-full">
<Label htmlFor="afterBufferTime">{t("after_event")} </Label>
<Controller
name="afterBufferTime"
control={formMethods.control}
defaultValue={eventType.afterEventBuffer || 0}
render={({ field: { onChange, value } }) => {
const afterBufferOptions = [
{
label: t("event_buffer_default"),
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
onChange={(val) => {
if (val) onChange(val.value);
}}
defaultValue={
afterBufferOptions.find((option) => option.value === value) || afterBufferOptions[0]
}
options={afterBufferOptions}
/>
);
}}
/>
</div>
</div> </div>
<div className="w-full"> <div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<Label htmlFor="slotInterval">{t("slot_interval")} </Label> <div className="w-full">
<Controller <Label htmlFor="minimumBookingNotice">{t("minimum_booking_notice")} </Label>
name="slotInterval" <MinimumBookingNoticeInput {...formMethods.register("minimumBookingNotice")} />
control={formMethods.control} </div>
render={() => { <div className="w-full">
const slotIntervalOptions = [ <Label htmlFor="slotInterval">{t("slot_interval")} </Label>
{ <Controller
label: t("slot_interval_default"), name="slotInterval"
value: -1, control={formMethods.control}
}, render={() => {
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({ const slotIntervalOptions = [
label: minutes + " " + t("minutes"), {
value: minutes, label: t("slot_interval_default"),
})), value: -1,
]; },
return ( ...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
<Select label: minutes + " " + t("minutes"),
isSearchable={false} value: minutes,
onChange={(val) => { })),
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null); ];
}} return (
defaultValue={ <Select
slotIntervalOptions.find((option) => option.value === eventType.slotInterval) || isSearchable={false}
slotIntervalOptions[0] onChange={(val) => {
} formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
options={slotIntervalOptions} }}
/> defaultValue={
); slotIntervalOptions.find((option) => option.value === eventType.slotInterval) ||
}} slotIntervalOptions[0]
/> }
options={slotIntervalOptions}
/>
);
}}
/>
</div>
</div> </div>
</div> </div>
<hr />
<hr className="my-8" />
<Controller <Controller
name="bookingLimits" name="bookingLimits"
control={formMethods.control} control={formMethods.control}
@@ -276,8 +262,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</SettingsToggle> </SettingsToggle>
)} )}
/> />
<hr />
<hr className="my-8" />
<Controller <Controller
name="periodType" name="periodType"
control={formMethods.control} control={formMethods.control}
@@ -446,8 +431,8 @@ const BookingLimits = () => {
}} }}
/> />
<Button <Button
size="icon" variant="icon"
StartIcon={Icon.FiTrash} StartIcon={FiTrash}
color="destructive" color="destructive"
onClick={() => { onClick={() => {
const current = currentBookingLimits; const current = currentBookingLimits;
@@ -461,7 +446,7 @@ const BookingLimits = () => {
{currentBookingLimits && Object.keys(currentBookingLimits).length <= 3 && ( {currentBookingLimits && Object.keys(currentBookingLimits).length <= 3 && (
<Button <Button
color="minimal" color="minimal"
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
onClick={() => { onClick={() => {
if (!currentBookingLimits || !watchBookingLimits) return; if (!currentBookingLimits || !watchBookingLimits) return;
const currentKeys = Object.keys(watchBookingLimits); const currentKeys = Object.keys(watchBookingLimits);
+32 -16
View File
@@ -12,16 +12,27 @@ import { z } from "zod";
import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations"; import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants"; import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui"; import { Button, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { FiEdit2, FiCheck, FiX, FiPlus } from "@calcom/ui/components/icon";
import { slugify } from "@lib/slugify"; import { slugify } from "@lib/slugify";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog"; import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import LocationSelect, {
SingleValueLocationOption,
LocationOption,
} from "@components/ui/form/LocationSelect";
type OptionTypeBase = { const getLocationFromType = (
label: string; type: EventLocationType["type"],
value: EventLocationType["type"]; locationOptions: Pick<EventTypeSetupProps, "locationOptions">["locationOptions"]
disabled?: boolean; ) => {
for (const locationOption of locationOptions) {
const option = locationOption.options.find((option) => option.value === type);
if (option) {
return option;
}
}
}; };
export const EventSetupTab = ( export const EventSetupTab = (
@@ -32,7 +43,7 @@ export const EventSetupTab = (
const { eventType, locationOptions, team } = props; const { eventType, locationOptions, team } = props;
const [showLocationModal, setShowLocationModal] = useState(false); const [showLocationModal, setShowLocationModal] = useState(false);
const [editingLocationType, setEditingLocationType] = useState<string>(""); const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined); const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration); const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({ const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
@@ -51,7 +62,8 @@ export const EventSetupTab = (
); );
const openLocationModal = (type: EventLocationType["type"]) => { const openLocationModal = (type: EventLocationType["type"]) => {
setSelectedLocation(locationOptions.find((option) => option.value === type)); const option = getLocationFromType(type, locationOptions);
setSelectedLocation(option);
setShowLocationModal(true); setShowLocationModal(true);
}; };
@@ -127,18 +139,20 @@ export const EventSetupTab = (
return true; return true;
}); });
const defaultValue = locationOptions.find((item) => item.label === "video")?.options;
return ( return (
<div className="w-full"> <div className="w-full">
{validLocations.length === 0 && ( {validLocations.length === 0 && (
<div className="flex"> <div className="flex">
<Select <LocationSelect
defaultValue={defaultValue}
placeholder={t("select")} placeholder={t("select")}
options={locationOptions} options={locationOptions}
isSearchable={false} isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm" className="block w-full min-w-0 flex-1 rounded-sm text-sm"
onChange={(e) => { onChange={(e: SingleValueLocationOption) => {
if (e?.value) { if (e?.value) {
const newLocationType: EventLocationType["type"] = e.value; const newLocationType = e.value;
const eventLocationType = getEventLocationType(newLocationType); const eventLocationType = getEventLocationType(newLocationType);
if (!eventLocationType) { if (!eventLocationType) {
return; return;
@@ -162,7 +176,7 @@ export const EventSetupTab = (
return null; return null;
} }
return ( return (
<li key={location.type} className="mb-2 rounded-md border border-neutral-300 py-1.5 px-2"> <li key={location.type} className="mb-2 rounded-md border border-gray-300 py-1.5 px-2">
<div className="flex max-w-full justify-between"> <div className="flex max-w-full justify-between">
<div key={index} className="flex flex-grow items-center"> <div key={index} className="flex flex-grow items-center">
<img <img
@@ -187,10 +201,10 @@ export const EventSetupTab = (
}} }}
aria-label={t("edit")} aria-label={t("edit")}
className="mr-1 p-1 text-gray-500 hover:text-gray-900"> className="mr-1 p-1 text-gray-500 hover:text-gray-900">
<Icon.FiEdit2 className="h-4 w-4" /> <FiEdit2 className="h-4 w-4" />
</button> </button>
<button type="button" onClick={() => removeLocation(location)} aria-label={t("remove")}> <button type="button" onClick={() => removeLocation(location)} aria-label={t("remove")}>
<Icon.FiX className="border-l-1 h-6 w-6 pl-1 text-gray-500 hover:text-gray-900 " /> <FiX className="border-l-1 h-6 w-6 pl-1 text-gray-500 hover:text-gray-900 " />
</button> </button>
</div> </div>
</div> </div>
@@ -199,7 +213,7 @@ export const EventSetupTab = (
})} })}
{validLocations.some((location) => location.type === MeetLocationType) && ( {validLocations.some((location) => location.type === MeetLocationType) && (
<div className="flex text-sm text-gray-600"> <div className="flex text-sm text-gray-600">
<Icon.FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" /> <FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal"> <Trans i18nKey="event_type_requres_google_cal">
<p> <p>
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work. The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
@@ -216,7 +230,7 @@ export const EventSetupTab = (
)} )}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && ( {validLocations.length > 0 && validLocations.length !== locationOptions.length && (
<li> <li>
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}> <Button StartIcon={FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
{t("add_location")} {t("add_location")}
</Button> </Button>
</li> </li>
@@ -362,7 +376,9 @@ export const EventSetupTab = (
saveLocation={saveLocation} saveLocation={saveLocation}
defaultValues={formMethods.getValues("locations")} defaultValues={formMethods.getValues("locations")}
selection={ selection={
selectedLocation ? { value: selectedLocation.value, label: t(selectedLocation.label) } : undefined selectedLocation
? { value: selectedLocation.value, label: t(selectedLocation.label), icon: selectedLocation.icon }
: undefined
} }
setSelectedLocation={setSelectedLocation} setSelectedLocation={setSelectedLocation}
setEditingLocationType={setEditingLocationType} setEditingLocationType={setEditingLocationType}
@@ -9,7 +9,8 @@ import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Webhook } from "@calcom/prisma/client"; import { Webhook } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, EmptyScreen, Icon, showToast } from "@calcom/ui"; import { Button, Dialog, DialogContent, EmptyScreen, showToast } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
export const EventTeamWebhooksTab = ({ export const EventTeamWebhooksTab = ({
eventType, eventType,
@@ -84,7 +85,7 @@ export const EventTeamWebhooksTab = ({
<Button <Button
color="secondary" color="secondary"
data-testid="new_webhook" data-testid="new_webhook"
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
onClick={() => setCreateModalOpen(true)}> onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")} {t("new_webhook")}
</Button> </Button>
@@ -130,7 +131,10 @@ export const EventTeamWebhooksTab = ({
{/* New webhook dialog */} {/* New webhook dialog */}
<Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}> <Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}>
<DialogContent title={t("create_webhook")} description={t("create_webhook_team_event_type")}> <DialogContent
enableOverflow
title={t("create_webhook")}
description={t("create_webhook_team_event_type")}>
<WebhookForm <WebhookForm
onSubmit={onCreateWebhook} onSubmit={onCreateWebhook}
onCancel={() => setCreateModalOpen(false)} onCancel={() => setCreateModalOpen(false)}
@@ -16,13 +16,13 @@ import {
ButtonGroup, ButtonGroup,
ConfirmationDialogContent, ConfirmationDialogContent,
Dialog, Dialog,
Divider, DropdownMenuSeparator,
Dropdown, Dropdown,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger, DropdownMenuTrigger,
HorizontalTabs, HorizontalTabs,
Icon,
Label, Label,
showToast, showToast,
Skeleton, Skeleton,
@@ -31,6 +31,21 @@ import {
VerticalDivider, VerticalDivider,
VerticalTabs, VerticalTabs,
} from "@calcom/ui"; } from "@calcom/ui";
import {
FiLink,
FiCalendar,
FiClock,
FiSliders,
FiRepeat,
FiGrid,
FiZap,
FiUsers,
FiExternalLink,
FiCode,
FiTrash,
FiMoreHorizontal,
FiLoader,
} from "@calcom/ui/components/icon";
import { EmbedButton, EmbedDialog } from "@components/Embed"; import { EmbedButton, EmbedDialog } from "@components/Embed";
@@ -55,48 +70,51 @@ function getNavigation(props: {
installedAppsNumber: number; installedAppsNumber: number;
}) { }) {
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props; const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const duration =
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
return [ return [
{ {
name: "event_setup_tab_title", name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`, href: `/event-types/${eventType.id}?tabName=setup`,
icon: Icon.FiLink, icon: FiLink,
info: `${eventType.length} Mins`, // TODO: Get this from props info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
}, },
{ {
name: "availability", name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`, href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar, icon: FiCalendar,
info: `default_schedule_name`, // TODO: Get this from props info: `default_schedule_name`, // TODO: Get this from props
}, },
{ {
name: "event_limit_tab_title", name: "event_limit_tab_title",
href: `/event-types/${eventType.id}?tabName=limits`, href: `/event-types/${eventType.id}?tabName=limits`,
icon: Icon.FiClock, icon: FiClock,
info: `event_limit_tab_description`, info: `event_limit_tab_description`,
}, },
{ {
name: "event_advanced_tab_title", name: "event_advanced_tab_title",
href: `/event-types/${eventType.id}?tabName=advanced`, href: `/event-types/${eventType.id}?tabName=advanced`,
icon: Icon.FiSliders, icon: FiSliders,
info: `event_advanced_tab_description`, info: `event_advanced_tab_description`,
}, },
{ {
name: "recurring", name: "recurring",
href: `/event-types/${eventType.id}?tabName=recurring`, href: `/event-types/${eventType.id}?tabName=recurring`,
icon: Icon.FiRepeat, icon: FiRepeat,
info: `recurring_event_tab_description`, info: `recurring_event_tab_description`,
}, },
{ {
name: "apps", name: "apps",
href: `/event-types/${eventType.id}?tabName=apps`, href: `/event-types/${eventType.id}?tabName=apps`,
icon: Icon.FiGrid, icon: FiGrid,
//TODO: Handle proper translation with count handling //TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`, info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
}, },
{ {
name: "workflows", name: "workflows",
href: `/event-types/${eventType.id}?tabName=workflows`, href: `/event-types/${eventType.id}?tabName=workflows`,
icon: Icon.FiZap, icon: FiZap,
info: `${enabledWorkflowsNumber} ${t("active")}`, info: `${enabledWorkflowsNumber} ${t("active")}`,
}, },
]; ];
@@ -153,7 +171,7 @@ function EventTypeSingleLayout({
navigation.splice(2, 0, { navigation.splice(2, 0, {
name: "assignment", name: "assignment",
href: `/event-types/${eventType.id}?tabName=team`, href: `/event-types/${eventType.id}?tabName=team`,
icon: Icon.FiUsers, icon: FiUsers,
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin", info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
}); });
navigation.push({ navigation.push({
@@ -204,17 +222,17 @@ function EventTypeSingleLayout({
<Button <Button
color="secondary" color="secondary"
target="_blank" target="_blank"
size="icon" variant="icon"
href={permalink} href={permalink}
rel="noreferrer" rel="noreferrer"
StartIcon={Icon.FiExternalLink} StartIcon={FiExternalLink}
/> />
</Tooltip> </Tooltip>
<Button <Button
color="secondary" color="secondary"
size="icon" variant="icon"
StartIcon={Icon.FiLink} StartIcon={FiLink}
tooltip={t("copy_link")} tooltip={t("copy_link")}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(permalink); navigator.clipboard.writeText(permalink);
@@ -223,15 +241,15 @@ function EventTypeSingleLayout({
/> />
<EmbedButton <EmbedButton
embedUrl={encodeURIComponent(embedLink)} embedUrl={encodeURIComponent(embedLink)}
StartIcon={Icon.FiCode} StartIcon={FiCode}
color="secondary" color="secondary"
size="icon" variant="icon"
tooltip={t("embed")} tooltip={t("embed")}
/> />
<Button <Button
color="secondary" color="secondary"
size="icon" variant="icon"
StartIcon={Icon.FiTrash} StartIcon={FiTrash}
tooltip={t("delete")} tooltip={t("delete")}
disabled={!hasPermsToDelete} disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}
@@ -241,37 +259,41 @@ function EventTypeSingleLayout({
<VerticalDivider className="hidden lg:block" /> <VerticalDivider className="hidden lg:block" />
<Dropdown> <Dropdown>
<DropdownMenuTrigger className="block h-9 w-9 justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 lg:hidden"> <DropdownMenuTrigger asChild>
<Icon.FiMoreHorizontal className="group-hover:text-gray-800" /> <Button className="lg:hidden" StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem className="focus:ring-gray-100"> <DropdownMenuItem className="focus:ring-gray-100">
<Button <DropdownItem
color="minimal"
StartIcon={Icon.FiExternalLink}
target="_blank" target="_blank"
type="button"
StartIcon={FiExternalLink}
href={permalink} href={permalink}
rel="noreferrer" rel="noreferrer">
className="min-w-full">
{t("preview")} {t("preview")}
</Button> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100"> <DropdownMenuItem className="focus:ring-gray-100">
<Button color="minimal" StartIcon={Icon.FiLink}> <DropdownItem
type="button"
StartIcon={FiLink}
onClick={() => {
navigator.clipboard.writeText(permalink);
showToast("Link copied!", "success");
}}>
{t("copy_link")} {t("copy_link")}
</Button> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100"> <DropdownMenuItem className="focus:ring-gray-100">
<Button <DropdownItem
color="minimal" type="button"
StartIcon={Icon.FiTrash} StartIcon={FiTrash}
disabled={!hasPermsToDelete} disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)} onClick={() => setDeleteDialogOpen(true)}>
className="min-w-full">
{t("delete")} {t("delete")}
</Button> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<Divider /> <DropdownMenuSeparator className="block sm:hidden" />
<div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100"> <div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100">
<Skeleton <Skeleton
as={Label} as={Label}
@@ -300,7 +322,7 @@ function EventTypeSingleLayout({
</Button> </Button>
</div> </div>
}> }>
<Suspense fallback={<Icon.FiLoader />}> <Suspense fallback={<FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8"> <div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block"> <div className="hidden xl:block">
<VerticalTabs <VerticalTabs
@@ -316,7 +338,7 @@ function EventTypeSingleLayout({
<div className="w-full ltr:mr-2 rtl:ml-2"> <div className="w-full ltr:mr-2 rtl:ml-2">
<div <div
className={classNames( className={classNames(
"mt-4 rounded-md border-neutral-200 bg-white sm:mx-0 xl:mt-0", "mt-4 rounded-md border-gray-200 bg-white sm:mx-0 xl:mt-0",
disableBorder ? "border-0 xl:-mt-4 " : "p-2 md:border md:p-6" disableBorder ? "border-0 xl:-mt-4 " : "p-2 md:border md:p-6"
)}> )}>
{children} {children}
@@ -1,4 +1,5 @@
import { Icon, SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui"; import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { FiClock, FiUser } from "@calcom/ui/components/icon";
function SkeletonLoader() { function SkeletonLoader() {
return ( return (
@@ -10,7 +11,7 @@ function SkeletonLoader() {
<SkeletonText className="h-4 w-24" /> <SkeletonText className="h-4 w-24" />
</div> </div>
</div> </div>
<ul className="divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden"> <ul className="divide-y divide-gray-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
<SkeletonItem /> <SkeletonItem />
@@ -31,11 +32,11 @@ function SkeletonItem() {
<div className=""> <div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse "> <ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap"> <li className="flex items-center whitespace-nowrap">
<Icon.FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" /> <FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText className="h-4 w-12" /> <SkeletonText className="h-4 w-12" />
</li> </li>
<li className="flex items-center whitespace-nowrap"> <li className="flex items-center whitespace-nowrap">
<Icon.FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" /> <FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText className="h-4 w-12" /> <SkeletonText className="h-4 w-12" />
</li> </li>
</ul> </ul>
@@ -113,7 +113,7 @@ const UserProfile = (props: IUserProfileProps) => {
name="avatar" name="avatar"
id="avatar" id="avatar"
placeholder="URL" placeholder="URL"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800" className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 text-sm focus:border-gray-800 focus:outline-none focus:ring-gray-800"
defaultValue={imageSrc} defaultValue={imageSrc}
/> />
<div className="flex items-center px-4"> <div className="flex items-center px-4">
@@ -146,7 +146,7 @@ const UserProfile = (props: IUserProfileProps) => {
ref={bioRef} ref={bioRef}
name="bio" name="bio"
id="bio" id="bio"
className="mt-1 block h-[60px] w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm" className="mt-1 block h-[60px] w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm"
defaultValue={user?.bio || undefined} defaultValue={user?.bio || undefined}
onChange={(event) => { onChange={(event) => {
setValue("bio", event.target.value); setValue("bio", event.target.value);
@@ -1,10 +1,10 @@
import { Icon } from "@calcom/ui"; import { ShieldCheckIcon } from "@calcom/ui/components/icon";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => { const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return ( return (
<div className="mb-4 sm:flex sm:items-start"> <div className="mb-4 sm:flex sm:items-start">
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10"> <div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<Icon.ShieldCheckIcon className="h-6 w-6 text-white" /> <ShieldCheckIcon className="h-6 w-6 text-white" />
</div> </div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="font-cal text-lg font-medium leading-6 text-gray-900" id="modal-title"> <h3 className="font-cal text-lg font-medium leading-6 text-gray-900" id="modal-title">
@@ -2,7 +2,8 @@ import { useMutation } from "@tanstack/react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Badge, Icon, showToast, Switch } from "@calcom/ui"; import { Badge, showToast, Switch } from "@calcom/ui";
import { FiArrowLeft } from "@calcom/ui/components/icon";
export function CalendarSwitch(props: { export function CalendarSwitch(props: {
type: string; type: string;
@@ -74,7 +75,7 @@ export function CalendarSwitch(props: {
/> />
{props.defaultSelected && ( {props.defaultSelected && (
<Badge variant="gray"> <Badge variant="gray">
<Icon.FiArrowLeft className="mr-1" /> {t("adding_events_to")} <FiArrowLeft className="mr-1" /> {t("adding_events_to")}
</Badge> </Badge>
)} )}
</div> </div>
+1 -1
View File
@@ -32,7 +32,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
</div> </div>
)} )}
<div className="mb-auto mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div className="mb-auto mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10"> <div className="mx-2 rounded-md border border-gray-200 bg-white px-4 py-10 sm:px-10">
{props.children} {props.children}
</div> </div>
<div className="mt-8 text-center text-sm text-gray-600"> <div className="mt-8 text-center text-sm text-gray-600">
-51
View File
@@ -1,51 +0,0 @@
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as Tooltip from "@radix-ui/react-tooltip";
import { Maybe } from "@calcom/trpc/server";
import classNames from "@lib/classNames";
import { defaultAvatarSrc } from "@lib/profile";
export type AvatarProps = {
className?: string;
size?: number;
imageSrc?: Maybe<string>;
title?: string;
alt: string;
gravatarFallbackMd5?: string;
};
/**
* @deprecated Use AvatarSSR instead. Once, there is no usage of Avatar, AvatarSSR can be renamed.
*/
export default function Avatar(props: AvatarProps) {
const { imageSrc, gravatarFallbackMd5, size, alt, title } = props;
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
const avatar = (
<AvatarPrimitive.Root>
<AvatarPrimitive.Image
src={imageSrc ?? undefined}
alt={alt}
className={classNames("rounded-full", `h-auto w-${size}`, props.className)}
/>
<AvatarPrimitive.Fallback delayMs={600}>
{gravatarFallbackMd5 && (
// eslint-disable-next-line @next/next/no-img-element
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={className} />
)}
</AvatarPrimitive.Fallback>
</AvatarPrimitive.Root>
);
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
<Tooltip.Arrow />
{title}
</Tooltip.Content>
</Tooltip.Tooltip>
) : (
<>{avatar}</>
);
}
-47
View File
@@ -1,47 +0,0 @@
import Link from "next/link";
import React from "react";
import classNames from "@lib/classNames";
import { AvatarSSR } from "@components/ui/AvatarSSR";
export type AvatarGroupProps = {
border?: string; // this needs to be the color of the parent container background, i.e.: border-white dark:border-gray-900
size: number;
truncateAfter?: number;
items: {
image: string;
title?: string;
alt?: string;
href?: string;
}[];
className?: string;
};
export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
return (
<ul className={classNames(props.className)}>
{props.items.slice(0, props.truncateAfter).map((item, idx) => {
if (item.image != null) {
const avatar = (
<AvatarSSR
className={props.border}
imageSrc={item.image}
title={item.title}
alt={item.alt || ""}
size={props.size}
/>
);
return (
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
{item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
</li>
);
}
})}
</ul>
);
};
export default AvatarGroup;
-62
View File
@@ -1,62 +0,0 @@
import { User } from "@prisma/client";
import * as Tooltip from "@radix-ui/react-tooltip";
import classNames from "@lib/classNames";
export type AvatarProps = (
| {
user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string };
}
| {
user?: null;
imageSrc: string;
}
) & {
className?: string;
size?: number;
title?: string;
href?: string;
alt: string;
};
// defaultAvatarSrc from profile.tsx can't be used as it imports crypto
function defaultAvatarSrc(md5: string) {
return `https://www.gravatar.com/avatar/${md5}?s=160&d=mp&r=PG`;
}
// An SSR Supported version of Avatar component.
export function AvatarSSR(props: AvatarProps) {
const { size, title } = props;
let imgSrc = "";
let alt: string = props.alt;
if (props.user) {
const user = props.user;
const nameOrUsername = user.name || user.username || "";
alt = alt || nameOrUsername;
if (user.avatar) {
imgSrc = user.avatar;
} else if (user.emailMd5) {
imgSrc = defaultAvatarSrc(user.emailMd5);
}
} else {
imgSrc = props.imageSrc;
}
const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`);
const avatar = imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger asChild>{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
<Tooltip.Arrow />
{title}
</Tooltip.Content>
</Tooltip.Tooltip>
) : (
<>{avatar}</>
);
}
+3 -3
View File
@@ -1,8 +1,8 @@
import classNames from "classnames"; import classNames from "classnames";
import React, { useState } from "react"; import { useState } from "react";
import { ControllerRenderProps } from "react-hook-form"; import { ControllerRenderProps } from "react-hook-form";
import { Icon } from "@calcom/ui"; import { FiEdit2 } from "@calcom/ui/components/icon";
const EditableHeading = function EditableHeading({ const EditableHeading = function EditableHeading({
value, value,
@@ -21,7 +21,7 @@ const EditableHeading = function EditableHeading({
<label className="min-w-8 relative inline-block"> <label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span> <span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && ( {!isEditing && isReady && (
<Icon.FiEdit2 className="ml-1 inline h-4 w-4 align-top text-gray-700 group-hover:text-gray-500" /> <FiEdit2 className="ml-1 inline h-4 w-4 align-top text-gray-700 group-hover:text-gray-500" />
)} )}
<input <input
{...passThroughProps} {...passThroughProps}
+3 -2
View File
@@ -1,11 +1,12 @@
import { Icon, Tooltip } from "@calcom/ui"; import { Tooltip } from "@calcom/ui";
import { FiInfo } from "@calcom/ui/components/icon";
export default function InfoBadge({ content }: { content: string }) { export default function InfoBadge({ content }: { content: string }) {
return ( return (
<> <>
<Tooltip side="top" content={content}> <Tooltip side="top" content={content}>
<span title={content}> <span title={content}>
<Icon.FiInfo className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" /> <FiInfo className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" />
</span> </span>
</Tooltip> </Tooltip>
</> </>
@@ -11,7 +11,8 @@ import { User } from "@calcom/prisma/client";
import { TRPCClientErrorLike } from "@calcom/trpc/client"; import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { RouterOutputs, trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app"; import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Icon, Input, Label } from "@calcom/ui"; import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Input, Label } from "@calcom/ui";
import { FiCheck, FiEdit2, FiExternalLink, StarIconSolid } from "@calcom/ui/components/icon";
export enum UsernameChangeStatusEnum { export enum UsernameChangeStatusEnum {
UPGRADE = "UPGRADE", UPGRADE = "UPGRADE",
@@ -190,7 +191,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="flex rounded-md"> <div className="flex rounded-md">
<span <span
className={classNames( className={classNames(
isInputUsernamePremium ? "border-1 border-orange-400 " : "", isInputUsernamePremium ? "border border-orange-400 " : "",
"hidden h-9 items-center rounded-l-md border border-r-0 border-gray-300 border-r-gray-300 bg-gray-50 px-3 text-sm text-gray-500 md:inline-flex" "hidden h-9 items-center rounded-l-md border border-r-0 border-gray-300 border-r-gray-300 bg-gray-50 px-3 text-sm text-gray-500 md:inline-flex"
)}> )}>
{process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")}/ {process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")}/
@@ -207,8 +208,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
className={classNames( className={classNames(
"border-l-1 mb-0 mt-0 rounded-md rounded-l-none font-sans text-sm leading-4 focus:!ring-0", "border-l-1 mb-0 mt-0 rounded-md rounded-l-none font-sans text-sm leading-4 focus:!ring-0",
isInputUsernamePremium isInputUsernamePremium
? "border-1 focus:border-1 border-orange-400 focus:border-orange-400" ? "border border-orange-400 focus:border focus:border-orange-400"
: "border-1 focus:border-2", : "border focus:border",
markAsError markAsError
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none" ? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none"
: "border-l-gray-300", : "border-l-gray-300",
@@ -230,8 +231,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
isInputUsernamePremium ? "text-orange-400" : "", isInputUsernamePremium ? "text-orange-400" : "",
usernameIsAvailable ? "" : "" usernameIsAvailable ? "" : ""
)}> )}>
{isInputUsernamePremium ? <Icon.StarIconSolid className="mt-[2px] w-6" /> : <></>} {isInputUsernamePremium ? <StarIconSolid className="mt-[2px] w-6" /> : <></>}
{!isInputUsernamePremium && usernameIsAvailable ? <Icon.FiCheck className="mt-2 w-6" /> : <></>} {!isInputUsernamePremium && usernameIsAvailable ? <FiCheck className="mt-2 w-6" /> : <></>}
</span> </span>
</div> </div>
</div> </div>
@@ -249,7 +250,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<DialogContent> <DialogContent>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]"> <div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiEdit2 className="m-auto h-6 w-6" /> <FiEdit2 className="m-auto h-6 w-6" />
</div> </div>
<div className="mb-4 w-full px-4 pt-1"> <div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} /> <DialogHeader title={t("confirm_username_change_dialog_title")} />
@@ -283,7 +284,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
data-testid="go-to-billing" data-testid="go-to-billing"
href={paymentLink}> href={paymentLink}>
<> <>
{t("go_to_stripe_billing")} <Icon.FiExternalLink className="ml-1 h-4 w-4" /> {t("go_to_stripe_billing")} <FiExternalLink className="ml-1 h-4 w-4" />
</> </>
</Button> </Button>
)} )}
@@ -7,17 +7,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TRPCClientErrorLike } from "@calcom/trpc/client"; import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app"; import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { import { Button, Dialog, DialogClose, DialogContent, DialogHeader, TextField } from "@calcom/ui";
Button, import { FiCheck, FiEdit2 } from "@calcom/ui/components/icon";
Dialog,
DialogClose,
DialogContent,
DialogHeader,
Icon,
Input,
Label,
TextField,
} from "@calcom/ui";
interface ICustomUsernameProps { interface ICustomUsernameProps {
currentUsername: string | undefined; currentUsername: string | undefined;
@@ -131,7 +122,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
autoCapitalize="none" autoCapitalize="none"
autoCorrect="none" autoCorrect="none"
className={classNames( className={classNames(
"mb-0 mt-0 h-6 rounded-md ltr:rounded-l-none rtl:rounded-r-none", "mb-0 mt-0 rounded-md ltr:rounded-l-none rtl:rounded-r-none",
markAsError markAsError
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0" ? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
: "" : ""
@@ -145,7 +136,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
{currentUsername !== inputUsernameValue && ( {currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex flex-row"> <div className="absolute right-[2px] top-6 flex flex-row">
<span className={classNames("mx-2 py-2")}> <span className={classNames("mx-2 py-2")}>
{usernameIsAvailable ? <Icon.FiCheck className="w-6" /> : <></>} {usernameIsAvailable ? <FiCheck className="w-6" /> : <></>}
</span> </span>
</div> </div>
)} )}
@@ -165,7 +156,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
<DialogContent> <DialogContent>
<div style={{ display: "flex", flexDirection: "row" }}> <div style={{ display: "flex", flexDirection: "row" }}>
<div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]"> <div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiEdit2 className="m-auto h-6 w-6" /> <FiEdit2 className="m-auto h-6 w-6" />
</div> </div>
<div className="mb-4 w-full px-4 pt-1"> <div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} /> <DialogHeader title={t("confirm_username_change_dialog_title")} />
@@ -2,9 +2,9 @@ import React from "react";
import { Props } from "react-select"; import { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui"; import { Avatar } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
import Avatar from "@components/ui/Avatar";
import Select from "@components/ui/form/Select"; import Select from "@components/ui/form/Select";
type CheckedSelectOption = { type CheckedSelectOption = {
@@ -41,14 +41,15 @@ export const CheckedSelect = ({
{...props} {...props}
/> />
{value.map((option) => ( {value.map((option) => (
<div key={option.value} className="border-1 border p-2 font-medium"> <div key={option.value} className="border p-2 font-medium">
<Avatar <Avatar
className="inline h-6 w-6 rounded-full ltr:mr-2 rtl:ml-2" className="inline ltr:mr-2 rtl:ml-2"
size="sm"
imageSrc={option.avatar} imageSrc={option.avatar}
alt={option.label} alt={option.label}
/> />
{option.label} {option.label}
<Icon.FiX <FiX
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))} onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500" className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500"
/> />
+2 -2
View File
@@ -3,7 +3,7 @@ import "react-calendar/dist/Calendar.css";
import "react-date-picker/dist/DatePicker.css"; import "react-date-picker/dist/DatePicker.css";
import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle"; import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle";
import { Icon } from "@calcom/ui"; import { FiCalendar } from "@calcom/ui/components/icon";
import classNames from "@lib/classNames"; import classNames from "@lib/classNames";
@@ -23,7 +23,7 @@ export const DatePicker = ({ minDate, disabled, date, onDatesChange, className }
className className
)} )}
clearIcon={null} clearIcon={null}
calendarIcon={<Icon.FiCalendar className="h-5 w-5 text-gray-500" />} calendarIcon={<FiCalendar className="h-5 w-5 text-gray-500" />}
value={date} value={date}
minDate={minDate} minDate={minDate}
disabled={disabled} disabled={disabled}
@@ -0,0 +1,64 @@
import { components, GroupBase, Props, SingleValue } from "react-select";
import type { EventLocationType } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import Select from "@components/ui/form/Select";
export type LocationOption = {
label: string;
value: EventLocationType["type"];
icon?: string;
disabled?: boolean;
};
export type SingleValueLocationOption = SingleValue<LocationOption>;
export type GroupOptionType = GroupBase<LocationOption>;
const OptionWithIcon = ({
icon,
isSelected,
label,
}: {
icon?: string;
isSelected?: boolean;
label: string;
}) => {
return (
<div className="flex items-center gap-3">
{icon && <img src={icon} alt="cover" className="h-3.5 w-3.5" />}
<span className={classNames("text-sm font-medium", isSelected ? "text-white" : "text-gray-900")}>
{label}
</span>
</div>
);
};
export default function LocationSelect(props: Props<LocationOption, false, GroupOptionType>) {
return (
<Select<LocationOption>
name="location"
components={{
Option: (props) => (
<components.Option {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} isSelected={props.isSelected} />
</components.Option>
),
SingleValue: (props) => (
<components.SingleValue {...props}>
<OptionWithIcon icon={props.data.icon} label={props.data.label} />
</components.SingleValue>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-3">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
{...props}
/>
);
}
+1
View File
@@ -1,3 +1,4 @@
// lets refactor and move this into packages/lib/hooks/
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
const useMediaQuery = (query: string) => { const useMediaQuery = (query: string) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@calcom/web", "name": "@calcom/web",
"version": "2.4.7", "version": "2.5.5",
"private": true, "private": true,
"scripts": { "scripts": {
"analyze": "ANALYZE=true next build", "analyze": "ANALYZE=true next build",
+13 -12
View File
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { COMPANY_NAME, DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants"; import { COMPANY_NAME, DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
import { Icon, HeadSeo } from "@calcom/ui"; import { HeadSeo } from "@calcom/ui";
import { FiFileText, FiCheck, FiBookOpen, FiChevronRight } from "@calcom/ui/components/icon";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
@@ -20,13 +21,13 @@ export default function Custom404() {
{ {
title: t("documentation"), title: t("documentation"),
description: t("documentation_description"), description: t("documentation_description"),
icon: Icon.FiFileText, icon: FiFileText,
href: DOCS_URL, href: DOCS_URL,
}, },
{ {
title: t("blog"), title: t("blog"),
description: t("blog_description"), description: t("blog_description"),
icon: Icon.FiBookOpen, icon: FiBookOpen,
href: `${WEBSITE_URL}/blog`, href: `${WEBSITE_URL}/blog`,
}, },
]; ];
@@ -75,7 +76,7 @@ export default function Custom404() {
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse"> className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50"> <span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50">
<Icon.FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" /> <FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
</span> </span>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@@ -90,7 +91,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("the_infrastructure_plan")}</p> <p className="text-base text-gray-500">{t("the_infrastructure_plan")}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</a> </a>
</li> </li>
@@ -103,7 +104,7 @@ export default function Custom404() {
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse"> className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50"> <span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" /> <FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span> </span>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@@ -116,7 +117,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p> <p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</Link> </Link>
</li> </li>
@@ -161,7 +162,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("join_our_community")}</p> <p className="text-base text-gray-500">{t("join_our_community")}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</a> </a>
</li> </li>
@@ -218,7 +219,7 @@ export default function Custom404() {
rel="noreferrer"> rel="noreferrer">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50"> <span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50">
<Icon.FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" /> <FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
</span> </span>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@@ -233,7 +234,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("claim_username_and_schedule_events")}</p> <p className="text-base text-gray-500">{t("claim_username_and_schedule_events")}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</a> </a>
</li> </li>
@@ -261,7 +262,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{link.description}</p> <p className="text-base text-gray-500">{link.description}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</Link> </Link>
</li> </li>
@@ -307,7 +308,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("join_our_community")}</p> <p className="text-base text-gray-500">{t("join_our_community")}</p>
</div> </div>
<div className="flex-shrink-0 self-center"> <div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" /> <FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div> </div>
</a> </a>
</li> </li>
+3 -2
View File
@@ -3,7 +3,8 @@ import { useRouter } from "next/router";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants"; import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, showToast } from "@calcom/ui"; import { Button, showToast } from "@calcom/ui";
import { FiCopy } from "@calcom/ui/components/icon";
export default function Error500() { export default function Error500() {
const { t } = useLocale(); const { t } = useLocale();
@@ -33,7 +34,7 @@ export default function Error500() {
<Button <Button
color="secondary" color="secondary"
className="mt-2 border-0 font-sans font-normal hover:bg-gray-300" className="mt-2 border-0 font-sans font-normal hover:bg-gray-300"
StartIcon={Icon.FiCopy} StartIcon={FiCopy}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(router.query.error as string); navigator.clipboard.writeText(router.query.error as string);
showToast("Link copied!", "success"); showToast("Link copied!", "success");
+9 -11
View File
@@ -26,13 +26,12 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { baseEventTypeSelect } from "@calcom/prisma/selects"; import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Icon, HeadSeo, AvatarGroup } from "@calcom/ui"; import { HeadSeo, AvatarGroup, Avatar } from "@calcom/ui";
import { BadgeCheckIcon, FiArrowRight } from "@calcom/ui/components/icon";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr"; import { EmbedProps } from "@lib/withEmbedSsr";
import { AvatarSSR } from "@components/ui/AvatarSSR";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) { export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
@@ -56,8 +55,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{eventTypes.map((type, index) => ( {eventTypes.map((type, index) => (
<li <li
key={index} key={index}
className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600"> className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<Icon.FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" /> <FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
<Link <Link
href={getUsernameSlugLink({ users: props.users, slug: type.slug })} href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
className="flex justify-between px-6 py-4" className="flex justify-between px-6 py-4"
@@ -132,11 +131,11 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
)}> )}>
{isSingleUser && ( // When we deal with a single user, not dynamic group {isSingleUser && ( // When we deal with a single user, not dynamic group
<div className="mb-8 text-center"> <div className="mb-8 text-center">
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} /> <Avatar imageSrc={user.avatar} size="xl" alt={nameOrUsername} />
<h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white"> <h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white">
{nameOrUsername} {nameOrUsername}
{user.verified && ( {user.verified && (
<Icon.BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" /> <BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
)} )}
</h1> </h1>
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p> <p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
@@ -145,8 +144,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<div <div
className={classNames( className={classNames(
"rounded-md ", "rounded-md ",
!isEventListEmpty && !isEventListEmpty && "border border-gray-200 dark:border-gray-700 dark:hover:border-gray-600"
"border border-neutral-200 dark:border-neutral-700 dark:hover:border-neutral-600"
)} )}
data-testid="event-types"> data-testid="event-types">
{user.away ? ( {user.away ? (
@@ -165,8 +163,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<div <div
key={type.id} key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }} style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600"> className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<Icon.FiArrowRight className="absolute right-4 top-4 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" /> <FiArrowRight className="absolute right-4 top-4 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */} {/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<Link <Link
prefetch={false} prefetch={false}
+1 -1
View File
@@ -24,7 +24,7 @@ export default function Type(props: AvailabilityPageProps) {
const { t } = useLocale(); const { t } = useLocale();
return props.away ? ( return props.away ? (
<div className="h-screen dark:bg-neutral-900"> <div className="h-screen dark:bg-gray-900">
<main className="mx-auto max-w-3xl px-4 py-24"> <main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types"> <div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900"> <div className="overflow-hidden rounded-sm border dark:border-gray-900">
+2 -2
View File
@@ -27,7 +27,7 @@ export type BookPageProps = inferSSRProps<typeof getServerSideProps>;
export default function Book(props: BookPageProps) { export default function Book(props: BookPageProps) {
const { t } = useLocale(); const { t } = useLocale();
return props.away ? ( return props.away ? (
<div className="h-screen dark:bg-neutral-900"> <div className="h-screen dark:bg-gray-900">
<main className="mx-auto max-w-3xl px-4 py-24"> <main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types"> <div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900"> <div className="overflow-hidden rounded-sm border dark:border-gray-900">
@@ -42,7 +42,7 @@ export default function Book(props: BookPageProps) {
</main> </main>
</div> </div>
) : props.isDynamicGroupBooking && !props.profile.allowDynamicBooking ? ( ) : props.isDynamicGroupBooking && !props.profile.allowDynamicBooking ? (
<div className="h-screen dark:bg-neutral-900"> <div className="h-screen dark:bg-gray-900">
<main className="mx-auto max-w-3xl px-4 py-24"> <main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types"> <div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900"> <div className="overflow-hidden rounded-sm border dark:border-gray-900">
+1 -1
View File
@@ -81,7 +81,7 @@ class MyDocument extends Document<Props> {
</Head> </Head>
<body <body
className="dark:bg-darkgray-50 desktop-transparent bg-gray-100" className="dark:bg-darkgray-50 desktop-transparent bg-gray-100 antialiased"
style={ style={
this.props.isEmbed this.props.isEmbed
? { ? {
+6 -1
View File
@@ -1,10 +1,10 @@
import { IdentityProvider } from "@prisma/client"; import { IdentityProvider } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { hashPassword } from "@calcom/lib/auth";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager"; import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { hashPassword } from "@lib/auth";
import slugify from "@lib/slugify"; import slugify from "@lib/slugify";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -12,6 +12,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return; return;
} }
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
res.status(403).json({ message: "Signup is disabled" });
return;
}
const data = req.body; const data = req.body;
const { email, password } = data; const { email, password } = data;
const username = slugify(data.username); const username = slugify(data.username);
+16 -5
View File
@@ -35,6 +35,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
licenseRequired={data.licenseRequired} licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly} isProOnly={data.isProOnly}
images={source.data?.items as string[] | undefined} images={source.data?.items as string[] | undefined}
isTemplate={data.isTemplate}
// tos="https://zoom.us/terms" // tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy" // privacy="https://zoom.us/privacy"
body={ body={
@@ -53,7 +54,7 @@ export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
return { return {
paths, paths,
fallback: false, fallback: "blocking",
}; };
}; };
@@ -61,7 +62,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true }; if (typeof ctx.params?.slug !== "string") return { notFound: true };
const app = await prisma.app.findUnique({ const app = await prisma.app.findUnique({
where: { slug: ctx.params.slug }, where: { slug: ctx.params.slug.toLowerCase() },
}); });
if (!app) return { notFound: true }; if (!app) return { notFound: true };
@@ -70,21 +71,31 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (!singleApp) return { notFound: true }; if (!singleApp) return { notFound: true };
const appDirname = app.dirName; const isTemplate = singleApp.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", app.dirName);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`); const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH); const postFilePath = path.join(README_PATH);
let source = ""; let source = "";
try { try {
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString(); source = fs.readFileSync(postFilePath).toString();
source = source.replace(/{DESCRIPTION}/g, singleApp.description);
} catch (error) { } catch (error) {
/* If the app doesn't have a README we fallback to the package description */
console.log(`No DESCRIPTION.md provided for: ${appDirname}`); console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = singleApp.description; source = singleApp.description;
} }
const { content, data } = matter(source); const { content, data } = matter(source);
if (data.items) {
data.items = data.items.map((item: string) => {
if (!item.includes("/api/app-store")) {
// Make relative paths absolute
return `/api/app-store/${appDirname}/${item}`;
}
return item;
});
}
return { return {
props: { props: {
source: { content, data }, source: { content, data },
+4 -3
View File
@@ -4,7 +4,8 @@ import Link from "next/link";
import { getAppRegistry } from "@calcom/app-store/_appRegistry"; import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, SkeletonText } from "@calcom/ui"; import { SkeletonText } from "@calcom/ui";
import { FiArrowLeft, FiArrowRight } from "@calcom/ui/components/icon";
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) { export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale(); const { t, isLocaleReady } = useLocale();
@@ -15,7 +16,7 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
<Link <Link
href="/apps" href="/apps"
className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900"> className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" /> <FiArrowLeft className="h-4 w-4" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "} {isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</Link> </Link>
</div> </div>
@@ -31,7 +32,7 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
<h3 className="font-medium capitalize">{category.name}</h3> <h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "} {t("number_apps", { count: category.count })}{" "}
<Icon.FiArrowRight className="inline-block h-4 w-4" /> <FiArrowRight className="inline-block h-4 w-4" />
</p> </p>
</div> </div>
</Link> </Link>
+43 -14
View File
@@ -7,12 +7,31 @@ import { getSession } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/client"; import type { AppCategories } from "@calcom/prisma/client";
import { inferSSRProps } from "@calcom/types/inferSSRProps"; import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { AllApps, AppStoreCategories, Icon, TextField, TrendingAppsSlider } from "@calcom/ui"; import {
AllApps,
AppStoreCategories,
HorizontalTabItemProps,
HorizontalTabs,
TextField,
PopularAppsSlider,
} from "@calcom/ui";
import { FiSearch } from "@calcom/ui/components/icon";
import AppsLayout from "@components/apps/layouts/AppsLayout"; import AppsLayout from "@components/apps/layouts/AppsLayout";
import { ssgInit } from "@server/lib/ssg"; import { ssgInit } from "@server/lib/ssg";
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
function AppsSearch({ function AppsSearch({
onChange, onChange,
className, className,
@@ -23,7 +42,7 @@ function AppsSearch({
return ( return (
<TextField <TextField
className="!border-gray-100 bg-gray-100 !pl-0 focus:!ring-offset-0" className="!border-gray-100 bg-gray-100 !pl-0 focus:!ring-offset-0"
addOnLeading={<Icon.FiSearch className="h-4 w-4 text-gray-500" />} addOnLeading={<FiSearch className="h-4 w-4 text-gray-500" />}
addOnClassname="!border-gray-100" addOnClassname="!border-gray-100"
containerClassName={classNames("focus:!ring-offset-0", className)} containerClassName={classNames("focus:!ring-offset-0", className)}
type="search" type="search"
@@ -43,20 +62,30 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
heading={t("app_store")} heading={t("app_store")}
subtitle={t("app_store_description")} subtitle={t("app_store_description")}
actions={(className) => ( actions={(className) => (
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} /> <div className="flex w-full flex-col md:flex-row md:justify-between lg:w-auto">
<div className="lg:hidden">
<HorizontalTabs tabs={tabs} />
</div>
<div>
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
</div>
</div>
)} )}
headerClassName="sm:hidden lg:block hidden"
emptyStore={!appStore.length}> emptyStore={!appStore.length}>
{!searchText && ( <div className="flex flex-col gap-y-8">
<> {!searchText && (
<AppStoreCategories categories={categories} /> <>
<TrendingAppsSlider items={appStore} /> <AppStoreCategories categories={categories} />
</> <PopularAppsSlider items={appStore} />
)} </>
<AllApps )}
apps={appStore} <AllApps
searchText={searchText} apps={appStore}
categories={categories.map((category) => category.name)} searchText={searchText}
/> categories={categories.map((category) => category.name)}
/>
</div>
</AppsLayout> </AppsLayout>
); );
} }
+37 -28
View File
@@ -13,11 +13,19 @@ import {
Alert, Alert,
Button, Button,
EmptyScreen, EmptyScreen,
Icon,
List, List,
AppSkeletonLoader as SkeletonLoader, AppSkeletonLoader as SkeletonLoader,
ShellSubHeading, ShellSubHeading,
} from "@calcom/ui"; } from "@calcom/ui";
import {
FiBarChart,
FiCalendar,
FiCreditCard,
FiGrid,
FiPlus,
FiShare2,
FiVideo,
} from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
@@ -92,8 +100,8 @@ function ConnectOrDisconnectIntegrationButton(props: {
} }
interface IntegrationsContainerProps { interface IntegrationsContainerProps {
variant?: keyof typeof InstalledAppVariants; variant?: typeof InstalledAppVariants[number];
exclude?: (keyof typeof InstalledAppVariants)[]; exclude?: typeof InstalledAppVariants[number][];
} }
interface IntegrationsListProps { interface IntegrationsListProps {
@@ -115,6 +123,7 @@ const IntegrationsList = ({ data }: IntegrationsListProps) => {
logo={item.logo} logo={item.logo}
description={item.description} description={item.description}
separate={true} separate={true}
isTemplate={item.isTemplate}
invalidCredential={item.invalidCredentialIds.length > 0} invalidCredential={item.invalidCredentialIds.length > 0}
actions={ actions={
<div className="flex w-16 justify-end"> <div className="flex w-16 justify-end">
@@ -138,13 +147,15 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
const { t } = useLocale(); const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant, exclude, onlyInstalled: true }); const query = trpc.viewer.integrations.useQuery({ variant, exclude, onlyInstalled: true });
const emptyIcon = { const emptyIcon = {
calendar: Icon.FiCalendar, calendar: FiCalendar,
conferencing: Icon.FiVideo, conferencing: FiVideo,
automation: Icon.FiShare2, automation: FiShare2,
analytics: Icon.FiBarChart, analytics: FiBarChart,
payment: Icon.FiCreditCard, payment: FiCreditCard,
other: Icon.FiGrid, web3: FiBarChart,
other: FiGrid,
}; };
return ( return (
<QueryCell <QueryCell
query={query} query={query}
@@ -166,7 +177,7 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
: "/apps" : "/apps"
} }
color="secondary" color="secondary"
StartIcon={Icon.FiPlus}> StartIcon={FiPlus}>
{t("add")} {t("add")}
</Button> </Button>
} }
@@ -198,31 +209,29 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
}; };
const querySchema = z.object({ const querySchema = z.object({
category: z.nativeEnum(InstalledAppVariants), category: z.enum(InstalledAppVariants),
}); });
type querySchemaType = z.infer<typeof querySchema>;
export default function InstalledApps() { export default function InstalledApps() {
const { t } = useLocale(); const { t } = useLocale();
const router = useRouter(); const router = useRouter();
const category = router.query.category; const category = router.query.category as querySchemaType["category"];
const categoryList: querySchemaType["category"][] = [
"payment",
"conferencing",
"automation",
"analytics",
"web3",
];
return ( return (
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}> <InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
{(category === InstalledAppVariants.payment || category === InstalledAppVariants.conferencing) && ( {categoryList.includes(category) && <IntegrationsContainer variant={category} />}
<IntegrationsContainer variant={category} /> {category === "calendar" && <CalendarListContainer />}
)} {category === "other" && (
{(category === InstalledAppVariants.automation || category === InstalledAppVariants.analytics) && ( <IntegrationsContainer variant={category} exclude={[...categoryList, "calendar"]} />
<IntegrationsContainer variant={category} />
)}
{category === InstalledAppVariants.calendar && <CalendarListContainer />}
{category === InstalledAppVariants.other && (
<IntegrationsContainer
exclude={[
InstalledAppVariants.conferencing,
InstalledAppVariants.calendar,
InstalledAppVariants.analytics,
InstalledAppVariants.automation,
]}
/>
)} )}
</InstalledAppsLayout> </InstalledAppsLayout>
); );
+3 -2
View File
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
import z from "zod"; import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, SkeletonText } from "@calcom/ui"; import { Button, SkeletonText } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
import AuthContainer from "@components/ui/AuthContainer"; import AuthContainer from "@components/ui/AuthContainer";
@@ -28,7 +29,7 @@ export default function Error() {
<AuthContainer title="" description=""> <AuthContainer title="" description="">
<div> <div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" /> <FiX className="h-6 w-6 text-red-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
+10 -3
View File
@@ -13,7 +13,8 @@ import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { Alert, Button, EmailField, Icon, PasswordField } from "@calcom/ui"; import { Alert, Button, EmailField, PasswordField } from "@calcom/ui";
import { FiArrowLeft } from "@calcom/ui/components/icon";
import { ErrorCode, getSession } from "@lib/auth"; import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants"; import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
@@ -85,7 +86,7 @@ export default function Login({
setTwoFactorRequired(false); setTwoFactorRequired(false);
methods.setValue("totpCode", ""); methods.setValue("totpCode", "");
}} }}
StartIcon={Icon.FiArrowLeft} StartIcon={FiArrowLeft}
color="minimal"> color="minimal">
{t("go_back")} {t("go_back")}
</Button> </Button>
@@ -115,7 +116,13 @@ export default function Login({
description={t("login")} description={t("login")}
showLogo showLogo
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")} heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}> footerText={
twoFactorRequired
? TwoFactorFooter
: process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true"
? LoginFooter
: null
}>
<FormProvider {...methods}> <FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form"> <form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
<div> <div>
+3 -2
View File
@@ -5,7 +5,8 @@ import { useEffect } from "react";
import { WEBSITE_URL } from "@calcom/lib/constants"; import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon } from "@calcom/ui"; import { Button } from "@calcom/ui";
import { FiCheck } from "@calcom/ui/components/icon";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -31,7 +32,7 @@ export default function Logout(props: Props) {
<AuthContainer title={t("logged_out")} description={t("youve_been_logged_out")} showLogo> <AuthContainer title={t("logged_out")} description={t("youve_been_logged_out")} showLogo>
<div className="mb-4"> <div className="mb-4">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<Icon.FiCheck className="h-6 w-6 text-green-600" /> <FiCheck className="h-6 w-6 text-green-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
+2 -2
View File
@@ -1,7 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui"; import { FiCheck } from "@calcom/ui/components/icon";
const StepDone = () => { const StepDone = () => {
const router = useRouter(); const router = useRouter();
@@ -18,7 +18,7 @@ const StepDone = () => {
}}> }}>
<div className="min-h-36 my-6 flex flex-col items-center justify-center"> <div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white"> <div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<Icon.FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" /> <FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div> </div>
<div className="max-w-[420px] text-center"> <div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2> <h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
+4 -7
View File
@@ -15,7 +15,6 @@ import type { Schedule as ScheduleType, TimeRange, WorkingHours } from "@calcom/
import { import {
Button, Button,
Form, Form,
Icon,
Label, Label,
showToast, showToast,
Skeleton, Skeleton,
@@ -25,6 +24,7 @@ import {
Tooltip, Tooltip,
VerticalDivider, VerticalDivider,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiInfo, FiPlus } from "@calcom/ui/components/icon";
import { HttpError } from "@lib/core/http/error"; import { HttpError } from "@lib/core/http/error";
@@ -56,7 +56,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
{t("date_overrides")}{" "} {t("date_overrides")}{" "}
<Tooltip content={t("date_overrides_info")}> <Tooltip content={t("date_overrides_info")}>
<span className="inline-block"> <span className="inline-block">
<Icon.FiInfo /> <FiInfo />
</span> </span>
</Tooltip> </Tooltip>
</h3> </h3>
@@ -74,7 +74,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))} excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
onChange={(ranges) => append({ ranges })} onChange={(ranges) => append({ ranges })}
Trigger={ Trigger={
<Button color="secondary" StartIcon={Icon.FiPlus} data-testid="add-override"> <Button color="secondary" StartIcon={FiPlus} data-testid="add-override">
Add an override Add an override
</Button> </Button>
} }
@@ -186,11 +186,8 @@ export default function Availability({ schedule }: { schedule: number }) {
}); });
}} }}
className="flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6"> className="flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6">
<div className="flex-1 divide-y divide-neutral-200 rounded-md border"> <div className="flex-1 divide-y divide-gray-200 rounded-md border">
<div className=" py-5 sm:p-6"> <div className=" py-5 sm:p-6">
<h3 className="mb-2 px-5 text-base font-medium leading-6 text-gray-900 sm:pl-0">
{t("change_start_end")}
</h3>
{typeof me.data?.weekStart === "string" && ( {typeof me.data?.weekStart === "string" && (
<Schedule <Schedule
control={control} control={control}
+4 -3
View File
@@ -5,7 +5,8 @@ import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { EmptyScreen, Icon, showToast } from "@calcom/ui"; import { EmptyScreen, showToast } from "@calcom/ui";
import { FiClock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell"; import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error"; import { HttpError } from "@lib/core/http/error";
@@ -76,7 +77,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
{schedules.length === 0 ? ( {schedules.length === 0 ? (
<div className="flex justify-center"> <div className="flex justify-center">
<EmptyScreen <EmptyScreen
Icon={Icon.FiClock} Icon={FiClock}
headline={t("new_schedule_heading")} headline={t("new_schedule_heading")}
description={t("new_schedule_description")} description={t("new_schedule_description")}
buttonRaw={<NewScheduleButton />} buttonRaw={<NewScheduleButton />}
@@ -84,7 +85,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
</div> </div>
) : ( ) : (
<div className="mb-16 overflow-hidden rounded-md border border-gray-200 bg-white"> <div className="mb-16 overflow-hidden rounded-md border border-gray-200 bg-white">
<ul className="divide-y divide-neutral-200" data-testid="schedules" ref={animationParentRef}> <ul className="divide-y divide-gray-200" data-testid="schedules" ref={animationParentRef}>
{schedules.map((schedule) => ( {schedules.map((schedule) => (
<ScheduleListItem <ScheduleListItem
displayOptions={{ displayOptions={{
+2 -2
View File
@@ -78,7 +78,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
.map((slot: IBusySlot) => ( .map((slot: IBusySlot) => (
<div <div
key={dayjs(slot.start).format("HH:mm")} key={dayjs(slot.start).format("HH:mm")}
className="overflow-hidden rounded-md bg-neutral-100" className="overflow-hidden rounded-md bg-gray-100"
data-testid="troubleshooter-busy-time"> data-testid="troubleshooter-busy-time">
<div className="px-4 py-5 text-black sm:p-6"> <div className="px-4 py-5 text-black sm:p-6">
{t("calendar_shows_busy_between")}{" "} {t("calendar_shows_busy_between")}{" "}
@@ -97,7 +97,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
</div> </div>
)); ));
return ( return (
<div className="overflow-hidden rounded-md bg-neutral-100"> <div className="overflow-hidden rounded-md bg-gray-100">
<div className="px-4 py-5 text-black sm:p-6">{t("calendar_no_busy_slots")}</div> <div className="px-4 py-5 text-black sm:p-6">{t("calendar_no_busy_slots")}</div>
</div> </div>
); );
+18 -15
View File
@@ -35,7 +35,8 @@ import { localStorage } from "@calcom/lib/webstorage";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client"; import { Prisma } from "@calcom/prisma/client";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui"; import { Button, EmailInput, HeadSeo } from "@calcom/ui";
import { FiX, FiChevronLeft, FiCheck, FiCalendar } from "@calcom/ui/components/icon";
import { timeZone } from "@lib/clock"; import { timeZone } from "@lib/clock";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -142,7 +143,7 @@ function RedirectionToast({ url }: { url: string }) {
setIsToastVisible(false); setIsToastVisible(false);
}} }}
className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white"> className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white">
<Icon.FiX className="h-6 w-6 text-white" /> <FiX className="h-6 w-6 text-white" />
</button> </button>
</div> </div>
</div> </div>
@@ -356,7 +357,7 @@ export default function Success(props: SuccessProps) {
<Link <Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"} href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white"> className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")} <FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</Link> </Link>
</div> </div>
)} )}
@@ -379,7 +380,7 @@ export default function Success(props: SuccessProps) {
<div <div
className={classNames( className={classNames(
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl", "main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-white dark:border-neutral-700", isBackgroundTransparent ? "" : "dark:bg-darkgray-100 dark:border-darkgray-200 bg-white",
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle" "px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
)} )}
role="dialog" role="dialog"
@@ -401,10 +402,10 @@ export default function Success(props: SuccessProps) {
<img src={giphyImage} alt="Gif from Giphy" /> <img src={giphyImage} alt="Gif from Giphy" />
)} )}
{!giphyImage && !needsConfirmation && !isCancelled && ( {!giphyImage && !needsConfirmation && !isCancelled && (
<Icon.FiCheck className="h-5 w-5 text-green-600" /> <FiCheck className="h-5 w-5 text-green-600" />
)} )}
{needsConfirmation && !isCancelled && <Icon.FiCalendar className="h-5 w-5 text-gray-900" />} {needsConfirmation && !isCancelled && <FiCalendar className="h-5 w-5 text-gray-900" />}
{isCancelled && <Icon.FiX className="h-5 w-5 text-red-600" />} {isCancelled && <FiX className="h-5 w-5 text-red-600" />}
</div> </div>
<div className="mt-6 mb-8 text-center last:mb-0"> <div className="mt-6 mb-8 text-center last:mb-0">
<h3 <h3
@@ -424,7 +425,8 @@ export default function Success(props: SuccessProps) {
<div className="mt-3"> <div className="mt-3">
<p className="text-gray-600 dark:text-gray-300">{getTitle()}</p> <p className="text-gray-600 dark:text-gray-300">{getTitle()}</p>
</div> </div>
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-300 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-200 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
{(isCancelled || reschedule) && cancellationReason && ( {(isCancelled || reschedule) && cancellationReason && (
<> <>
<div className="font-medium"> <div className="font-medium">
@@ -594,7 +596,7 @@ export default function Success(props: SuccessProps) {
</> </>
) : ( ) : (
<> <>
<hr className="border-bookinglightest dark:border-darkgray-300" /> <hr className="border-bookinglightest dark:border-darkgray-200" />
<CancelBooking <CancelBooking
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }} booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }}
profile={{ name: props.profile.name, slug: props.profile.slug }} profile={{ name: props.profile.name, slug: props.profile.slug }}
@@ -636,7 +638,7 @@ export default function Success(props: SuccessProps) {
encodeURIComponent(new RRule(props.eventType.recurringEvent).toString()) encodeURIComponent(new RRule(props.eventType.recurringEvent).toString())
: "") : "")
} }
className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white"> className="h-10 w-10 rounded-sm border border-gray-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-gray-700 dark:text-white">
<svg <svg
className="-mt-1.5 inline-block h-4 w-4" className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor" fill="currentColor"
@@ -659,7 +661,7 @@ export default function Success(props: SuccessProps) {
eventName eventName
) + (location ? "&location=" + location : "") ) + (location ? "&location=" + location : "")
} }
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" className="mx-2 h-10 w-10 rounded-sm border border-gray-200 px-3 py-2 dark:border-gray-700 dark:text-white"
target="_blank"> target="_blank">
<svg <svg
className="mr-1 -mt-1.5 inline-block h-4 w-4" className="mr-1 -mt-1.5 inline-block h-4 w-4"
@@ -683,7 +685,7 @@ export default function Success(props: SuccessProps) {
eventName eventName
) + (location ? "&location=" + location : "") ) + (location ? "&location=" + location : "")
} }
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" className="mx-2 h-10 w-10 rounded-sm border border-gray-200 px-3 py-2 dark:border-gray-700 dark:text-white"
target="_blank"> target="_blank">
<svg <svg
className="mr-1 -mt-1.5 inline-block h-4 w-4" className="mr-1 -mt-1.5 inline-block h-4 w-4"
@@ -696,7 +698,7 @@ export default function Success(props: SuccessProps) {
</Link> </Link>
<Link <Link
href={"data:text/calendar," + eventLink()} href={"data:text/calendar," + eventLink()}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" className="mx-2 h-10 w-10 rounded-sm border border-gray-200 px-3 py-2 dark:border-gray-700 dark:text-white"
download={props.eventType.title + ".ics"}> download={props.eventType.title + ".ics"}>
<svg <svg
version="1.1" version="1.1"
@@ -928,6 +930,7 @@ const schema = z.object({
uid: z.string(), uid: z.string(),
email: z.string().optional(), email: z.string().optional(),
eventTypeSlug: z.string().optional(), eventTypeSlug: z.string().optional(),
cancel: stringToBoolean,
}); });
const handleSeatsEventTypeOnBooking = ( const handleSeatsEventTypeOnBooking = (
@@ -961,7 +964,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context); const ssr = await ssrInit(context);
const parsedQuery = schema.safeParse(context.query); const parsedQuery = schema.safeParse(context.query);
if (!parsedQuery.success) return { notFound: true }; if (!parsedQuery.success) return { notFound: true };
const { uid, email, eventTypeSlug } = parsedQuery.data; const { uid, email, eventTypeSlug, cancel } = parsedQuery.data;
const bookingInfo = await prisma.booking.findFirst({ const bookingInfo = await prisma.booking.findFirst({
where: { where: {
@@ -1047,7 +1050,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs), customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
}; };
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl) { if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl && !cancel) {
return { return {
redirect: { redirect: {
destination: eventType.successRedirectUrl, destination: eventType.successRedirectUrl,
+3 -2
View File
@@ -9,7 +9,8 @@ import BookingLayout from "@calcom/features/bookings/layout/BookingLayout";
import { filterQuerySchema, useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; import { filterQuerySchema, useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Alert, Button, EmptyScreen, Icon } from "@calcom/ui"; import { Alert, Button, EmptyScreen } from "@calcom/ui";
import { FiCalendar } from "@calcom/ui/components/icon";
import { useInViewObserver } from "@lib/hooks/useInViewObserver"; import { useInViewObserver } from "@lib/hooks/useInViewObserver";
@@ -176,7 +177,7 @@ export default function Bookings() {
{query.status === "success" && isEmpty && ( {query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:pt-0"> <div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen <EmptyScreen
Icon={Icon.FiCalendar} Icon={FiCalendar}
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })} headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
description={t("no_status_bookings_yet_description", { description={t("no_status_bookings_yet_description", {
status: t(status).toLowerCase(), status: t(status).toLowerCase(),
+8 -7
View File
@@ -196,16 +196,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0", periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType, schedulingType: eventType.schedulingType,
minimumBookingNotice: eventType.minimumBookingNotice, minimumBookingNotice: eventType.minimumBookingNotice,
minimumBookingNoticeInDurationType: convertToNewDurationType(
"minutes",
findDurationType(eventType.minimumBookingNotice),
eventType.minimumBookingNotice
),
metadata, metadata,
hosts: !!eventType.hosts?.length hosts: !!eventType.hosts?.length
? eventType.hosts.filter((host) => !host.isFixed) ? eventType.hosts.filter((host) => !host.isFixed)
: eventType.users.map((user) => ({ userId: user.id })), : eventType.users
hostsFixed: eventType.hosts.filter((host) => host.isFixed), .filter(() => eventType.schedulingType === SchedulingType.ROUND_ROBIN)
.map((user) => ({ userId: user.id })),
hostsFixed: !!eventType.hosts?.length
? eventType.hosts.filter((host) => host.isFixed)
: eventType.users
.filter(() => eventType.schedulingType === SchedulingType.COLLECTIVE)
.map((user) => ({ userId: user.id })),
}, },
resolver: zodResolver( resolver: zodResolver(
z z
+47 -33
View File
@@ -26,13 +26,27 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
EmptyScreen, EmptyScreen,
Icon,
showToast, showToast,
Switch, Switch,
Avatar, Avatar,
AvatarGroup, AvatarGroup,
Tooltip, Tooltip,
} from "@calcom/ui"; } from "@calcom/ui";
import {
FiArrowDown,
FiArrowUp,
FiClipboard,
FiCode,
FiCopy,
FiEdit,
FiEdit2,
FiExternalLink,
FiLink,
FiMoreHorizontal,
FiTrash,
FiUpload,
FiUsers,
} from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell"; import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error"; import { HttpError } from "@lib/core/http/error";
@@ -251,7 +265,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
const lastItem = types[types.length - 1]; const lastItem = types[types.length - 1];
return ( return (
<div className="mb-16 flex overflow-hidden rounded-md border border-gray-200 bg-white"> <div className="mb-16 flex overflow-hidden rounded-md border border-gray-200 bg-white">
<ul ref={parent} className="!static w-full divide-y divide-neutral-200" data-testid="event-types"> <ul ref={parent} className="!static w-full divide-y divide-gray-200" data-testid="event-types">
{types.map((type, index) => { {types.map((type, index) => {
const embedLink = `${group.profile.slug}/${type.slug}`; const embedLink = `${group.profile.slug}/${type.slug}`;
const calLink = `${CAL_URL}/${embedLink}`; const calLink = `${CAL_URL}/${embedLink}`;
@@ -263,7 +277,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<button <button
className="invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]" className="invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, -1)}> onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" /> <FiArrowUp className="h-5 w-5" />
</button> </button>
)} )}
@@ -271,7 +285,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<button <button
className="invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]" className="invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, 1)}> onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" /> <FiArrowDown className="h-5 w-5" />
</button> </button>
)} )}
<MemoizedItem type={type} group={group} readOnly={readOnly} /> <MemoizedItem type={type} group={group} readOnly={readOnly} />
@@ -312,17 +326,17 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Button <Button
color="secondary" color="secondary"
target="_blank" target="_blank"
size="icon" variant="icon"
href={calLink} href={calLink}
StartIcon={Icon.FiExternalLink} StartIcon={FiExternalLink}
/> />
</Tooltip> </Tooltip>
<Tooltip content={t("copy_link")}> <Tooltip content={t("copy_link")}>
<Button <Button
color="secondary" color="secondary"
size="icon" variant="icon"
StartIcon={Icon.FiLink} StartIcon={FiLink}
onClick={() => { onClick={() => {
showToast(t("link_copied"), "success"); showToast(t("link_copied"), "success");
navigator.clipboard.writeText(calLink); navigator.clipboard.writeText(calLink);
@@ -336,9 +350,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
className="radix-state-open:rounded-r-md"> className="radix-state-open:rounded-r-md">
<Button <Button
type="button" type="button"
size="icon" variant="icon"
color="secondary" color="secondary"
StartIcon={Icon.FiMoreHorizontal} StartIcon={FiMoreHorizontal}
/> />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
@@ -346,7 +360,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem <DropdownItem
type="button" type="button"
data-testid={"event-type-edit-" + type.id} data-testid={"event-type-edit-" + type.id}
StartIcon={Icon.FiEdit2} StartIcon={FiEdit2}
onClick={() => router.push("/event-types/" + type.id)}> onClick={() => router.push("/event-types/" + type.id)}>
{t("edit")} {t("edit")}
</DropdownItem> </DropdownItem>
@@ -355,7 +369,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem <DropdownItem
type="button" type="button"
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy} StartIcon={FiCopy}
onClick={() => openDuplicateModal(type)}> onClick={() => openDuplicateModal(type)}>
{t("duplicate")} {t("duplicate")}
</DropdownItem> </DropdownItem>
@@ -364,22 +378,23 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<EmbedButton <EmbedButton
as={DropdownItem} as={DropdownItem}
type="button" type="button"
StartIcon={Icon.FiCode} StartIcon={FiCode}
className="w-full rounded-none" className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}> embedUrl={encodeURIComponent(embedLink)}>
{t("embed")} {t("embed")}
</EmbedButton> </EmbedButton>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" /> <DropdownMenuSeparator />
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */} {/* 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) && ( {(group.metadata?.readOnly === false || group.metadata.readOnly === null) && (
<DropdownMenuItem> <DropdownMenuItem>
<DropdownItem <DropdownItem
color="destructive"
onClick={() => { onClick={() => {
setDeleteDialogOpen(true); setDeleteDialogOpen(true);
setDeleteDialogTypeId(type.id); setDeleteDialogTypeId(type.id);
}} }}
StartIcon={Icon.FiTrash} StartIcon={FiTrash}
className="w-full rounded-none"> className="w-full rounded-none">
{t("delete")} {t("delete")}
</DropdownItem> </DropdownItem>
@@ -395,7 +410,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="min-w-9 mx-5 flex sm:hidden"> <div className="min-w-9 mx-5 flex sm:hidden">
<Dropdown> <Dropdown>
<DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}> <DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}>
<Button type="button" size="icon" color="secondary" StartIcon={Icon.FiMoreHorizontal} /> <Button type="button" variant="icon" color="secondary" StartIcon={FiMoreHorizontal} />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuPortal> <DropdownMenuPortal>
<DropdownMenuContent> <DropdownMenuContent>
@@ -403,7 +418,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Link href={calLink} target="_blank"> <Link href={calLink} target="_blank">
<Button <Button
color="minimal" color="minimal"
StartIcon={Icon.FiExternalLink} StartIcon={FiExternalLink}
className="w-full rounded-none"> className="w-full rounded-none">
{t("preview")} {t("preview")}
</Button> </Button>
@@ -415,7 +430,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal" color="minimal"
className="w-full rounded-none text-left" className="w-full rounded-none text-left"
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiClipboard} StartIcon={FiClipboard}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(calLink); navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success"); showToast(t("link_copied"), "success");
@@ -430,7 +445,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal" color="minimal"
className="w-full rounded-none" className="w-full rounded-none"
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiUpload} StartIcon={FiUpload}
onClick={() => { onClick={() => {
navigator navigator
.share({ .share({
@@ -451,7 +466,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
onClick={() => router.push("/event-types/" + type.id)} onClick={() => router.push("/event-types/" + type.id)}
color="minimal" color="minimal"
className="w-full rounded-none" className="w-full rounded-none"
StartIcon={Icon.FiEdit}> StartIcon={FiEdit}>
{t("edit")} {t("edit")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
@@ -461,12 +476,12 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal" color="minimal"
className="w-full rounded-none" className="w-full rounded-none"
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy} StartIcon={FiCopy}
onClick={() => openDuplicateModal(type)}> onClick={() => openDuplicateModal(type)}>
{t("duplicate")} {t("duplicate")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" /> <DropdownMenuSeparator />
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
<Button <Button
onClick={() => { onClick={() => {
@@ -474,7 +489,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
setDeleteDialogTypeId(type.id); setDeleteDialogTypeId(type.id);
}} }}
color="destructive" color="destructive"
StartIcon={Icon.FiTrash} StartIcon={FiTrash}
className="w-full rounded-none"> className="w-full rounded-none">
{t("delete")} {t("delete")}
</Button> </Button>
@@ -512,14 +527,13 @@ const EventTypeListHeading = ({
}: EventTypeListHeadingProps): JSX.Element => { }: EventTypeListHeadingProps): JSX.Element => {
return ( return (
<div className="mb-4 flex items-center space-x-2"> <div className="mb-4 flex items-center space-x-2">
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}> <Avatar
<Avatar alt={profile?.name || ""}
alt={profile?.name || ""} href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined} imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size="sm" size="md"
className="mt-1 inline ltr:mr-2 rtl:ml-2" className="mt-1 inline-flex justify-center"
/> />
</Link>
<div> <div>
<Link <Link
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"} href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
@@ -530,7 +544,7 @@ const EventTypeListHeading = ({
<span className="relative -top-px text-xs text-gray-500 ltr:ml-2 ltr:mr-2 rtl:ml-2"> <span className="relative -top-px text-xs text-gray-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
<Link href={`/settings/teams/${teamId}/members`}> <Link href={`/settings/teams/${teamId}/members`}>
<Badge variant="gray"> <Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" /> <FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount} {membershipCount}
</Badge> </Badge>
</Link> </Link>
@@ -551,7 +565,7 @@ const CreateFirstEventTypeView = () => {
return ( return (
<EmptyScreen <EmptyScreen
Icon={Icon.FiLink} Icon={FiLink}
headline={t("new_event_type_heading")} headline={t("new_event_type_heading")}
description={t("new_event_type_description")} description={t("new_event_type_description")}
/> />
+4 -3
View File
@@ -8,7 +8,8 @@ import { classNames } from "@calcom/lib";
import { WEBAPP_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Button, Icon, Meta } from "@calcom/ui"; import { Button, Meta } from "@calcom/ui";
import { FiExternalLink } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@@ -29,7 +30,7 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
</div> </div>
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div> <div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
</section> </section>
<hr className="border-neutral-200" /> <hr className="border-gray-200" />
</> </>
); );
}; };
@@ -55,7 +56,7 @@ const BillingView = () => {
<CtaRow <CtaRow
title={t("billing_manage_details_title")} title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}> description={t("billing_manage_details_description")}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={Icon.FiExternalLink}> <Button color="primary" href={billingHref} target="_blank" EndIcon={FiExternalLink}>
{t("billing_portal")} {t("billing_portal")}
</Button> </Button>
</CtaRow> </CtaRow>
@@ -14,10 +14,10 @@ import {
Dialog, Dialog,
DialogContent, DialogContent,
EmptyScreen, EmptyScreen,
Icon,
Meta, Meta,
AppSkeletonLoader as SkeletonLoader, AppSkeletonLoader as SkeletonLoader,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiLink, FiPlus } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@@ -35,7 +35,7 @@ const ApiKeysView = () => {
return ( return (
<Button <Button
color="secondary" color="secondary"
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
onClick={() => { onClick={() => {
setApiKeyToEdit(undefined); setApiKeyToEdit(undefined);
setApiKeyModal(true); setApiKeyModal(true);
@@ -75,7 +75,7 @@ const ApiKeysView = () => {
</> </>
) : ( ) : (
<EmptyScreen <EmptyScreen
Icon={Icon.FiLink} Icon={FiLink}
headline={t("create_first_api_key")} headline={t("create_first_api_key")}
description={t("create_first_api_key_description", { appName: APP_NAME })} description={t("create_first_api_key_description", { appName: APP_NAME })}
buttonRaw={<NewApiKeyButton />} buttonRaw={<NewApiKeyButton />}
@@ -4,10 +4,10 @@ import { Controller, useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout"; import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { APP_NAME } from "@calcom/lib/constants"; import { APP_NAME } from "@calcom/lib/constants";
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasTeamPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { import {
Badge,
Button, Button,
ColorPicker, ColorPicker,
Form, Form,
@@ -17,6 +17,7 @@ import {
SkeletonContainer, SkeletonContainer,
SkeletonText, SkeletonText,
Switch, Switch,
UpgradeTeamsBadge,
} from "@calcom/ui"; } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@@ -50,6 +51,8 @@ const AppearanceView = () => {
const utils = trpc.useContext(); const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery(); const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { isLoading: isTeamPlanStatusLoading, hasTeamPlan } = useHasTeamPlan();
const formMethods = useForm({ const formMethods = useForm({
defaultValues: { defaultValues: {
theme: user?.theme, theme: user?.theme,
@@ -73,7 +76,8 @@ const AppearanceView = () => {
}, },
}); });
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />; if (isLoading || isTeamPlanStatusLoading)
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null; if (!user) return null;
@@ -121,7 +125,7 @@ const AppearanceView = () => {
/> />
</div> </div>
<hr className="border-1 my-8 border-neutral-200" /> <hr className="my-8 border border-gray-200" />
<div className="mb-6 flex items-center text-sm"> <div className="mb-6 flex items-center text-sm">
<div> <div>
<p className="font-semibold">{t("custom_brand_colors")}</p> <p className="font-semibold">{t("custom_brand_colors")}</p>
@@ -162,12 +166,12 @@ const AppearanceView = () => {
{/* TODO future PR to preview brandColors */} {/* TODO future PR to preview brandColors */}
{/* <Button {/* <Button
color="secondary" color="secondary"
EndIcon={Icon.FiExternalLink} EndIcon={FiExternalLink}
className="mt-6" className="mt-6"
onClick={() => window.open(`${WEBAPP_URL}/${user.username}/${user.eventTypes[0].title}`, "_blank")}> onClick={() => window.open(`${WEBAPP_URL}/${user.username}/${user.eventTypes[0].title}`, "_blank")}>
Preview Preview
</Button> */} </Button> */}
<hr className="border-1 my-8 border-neutral-200" /> <hr className="my-8 border border-gray-200" />
<Controller <Controller
name="hideBranding" name="hideBranding"
control={formMethods.control} control={formMethods.control}
@@ -180,18 +184,18 @@ const AppearanceView = () => {
<p className="font-semibold ltr:mr-2 rtl:ml-2"> <p className="font-semibold ltr:mr-2 rtl:ml-2">
{t("disable_cal_branding", { appName: APP_NAME })} {t("disable_cal_branding", { appName: APP_NAME })}
</p> </p>
<Badge variant="gray">{t("pro")}</Badge> <UpgradeTeamsBadge />
</div> </div>
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p> <p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Switch <Switch
id="hideBranding" id="hideBranding"
disabled={!session.data?.user.belongsToActiveTeam} disabled={!hasTeamPlan}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true }) formMethods.setValue("hideBranding", checked, { shouldDirty: true })
} }
checked={!session.data?.user.belongsToActiveTeam ? false : value} checked={hasTeamPlan ? value : false}
/> />
</div> </div>
</div> </div>
@@ -14,7 +14,6 @@ import {
Badge, Badge,
Button, Button,
EmptyScreen, EmptyScreen,
Icon,
List, List,
ListItem, ListItem,
ListItemText, ListItemText,
@@ -25,6 +24,7 @@ import {
SkeletonText, SkeletonText,
showToast, showToast,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiPlus, FiCalendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell"; import { QueryCell } from "@lib/QueryCell";
@@ -52,7 +52,7 @@ const AddCalendarButton = () => {
return ( return (
<> <>
<Button color="secondary" StartIcon={Icon.FiPlus} href="/apps/categories/calendar"> <Button color="secondary" StartIcon={FiPlus} href="/apps/categories/calendar">
{t("add_calendar")} {t("add_calendar")}
</Button> </Button>
</> </>
@@ -89,7 +89,7 @@ const CalendarsView = () => {
<div> <div>
<div className="mt-4 flex space-x-4 rounded-md border-gray-200 bg-gray-50 p-2 sm:mx-0 sm:p-10 md:border md:p-6 xl:mt-0"> <div className="mt-4 flex space-x-4 rounded-md border-gray-200 bg-gray-50 p-2 sm:mx-0 sm:p-10 md:border md:p-6 xl:mt-0">
<div className=" flex h-9 w-9 items-center justify-center rounded-md border-2 border-gray-200 bg-white p-[6px]"> <div className=" flex h-9 w-9 items-center justify-center rounded-md border-2 border-gray-200 bg-white p-[6px]">
<Icon.FiCalendar className="h-6 w-6" /> <FiCalendar className="h-6 w-6" />
</div> </div>
<div className="flex w-full flex-col space-y-3"> <div className="flex w-full flex-col space-y-3">
@@ -197,7 +197,7 @@ const CalendarsView = () => {
</div> </div>
) : ( ) : (
<EmptyScreen <EmptyScreen
Icon={Icon.FiCalendar} Icon={FiCalendar}
headline={t("no_calendar_installed")} headline={t("no_calendar_installed")}
description={t("no_calendar_installed_description")} description={t("no_calendar_installed_description")}
buttonText={t("add_a_calendar")} buttonText={t("add_a_calendar")}
@@ -13,17 +13,17 @@ import {
Dropdown, Dropdown,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger, DropdownMenuTrigger,
Icon,
List, List,
ListItem,
ListItemText,
ListItemTitle,
Meta, Meta,
showToast, showToast,
SkeletonContainer, SkeletonContainer,
SkeletonText, SkeletonText,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiAlertCircle, FiMoreHorizontal, FiTrash } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@@ -75,41 +75,37 @@ const ConferencingLayout = () => {
apps.items apps.items
.map((app) => ({ ...app, title: app.title || app.name })) .map((app) => ({ ...app, title: app.title || app.name }))
.map((app) => ( .map((app) => (
<ListItem className="flex-col border-0" key={app.title}> <AppListCard
<div className="flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse"> description={app.description}
{ title={app.title}
// eslint-disable-next-line @next/next/no-img-element logo={app.logo}
app.logo && <img className="h-10 w-10" src={app.logo} alt={app.title} /> key={app.title}
} isDefault={app.isGlobal}
<div className="flex-grow truncate pl-2"> actions={
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
<h3 className="truncate text-sm font-medium text-gray-900">{app.title}</h3>
</ListItemTitle>
<ListItemText component="p">{app.description}</ListItemText>
</div>
<div> <div>
<Dropdown> <Dropdown>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button StartIcon={Icon.FiMoreHorizontal} size="icon" color="secondary" /> <Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem> <DropdownMenuItem>
<Button <DropdownItem
type="button"
color="destructive" color="destructive"
StartIcon={Icon.FiTrash}
disabled={app.isGlobal} disabled={app.isGlobal}
StartIcon={FiTrash}
onClick={() => { onClick={() => {
setDeleteCredentialId(app.credentialIds[0]); setDeleteCredentialId(app.credentialIds[0]);
setDeleteAppModal(true); setDeleteAppModal(true);
}}> }}>
{t("remove_app")} {t("remove_app")}
</Button> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</Dropdown> </Dropdown>
</div> </div>
</div> }
</ListItem> />
))} ))}
</List> </List>
@@ -118,7 +114,7 @@ const ConferencingLayout = () => {
title={t("Remove app")} title={t("Remove app")}
description={t("are_you_sure_you_want_to_remove_this_app")} description={t("are_you_sure_you_want_to_remove_this_app")}
type="confirmation" type="confirmation"
Icon={Icon.FiAlertCircle}> Icon={FiAlertCircle}>
<DialogFooter> <DialogFooter>
<Button color="primary" onClick={() => deleteAppMutation.mutate({ id: deleteCredentialId })}> <Button color="primary" onClick={() => deleteAppMutation.mutate({ id: deleteCredentialId })}>
{t("yes_remove_app")} {t("yes_remove_app")}
@@ -22,7 +22,6 @@ import {
DialogFooter, DialogFooter,
DialogTrigger, DialogTrigger,
Form, Form,
Icon,
ImageUploader, ImageUploader,
Label, Label,
Meta, Meta,
@@ -34,6 +33,7 @@ import {
SkeletonText, SkeletonText,
TextField, TextField,
} from "@calcom/ui"; } from "@calcom/ui";
import { FiAlertTriangle, FiTrash2 } from "@calcom/ui/components/icon";
import TwoFactor from "@components/auth/TwoFactor"; import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@@ -223,13 +223,13 @@ const ProfileView = () => {
} }
/> />
<hr className="my-6 border-neutral-200" /> <hr className="my-6 border-gray-200" />
<Label>{t("danger_zone")}</Label> <Label>{t("danger_zone")}</Label>
{/* Delete account Dialog */} {/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}> <Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={Icon.FiTrash2}> <Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={FiTrash2}>
{t("delete_account")} {t("delete_account")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@@ -237,7 +237,7 @@ const ProfileView = () => {
title={t("delete_account_modal_title")} title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })} description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation" type="creation"
Icon={Icon.FiAlertTriangle}> Icon={FiAlertTriangle}>
<> <>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p> <p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && ( {isCALIdentityProviver && (
@@ -278,7 +278,7 @@ const ProfileView = () => {
title={t("confirm_password")} title={t("confirm_password")}
description={t("confirm_password_change_email")} description={t("confirm_password_change_email")}
type="creation" type="creation"
Icon={Icon.FiAlertTriangle}> Icon={FiAlertTriangle}>
<> <>
<PasswordField <PasswordField
data-testid="password" data-testid="password"
+11 -1
View File
@@ -92,7 +92,11 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
{...register("username")} {...register("username")}
required required
/> />
<EmailField {...register("email")} /> <EmailField
{...register("email")}
disabled={prepopulateFormValues?.email}
className="disabled:bg-gray-200 disabled:hover:cursor-not-allowed"
/>
<PasswordField <PasswordField
labelProps={{ labelProps={{
className: "block text-sm font-medium text-gray-700", className: "block text-sm font-medium text-gray-700",
@@ -145,6 +149,12 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
prepopulateFormValues: undefined, prepopulateFormValues: undefined,
}; };
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
return {
notFound: true,
};
}
// no token given, treat as a normal signup without verification token // no token given, treat as a normal signup without verification token
if (!token) { if (!token) {
return { return {
+5 -4
View File
@@ -12,7 +12,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme"; import useTheme from "@calcom/lib/hooks/useTheme";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, Icon, HeadSeo, AvatarGroup } from "@calcom/ui"; import { Avatar, Button, HeadSeo, AvatarGroup } from "@calcom/ui";
import { FiArrowRight } from "@calcom/ui/components/icon";
import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -36,12 +37,12 @@ function TeamPage({ team }: TeamPageProps) {
}, [telemetry, router.asPath]); }, [telemetry, router.asPath]);
const EventTypes = () => ( const EventTypes = () => (
<ul className="rounded-md border border-neutral-200 dark:border-neutral-700"> <ul className="rounded-md border border-gray-200 dark:border-gray-700">
{team.eventTypes.map((type, index) => ( {team.eventTypes.map((type, index) => (
<li <li
key={index} key={index}
className={classNames( className={classNames(
"dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600", "dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600",
!isEmbed && "bg-white" !isEmbed && "bg-white"
)}> )}>
<Link <Link
@@ -113,7 +114,7 @@ function TeamPage({ team }: TeamPageProps) {
<aside className="mt-8 flex justify-center text-center dark:text-white"> <aside className="mt-8 flex justify-center text-center dark:text-white">
<Button <Button
color="minimal" color="minimal"
EndIcon={Icon.FiArrowRight} EndIcon={FiArrowRight}
className="dark:hover:bg-darkgray-200" className="dark:hover:bg-darkgray-200"
href={`/team/${team.slug}?members=1`} href={`/team/${team.slug}?members=1`}
shallow={true}> shallow={true}>
+4 -3
View File
@@ -4,7 +4,8 @@ import { TeamsListing } from "@calcom/features/ee/teams/components";
import Shell from "@calcom/features/shell/Shell"; import Shell from "@calcom/features/shell/Shell";
import { WEBAPP_URL } from "@calcom/lib/constants"; import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon } from "@calcom/ui"; import { Button } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
@@ -16,8 +17,8 @@ function Teams() {
subtitle={t("create_manage_teams_collaborative")} subtitle={t("create_manage_teams_collaborative")}
CTA={ CTA={
<Button <Button
size="fab" variant="fab"
StartIcon={Icon.FiPlus} StartIcon={FiPlus}
type="button" type="button"
href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}> href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}>
{t("new")} {t("new")}
+5 -4
View File
@@ -4,7 +4,8 @@ import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat"; import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { Button, Icon, HeadSeo } from "@calcom/ui"; import { Button, HeadSeo } from "@calcom/ui";
import { FiArrowRight, FiCalendar, FiX } from "@calcom/ui/components/icon";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -28,7 +29,7 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div> <div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" /> <FiX className="h-6 w-6 text-red-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline">
@@ -40,14 +41,14 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
{props.booking.title} {props.booking.title}
</h2> </h2>
<p className="text-center text-gray-500"> <p className="text-center text-gray-500">
<Icon.FiCalendar className="mr-1 -mt-1 inline-block h-4 w-4" /> <FiCalendar className="mr-1 -mt-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")} {dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
</p> </p>
</div> </div>
</div> </div>
<div className="mt-5 text-center sm:mt-6"> <div className="mt-5 text-center sm:mt-6">
<div className="mt-5"> <div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={Icon.FiArrowRight}> <Button data-testid="return-home" href="/event-types" EndIcon={FiArrowRight}>
{t("go_back")} {t("go_back")}
</Button> </Button>
</div> </div>
@@ -5,7 +5,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat"; import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, Icon, HeadSeo } from "@calcom/ui"; import { Button, HeadSeo } from "@calcom/ui";
import { FiArrowRight, FiCalendar, FiX } from "@calcom/ui/components/icon";
export default function MeetingNotStarted(props: inferSSRProps<typeof getServerSideProps>) { export default function MeetingNotStarted(props: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale(); const { t } = useLocale();
@@ -26,7 +27,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div> <div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" /> <FiX className="h-6 w-6 text-red-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline">
@@ -38,7 +39,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
{props.booking.title} {props.booking.title}
</h2> </h2>
<p className="text-center text-gray-500"> <p className="text-center text-gray-500">
<Icon.FiCalendar className="mr-1 -mt-1 inline-block h-4 w-4" /> <FiCalendar className="mr-1 -mt-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")} {dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
</p> </p>
</div> </div>
@@ -50,7 +51,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
</div> </div>
<div className="mt-5 text-center sm:mt-6"> <div className="mt-5 text-center sm:mt-6">
<div className="mt-5"> <div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={Icon.FiArrowRight}> <Button data-testid="return-home" href="/event-types" EndIcon={FiArrowRight}>
{t("go_back")} {t("go_back")}
</Button> </Button>
</div> </div>
+4 -3
View File
@@ -1,5 +1,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, HeadSeo } from "@calcom/ui"; import { Button, HeadSeo } from "@calcom/ui";
import { FiX, FiArrowRight } from "@calcom/ui/components/icon";
export default function NoMeetingFound() { export default function NoMeetingFound() {
const { t } = useLocale(); const { t } = useLocale();
@@ -21,7 +22,7 @@ export default function NoMeetingFound() {
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div> <div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100"> <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" /> <FiX className="h-6 w-6 text-red-600" />
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-headline">
@@ -34,7 +35,7 @@ export default function NoMeetingFound() {
</div> </div>
<div className="mt-5 text-center sm:mt-6"> <div className="mt-5 text-center sm:mt-6">
<div className="mt-5"> <div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={Icon.FiArrowRight}> <Button data-testid="return-home" href="/event-types" EndIcon={FiArrowRight}>
{t("go_back_home")} {t("go_back_home")}
</Button> </Button>
</div> </div>
+26 -1
View File
@@ -1,6 +1,6 @@
import type { Page, WorkerInfo } from "@playwright/test"; import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client"; import type Prisma from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client"; import { Prisma as PrismaType, MembershipRole } from "@prisma/client";
import { hash } from "bcryptjs"; import { hash } from "bcryptjs";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
@@ -34,6 +34,27 @@ const seededForm = {
type UserWithIncludes = PrismaType.UserGetPayload<typeof userWithEventTypes>; type UserWithIncludes = PrismaType.UserGetPayload<typeof userWithEventTypes>;
const createTeamAndAddUser = async ({ user }: { user: { id: number; role?: MembershipRole } }) => {
const team = await prisma.team.create({
data: {
name: "",
slug: `team-${Date.now()}`,
},
});
if (!team) {
return;
}
const { role = MembershipRole.OWNER, id: userId } = user;
await prisma.membership.create({
data: {
teamId: team.id,
userId,
role: role,
},
});
};
// creates a user fixture instance and stores the collection // creates a user fixture instance and stores the collection
export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => { export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
const store = { users: [], page } as { users: UserFixture[]; page: typeof page }; const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
@@ -42,6 +63,7 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
opts?: CustomUserOpts | null, opts?: CustomUserOpts | null,
scenario: { scenario: {
seedRoutingForms?: boolean; seedRoutingForms?: boolean;
hasTeam?: true;
} = {} } = {}
) => { ) => {
const _user = await prisma.user.create({ const _user = await prisma.user.create({
@@ -193,6 +215,9 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
where: { id: _user.id }, where: { id: _user.id },
include: userIncludes, include: userIncludes,
}); });
if (scenario.hasTeam) {
await createTeamAndAddUser({ user: { id: user.id, role: "OWNER" } });
}
const userFixture = createUserFixture(user, store.page!); const userFixture = createUserFixture(user, store.page!);
store.users.push(userFixture); store.users.push(userFixture);
return userFixture; return userFixture;
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

@@ -426,7 +426,6 @@
"password_hint_num": "يحتوي على رقم واحد على الأقل", "password_hint_num": "يحتوي على رقم واحد على الأقل",
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من 7 أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة", "invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من 7 أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
"incorrect_password": "كلمة المرور غير صحيحة.", "incorrect_password": "كلمة المرور غير صحيحة.",
"1_on_1": "اجتماع 1 مع 1",
"24_h": "24 ساعة", "24_h": "24 ساعة",
"use_setting": "استخدام الإعداد", "use_setting": "استخدام الإعداد",
"am_pm": "صباحًا/مساءً", "am_pm": "صباحًا/مساءً",
@@ -68,7 +68,7 @@
"event_still_awaiting_approval": "Událost stále čeká na schválení", "event_still_awaiting_approval": "Událost stále čeká na schválení",
"booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}", "booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}",
"your_meeting_has_been_booked": "Vaše schůzka byla rezervována", "your_meeting_has_been_booked": "Vaše schůzka byla rezervována",
"event_type_has_been_rescheduled_on_time_date": "Váš {{eventType}} s {{name}} byl přesunut na {{date}}.", "event_type_has_been_rescheduled_on_time_date": "Váš {{title}} byl přeplánován na {{date}}.",
"event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy", "event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy",
"request_reschedule_title_attendee": "Žádost o přeplánování rezervace", "request_reschedule_title_attendee": "Žádost o přeplánování rezervace",
"request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.", "request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.",
@@ -426,7 +426,6 @@
"password_hint_num": "Obsahuje alespoň 1 číslo", "password_hint_num": "Obsahuje alespoň 1 číslo",
"invalid_password_hint": "Heslo musí být minimálně 7 znaků dlouhé, obsahovat alespoň jedno číslo a obsahovat kombinaci velkých a malých písmen", "invalid_password_hint": "Heslo musí být minimálně 7 znaků dlouhé, obsahovat alespoň jedno číslo a obsahovat kombinaci velkých a malých písmen",
"incorrect_password": "Heslo není správné.", "incorrect_password": "Heslo není správné.",
"1_on_1": "Jeden na jednoho",
"24_h": "24h", "24_h": "24h",
"use_setting": "Použít nastavení", "use_setting": "Použít nastavení",
"am_pm": "am/pm", "am_pm": "am/pm",
@@ -9,7 +9,7 @@
"have_any_questions": "Haben Sie Fragen? Wir sind hier um Ihnen zu helfen.", "have_any_questions": "Haben Sie Fragen? Wir sind hier um Ihnen zu helfen.",
"reset_password_subject": "{{appName}}: Anleitung zum Zurücksetzen des Passworts", "reset_password_subject": "{{appName}}: Anleitung zum Zurücksetzen des Passworts",
"event_declined_subject": "Abgelehnt: {{eventType}} mit {{name}} am {{date}}", "event_declined_subject": "Abgelehnt: {{eventType}} mit {{name}} am {{date}}",
"event_cancelled_subject": "Abgesagt: {{eventType}} mit {{name}} am {{date}}", "event_cancelled_subject": "Storniert: {{title}} um {{date}}",
"event_request_declined": "Ihre Event-Anfrage wurde abgelehnt", "event_request_declined": "Ihre Event-Anfrage wurde abgelehnt",
"event_request_declined_recurring": "Ihre wiederkehrende Terminanfrage wurde abgelehnt", "event_request_declined_recurring": "Ihre wiederkehrende Terminanfrage wurde abgelehnt",
"event_request_cancelled": "Ihr geplanter Termin wurde abgesagt", "event_request_cancelled": "Ihr geplanter Termin wurde abgesagt",
@@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung", "event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}", "booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
"your_meeting_has_been_booked": "Dein Termin wurde gebucht", "your_meeting_has_been_booked": "Dein Termin wurde gebucht",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.", "event_type_has_been_rescheduled_on_time_date": "Ihr {{title}} wurde auf den {{date}} verschoben.",
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.", "event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung", "request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.", "request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
@@ -425,7 +425,6 @@
"password_hint_num": "Enthält mindestens 1 Zahl", "password_hint_num": "Enthält mindestens 1 Zahl",
"invalid_password_hint": "Das Passwort muss mindestens 7 Zeichen lang sein und mindestens eine Nummer beinhalten, sowie eine Mischung aus Groß- und Kleinbuchstaben sein", "invalid_password_hint": "Das Passwort muss mindestens 7 Zeichen lang sein und mindestens eine Nummer beinhalten, sowie eine Mischung aus Groß- und Kleinbuchstaben sein",
"incorrect_password": "Passwort ist falsch", "incorrect_password": "Passwort ist falsch",
"1_on_1": "1-on-1",
"24_h": "24 Std", "24_h": "24 Std",
"use_setting": "Benutze Einstellung", "use_setting": "Benutze Einstellung",
"am_pm": "am/pm", "am_pm": "am/pm",
+167 -1
View File
@@ -38,20 +38,37 @@
"confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα", "confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα",
"check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.", "check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.",
"event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας", "event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας",
"ics_event_title": "{{eventType}} με {{name}}",
"join_by_entrypoint": "Συμμετάσχετε από {{entryPoint}}",
"notes": "Σημειώσεις",
"manage_my_bookings": "Διαχείριση κρατήσεων",
"event_type": "Τύπος Συμβάντος", "event_type": "Τύπος Συμβάντος",
"meeting_password": "Συνθηματικό Συνάντησης", "meeting_password": "Συνθηματικό Συνάντησης",
"meeting_url": "URL Συνάντησης", "meeting_url": "URL Συνάντησης",
"meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε", "meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε",
"hi": "Γεια", "hi": "Γεια",
"join_team": "Γίνε μέλος της ομάδας",
"manage_this_team": "Διαχείριση αυτής της ομάδας", "manage_this_team": "Διαχείριση αυτής της ομάδας",
"team_info": "Πληροφορίες Ομάδας", "team_info": "Πληροφορίες Ομάδας",
"request_another_invitation_email": "Αν προτιμάτε να μην χρησιμοποιείτε το {{toEmail}} ως {{appName}} email σας ή να έχετε ήδη λογαριασμό {{appName}} , παρακαλώ ζητήστε μια άλλη πρόσκληση σε αυτό το email.",
"you_have_been_invited": "Έχετε προσκληθεί να γίνετε μέλος της ομάδας {{teamName}}",
"user_invited_you": "Ο χρήστης {{user}} σας προσκάλεσε να συμμετάσχετε στην ομάδα {{team}} στο {{appName}}",
"hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα", "hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα",
"link_expires": "Υ.Γ. Λήγει σε {{expiresIn}} ώρες.",
"team_upgrade_banner_action": "Αναβάθμισε εδώ",
"team_upgraded_successfully": "Η ομάδα σας αναβαθμίστηκε επιτυχώς!",
"use_link_to_reset_password": "Χρησιμοποιήστε τον παρακάτω σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασης",
"hey_there": "Γεια σας,",
"forgot_your_password_calcom": "Ξεχάσατε τον κωδικό πρόσβασης? - {{appName}}",
"confirm_delete_webhook": "Ναι, διαγραφή webhook",
"edit_webhook": "Επεξεργασία Webhook", "edit_webhook": "Επεξεργασία Webhook",
"delete_webhook": "Διαγραφή Webhook", "delete_webhook": "Διαγραφή Webhook",
"webhook_status": "Κατάσταση Webhook",
"webhook_enabled": "Ενεργοποιημένο Webhook", "webhook_enabled": "Ενεργοποιημένο Webhook",
"webhook_disabled": "Απενεργοποιημένο Webhook", "webhook_disabled": "Απενεργοποιημένο Webhook",
"webhook_response": "Απάντηση Webhook", "webhook_response": "Απάντηση Webhook",
"webhook_test": "Έλεγχος Webhook", "webhook_test": "Έλεγχος Webhook",
"manage_your_webhook": "Διαχείριση του webhook σας",
"webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!", "webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!",
"webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!", "webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!",
"webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!", "webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!",
@@ -62,6 +79,14 @@
"past": "Παρελθοντικά", "past": "Παρελθοντικά",
"choose_a_file": "Επιλογή αρχείου...", "choose_a_file": "Επιλογή αρχείου...",
"upload_image": "Μεταφόρτωση εικόνας", "upload_image": "Μεταφόρτωση εικόνας",
"view_notifications": "Εμφάνιση ειδοποιήσεων",
"view_public_page": "Προβολή δημόσιας σελίδας",
"copy_public_page_link": "Αντιγραφή συνδέσμου δημόσιας σελίδας",
"sign_out": "Αποσύνδεση",
"2fa_confirm_current_password": "Επιβεβαιώστε τον κωδικό πρόσβασης σας για να ξεκινήσετε.",
"required": "Υποχρεωτικό",
"optional": "Προαιρετικό",
"guests": "Επισκέπτες",
"username": "Όνομα χρήστη", "username": "Όνομα χρήστη",
"is_still_available": "είναι ακόμα διαθέσιμο.", "is_still_available": "είναι ακόμα διαθέσιμο.",
"documentation": "Τεκμηρίωση", "documentation": "Τεκμηρίωση",
@@ -88,17 +113,37 @@
"dont_have_an_account": "Δεν έχετε λογαριασμό;", "dont_have_an_account": "Δεν έχετε λογαριασμό;",
"sign_in_account": "Συνδεθείτε στο λογαριασμό σας", "sign_in_account": "Συνδεθείτε στο λογαριασμό σας",
"sign_in": "Είσοδος", "sign_in": "Είσοδος",
"error_during_login": "Παρουσιάστηκε σφάλμα κατά τη σύνδεσή σας. Επιστρέψτε στην οθόνη σύνδεσης και προσπαθήστε ξανά.",
"request_password_reset": "Αποστολή email επαναφοράς",
"forgot_password": "Ξεχάσατε τον κωδικό πρόσβασης?",
"all_apps": "Όλα",
"check_email_reset_password": "Ελέγξτε το email σας. Σας στείλαμε ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.",
"few_sentences_about_yourself": "Μερικά λόγια για τον εσάς. Αυτό θα εμφανιστεί στην προσωπική σελίδα σας.",
"set_availability": "Όρισε τη διαθεσιμότητά σου",
"details": "Λεπτομέρειες",
"welcome": "Καλωσορίσατε",
"welcome_back": "Καλωσορίσατε πίσω",
"welcome_to_calcom": "Καλωσορίσατε στο {{appName}}",
"connect_caldav": "Σύνδεση στο CalDav (Beta)",
"credentials_stored_and_encrypted": "Τα διαπιστευτήριά σας θα αποθηκευτούν και θα κρυπτογραφηθούν.",
"connect": "Σύνδεση", "connect": "Σύνδεση",
"try_for_free": "Δοκιμάστε δωρεάν", "try_for_free": "Δοκιμάστε δωρεάν",
"add_to_calendar": "Προσθήκη στο ημερολόγιο", "add_to_calendar": "Προσθήκη στο ημερολόγιο",
"add_another_calendar": "Προσθήκη άλλου ημερολογίου", "add_another_calendar": "Προσθήκη άλλου ημερολογίου",
"email_sign_in_subject": "Ο σύνδεσμος εισόδου για {{appName}}",
"meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί", "meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί",
"submitted": "Η κράτησή σας έχει υποβληθεί", "submitted": "Η κράτησή σας έχει υποβληθεί",
"booking_submitted": "Η κράτησή σας έχει υποβληθεί",
"booking_confirmed": "Η κράτησή σας έχει επιβεβαιωθεί",
"booking_confirmed_recurring": "Η επαναλαμβανόμενη συνάντησή σας έχει επιβεβαιωθεί",
"warning_recurring_event_payment": "Οι πληρωμές δεν υποστηρίζονται ακόμη στις επαναλαμβανόμενες εκδηλώσεις",
"enter_new_password": "Εισάγετε τον νέο κωδικό πρόσβασης που θέλετε για το λογαριασμό σας.",
"reset_password": "Επαναφορά Κωδικού Πρόσβασης", "reset_password": "Επαναφορά Κωδικού Πρόσβασης",
"change_your_password": "Αλλαγή κωδικού πρόσβασης", "change_your_password": "Αλλαγή κωδικού πρόσβασης",
"show_password": "Εμφάνιση κωδικού πρόσβασης", "show_password": "Εμφάνιση κωδικού πρόσβασης",
"hide_password": "Απόκρυψη κωδικού πρόσβασης", "hide_password": "Απόκρυψη κωδικού πρόσβασης",
"try_again": "Δοκιμάστε ξανά", "try_again": "Δοκιμάστε ξανά",
"request_is_expired": "Το αίτημα έχει λήξει.",
"sunday_time_error": "Μη έγκυρη ώρα την Κυριακή", "sunday_time_error": "Μη έγκυρη ώρα την Κυριακή",
"monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα", "monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα",
"tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη", "tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη",
@@ -106,6 +151,127 @@
"thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη", "thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη",
"friday_time_error": "Μη έγκυρη ώρα την Παρασκευή", "friday_time_error": "Μη έγκυρη ώρα την Παρασκευή",
"saturday_time_error": "Μη έγκυρη ώρα το Σάββατο", "saturday_time_error": "Μη έγκυρη ώρα το Σάββατο",
"error_with_status_code_occured": "Παρουσιάστηκε ένα σφάλμα με κωδικό κατάστασης {{status}}.",
"booking_already_cancelled": "Αυτή η κράτηση είχε ήδη ακυρωθεί",
"booking_already_accepted_rejected": "Αυτή η κράτηση έγινε ήδη αποδεκτή ή απορρίφθηκε",
"go_back_home": "Επιστροφή στην αρχική σελίδα",
"or_go_back_home": "Ή επιστροφή στην αρχική σελίδα",
"no_meeting_found": "Δε βρέθηκε συνάντηση", "no_meeting_found": "Δε βρέθηκε συνάντηση",
"bookings": "Κρατήσεις" "no_status_bookings_yet": "Δεν υπάρχουν {{status}} κρατήσεις",
"no_status_bookings_yet_description": "Δεν έχετε {{status}} κρατήσεις. {{description}}",
"bookings": "Κρατήσεις",
"unconfirmed_bookings_tooltip": "Μη επιβεβαιωμένες κρατήσεις",
"start_time": "Ώρα έναρξης",
"end_time": "Ώρα λήξης",
"your_day_starts_at": "Η ημέρα σας ξεκινάει στις",
"your_day_ends_at": "Η ημέρα σας τελειώνει στις",
"change_available_times": "Αλλαγή διαθέσιμων ωρών",
"change_your_available_times": "Αλλάξτε τις διαθέσιμες ώρες σας",
"change_start_end": "Αλλάξτε την ώρα έναρξης και λήξης της ημέρας σας",
"current_start_date": "Αυτή τη στιγμή, η ημέρα σας έχει οριστεί να ξεκινήσει στις",
"email": "Email",
"email_placeholder": "jdoe@example.com",
"loading": "Φόρτωση...",
"deleting": "Διαγραφή...",
"developer": "Προγραμματιστές",
"schedule_a_meeting": "Προγραμματισμός συνάντησης",
"view_and_manage_billing_details": "Προβολή και διαχείριση των στοιχείων χρέωσης",
"view_and_edit_billing_details": "Προβολή και επεξεργασία των στοιχείων χρέωσης σας, καθώς και ακύρωση της συνδρομής σας.",
"need_anything_else": "Χρειάζεστε οτιδήποτε άλλο;",
"further_billing_help": "Αν χρειάζεστε περαιτέρω βοήθεια με την τιμολόγηση, η ομάδα υποστήριξής μας είναι εδώ για να βοηθήσει.",
"contact": "Επικοινωνία",
"contact_our_support_team": "Επικοινωνήστε με την ομάδα υποστήριξης",
"no_event_types_have_been_setup": "Αυτός ο χρήστης δεν έχει ορίσει ακόμα τύπους γεγονότων.",
"edit_logo": "Επεξεργασία λογοτύπου",
"upload_a_logo": "Ανεβάστε ένα λογότυπο",
"remove_logo": "Αφαίρεση λογότυπου",
"enable": "Ενεργοποίηση",
"2fa": "Ταυτοποίηση Δύο Παραμέτρων",
"security": "Ασφάλεια",
"manage_account_security": "Διαχειριστείτε την ασφάλεια του λογαριασμού σας.",
"password": "Κωδικός",
"password_updated_successfully": "Ο κωδικός πρόσβασης ενημερώθηκε επιτυχώς",
"password_has_been_changed": "Ο κωδικός πρόσβασής σας έχει αλλάξει με επιτυχία.",
"error_changing_password": "Σφάλμα στην αλλαγή κωδικού πρόσβασης",
"something_went_wrong": "Κάτι πήγε στραβά.",
"something_doesnt_look_right": "Κάτι δεν φαίνεται σωστά;",
"please_try_again": "Παρακαλώ δοκιμάστε ξανά.",
"super_secure_new_password": "Ο σούπερ ασφαλής νέος κωδικός πρόσβασης σας",
"new_password": "Νέος Κωδικός",
"your_old_password": "Ο παλιός σας κωδικός",
"current_password": "Τρέχων Κωδικός",
"change_password": "Αλλαγή Κωδικού",
"change_secret": "Αλλαγή Μυστικού",
"new_password_matches_old_password": "Ο νέος κωδικός πρόσβασης ταιριάζει με τον παλιό σας κωδικό. Παρακαλώ επιλέξτε διαφορετικό κωδικό πρόσβασης.",
"your_name": "Το όνομά σας",
"email_address": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
"enter_valid_email": "Παρακαλώ εισάγετε ένα έγκυρο email",
"location": "Τοποθεσία",
"address": "Διεύθυνση",
"enter_address": "Εισάγετε διεύθυνση",
"yes": "ναι",
"no": "όχι",
"additional_notes": "Πρόσθετες σημειώσεις",
"your_number": "Ο αριθμός τηλεφώνου σας",
"phone_number": "Αριθμός Τηλεφώνου",
"attendee_phone_number": "Αριθμός Τηλεφώνου Συμμετέχοντα",
"host_phone_number": "Ο αριθμός τηλεφώνου σας",
"enter_phone_number": "Εισάγετε αριθμό τηλεφώνου",
"or": "Ή",
"go_back": "Επιστροφή",
"email_or_username": "Email ή Όνομα χρήστη",
"role": "Ρόλος",
"edit_role": "Επεξεργασία Ρόλου",
"edit_team": "Επεξεργασία ομάδας",
"reject": "Απορρίψτε",
"reject_all": "Απόρριψη όλων",
"accept": "Αποδοχή",
"leave": "Αποχώρηση",
"team_name": "Όνομα ομάδας",
"your_team_name": "Το όνομα της ομάδας σας",
"team_updated_successfully": "Επιτυχής ενημέρωση ομάδας",
"your_team_updated_successfully": "Η ομάδα σας ενημερώθηκε με επιτυχία.",
"about": "Σχετικά",
"confirm_remove_member": "Ναι, αφαίρεση μέλους",
"remove_member": "Αφαίρεση μέλους",
"manage_your_team": "Διαχείριση ομάδας",
"no_teams": "Δεν έχετε καμία ομάδα ακόμη.",
"submit": "Υποβολή",
"delete": "Διαγραφή",
"update": "Ενημέρωση",
"save": "Αποθήκευση",
"pending": "Εκκρεμεί",
"share": "Κοινοποίηση",
"leave_team": "Αποχώρηση από ομάδα",
"confirm_leave_team": "Ναι, αποχώρηση από την ομάδα",
"preview": "Προεπισκόπηση",
"link_copied": "Ο σύνδεσμος αντιγράφηκε!",
"private_link_copied": "Ο ιδιωτικός σύνδεσμος αντιγράφηκε!",
"title": "Τίτλος",
"description": "Περιγραφή",
"preview_team": "Προεπισκόπηση ομάδας",
"duration": "Διάρκεια",
"minutes": "Λεπτά",
"one_time_link": "Σύνδεσμος μιας χρήσης",
"first_day_of_week": "Πρώτη ημέρα της εβδομάδας",
"weekly_one": "εβδομάδα",
"weekly_other": "εβδομάδες",
"monthly_one": "μήνας",
"monthly_other": "μήνες",
"yearly_one": "έτος",
"yearly_other": "έτη",
"max": "Μέγιστο",
"create_team": "Δημιουργία ομάδας",
"name": "Όνομα",
"create_new_team": "Δημιουργία νέας ομάδας",
"new_team": "Νέα Ομάδα",
"teams": "Ομάδες",
"team": "Ομάδα",
"enabled": "Ενεργοποιημένο",
"disabled": "Απενεργοποιημένο",
"show_advanced_settings": "Εμφάνιση προχωρημένων ρυθμίσεων",
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
"label": "Ετικέτα",
"edit": "Επεξεργασία",
"disable_guests": "Απενεργοποίηση επισκεπτών"
} }
+21 -4
View File
@@ -23,6 +23,8 @@
"rejection_reason_description": "Are you sure you want to reject the booking? We'll let the person who tried to book know. You can provide a reason below.", "rejection_reason_description": "Are you sure you want to reject the booking? We'll let the person who tried to book know. You can provide a reason below.",
"rejection_confirmation": "Reject the booking", "rejection_confirmation": "Reject the booking",
"manage_this_event": "Manage this event", "manage_this_event": "Manage this event",
"invite_team_member": "Invite team member",
"invite_team_notifcation_badge":"Inv.",
"your_event_has_been_scheduled": "Your event has been scheduled", "your_event_has_been_scheduled": "Your event has been scheduled",
"your_event_has_been_scheduled_recurring": "Your recurring event has been scheduled", "your_event_has_been_scheduled_recurring": "Your recurring event has been scheduled",
"accept_our_license": "Accept our license by changing the .env variable <1>NEXT_PUBLIC_LICENSE_CONSENT</1> to '{{agree}}'.", "accept_our_license": "Accept our license by changing the .env variable <1>NEXT_PUBLIC_LICENSE_CONSENT</1> to '{{agree}}'.",
@@ -64,7 +66,7 @@
"someone_requested_an_event": "Someone has requested to schedule an event on your calendar.", "someone_requested_an_event": "Someone has requested to schedule an event on your calendar.",
"someone_requested_password_reset": "Someone has requested a link to change your password.", "someone_requested_password_reset": "Someone has requested a link to change your password.",
"password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.", "password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.",
"event_awaiting_approval_subject": "Awaiting Approval: {{eventType}} at {{date}}", "event_awaiting_approval_subject": "Awaiting Approval: {{title}} at {{date}}",
"event_still_awaiting_approval": "An event is still waiting for your approval", "event_still_awaiting_approval": "An event is still waiting for your approval",
"booking_submitted_subject": "Booking Submitted: {{title}} at {{date}}", "booking_submitted_subject": "Booking Submitted: {{title}} at {{date}}",
"your_meeting_has_been_booked": "Your meeting has been booked", "your_meeting_has_been_booked": "Your meeting has been booked",
@@ -429,7 +431,6 @@
"password_hint_num": "Contain at least 1 number", "password_hint_num": "Contain at least 1 number",
"invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters", "invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
"incorrect_password": "Password is incorrect.", "incorrect_password": "Password is incorrect.",
"1_on_1": "1-on-1",
"24_h": "24h", "24_h": "24h",
"use_setting": "Use setting", "use_setting": "Use setting",
"am_pm": "am/pm", "am_pm": "am/pm",
@@ -660,6 +661,7 @@
"edit_availability": "Edit availability", "edit_availability": "Edit availability",
"configure_availability": "Configure times when you are available for bookings.", "configure_availability": "Configure times when you are available for bookings.",
"copy_times_to": "Copy times to", "copy_times_to": "Copy times to",
"copy_times_to_tooltip": "Copy times to …",
"change_weekly_schedule": "Change your weekly schedule", "change_weekly_schedule": "Change your weekly schedule",
"logo": "Logo", "logo": "Logo",
"error": "Error", "error": "Error",
@@ -789,6 +791,7 @@
"number_apps_one": "{{count}} App", "number_apps_one": "{{count}} App",
"number_apps_other": "{{count}} Apps", "number_apps_other": "{{count}} Apps",
"trending_apps": "Trending Apps", "trending_apps": "Trending Apps",
"most_popular":"Most Popular",
"explore_apps": "{{category}} apps", "explore_apps": "{{category}} apps",
"installed_apps": "Installed Apps", "installed_apps": "Installed Apps",
"free_to_use_apps": "Free", "free_to_use_apps": "Free",
@@ -799,12 +802,14 @@
"no_category_apps_description_analytics": "Add an analytics app for your booking pages", "no_category_apps_description_analytics": "Add an analytics app for your booking pages",
"no_category_apps_description_automation": "Add an automation app to use", "no_category_apps_description_automation": "Add an automation app to use",
"no_category_apps_description_other": "Add any other type of app to do all sorts of things", "no_category_apps_description_other": "Add any other type of app to do all sorts of things",
"no_category_apps_description_web3": "Add a web3 app for your booking pages",
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.", "installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
"installed_app_conferencing_description": "Add your favourite video conferencing apps for your meetings", "installed_app_conferencing_description": "Add your favourite video conferencing apps for your meetings",
"installed_app_payment_description": "Configure which payment processing services to use when charging your clients.", "installed_app_payment_description": "Configure which payment processing services to use when charging your clients.",
"installed_app_analytics_description": "Configure which analytics apps to use for your booking pages", "installed_app_analytics_description": "Configure which analytics apps to use for your booking pages",
"installed_app_other_description": "All your installed apps from other categories.", "installed_app_other_description": "All your installed apps from other categories.",
"installed_app_automation_description": "Configure which automation apps to use", "installed_app_automation_description": "Configure which automation apps to use",
"installed_app_web3_description": "Configure which web3 apps to use for your booking pages",
"analytics": "Analytics", "analytics": "Analytics",
"empty_installed_apps_headline": "No apps installed", "empty_installed_apps_headline": "No apps installed",
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.", "empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
@@ -1217,6 +1222,7 @@
"connect_automation_apps": "Connect automation apps", "connect_automation_apps": "Connect automation apps",
"connect_analytics_apps": "Connect analytics apps", "connect_analytics_apps": "Connect analytics apps",
"connect_other_apps": "Connect other apps", "connect_other_apps": "Connect other apps",
"connect_web3_apps": "Connect web3 apps",
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}", "current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
"add_variable": "Add variable", "add_variable": "Add variable",
"custom_phone_number": "Custom phone number", "custom_phone_number": "Custom phone number",
@@ -1233,6 +1239,7 @@
"to": "To", "to": "To",
"workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully", "workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully",
"download_responses": "Download Responses", "download_responses": "Download Responses",
"download_responses_description": "Download all responses to your form in CSV format.",
"download": "Download", "download": "Download",
"create_your_first_form": "Create your first form", "create_your_first_form": "Create your first form",
"create_your_first_form_description": "With Routing Forms you can ask qualifying questions and route to the correct person or event type.", "create_your_first_form_description": "With Routing Forms you can ask qualifying questions and route to the correct person or event type.",
@@ -1275,6 +1282,8 @@
"routing_forms_send_email_owner": "Send Email to Owner", "routing_forms_send_email_owner": "Send Email to Owner",
"routing_forms_send_email_owner_description": "Sends an email to the owner when the form is submitted", "routing_forms_send_email_owner_description": "Sends an email to the owner when the form is submitted",
"add_new_form": "Add new form", "add_new_form": "Add new form",
"create_your_first_route": "Create your first route",
"route_to_the_right_person": "Route to the right person based on the answers to your form",
"form_description": "Create your form to route a booker", "form_description": "Create your form to route a booker",
"copy_link_to_form": "Copy link to form", "copy_link_to_form": "Copy link to form",
"theme": "Theme", "theme": "Theme",
@@ -1305,6 +1314,7 @@
"password_reset_leading": "If you don't receive an email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.", "password_reset_leading": "If you don't receive an email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.",
"password_updated": "Password updated!", "password_updated": "Password updated!",
"pending_payment": "Pending payment", "pending_payment": "Pending payment",
"pending_invites":"Pending Invites",
"confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more.", "confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more.",
"not_on_cal": "Not on {{appName}}", "not_on_cal": "Not on {{appName}}",
"no_calendar_installed": "No calendar installed", "no_calendar_installed": "No calendar installed",
@@ -1460,6 +1470,7 @@
"individual":"Individual", "individual":"Individual",
"all_bookings_filter_label":"All Bookings", "all_bookings_filter_label":"All Bookings",
"all_users_filter_label":"All Users", "all_users_filter_label":"All Users",
"your_bookings_filter_label":"Your Bookings",
"meeting_url_variable": "Meeting url", "meeting_url_variable": "Meeting url",
"meeting_url_info": "The event meeting conference url", "meeting_url_info": "The event meeting conference url",
"date_overrides": "Date overrides", "date_overrides": "Date overrides",
@@ -1498,7 +1509,7 @@
"round_robin_hosts": "Round-Robin Hosts", "round_robin_hosts": "Round-Robin Hosts",
"minimum_round_robin_hosts_count": "Number of hosts required to attend", "minimum_round_robin_hosts_count": "Number of hosts required to attend",
"hosts": "Hosts", "hosts": "Hosts",
"upgrade_to_enable_feature": "You need to upgrade to enable this feature. Click to upgrade.", "upgrade_to_enable_feature": "You need to create a team to enable this feature. Click to create a team.",
"new_attendee": "New Attendee", "new_attendee": "New Attendee",
"awaiting_approval": "Awaiting Approval", "awaiting_approval": "Awaiting Approval",
"requires_google_calendar": "This app requires a Google Calendar connection", "requires_google_calendar": "This app requires a Google Calendar connection",
@@ -1506,5 +1517,11 @@
"using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar", "using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
"continue_to_install_google_calendar": "Continue to install Google Calendar", "continue_to_install_google_calendar": "Continue to install Google Calendar",
"install_google_meet": "Install Google Meet", "install_google_meet": "Install Google Meet",
"install_google_calendar": "Install Google Calendar" "install_google_calendar": "Install Google Calendar",
"sender_name": "Sender name",
"no_recordings_found": "No recordings found",
"reporting": "Reporting",
"reporting_feature": "See all incoming from data and download it as a CSV",
"teams_plan_required": "Teams plan required",
"routing_forms_are_a_great_way": "Routing forms are a great way to route your incoming leads to the right person. Upgrade to a Teams plan to access this feature."
} }
@@ -68,7 +68,7 @@
"event_still_awaiting_approval": "Un evento aún está esperando su aprobación", "event_still_awaiting_approval": "Un evento aún está esperando su aprobación",
"booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}", "booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}",
"your_meeting_has_been_booked": "Su reunión ha sido reservada", "your_meeting_has_been_booked": "Su reunión ha sido reservada",
"event_type_has_been_rescheduled_on_time_date": "Tu {{eventType}} con {{name}} se reprogramó para el {{date}}.", "event_type_has_been_rescheduled_on_time_date": "Tu {{title}} ha sido reprogramado para {{date}}.",
"event_has_been_rescheduled": "Tu evento ha sido reprogramado.", "event_has_been_rescheduled": "Tu evento ha sido reprogramado.",
"request_reschedule_title_attendee": "Solicitud para reprogramar su reserva", "request_reschedule_title_attendee": "Solicitud para reprogramar su reserva",
"request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.", "request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.",
@@ -426,7 +426,6 @@
"password_hint_num": "Debe contener al menos 1 número", "password_hint_num": "Debe contener al menos 1 número",
"invalid_password_hint": "La contraseña debe tener un mínimo de 7 caracteres que contengan al menos un número y una mezcla de letras mayúsculas y minúsculas", "invalid_password_hint": "La contraseña debe tener un mínimo de 7 caracteres que contengan al menos un número y una mezcla de letras mayúsculas y minúsculas",
"incorrect_password": "La Contraseña es Incorrecta.", "incorrect_password": "La Contraseña es Incorrecta.",
"1_on_1": "1 a 1",
"24_h": "24hs", "24_h": "24hs",
"use_setting": "Usar Ajuste", "use_setting": "Usar Ajuste",
"am_pm": "am/pm", "am_pm": "am/pm",
@@ -0,0 +1,79 @@
{
"day_one": "egun {{count}}",
"day_other": "{{count}} egun",
"second_one": "segundo {{count}}",
"second_other": "{{count}} segundo",
"accept_invitation": "Onartu gonbidapena",
"have_any_questions": "Galderarik? Laguntzeko gaude.",
"reset_password_subject": "{{appName}}: Pasahitza berrezartzeko argibideak",
"event_declined_subject": "Baztertua: {{title}} {{date}}(e)an",
"event_cancelled_subject": "Bertan behera: {{title}} {{date}}(e)an",
"event_request_declined": "Zure gertaera-eskaera baztertua izan da",
"event_request_declined_recurring": "Zure gertaera errepikari-eskaera baztertua izan da",
"event_request_cancelled": "Zure programatutako gertaera baztertua izan da",
"organizer": "Antolatzailea",
"need_to_reschedule_or_cancel": "Programazioa aldatu edo bertan behera utzi behar duzu?",
"cancellation_reason": "Bertan behera uztearen arrazoia (aukerakoa)",
"cancellation_reason_placeholder": "Zergatik utzi duzu bertan behera?",
"rejection_reason": "Errefusatzeko arrazoia",
"rejection_reason_title": "Errefusatu erreserba-eskaera?",
"rejection_reason_description": "Ziur zaude erreserba errefusatu nahi duzula? Erreserba-eskaera egin duen pertsonari jakinaraziko zaio. Arrazoi bat adieraz dezakezu behean.",
"rejection_confirmation": "Errefusatu erreserba",
"manage_this_event": "Kudeatu gertaera hau",
"your_event_has_been_scheduled": "Zure gertaera programatu da",
"error_message": "Errore-mezua honakoa ian da: '{{errorMessage}}'",
"refund_failed_subject": "Itzulketak huts egin du: {{name}} - {{date}} - {{eventType}}",
"refund_failed": "Huts egin du itzulketak {{eventType}} gertaerarako, {{userName}}(r)ekin {{date}}(e)an.",
"a_refund_failed": "Itzulketa batek huts egin du",
"awaiting_payment_subject": "Ordainketaren zain: {{title}} {{date}}(e)an",
"help": "Laguntza",
"price": "Prezioa",
"paid": "Ordainduta",
"refunded": "Itzulita",
"payment": "Ordainketa",
"pay_now": "Ordaindu orain",
"no_more_results": "Emaitza gehiagorik ez",
"no_results": "Emaitzarik ez",
"load_more_results": "Kargatu emaitza gehiago",
"confirmed_event_type_subject": "Baieztatua: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
"confirm_or_reject_request": "Baieztatu edo errefusatu eskaera",
"check_bookings_page_to_confirm_or_reject": "Begiratu zure erreserba-orrialdea erreserba baieztatu edo errefusatzeko.",
"event_awaiting_approval": "Gertaera bat zure onarpenaren zain dago",
"booking_submitted_subject": "Erreserba bidalita: {{title}} {{date}}(e)an",
"event_type_has_been_rescheduled_on_time_date": "Zure {{title}} getaeraren programazioa aldatu egin da {{date}}(e)ra.",
"hi_user_name": "Kaixo {{name}}",
"ics_event_title": "{{eventType}} {{name}}(r)ekin",
"notes": "Oharrak",
"manage_my_bookings": "Kudeatu nire erreserbak",
"rejected_event_type_with_organizer": "Errefusatua: {{eventType}} {{organizer}}(r)ekin {{date}}(e)an",
"hi": "Kaixo",
"change_bookings_availability": "Aldatu noiz zauden prest erreserbak jasotzeko",
"create_booking_link_with_calcom": "Sor ezazu zeure erreserba-esteka {{appName}}(e)kin",
"user_needs_to_confirm_or_reject_booking": "{{user}}(e)k erreserba baieztatu edo errefusatu behar du oraindik.",
"booking_submitted": "Zure erreserba bidali da",
"booking_confirmed": "Zure erreserba baieztatu da",
"back_to_bookings": "Itzuli erreserbatara",
"really_cancel_booking": "Benetan bertan behera utzi nahi duzu zure erreserba?",
"cannot_cancel_booking": "Ezin duzu erreserba hau bertan behera utzi",
"booking_already_cancelled": "Erreserba hau bertan behera utzi da dagoeneko",
"booking_already_accepted_rejected": "Erreserba hau onartu edo errefusatu da dagoeneko",
"bookings": "Erreserbak",
"past_bookings": "Zure iraganeko erreserbak agertuko dira hemen.",
"cancelled_bookings": "Zure bertan behera utzitako erreserbak agertuko dira hemen.",
"unconfirmed_bookings": "Zure baieztatu gabeko erreserbak agertuko dira hemen.",
"unconfirmed_bookings_tooltip": "Baieztatu gabeko erreserbak",
"booking_cancelled": "Erreserba bertan behera",
"booking_rescheduled": "Erreserbaren programazioa aldatuta",
"booking_created": "Erreserba sortuta",
"edit_booking": "Aldatu erreserba",
"reschedule_booking": "Aldatu erreserbaren programazioa",
"location_changed_event_type_subject": "Kokapena aldatu da: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
"booking_details": "Erreserbaren xehetasunak",
"reminder_email": "Gogorarazpena: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
"new_seat_subject": "{{name}} parte-hartzaile berria {{eventType}}(e)n {{date}}(e)an",
"booking_appearance": "Erreserba-orriaren itxura",
"limit_booking_frequency": "Mugatu erreserbatzeko maiztasuna",
"booking_tentative": "Erreserba hau behin-behinekoa da",
"limit_future_bookings": "Mugatu etorkizuneko erreserbak",
"all_bookings_filter_label": "Erreserba guztiak"
}
Loaded 100 of 460 files, more files were not shown because too many files have changed in this diff. Show more