Compare commits

..
321 Commits
Author SHA1 Message Date
zomars 4e73c4f7fb v1.9.1 2022-08-23 09:23:08 -07:00
f4ea385c7f Fixes collective availability for teams with overlapping day timezones (#3898)
* WIP

* Fix for team availability with time offsets

* Prevent empty schedule from opening up everything

* When no utcOffset or timeZone's are given, default to 0 utcOffset (UTC)

* timeZone should not be part of getUserAvailability

* Prevents {days:[X],startTime:0,endTime:0} error entry

* Added getAggregateWorkingHours() (#3913)

* Added test for getAggregateWorkingHours

* Timezone isn't used here anymore

* fix: developer docs url (#3914)

* fix: developer docs url added

* chore : remove /

* chore : import url

Co-authored-by: Zach Waterfield <zlwaterfield@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Test fixes

* Reinstate prisma (generate only) and few comments

* Test fixes

* Skipping getSchedule again

* Added await to expect() as it involves async logic causing the promise to timeout

* Test cleanup

* Update jest.config.ts

Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Zach Waterfield <zlwaterfield@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-22 23:53:51 +00:00
cb1d881cc0 fix: developer docs url (#3914)
* fix: developer docs url added

* chore : remove /

* chore : import url

Co-authored-by: Zach Waterfield <zlwaterfield@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-22 22:56:55 +02:00
+1 27c5bf9dbc Design issues on success page in some languages (#3900)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-22 22:40:35 +02:00
github-actions[bot]andCrowdin Bot ef7dd5102b New Crowdin translations by Github Action (#3929)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-22 22:36:49 +02:00
github-actions[bot]andCrowdin Bot 0c3e309596 New Crowdin translations by Github Action (#3927)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-22 22:28:56 +02:00
Peer RichelsenandAlex van Andel 1b942c4003 redesigned cancel page to new design (#3923)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-08-22 19:48:33 +00:00
b328527434 fix/auto-connect-calendar-3582 (#3891)
* Added alert when there isn't default calendar connected

* Add default calendar externalId to select placeholder

* Fix typos

* Fixes prisma import

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-22 19:34:28 +00:00
Omar López 662b437a18 Merge branch 'production' into main 2022-08-22 10:32:25 -07:00
github-actions[bot]andCrowdin Bot e7f1413ed8 New Crowdin translations by Github Action (#3926)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-22 17:09:42 +02:00
github-actions[bot]andCrowdin Bot 071b20b6ec New Crowdin translations by Github Action (#3924)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-22 16:09:29 +02:00
github-actions[bot]andCrowdin Bot bf6dd6cb32 New Crowdin translations by Github Action (#3909)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-22 12:59:20 +02:00
Peer Richelsen c44e444271 Revert " cancel booking new design added" (#3922)
This reverts commit a06c564517.
2022-08-22 12:40:41 +02:00
Udit Takkar a06c564517 cancel booking new design added (#3660)
* feat:  cancel booking new design added

* style: add space-y in button

* style: ui fixed for recurring booking

* fix: scroll in recurring event and use translation

* style: text 2xl instead of text-[24px]

* fix: typo, spacing

* fix: replaced multiple h2 tags with div

* fix: padding and Icon name
2022-08-22 12:32:06 +02:00
Julian Benegas 02b5a2bc1f Revert "adjust react version"
This reverts commit 6e161da35b.
2022-08-19 13:23:34 -03:00
Julian Benegas 6e161da35b adjust react version 2022-08-19 13:22:07 -03:00
github-actions[bot]andCrowdin Bot b257cdbf1e New Crowdin translations by Github Action (#3897)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-18 20:18:03 +02:00
zomars 9de723d557 Sync submodules 2022-08-18 11:54:58 -06:00
zomars 081d5ae9c8 NEXTAUTH_DOMAIN isn't used 2022-08-18 11:53:29 -06:00
cddca2b10d New Crowdin translations by Github Action (#3890)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-18 18:10:03 +02:00
Peer Richelsen 6ed0e5e281 use default cursor on desktop (#3876) 2022-08-17 22:50:25 +02:00
github-actions[bot]andCrowdin Bot 1fd8d50e52 New Crowdin translations by Github Action (#3878)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-17 22:50:01 +02:00
Peer Richelsen 5206a37fe4 temporally removed missing calendar badge on event-types (#3889) 2022-08-17 13:51:45 -06:00
Peer Richelsen 025019f789 Update [uid].tsx (#3888) 2022-08-17 19:32:46 +01:00
zomars d58bd02cab Submodule sync 2022-08-17 11:41:08 -06:00
b8acf1e180 Update all Yarn dependencies (2022-07-29) (#3599)
* Update all Yarn dependencies (2022-07-29)

* Adds syncpack config

* Fixes mismatches

* Update yarn.lock

* RRule fixes

* Locking dayjs to fix build

* Type fixes

* Fixes mismatches

* Submodule sync

* Update yarn.lock

* Update event.ts

* Conflict fixes

* Fixes prisma warnings

* Liting

* Upgrade next, zod

* Prevents articfact overwriting

* Yarn fixes

* Jest fixes

* Submodule sync

* Formatting

* Submodule sync

* Adds provider for react-tooltip

* Removed dotenv-cli

* Readds dotenv-cli

* Skips getSchedule tests

Until prisma is mocked properly

* Fixes

* Revert prisma seed script

* E2E fixes

* test

* Removed deprecated req.page in middleware

* Make tests stable

* Unskip getSchedule tests

* fixed 404 logo on cal video (#3885)

* Removed PW aliases as aren't needed anymore

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-17 11:38:21 -06:00
Peer Richelsen d8fab68edd fixed 404 logo on cal video (#3885) 2022-08-17 16:59:00 +02:00
Hariom Balhara 592d46f758 Fix desktop app link (#3883) 2022-08-17 11:07:19 +02:00
Alex van Andelandzomars a331d02668 Hotfix/dos mitigation attempt error configuration (#3879)
* Fixes 'Task timed out after 60.03 seconds'

* DDoS rewrite fixes

Co-authored-by: zomars <zomars@me.com>
2022-08-16 17:43:01 -06:00
Syed Ali Shahbaz 73e38384e2 hotfix dynamic issue (#3864) 2022-08-16 17:43:01 -06:00
Alex van Andelandzomars f5b5150cf3 Hotfix/dos mitigation attempt error configuration (#3879)
* Fixes 'Task timed out after 60.03 seconds'

* DDoS rewrite fixes

Co-authored-by: zomars <zomars@me.com>
2022-08-17 00:38:26 +01:00
zomars 7e0dbee2ca DDoS rewrite fixes 2022-08-16 16:28:19 -06:00
Syed Ali Shahbaz 46715a6cd4 hotfix dynamic issue (#3864) 2022-08-16 16:07:44 -06:00
Alex van Andelandzomars 1e5cb0bbd0 Attempt at redirect to 404 (#3877)
* Attempt at redirect to 404

404 should be able to handle the traffic no problem - better ideas welcome. Just a temp fix I imagine; we're not going to want to keep this nor can we expect the User-Agent to continue identifying the traffic source.

* Update next.config.js

Co-authored-by: zomars <zomars@me.com>
2022-08-16 16:02:04 -06:00
Alex van Andelandzomars 3a54ca2c95 Attempt at redirect to 404 (#3877)
* Attempt at redirect to 404

404 should be able to handle the traffic no problem - better ideas welcome. Just a temp fix I imagine; we're not going to want to keep this nor can we expect the User-Agent to continue identifying the traffic source.

* Update next.config.js

Co-authored-by: zomars <zomars@me.com>
2022-08-16 16:01:38 -06:00
zomars 79d37676fc Middleware rewrite shenanigans 2022-08-16 13:59:38 -06:00
zomars 4f76654813 Whitelist only cal domain as callbackUrls 2022-08-16 13:55:50 -06:00
zomars 6dcb0c43ab DDOS mitigation updates 2022-08-16 13:50:09 -06:00
Hariom Balhara cf9116b5bb Avoid DDOS (#3871) 2022-08-16 19:15:13 +02:00
github-actions[bot]andCrowdin Bot 4df54ef3d1 New Crowdin translations by Github Action (#3857)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-16 13:34:04 +02:00
Jake CooperandPeer Richelsen 82c98a8b7b Update Railway Template (#3862)
* Update Railway Template

* Update URL

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-16 11:30:56 +00:00
Peer Richelsen 0d7be1886e fixed iframe in lark suite, added lark contact info (#3866)
* fixed iframe in lark suite, added lark contact info

* Update _metadata.ts
2022-08-16 13:28:52 +02:00
Hariom Balharaandkodiakhq[bot] bc6c9e1b0a Hotfix: Embed - Fix issue in accessing sessionStorage in certain scenarios (#3851)
* Fixes issue when sessionStorage is not accessible in privacy focussed modes in various browsers

* Fix eslint errors

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-16 04:18:13 +00:00
mohammed hussamandhussamkhatib 732acb5b25 fix: remove redundant AND (#3833)
Co-authored-by: hussamkhatib <hussamkhatib@gmail.com>
2022-08-15 16:35:44 -06:00
Carina WollendorferandCarinaWolli 5b6818a3cc fixes issue with variables not working (#3859)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-08-15 16:32:37 -06:00
Carina WollendorferandCarinaWolli 497d4902af fixes issue with variables not working (#3859)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-08-15 16:32:11 -06:00
Udit Takkar 8c7579fede fix: padding of kBarTrigger (#3846) 2022-08-15 16:31:27 -06:00
4ce6b91a79 Close.com App (#3709)
* WIP close.com app

* Removing leaked dev key (now invalid)

* Misspelled env variable

* Making progress still WIP

* Progress + tests

* Final touches

* More unit tests

* Finished up tests

* Merge main

* Removing unneeded stuff + submodules

* Removing static props, fields fix

* Removing unneeded stuff p2

* Commenting

* Standarizing APIs

* Zodifying

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-15 20:45:15 +00:00
zomars 8d0caa522e v1.9.0 2022-08-15 14:24:26 -06:00
89510e6af6 Zapier Trigger: After Meeting ends (#3827)
* add new trigger with first simple job scheduling

* fix DB update

* use array to save scheduled jobs in booking

* cancel scheduled jobs when zap disabled or zapier disconnected

* schedule jobs only for confirmed bookings

* schedule jobs for already existing bookings

* fix bug to create workflow reminders when confirming recurring events

* delete remove all zapier webhooks when api key is deleted

* schedule job for all confirmed recurring bookings

* fix zapier trigger and workflow reminders when cancelling recurring events

* code clean up

* code clean up

* add migration

* add type package for node-schedule

* remove nodescheduler

* add updated nodescheduler

* move code to app-store

* add meeting ended to webhook constants

* udpate zapier README.md

* implement QA suggestions

* add default handler and fix imports

* Type fix

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-15 20:18:41 +00:00
Alex van Andel 297a965508 Bugfix/i3531 round robin assigns to unavailable members (#3813)
* wip commit

* Finished new algorithm for fetching the least recently booked user

* ROUND_ROBIN fix

* Removed redundant import

* Prisma dependency turned getLuckyUser into a server-only function

* DRY avatars

* Properly passThrough

* name can be undefined.

* Remove debug artefact
2022-08-15 13:52:01 -06:00
Udit Takkarandkodiakhq[bot] c644a10688 fix: css for location (#3744)
* fix: css for location

* fix: recurring icon positioning

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-15 13:35:48 -06:00
Jatin Ranka 8ff762de3e Feat: make locations dropdown searchable (#3858) 2022-08-15 13:18:34 -06:00
Alex van Andel fb2ed0b5ad Bring back Dropdown used to copy (#3843) 2022-08-15 18:03:24 +00:00
Alex van Andel 358d733b5f Allow scrolling when the dialog exceeds screen height (#3850) 2022-08-15 16:22:58 +00:00
Peer Richelsen 22a4a1b2ab added lark screenshots and youtube video (#3839) 2022-08-13 22:03:51 +02:00
Peer Richelsen bcd80eab34 fixed back button on desktop (#3838) 2022-08-13 21:42:32 +02:00
Hariom Balharaandkodiakhq[bot] c3fbf8224b Feature: Routing Forms Typeform App and other improvements (#3625)
* Add Embed ModalBox for routing forms

* Add duplicate form support

* Fix duplication logic

* Change to feathericons everywhere and other fixes

* Dont allow routes for fallback route

* Fix all TS issues

* Fix tests

* Support routing using query params

* Support multiselect in router endpoint

* Fix the issue where app goes in embed mode after viewing embed once

* Add router url tests

* Add Responses download and form toggling tests

* Add required validation test

* Change Icons everywhere

* App typeform app

* Improvements in cli

* Add typeform how-to-use page

* Add typeform how-to-use page and screenshots

* Fix TS error

* Add missing image

* Update CliApp.tsx

* Revert unexpected zapier change

* Revert yarn.lock, not sure why it was modified

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-13 11:04:57 +00:00
Will GittoesandSyed Ali Shahbaz 4cb5a56d41 Fix early return when expanding RRULE in CalDav/iCal (#3665)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2022-08-12 15:28:45 -06:00
5e013d5864 Fixes issues with video location links from apps (#3760)
* fix issue with video apps link not saved as location

* use link as location for ping.gg

* fix around link in availability page + allow breaking line for locations

* use find instead of filter to get the first item

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

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-12 14:46:40 -06:00
zomars d90a431dcc Adds actions to cleanup preview DBs 2022-08-12 14:40:48 -06:00
ee02112a7c Dynamic Links re-integrate with availability logic (#3687)
* -re init dynamic links

* typing fix 001

* added missing div closing tag

* added necessary DB column pull to satisfy Type errors

* further type fixes

* WIP

* removed console logs

* some revert

* WIP

* another approach

* enabled dynamic links availability fetch

* Added users to eventTypeObject for consistency

* WIP: Moving user and changing map item name

* Fix user list call

* Removed explicit User type in map

* modify default user attributes

* adds explicit users to EventTypeObject in teams

* Updated availability page

* Updates Availability

* Futher availability change

* Remove explicit user type from slot router

* more fixes

* more fixes WIP

* cleaning up more errors WIP

* object assign used for typesafety

* added check if dynamic booking is allowed by all users

* cleaned up console logs

* clean up

* Improvement

* resolving suggestions by alex

* changes requested by Omar

* Filter out empty usernames instead of accepting null

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-12 19:29:29 +00:00
Alex van AndelandOmar López 870f70fc38 Imports performance explicitly for Node v14 (#3823)
* Imports performance explicitly for Node v14

* Update packages/trpc/server/routers/viewer/slots.tsx

* Adds missing performance imports

* Update turbo.json

Co-authored-by: Omar López <zomars@me.com>
2022-08-12 18:18:13 +00:00
zomars cc48b87fa3 Merge branch 'main' of https://github.com/calcom/cal.com 2022-08-12 11:50:16 -06:00
zomars a839cf7f59 Adds symlink to use DATABASE_URL from root 2022-08-12 11:23:30 -06:00
mohammed hussamandAlex van Andel 935491c70f migrate api/availability/[user] to viewer.availability.user in trpc (#3591)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-08-12 11:16:55 -06:00
zomars 62c168176d Adds symlink to use DATABASE_URL from root 2022-08-11 19:53:16 -06:00
zomars 50a20e2c3b Clarifies new db vs old db 2022-08-11 18:43:59 -06:00
zomars f28a1312a6 chmod vercel script 2022-08-11 18:31:30 -06:00
zomars 4c89a64f0e Adds script to generate dummy DB per preview 2022-08-11 18:29:51 -06:00
Leo Giovanetti f57726dc95 Hotfix: Signup fix error not shown (#3815)
* Fix error not shown

* Simplifying

* Removing dead code
2022-08-11 23:48:58 +00:00
Omar Lópezandkodiakhq[bot] a2a5d48037 Upgrade turborepo v4 (#3804)
* Upgrades to turborepo 4

Adds turbo eslint config

* Linting according to turbo eslint plugin

* Add missing variables to turbo config

* Adds more missing env vars to turbo json

* Update turbo.json

* Update turbo.json

* Matches local lint with CI lint

* Update turbo.json

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-11 13:17:56 -06:00
Syed Ali Shahbaz 870cb1f872 adds attendee (#3808) 2022-08-11 11:16:44 -06:00
Omar Lópezandkodiakhq[bot] accf845f0e Allows tree shaking unused modules (#3752)
* Allows tree shaking unused modules

* don't tree shake embed-core side effects

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-11 10:36:30 -06:00
Pavel Shapelandzomars 6d79f80928 refactor: use BookingReference instead of DailyEventReference (#3667)
* refactor: use BookingReference instead of DailyEventReference

* refactor: migrate DailyEventReference records to BookingReference

* refactor: drop DailyEventReference table

* fix linting

* Daily Video API adapter fixes

Co-authored-by: zomars <zomars@me.com>
2022-08-10 18:53:05 -06:00
Udit TakkarandCarina Wollendorfer 14f0c30584 fix: error message for riverside and whereby added (#3751)
* fix: error message for riverside and whereby added

* fix: around and ping error message added

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2022-08-10 15:13:40 -04:00
zomars 4bfa63e804 Submodule sync 2022-08-09 17:37:08 -06:00
d363f6e2f6 Fixes security issue when updating workflows (#3661)
* check if workflow is deleted before deleting reminders

* disable continue button when mutation is loading

* check if new active event types belong to user

* fix issue with onCascade deletion

* Simplified ANDs

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-09 23:27:49 +00:00
zomars e8d6d50bc2 v1.8.5 2022-08-09 16:56:53 -06:00
9221042db4 feat: signin test email magic signup in app (#3749)
* feat: signin test email magic signup in app

* fix: no signIn page in nextauth

* fix: remove commented signIN

* Update apps/web/pages/auth/signin.tsx

remove import useRouter

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

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-09 15:38:21 -06:00
alannncandOmar López b936b7c256 Fix url redirect to a only path redirect when joining a team (#3757)
* fix full url redirect to a only path redirect

* Remove string templates

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

Co-authored-by: Omar López <zomars@me.com>
2022-08-09 15:19:44 -06:00
Udit TakkarandCarina Wollendorfer 57869f0b88 fix: cancel booking buttons design (#3748)
* fix: cancel booking buttons design

* chore: remove rounded

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2022-08-09 14:23:55 -04:00
alannnc 7a4795bdbf Fix request-reschedule for team events (#3721) 2022-08-09 11:23:12 -06:00
Peer Richelsen 773bb5424b Update README.md 2022-08-09 15:55:48 +02:00
Peer Richelsen 2c600cff43 Update README.md 2022-08-09 15:55:04 +02:00
Omar Lópezandsean-brydon 0eeee42811 Adds middleware to get V2 early access (#3617)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-08-09 10:21:15 +01:00
Udit Takkarandkodiakhq[bot] 8eb45ee1eb fix: remove mt (#3740)
* fix: remove mt

* fix: un comment icon

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-09 04:49:46 +00:00
Alex van AndelandLeo Giovanetti 87d4afea13 Same-day schedules created invalid workingHours (#3742)
* Same-day schedules created invalid workingHours

* Uncomment logger

* Previous version did not properly substract the days

Co-authored-by: Leo Giovanetti <hello@leog.me>
2022-08-08 14:17:33 -06:00
Peer Richelsenandzomars 7776bbbf52 added a few desktop only improvements (#3725)
* added a few desktop only improvements

* added horizontal line

* fixed icon

* hide download button when already in desktop app

* codacy fixes

Co-authored-by: zomars <zomars@me.com>
2022-08-08 13:39:51 -06:00
88384192f8 Fixes bug when changing the location of a booking (#3585)
* use correct booking

* delete calendar event and update booking reference

* use middleware to get booking

* updating event instad of deleting and creating a new one

* fix issue with google hangout link

* remove unsed imports

* Apply suggestions from code review

* Update bookings.tsx

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-08 19:38:02 +00:00
zomars f036f0fb64 RHF conditionals fixes
Improved legibility
2022-08-08 13:24:49 -06:00
Ilya KatzandJoe Au-Yeung d0f34aa699 Update instructions for integration with google calendar (#3558)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-08-08 11:06:53 -04:00
+2 507f5d028e Setup Wizard Not Pulling Timezone (#3677)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Justine Nakitto <justinejjingo@gmail.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Justine Nakitto <justinejjingo@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-08 12:19:11 +00:00
Leo GiovanettiandPeer Richelsen 477c411611 Updating website to latest (#3731)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-08 11:05:44 +02:00
alannncandkodiakhq[bot] 3652ffa40a Added notes for running tests for embed-core (#3720)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-08 08:06:44 +00:00
Peer Richelsenandsean-brydon 1332b76441 added ping.gg app (#3728)
* added ping.gg app, not sure why its not showing

* nit

* Fixes

* fixed email

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-08-08 05:57:09 +00:00
Peer Richelsen 1a3aaa134e Update README.md 2022-08-07 19:45:32 +02:00
Peer Richelsen af06ded584 Update README.md 2022-08-07 19:44:31 +02:00
Peer Richelsen 2f9862bb47 fixed icons (#3730) 2022-08-07 14:29:07 +02:00
zomars 22d3fb7e92 Adds verify page 2022-08-05 21:09:52 -06:00
zomars 3ed206df23 Squashed commit of the following:
commit c378c647fe4822071cbdfc577fcfc950e53973df
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 17:36:21 2022 -0600

    Cleanup

commit a89cc7981df1c2aae96188466833490eb3b227a1
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 17:17:57 2022 -0600

    redirect fixes

commit a27979c2b3f06c9956ee9abfef1e82a5558b6411
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 17:02:33 2022 -0600

    Fingers crossed

commit c08a09e8d2ba3eb2561f418bb3b0370245f79bf7
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 16:28:55 2022 -0600

    Update [...nextauth].tsx

commit 4063bddb2ea3cbf3dda6be6cc10070ecc1eb578c
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 16:05:21 2022 -0600

    Submodule sync

commit e44dc73a3b751ba4cacc85abfe6680a668393c0d
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 16:05:06 2022 -0600

    Upgrade next-auth

commit 2b36e0a1b376083739ea06b793f1523e2f158a33
Author: zomars <zomars@me.com>
Date:   Fri Aug 5 15:23:03 2022 -0600

    Magic signup fixes

commit 2cc81ca7e2f79a4e3fcab370e23ac29cbe3ee18d
Author: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Date:   Thu Aug 4 11:49:51 2022 -0400

    Properly parse list calendar call to MS Graph (#3698)

commit 65887d8f9db8b399964fce38ec104ed24fcc48e6
Author: Leo Giovanetti <hello@leog.me>
Date:   Thu Aug 4 12:52:50 2022 -0300

    Fix app-store seeding (#3693) (#3697)

    Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

    Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
    Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

commit 71594b962352519c4ed4ee6cf68d16aac9b71e26
Author: Leo Giovanetti <hello@leog.me>
Date:   Wed Aug 3 16:48:07 2022 -0300

    fixed dark mode profile page (#3650) (#3681)

    * fixed dark mode profile page

    * nit

    Co-authored-by: Peer Richelsen <peeroke@gmail.com>

commit 78a5ae988274ff05f26309033821acc48e22823d
Author: Leo Giovanetti <hello@leog.me>
Date:   Wed Aug 3 14:05:02 2022 -0300

    Potential fix (#3671) (#3676)

commit b006240b0c62e9e62944cb7184793f099358e2ec
Merge: 6179b3fbe e7418f68a
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 18:20:47 2022 -0600

    Merge branch 'main' into production

commit 6179b3fbe06709f1cf3a4e54214a7dbb1543765e
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 18:20:40 2022 -0600

    Fixes successRedirectUrl validation

commit 14443099e0377e8d9af52f53f15c2f8b91b784d2
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 17:59:47 2022 -0600

    successRedirectUrl fixes
2022-08-05 18:08:05 -06:00
ff3bd5fc61 Event description now supports markdown formatting (#3688)
* yarn

* yarn update

* added Mardown style formatting with sanitized inputs

* Revert "yarn"

This reverts commit 46eb3595cf687504c6783315749b3124936080b9.

* Revert "yarn"

This reverts commit 46eb3595cf687504c6783315749b3124936080b9.

* Revert "yarn update"

This reverts commit f4600d83a67b930c6dd72e292827578856ca5428.

* Revert "yarn"

This reverts commit 46eb3595cf687504c6783315749b3124936080b9.

* Revert "yarn update"

This reverts commit f4600d83a67b930c6dd72e292827578856ca5428.

* Revert "Revert "yarn update""

This reverts commit 8ccab5ee237fb228115229f3c89cf4b848bc9449.

* Revert "Revert "yarn""

This reverts commit 78a755eb39cb518eb0526272a3c11882c48004de.

* yarn.lock removed

* Parses using prisma middleware

* Reverting changes

* Fixes hydration warning

* Fixes Dom warnings

* Update yarn.lock

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-05 18:44:30 +00:00
be16b72400 feat: support lark calendar (#3019)
* feat: support lark calendar

* New Crowdin translations by Github Action (#3016)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* fix: lark calendar get app ticket bugs

* feat: add send msg service for lark bot

* fix: comment on PR of lark-calendar

* chore: update lark bot message

* Refactors add endpoint

* Adds missing GET endpoint handler

* Update yarn.lock

* fix: comments

* Update yarn.lock

* fix: comment about inferred type

* Added fetcher helper

* Update yarn.lock

Co-authored-by: chengcheng.frontend <chengcheng.frontend@bytedance.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-05 17:56:20 +00:00
775fe92960 Fix bug for reschedule on opt-in bookings (#3678)
* Fix bug for reschedule on opt-in bookings

* Fix a missing query parameter being removed when changing month, useRouterQuery

* Added test for rescheduling user type condition

* Fix conditional that triggers input disabling when rescheduling

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-08-05 17:08:47 +00:00
Omar López 70d6c8d8e7 Cleans up cli generator and enables prettier formatting (#3718) 2022-08-05 17:41:26 +01:00
Udit Takkar f589d06ed4 fix: icon positioning (#3715) 2022-08-05 20:33:27 +05:30
Udit TakkarandPeer Richelsen 2b7b03fffd fix: group hover bug (#3699)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-05 09:07:06 +00:00
a53ca3389c updates to all public booking pages (#3648)
* updates to all public booking pages

* sidebar tweak

* resolving error

* Fix linting errors

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-05 08:46:44 +00:00
Hariom Balhara 85764b6aa6 Embed Test: Verifies that app remains hidden till link is ready (#3669)
* Add test to verify that webapp remains hidden till link is considered ready

* Make tests more stable
2022-08-05 08:26:15 +00:00
Hariom Balhara ee767a3e41 Fix textarea dark theme (#3712) 2022-08-05 07:32:21 +00:00
Leo Giovanetti 414233961c Fixing App tabs routing (#3711)
* Removing linkProps

* Fixing format
2022-08-05 05:21:43 +00:00
Leo Giovanetti 7747b3d9a0 Adding debugging info (#3706) 2022-08-04 16:55:20 -03:00
Udit Takkar da638c4b1b fix: made add-to-calendar responsive (#3703) 2022-08-04 13:27:04 -06:00
218c3d7b50 fix heroku build (#3630)
* pick https://github.com/nosovk/cal.com/pull/1

* make all devdeps to devDependencies

* add more ENV to Heroku config
https://github.com/calcom/cal.com/pull/3630

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-04 11:44:29 -06:00
Joe Au-Yeung 37309e8435 Properly parse list calendar call to MS Graph (#3698) 2022-08-04 16:49:51 +01:00
Hariom Balharaandkodiakhq[bot] 2aee450c0b Fix app-store seeding (#3693)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-04 14:15:23 +00:00
Carina Wollendorfer ca7a78dcda Improve design of workflow reminder email (#3685) 2022-08-04 08:52:05 +00:00
fc4b88e222 New Crowdin translations by Github Action (#3689)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-08-04 10:39:19 +02:00
github-actions[bot] affedd1014 New Crowdin translations by Github Action (#3683) 2022-08-04 09:25:24 +01:00
Carina Wollendorfer 4240235cb8 make edit button full width (#3686) 2022-08-04 09:23:36 +01:00
2a73fd8188 Variables for custom templates in workflows (#3602)
* improve functionality to update a step

* remove console logs

* fix issue with active event types

* allow null value for time and timeUnit

* sort steps asc step number

* add action to workflow (frontend)

* add  phone number input for SMS to specific number

* use PhoneInput for number input + input validation

* improve invalid input for phone number

* improve UI of phoneInput

* Improve design and validation

* fix undefined error

* set default action when adding action

* include all team event types

* fix phone number input for editing steps

* fix update muation to add steps

* remove console logs

* fix order of steps

* functionality to delete steps

* add trigger when event is cancelled

* add custom email body

* sms and email reminder updates

* add custom emails

* add custom email subject

* send reminder email to all attendees

* update migration

* fix default value for time and timeUnit

* save email reminders to database

* clean code

* add custom template to SMS actions

* schedule emails with sendgrid

* clean code

* add workflow templates

* keep custom template saved when changing templates

* create reminder template for email

* add dot at the end of sentace for email template

* fix merge error

* fix issue that template was not saved

* include sending emails for when event is cancelled

* fix bug that email was always sent

* add templates to sms reminders

* add info that sending sms to attendees won't trigger for already exisitng bookings

* only schedule sms for attendees when smsReminderNumber exists

* only schedule sms for attendees when smsReminderNumber exists

* set scheduled of workflow reminder to false when longer than 72 hours

* add cron for email scheduling + fixes for for sms an email scheduling

* adjust step number when deleting a step

* cast to boolean with !!

* update cron job for email reminders

* update sms template

* send reminder email not to guests

* remove sendTo from workflow reminder

* fixes sending sms without name + removing sendTo everywhere

* fix undefined name in sms template

* set user name to undefined for sending sms to a specific number

* fix singular and plural for time unit

* set to edit mode when changing action and custom template is selected

* delete reminders when booking cancelled or not active anymore

* fix type errors

* fix error that deleted reminders twice

* create booking reminders for existing bookings when eventType is set active

* improve email and sms templates

* use BookingInfo type instead of calendarEvent for reminder emails

* schedule emails for already existing bookings

* add and remove reminders for new active event types and cancelled events

* connect add action button with last step

* fix step container width for mobile view

* helper functions that return options for select

* fix typo and remove comment

* clean code

* add/improve error messages for forms

* fix typo

* clean code

* improve email template

* clean code

* fix missing prop

* save reference id when scheduling reminder

* fix step not added because of changed id for new steps

* small fixes + code cleanup

* code cleanup

* show error message when number is invalid

* fix typo

* fix phone number input when location is already phone

* set multi select checkbox to read only

* change email scheduling in cron job from 7 days to 72 hours

* show active event types in workflow list

* fix trigger information for workflow list

* improve layout for small screens in workflow list

* remove optional from zod type for workflow name

* order workflows by id

* use link icon to show active event types

* fix plural and add translation for showing nr of active eventtypes

* fix text for sms reminder template

* add reminders for added steps

* remove optional for activeOn

* improve reminder templates

* improve design of custom input fields

* set edit mode to false when phone number isn't needed anymore

* set sendTo in workflow step only for SMS_NUMBER action

* set email body and subject only when custom template

* only delete reminders that belong to workflow steps

* improve text for new event book trigger

* move reminders folder to workflows

* fix issue that save button was sometimes enabled in edit mode

* fix form issues for send to

* delete all scheduled reminders when workflow is deleted

* use enum for method

* fix imports for workflow methods

* add missing import

* fix edit mode

* create reminders when event is confirmed

* add reminderScheduler to reduce duplicate code

* make workflow enterprise and pro only feature

* move all files to /ee/ folder

* move package.json change to /ee/ folder

* add pro badge to shell

* set to edit mode to true if email subject is missing when action changes

* fix loading bug

* add migration

* fix old imports

* don't schedule reminders for opt-ins

* fix style of email body

* code clean up

* Update yarn.lock

* fix isLoading for active on dropdown

* update import for prisma

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

* update imports

* remove console

* use session to check if user has valid license

* use defaultHandler

* clean up code

* Create db-staging-snapshot.yml

* move LisenceRequired inside shell

* update import for FormValues

* fix phone input design

* fix disabled save button for edit mode

* squah all migration into a single one

* use isAfter and isBefore instead of isBetween

* import dayjs from @calcom

* validate phone number for sms reminders when booking event

* Allows auto approvals for crowdin

* add dropdown for adding dynamic variables to string

* use lighter text colour for disabled button

* change textarea for email subject to one row only

* add function to create custom template with dynamic variables

* add location to dynamic variables

* add missing locations to custom template

* add variables at cursor position

* remove second curly brakets from variables

* code cleanup

* fix names for attendee and organizer

* enable internationaliztion for custom reminders

* disabled add workflow button when mutation is loading

* fix translation keys

* internationalize dates

* code clean up

* add translation for email subject

* skip translation for english

* remove language from BookingInfo type

* add additional notes as variable

* add custom inputs as variables

* add information for custom inputs

* add translation

* Add formated variables in additional input information

* fix type errors

* code clean up

* Update AddVariablesDropdown.tsx

* Update variableTranslations.ts

* Update variableTranslations.ts

* Formatting

* Update [workflow].tsx

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-03 22:22:38 +00:00
e878e1a665 Fix/api build (#3675)
* fix: shared next-auth calendso session type fix

* fix: move imports from @lib -> @calcom/lib to fix types

* Consolidates next-auth definitions

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: zomars <zomars@me.com>
2022-08-03 13:18:26 -06:00
298a6d5e15 Tweaks for Website signup form (#3635)
* WIP, only deep testing needed

* Feedback suggestions

* Uses early returns instead

* Allows overriding t function via prop

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-08-03 13:59:40 -03:00
sean-brydon 532723fb41 Update V2 components + Move to react-icons (#3670) 2022-08-03 16:01:29 +00:00
b3b82fa1cf When no location, defaults to cal video (#3517)
* yarn

* fixed bug

* fixed warning

* fixed problem on server side

* yarn update

* Delete yarn.lock

* removed unused type

* Revert "Delete yarn.lock"

This reverts commit 4fe99af4757343d9efb2326be546cd81161c8da3.

revert delete yarn.lock

* Delete yarn.lock

* Revert "Delete yarn.lock"

This reverts commit 9f76a548e0950202674e5c5fcb755db3ff7867bd.

revert delete yarn.lock

* Revert "yarn update"

This reverts commit 2bef496c1693acd509a7b577ca7013e94ab7200a.

* yarn

* remove yarn.lock

* remove yarn.lock

* removed double declaration

* Revert "yarn update"

This reverts commit 2bef496c1693acd509a7b577ca7013e94ab7200a.

* stop tracking yarn lockfile

* undo all yarn lockfile changes

* Revert "yarn"

This reverts commit 46eb3595cf687504c6783315749b3124936080b9.

* remove all yarn changes

* Update packages/trpc/server/routers/viewer/bookings.tsx

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

* zod transform insteead of ternary operator

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-03 09:35:08 -06:00
Leo Giovanetti 48a1f22ca5 Potential fix (#3671) 2022-08-03 15:23:01 +00:00
Peer Richelsen 77bf1d0712 Update README.md 2022-08-03 14:38:11 +02:00
Peer Richelsen 23c79fff75 Update README.md 2022-08-03 14:37:45 +02:00
Peer Richelsen abea19ad39 Update LICENSE 2022-08-03 14:36:43 +02:00
Peer Richelsen 5ca2d7cae2 Update LICENSE 2022-08-03 14:36:32 +02:00
Adam Garbowski 3ac140fad5 [Proposal] Event type error popover proposal (#3627) 2022-08-03 09:03:01 +00:00
Heiki 5358e5848d feat(troubleshoot): add event titles to the troubleshoot page (#3434) (#3437) 2022-08-03 09:19:16 +02:00
6301ea16f8 Avoid duplicating any video credential (#3593)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-08-02 12:52:21 -06:00
alannnc e767d233ad Fix/google calendar false alert (#3636) 2022-08-02 18:07:45 +00:00
Peer Richelsen 7480a73905 fixed dark mode profile page (#3650)
* fixed dark mode profile page

* nit
2022-08-02 18:59:33 +02:00
Agusti Fernandez Pardo fe50b8de18 fix: cleanup onboarding username (#3638) 2022-08-02 10:33:21 -06:00
Agusti Fernandez Pardo fd8b323e47 fix: base_url for login link should be WEBAPP_URL not WEBSITE_URL (#3637) 2022-08-02 10:05:09 -06:00
Leo Giovanettiandkodiakhq[bot] 760026b2e6 Correcting syntax of overloading (#3598)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-02 05:52:25 +00:00
zomars 53de4ddaaf Fixes successRedirectUrl
commit b006240b0c62e9e62944cb7184793f099358e2ec
Merge: 6179b3fbe e7418f68a
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 18:20:47 2022 -0600

    Merge branch 'main' into production

commit 6179b3fbe06709f1cf3a4e54214a7dbb1543765e
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 18:20:40 2022 -0600

    Fixes successRedirectUrl validation

commit 14443099e0377e8d9af52f53f15c2f8b91b784d2
Author: zomars <zomars@me.com>
Date:   Mon Aug 1 17:59:47 2022 -0600

    successRedirectUrl fixes
2022-08-01 18:45:47 -06:00
zomars e7418f68a4 Submodule sync 2022-08-01 17:44:40 -06:00
zomars 3ae4655449 v1.8.4 2022-08-01 17:42:06 -06:00
zomars 299f69aa7b Fixes successRedirectUrl 2022-08-01 17:20:06 -06:00
c1896fc833 Feat/onboarding username (#3580)
* fix: remove duplicate button in textusernamefield

* feat: adds username if not present in getting-started first onboarding step

* fix: credentials

* fix: typo

* fix: user type

* fix: type errors

* Validates successRedirectUrl earlier with zod

* Removes deprecated api endpoint

* Reduces the data needed for the frontend on onboarding

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-08-01 15:44:08 -06:00
zomars 54acf6d28f Sorts root scripts and adds prisma alias 2022-08-01 13:50:30 -06:00
Joe Au-Yeung 7e8f13148e Fix app id when creating credential (#3629) 2022-08-01 13:25:04 -06:00
b42f0ffadd Fixes unique constraint error of batchId when scheduling emails (#3542)
* make batchid unique + return response status 200

* use correct batchId

* change for loop and updateMany

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-01 10:20:21 +00:00
Hariom Balharaandkodiakhq[bot] 9346ae644f Use non shallow mode (#3620)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-08-01 09:40:38 +02:00
d6fd9b5df3 Hide dropdown under modal dialog (#3607)
Co-authored-by: Adam Garbowski <adamgarbowski90@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-31 21:48:17 +02:00
github-actions[bot]andCrowdin Bot 529adc4137 New Crowdin translations by Github Action (#3609)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-31 10:27:04 +02:00
Omar Lópezandkodiakhq[bot] c9d7412a43 Refactors twilio provider (#3604)
Prevents multiple twilio redeclarations

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-30 16:02:34 +00:00
Carina WollendorferandCarinaWolli dba48ab852 fix design issue (#3606)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-07-30 15:49:01 +00:00
0bca141b74 Fix last day of month handling logic in tests (#3605)
* Fix last day of month handling logic

* Update apps/web/test/lib/getSchedule.test.ts

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

* Update apps/web/test/lib/getSchedule.test.ts

* Update apps/web/test/lib/getSchedule.test.ts

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-07-30 09:33:32 -06:00
a4e91a0666 Improved German locale (#3521)
Improved and corrected the grammar and wording.

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-30 16:56:43 +02:00
mohammed hussam 8efb98698d change prerequistic of node.js from min 14.x to 15.x (#3587) 2022-07-30 16:54:42 +02:00
Peer Richelsen 1403a567ec nit change to readme 2022-07-30 15:25:00 +02:00
Peer Richelsen bd6a84e986 added figma design system and help wanted shields to readme 2022-07-30 15:24:33 +02:00
Peer Richelsen bd41d9d5f0 added help wanted project board 2022-07-30 15:15:42 +02:00
github-actions[bot]andCrowdin Bot 7e7938a4d9 New Crowdin translations by Github Action (#3569)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-30 10:15:04 +02:00
zomars 2f03f18447 Fixes missing import 2022-07-29 22:34:57 -06:00
zomars 2b054862df Uses ISR on setup pages 2022-07-29 22:04:59 -06:00
zomars 382f11f740 Update [status].tsx 2022-07-29 21:52:43 -06:00
zomars 920050022a Update WebhookListContainer.tsx 2022-07-29 21:52:43 -06:00
zomars 95a0496f32 Type fixes 2022-07-29 21:52:43 -06:00
zomars 1ac36882ac Update Kbar.tsx 2022-07-29 21:52:43 -06:00
zomars a4b8cc8391 Fixes dotenv replacements 2022-07-29 19:30:02 -06:00
zomars e7a91d4594 Squashed commit of the following:
commit e84ca3c1fcb625f8cde160b31c1f2b5def78e0bf
Author: zomars <zomars@me.com>
Date:   Thu Jul 28 14:10:57 2022 -0600

    fix

commit 29025a1104c2853597b5f3abd0d26aaa27a76aa2
Author: zomars <zomars@me.com>
Date:   Thu Jul 28 14:10:04 2022 -0600

    Force vercel deploy

commit b070a6460cf871488340a22d71c14962f37e7023
Author: Hariom Balhara <hariombalhara@gmail.com>
Date:   Thu Jul 28 19:03:07 2022 +0530

    Avoid global variables leak by making the entire code IIFE (#3543) (#3568)

    Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

    Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

commit f05c7c40829947aefb0c67b931420bca18b79b29
Author: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Date:   Wed Jul 27 16:19:59 2022 -0400

    Add axiom to next config (#3556)

commit c3d05e86862277c270557bab98e170cc8b051afe
Author: Hariom Balhara <hariombalhara@gmail.com>
Date:   Wed Jul 27 14:12:09 2022 +0530

    Potential fix, broken workflow (#3545)

commit 36e1c9272fa9b86cbe9e53e03e83799820674f17
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 18:27:57 2022 -0600

    Update [schedule].tsx

commit f39535feea22a11a857d179f3506803602a95728
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 18:07:30 2022 -0600

    Update [status].tsx

commit c3ac1bcc8c7ff8dc7ebf178d23a6e5c7ea223749
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:53:43 2022 -0600

    Update [schedule].tsx

commit 39364d8476b3c582d4a7fe3f8d139c5d254e3ecf
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:43:13 2022 -0600

    Debugging prod

commit 4b428d22dec734dab19096b4fe07741f18e5bc5d
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:20:44 2022 -0600

    Update checkLicense.ts

commit 4212e6d61908face52d647bb661d71db959cf864
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:11:37 2022 -0600

    Booking fixes attempt

commit e3160027ea0ce40d129b445daeb3bb17711ac640
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 13:52:35 2022 -0600

    migration conflict fixes
2022-07-29 19:28:54 -06:00
Sagar 54c8c294fc Fix Deploy with Vercel and add note and build commands and root directory (#3590) 2022-07-29 18:49:15 -06:00
zomars 49f584a956 Fixes tailwind for EE code 2022-07-29 16:26:31 -06:00
Adam Garbowski 67f72223bf Fix broken getLocaleFromHeaders import (#3592) 2022-07-29 08:28:53 -06:00
Peer Richelsen 534efe53fe added status.cal.com badge 2022-07-29 15:47:44 +02:00
Leo Giovanetti 5437005c7e UI tweaks (#3576) 2022-07-29 15:23:54 +02:00
zomars b931cf2d7c Submodule sync 2022-07-28 14:25:38 -06:00
Omar Lópezandkodiakhq[bot] 464343f5ab Refactors EE code (#3490)
* WIP

* WIP

* Type and migration fixes

* Adds missing default import

* Fixes import

* Fixes tRPC imports in App Store

* Migrate stripe helpers

* WIP

* Type fixes

* Type fix?

* WIP

* WIP

* Update index.ts

* Fixes

* Update workflow.tsx

* Moved queries to lib

* Moves QueryCell

* Migrates MultiSelectCheckboxes

* WIP

* CryptoSection type fixes

* WIP

* Import fixes

* Build fixes

* Update app-providers.tsx

* Build fixes

* Upgrades hookform zod resolvers

* Build fixes

* Cleanup

* Build fixes

* Relocates QueryCell to ui

* Moved List and SkeletonLoader

* Revert QueryCell migration

* Can't use QueryCell here

* oops

* CryptoSection cleanup

* Update app-providers.tsx

* Moved ee to features

* ee to features/ee

* Removes @calcom/ee

* Adds possible feature locations

* Build fixes

* Migrates stripe to app-store lib

* Colocates stripe imports

* Update subscription.ts

* Submodule sync

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-28 13:58:26 -06:00
Bailey Pumfleet 84b4bb397f Improve help dialog strings (#3570) 2022-07-28 15:54:48 +00:00
Muhammad AliandBailey Pumfleet b90cf96ab9 Fix: Doc typo fix in coding styling (Developer docs) (#3533)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-07-28 15:56:42 +01:00
github-actions[bot]andCrowdin Bot 5d61d2d79b New Crowdin translations by Github Action (#3539)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-28 11:56:43 +01:00
Hariom Balharaandkodiakhq[bot] 1be5510c5e Embedded Routing Forms - Part1 (#3530)
* Support dark theme in routing form

* Fix Embed detection

* Add routing form embed example

* Better rendering support in dark mode for react-select

* Fix more theme issues

* Added test for Routing Form Embed

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-28 10:50:25 +00:00
Hariom Balhara e871781079 Fix getSchedule tests (#3560) 2022-07-28 16:07:00 +05:30
bfa70dcc83 Feat/onboarding admin (#3486)
* WIP

* API and step done fallback

* Finishing up tweaks

* Inline comment

* Translations

* Update apps/web/pages/api/auth/setup.ts

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

* Update apps/web/pages/api/auth/setup.ts

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

* Update apps/web/pages/api/auth/setup.ts

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

* Update apps/web/pages/api/auth/setup.ts

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

* Linting fixes

* Update apps/web/pages/auth/setup.tsx

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

* Linting fix

* Moving to v2

* Translations

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-27 17:28:21 -06:00
sean-brydonandkodiakhq[bot] b3bd8c1a58 Few updates to V2 components (#3532)
* Fix breadcrumb colors

* HorizontalTabs

* Team List Item WIP

* Horizontal Tabs

* Cards

* Remove team list item WIP

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-27 23:12:27 +01:00
Joe Au-Yeung 2c23949cea Add axiom to next config (#3556) 2022-07-27 16:19:59 -04:00
sean-brydon 6e3825d95a Fixes depth (#3555) 2022-07-27 13:50:10 -06:00
525ce0d075 Fix/office365 api call (#3534)
* WIP fixes for handling office365 api call

* First working version microsoft api call handling

* Remove logs

* Clean up and improve function names

* Clean up

* Remove log

* Fix function missing its correct name

* Fix direct return of statement

* Cleanup

* Simplify with private fetcher

* Update CalendarService.ts

* Update getOfficeAppKeys.ts

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-27 19:12:42 +00:00
zomars 7537ea5b91 Fixes button appearance on Safari 2022-07-27 12:58:24 -06:00
Hariom Balharaandkodiakhq[bot] 47468798fd Avoid global variables leak by making the entire code IIFE (#3543)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-27 15:58:51 +00:00
sean-brydon 587526186f Updates build DIR/command (#3550) 2022-07-27 12:02:20 +01:00
sean-brydon 7582d93d6a Fix chormatic Action (#3548) 2022-07-27 11:33:10 +01:00
Peer Richelsen b9645b4de5 feather icons and inter (#3366) 2022-07-26 20:24:00 -06:00
zomars a1fcdcae44 v1.8.3 2022-07-26 18:46:14 -06:00
zomars cf5912335e hotfix: bookings and availability pages
commit 36e1c9272fa9b86cbe9e53e03e83799820674f17
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 18:27:57 2022 -0600

    Update [schedule].tsx

commit f39535feea22a11a857d179f3506803602a95728
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 18:07:30 2022 -0600

    Update [status].tsx

commit c3ac1bcc8c7ff8dc7ebf178d23a6e5c7ea223749
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:53:43 2022 -0600

    Update [schedule].tsx

commit 39364d8476b3c582d4a7fe3f8d139c5d254e3ecf
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:43:13 2022 -0600

    Debugging prod

commit 4b428d22dec734dab19096b4fe07741f18e5bc5d
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:20:44 2022 -0600

    Update checkLicense.ts

commit 4212e6d61908face52d647bb661d71db959cf864
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 17:11:37 2022 -0600

    Booking fixes attempt

commit e3160027ea0ce40d129b445daeb3bb17711ac640
Author: zomars <zomars@me.com>
Date:   Tue Jul 26 13:52:35 2022 -0600

    migration conflict fixes
2022-07-26 18:45:10 -06:00
zomars 5ad25a692f v1.8.2 2022-07-26 13:03:19 -06:00
3bdf4a84f8 Prevent team members from creating events (#3498)
* Delete CreateEventTypeBtn.tsx

* Adding Permission to creation router

* Disable button if membership role is member

* Fix linting error

* Update explicit role selection

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

* Type fix

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-07-26 18:25:42 +00:00
Joe Au-Yeung 7a306900ab Fix input on trial accounts (#3522)
* Fix input on trial accounts

* Refactor conditional
2022-07-26 12:04:20 -06:00
35259b75f6 Display correct time format on availability page (#3441)
* Pass time format

* Write 24hr time format

* Remove console.log

* Change regex

* Add console.log

* Remove console.log

* Remove console.log

* Update TimeOptions.tsx

* Write time format to localstorage on user create / edit

* Grab and set from local storage

* On user create grab timeformat from browser

* Update timeFormat in DB

* Fix typo

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-07-26 12:02:44 -06:00
zomars 538c5dff43 Playwright should not live on deps 2022-07-26 10:54:25 -06:00
zomars 590ec3bf23 Update check-if-ui-has-changed.yml 2022-07-26 10:19:53 -06:00
zomars 53befd7c5c Build fixes
handleErrorsJson signature changed which triggered errors here
2022-07-26 10:00:59 -06:00
Alex van Andelandsean-brydon 047d4a0773 Some optimisations to the storybook (#3526)
* Some optimisations to the storybook

* Fixed merge artifact

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-07-26 15:05:11 +01:00
ef6ed6050b Feat: Add next-themes for theme handling (#3263)
* chore: add next-themes

* feat: add next theme provider

* fix: modify useTheme hook to use next-themes

* fix: use the new hook for theming

* remove conditional rendering with isReady boolean flag

* remove Theme component returned from useTheme hook

* Update missing useTheme usage

* Fix theme switching as per system. Also, ensure that booking and non booking pages can maintain their own requirements of theme simulatenously

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-07-26 08:27:57 +00:00
Carina WollendorferandCarinaWolli 2b5759e8bb fix translation key (#3527)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-07-25 19:38:04 -06:00
33e79c8ad5 Improve phone number input (#3472)
* add country code once country selected

* add changes also for when editing workflows

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-25 19:37:32 -06:00
629131b7f9 Disallow search engine indexing of hidden event types (#3478)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-07-26 00:17:15 +00:00
Alex van Andel a156a784a1 Fixes Atlantic/Azores blank results (#3516) 2022-07-25 15:45:34 -06:00
Carina WollendorferandCarinaWolli d8b2fc94b6 fix cron job for reminders (#3488)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-07-25 15:04:08 -06:00
Leo Giovanettiandsean-brydon e21e622096 Storybook router issue fix (#3525)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-07-25 22:02:33 +01:00
Omar Lópezandkodiakhq[bot] 160ccc3502 Fixes/bookings 500 (#3523)
* Fixes an annoying 500 error in bookings

* Fixes back to bookings link

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-25 20:22:56 +00:00
Leo Giovanettiandkodiakhq[bot] 82d8db5f70 HubSpot app improvement: create contacts (#3502)
* WIP

* Last changes

* Create non-existing contacts from attendees

* Adding list of features

* Removing unneeded code

* Missing error when creating contacts

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-25 16:13:52 -03:00
sean-brydon 43a6e3f5ce Fix V1 Colors (#3520) 2022-07-25 12:57:16 +01:00
Omar Lópezandkodiakhq[bot] c03ca5549f Wipe my cal e2e fixes (#3509)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-24 20:18:36 +00:00
Leo GiovanettiandPeer Richelsen e395c9297d Fixing already installed apps & link to them (#3500)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-23 00:59:30 +00:00
277b0c4c92 Feat/design system (#3051)
* Storybook Boilerplate setup

* Inital Setup

* First story

* Color Design System

* Badge Story + Comp

* Checkbox UI + Stories

* Update Red colors + Button Group

* Switch+Stories / Default brand color

* Update Version + Button Group combined

* Compact Butotn Group

* Tidy up Selectors

* Adds Tooltip to Button

* TextInput

* Update SB

* Prefix Input

* Match text area styles

* Prefix Controls

* Update spacing on text area

* Text Input Suffix

* Color Picker

* Update storybook

* Icon Suffix/Prefix

* Datepicker + move components to monorepo

* Text color on labels

* Move Radio over to monorepo

* Move CustomBranding to calcom/ib

* Radio

* IconBadge Component

* Update radio indicator background

* Disabled radio state

* Delete yarn.lock

* Revert "Delete yarn.lock"

This reverts commit 9b99d244b70872153a16bec1f1f3bc651e94be7a.

* Fix webhook test

* Replace old toast location

* Update radio path

* Empty State

* Update Badge.tsx

* Update Badge.tsx

* Banner Component+story

* Creation Modal

* Creation Dialog updated

* Button hover dialog

* Confirmation Modal

* Datepicker (Booking)

* PageHeader

* Fix border width

* PageHeader update search bar

* Fix input height

* Fix button group size

* Add spacing between badges - font smoothing

* Update button position on banner

* Banner update

* Fixing focus state on suffix/prefix inputs

* Implement A11y addon

* Add aria label

* error && "text-red-800"

* Fix button hover

* Change colors

* Generate snapshot tests for on hover button

* Revert colors to demo

* Change colors

* Fix Linear Issues

* Form Stepper component

* Add padding back to input

* Move ui to UI_V2

* Use V2

* Update imports for v1

* Update imports for v1

* Upgrade to nextjs in storybook root

* Update website submodule

* Avatar Groups

* Fix webpack again

* Vertical Tab Item

[WIP] - active state on small item is not working currently

* Vertical Tab Group

* Add Github action

* Fix website submodule

* Fix GH action

* Rename Workflow

* Adds lint report for CI

* Lint report fixes

* NavigationItem comments

* VerticalTabItem type fixes

* Fix avatar blur

* Fix comments

* Adding isEmbed to window object

* Disable components that use router mock.

* Load inter via google fonts

* Started select

* Adding base Breadcrumb

* Update readme

* Formatting

* Fixes

* Dependencies matching

* Linting

* Update FormStep.stories.tsx

* Linting

* Update MultiSelectCheckboxes.tsx

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-22 18:39:50 -06:00
zomars dd9adff32a Adds missing migration 2022-07-22 18:25:46 -06:00
github-actions[bot]andCrowdin Bot b8a807da59 New Crowdin translations by Github Action (#3501)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-22 23:05:42 +02:00
Pavel ShapelandPeer Richelsen 342efaed31 Refactor: use EditableHeading in event-types/[type].tsx (#3468)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-22 20:51:38 +00:00
Joe Au-Yeung 982ca3a783 Add verification json (#3505) 2022-07-22 21:25:55 +02:00
Omar Lópezandkodiakhq[bot] 9447f16b82 Migrates all tRPC code to a monorepo package (#3484)
* WIP

* WIP

* Type and migration fixes

* Adds missing default import

* Fixes import

* Fixes tRPC imports in App Store

* Migrate stripe helpers

* WIP

* Type fixes

* Type fix?

* Test fixes

* Adds missing stripe packages

* Moved queries to lib instead

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-22 11:27:06 -06:00
e82bcb6dba fixes #3336 - Avatar on New Event Type (#3465)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-22 10:04:40 -06:00
Hariom Balhara c0c0bbccf7 Add session info to Beacon (#3497) 2022-07-22 14:38:02 +00:00
Hariom Balhara e04cb8fc61 Riverside,whereby doesnt have videoApi handle that (#3494) 2022-07-22 11:52:17 +00:00
Hariom Balhara 9b5d311136 Hotfix: Embed ModalBox - Hidden Content (#3491) 2022-07-22 12:59:13 +05:30
github-actions[bot]andCrowdin Bot c2d6c30221 New Crowdin translations by Github Action (#3483)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-21 18:00:21 -06:00
Carina WollendorferandCarinaWolli 9709ee4e71 fix reschedule translation (#3487)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-07-21 16:39:39 -06:00
zomars da29b8893a Adds missing migration 2022-07-21 16:12:10 -06:00
zomars b94f78f6e1 Update labeler.yml 2022-07-21 16:12:01 -06:00
Omar López 0a125b6900 Revert "Self-hosted onboarding first admin wizard (#3393)" (#3485)
This reverts commit ee14423f4c.
2022-07-21 15:05:52 -06:00
Leo Giovanetti ee14423f4c Self-hosted onboarding first admin wizard (#3393)
* WIP

* API and step done fallback

* Finishing up tweaks

* Inline comment

* Translations
2022-07-21 19:29:24 +00:00
c93e238e15 Fixes bug that new line is not working in workflow reminder emails (#3452)
* add html format to email template

* remove workflow reminder email

* fix that text was used instead of html

* fixes that email subject was used instead of body

* remove \n\n from text template

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-21 18:56:20 +00:00
d3fcb8bf7d Team Impersonation (#3450)
* Team Impersonation

* Refactor - Disable Imeprsonate button

* Change copy

* Add .env toggle

* Fix eslint

* Update role selection

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

* New 'admin' seed user, improve flow ImpersonationProvider

* Fix impersonation string

* Adds fullstop

Co-authored-by: Sean Brydon <seanbrydon.me@gmail.ocm>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-21 17:02:20 +00:00
Hariom Balharaandzomars 471420c1d4 Add getSchedule tests (#3233)
* Add getSchedule tests

* Add first integration test

* Update turbo.json

* Make sure unit tests run asap

* Worker threads

* Improve team event test

* Remove unrelated changes

* Improve tests readability

* Update CalendarManager.ts

* Add README

* Debug tests

* Temporarily disabled build errors

* Fix failing tests

* Remove unncessary logs

Co-authored-by: zomars <zomars@me.com>
2022-07-21 10:44:23 -06:00
2eedf74eb7 Apply suggestions from code review (#3480)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2022-07-21 16:05:13 +00:00
github-actions[bot]andCrowdin Bot 5a9ca309d3 New Crowdin translations by Github Action (#3461)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-21 17:41:57 +02:00
sean-brydonandAlex van Andel 12c33ea88e Hide button if readonly (#3471)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-07-21 15:25:10 +02:00
Hariom Balhara faac59fb8e Fix TS errors and weird instances of undefined error due to prisma upgrade and turbo cache (#3475)
* Fix TS errors

* Always reexecute prisma postinstall as prisma version might change after yarn
2022-07-21 09:59:37 +00:00
Carina Wollendorfer c500952dca show error message when wrong workflow id (#3474) 2022-07-20 18:33:35 -06:00
Carina Wollendorfer 0a336df162 fix setting away status (#3473) 2022-07-20 18:32:46 -06:00
alannncandOmar López 5a0cef7fb9 fix/error-handling-with-proper-status (#3453)
* Add conditional on error handling

* Fix return type for handleError

Co-authored-by: Omar López <zomars@me.com>
2022-07-20 14:02:00 -06:00
13d13c71c6 Implementing additional feedback from Workflows PR (#3456)
* implement feedback

* put formSchema outside of react component

* don't show new workflow button for free Users

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-07-20 19:48:40 +00:00
zomars 3ff43520eb Bump prisma to v4.1 2022-07-20 13:17:23 -06:00
Omar López 4874a6e34c Upgrades prisma to v4 (#3179)
* Upgrades prisma to v4

* Upgrades prisma to v4

* Upgrades prisma to v4

* Removes preview feature as it's on stable now

* Adds missing owner relationship

* Adds missing onCascades to prevent orphans

* TODO

* Adds default date to feedback

* Adds missing foreign keys

* Update schema.prisma

* Update console

* Fixes build error

* Update console

* Delete migration.sql
2022-07-20 12:49:53 -06:00
5b7cd476a7 Feature: Routing Forms Webhook for Form Responses (#3444)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-20 18:30:57 +00:00
alannnc b47c75304e Fixed action button display while responsive and fix translations (#3469) 2022-07-20 18:12:18 +00:00
zomars be799e35d5 v1.8.1 2022-07-20 10:15:32 -06:00
zomars e35b9155d2 Submodule sync 2022-07-20 10:14:58 -06:00
Pavel ShapelandPeer Richelsen d1d467d28d Fix a typo in the server lib (#3439)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-20 13:34:17 +00:00
Ciarán Hanrahanandkodiakhq[bot] b2e5ac4cda subtle adjustment to the position of the search icon (#3440)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-20 14:47:33 +05:30
Hariom Balhara 4d2f134dc7 Hide useless windows scrollbar when they are not event required (#3458) 2022-07-20 09:57:24 +02:00
Leo Giovanettiandzomars aa166190e9 Hotfix: Cancelling recurring events follow-up (#3454)
* Other fixes and applying feedback

* Adds defaultResponder to handle zod errors

Co-authored-by: zomars <zomars@me.com>
2022-07-19 17:29:52 -06:00
0e9d754f64 Refactor cancel integrations logic (#3416)
* Refactor cancel integrations logic

* Add additional comments

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-19 16:00:01 -06:00
github-actions[bot]andCrowdin Bot 3b7f18cb5d New Crowdin translations by Github Action (#3398)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-19 21:54:38 +02:00
Leo Giovanettiandkodiakhq[bot] 63660a37f9 Cancelling recurring vs single event (#3448)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-19 19:06:23 +00:00
Hariom Balharaandkodiakhq[bot] 805419d58b Import ee from package instead of as @ee as that isnt available to api (#3446)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-19 18:48:28 +00:00
Hariom Balharaandkodiakhq[bot] 0923b83eaf Fix routing (#3445)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-19 12:36:39 -06:00
f40b035ff8 Hotfix: Unrelated bookings marked as cancelled due to recurring event (#3427)
* Fix updatedAt not updating

* Fix accidental cacnellation of all bookings if recurringEventId is null

* Update apps/web/pages/api/cancel.ts

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* Fix linting

* Linting warnings

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-07-19 11:06:43 -06:00
Hariom Balharaandkodiakhq[bot] 2b7f7a06ac Use nodeJS 14+ compatible method for replacing (#3429)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-19 18:24:19 +05:30
sean-brydon baab6ecb2e Remove Add Another for global - fix spacing (#3432) 2022-07-19 11:33:29 +02:00
alannnc 9861156edb Added optional to password to zoom response (#3422) 2022-07-18 13:02:40 -06:00
Omar López df3e3e8237 Disables batching for i18n query (#3181)
* Disables batching for i18n query

* Makes i18n batch skip explicit
2022-07-18 12:59:51 -06:00
Peer Richelsen ef36a93a55 moved impersonating alert to the top (#3420) 2022-07-18 12:08:48 -06:00
6fc3e17b47 When rescheduling update specific calendar (#3375)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-18 11:37:47 -04:00
Hariom Balharaandkodiakhq[bot] a9ad9a6a39 Ensure / is used for paths in import (#3411)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-18 13:30:05 +00:00
Peer Richelsen 1b09b7efd4 added title to forms, removed shadow-sm (#3412) 2022-07-18 12:52:50 +00:00
sean-brydon f43bfd308b Remove shallow routing (#3401) 2022-07-17 15:42:13 +05:30
Taha 4dec3fb97c Fix the path for "schema.prisma" file in README.md (#3386) 2022-07-16 13:30:58 -06:00
Syed Ali Shahbaz ec07f84d1e HOTFIX: fixes soft failing on settings/profile routes (#3394)
* fixes soft failing on settings/profile routes

* Fixed auto changed code

* Fixed auto changed code

Probably down to some random windows incompatibility with the parser/beautifier at commit time
2022-07-16 09:45:44 +02:00
martincollignonandkodiakhq[bot] 07965b2a32 Edited typo (#3391)
"Workflows enable you to automate sending reminders and notifcations." --> "Workflows enable you to automate sending reminders and notifications."

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-15 23:00:59 +00:00
alannnc 4c2f6df94d Fix possible 404 when obtaining calendar busy lists (#3345) 2022-07-15 20:28:24 +00:00
Carina WollendorferandCarinaWolli 48916cf85e fix bug with disabled save button (#3390)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-07-15 13:54:20 -06:00
github-actions[bot]andCrowdin Bot 5e81d9fb56 New Crowdin translations by Github Action (#3387)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-15 21:15:08 +02:00
zomars 77bf376c94 v1.8.0 2022-07-15 11:20:40 -06:00
zomars 200a704756 Submodule sync 2022-07-15 11:20:12 -06:00
6dd63ee3da Bug(header): profile dropdown flickering on mobile (#3071)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-07-15 11:17:54 -06:00
sean-brydonandOmar López e99b018d3b Handle fake video apps (#3232)
Co-authored-by: Omar López <zomars@me.com>
2022-07-15 19:13:31 +02:00
github-actions[bot]andCrowdin Bot 8f020bf1d0 New Crowdin translations by Github Action (#3385)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-15 11:00:37 -06:00
Peer Richelsenandkodiakhq[bot] 78e2db6d70 extracted upgrade to pro strings (#3383)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-15 10:55:59 -06:00
ff6a7bf2a6 Fixes broken avatar slug link in availability page : OG image (#3255)
* fixed broken avatar slug link

* alternate fix

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-15 10:54:14 -06:00
0433b0a69a New Crowdin translations by Github Action (#3382)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-15 18:40:39 +02:00
Peer RichelsenandOmar López ee552d8d64 purged shadow-sm (#3381)
Co-authored-by: Omar López <zomars@me.com>
2022-07-15 18:39:11 +02:00
Carina Wollendorfer dda5750bbc Add workflow setup information to README.md (#3379) 2022-07-15 09:38:09 -06:00
18389f459d minor improvements to routing forms (#3380)
* minor improvements to routing forms

* UX improvements and text changes

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-15 11:46:05 +00:00
github-actions[bot]andCrowdin Bot fd16d734fb New Crowdin translations by Github Action (#3364)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-15 12:42:58 +02:00
Hariom BalharaandOmar López 55152141ee Routing Forms - Avoid Flicker, Fix preload of Cal Font (#3368)
* Fix flicker due to Shell on Routing Form pages. Fix font preload(unrealted to routing forms)

* Update _app.tsx

Co-authored-by: Omar López <zomars@me.com>
2022-07-15 05:47:37 +00:00
75b817531e Fixes timezone in reminder templates (workflows) (#3376)
* fix time zone in reminder template

* fix timezone in email subject

* add missing timezone

* fix date in sms reminder template

* use organizer time zone if message send to host

* fix build error

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-07-14 19:05:01 -04:00
zomars 9162d90504 Restores old CI config 2022-07-14 15:47:09 -06:00
Carina WollendorferandOmar López bf2843c17e Create cron-scheduleSMSReminders.yml (#3373)
Co-authored-by: Omar López <zomars@me.com>
2022-07-14 15:22:40 -06:00
Carina Wollendorfer 6a28396499 create cron-scheduleEmailReminders (#3374) 2022-07-14 15:12:18 -06:00
zomars 1a0a318a55 Revert "Adds custom rewrite logic for hosted cal.com"
This reverts commit 9e5b1f121f.

# Conflicts:
#	apps/web/next.config.js
2022-07-14 14:56:00 -06:00
zomars 99b821edcd Update LicenseRequired.tsx 2022-07-14 14:48:50 -06:00
Peer Richelsen ded0b1410a Update LicenseRequired.tsx 2022-07-14 14:49:52 +02:00
58d1c28e9d Routing Forms (#2785)
* Add Routing logic to Query builder

* Make a working redirect

* Make it an app

* Move pages and components to App

* Integrate all pages in the app

* Integrate prisma everywhere

* Fix Routing Link

* Add routing preview

* Fixes

* Get deplouyed on preview with ts disabled

* Fix case

* add reordering for routes

* Move away from react DnD

* Add sidebar

* Add sidebar support and select support

* Various fixes and improvements

* Ignore eslint temporarly

* Route might be falsy

* Make CalNumber support required validation

* Loader improvements

* Add SSR support

* Fix few typescript issues

* More typesafety, download csv, bug fiees

* Add seo friendly link

* Avoid seding credebtials to frontend

* Self review fixes

* Improvements in app-store

* Cahnge Form layout

* Add scaffolding for app tests

* Add playwright tests and add user check in serving data

* Add CI tests

* Add route builder test

* Styling

* Apply suggestions from code review

Co-authored-by: Agusti Fernandez Pardo <6601142+agustif@users.noreply.github.com>

* Changes as per loom feedback

* Increase time for tests

* Fix PR suggestions

* Import CSS only in the module

* Fix codacy issues

* Move the codebbase to ee and add PRO and license check

* Add Badge

* Avoid lodash import

* Fix TS error

* Fix lint errors

* Fix bug to merge conflicts resolution - me query shouldnt cause the Shell to go in loading state

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Agusti Fernandez Pardo <6601142+agustif@users.noreply.github.com>
2022-07-14 12:40:53 +00:00
Peer Richelsen 7ec5f01647 kbar follow up (#3361)
* added more routes to kbar

* added right direction for tooltip, moved search icon next to logo, added keyboard shortcuts to command bar

* added right direction for tooltip, moved search icon next to logo, added keyboard shortcuts to command bar

* fixed search icon for tablet

* fixed search icon for mobile

* hide keyboard shortcut legend on mobile

* extracted strings
2022-07-14 11:32:28 +00:00
github-actions[bot]andCrowdin Bot 6f073762fa New Crowdin translations by Github Action (#3362)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-14 13:12:56 +02:00
Hariom Balhara ebd4750f2d Improves UI stability of availability page. No CLS now. (#3347) 2022-07-14 10:28:46 +00:00
b1e4c23adc Fixes label to set Riverside link (#3358)
* fix label for riverside link

* add missing space

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-14 07:14:55 +00:00
github-actions[bot]andCrowdin Bot 03b8643d1e New Crowdin translations by Github Action (#3360)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-14 09:03:59 +02:00
318ec316d5 Adds a fully extensible command(ctrl)+k interface for Cal (#3346)
* init

* action function added

* kbar trigger placed

* UI improvements to KBar, added command + K tooltip

* renamed quick find to commandbar

* replaced window router with nextjs router

* keyboard up n down nav shows up

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-14 06:45:07 +00:00
bf7c86ffe5 Fix Caldav not valid destination calendar and only display calendars that user can edit (#3326)
* Default readOnly to false

* Display only calendars users can edit

* Remove console.log

* Remove console.log

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-13 18:16:24 -06:00
33e36e5320 Only make web3 call if credential exists (#3323)
* Add has web3 credential

* Address feedback

* Address feedback

* Fix e2e test

* Remove unused code

* Remove unused import

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-13 18:14:39 -06:00
39199e515e Workflows (#3236)
* build basic database structure and basic design

* create simple workflow list

* add editing dots to list

* add mutation to create workflows

* add createMutation on submit + redirect to editing page

* redirect to edit page when clicking on row

* add functionality to delete workflow

* add timeUnit + input validation

* add empty screen view

* add time before it triggers to description

* add multi select with checkboxes

* remove getServerSideProps

* set default time period to 24

* fetch eventypes and display in dropdown

* add functionality to update workflows + many-to-many relationship

* fix all checked event types

* add SMS reminders

* fix bug with trigger + relocate sms template

* clean code

* add model for unscheduled reminders

* fix selected eventTypes

* fixing value to show how many event types selected

* fix plural of event types in select

* add onDelete cascade for all relations

* fix errors

* add functionality to send SMS to specific number

* fix type error for timeUnit

* set default value for time unit + fix type issues

* remove console.logs

* fix error in checking if scheduled date is more than 1h in advance

* fix build errors

* add migration for workflows

* add basic UI for editing workflow steps

* add formSchema

* improve functionality to update a step

* remove console logs

* fix issue with active event types

* allow null value for time and timeUnit

* sort steps asc step number

* add action to workflow (frontend)

* add  phone number input for SMS to specific number

* use PhoneInput for number input + input validation

* improve invalid input for phone number

* improve UI of phoneInput

* Improve design and validation

* fix undefined error

* set default action when adding action

* include all team event types

* fix phone number input for editing steps

* fix update muation to add steps

* remove console logs

* fix order of steps

* functionality to delete steps

* add trigger when event is cancelled

* add custom email body

* sms and email reminder updates

* add custom emails

* add custom email subject

* send reminder email to all attendees

* update migration

* fix default value for time and timeUnit

* save email reminders to database

* clean code

* add custom template to SMS actions

* schedule emails with sendgrid

* clean code

* add workflow templates

* keep custom template saved when changing templates

* create reminder template for email

* add dot at the end of sentace for email template

* fix merge error

* fix issue that template was not saved

* include sending emails for when event is cancelled

* fix bug that email was always sent

* add templates to sms reminders

* add info that sending sms to attendees won't trigger for already exisitng bookings

* only schedule sms for attendees when smsReminderNumber exists

* only schedule sms for attendees when smsReminderNumber exists

* set scheduled of workflow reminder to false when longer than 72 hours

* add cron for email scheduling + fixes for for sms an email scheduling

* adjust step number when deleting a step

* cast to boolean with !!

* update cron job for email reminders

* update sms template

* send reminder email not to guests

* remove sendTo from workflow reminder

* fixes sending sms without name + removing sendTo everywhere

* fix undefined name in sms template

* set user name to undefined for sending sms to a specific number

* fix singular and plural for time unit

* set to edit mode when changing action and custom template is selected

* delete reminders when booking cancelled or not active anymore

* fix type errors

* fix error that deleted reminders twice

* create booking reminders for existing bookings when eventType is set active

* improve email and sms templates

* use BookingInfo type instead of calendarEvent for reminder emails

* schedule emails for already existing bookings

* add and remove reminders for new active event types and cancelled events

* connect add action button with last step

* fix step container width for mobile view

* helper functions that return options for select

* fix typo and remove comment

* clean code

* add/improve error messages for forms

* fix typo

* clean code

* improve email template

* clean code

* fix missing prop

* save reference id when scheduling reminder

* fix step not added because of changed id for new steps

* small fixes + code cleanup

* code cleanup

* show error message when number is invalid

* fix typo

* fix phone number input when location is already phone

* set multi select checkbox to read only

* change email scheduling in cron job from 7 days to 72 hours

* show active event types in workflow list

* fix trigger information for workflow list

* improve layout for small screens in workflow list

* remove optional from zod type for workflow name

* order workflows by id

* use link icon to show active event types

* fix plural and add translation for showing nr of active eventtypes

* fix text for sms reminder template

* add reminders for added steps

* remove optional for activeOn

* improve reminder templates

* improve design of custom input fields

* set edit mode to false when phone number isn't needed anymore

* set sendTo in workflow step only for SMS_NUMBER action

* set email body and subject only when custom template

* only delete reminders that belong to workflow steps

* improve text for new event book trigger

* move reminders folder to workflows

* fix issue that save button was sometimes enabled in edit mode

* fix form issues for send to

* delete all scheduled reminders when workflow is deleted

* use enum for method

* fix imports for workflow methods

* add missing import

* fix edit mode

* create reminders when event is confirmed

* add reminderScheduler to reduce duplicate code

* make workflow enterprise and pro only feature

* move all files to /ee/ folder

* move package.json change to /ee/ folder

* add pro badge to shell

* set to edit mode to true if email subject is missing when action changes

* fix loading bug

* add migration

* fix old imports

* don't schedule reminders for opt-ins

* fix style of email body

* code clean up

* Update yarn.lock

* fix isLoading for active on dropdown

* update import for prisma

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

* update imports

* remove console

* use session to check if user has valid license

* use defaultHandler

* clean up code

* Create db-staging-snapshot.yml

* move LisenceRequired inside shell

* update import for FormValues

* fix phone input design

* fix disabled save button for edit mode

* squah all migration into a single one

* use isAfter and isBefore instead of isBetween

* import dayjs from @calcom

* validate phone number for sms reminders when booking event

* Allows auto approvals for crowdin

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-07-14 00:10:45 +00:00
alannnc 44b8cedef0 Fix/main zoom schema parsing (#3275)
* zod zoom parsing only required fields

* Change logger for console.error
2022-07-13 18:01:00 -06:00
zomars c7d220e998 Preventing technical debt to break linting 2022-07-13 17:57:54 -06:00
zomars 78f0b095ae Update submodule-sync.yml 2022-07-13 16:17:28 -06:00
zomars 9a0b1832c3 Update submodule-sync.yml 2022-07-13 16:13:29 -06:00
github-actions[bot]andCrowdin Bot c8ef235916 New Crowdin translations by Github Action (#3356)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-13 23:57:20 +02:00
zomars 9760a2606e Submodule sync 2022-07-13 15:15:40 -06:00
zomars e916c67415 Linting fixes 2022-07-13 15:14:16 -06:00
zomars f6684c001c Fixes lint:fix script 2022-07-13 15:11:40 -06:00
zomars 9e5b1f121f Adds custom rewrite logic for hosted cal.com 2022-07-13 15:03:09 -06:00
zomars 179da11f4b Submodule sync 2022-07-12 16:32:31 -06:00
zomars 4bbca06bea Linting 2022-07-12 16:31:09 -06:00
zomars c5263efce2 Adds missing linting scripts 2022-07-12 16:31:09 -06:00
zomars 578c3d64ef Enables react/self-closing-comp lint rule 2022-07-12 16:31:09 -06:00
sean-brydon dbca46d677 Fixing 500 on slack OAuth (#3342) 2022-07-12 20:33:46 +00:00
zomars 1f953c5ca0 Update lint.yml 2022-07-12 14:15:06 -06:00
zomars a677ffb2a5 Update lint.yml 2022-07-12 14:11:29 -06:00
zomars 0ff6d4557a Fixing lint annotations 2022-07-12 14:05:15 -06:00
zomars 7bcc5c4548 eslint react/jsx-curly-brace-presence 2022-07-12 11:52:13 -06:00
zomars dbfc1477aa Formatting 2022-07-12 11:52:13 -06:00
Leo Giovanettiandkodiakhq[bot] b1d637d1a6 Upgrading nextjs and collect (#3252)
* Upgrading nextjs and collect

* Fixing suggestions + yarn.lock

* Updating submodules

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-12 11:43:53 -06:00
Peer Richelsen c3c7f82841 hide back button for embeds (#3339) 2022-07-12 14:33:12 +00:00
8d40e7cfd2 HotFix - Embed Flicker for newly converted static availability page (#3277)
* Add missing embed fingerprint

* Fix embed flicker for Availability page

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-07-12 14:18:53 +00:00
46266781b5 Update calendar when new attendees are added to bookings w/ seats (#3257)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-07-12 09:16:34 -04:00
894 changed files with 40392 additions and 18945 deletions
+8
View File
@@ -88,3 +88,11 @@ VITAL_REGION="us"
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
ZAPIER_INVITE_LINK=""
# *********************************************************************************************************
# - LARK
# Needed to enable Lark Calendar integration and Login with Lark
# @see <https://open.larksuite.com/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/g>
LARK_OPEN_APP_ID=""
LARK_OPEN_APP_SECRET=""
LARK_OPEN_VERIFICATION_TOKEN=""
# *********************************************************************************************************
+16 -2
View File
@@ -21,7 +21,7 @@ CALCOM_LICENSE_KEY=
# ***********************************************************************************************************
# - DATABASE ************************************************************************************************
# ⚠️ ⚠️ ⚠️ DATABASE_URL got moved to `packages/prisma/.env.example` ⚠️ ⚠️ ⚠️
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
# ***********************************************************************************************************
# - SHARED **************************************************************************************************
@@ -52,7 +52,7 @@ NEXTAUTH_URL=
# You can use: `openssl rand -base64 32` to generate one
NEXTAUTH_SECRET=
# Used for cross-domain cookie authentication
NEXTAUTH_COOKIE_DOMAIN=.example.com
NEXTAUTH_COOKIE_DOMAIN=
# Set this to '1' if you don't want Cal to collect anonymous usage
CALCOM_TELEMETRY_DISABLED=
@@ -77,6 +77,17 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
# Inbox to send user feedback
SEND_FEEDBACK_EMAIL=
# Sengrid
# Used for email reminders in workflows
SENDGRID_API_KEY=
SENDGRID_EMAIL=
# Twilio
# Used to send SMS reminders in workflows
TWILIO_SID=
TWILIO_TOKEN=
TWILIO_MESSAGING_SID=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally
NEXT_PUBLIC_IS_E2E=
@@ -116,3 +127,6 @@ EMAIL_SERVER_PASSWORD='<office365_password>'
## @see https://support.google.com/accounts/answer/185833
# EMAIL_SERVER_PASSWORD='<gmail_app_password>'
# **********************************************************************************************************
# Set the following value to true if you wish to enable Team Impersonation
NEXT_PUBLIC_TEAM_IMPERSONATION=false
@@ -0,0 +1,36 @@
# .github/workflows/chromatic.yml
# Workflow name
name: 'Chromatic'
# Event for the workflow
on:
pull_request_target: # So we can test on forks
branches:
- main
paths:
- apps/storybook/**
- packages/ui/**
workflow_dispatch:
# List of jobs
jobs:
chromatic-deployment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 0
- name: Install dependencies
run: yarn
# 👇 Adds Chromatic as a step in the workflow
- name: Publish to Chromatic
uses: chromaui/action@v1
# Options required to the GitHub Chromatic Action
with:
# 👇 Chromatic projectToken, refer to the manage page to obtain it.
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
workingDir: "apps/storybook"
buildScriptName: "build"
+1 -1
View File
@@ -1,6 +1,6 @@
name: Check types
on:
pull_request:
pull_request_target:
branches:
- main
paths:
@@ -0,0 +1,20 @@
name: Cleanup Preview Databases
env:
SNAPLET_ACCESS_TOKEN: ${{ secrets.SNAPLET_ACCESS_TOKEN }}
SNAPLET_PROJECT_ID: cl4u26bwz7962859ply7ibuo43t
on:
pull_request:
types: [closed]
branches:
- main
jobs:
delete:
runs-on: ubuntu-latest
steps:
- uses: snaplet/action@v1
if: ${{ env.SNAPLET_ACCESS_TOKEN && env.SNAPLET_PROJECT_ID }}
with:
delete: true
@@ -0,0 +1,23 @@
name: Cron - scheduleEmailReminders
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
jobs:
cron-scheduleEmailReminders:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/workflows/scheduleEmailReminders \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
@@ -0,0 +1,23 @@
name: Cron - scheduleSMSReminders
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
jobs:
cron-scheduleSMSReminders:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/workflows/scheduleSMSReminders \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
+99
View File
@@ -0,0 +1,99 @@
name: E2E App-Store Apps
on:
push:
branches: [ feature/event-routing ]
pull_request_target: # So we can test on forks
branches:
- main
paths-ignore:
- apps/api/**
- apps/console/**
- apps/docs/**
- apps/swagger/**
- apps/website/**
- apps/web/public/**
jobs:
test:
timeout-minutes: 20
name: E2E App-Store Apps
strategy:
matrix:
node: ["16.x"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
env:
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
NEXT_PUBLIC_WEBAPP_URL: http://localhost:3000
NEXT_PUBLIC_WEBSITE_URL: http://localhost:3000
NEXTAUTH_SECRET: secret
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: true
# CRON_API_KEY: xxx
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
PAYMENT_FEE_PERCENTAGE: 0.005
PAYMENT_FEE_FIXED: 10
SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
SAML_ADMINS: pro@example.com
NEXTAUTH_URL: http://localhost:3000/api/auth
NEXT_PUBLIC_IS_E2E: 1
# EMAIL_FROM: e2e@cal.com
# EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
# EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
# EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
# EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD }}
# MS_GRAPH_CLIENT_ID: xxx
# MS_GRAPH_CLIENT_SECRET: xxx
# ZOOM_CLIENT_ID: xxx
# ZOOM_CLIENT_SECRET: xxx
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
services:
postgres:
image: postgres:12.1
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
ports:
- 5432:5432
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 2
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: "yarn"
- name: Cache playwright binaries
uses: actions/cache@v2
id: playwright-cache
with:
path: |
~/Library/Caches/ms-playwright
~/.cache/ms-playwright
${{ github.workspace }}/node_modules/playwright
key: cache-playwright-${{ hashFiles('**/yarn.lock') }}
restore-keys: cache-playwright-
- run: yarn --frozen-lockfile
- name: Install playwright deps
# if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Run Tests
run: yarn app-e2e-quick
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-core
path: packages/app-store/**/playwright/results
+1 -1
View File
@@ -1,6 +1,6 @@
name: E2E Embed tests and booking flow(for non-embed as well)
on:
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
# Embed e2e - tests verify booking flow which is applicable to non-embed case also. So, don't ignore apps/web changes.
+1 -1
View File
@@ -1,6 +1,6 @@
name: E2E test
on:
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
paths:
+3
View File
@@ -10,5 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
# Checking the actor will prevent your Action run failing on non-Dependabot
# PRs but also ensures that it only does work for Dependabot PRs.
if: ${{ github.actor == 'github-actions[bot]' }}
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
+4 -3
View File
@@ -1,6 +1,6 @@
name: Lint
on:
pull_request:
pull_request_target:
branches:
- main
paths:
@@ -35,10 +35,11 @@ jobs:
run: jq -s '[.[]]|flatten' lint-results/*.json &> lint-results/eslint_report.json
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@1.2.0
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
repo-token: ${{ secrets.GITHUB_TOKEN }}
report-json: "lint-results/eslint_report.json"
only-pr-files: false
- name: Upload ESLint report
if: ${{ always() }}
+7 -8
View File
@@ -3,7 +3,6 @@ on:
schedule:
- cron: "15 */4 * * *"
workflow_dispatch: ~
jobs:
submodule-sync:
name: Submodule update
@@ -11,11 +10,11 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: run action
uses: releasehub-com/github-action-create-pr-parent-submodule@v1
with:
github_token: ${{ secrets.GH_ACCESS_TOKEN }}
parent_repository: "calcom/cal.com"
checkout_branch: "main"
pr_against_branch: "main"
owner: "calcom"
submodules: recursive
token: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Commit
run: |
git config user.email "actions@github.com"
git config user.name "github-actions"
git commit -am "Auto updated submodule references" && git push || echo "No changes to commit"
+4
View File
@@ -2,6 +2,7 @@
# .env file
.env
!packages/prisma/.env
# dependencies
node_modules
@@ -73,6 +74,9 @@ dist
# Linting
lint-results
#Storybook
apps/storybook/build-storybook.log
# Snaplet
.snaplet/snapshots
.snaplet/structure.d.ts
-4
View File
@@ -22,10 +22,6 @@
"group": {
"kind": "build",
"isDefault": true
},
// Try start the task on folder open
"runOptions": {
"runOn": "folderOpen"
}
},
{
+1 -1
View File
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
Portions of this software are licensed as follows:
* All content that resides under "apps/web/ee/" (https://github.com/calcom/cal.com/tree/main/apps/web/ee) directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE".
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE".
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
+60 -10
View File
@@ -26,6 +26,7 @@
<p align="center">
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://status.cal.com"><img src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=26817795"><img src="https://img.shields.io/badge/Hacker%20News-311-%23FF6600" alt="Hacker News"></a>
<a href="https://github.com/calcom/cal.com/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
@@ -36,6 +37,8 @@
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
<a href="https://twitter.com/calcom"><img src="https://img.shields.io/twitter/follow/calcom?style=flat"></a>
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
<a href="https://github.com/orgs/calcom/projects/1/views/25"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
<a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
</p>
@@ -84,7 +87,7 @@ To get a local copy up and running, please follow these simple steps.
Here is what you need to be able to run Cal.
- Node.js (Version: >=14.x <17)
- Node.js (Version: >=15.x <17)
- PostgreSQL
- Yarn _(recommended)_
@@ -133,7 +136,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
#### Manual setup
1. Configure environment variables in the `packages/prisma/.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
```
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
@@ -167,7 +170,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
</details>
1. Set a 32 character random string in your .env file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `apps/web/prisma/schema.prisma`)
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
```sh
yarn workspace @calcom/prisma db-deploy
@@ -189,7 +192,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
1. Click on the `User` model to add a new user record.
1. Fill out the fields `email`, `username`, `password`, and set `metadata` to empty `{}` (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user.
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `apps/web/prisma/schema.prisma` file.
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `packages/prisma/schema.prisma` file.
1. Open a browser to [http://localhost:3000](http://localhost:3000) and login with your just created, first user.
### E2E-Testing
@@ -260,12 +263,16 @@ yarn workspace @calcom/web playwright-report
### Docker
The Docker configuration for Cal is an effort powered by people within the community. Cal.com, Inc. does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
The Docker configuration for Cal is an effort powered by people within the community.
If you want to contribute to the Docker repository, [reply here](https://github.com/calcom/docker/discussions/32).
The Docker configuration can be found [in our docker repository](https://github.com/calcom/docker).
Issues with Docker? Find your answer or open a new discussion [here](https://github.com/calcom/docker/discussions) to ask the community.
Cal.com, Inc. does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
### Heroku
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
@@ -274,20 +281,21 @@ The Docker configuration can be found [in our docker repository](https://github.
### Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fcalendso%2Fcalendso&plugins=postgresql&envs=GOOGLE_API_CREDENTIALS%2CBASE_URL%2CNEXTAUTH_URL%2CPORT&BASE_URLDefault=http%3A%2F%2Flocalhost%3A3000&NEXTAUTH_URLDefault=http%3A%2F%2Flocalhost%3A3000&PORTDefault=3000)
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/cal)
You can deploy Cal on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal on their platform.
### Vercel
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com&env=DATABASE_URL,NEXT_PUBLIC_WEBAPP_URL,NEXTAUTH_SECRET,NEXTAUTH_URL,CALENDSO_ENCRYPTION_KEY,NEXT_PUBLIC_LICENSE_CONSENT,CALCOM_LICENSE_KEY&envDescription=See%20all%20available%20env%20vars&envLink=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com%2Fblob%2Fmain%2F.env.example&project-name=cal&repo-name=cal.com)
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com&env=DATABASE_URL,NEXT_PUBLIC_WEBAPP_URL,NEXTAUTH_URL,NEXTAUTH_SECRET,CRON_API_KEY,CALENDSO_ENCRYPTION_KEY,NEXT_PUBLIC_LICENSE_CONSENT&envDescription=See%20all%20available%20env%20vars&envLink=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com%2Fblob%2Fmain%2F.env.example&project-name=cal&repo-name=cal.com&build-command=cd%20../..%20%26%26%20yarn%20build&root-directory=apps%2Fweb%2F)
<!-- ROADMAP -->
## Roadmap
<a href="https://cal.com/roadmap"><img src="https://user-images.githubusercontent.com/8019099/176388295-25081ca4-207e-4468-8079-37b195fa8e59.png" alt="Cal.com Roadmap" /></a>
<a href="https://cal.com/roadmap"><img src="https://user-images.githubusercontent.com/8019099/176388295-25081ca4-207e-4468-8079-37b195fa8e59.png" alt="Cal.com Roadmap" /></a>
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.
@@ -299,7 +307,7 @@ Please see our [contributing guide](/CONTRIBUTING.md).
### Good First Issues
We have a list of [good first issues](https://github.com/calcom/cal.com/labels/✅%20good%20first%20issue) that contain bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
We have a list of [help wanted](https://github.com/orgs/calcom/projects/1/views/25) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
## Integrations
@@ -317,6 +325,23 @@ We have a list of [good first issues](https://github.com/calcom/cal.com/labels/
10. The key will be created and you will be redirected back to the Credentials page. Select the newly generated client ID under OAuth 2.0 Client IDs.
11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the .env file as the value for GOOGLE_API_CREDENTIALS key.
#### _Adding google calendar to Cal.com App Store_
After adding Google credentials, you can now Google Calendar App to the app store.
You can repopulate the App store by running
```
cd packages/prisma
yarn seed-app-store
```
You will need to complete a few more steps to activate Google Calendar App.
Make sure to complete section "Obtaining the Google API Credentials". After the do the
following
1. Add extra redirect URL `<Cal.com URL>/api/auth/callback/google`
1. Under 'OAuth concent screen', click "PUBLISH APP"
### Obtaining Microsoft Graph Client ID and Secret
1. Open [Azure App Registration](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and select New registration
@@ -425,6 +450,31 @@ Next make sure you have your app running `yarn dx`. Then in the slack chat type
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
### Setting up SendGrid for Email reminders
1. Create a SendGrid account (https://signup.sendgrid.com/)
2. Go to Settings -> API keys and create an API key
3. Copy API key to your .env file into the SENDGRID_API_KEY field
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
### Setting up Twilio for SMS reminders
1. Create a Twilio account (https://www.twilio.com/try-twilio)
2. Click ‘Get a Twilio phone number’
3. Copy Account SID to your .env file into the TWILIO_SID field
4. Copy Auth Token to your .env file into the TWILIO_TOKEN field
5. Create a messaging service (Develop -> Messaging -> Services)
6. Choose any name for the messaging service
7. Click 'Add Senders'
8. Choose phone number as sender type
9. Add the listed phone number
10. Leave all other fields as they are
11. Complete setup and click ‘View my new Messaging Service’
12. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field
<!-- LICENSE -->
## License
+22 -21
View File
@@ -1,4 +1,5 @@
# Security
Contact: security@cal.com
Based on [https://supabase.com/.well-known/security.txt](https://supabase.com/.well-known/security.txt)
@@ -9,30 +10,30 @@ If you discover a vulnerability, we would like to know about it so we can take s
## Out of scope vulnerabilities:
* Clickjacking on pages with no sensitive actions.
* Unauthenticated/logout/login CSRF.
* Attacks requiring MITM or physical access to a user's device.
* Any activity that could lead to the disruption of our service (DoS).
* Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS.
* Email spoofing
* Missing DNSSEC, CAA, CSP headers
* Lack of Secure or HTTP only flag on non-sensitive cookies
* Deadlinks
- Clickjacking on pages with no sensitive actions.
- Unauthenticated/logout/login CSRF.
- Attacks requiring MITM or physical access to a user's device.
- Any activity that could lead to the disruption of our service (DoS).
- Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS.
- Email spoofing
- Missing DNSSEC, CAA, CSP headers
- Lack of Secure or HTTP only flag on non-sensitive cookies
- Deadlinks
## Please do the following:
* E-mail your findings to [security@cal.com](mailto:security@cal.com).
* Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you.
* Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
* Do not reveal the problem to others until it has been resolved,
* Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties,
* Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation.
- E-mail your findings to [security@cal.com](mailto:security@cal.com).
- Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you.
- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
- Do not reveal the problem to others until it has been resolved,
- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties,
- Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation.
## What we promise:
* We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date,
* If you have followed the instructions above, we will not take any legal action against you in regard to the report,
* We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission,
* We will keep you informed of the progress towards resolving the problem,
* In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and
* We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved.
- We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date,
- If you have followed the instructions above, we will not take any legal action against you in regard to the report,
- We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission,
- We will keep you informed of the progress towards resolving the problem,
- In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and
- We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved.
+59 -4
View File
@@ -10,18 +10,73 @@
}
],
"env": {
"BASE_URL": {
"PGSSLMODE": {
"description": "If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then put 'no-verify' here.",
"value": "no-verify"
},
"NEXT_PUBLIC_WEBAPP_URL": {
"description": "Replace HEROKU_APP_NAME with the name given to your app",
"value": "https://HEROKU_APP_NAME.herokuapp.com"
"value": "https://HEROKU_APP_NAME.herokuapp.com",
"required": "true"
},
"CALENDSO_ENCRYPTION_KEY": {
"description": "Application Key for symmetric encryption and decryption. Must be 32 bytes for AES256 encryption algorithm.",
"value": "secret"
"value": "secret",
"required": "true"
},
"NEXTAUTH_SECRET": "secret",
"NEXTAUTH_URL": {
"description": "Replace HEROKU_APP_NAME with the name given to your app",
"value": "https://HEROKU_APP_NAME.herokuapp.com"
},
"NEXTAUTH_SECRET": {
"description": "Cookie encryption key",
"generator": "secret",
"required": "true"
},
"NEXT_PUBLIC_LICENSE_CONSENT": {
"description": "License consent",
"value": "true",
"required": "true"
},
"CRON_API_KEY": {
"description": "ApiKey for cronjobs",
"value": ""
},
"SEND_FEEDBACK_EMAIL": {
"description": "Send feedback email",
"value": ""
},
"SENDGRID_API_KEY": {
"description": "Sendgrid api key. Used for email reminders in workflows",
"value": ""
},
"SENDGRID_EMAIL": {
"description": "Sendgrid email. Used for email reminders in workflows",
"value": ""
},
"EMAIL_FROM": {
"description": "Configures the global From: header whilst sending emails",
"value": ""
},
"EMAIL_SERVER_HOST": {
"description": "Configures the global SMTP server host",
"value": "smtp.gmail.com"
},
"EMAIL_SERVER_PORT": {
"description": "Configures the global SMTP server port",
"value": "465"
},
"EMAIL_SERVER_USER": {
"description": "Configures the global SMTP server user",
"value": "<gmail_emailAddress>"
},
"EMAIL_SERVER_PASSWORD": {
"description": "Configures the global SMTP server password",
"value": "<gmail_app_password>"
},
"NEXT_PUBLIC_TEAM_IMPERSONATION": {
"description": "Set the following value to true if you wish to enable Team Impersonation",
"value": "false"
}
},
"scripts": {
+8 -7
View File
@@ -4,26 +4,27 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "next build",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"dev": "PORT=4000 next",
"lint": "next lint",
"type-check": "tsc --pretty --noEmit",
"lint": "eslint . --ignore-path .gitignore",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
"lint:report": "eslint . --format json --output-file ../../lint-results/docs.json",
"start": "PORT=4000 next start",
"build": "next build"
"type-check": "tsc --pretty --noEmit"
},
"author": "Cal.com, Inc.",
"license": "MIT",
"dependencies": {
"iframe-resizer-react": "^1.1.0",
"next": "^12.1.6",
"next": "^12.2.5",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^18.1.0",
"react-dom": "^18.1.0"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@calcom/config": "*",
"eslint": "^8.15.0"
"eslint": "^8.20.0"
}
}
+2 -2
View File
@@ -10,10 +10,10 @@ We use a number of style guides written by other amazing companies, simply becau
We don't expect you to study every single rule of each style guide, but these are great reference points as to how your code should be styled. We may reject a pull request if your code style significantly differs from these style guides however.
## ESLint & Prettier
Calendso uses the ESLint and Prettier formatting tools, and the repository comes with defined rules for each tool. We recommend setting up both tools and using these to help automatically style your code to our guidelines.
Cal.com uses the ESLint and Prettier formatting tools, and the repository comes with defined rules for each tool. We recommend setting up both tools and using these to help automatically style your code to our guidelines.
## JavaScript/TypeScript
We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for all JavaScript and Typescript code.
We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for all JavaScript and TypeScript code.
## HTML & CSS
We use the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) for any HTML and CSS markup. However, exceptions to the HTML guide apply where JSX differentiates from standard HTML.
+1 -2
View File
@@ -34,7 +34,6 @@ Here is what you need to be able to run Cal.
```sh
cp apps/web/.env.example apps/web/.env
cp packages/prisma/.env.example packages/prisma/.env
```
1. Install packages with yarn
@@ -54,7 +53,7 @@ yarn dx
### Manual setup
1. Configure database in the `packages/prisma/.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
1. Configure database in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
```text
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
+37
View File
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
storybook-static/
+57
View File
@@ -0,0 +1,57 @@
const path = require("path");
module.exports = {
stories: ["../stories/**/*.stories.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-addon-designs",
"@storybook/addon-a11y",
"storybook-addon-next",
],
framework: "@storybook/react",
core: {
builder: "webpack5",
},
webpackFinal: async (config) => {
/**
* Fixes font import with /
* @see https://github.com/storybookjs/storybook/issues/12844#issuecomment-867544160
*/
config.resolve.roots = [path.resolve(__dirname, "../public"), "node_modules"];
/**
* Why webpack5... Just why?
* @type {{console: boolean, process: boolean, timers: boolean, os: boolean, querystring: boolean, sys: boolean, fs: boolean, url: boolean, crypto: boolean, path: boolean, zlib: boolean, punycode: boolean, util: boolean, stream: boolean, assert: boolean, string_decoder: boolean, domain: boolean, vm: boolean, tty: boolean, http: boolean, buffer: boolean, constants: boolean, https: boolean, events: boolean}}
*/
config.resolve.fallback = {
fs: false,
assert: false,
buffer: false,
console: false,
constants: false,
crypto: false,
domain: false,
events: false,
http: false,
https: false,
os: false,
path: false,
punycode: false,
process: false,
querystring: false,
stream: false,
string_decoder: false,
sys: false,
timers: false,
tty: false,
url: false,
util: false,
vm: false,
zlib: false,
};
return config;
},
};
@@ -0,0 +1,8 @@
<script>
window.isEmbed = ()=> {
return location.search.includes("embed=")
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500&display=swap');
</style>
+24
View File
@@ -0,0 +1,24 @@
import { RouterContext } from "next/dist/shared/lib/router-context";
import * as NextImage from "next/image";
import "../styles/globals.css";
const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, "default", {
configurable: true,
value: (props) => <OriginalNextImage {...props} unoptimized />,
});
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
},
};
+21
View File
@@ -0,0 +1,21 @@
# Storybook & UI
Storybook is home to all of our commonly used components. We use this app to visually show all components that we have within the project + automatic type documentation generated for each component.
All changes to storybook/ui must require a visual/code review.
- Visual reviews happen on a tool called [Chormatic](http://chromatic.com/) and these reviews will happen by our product team.
- Code reviews happen as normal on any changes to storybook/ui designs
## Deployment
To deploy this project run
```bash
cd apps/storybook
```
```bash
yarn dev
```
Vendored Executable
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+7
View File
@@ -0,0 +1,7 @@
const withTM = require("next-transpile-modules")(["@calcom/dayjs", "@calcom/ui"]);
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = withTM(nextConfig);
+63
View File
@@ -0,0 +1,63 @@
{
"name": "@calcom/storybook",
"version": "0.1.0",
"private": true,
"scripts": {
"dev:next": "next dev",
"build:next": "next build",
"start": "next start",
"lint": "eslint . --ignore-path .gitignore",
"lint:report": "eslint . --format json --output-file ../../lint-results/storybook.json",
"dev": "start-storybook -p 6006",
"build": "build-storybook",
"chromatic": "npx chromatic --project-token=56fadc9ab496"
},
"dependencies": {
"@calcom/dayjs": "*",
"@calcom/ui": "*",
"@radix-ui/react-avatar": "^0.1.4",
"@radix-ui/react-collapsible": "^0.1.0",
"@radix-ui/react-dialog": "^0.1.0",
"@radix-ui/react-dropdown-menu": "^0.1.1",
"@radix-ui/react-id": "^0.1.0",
"@radix-ui/react-radio-group": "^0.1.1",
"@radix-ui/react-slider": "^0.1.1",
"@radix-ui/react-switch": "^0.1.1",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^12.2.5",
"next-transpile-modules": "^9.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-hot-toast": "^2.3.0"
},
"devDependencies": {
"@babel/core": "^7.18.9",
"@calcom/config": "*",
"@storybook/addon-a11y": "^6.5.9",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-interactions": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.9",
"@storybook/manager-webpack5": "^6.5.9",
"@storybook/react": "^6.5.9",
"@types/node": "16.9.1",
"@types/react": "^18.0.9",
"@types/react-dom": "18.0.4",
"autoprefixer": "^10.4.7",
"babel-loader": "^8.2.5",
"chromatic": "^6.7.1",
"eslint": "^8.20.0",
"postcss": "^8.4.13",
"postcss-loader": "^7.0.0",
"storybook-addon-designs": "^6.3.1",
"storybook-addon-next": "^1.6.7",
"tailwindcss": "^3.1.6",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.7.4"
},
"readme": "ERROR: No README data found!",
"_id": "@calcom/storybook@0.1.0"
}
+9
View File
@@ -0,0 +1,9 @@
import type { AppProps } from "next/app";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
+63
View File
@@ -0,0 +1,63 @@
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
const Home: NextPage = () => {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing <code className={styles.code}>pages/index.tsx</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a href="https://github.com/vercel/next.js/tree/canary/examples" className={styles.card}>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}>
<h2>Deploy &rarr;</h2>
<p>Instantly deploy your Next.js site to a public URL with Vercel.</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer">
Powered by{" "}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
);
};
export default Home;
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+26
View File
@@ -0,0 +1,26 @@
import { ComponentMeta } from "@storybook/react";
import Avatar from "@calcom/ui/v2/Avatar";
export default {
title: "Avatar",
component: Avatar,
} as ComponentMeta<typeof Avatar>;
export const Default = () => {
return (
<>
<Avatar size="sm" alt="Avatar Story" gravatarFallbackMd5="Ui@CAL.com" />
<Avatar size="lg" alt="Avatar Story" gravatarFallbackMd5="Ui@CAL.com" />
</>
);
};
export const Accepted = () => {
return (
<>
<Avatar size="sm" alt="Avatar Story" gravatarFallbackMd5="Ui@CAL.com" accepted />
<Avatar size="lg" alt="Avatar Story" gravatarFallbackMd5="Ui@CAL.com" accepted />
</>
);
};
@@ -0,0 +1,60 @@
import { ComponentMeta } from "@storybook/react";
import Avatar from "@calcom/ui/v2/Avatar";
import AvatarGroup from "@calcom/ui/v2/AvatarGroup";
export default {
title: "Avatar/Group",
component: AvatarGroup,
} as ComponentMeta<typeof AvatarGroup>;
const IMAGES = [
{
image: "https://cal.com/stakeholder/peer.jpg",
alt: "Peer",
title: "Peer Richelsen",
},
{
image: "https://cal.com/stakeholder/bailey.jpg",
alt: "Bailey",
title: "Bailey Pumfleet",
},
{
image: "https://cal.com/stakeholder/alex-van-andel.jpg",
alt: "Alex",
title: "Alex Van Andel",
},
{
image: "https://cal.com/stakeholder/ciaran.jpg",
alt: "Ciarán",
title: "Ciarán Hanrahan",
},
{
image: "https://cal.com/stakeholder/peer.jpg",
alt: "Peer",
title: "Peer Richelsen",
},
{
image: "https://cal.com/stakeholder/bailey.jpg",
alt: "Bailey",
title: "Bailey Pumfleet",
},
{
image: "https://cal.com/stakeholder/alex-van-andel.jpg",
alt: "Alex",
title: "Alex Van Andel",
},
{
image: "https://cal.com/stakeholder/ciaran.jpg",
alt: "Ciarán",
title: "Ciarán Hanrahan",
},
];
export const Default = () => {
return (
<>
<AvatarGroup size="lg" items={IMAGES} />
</>
);
};
+58
View File
@@ -0,0 +1,58 @@
import { ComponentMeta } from "@storybook/react";
import { Bell } from "react-feather";
import { Badge } from "@calcom/ui/v2";
export default {
title: "Badge",
component: Badge,
} as ComponentMeta<typeof Badge>;
export const All = () => (
<div className="">
<h1>Default</h1>
<div className="mb-4 flex space-x-2">
<Badge variant="gray">Badge</Badge>
<Badge variant="red">Badge</Badge>
<Badge variant="green">Badge</Badge>
<Badge variant="orange">Badge</Badge>
<Badge variant="blue">Badge</Badge>
</div>
<h1>Icons</h1>
<div className="mb-4 flex space-x-2">
<Badge variant="gray" StartIcon={Bell}>
Badge
</Badge>
<Badge variant="red" StartIcon={Bell}>
Badge
</Badge>
<Badge variant="green" StartIcon={Bell}>
Badge
</Badge>
<Badge variant="orange" StartIcon={Bell}>
Badge
</Badge>
<Badge variant="blue" StartIcon={Bell}>
Badge
</Badge>
</div>
<h1>Large</h1>
<div className="flex space-x-2">
<Badge variant="gray" size="lg">
Badge
</Badge>
<Badge variant="red" size="lg">
Badge
</Badge>
<Badge variant="green" size="lg">
Badge
</Badge>
<Badge variant="orange" size="lg">
Badge
</Badge>
<Badge variant="blue" size="lg">
Badge
</Badge>
</div>
</div>
);
+46
View File
@@ -0,0 +1,46 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Info } from "react-feather";
import { Banner } from "@calcom/ui/v2";
export default {
title: "Banner",
component: Banner,
} as ComponentMeta<typeof Banner>;
export const Default = () => {
return (
<Banner
variant="neutral"
title="Summarise what happened"
description="Describe what can be done about it here."
Icon={Info}
onDismiss={() => console.log("dismissed")}
/>
);
};
export const Warning = () => {
return (
<Banner
variant="warning"
title="Summarise what happened"
description="Describe what can be done about it here."
Icon={Info}
onDismiss={() => console.log("dismissed")}
/>
);
};
export const Error = () => {
return (
<Banner
variant="error"
title="Summarise what happened"
description="Describe what can be done about it here."
errorMessage="Event creation failed"
Icon={Info}
onDismiss={() => console.log("dismissed")}
/>
);
};
@@ -0,0 +1,22 @@
import { ComponentMeta } from "@storybook/react";
import { Breadcrumb, BreadcrumbItem } from "@calcom/ui/v2";
export default {
title: "Breadcrumbs",
component: Breadcrumb,
} as ComponentMeta<typeof Breadcrumb>;
export const Default = () => (
<Breadcrumb>
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem href="/">Test</BreadcrumbItem>
</Breadcrumb>
);
Default.parameters = {
nextRouter: {
path: "/test",
asPath: "/test",
},
};
+95
View File
@@ -0,0 +1,95 @@
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { Trash2 } from "react-feather";
import { Button as ButtonComponent } from "@calcom/ui/v2";
export default {
title: "Button",
component: ButtonComponent,
argTypes: {
color: {
options: ["primary", "secondary", "minimal", "destructive"],
control: { type: "select" },
},
disabled: {
options: [true, false],
control: { type: "boolean" },
},
loading: {
options: [true, false],
control: { type: "boolean" },
},
size: {
options: ["base", "lg", "icon"],
control: { type: "radio" },
},
},
} as ComponentMeta<typeof ButtonComponent>;
const Template: ComponentStory<typeof ButtonComponent> = (args) => <ButtonComponent {...args} />;
export const All = () => (
<div>
<h1>Primary</h1>
<div className="flex space-x-2">
<ButtonComponent aria-label="Button Text">Button Text</ButtonComponent>
<ButtonComponent disabled aria-label="Button Text">
Button Text
</ButtonComponent>
</div>
<h1>Secondary</h1>
<div className="flex space-x-2">
<ButtonComponent color="secondary" aria-label="Button Text">
Button Text
</ButtonComponent>
<ButtonComponent disabled color="secondary" aria-label="Button Text">
Button Text
</ButtonComponent>
<ButtonComponent size="icon" color="secondary" StartIcon={Trash2} aria-label="Button Text" />
</div>
<h1>Minimal</h1>
<div className="flex">
<ButtonComponent color="minimal" aria-label="Button Text">
Button Text
</ButtonComponent>
<ButtonComponent disabled color="minimal" aria-label="Button Text">
Button Text
</ButtonComponent>
<ButtonComponent size="icon" color="minimal" StartIcon={Trash2} aria-label="Button Text" />
</div>
<h1>Destructive</h1>
<ButtonComponent size="icon" color="destructive" StartIcon={Trash2} aria-label="Button Text" />
<h1>Tooltip</h1>
<ButtonComponent
tooltip="Deletes EventTypes"
size="icon"
color="destructive"
StartIcon={Trash2}
aria-label="Button Text"
/>
</div>
);
export const Button = Template.bind({});
Button.args = {
color: "primary",
children: "Button Text",
};
export const Disabled = Template.bind({});
Disabled.args = {
...Button.args,
disabled: true,
};
export const Loading = Template.bind({});
Loading.args = {
...Button.args,
loading: true,
};
export const Icon = Template.bind({});
Icon.args = {
color: "secondary",
StartIcon: Trash2,
size: "icon",
};
@@ -0,0 +1,31 @@
import { ComponentMeta } from "@storybook/react";
import { ArrowLeft, ArrowRight, Clipboard, Navigation2, Trash2 } from "react-feather";
import { Button, ButtonGroup } from "@calcom/ui/v2";
export default {
title: "Button Group",
component: ButtonGroup,
} as ComponentMeta<typeof ButtonGroup>;
export const Default = () => (
<ButtonGroup>
<Button StartIcon={Trash2} size="icon" color="secondary" />
<Button StartIcon={Navigation2} size="icon" color="secondary" />
<Button StartIcon={Clipboard} size="icon" color="secondary" />
</ButtonGroup>
);
export const Combined = () => (
<div className="flex flex-col space-y-2">
<ButtonGroup combined>
<Button StartIcon={Trash2} size="icon" color="secondary" combined />
<Button StartIcon={Navigation2} size="icon" color="secondary" combined />
<Button StartIcon={Clipboard} size="icon" color="secondary" combined />
</ButtonGroup>
<ButtonGroup combined>
<Button StartIcon={ArrowLeft} size="icon" color="secondary" combined />
<Button StartIcon={ArrowRight} size="icon" color="secondary" combined />
</ButtonGroup>
</div>
);
+53
View File
@@ -0,0 +1,53 @@
import { ComponentMeta } from "@storybook/react";
import { BaseCardProps, Card } from "@calcom/ui/v2/Card";
export default {
title: "Cards",
component: Card,
} as ComponentMeta<typeof Card>;
const profileProps: BaseCardProps[] = [
{
variant: "ProfileCard",
title: "Alex Fisher",
image:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
description: "Co-founder at Attio. Book a meeting to discuss enterprise deals.",
},
{
variant: "ProfileCard",
title: "Julian Wan",
image:
"https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80",
description: "Co-founder at Attio. Book a meeting to discuss enterprise deals.",
},
];
export const Profile = () => {
return (
<div className="flex w-full space-x-2 bg-gray-200 p-4">
<Card {...profileProps[0]} />
<Card {...profileProps[1]} />
</div>
);
};
const appStoreProps: BaseCardProps = {
variant: "AppStore",
title: "Giphy",
image: "https://app.cal.com/api/app-store/giphy/icon.svg",
description: "Add GIFs to spice up your booking confirmations.",
actionButton: {
href: "#",
child: "View Details",
},
};
export const AppStore = () => {
return (
<div className="w-full bg-gray-200 p-4">
<Card {...appStoreProps} />
</div>
);
};
@@ -0,0 +1,31 @@
import { ComponentMeta } from "@storybook/react";
import { Checkbox } from "@calcom/ui/v2";
export default {
title: "Checkbox",
component: Checkbox,
} as ComponentMeta<typeof Checkbox>;
export const All = () => (
<div className="flex flex-col space-y-2">
<Checkbox label="Default" description="Toggle on and off something" />
<Checkbox label="Error" description="Toggle on and off something" error />
<Checkbox label="Disabled" description="Toggle on and off something" disabled />
<Checkbox label="Disabled Checked" description="Toggle on and off something" checked disabled />
<hr />
<Checkbox description="Default" descriptionAsLabel />
<Checkbox description="Error" descriptionAsLabel error />
<Checkbox description="Disabled" descriptionAsLabel disabled />
<Checkbox description="Disabled" descriptionAsLabel disabled checked />
</div>
);
export const CheckboxField = () => <Checkbox description="Default" descriptionAsLabel />;
export const CheckboxError = () => <Checkbox description="Error" descriptionAsLabel />;
export const CheckboxDisabled = () => (
<>
<Checkbox description="Disabled" descriptionAsLabel disabled />
<Checkbox description="Disabled" descriptionAsLabel disabled checked />
</>
);
+208
View File
@@ -0,0 +1,208 @@
import { useState } from "react";
import ColorPicker from "@calcom/ui/v2/colorpicker";
// eslint-disable-next-line import/no-anonymous-default-export
export default {
title: "Colors",
component: ColorPicker,
};
const COLORS = {
brand: {
50: "#f3f3f4",
100: "#e7e8e9",
200: "#c4c5c9",
300: "#a0a3a9",
400: "#585d68",
500: "#111827", // Brand color
600: "#0f1623",
700: "#0d121d",
800: "#0a0e17",
900: "#080c13",
},
gray: {
50: "#F8F8F8",
100: "#F5F5F5",
200: "#E1E1E1",
300: "#CFCFCF",
400: "#ACACAC",
500: "#888888",
600: "#494949",
700: "#3E3E3E",
800: "#313131",
900: "#292929",
},
neutral: {
50: "#F8F8F8",
100: "#F5F5F5",
200: "#E1E1E1",
300: "#CFCFCF",
400: "#ACACAC",
500: "#888888",
600: "#494949",
700: "#3E3E3E",
800: "#313131",
900: "#292929",
},
primary: {
50: "#F4F4F4",
100: "#E8E8E8",
200: "#C6C6C6",
300: "#A3A3A3",
400: "#5F5F5F",
500: "#1A1A1A",
600: "#171717",
700: "#141414",
800: "#101010",
900: "#0D0D0D",
},
secondary: {
50: "#F5F8F7",
100: "#EBF0F0",
200: "#CDDAD9",
300: "#AEC4C2",
400: "#729894",
500: "#356C66",
600: "#30615C",
700: "#28514D",
800: "#20413D",
900: "#223B41",
},
red: {
50: "#FEF2F2",
100: "#FEE2E2",
200: "#FECACA",
300: "#FCA5A5",
400: "#F87171",
500: "#EF4444",
600: "#DC2626",
700: "#B91C1C",
800: "#991B1B",
900: "#7F1D1D",
},
orange: {
50: "#FFF7ED",
100: "#FFEDD5",
200: "#FED7AA",
300: "#FDBA74",
400: "#FB923C",
500: "#F97316",
600: "#EA580C",
700: "#C2410C",
800: "#9A3412",
900: "#7C2D12",
},
green: {
50: "#ECFDF5",
100: "#D1FAE5",
200: "#A7F3D0",
300: "#6EE7B7",
400: "#34D399",
500: "#10B981",
600: "#059669",
700: "#047857",
800: "#065F46",
900: "#064E3B",
},
};
export const All = () => {
return (
<div className="w-full">
<div>
{Object.keys(COLORS.brand).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.brand[color]}>
<div className="w-full">Brand</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.brand[color]}</div>
<div
style={{
backgroundColor: COLORS.brand[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
<hr />
{Object.keys(COLORS.gray).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.gray[color]}>
<div className="w-full">gray</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.gray[color]}</div>
<div
style={{
backgroundColor: COLORS.gray[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
<hr />
{Object.keys(COLORS.secondary).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.secondary[color]}>
<div className="w-full">secondary</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.secondary[color]}</div>
<div
style={{
backgroundColor: COLORS.secondary[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
<hr />
{Object.keys(COLORS.red).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.red[color]}>
<div className="w-full">red</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.red[color]}</div>
<div
style={{
backgroundColor: COLORS.red[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
<hr />
{Object.keys(COLORS.orange).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.orange[color]}>
<div className="w-full">orange</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.orange[color]}</div>
<div
style={{
backgroundColor: COLORS.orange[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
<hr />
{Object.keys(COLORS.green).map((color) => (
<div className="flex flex-row space-x-2" key={COLORS.green[color]}>
<div className="w-full">green</div>
<div className="w-full">{color}</div>
<div className="w-full">{COLORS.green[color]}</div>
<div
style={{
backgroundColor: COLORS.green[color],
width: "100%",
height: "32px",
}}
/>
</div>
))}
</div>
</div>
);
};
export const ColorPickerComponent = () => {
const [color, setColor] = useState("3B82F6");
return <ColorPicker defaultValue={color} onChange={(val) => setColor(val)} />;
};
@@ -0,0 +1,17 @@
import { useState } from "react";
import DatePicker from "@calcom/ui/v2/booker/DatePicker";
export default {
title: "Datepicker",
component: DatePicker,
};
export const Default = () => {
const [selected, setSelected] = useState<Date | undefined>(undefined);
return (
<div style={{ width: "455px" }}>
<DatePicker selected={selected} onChange={setSelected} locale="en" />
</div>
);
};
@@ -0,0 +1,26 @@
import { ComponentMeta } from "@storybook/react";
import { Bell } from "react-feather";
import { EmptyScreen as EmptyScreenPattern } from "@calcom/ui/v2";
export default {
title: "pattern/Empty Screen",
component: EmptyScreenPattern,
decorators: [
(Story) => (
<div className="flex items-center justify-center">
<Story />
</div>
),
],
} as ComponentMeta<typeof EmptyScreenPattern>;
export const EmptyScreenS = () => (
<EmptyScreenPattern
Icon={Bell}
headline="Empty State Header"
description="Ullamco dolor nulla sint nulla occaecat aliquip id elit fugiat et excepteur magna. Nisi tempor anim do tempor irure fugiat ad occaecat. Mollit ea eiusmod pariatur sunt deserunt eu eiusmod. Sit reprehenderit cupidatat consequat commodo in aliqua ea et. Et quis sit enim proident dolor mollit consectetur tempor dolore reprehenderit consequat adipisicing reprehenderit officia. Sint eu sunt fugiat laborum Lorem irure aute nulla et. Do non in enim ipsum ea."
buttonText="Veniam ut ipsum"
buttonOnClick={() => console.log("Button Clicked")}
/>
);
@@ -0,0 +1,27 @@
import { ComponentMeta } from "@storybook/react";
import { useState } from "react";
import { FormStep } from "@calcom/ui/v2";
export default {
title: "Form Step",
component: FormStep,
} as ComponentMeta<typeof FormStep>;
export const Default = () => {
const STEPS = 4;
const [currentStep, setCurrentStep] = useState(1);
return (
<div className="flex flex-col items-center justify-center space-y-14 p-20">
<div className="w-1/2">
<FormStep steps={STEPS} currentStep={currentStep} />
<div className="flex space-x-2 pt-4">
<button onClick={() => currentStep - 1 > 0 && setCurrentStep((old) => old - 1)}>Previous</button>
<button onClick={() => currentStep + 1 < STEPS + 1 && setCurrentStep((old) => old + 1)}>
Next
</button>
</div>
</div>
</div>
);
};
@@ -0,0 +1,30 @@
// Disabling until we figure out what is happening with the RouterMock.
import { ComponentMeta } from "@storybook/react";
import { HorizontalTabs } from "@calcom/ui/v2/navigation/tabs";
import { HorizontalTabItemProps } from "@calcom/ui/v2/navigation/tabs/HorizontalTabItem";
export default {
title: "Horizontal Tabs",
component: HorizontalTabs,
} as ComponentMeta<typeof HorizontalTabs>;
const HorizontalTabsPropsDefault: HorizontalTabItemProps[] = [
{
name: "Tab One",
href: "/tab-one",
},
];
export const Default = () => (
<div className="w-full p-4" style={{ backgroundColor: "#F9FAFB" }}>
<HorizontalTabs tabs={HorizontalTabsPropsDefault} />
</div>
);
Default.parameters = {
nextRouter: {
path: "/[page]",
asPath: "/profile",
},
};
+68
View File
@@ -0,0 +1,68 @@
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { Copy } from "react-feather";
import DatePicker from "@calcom/ui/v2/form/DatePicker";
import { TextAreaField, TextField } from "@calcom/ui/v2/form/fields";
export default {
title: "Inputs",
component: TextField,
argTypes: {
disabled: {
options: [false, true],
},
},
} as ComponentMeta<typeof TextField>;
const TextInputTemplate: ComponentStory<typeof TextField> = (args) => <TextField {...args} />;
// name="demo" label="Demo Label" hint="Hint text"
export const TextInput = TextInputTemplate.bind({});
TextInput.args = {
name: "demo",
label: "Demo Label",
hint: "Hint Text",
disabled: false,
};
export const TextInputPrefix = TextInputTemplate.bind({});
TextInputPrefix.args = {
name: "demo",
label: "Demo Label",
hint: "Hint Text",
addOnLeading: "https://",
disabled: false,
};
export const TextInputSuffix = TextInputTemplate.bind({});
TextInputSuffix.args = {
name: "demo",
label: "Demo Label",
hint: "Hint Text",
addOnSuffix: "Minutes",
disabled: false,
};
export const TextInputPrefixIcon = TextInputTemplate.bind({});
TextInputPrefixIcon.args = {
name: "demo",
label: "Demo Label",
hint: "Hint Text",
addOnFilled: false,
addOnSuffix: <Copy />,
disabled: false,
};
export const TextInputSuffixIcon = TextInputTemplate.bind({});
TextInputSuffixIcon.args = {
name: "demo",
label: "Demo Label",
hint: "Hint Text",
addOnFilled: false,
addOnLeading: <Copy />,
disabled: false,
};
export const TextAreaInput: ComponentStory<typeof TextAreaField> = () => (
<TextAreaField name="Text-area-input" label="Text Area" />
);
export const DatePickerInput: ComponentStory<typeof DatePicker> = () => <DatePicker date={new Date()} />;
+52
View File
@@ -0,0 +1,52 @@
import { useState } from "react";
import { Info } from "react-feather";
import { Button } from "@calcom/ui/v2";
import { Dialog, DialogContent, DialogTrigger } from "@calcom/ui/v2/Dialog";
import { TextField } from "@calcom/ui/v2/form/fields";
export default {
title: "pattern/Modal",
component: Dialog,
};
export const Creation = () => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
</DialogTrigger>
<DialogContent
title="Header"
description="Optional Description"
type="creation"
actionText="Create"
actionOnClick={() => setOpen(false)}>
<TextField name="Label" />
<TextField name="Label" />
<TextField name="Label" />
<TextField name="Label" />
</DialogContent>
</Dialog>
);
};
export const Confirmation = () => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Open Modal</Button>
</DialogTrigger>
<DialogContent
title="Header"
description="Optional Description"
type="confirmation"
actionText="Confirm"
Icon={Info}
actionOnClick={() => setOpen(false)}
/>
</Dialog>
);
};
@@ -0,0 +1,25 @@
import { ComponentMeta } from "@storybook/react";
import { Toaster } from "react-hot-toast";
import { Button } from "@calcom/ui/v2";
import showToast from "@calcom/ui/v2/notfications";
export default {
title: "Notifcations-Toasts",
decorators: [
(Story) => (
<>
<Story />
<Toaster />
</>
),
],
} as ComponentMeta<typeof All>; // We have to fake this type as the story for this component isn't really a component.
export const All = () => (
<div className="flex space-x-2">
<Button onClick={() => showToast("This is a Neutral toast", "warning")}>Neutral</Button>
<Button onClick={() => showToast("This is a Success toast", "success")}>Success</Button>
<Button onClick={() => showToast("This is a Error toast", "error")}>Error</Button>
</div>
);
@@ -0,0 +1,52 @@
import { ComponentMeta } from "@storybook/react";
import { Search } from "react-feather";
import { Button, PageHeader } from "@calcom/ui/v2";
import { TextField } from "@calcom/ui/v2/form";
export default {
title: "pattern/Page Header",
component: PageHeader,
decorators: [
(Story) => (
<div className="">
<Story />
</div>
),
],
} as ComponentMeta<typeof PageHeader>;
export const Default = () => (
<PageHeader title="Title" description="Some description about the header above" />
);
export const ButtonRight = () => (
<PageHeader
title="Title"
description="Some description about the header above"
rightAlignedComponent={<Button>Button Text</Button>}
/>
);
export const ComingSoon = () => (
<PageHeader
title="Title"
description="Some description about the header above"
badgeText="Coming Soon"
badgeVariant="gray"
/>
);
export const SearchInstalledApps = () => (
<PageHeader
title="Search booking"
description="See upcoming and past events booking through your event type link"
rightAlignedComponent={
<TextField
containerClassName="h-9 max-h-9"
addOnLeading={<Search />}
name="search"
labelSrOnly
placeholder="WIP"
/>
}
/>
);
+19
View File
@@ -0,0 +1,19 @@
import * as Radio from "@calcom/ui/v2/form/radio-area/Radio";
import { RadioField } from "@calcom/ui/v2/form/radio-area/Radio";
export default {
title: "Radio",
component: RadioField,
};
export const RadioGroupDemo = () => {
return (
<form>
<Radio.Group aria-label="View density" defaultValue="default">
<RadioField label="Default" id="r1" value="1" />
<RadioField label="Next" id="r2" value="2" />
<RadioField label="Disabled" id="r3" disabled value="1" />
</Radio.Group>
</form>
);
};
+10
View File
@@ -0,0 +1,10 @@
import { ComponentMeta } from "@storybook/react";
import { Select } from "@calcom/ui/v2";
export default {
title: "Form/Select",
component: Select,
} as ComponentMeta<typeof Select>;
export const Single = () => <Select items={[{ value: "Test Test" }]} />;
+15
View File
@@ -0,0 +1,15 @@
import { ComponentMeta } from "@storybook/react";
import { Switch } from "@calcom/ui/v2";
export default {
title: "Switch",
component: Switch,
} as ComponentMeta<typeof Switch>;
export const All = () => (
<div className="flex flex-col space-y-2">
<p>Checked works in app but storybook doesnt like it</p>
<Switch label="Default" />
</div>
);
@@ -0,0 +1,67 @@
// Disabling until we figure out what is happening with the RouterMock.
import { ComponentMeta } from "@storybook/react";
import { Link } from "react-feather";
import { VerticalTabItem } from "@calcom/ui/v2";
export default {
title: "VerticalTabItem",
component: VerticalTabItem,
} as ComponentMeta<typeof VerticalTabItem>;
const TabItemProps = {
name: "Event Setup",
icon: Link,
href: "/settings/event",
info: "60 mins, Zoom",
};
// export const Default = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem {...TabItemProps}></VerticalTabItem>
// </div>
// );
// export const Active = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem {...TabItemProps}></VerticalTabItem>
// </div>
// );
// Mocking next router to show active state
// Active.parameters = {
// nextRouter: {
// path: "/settings/[tab]",
// asPath: "/settings/event",
// },
// };
// export const Disabled = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem {...TabItemProps} disabled></VerticalTabItem>
// </div>
// );
// export const NoIconNoInfo = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem name="Event Setup" href="/settings/event"></VerticalTabItem>
// </div>
// );
// export const IconNoInfo = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem name="Event Setup" href="/settings/event" icon={Link}></VerticalTabItem>
// </div>
// );
// export const IconNoInfoActive = () => (
// <div className="h-20 w-full bg-gray-100 p-4">
// <VerticalTabItem name="Event Setup" href="/settings/events" icon={Link}></VerticalTabItem>
// </div>
// );
// IconNoInfoActive.paramaters = {
// nextRouter: {
// path: "/settings/[tab]",
// asPath: "/settings/events",
// },
// };
@@ -0,0 +1,85 @@
// Disabling until we figure out what is happening with the RouterMock.
import { ComponentMeta } from "@storybook/react";
import { Calendar, Clock, Grid, Link, RefreshCw, User, Users } from "react-feather";
import { VerticalTabs } from "@calcom/ui/v2/navigation/tabs";
export default {
title: "Vertical Tabs",
component: VerticalTabs,
} as ComponentMeta<typeof VerticalTabs>;
const VerticalTabsPropsDefault = [
{
name: "Security",
icon: Link,
href: "/security",
},
{
name: "Profile",
icon: User,
href: "/profile",
},
{
name: "Calendar Sync",
icon: RefreshCw,
href: "/cal-sync",
},
{
name: "Teams",
icon: Users,
href: "/Teams",
},
];
export const Default = () => (
<div className="w-full p-4" style={{ backgroundColor: "#F9FAFB" }}>
<VerticalTabs tabs={VerticalTabsPropsDefault} />
</div>
);
Default.parameters = {
nextRouter: {
path: "/[page]",
asPath: "/profile",
},
};
const VerticalTabsPropsInfo = [
{
name: "Event Setup",
icon: Link,
href: "/events",
info: "60 Mins, Zoom",
},
{
name: "Availability",
icon: Calendar,
href: "/availability",
info: "Working Hours",
},
{
name: "Limits",
icon: Clock,
href: "/limits",
info: "Buffers, Limits & more...",
},
{
name: "Apps",
icon: Grid,
href: "/apps",
info: "3 apps - 0 active",
},
];
export const Info = () => (
<div className="w-full p-4" style={{ backgroundColor: "#F9FAFB" }}>
<VerticalTabs tabs={VerticalTabsPropsInfo} />
</div>
);
Info.parameters = {
nextRouter: {
path: "/[page]",
asPath: "/events",
},
};
+11
View File
@@ -0,0 +1,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--brand-color: #292929;
--brand-text-color: #ffffff;
--brand-color-dark-mode: #fafafa;
--brand-text-color-dark-mode: #292929;
font-family: 'Inter', sans-serif;
}
+167
View File
@@ -0,0 +1,167 @@
module.exports = {
content: ["../../packages/ui/**/*.{js,ts,jsx,tsx}", "./stories/**/*.{js,ts,tsx,jsx}"],
darkMode: "class",
theme: {
fontFamily: {
cal: ["Cal Sans", "sans-serif"],
mono: ["Roboto Mono", "monospace"],
},
extend: {
colors: {
/* your primary brand color */
brandcontrast: "var(--brand-text-color)",
darkmodebrand: "var(--brand-color-dark-mode)",
darkmodebrandcontrast: "var(--brand-text-color-dark-mode)",
bookinglightest: "var(--booking-lightest-color)",
bookinglighter: "var(--booking-lighter-color)",
bookinglight: "var(--booking-light-color)",
bookingmedian: "var(--booking-median-color)",
bookingdark: "var(--booking-dark-color)",
bookingdarker: "var(--booking-darker-color)",
bookinghighlight: "var(--booking-highlight-color)",
black: "#111111",
brand: {
// Figure out a way to automate this for self hosted users
// Goto https://javisperez.github.io/tailwindcolorshades to generate your brand color
50: "#d1d5db",
100: "#9ca3af",
200: "#6b7280",
300: "#4b5563",
400: "#374151",
500: "#111827", // Brand color
600: "#0f1623",
700: "#0d121d",
800: "#0a0e17",
900: "#080c13",
DEFAULT: "#111827",
},
gray: {
50: "#F9FAFB",
100: "#F3F4F6",
200: "#E5E7EB",
300: "#D1D5DB",
400: "#9CA3AF",
500: "#6B7280",
600: "#4B5563",
700: "#374151",
800: "#1F2937",
900: "#111827",
},
neutral: {
50: "#F8F8F8",
100: "#F5F5F5",
200: "#E1E1E1",
300: "#CFCFCF",
400: "#ACACAC",
500: "#888888",
600: "#494949",
700: "#3E3E3E",
800: "#313131",
900: "#292929",
},
primary: {
50: "#F4F4F4",
100: "#E8E8E8",
200: "#C6C6C6",
300: "#A3A3A3",
400: "#5F5F5F",
500: "#1A1A1A",
600: "#171717",
700: "#141414",
800: "#101010",
900: "#0D0D0D",
},
secondary: {
50: "#F5F8F7",
100: "#EBF0F0",
200: "#CDDAD9",
300: "#AEC4C2",
400: "#729894",
500: "#356C66",
600: "#30615C",
700: "#28514D",
800: "#20413D",
900: "#223B41",
},
red: {
50: "#FFF5F5",
100: "#FFE3E2",
200: "#FFC9C9",
300: "#FEA8A8",
400: "#FF8787",
500: "#FF6B6B",
600: "#FA5352",
700: "#F03E3F",
800: "#E03130",
900: "#C92B2B",
},
orange: {
50: "#FFF4E5",
100: "#FFE8CC",
200: "#FFD8A8",
300: "#FFBF78",
400: "#FFA94E",
500: "#FF922B",
600: "#FD7E14",
700: "#F76706",
800: "#E8580C",
900: "#D94810",
},
green: {
50: "#EBFCEE",
100: "#D2F9D9",
200: "#B1F2BA",
300: "#8CE99A",
400: "#69DB7C",
500: "#51CF66",
600: "#40C057",
700: "#36B24D",
800: "#2F9E44",
900: "#2B8A3E",
},
blue: {
50: "#E7F5FF",
100: "#D0EBFF",
200: "#A4D8FF",
300: "#74C0FC",
400: "#4DABF7",
500: "#339AF0",
600: "#238BE6",
700: "#1C7ED7",
800: "#1971C2",
900: "#1763AB",
},
},
fontFamily: {
cal: ["inter", "sans-serif"],
mono: ["Roboto Mono", "monospace"],
},
maxHeight: (theme) => ({
0: "0",
97: "25rem",
...theme("spacing"),
full: "100%",
screen: "100vh",
}),
minHeight: (theme) => ({
0: "0",
...theme("spacing"),
full: "100%",
screen: "100vh",
}),
minWidth: (theme) => ({
0: "0",
...theme("spacing"),
full: "100%",
screen: "100vw",
}),
maxWidth: (theme, { breakpoints }) => ({
0: "0",
...theme("spacing"),
...breakpoints(theme("screens")),
full: "100%",
screen: "100vw",
}),
},
},
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "@calcom/tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"compilerOptions": {
"noImplicitAny": false,
"baseUrl": "."
}
}
+11 -7
View File
@@ -3,23 +3,27 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "PORT=4200 next dev",
"build": "next build",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"dev": "PORT=4200 next dev",
"lint": "eslint . --ignore-path .gitignore",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
"lint:report": "eslint . --format json --output-file ../../lint-results/swagger.json",
"start": "PORT=4200 next start"
},
"dependencies": {
"highlight.js": "^11.5.1",
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "12.1.6",
"next": "^12.2.5",
"openapi-snippet": "^0.13.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"swagger-ui-react": "4.11.1"
},
"devDependencies": {
"@types/node": "16.9.1",
"@types/react": "18.0.9",
"@types/react": "^18.0.9",
"@types/react-dom": "18.0.4",
"typescript": "4.6.4"
"typescript": "^4.7.4"
}
}
+8 -15
View File
@@ -1,24 +1,17 @@
{
"extends": "@calcom/tsconfig/nextjs.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": [
"next-env.d.ts",
"../../packages/types/*.d.ts",
"../../packages/types/next-auth.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules"]
}
+3 -3
View File
@@ -1,7 +1,7 @@
import { XIcon } from "@heroicons/react/outline";
import { useState } from "react";
import { useLocale } from "@lib/hooks/useLocale";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
export default function AddToHomescreen() {
const { t } = useLocale();
@@ -40,7 +40,7 @@ export default function AddToHomescreen() {
type="button"
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>
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
<Icon.FiX className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</div>
@@ -4,11 +4,11 @@ import { OptionProps } from "react-select";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { trpc } from "@lib/trpc";
interface AdditionalCalendarSelectorProps {
isLoading?: boolean;
@@ -45,12 +45,12 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
const options = data.items.map((item) => ({
label: item.name,
slug: item.slug,
image: item.imageSrc,
image: item.logo,
type: item.type,
}));
return (
<Select
name={"additionalCalendar"}
name="additionalCalendar"
placeholder={t("connect_additional_calendar")}
options={options}
styles={{
@@ -73,7 +73,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
},
}}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 font-medium text-gray-700 sm:text-sm"
className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 text-sm font-medium text-gray-700"
isLoading={isLoading}
components={{ Option: ImageOption }}
/>
+246 -197
View File
@@ -1,26 +1,19 @@
import {
BookOpenIcon,
CheckIcon,
DocumentTextIcon,
ExternalLinkIcon,
FlagIcon,
MailIcon,
PlusIcon,
ShieldCheckIcon,
} from "@heroicons/react/outline";
import { ChevronLeftIcon } from "@heroicons/react/solid";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton } from "@calcom/app-store/components";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Button, SkeletonButton } from "@calcom/ui";
import Badge from "@calcom/ui/Badge";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import Shell from "@components/Shell";
import Badge from "@components/ui/Badge";
export default function App({
const Component = ({
name,
type,
logo,
@@ -36,25 +29,19 @@ export default function App({
email,
tos,
privacy,
}: {
name: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
body: React.ReactNode;
categories: string[];
author: string;
pro?: boolean;
price?: number;
commission?: number;
feeType?: AppType["feeType"];
docs?: string;
website?: string;
email: string; // required
tos?: string;
privacy?: string;
}) {
isProOnly,
}: Parameters<typeof App>[0]) => {
const { t } = useLocale();
const { data: user } = trpc.useQuery(["viewer.me"]);
const mutation = useAddAppMutation(null, {
onSuccess: () => {
showToast("App successfully installed", "success");
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || "App could not be installed", "error");
},
});
const priceInDollar = Intl.NumberFormat("en-US", {
style: "currency",
@@ -90,176 +77,238 @@ export default function App({
getInstalledApp(type);
}, [type]);
const allowedMultipleInstalls = categories.indexOf("calendar") > -1;
return (
<>
<Shell large isPublic>
<div className="-mx-4 md:-mx-8">
<div className="bg-gray-50 px-8">
<Link href="/apps">
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800">
<ChevronLeftIcon className="h-5 w-5" /> {t("browse_apps")}
</a>
</Link>
<div className="items-center justify-between py-4 sm:flex sm:py-8">
<div className="flex">
<img className="h-16 w-16 rounded-sm" src={logo} alt={name} />
<header className="px-4 py-2">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
<h2 className="text-sm text-gray-500">
<span className="capitalize">{categories[0]}</span> • {t("published_by", { author })}
</h2>
</header>
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{!isLoading ? (
isGlobal || (installedAppCount > 0 && allowedMultipleInstalls) ? (
<div className="space-x-3">
<Button StartIcon={CheckIcon} color="secondary" disabled>
{installedAppCount > 0
? t("active_install", { count: installedAppCount })
: t("globally_install")}
</Button>
<InstallAppButton
type={type}
render={(buttonProps) => (
<Button StartIcon={PlusIcon} data-testid="install-app-button" {...buttonProps}>
{t("add_another")}
</Button>
)}
/>
</div>
) : (
return (
<div className="-mx-4 md:-mx-8">
<div className="bg-gray-50 px-8">
<Link href="/apps">
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("browse_apps")}
</a>
</Link>
<div className="items-center justify-between py-4 sm:flex sm:py-8">
<div className="flex">
<img className="h-16 w-16 rounded-sm" src={logo} alt={name} />
<header className="px-4 py-2">
<div className="flex items-center">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
{isProOnly && user?.plan === "FREE" ? (
<Badge className="ml-2" variant="default">
PRO
</Badge>
) : null}
</div>
<h2 className="text-sm text-gray-500">
<span className="capitalize">{categories[0]}</span> • {t("published_by", { author })}
</h2>
</header>
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{!isLoading ? (
isGlobal || (installedAppCount > 0 && allowedMultipleInstalls) ? (
<div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
{installedAppCount > 0
? t("active_install", { count: installedAppCount })
: t("globally_install")}
</Button>
{!isGlobal && (
<InstallAppButton
type={type}
render={(buttonProps) => (
<Button data-testid="install-app-button" {...buttonProps}>
{t("install_app")}
</Button>
)}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button StartIcon={Icon.FiPlus} {...props} data-testid="install-app-button">
{t("add_another")}
</Button>
);
}}
/>
)
) : (
<SkeletonButton width="24" height="10" />
)}
{price !== 0 && (
<small className="block text-right">
{feeType === "usage-based"
? commission + "% + " + priceInDollar + "/booking"
: priceInDollar}
{feeType === "monthly" && "/" + t("month")}
</small>
)}
</div>
</div>
{/* reintroduce once we show permissions and features
<NavTabs tabs={tabs} linkProps={{ shallow: true }} /> */}
</div>
<div className="justify-between px-8 py-10 md:flex">
<div className="prose-sm prose mb-6">{body}</div>
<div className="md:max-w-80 flex-1 md:ml-8">
<h4 className="font-medium text-gray-900 ">{t("categories")}</h4>
<div className="space-x-2">
{categories.map((category) => (
<Link href={"/apps/categories/" + category} key={category}>
<a>
<Badge variant="success">{category}</Badge>
</a>
</Link>
))}
</div>
<h4 className="mt-8 font-medium text-gray-900 ">{t("pricing")}</h4>
<small>
{price === 0 ? (
"Free"
) : (
<>
{Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && "/" + t("month")}
</>
)}
)}
</div>
) : installedAppCount > 0 ? (
<Button color="secondary" disabled title="App already installed">
{t("installed")}
</Button>
) : (
<InstallAppButton
type={type}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button data-testid="install-app-button" {...props}>
{t("install_app")}
</Button>
);
}}
/>
)
) : (
<SkeletonButton width="24" height="10" />
)}
{price !== 0 && (
<small className="block text-right">
{feeType === "usage-based" ? commission + "% + " + priceInDollar + "/booking" : priceInDollar}
{feeType === "monthly" && "/" + t("month")}
</small>
<h4 className="mt-8 mb-2 font-medium text-gray-900 ">{t("learn_more")}</h4>
<ul className="prose -ml-1 -mr-1 text-xs leading-5">
{docs && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={docs}>
<BookOpenIcon className="mr-1 -mt-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
)}
{website && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={website}>
<ExternalLinkIcon className="mr-1 -mt-px inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
)}
{email && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={"mailto:" + email}>
<MailIcon className="mr-1 -mt-px inline h-4 w-4" />
{email}
</a>
</li>
)}
{tos && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={tos}>
<DocumentTextIcon className="mr-1 -mt-px inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
)}
{privacy && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={privacy}>
<ShieldCheckIcon className="mr-1 -mt-px inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
)}
</ul>
<hr className="my-6" />
<small className="leading-1 block text-gray-500">
Every app published on the Cal.com App Store is open source and thoroughly tested via peer
reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are
published by Cal.com. If you encounter inappropriate content or behaviour please report it.
</small>
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
<FlagIcon className="inline h-3 w-3" /> Report App
</a>
</div>
)}
</div>
</div>
</Shell>
</>
</div>
<div className="justify-between px-8 py-10 md:flex">
<div className="prose-sm prose mb-6">{body}</div>
<div className="md:max-w-80 flex-1 md:ml-8">
<h4 className="font-medium text-gray-900 ">{t("categories")}</h4>
<div className="space-x-2">
{categories.map((category) => (
<Link href={"/apps/categories/" + category} key={category}>
<a>
<Badge variant="success">{category}</Badge>
</a>
</Link>
))}
</div>
<h4 className="mt-8 font-medium text-gray-900 ">{t("pricing")}</h4>
<small>
{price === 0 ? (
"Free"
) : (
<>
{Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && "/" + t("month")}
</>
)}
</small>
<h4 className="mt-8 mb-2 font-medium text-gray-900 ">{t("learn_more")}</h4>
<ul className="prose -ml-1 -mr-1 text-xs leading-5">
{docs && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={docs}>
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
)}
{website && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={website}>
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
)}
{email && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={"mailto:" + email}>
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4" />
{email}
</a>
</li>
)}
{tos && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={tos}>
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
)}
{privacy && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={privacy}>
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
)}
</ul>
<hr className="my-6" />
<small className="leading-1 block text-gray-500">
Every app published on the Cal.com App Store is open source and thoroughly tested via peer
reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are
published by Cal.com. If you encounter inappropriate content or behaviour please report it.
</small>
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
<Icon.FiFlag className="inline h-3 w-3" /> Report App
</a>
</div>
</div>
</div>
);
};
export default function App(props: {
name: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
body: React.ReactNode;
categories: string[];
author: string;
pro?: boolean;
price?: number;
commission?: number;
feeType?: AppType["feeType"];
docs?: string;
website?: string;
email: string; // required
tos?: string;
privacy?: string;
licenseRequired: AppType["licenseRequired"];
isProOnly: AppType["isProOnly"];
}) {
return (
<Shell large isPublic>
{props.licenseRequired ? (
<LicenseRequired>
<Component {...props} />
</LicenseRequired>
) : (
<Component {...props} />
)}
</Shell>
);
}
+1 -3
View File
@@ -19,9 +19,7 @@ export default function AppsShell({ children }: { children: React.ReactNode }) {
return (
<>
<div className="mb-12 block lg:hidden">
{status === "authenticated" && <NavTabs tabs={tabs} linkProps={{ shallow: true }} />}
</div>
<div className="mb-12 block lg:hidden">{status === "authenticated" && <NavTabs tabs={tabs} />}</div>
<main className="pb-6">{children}</main>
</>
);
+1 -1
View File
@@ -24,7 +24,7 @@ const tabs = [
export default function BookingsShell({ children }: { children: React.ReactNode }) {
return (
<>
<NavTabs tabs={tabs} linkProps={{ shallow: true }} />
<NavTabs tabs={tabs} />
<main>{children}</main>
</>
);
@@ -3,8 +3,8 @@ import React, { useEffect, useState } from "react";
import Select from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
import { DestinationCalendar } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
interface Props {
onChange: (value: { externalId: string; integration: string }) => void;
@@ -12,6 +12,7 @@ interface Props {
hidePlaceholder?: boolean;
/** The external Id of the connected calendar */
value: string | undefined;
destinationCalendar?: DestinationCalendar | null;
}
const DestinationCalendarSelector = ({
@@ -19,6 +20,7 @@ const DestinationCalendarSelector = ({
isLoading,
value,
hidePlaceholder,
destinationCalendar,
}: Props): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"]);
@@ -61,16 +63,27 @@ const DestinationCalendarSelector = ({
query.data.connectedCalendars.map((selectedCalendar) => ({
key: selectedCalendar.credentialId,
label: `${selectedCalendar.integration.title} (${selectedCalendar.primary?.name})`,
options: (selectedCalendar.calendars ?? []).map((cal) => ({
label: cal.name || "",
value: `${cal.integration}:${cal.externalId}`,
})),
options: (selectedCalendar.calendars ?? [])
.filter((cal) => cal.readOnly === false)
.map((cal) => ({
label: cal.name || "",
value: `${cal.integration}:${cal.externalId}`,
})),
})) ?? [];
return (
<div className="relative" title={`${t("select_destination_calendar")}: ${selectedOption?.label || ""}`}>
<Select
name={"primarySelectedCalendar"}
placeholder={!hidePlaceholder ? `${t("select_destination_calendar")}:` : undefined}
name="primarySelectedCalendar"
placeholder={
!hidePlaceholder ? (
`${t("select_destination_calendar")}`
) : (
<span>
{t("default_calendar_selected")} ({destinationCalendar?.externalId})
</span>
)
}
options={options}
styles={{
placeholder: (styles) => ({ ...styles, ...content(hidePlaceholder) }),
@@ -98,7 +111,7 @@ const DestinationCalendarSelector = ({
}}
isSearchable={false}
className={classNames(
"mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 sm:text-sm",
"mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 text-sm",
!hidePlaceholder && "font-medium"
)}
onChange={(option) => {
+71 -73
View File
@@ -1,4 +1,3 @@
import { ArrowLeftIcon, ChevronRightIcon, CodeIcon, EyeIcon, SunIcon } from "@heroicons/react/solid";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import classNames from "classnames";
import { useRouter } from "next/router";
@@ -7,13 +6,12 @@ import { components, ControlProps } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { EventType } from "@calcom/prisma/client";
import { Button, Switch } from "@calcom/ui";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
import { trpc } from "@lib/trpc";
import NavTabs from "@components/NavTabs";
import ColorPicker from "@components/ui/colorpicker";
@@ -40,7 +38,7 @@ type PreviewState = {
brandColor: string;
};
};
const queryParamsForDialog = ["embedType", "tabName", "eventTypeId"];
const queryParamsForDialog = ["embedType", "tabName", "embedUrl"];
const getDimension = (dimension: string) => {
if (dimension.match(/^\d+$/)) {
@@ -143,7 +141,7 @@ function MyComponent() {
const cal = await getCalApi();
${uiInstructionCode}
})();
}, [])
}, [])
return <Cal calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} />;
};`;
},
@@ -164,7 +162,7 @@ function MyComponent() {
Cal("floatingButton", ${floatingButtonArg});
${uiInstructionCode}
})();
}, [])
}, [])
};`;
},
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
@@ -177,7 +175,7 @@ function MyComponent() {
const cal = await getCalApi();
${uiInstructionCode}
})();
}, [])
}, [])
return <button data-cal-link="${calLink}" />;
};`;
},
@@ -452,7 +450,7 @@ const tabs = [
{
name: "HTML",
tabName: "embed-code",
icon: CodeIcon,
icon: Icon.FiCode,
type: "code",
Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -474,7 +472,8 @@ const tabs = [
data-testid="embed-code"
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
name="embed-code"
className="h-[calc(100%-50px)]"
className="h-[calc(100%-50px)] font-mono"
style={{ resize: "none", overflow: "auto" }}
readOnly
value={
`<!-- Cal ${embedType} embed code begins -->\n` +
@@ -488,7 +487,8 @@ ${getEmbedSnippetString()}
${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previewState })}
</script>
<!-- Cal ${embedType} embed code ends -->`
}></TextArea>
}
/>
<p className="hidden text-sm text-gray-500">
{t(
"Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options."
@@ -501,7 +501,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
{
name: "React",
tabName: "embed-react",
icon: CodeIcon,
icon: Icon.FiCode,
type: "code",
Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -521,8 +521,9 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
data-testid="embed-react"
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
name="embed-react"
className="h-[calc(100%-50px)]"
className="h-[calc(100%-50px)] font-mono"
readOnly
style={{ resize: "none", overflow: "auto" }}
value={`/* First make sure that you have installed the package */
/* If you are using yarn */
@@ -531,7 +532,8 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
/* If you are using npm */
// npm install @calcom/embed-react
${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, previewState })}
`}></TextArea>
`}
/>
</>
);
}),
@@ -539,7 +541,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
{
name: "Preview",
tabName: "embed-preview",
icon: EyeIcon,
icon: Icon.FiEye,
type: "iframe",
Component: forwardRef<
HTMLIFrameElement | HTMLTextAreaElement | null,
@@ -575,7 +577,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
return (
<components.Control {...props}>
<SunIcon className="h-[32px] w-[32px] text-gray-500" />
<Icon.FiSun className="ml-2 h-4 w-4 text-gray-500" />
{children}
</components.Control>
);
@@ -594,10 +596,10 @@ const ChooseEmbedTypesDialogContent = () => {
<p className="text-sm text-gray-500">{t("choose_ways_put_cal_site")}</p>
</div>
</div>
<div className="flex">
<div className="flex items-start">
{embeds.map((embed, index) => (
<button
className="mr-2 w-1/3 p-3 text-left hover:rounded-md hover:border hover:bg-neutral-100"
className="mr-2 w-1/3 border border-transparent p-3 text-left hover:rounded-md hover:border-gray-200 hover:bg-neutral-100"
key={index}
data-testid={embed.type}
onClick={() => {
@@ -621,11 +623,11 @@ const ChooseEmbedTypesDialogContent = () => {
};
const EmbedTypeCodeAndPreviewDialogContent = ({
eventTypeId,
embedType,
embedUrl,
}: {
eventTypeId: EventType["id"];
embedType: EmbedType;
embedUrl: string;
}) => {
const { t } = useLocale();
const router = useRouter();
@@ -640,13 +642,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
const refOfEmbedCodesRefs = useRef(embedCodeRefs);
const embed = embeds.find((embed) => embed.type === embedType);
const { data: eventType, isLoading } = trpc.useQuery([
"viewer.eventTypes.get",
{
id: +eventTypeId,
},
]);
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
const [previewState, setPreviewState] = useState({
@@ -688,18 +683,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
});
}
if (isLoading) {
return null;
}
if (!embed || !eventType) {
if (!embed || !embedUrl) {
close();
return null;
}
const calLink = `${eventType.team ? `team/${eventType.team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
const calLink = decodeURIComponent(embedUrl);
const addToPalette = (update: typeof previewState["palette"]) => {
setPreviewState((previewState) => {
@@ -802,11 +791,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
},
});
}}>
<ArrowLeftIcon className="mr-4 w-4"></ArrowLeftIcon>
<Icon.FiArrowLeft className="mr-4 w-4" />
</button>
{embed.title}
</h3>
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")}></hr>
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")} />
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
open={isEmbedCustomizationOpen}
@@ -821,7 +810,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
? "Floating Popup Customization"
: "Element Click Customization"}
</div>
<ChevronRightIcon
<Icon.FiChevronRight
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
@@ -852,7 +841,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
}}
addOnLeading={<InputLeading>W</InputLeading>}
/>
<span className="p-2">x</span>
<span className="p-2">×</span>
<TextField
labelProps={{ className: "hidden" }}
name="height"
@@ -918,7 +907,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
},
};
});
}}></Switch>
}}
/>
</div>
<div
className={classNames(
@@ -939,7 +929,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
});
}}
defaultValue={FloatingPopupPositionOptions[0]}
options={FloatingPopupPositionOptions}></Select>
options={FloatingPopupPositionOptions}
/>
</div>
<div
className={classNames(
@@ -960,7 +951,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
},
};
});
}}></ColorPicker>
}}
/>
</div>
</div>
<div
@@ -982,20 +974,21 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
},
};
});
}}></ColorPicker>
}}
/>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div>
<hr className="mt-4"></hr>
<hr className="mt-4" />
<div className="mt-4 font-medium">
<Collapsible
open={isBookingCustomizationOpen}
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleTrigger className="flex w-full" type="button">
<div className="text-base font-medium text-neutral-900">Cal Booking Customization</div>
<ChevronRightIcon
<Icon.FiChevronRight
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
@@ -1022,7 +1015,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
};
});
}}
options={ThemeOptions}></Select>
options={ThemeOptions}
/>
</Label>
{[
{ name: "brandColor", title: "Brand Color" },
@@ -1065,7 +1059,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
embedType={embedType}
calLink={calLink}
previewState={previewState}
ref={refOfEmbedCodesRefs.current[tab.name]}></tab.Component>
ref={refOfEmbedCodesRefs.current[tab.name]}
/>
) : (
<tab.Component
embedType={embedType}
@@ -1075,7 +1070,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
/>
)}
</div>
<div className={router.query.tabName == "embed-preview" ? "block" : "hidden"}></div>
<div className={router.query.tabName == "embed-preview" ? "block" : "hidden"} />
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
{tab.type === "code" ? (
@@ -1107,39 +1102,46 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
export const EmbedDialog = () => {
const router = useRouter();
const eventTypeId: EventType["id"] = +(router.query.eventTypeId as string);
const embedUrl: string = router.query.embedUrl as string;
return (
<Dialog name="embed" clearQueryParamsOnClose={queryParamsForDialog}>
<Dialog
name="embed"
clearQueryParamsOnClose={queryParamsForDialog}
onOpenChange={(open) => {
if (!open) window.resetEmbedStatus();
}}>
{!router.query.embedType ? (
<ChooseEmbedTypesDialogContent />
) : (
<EmbedTypeCodeAndPreviewDialogContent
eventTypeId={eventTypeId}
embedType={router.query.embedType as EmbedType}
embedUrl={embedUrl}
/>
)}
</Dialog>
);
};
export const EmbedButton = ({
eventTypeId,
className = "",
dark,
...props
}: {
eventTypeId: EventType["id"];
type EmbedButtonProps<T> = {
embedUrl: string;
children?: React.ReactNode;
className: string;
dark?: boolean;
}) => {
const { t } = useLocale();
as?: T;
};
export const EmbedButton = <T extends React.ElementType>({
embedUrl,
children,
className = "",
as,
...props
}: EmbedButtonProps<T> & React.ComponentPropsWithoutRef<T>) => {
const router = useRouter();
className = classNames(className, "hidden lg:flex");
const openEmbedModal = () => {
const query = {
...router.query,
dialog: "embed",
eventTypeId,
embedUrl,
};
router.push(
{
@@ -1150,20 +1152,16 @@ export const EmbedButton = ({
{ shallow: true }
);
};
const Component = as ?? Button;
return (
<Button
type="button"
color="minimal"
size="sm"
className={className}
<Component
{...props}
data-test-eventtype-id={eventTypeId}
data-testid={"event-type-embed"}
className={className}
data-test-embed-url={embedUrl}
data-testid="embed"
onClick={() => openEmbedModal()}>
<CodeIcon
className={classNames("h-4 w-4 ltr:mr-2 rtl:ml-2", dark ? "" : "text-neutral-500")}></CodeIcon>
{t("Embed")}
</Button>
{children}
</Component>
);
};
+6 -1
View File
@@ -1,11 +1,16 @@
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { trpc } from "@lib/trpc";
import { trpc } from "@calcom/trpc/react";
export function useViewerI18n() {
return trpc.useQuery(["viewer.public.i18n"], {
staleTime: Infinity,
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
* We intend to not cache i18n query
**/
context: { skipBatch: true },
});
}
+1 -1
View File
@@ -142,7 +142,7 @@ export default function ImageUploader({
</div>
)}
{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 hover:shadow-sm 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-neutral-900 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent dark:text-white dark:hover:bg-gray-900">
<input
onInput={onInputFile}
type="file"
+5 -3
View File
@@ -18,6 +18,7 @@ export interface NavTabProps {
tabName?: string;
icon?: SVGComponent;
adminRequired?: boolean;
className?: string;
}[];
linkProps?: Omit<LinkProps, "href">;
}
@@ -58,7 +59,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
: noop;
const Component = tab.adminRequired ? AdminRequired : Fragment;
const className = tab.className || "";
return (
<Component key={tab.name}>
<Link key={tab.name} href={href} {...linkProps}>
@@ -68,14 +69,15 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
isCurrent
? "border-neutral-900 text-neutral-900"
: "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
)}
aria-current={isCurrent ? "page" : undefined}>
{tab.icon && (
<tab.icon
className={classNames(
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
"-ml-0.5 hidden h-5 w-5 ltr:mr-2 rtl:ml-2 sm:inline-block"
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
)}
aria-hidden="true"
/>
+9 -16
View File
@@ -1,48 +1,41 @@
import {
CreditCardIcon,
KeyIcon,
LockClosedIcon,
UserGroupIcon,
UserIcon,
CodeIcon,
} from "@heroicons/react/solid";
import React, { ComponentProps } from "react";
import ErrorBoundary from "@lib/ErrorBoundary";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import NavTabs from "./NavTabs";
import Shell from "./Shell";
const tabs = [
{
name: "profile",
href: "/settings/profile",
icon: UserIcon,
icon: Icon.FiUser,
},
{
name: "teams",
href: "/settings/teams",
icon: UserGroupIcon,
icon: Icon.FiUsers,
},
{
name: "security",
href: "/settings/security",
icon: KeyIcon,
icon: Icon.FiKey,
},
{
name: "developer",
href: "/settings/developer",
icon: CodeIcon,
icon: Icon.FiTerminal,
},
{
name: "billing",
href: "/settings/billing",
icon: CreditCardIcon,
icon: Icon.FiCreditCard,
},
{
name: "admin",
href: "/settings/admin",
icon: LockClosedIcon,
icon: Icon.FiLock,
adminRequired: true,
},
];
+4 -5
View File
@@ -1,10 +1,9 @@
import { InformationCircleIcon } from "@heroicons/react/outline";
import { Trans } from "next-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { useLocale } from "@lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
export function UpgradeToProDialog({
modalOpen,
@@ -20,7 +19,7 @@ export function UpgradeToProDialog({
<Dialog open={modalOpen}>
<DialogContent>
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100">
<InformationCircleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />
<Icon.FiAlertTriangle className="h-6 w-6 text-yellow-400" aria-hidden="true" />
</div>
<div className="mb-4 sm:flex sm:items-start">
<div className="mt-3 sm:mt-0 sm:text-left">
@@ -43,7 +42,7 @@ export function UpgradeToProDialog({
</div>
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose asChild>
<Button className="btn-wide table-cell text-center" onClick={() => setModalOpen(false)}>
<Button className="table-cell w-full text-center" onClick={() => setModalOpen(false)}>
{t("dismiss")}
</Button>
</DialogClose>
+1
View File
@@ -19,6 +19,7 @@ export default function AllApps({ apps }: { apps: App[] }) {
logo={app.logo}
rating={app.rating}
reviews={app.reviews}
isProOnly={app.isProOnly}
/>
))}
</div>
+12 -1
View File
@@ -1,5 +1,7 @@
import Link from "next/link";
import { trpc } from "@calcom/trpc/react";
import Badge from "@calcom/ui/Badge";
import Button from "@calcom/ui/Button";
interface AppCardProps {
@@ -10,9 +12,11 @@ interface AppCardProps {
description: string;
rating: number;
reviews?: number;
isProOnly?: boolean;
}
export default function AppCard(props: AppCardProps) {
const { data: user } = trpc.useQuery(["viewer.me"]);
return (
<Link href={"/apps/" + props.slug}>
<a
@@ -32,7 +36,14 @@ export default function AppCard(props: AppCardProps) {
Add
</Button>
</div>
<h3 className="font-medium">{props.name}</h3>
<div className="flex items-center">
<h3 className="font-medium">{props.name}</h3>
{props.isProOnly && user?.plan === "FREE" ? (
<Badge className="ml-2" variant="default">
PRO
</Badge>
) : null}
</div>
{/* TODO: add reviews <div className="flex text-sm text-gray-800">
<span>{props.rating} stars</span> <StarIcon className="ml-1 mt-0.5 h-4 w-4 text-yellow-600" />
<span className="pl-1 text-gray-500">{props.reviews} reviews</span>
+4 -3
View File
@@ -1,9 +1,10 @@
import Glide, { Options } from "@glidejs/glide";
import "@glidejs/glide/dist/css/glide.core.min.css";
import "@glidejs/glide/dist/css/glide.theme.min.css";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/solid";
import { useEffect, useRef } from "react";
import { Icon } from "@calcom/ui/Icon";
const Slider = <T extends string | unknown>({
title = "",
className = "",
@@ -51,10 +52,10 @@ const Slider = <T extends string | unknown>({
)}
<div className="glide__arrows ml-auto" data-glide-el="controls">
<button data-glide-dir="<" className="mr-4">
<ArrowLeftIcon className="h-5 w-5 text-gray-600 hover:text-black" />
<Icon.FiArrowLeft className="h-5 w-5 text-gray-600 hover:text-black" />
</button>
<button data-glide-dir=">">
<ArrowRightIcon className="h-5 w-5 text-gray-600 hover:text-black" />
<Icon.FiArrowRight className="h-5 w-5 text-gray-600 hover:text-black" />
</button>
</div>
</div>
@@ -30,6 +30,7 @@ const TrendingAppsSlider = <T extends App>({ items }: { items: T[] }) => {
logo={app.logo}
rating={app.rating}
reviews={app.reviews}
isProOnly={app.isProOnly}
/>
)}
/>
+3 -3
View File
@@ -2,11 +2,11 @@ import { signIn } from "next-auth/react";
import { Dispatch, SetStateAction } from "react";
import { useFormContext } from "react-hook-form";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { trpc } from "@lib/trpc";
interface Props {
email: string;
@@ -34,7 +34,7 @@ export default function SAMLLogin(props: Props) {
<div className="mt-5">
<Button
color="secondary"
data-testid={"saml"}
data-testid="saml"
className="flex w-full justify-center"
onClick={async (event) => {
event.preventDefault();
@@ -1,15 +1,15 @@
import { PlusIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Form } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import { trpc } from "@lib/trpc";
export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) {
const router = useRouter();
@@ -41,7 +41,7 @@ export function NewScheduleButton({ name = "new-schedule" }: { name?: string })
return (
<Dialog name={name} clearQueryParamsOnClose={["copy-schedule-id"]}>
<DialogTrigger asChild>
<Button data-testid={name} StartIcon={PlusIcon}>
<Button data-testid={name} StartIcon={Icon.FiPlus}>
{t("new_schedule_btn")}
</Button>
</DialogTrigger>
@@ -65,7 +65,7 @@ export function NewScheduleButton({ name = "new-schedule" }: { name?: string })
type="text"
id="name"
required
className="block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className="block w-full rounded-sm border-gray-300 text-sm"
placeholder={t("default_schedule_name")}
{...register("name")}
/>
+28 -24
View File
@@ -1,5 +1,3 @@
import { PlusIcon, TrashIcon } from "@heroicons/react/outline";
import { DuplicateIcon } from "@heroicons/react/solid";
import classNames from "classnames";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
@@ -8,7 +6,9 @@ import { GroupBase, Props } from "react-select";
import dayjs, { Dayjs, ConfigType } from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import Dropdown, { DropdownMenuTrigger, DropdownMenuContent } from "@calcom/ui/Dropdown";
import Dropdown, { DropdownMenuContent, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { defaultDayRange } from "@lib/availability";
import { weekdayNames } from "@lib/core/i18n/weekday";
@@ -202,7 +202,7 @@ export const DayRanges = ({
const { setValue, watch } = useFormContext();
// XXX: Hack to make copying times work; `fields` is out of date until save.
const watcher = watch(name);
const { t } = useLocale();
const { fields, replace, append, remove } = useFieldArray({
name,
});
@@ -230,36 +230,40 @@ export const DayRanges = ({
<div className="space-y-2">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center rtl:space-x-reverse">
<div className="flex flex-grow sm:flex-grow-0">
<div className="flex flex-grow space-x-1 sm:flex-grow-0">
<TimeRangeField name={`${name}.${index}`} />
<Button
type="button"
size="icon"
color="minimal"
StartIcon={TrashIcon}
type="button"
StartIcon={Icon.FiTrash}
onClick={() => remove(index)}
/>
</div>
{index === 0 && (
<div className="absolute top-2 right-0 text-right sm:relative sm:top-0 sm:flex-grow">
<Button
className="text-neutral-400"
type="button"
color="minimal"
size="icon"
StartIcon={PlusIcon}
onClick={handleAppend}
/>
<Tooltip content={t("add_time_availability") as string}>
<Button
className="text-neutral-400"
type="button"
color="minimal"
size="icon"
StartIcon={Icon.FiPlus}
onClick={handleAppend}
/>
</Tooltip>
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
type="button"
color="minimal"
size="icon"
StartIcon={DuplicateIcon}
onClick={handleAppend}
/>
</DropdownMenuTrigger>
<Tooltip content={t("duplicate") as string}>
<DropdownMenuTrigger asChild>
<Button
type="button"
color="minimal"
size="icon"
StartIcon={Icon.FiCopy}
onClick={handleAppend}
/>
</DropdownMenuTrigger>
</Tooltip>
<DropdownMenuContent>
<CopyTimes
disabled={[parseInt(name.substring(name.lastIndexOf(".") + 1), 10)]}
@@ -1,14 +1,13 @@
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
import Link from "next/link";
import { Fragment } from "react";
import { availabilityAsString } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Availability } from "@calcom/prisma/client";
import { inferQueryOutput } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
import { inferQueryOutput } from "@lib/trpc";
import { Icon } from "@calcom/ui/Icon";
export function ScheduleListItem({
schedule,
@@ -48,7 +47,7 @@ export function ScheduleListItem({
</div>
<Dropdown>
<DropdownMenuTrigger className="group mr-5 h-10 w-10 border border-transparent p-0 text-neutral-500 hover:border-gray-200">
<DotsHorizontalIcon className="h-5 w-5 group-hover:text-gray-800" />
<Icon.FiMoreHorizontal className="h-5 w-5 group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
@@ -62,7 +61,7 @@ export function ScheduleListItem({
type="button"
color="warn"
className="w-full font-normal"
StartIcon={isDeleting ? undefined : TrashIcon}
StartIcon={isDeleting ? undefined : Icon.FiTrash}
loading={isDeleting}>
{isDeleting ? t("deleting") : t("delete_schedule")}
</Button>
@@ -19,13 +19,13 @@ function SkeletonItem() {
<li className="group flex w-full items-center justify-between px-2 py-[23px] sm:px-6">
<div className="flex-grow truncate text-sm">
<div className="flex flex-col space-y-2">
<SkeletonText width="32" height="4"></SkeletonText>
<SkeletonText width="32" height="2"></SkeletonText>
<SkeletonText width="32" height="4" />
<SkeletonText width="32" height="2" />
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText width="12" height="6"></SkeletonText>
<SkeletonText width="12" height="6" />
</div>
</div>
</li>
@@ -37,8 +37,8 @@ export const AvailabilitySelectSkeletonLoader = () => {
<li className="group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3">
<div className="flex-grow truncate text-sm">
<div className="flex justify-between">
<SkeletonText width="32" height="4"></SkeletonText>
<SkeletonText width="4" height="4"></SkeletonText>
<SkeletonText width="32" height="4" />
<SkeletonText width="4" height="4" />
</div>
</div>
</li>
+11 -22
View File
@@ -1,4 +1,3 @@
import { SchedulingType } from "@prisma/client";
import Link from "next/link";
import { useRouter } from "next/router";
import { FC, useEffect, useState } from "react";
@@ -6,23 +5,18 @@ import { FC, useEffect, useState } from "react";
import dayjs, { Dayjs } from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { nameOfDay } from "@calcom/lib/weekday";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
import type { Slot } from "@server/routers/viewer/slots";
type AvailableTimesProps = {
timeFormat: string;
eventTypeId: number;
recurringCount: number | undefined;
eventTypeSlug: string;
date: Dayjs;
users: {
username: string | null;
}[];
schedulingType: SchedulingType | null;
seatsPerTimeSlot?: number | null;
slots?: Slot[];
isLoading: boolean;
@@ -36,7 +30,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
eventTypeSlug,
recurringCount,
timeFormat,
schedulingType,
seatsPerTimeSlot,
}) => {
const { t, i18n } = useLocale();
@@ -50,14 +43,14 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
}, []);
return (
<div className="mt-8 flex flex-col text-center sm:mt-0 sm:w-1/3 sm:pl-4 md:-mb-5">
<div className="mb-4 text-left text-lg font-light text-gray-600">
<span className="text-bookingdarker w-1/2 dark:text-white">
<strong>{nameOfDay(i18n.language, Number(date.format("d")))}</strong>
<span className="text-bookinglight">
{date.format(", D ")}
{date.toDate().toLocaleString(i18n.language, { month: "long" })}
</span>
<div className="mt-8 flex flex-col px-4 text-center sm:mt-0 sm:w-1/3 sm:p-5 md:-mb-4 ">
<div className="mb-4 text-left text-base">
<span className="text-bookingdarker mb-8 w-1/2 break-words font-semibold text-gray-900 dark:text-white">
{nameOfDay(i18n.language, Number(date.format("d")))}
</span>
<span className="text-bookinglight font-medium">
{date.format(", D ")}
{date.toDate().toLocaleString(i18n.language, { month: "long" })}
</span>
</div>
<div className="grid flex-grow grid-cols-2 gap-x-2 overflow-y-auto sm:block md:h-[364px]">
@@ -83,10 +76,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
bookingUrl.query.rescheduleUid = rescheduleUid as string;
}
if (schedulingType === SchedulingType.ROUND_ROBIN) {
bookingUrl.query.user = slot.users;
}
// If event already has an attendee add booking id
if (slot.bookingUid) {
bookingUrl.query.bookingUid = slot.bookingUid;
@@ -102,13 +91,13 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
brand === "#fff" || brand === "#ffffff" ? "border-brandcontrast" : "border-brand"
)}>
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && <p className={`text-sm`}>{t("booking_full")}</p>}
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
</div>
) : (
<Link href={bookingUrl} prefetch={false}>
<a
className={classNames(
"text-primary-500 hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast mb-2 block rounded-sm border bg-white py-4 font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
"text-primary-500 hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast mb-2 block rounded-sm border bg-white py-4 text-sm font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
brand === "#fff" || brand === "#ffffff" ? "border-brandcontrast" : "border-brand"
)}
data-testid="time">
+36 -37
View File
@@ -1,26 +1,17 @@
import {
BanIcon,
CheckIcon,
ClockIcon,
LocationMarkerIcon,
PaperAirplaneIcon,
PencilAltIcon,
XIcon,
} from "@heroicons/react/outline";
import { RefreshIcon } from "@heroicons/react/solid";
import { BookingStatus } from "@prisma/client";
import { useRouter } from "next/router";
import { useState } from "react";
import { useMutation } from "react-query";
import dayjs from "@calcom/dayjs";
import { parseRecurringEvent } from "@calcom/lib";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { inferQueryInput, inferQueryOutput, trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { TextArea } from "@calcom/ui/form/fields";
@@ -28,8 +19,7 @@ import { HttpError } from "@lib/core/http/error";
import useMeQuery from "@lib/hooks/useMeQuery";
import { linkValueToString } from "@lib/linkValueToString";
import { LocationType } from "@lib/location";
import { parseRecurringDates } from "@lib/parseDate";
import { inferQueryInput, inferQueryOutput, trpc } from "@lib/trpc";
import { extractRecurringDates } from "@lib/parseDate";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
@@ -41,7 +31,7 @@ type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringCount?: number;
recurringBookings?: BookingItem[];
};
function BookingListItem(booking: BookingItemProps) {
@@ -102,7 +92,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setRejectionDialogIsOpen(true);
},
icon: BanIcon,
icon: Icon.FiSlash,
disabled: mutation.isLoading,
},
{
@@ -114,7 +104,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
mutation.mutate(true);
},
icon: CheckIcon,
icon: Icon.FiCheck,
disabled: mutation.isLoading,
color: "primary",
},
@@ -127,24 +117,30 @@ function BookingListItem(booking: BookingItemProps) {
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? t("cancel_all_remaining")
: t("cancel"),
href: `/cancel/${booking.uid}`,
icon: XIcon,
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
href: `/cancel/${booking.uid}${
booking.listingStatus === "recurring" && booking.recurringEventId !== null
? "?allRemainingBookings=true"
: ""
}`,
icon: Icon.FiX,
},
{
id: "edit_booking",
label: t("edit_booking"),
icon: PencilAltIcon,
icon: Icon.FiEdit,
actions: [
{
id: "reschedule",
icon: ClockIcon,
icon: Icon.FiClock,
label: t("reschedule_booking"),
href: `/reschedule/${booking.uid}`,
},
{
id: "reschedule_request",
icon: PaperAirplaneIcon,
iconClassName: "rotate-45 w-[18px] -ml-[2px]",
icon: Icon.FiSend,
iconClassName: "rotate-45 w-[16px] -translate-x-0.5 ",
label: t("send_reschedule_request"),
onClick: () => {
setIsOpenRescheduleDialog(true);
@@ -156,7 +152,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setIsOpenLocationDialog(true);
},
icon: LocationMarkerIcon,
icon: Icon.FiMapPin,
},
],
},
@@ -169,7 +165,7 @@ function BookingListItem(booking: BookingItemProps) {
const RequestSentMessage = () => {
return (
<div className="ml-1 mr-8 flex text-gray-500" data-testid="request_reschedule_sent">
<PaperAirplaneIcon className="-mt-[1px] w-4 rotate-45" />
<Icon.FiSend className="-mt-[1px] w-4 rotate-45" />
<p className="ml-2 ">{t("reschedule_request_sent")}</p>
</div>
);
@@ -191,7 +187,11 @@ function BookingListItem(booking: BookingItemProps) {
if (
newLocationType === LocationType.InPerson ||
newLocationType === LocationType.Link ||
newLocationType === LocationType.UserPhone
newLocationType === LocationType.UserPhone ||
newLocationType === LocationType.Riverside ||
newLocationType === LocationType.Around ||
newLocationType === LocationType.Whereby ||
newLocationType === LocationType.Ping
) {
newLocation = details[Object.keys(details)[0]];
}
@@ -202,14 +202,10 @@ function BookingListItem(booking: BookingItemProps) {
let recurringStrings: string[] = [];
let recurringDates: Date[] = [];
const today = new Date();
if (booking.recurringCount && booking.eventType.recurringEvent?.freq !== undefined) {
[recurringStrings, recurringDates] = parseRecurringDates(
{
startDate: booking.startTime,
timeZone: user?.timeZone,
recurringEvent: parseRecurringEvent(booking.eventType.recurringEvent),
recurringCount: booking.recurringCount,
},
if (booking.recurringBookings && booking.eventType.recurringEvent?.freq !== undefined) {
[recurringStrings, recurringDates] = extractRecurringDates(
booking.recurringBookings,
user?.timeZone,
i18n
);
if (booking.status === BookingStatus.PENDING) {
@@ -308,7 +304,7 @@ function BookingListItem(booking: BookingItemProps) {
{dayjs(booking.endTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
</div>
<div className="text-sm text-gray-400">
{booking.recurringCount &&
{booking.recurringBookings &&
booking.eventType?.recurringEvent?.freq &&
(booking.listingStatus === "recurring" || booking.listingStatus === "cancelled") && (
<div className="underline decoration-gray-400 decoration-dashed underline-offset-2">
@@ -318,8 +314,11 @@ function BookingListItem(booking: BookingItemProps) {
<p key={key}>{aDate}</p>
))}>
<div className="text-gray-600 dark:text-white">
<RefreshIcon className="float-left mr-1 mt-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="pl-[21px]">
<Icon.FiRefreshCcw
stroke-width="3"
className="float-left mr-1 mt-1.5 inline-block h-3 w-3 text-gray-400"
/>
<p className="mt-1 pl-5 text-xs">
{booking.status === BookingStatus.ACCEPTED
? `${t("event_remaining", {
count: recurringDates.length,
@@ -327,7 +326,7 @@ function BookingListItem(booking: BookingItemProps) {
: getEveryFreqFor({
t,
recurringEvent: booking.eventType.recurringEvent,
recurringCount: booking.recurringCount,
recurringCount: booking.recurringBookings.length,
})}
</p>
</div>
+83 -84
View File
@@ -1,13 +1,12 @@
import { XIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RecurringEvent } from "@calcom/types/Calendar";
import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button } from "@calcom/ui/Button";
import useTheme from "@lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { Icon } from "@calcom/ui/Icon";
type Props = {
booking: {
@@ -32,90 +31,90 @@ export default function CancelBooking(props: Props) {
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
const { isReady, Theme } = useTheme(props.theme);
useTheme(props.theme);
if (isReady) {
return (
<>
<Theme />
{error && (
<div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{error}
</h3>
</div>
return (
<>
{error && (
<div>
<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" />
</div>
)}
{!error && (
<div className="mt-5 sm:mt-6">
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
<textarea
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
rows={3}
/>
<div className="flex rtl:space-x-reverse">
{!props.recurringEvent && (
<div className="w-full">
<Button color="secondary" onClick={() => router.push("/reschedule/" + booking?.uid)}>
{t("reschedule_this")}
</Button>
</div>
)}
<div className="w-full space-x-2 text-right">
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
{t("nevermind")}
</Button>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{error}
</h3>
</div>
</div>
)}
{!error && (
<div className="mt-5 sm:mt-6">
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
<textarea
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
rows={3}
/>
<div className=" flex flex-col-reverse rtl:space-x-reverse sm:flex-row">
{!props.recurringEvent && (
<div className=" border-bookinglightest mt-5 flex w-full justify-center border-t pt-3 sm:mt-0 sm:justify-start sm:border-0 sm:pt-0">
<Button
data-testid="cancel"
onClick={async () => {
setLoading(true);
const payload = {
uid: booking?.uid,
reason: cancellationReason,
};
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
method: "DELETE",
});
if (res.status >= 200 && res.status < 300) {
await router.push(
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
profile.slug
}&team=${team ? 1 : 0}&recurring=${!!props.recurringEvent}`
);
} else {
setLoading(false);
setError(
`${t("error_with_status_code_occured", { status: res.status })} ${t(
"please_try_again"
)}`
);
}
}}
loading={loading}>
{t("cancel_event")}
color="secondary"
className="border-0 sm:border"
onClick={() => router.push("/reschedule/" + booking?.uid)}>
{t("reschedule_this")}
</Button>
</div>
)}
<div className="mb-2 flex w-full space-x-2 text-right sm:mb-0 ">
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
{t("nevermind")}
</Button>
<Button
className="flex grow justify-center"
data-testid="cancel"
onClick={async () => {
setLoading(true);
const payload = {
uid: booking?.uid,
reason: cancellationReason,
};
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
method: "DELETE",
});
if (res.status >= 200 && res.status < 300) {
await router.push(
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
profile.slug
}&team=${team ? 1 : 0}&recurring=${!!props.recurringEvent}`
);
} else {
setLoading(false);
setError(
`${t("error_with_status_code_occured", { status: res.status })} ${t(
"please_try_again"
)}`
);
}
}}
loading={loading}>
{t("cancel_event")}
</Button>
</div>
</div>
)}
</>
);
}
return <></>;
</div>
)}
</>
);
}
+8 -9
View File
@@ -1,21 +1,20 @@
import { FC, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Switch from "@calcom/ui/Switch";
import { useLocale } from "@lib/hooks/useLocale";
import TimezoneSelect, { ITimezoneOption } from "@components/ui/form/TimezoneSelect";
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/form/TimezoneSelect";
import { is24h, timeZone } from "../../lib/clock";
type Props = {
onSelectTimeZone: (selectedTimeZone: string) => void;
onToggle24hClock: (is24hClock: boolean) => void;
timeFormat: string;
};
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone }) => {
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone, timeFormat }) => {
const [selectedTimeZone, setSelectedTimeZone] = useState("");
const [is24hClock, setIs24hClock] = useState(false);
const [is24hClock, setIs24hClock] = useState(timeFormat === "HH:mm" && true);
const { t } = useLocale();
useEffect(() => {
@@ -34,9 +33,9 @@ const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone }) => {
};
return selectedTimeZone !== "" ? (
<div className="max-w-80 absolute z-10 w-full rounded-sm border border-gray-200 bg-white px-4 py-2 dark:border-0 dark:bg-gray-700">
<div className="max-w-80 absolute z-10 w-full rounded-sm rounded border border-gray-200 bg-white px-4 pt-4 pb-3 shadow-sm dark:border-0 dark:bg-gray-700">
<div className="mb-4 flex">
<div className="font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
<div className="text-sm font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
<div className="ml-auto flex items-center">
<label className="ltl:mr-3 mr-2 align-text-top text-sm font-medium text-neutral-700 ltr:ml-3 rtl:mr-3 dark:text-white">
{t("am_pm")}
@@ -53,7 +52,7 @@ const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone }) => {
id="timeZone"
value={selectedTimeZone}
onChange={(tz: ITimezoneOption) => setSelectedTimeZone(tz.value)}
className="focus:border-brand mt-1 mb-2 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
className="focus:border-brand mt-1 mb-2 block w-full rounded-md border-gray-300 text-sm focus:ring-black"
/>
</div>
) : null;
@@ -0,0 +1,34 @@
import { CAL_URL } from "@calcom/lib/constants";
import AvatarGroup, { AvatarGroupProps } from "@components/ui/AvatarGroup";
export const UserAvatars = ({
profile,
users,
...props
}: {
profile: { image: string | null; name?: string | null };
showMembers: boolean;
users: { username: string | null; name?: string | null }[];
} & Pick<AvatarGroupProps, "size" | "truncateAfter">) => {
const showMembers = !users.find((user) => user.name === profile.name) && props.showMembers;
return (
<AvatarGroup
border="border-2 dark:border-gray-800 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
...(showMembers
? users.map((user) => ({
title: user.name,
image: `${CAL_URL}/${user.username}/avatar.png`,
alt: user.name || undefined,
}))
: []),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
}
size={props.size}
truncateAfter={props.truncateAfter}
/>
);
};
@@ -1,22 +1,8 @@
// Get router variables
import {
ArrowLeftIcon,
ChevronDownIcon,
ChevronUpIcon,
ClipboardCheckIcon,
ClockIcon,
CreditCardIcon,
GlobeIcon,
InformationCircleIcon,
LocationMarkerIcon,
RefreshIcon,
VideoCameraIcon,
} from "@heroicons/react/solid";
import { EventType } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useContracts } from "contexts/contractsContext";
import { TFunction } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
@@ -30,27 +16,31 @@ import {
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { useContracts } from "@calcom/features/ee/web3/contexts/contractsContext";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { CAL_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { localStorage } from "@calcom/lib/webstorage";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import DatePicker from "@calcom/ui/booker/DatePicker";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useTheme from "@lib/hooks/useTheme";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { detectBrowserTimeFormat } from "@lib/timeFormat";
import { trpc } from "@lib/trpc";
import CustomBranding from "@components/CustomBranding";
import AvailableTimes from "@components/booking/AvailableTimes";
import TimeOptions from "@components/booking/TimeOptions";
import { UserAvatars } from "@components/booking/UserAvatars";
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
import { HeadSeo } from "@components/seo/head-seo";
import AvatarGroup from "@components/ui/AvatarGroup";
import PoweredByCal from "@components/ui/PoweredByCal";
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
@@ -64,6 +54,10 @@ export const locationKeyToString = (location: LocationObject, t: TFunction) => {
case LocationType.InPerson:
return location.address || "In Person"; // If disabled address won't exist on the object
case LocationType.Link:
case LocationType.Ping:
case LocationType.Riverside:
case LocationType.Around:
case LocationType.Whereby:
return location.link || "Link"; // If disabled link won't exist on the object
case LocationType.Phone:
return t("your_number");
@@ -97,7 +91,7 @@ const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
return (
<div className="flex h-full flex-col justify-end">
<button title={t("profile")} onClick={() => router.replace(`${WEBSITE_URL}/${slug}`)}>
<ArrowLeftIcon className="h-4 w-4 text-black transition-opacity hover:cursor-pointer dark:text-white" />
<Icon.FiArrowLeft className="h-4 w-4 text-black transition-opacity hover:cursor-pointer dark:text-white" />
<p className="sr-only">Go Back</p>
</button>
</div>
@@ -106,20 +100,26 @@ const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
const useSlots = ({
eventTypeId,
eventTypeSlug,
startTime,
endTime,
usernameList,
timeZone,
}: {
eventTypeId: number;
eventTypeSlug: string;
startTime?: Dayjs;
endTime?: Dayjs;
timeZone: string;
usernameList: string[];
timeZone?: string;
}) => {
const { data, isLoading, isIdle } = trpc.useQuery(
[
"viewer.public.slots.getSchedule",
{
eventTypeId,
eventTypeSlug,
usernameList,
startTime: startTime?.toISOString() || "",
endTime: endTime?.toISOString() || "",
timeZone,
@@ -127,7 +127,6 @@ const useSlots = ({
],
{ enabled: !!startTime && !!endTime }
);
const [cachedSlots, setCachedSlots] = useState<NonNullable<typeof data>["slots"]>({});
useEffect(() => {
@@ -145,14 +144,16 @@ const SlotPicker = ({
timeFormat,
timeZone,
recurringEventCount,
users,
seatsPerTimeSlot,
weekStart = 0,
}: {
eventType: Pick<EventType, "id" | "schedulingType" | "slug">;
timeFormat: string;
timeZone: string;
timeZone?: string;
seatsPerTimeSlot?: number;
recurringEventCount?: number;
users: string[];
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}) => {
const [selectedDate, setSelectedDate] = useState<Dayjs>();
@@ -166,14 +167,18 @@ const SlotPicker = ({
// Etc/GMT is not actually a timeZone, so handle this select option explicitly to prevent a hard crash.
if (timeZone === "Etc/GMT") {
setBrowsingDate(dayjs.utc(month).startOf("month"));
setBrowsingDate(dayjs.utc(month).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0));
if (date) {
setSelectedDate(dayjs.utc(date));
}
} else {
setBrowsingDate(dayjs(month).tz(timeZone, true).startOf("month"));
// Set the start of the month without shifting time like startOf() may do.
setBrowsingDate(
dayjs.tz(month, timeZone).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0)
);
if (date) {
setSelectedDate(dayjs(date).tz(timeZone, true));
// It's important to set the date immediately to the timeZone, dayjs(date) will convert to browsertime.
setSelectedDate(dayjs.tz(date, timeZone));
}
}
}, [router.isReady, month, date, timeZone]);
@@ -181,12 +186,16 @@ const SlotPicker = ({
const { i18n, isLocaleReady } = useLocale();
const { slots: _1 } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
startTime: selectedDate?.startOf("day"),
endTime: selectedDate?.endOf("day"),
timeZone,
});
const { slots: _2, isLoading } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
startTime: browsingDate?.startOf("month"),
endTime: browsingDate?.endOf("month"),
timeZone,
@@ -198,12 +207,12 @@ const SlotPicker = ({
<>
<DatePicker
isLoading={isLoading}
className={
"mt-8 w-full sm:mt-0 sm:min-w-[455px] " +
(selectedDate
? "sm:w-1/2 sm:border-r sm:pl-4 sm:pr-6 sm:dark:border-gray-700 md:w-1/3 "
: "sm:pl-4")
}
className={classNames(
"mt-8 w-full sm:mt-0 sm:min-w-[455px]",
selectedDate
? "border-gray-200 sm:w-1/2 sm:border-r sm:p-4 sm:pr-6 sm:dark:border-gray-700 md:w-1/3 "
: "sm:p-4"
)}
includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)}
locale={isLocaleReady ? i18n.language : "en"}
selected={selectedDate}
@@ -227,8 +236,6 @@ const SlotPicker = ({
eventTypeSlug={eventType.slug}
seatsPerTimeSlot={seatsPerTimeSlot}
recurringCount={recurringEventCount}
schedulingType={eventType.schedulingType}
users={[]}
/>
)}
</>
@@ -239,15 +246,18 @@ function TimezoneDropdown({
onChangeTimeFormat,
onChangeTimeZone,
timeZone,
timeFormat,
}: {
onChangeTimeFormat: (newTimeFormat: string) => void;
onChangeTimeZone: (newTimeZone: string) => void;
timeZone?: string;
timeFormat: string;
}) {
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
useEffect(() => {
handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -264,18 +274,22 @@ function TimezoneDropdown({
return (
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Collapsible.Trigger className="min-w-32 text-bookinglight mb-1 -ml-2 px-2 py-1 text-left dark:text-white">
<p className="text-gray-600 dark:text-white">
<GlobeIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<p className="py-1 text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{timeZone}
{isTimeOptionsOpen ? (
<ChevronUpIcon className="ml-1 -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiChevronUp className="ml-1 -mt-1 inline-block h-4 w-4 text-gray-500" />
) : (
<ChevronDownIcon className="ml-1 -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiChevronDown className="ml-1 -mt-1 inline-block h-4 w-4 text-gray-500" />
)}
</p>
</Collapsible.Trigger>
<Collapsible.Content>
<TimeOptions onSelectTimeZone={handleSelectTimeZone} onToggle24hClock={handleToggle24hClock} />
<TimeOptions
onSelectTimeZone={handleSelectTimeZone}
onToggle24hClock={handleToggle24hClock}
timeFormat={timeFormat}
/>
</Collapsible.Content>
</Collapsible.Root>
);
@@ -289,10 +303,14 @@ const dateQuerySchema = z.object({
const useRouterQuery = <T extends string>(name: T) => {
const router = useRouter();
const query = z.object({ [name]: z.string().optional() }).parse(router.query);
const existingQueryParams = router.asPath.split("?")[1];
const urlParams = new URLSearchParams(existingQueryParams);
const query = Object.fromEntries(urlParams);
const setQuery = (newValue: string | number | null | undefined) => {
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
};
return { [name]: query[name], setQuery } as {
@@ -305,7 +323,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
const isEmbed = useIsEmbed();
const query = dateQuerySchema.parse(router.query);
const { rescheduleUid } = query;
const { Theme } = useTheme(profile.theme);
useTheme(profile.theme);
const { t } = useLocale();
const { contracts } = useContracts();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
@@ -350,6 +368,8 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
}
}, [telemetry]);
// get dynamic user list here
const userList = eventType.users.map((user) => user.username).filter(notEmpty);
// Recurring event sidebar requires more space
const maxWidth = isAvailableTimesVisible
? recurringEventCount
@@ -361,22 +381,29 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
const timezoneDropdown = useMemo(
() => (
<TimezoneDropdown
timeFormat={timeFormat}
onChangeTimeFormat={setTimeFormat}
timeZone={timeZone}
onChangeTimeZone={setTimeZone}
/>
),
[timeZone]
[timeZone, timeFormat]
);
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const slug = rawSlug.join("/");
return (
<>
<Theme />
<HeadSeo
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
name={profile.name || undefined}
username={profile.slug || undefined}
username={slug || undefined}
nextSeoProps={{
nofollow: eventType.hidden,
noindex: eventType.hidden,
}}
/>
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
<div>
@@ -397,20 +424,10 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{/* mobile: details */}
<div className="block px-4 pt-4 sm:p-8 md:hidden">
<div>
<AvatarGroup
border="border-2 dark:border-gray-800 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
...eventType.users
.filter((user) => user.name !== profile.name)
.map((user) => ({
title: user.name,
image: `${CAL_URL}/${user.username}/avatar.png`,
alt: user.name || undefined,
})),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
}
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={9}
truncateAfter={5}
/>
@@ -420,16 +437,18 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
<h1 className="text-bookingdark mb-4 break-words text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
<div className="flex flex-col space-y-4">
<div className="flex flex-col space-y-2">
{eventType?.description && (
<p className="text-gray-600 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{eventType.description}
</p>
<div className="flex py-1 text-sm font-medium text-gray-600 dark:text-white">
<div>
<Icon.FiInfo className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
</div>
<EventTypeDescriptionSafeHTML eventType={eventType} />
</div>
)}
{eventType?.requiresConfirmation && (
<p className="text-gray-600 dark:text-white">
<ClipboardCheckIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{t("requires_confirmation")}
</p>
)}
@@ -438,9 +457,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiVideo className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiMapPin className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
)}
{locationKeyToString(eventType.locations[0], t)}
@@ -449,7 +468,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{eventType.locations.length > 1 && (
<div className="flex-warp flex text-gray-600 dark:text-white">
<div className="mr-[10px] ml-[2px] -mt-1 ">
<LocationMarkerIcon className="inline-block h-4 w-4 text-gray-400" />
<Icon.FiMapPin className="inline-block h-4 w-4 text-gray-500" />
</div>
<p>
{eventType.locations.map((el, i, arr) => {
@@ -466,12 +485,12 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</div>
)}
<p className="text-gray-600 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-500" />
{eventType.length} {t("minutes")}
</p>
{eventType.price > 0 && (
<div className="text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 dark:text-gray-400" />
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 dark:text-gray-500" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
@@ -482,9 +501,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</div>
)}
{!rescheduleUid && eventType.recurringEvent && (
<div className="text-gray-600 dark:text-white">
<RefreshIcon className="float-left mr-[10px] mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<div className="ml-[27px]">
<div className="flex items-center text-gray-600 dark:text-white">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-1 ml-[2px] inline-block h-4 w-4 shrink-0 text-gray-500" />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
@@ -492,7 +511,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 h-7 rounded-sm border-gray-300 bg-white text-gray-600 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white sm:text-sm"
className="w-15 h-7 rounded-sm border-gray-300 bg-white text-sm text-gray-600 [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
@@ -517,7 +536,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{typeof booking.startTime === "string" &&
parseDate(dayjs(booking.startTime), i18n)}
</p>
@@ -530,77 +549,67 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</div>
</div>
<div className="p-4 sm:flex sm:py-5">
<div className="overflow-hidden sm:flex">
<div
className={
"hidden overflow-hidden pr-8 sm:border-r sm:dark:border-gray-700 md:flex md:flex-col " +
"hidden overflow-hidden border-gray-200 p-5 sm:border-r sm:dark:border-gray-700 md:flex md:flex-col " +
(isAvailableTimesVisible ? "sm:w-1/3" : recurringEventCount ? "sm:w-2/3" : "sm:w-1/2")
}>
<AvatarGroup
border="border-2 dark:border-gray-800 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
...eventType.users
.filter((user) => user.name !== profile.name)
.map((user) => ({
title: user.name,
alt: user.name,
image: `${CAL_URL}/${user.username}/avatar.png`,
})),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
}
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={10}
truncateAfter={3}
/>
<h2 className="mt-3 break-words font-medium text-gray-500 dark:text-gray-300">
<h2 className="mt-2 break-words text-sm font-medium text-gray-500 dark:text-gray-300">
{profile.name}
</h2>
<h1 className="font-cal mb-4 break-words text-xl font-semibold text-gray-900 dark:text-white">
<h1 className="font-cal mb-6 break-words text-2xl text-gray-900 dark:text-white">
{eventType.title}
</h1>
<div className="flex flex-col space-y-4">
<div className="flex flex-col space-y-2">
{eventType?.description && (
<div className="flex text-gray-600 dark:text-white">
<div className="flex py-1 text-sm font-medium text-gray-600 dark:text-white">
<div>
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiInfo className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
</div>
<p>{eventType.description}</p>
<EventTypeDescriptionSafeHTML eventType={eventType} />
</div>
)}
{eventType?.requiresConfirmation && (
<div className="flex text-gray-600 dark:text-white">
<div className="flex items-center text-sm font-medium text-gray-600 dark:text-white">
<div>
<ClipboardCheckIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
</div>
{t("requires_confirmation")}
</div>
)}
{eventType.locations.length === 1 && (
<p className="text-gray-600 dark:text-white">
<p className="py-1 text-sm font-medium text-gray-600 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiVideo className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiMapPin className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length > 1 && (
<div className="flex-warp flex text-gray-600 dark:text-white">
<div className="flex-warp flex items-center font-medium text-gray-600 dark:text-white">
<div className="mr-[10px] ml-[2px] -mt-1 ">
<LocationMarkerIcon className="inline-block h-4 w-4 text-gray-400" />
<Icon.FiMapPin className="inline-block h-4 w-4 text-gray-500" />
</div>
<p>
<p className="py-1 text-sm font-medium text-gray-600 dark:text-white">
{eventType.locations.map((el, i, arr) => {
return (
<span key={el.type}>
{locationKeyToString(el, t)}{" "}
{arr.length - 1 !== i && (
<span className="font-light"> {t("or_lowercase")} </span>
<span className="text-sm font-light"> {t("or_lowercase")} </span>
)}
</span>
);
@@ -608,14 +617,14 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
</p>
</div>
)}
<p className="text-gray-600 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="py-1 text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-500" />
{eventType.length} {t("minutes")}
</p>
{!rescheduleUid && eventType.recurringEvent && (
<div className="text-gray-600 dark:text-white">
<RefreshIcon className="float-left mr-[10px] mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<div className="ml-[27px]">
<div className="flex items-center text-gray-600 dark:text-white">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-1 ml-[2px] inline-block h-4 w-4 text-gray-500" />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
@@ -623,7 +632,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 h-7 rounded-sm border-gray-300 bg-white text-gray-600 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white sm:text-sm"
className="w-15 h-7 rounded-sm border-gray-300 bg-white text-sm text-gray-600 [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
@@ -639,7 +648,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
)}
{eventType.price > 0 && (
<p className="-ml-2 px-2 py-1 text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-500" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
@@ -652,7 +661,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{timezoneDropdown}
</div>
<GoBackToPreviousPage t={t} />
{!isEmbed && <GoBackToPreviousPage t={t} />}
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
@@ -662,34 +671,27 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)*/}
</div>
{timeZone && (
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? ([
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
].indexOf(profile.weekStart) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
/>
)}
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
? (["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
profile.weekStart
) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
: profile.weekStart /* Allows providing weekStart as number */
}
eventType={eventType}
timeFormat={timeFormat}
timeZone={timeZone}
users={userList}
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
recurringEventCount={recurringEventCount}
/>
</div>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
+445 -395
View File
@@ -1,19 +1,8 @@
import {
CalendarIcon,
ClockIcon,
CreditCardIcon,
ExclamationCircleIcon,
ExclamationIcon,
InformationCircleIcon,
ClipboardCheckIcon,
RefreshIcon,
} from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import { EventTypeCustomInputType } from "@prisma/client";
import { useContracts } from "contexts/contractsContext";
import { EventTypeCustomInputType, WorkflowActions } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
import dynamic from "next/dynamic";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
@@ -24,35 +13,37 @@ import { useMutation } from "react-query";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { useContracts } from "@calcom/features/ee/web3/contexts/contractsContext";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { createPaymentLink } from "@calcom/stripe/client";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Button } from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { EmailInput, Form } from "@calcom/ui/form/fields";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import useTheme from "@lib/hooks/useTheme";
import { LocationObject, LocationType } from "@lib/location";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
import slugify from "@lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import CustomBranding from "@components/CustomBranding";
import AvatarGroup from "@components/ui/AvatarGroup";
import type PhoneInputType from "@components/ui/form/PhoneInput";
import { UserAvatars } from "@components/booking/UserAvatars";
import { BookPageProps } from "../../../pages/[user]/book";
import { HashLinkPageProps } from "../../../pages/d/[link]/book";
@@ -67,11 +58,6 @@ declare global {
};
}
/** These are like 40kb that not every user needs */
const PhoneInput = dynamic(
() => import("@components/ui/form/PhoneInput")
) as unknown as typeof PhoneInputType;
type BookingPageProps = (BookPageProps | TeamBookingPageProps | HashLinkPageProps) & {
locationLabels: Record<LocationType, string>;
};
@@ -88,6 +74,7 @@ type BookingFormValues = {
[key: string]: string | boolean;
};
rescheduleReason?: string;
smsReminderNumber?: string;
};
const BookingPage = ({
@@ -207,7 +194,7 @@ const BookingPage = ({
});
const rescheduleUid = router.query.rescheduleUid as string;
const { isReady, Theme } = useTheme(profile.theme);
useTheme(profile.theme);
const date = asStringOrNull(router.query.date);
const [guestToggle, setGuestToggle] = useState(booking && booking.attendees.length > 1);
@@ -232,11 +219,25 @@ const BookingPage = ({
? booking?.attendees.slice(1).map((attendee) => attendee.email)
: [];
// There should only exists one default userData variable for primaryAttendee.
const defaultUserValues = {
email: booking?.attendees[0].email
? booking.attendees[0].email
: router.query.email
? (router.query.email as string)
: "",
name: booking?.attendees[0].name
? booking.attendees[0].name
: router.query.name
? (router.query.name as string)
: "",
};
const defaultValues = () => {
if (!rescheduleUid) {
return {
name: (router.query.name as string) || (!loggedInIsOwner && session?.user?.name) || "",
email: (router.query.email as string) || (!loggedInIsOwner && session?.user?.email) || "",
name: defaultUserValues.name || (!loggedInIsOwner && session?.user?.name) || "",
email: defaultUserValues.email || (!loggedInIsOwner && session?.user?.email) || "",
notes: (router.query.notes as string) || "",
guests: ensureArray(router.query.guest) as string[],
customInputs: eventType.customInputs.reduce(
@@ -258,8 +259,8 @@ const BookingPage = ({
const customInputType = booking.customInputs;
return {
name: primaryAttendee.name || "",
email: primaryAttendee.email || "",
name: defaultUserValues.name,
email: defaultUserValues.email || "",
guests: guestListEmails,
notes: booking.description || "",
rescheduleReason: "",
@@ -283,6 +284,10 @@ const BookingPage = ({
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
smsReminderNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
})
.passthrough();
@@ -321,6 +326,18 @@ const BookingPage = ({
case LocationType.UserPhone: {
return locationInfo(locationType)?.hostPhoneNumber || "";
}
case LocationType.Around: {
return locationInfo(locationType)?.link || "";
}
case LocationType.Riverside: {
return locationInfo(locationType)?.link || "";
}
case LocationType.Whereby: {
return locationInfo(locationType)?.link || "";
}
case LocationType.Ping: {
return locationInfo(locationType)?.link || "";
}
// Catches all other location types, such as Google Meet, Zoom etc.
default:
return selectedLocation || "";
@@ -396,6 +413,8 @@ const BookingPage = ({
})),
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
}));
recurringMutation.mutate(recurringBookings);
} else {
@@ -421,18 +440,35 @@ const BookingPage = ({
})),
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
});
}
};
const disableInput = !!rescheduleUid;
// Should be disabled when rescheduleUid is present and data was found in defaultUserValues name/email fields.
const disableInput = !!rescheduleUid && !!defaultUserValues.email && !!defaultUserValues.name;
const disabledExceptForOwner = disableInput && !loggedInIsOwner;
const inputClassName =
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 disabled:dark:text-gray-500 sm:text-sm";
"focus:border-brand block w-full rounded-sm border-gray-300 focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 disabled:dark:text-gray-500 text-sm";
let isSmsReminderNumberNeeded = false;
if (eventType.workflows.length > 0) {
eventType.workflows.forEach((workflowReference) => {
if (workflowReference.workflow.steps.length > 0) {
workflowReference.workflow.steps.forEach((step) => {
if (step.action === WorkflowActions.SMS_ATTENDEE) {
isSmsReminderNumberNeeded = true;
return;
}
});
}
});
}
return (
<div>
<Theme />
<Head>
<title>
{rescheduleUid
@@ -455,402 +491,416 @@ const BookingPage = ({
isEmbed ? "" : "sm:my-24",
"my-0 max-w-3xl "
)}>
{isReady && (
<div
className={classNames(
"main overflow-hidden",
isEmbed ? "" : "border border-gray-200",
isBackgroundTransparent ? "" : "dark:border-1 bg-white dark:bg-gray-800",
"rounded-md dark:border-gray-600 sm:border"
)}>
<div className="px-4 py-5 sm:flex sm:p-4">
<div className="sm:w-1/2 sm:border-r sm:dark:border-gray-700">
<AvatarGroup
border="border-2 border-white dark:border-gray-800"
size={14}
items={[{ image: profile.image || "", alt: profile.name || "" }].concat(
eventType.users
.filter((user) => user.name !== profile.name)
.map((user) => ({
image: user.avatar || "",
alt: user.name || "",
}))
)}
/>
<h2 className="font-cal text-bookinglight mt-2 font-medium dark:text-gray-300">
{profile.name}
</h2>
<h1 className="text-bookingdark mb-4 text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{!!eventType.seatsPerTimeSlot && (
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} mb-2`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
)}
{eventType?.description && (
<p className="text-bookinglight mb-2 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{eventType.description}
</p>
)}
{eventType?.requiresConfirmation && (
<p className="text-bookinglight mb-2 dark:text-white">
<ClipboardCheckIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{t("requires_confirmation")}
</p>
)}
<p className="text-bookinglight mb-2 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
{eventType.length} {t("minutes")}
<div
className={classNames(
"main overflow-hidden",
isEmbed ? "" : "border border-gray-200",
isBackgroundTransparent ? "" : "dark:border-1 bg-white dark:bg-gray-800",
"rounded-md dark:border-gray-600 sm:border"
)}>
<div className="sm:flex">
<div className="px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6 sm:dark:border-gray-700">
<UserAvatars
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={14}
/>
<h2 className="font-cal text-bookinglight mt-2 font-medium dark:text-gray-300">
{profile.name}
</h2>
<h1 className="text-bookingdark mb-4 text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{!!eventType.seatsPerTimeSlot && (
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} mb-2`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
{eventType.price > 0 && (
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
)}
{eventType?.description && (
<p className="text-bookinglight mb-2 text-sm dark:text-white">
<Icon.FiInfo className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{eventType.description}
</p>
)}
{eventType?.requiresConfirmation && (
<p className="text-bookinglight mb-2 text-sm dark:text-white">
<Icon.FiClipboard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{t("requires_confirmation")}
</p>
)}
<p className="text-bookinglight mb-2 text-sm dark:text-white">
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
{eventType.length} {t("minutes")}
</p>
{eventType.price > 0 && (
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1 text-sm dark:text-white">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
style="currency"
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="mb-3 text-sm text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
</p>
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="mb-3 text-gray-600 dark:text-white">
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
</p>
</div>
)}
<div className="text-bookinghighlight mb-4 flex items-center text-sm">
<Icon.FiCalendar className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<div>
{(rescheduleUid || !eventType.recurringEvent?.freq) &&
parseDate(dayjs(date).tz(timeZone()), i18n)}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((aDate, key) => <p key={key}>{aDate}</p>)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((aDate, key) => (
<p key={key}>{aDate}</p>
))}>
<p className="text-sm text-gray-600 dark:text-white">
{t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
{eventTypeDetail.isWeb3Active && eventType.metadata.smartContractAddress && (
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1">
{t("requires_ownership_of_a_token") + " " + eventType.metadata.smartContractAddress}
</p>
)}
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm text-gray-600 dark:text-white" data-testid="former_time_p">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
</div>
<div className="p-6 sm:w-1/2">
<Form form={bookingForm} handleSubmit={bookEvent}>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
{t("your_name")}
</label>
<div className="mt-1">
<input
{...bookingForm.register("name", { required: true })}
type="text"
name="name"
id="name"
required
className={inputClassName}
placeholder={t("example_name")}
disabled={disableInput}
/>
</div>
)}
<div className="text-bookinghighlight mb-4 flex">
<CalendarIcon className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<div className="-mt-1">
{(rescheduleUid || !eventType.recurringEvent?.freq) &&
parseDate(dayjs(date).tz(timeZone()), i18n)}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((aDate, key) => <p key={key}>{aDate}</p>)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((aDate, key) => (
<p key={key}>{aDate}</p>
))}>
<p className="text-gray-600 dark:text-white">
{t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-white">
{t("email_address")}
</label>
<div className="mt-1">
<EmailInput
{...bookingForm.register("email")}
required
className={classNames(
inputClassName,
bookingForm.formState.errors.email
? "border-red-700 focus:ring-red-700"
: " border-gray-300 dark:border-gray-900"
)}
placeholder="you@example.com"
type="search" // Disables annoying 1password intrusive popup (non-optimal, I know I know...)
disabled={disableInput}
/>
{bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<p>{t("email_validation_error")}</p>
</div>
)}
</div>
</div>
{eventTypeDetail.isWeb3Active && eventType.metadata.smartContractAddress && (
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1">
{t("requires_ownership_of_a_token") + " " + eventType.metadata.smartContractAddress}
</p>
)}
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-gray-600 dark:text-white" data-testid="former_time_p">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
</div>
<div className="sm:w-1/2 sm:pl-8 sm:pr-4">
<Form form={bookingForm} handleSubmit={bookEvent}>
{locations.length > 1 && (
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
{t("your_name")}
</label>
<div className="mt-1">
<input
{...bookingForm.register("name", { required: true })}
type="text"
name="name"
id="name"
required
className={inputClassName}
placeholder={t("example_name")}
disabled={disableInput}
/>
</div>
<span className="block text-sm font-medium text-gray-700 dark:text-white">
{t("location")}
</span>
{locations.map((location, i) => (
<label key={i} className="block">
<input
type="radio"
className="location h-4 w-4 border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
{...bookingForm.register("locationType", { required: true })}
value={location.type}
defaultChecked={selectedLocation === location.type}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-gray-500">
{locationLabels[location.type]}
</span>
</label>
))}
</div>
)}
{selectedLocation === LocationType.Phone && (
<div className="mb-4">
<label
htmlFor="email"
htmlFor="phone"
className="block text-sm font-medium text-gray-700 dark:text-white">
{t("email_address")}
{t("phone_number")}
</label>
<div className="mt-1">
<EmailInput
{...bookingForm.register("email")}
<PhoneInput<BookingFormValues>
control={bookingForm.control}
name="phone"
placeholder={t("enter_phone_number")}
id="phone"
required
className={classNames(
inputClassName,
bookingForm.formState.errors.email
? "border-red-700 focus:ring-red-700"
: " border-gray-300 dark:border-gray-900"
)}
placeholder="you@example.com"
type="search" // Disables annoying 1password intrusive popup (non-optimal, I know I know...)
disabled={disableInput}
/>
{bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<ExclamationCircleIcon className="mr-2 h-3 w-3" />
<p>{t("email_validation_error")}</p>
</div>
)}
</div>
</div>
{locations.length > 1 && (
<div className="mb-4">
<span className="block text-sm font-medium text-gray-700 dark:text-white">
{t("location")}
</span>
{locations.map((location, i) => (
<label key={i} className="block">
<input
type="radio"
className="location h-4 w-4 border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
{...bookingForm.register("locationType", { required: true })}
value={location.type}
defaultChecked={selectedLocation === location.type}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-gray-500">
{locationLabels[location.type]}
</span>
</label>
))}
</div>
)}
{selectedLocation === LocationType.Phone && (
<div className="mb-4">
<label
htmlFor="phone"
className="block text-sm font-medium text-gray-700 dark:text-white">
{t("phone_number")}
</label>
<div className="mt-1">
<PhoneInput<BookingFormValues>
control={bookingForm.control}
name="phone"
placeholder={t("enter_phone_number")}
id="phone"
required
disabled={disableInput}
/>
{bookingForm.formState.errors.phone && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<p>{t("invalid_number")}</p>
</div>
{bookingForm.formState.errors.phone && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<ExclamationCircleIcon className="mr-2 h-3 w-3" />
<p>{t("invalid_number")}</p>
</div>
)}
</div>
)}
{eventType.customInputs
.sort((a, b) => a.id - b.id)
.map((input) => (
<div className="mb-4" key={input.id}>
{input.type !== EventTypeCustomInputType.BOOL && (
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
</label>
)}
</div>
)}
{eventType.customInputs
.sort((a, b) => a.id - b.id)
.map((input) => (
<div className="mb-4" key={input.id}>
{input.type !== EventTypeCustomInputType.BOOL && (
{input.type === EventTypeCustomInputType.TEXTLONG && (
<textarea
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
rows={3}
className={inputClassName}
placeholder={input.placeholder}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.TEXT && (
<input
type="text"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className={inputClassName}
placeholder={input.placeholder}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.NUMBER && (
<input
type="number"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className={inputClassName}
placeholder=""
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.BOOL && (
<div className="flex h-5 items-center">
<input
type="checkbox"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
placeholder=""
disabled={disabledExceptForOwner}
/>
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
</label>
)}
{input.type === EventTypeCustomInputType.TEXTLONG && (
<textarea
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
rows={3}
className={inputClassName}
placeholder={input.placeholder}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.TEXT && (
<input
type="text"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className={inputClassName}
placeholder={input.placeholder}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.NUMBER && (
<input
type="number"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className={inputClassName}
placeholder=""
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.BOOL && (
<div className="flex h-5 items-center">
<input
type="checkbox"
{...bookingForm.register(`customInputs.${input.id}`, {
required: input.required,
})}
id={"custom_" + input.id}
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
placeholder=""
disabled={disabledExceptForOwner}
/>
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
</label>
</div>
)}
</div>
))}
{!eventType.disableGuests && (
<div className="mb-4">
{!guestToggle && (
<label
onClick={() => setGuestToggle(!guestToggle)}
htmlFor="guests"
className="mb-1 block text-sm font-medium hover:cursor-pointer dark:text-white">
{/*<UserAddIcon className="inline-block w-5 h-5 mr-1 -mt-1" />*/}
{t("additional_guests")}
</label>
)}
{guestToggle && (
<div>
<label
htmlFor="guests"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{t("guests")}
</label>
{!disableInput && (
<Controller
control={bookingForm.control}
name="guests"
render={({ field: { onChange, value } }) => (
<ReactMultiEmail
className="relative"
placeholder="guest@example.com"
emails={value}
onChange={onChange}
getLabel={(
email: string,
index: number,
removeEmail: (index: number) => void
) => {
return (
<div data-tag key={index} className="cursor-pointer">
{email}
{!disableInput && (
<span data-tag-handle onClick={() => removeEmail(index)}>
×
</span>
)}
</div>
);
}}
/>
)}
/>
)}
{/* Custom code when guest emails should not be editable */}
{disableInput && guestListEmails && guestListEmails.length > 0 && (
<div data-tag className="react-multi-email">
{/* // @TODO: user owners are appearing as guest here when should be only user input */}
{guestListEmails.map((email, index) => {
return (
<div key={index} className="cursor-pointer">
<span data-tag>{email}</span>
</div>
);
})}
</div>
)}
</div>
)}
</div>
)}
))}
{!eventType.disableGuests && (
<div className="mb-4">
<label
htmlFor="notes"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{rescheduleUid ? t("reschedule_optional") : t("additional_notes")}
</label>
{rescheduleUid ? (
<textarea
{...bookingForm.register("rescheduleReason")}
id="rescheduleReason"
name="rescheduleReason"
rows={3}
className={inputClassName}
placeholder={t("reschedule_placeholder")}
disabled={disabledExceptForOwner}
/>
) : (
<textarea
{...bookingForm.register("notes")}
id="notes"
name="notes"
rows={3}
className={inputClassName}
placeholder={t("share_additional_notes")}
disabled={disabledExceptForOwner}
/>
{!guestToggle && (
<label
onClick={() => setGuestToggle(!guestToggle)}
htmlFor="guests"
className="mb-1 block text-sm font-medium hover:cursor-pointer dark:text-white">
{/*<UserAddIcon className="inline-block w-5 h-5 mr-1 -mt-1" />*/}
{t("additional_guests")}
</label>
)}
{guestToggle && (
<div>
<label
htmlFor="guests"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{t("guests")}
</label>
{!disableInput && (
<Controller
control={bookingForm.control}
name="guests"
render={({ field: { onChange, value } }) => (
<ReactMultiEmail
className="relative"
placeholder="guest@example.com"
emails={value}
onChange={onChange}
getLabel={(
email: string,
index: number,
removeEmail: (index: number) => void
) => {
return (
<div data-tag key={index} className="cursor-pointer">
{email}
{!disableInput && (
<span data-tag-handle onClick={() => removeEmail(index)}>
×
</span>
)}
</div>
);
}}
/>
)}
/>
)}
{/* Custom code when guest emails should not be editable */}
{disableInput && guestListEmails && guestListEmails.length > 0 && (
<div data-tag className="react-multi-email">
{/* // @TODO: user owners are appearing as guest here when should be only user input */}
{guestListEmails.map((email, index) => {
return (
<div key={index} className="cursor-pointer">
<span data-tag>{email}</span>
</div>
);
})}
</div>
)}
</div>
)}
</div>
<div className="flex items-start space-x-2 rtl:space-x-reverse">
<Button
type="submit"
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
loading={mutation.isLoading || recurringMutation.isLoading}>
{rescheduleUid ? t("reschedule") : t("confirm")}
</Button>
<Button color="secondary" type="button" onClick={() => router.back()}>
{t("cancel")}
</Button>
</div>
</Form>
{(mutation.isError || recurringMutation.isError) && (
<ErrorMessage error={mutation.error || recurringMutation.error} />
)}
</div>
{isSmsReminderNumberNeeded && selectedLocation !== LocationType.Phone && (
<div className="mb-4">
<label
htmlFor="smsReminderNumber"
className="block text-sm font-medium text-gray-700 dark:text-white">
{t("number_for_sms_reminders")}
</label>
<div className="mt-1">
<PhoneInput<BookingFormValues>
control={bookingForm.control}
name="smsReminderNumber"
placeholder={t("enter_phone_number")}
id="smsReminderNumber"
required
disabled={disableInput}
/>
</div>
{bookingForm.formState.errors.smsReminderNumber && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<p>{t("invalid_number")}</p>
</div>
)}
</div>
)}
<div className="mb-4">
<label
htmlFor="notes"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{rescheduleUid ? t("reschedule_optional") : t("additional_notes")}
</label>
{rescheduleUid ? (
<textarea
{...bookingForm.register("rescheduleReason")}
id="rescheduleReason"
name="rescheduleReason"
rows={3}
className={inputClassName}
placeholder={t("reschedule_placeholder")}
/>
) : (
<textarea
{...bookingForm.register("notes")}
id="notes"
name="notes"
rows={3}
className={inputClassName}
placeholder={t("share_additional_notes")}
disabled={disabledExceptForOwner}
/>
)}
</div>
<div className="flex items-start space-x-2 rtl:space-x-reverse">
<Button
type="submit"
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
loading={mutation.isLoading || recurringMutation.isLoading}>
{rescheduleUid ? t("reschedule") : t("confirm")}
</Button>
<Button color="secondary" type="button" onClick={() => router.back()}>
{t("cancel")}
</Button>
</div>
</Form>
{(mutation.isError || recurringMutation.isError) && (
<ErrorMessage error={mutation.error || recurringMutation.error} />
)}
</div>
</div>
)}
</div>
</main>
</div>
);
@@ -866,7 +916,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 className="flex">
<div className="flex-shrink-0">
<ExclamationIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />
<Icon.FiAlertTriangle className="h-5 w-5 text-yellow-400" aria-hidden="true" />
</div>
<div className="ltr:ml-3 rtl:mr-3">
<p className="text-sm text-yellow-700">
@@ -1,11 +1,10 @@
import { ExclamationIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/solid";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/Button";
import { DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
export type DeleteStripeDialogContentProps = {
cancelAllBookingsBtnText?: string;
@@ -37,17 +36,17 @@ export default function DeleteStripeDialogContent(props: PropsWithChildren<Delet
<div className="mt-0.5 ltr:mr-3">
{variety === "danger" && (
<div className="mx-auto rounded-full bg-red-100 p-2 text-center">
<ExclamationIcon className="h-5 w-5 text-red-600" />
<Icon.FiAlertTriangle className="h-5 w-5 text-red-600" />
</div>
)}
{variety === "warning" && (
<div className="mx-auto rounded-full bg-orange-100 p-2 text-center">
<ExclamationIcon className="h-5 w-5 text-orange-600" />
<Icon.FiAlertTriangle className="h-5 w-5 text-orange-600" />
</div>
)}
{variety === "success" && (
<div className="mx-auto rounded-full bg-green-100 p-2 text-center">
<CheckIcon className="h-5 w-5 text-green-600" />
<Icon.FiCheck className="h-5 w-5 text-green-600" />
</div>
)}
</div>
@@ -1,31 +1,25 @@
import { LocationMarkerIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import dynamic from "next/dynamic";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { z } from "zod";
import classNames from "@calcom/lib/classNames";
import { LocationOptionsToString } from "@calcom/app-store/locations";
import { LocationType } from "@calcom/core/location";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Form } from "@calcom/ui/form/fields";
import { QueryCell } from "@lib/QueryCell";
import { linkValueToString } from "@lib/linkValueToString";
import { LocationType } from "@lib/location";
import { LocationOptionsToString } from "@lib/locationOptions";
import { inferQueryOutput, trpc } from "@lib/trpc";
import CheckboxField from "@components/ui/form/CheckboxField";
import type PhoneInputType from "@components/ui/form/PhoneInput";
import Select from "@components/ui/form/Select";
const PhoneInput = dynamic(
() => import("@components/ui/form/PhoneInput")
) as unknown as typeof PhoneInputType;
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type OptionTypeBase = {
@@ -91,6 +85,11 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
.string()
.regex(/^http(s)?:\/\/(www\.)?around.co\/[a-zA-Z0-9]*/)
.optional()
: selection?.value === LocationType.Ping
? z
.string()
.regex(/^http(s)?:\/\/(www\.)?ping.gg\/call\/[a-zA-Z0-9]*/)
.optional()
: selection?.value === LocationType.Riverside
? z
.string()
@@ -154,7 +153,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}></CheckboxField>
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
@@ -172,7 +172,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
{...locationFormMethods.register("locationLink")}
required
id="link"
className="block w-full rounded-sm border-gray-300 sm:text-sm"
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
@@ -200,7 +200,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
: undefined
}
onChange={(e) => locationFormMethods.setValue("displayLocationPublicly", e.target.checked)}
informationIconText={t("display_location_info_badge")}></CheckboxField>
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
@@ -245,7 +246,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
id="wherebylink"
placeholder="https://www.whereby.com/cal"
required
className={"block w-full rounded-sm border-gray-300 text-sm"}
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
@@ -254,6 +255,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
: undefined
}
/>
{locationFormMethods.formState.errors.locationLink && (
<p className="mt-1 text-sm text-red-500">{t("invalid_whereby_link")}</p>
)}
</div>
{!booking && (
<div className="mt-3">
@@ -272,7 +276,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}></CheckboxField>
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
@@ -292,7 +297,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
id="aroundlink"
placeholder="https://www.around.co/rick"
required
className={"block w-full rounded-sm border-gray-300 text-sm"}
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
@@ -301,6 +306,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
: undefined
}
/>
{locationFormMethods.formState.errors.locationLink && (
<p className="mt-1 text-sm text-red-500">{t("invalid_around_link")}</p>
)}
</div>
{!booking && (
<div className="mt-3">
@@ -319,7 +327,59 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}></CheckboxField>
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
)}
</div>
</>
) : selectedLocation === LocationType.Ping ? (
<>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
{t("set_ping_link")}
</label>
<div className="mt-1">
<input
type="text"
{...locationFormMethods.register("locationLink")}
id="pinglink"
placeholder="https://www.ping.gg/call/theo"
required
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
(location: { type: LocationType }) => location.type === LocationType.Ping
)?.address
: undefined
}
/>
{locationFormMethods.formState.errors.locationLink && (
<p className="mt-1 text-sm text-red-500">{t("invalid_ping_link")}</p>
)}
</div>
{!booking && (
<div className="mt-3">
<Controller
name="displayLocationPublicly"
control={locationFormMethods.control}
render={() => (
<CheckboxField
defaultChecked={
defaultValues
? defaultValues.find((location) => location.type === LocationType.Ping)
?.displayLocationPublicly
: undefined
}
description={t("display_location_label")}
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
@@ -330,7 +390,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
{t("set_around_link")}
{t("set_riverside_link")}
</label>
<div className="mt-1">
<input
@@ -339,7 +399,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
id="aroundlink"
placeholder="https://www.riverside.fm/studio/rick"
required
className={"block w-full rounded-sm border-gray-300 text-sm"}
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
@@ -348,6 +408,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
: undefined
}
/>
{locationFormMethods.formState.errors.locationLink && (
<p className="mt-1 text-sm text-red-500">{t("invalid_riverside_link")}</p>
)}
</div>
{!booking && (
<div className="mt-3">
@@ -366,7 +429,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}></CheckboxField>
informationIconText={t("display_location_info_badge")}
/>
)}
/>
</div>
@@ -383,7 +447,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<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">
<LocationMarkerIcon className="text-primary-600 h-6 w-6" />
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
@@ -393,7 +457,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
)}
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"></div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" />
</div>
{booking && (
<>
@@ -417,7 +481,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
newLocation === LocationType.Link ||
newLocation === LocationType.Whereby ||
newLocation === LocationType.Around ||
newLocation === LocationType.Riverside
newLocation === LocationType.Riverside ||
newLocation === LocationType.Ping
) {
details = { link: values.locationLink, displayLocationPublicly };
}
@@ -454,8 +519,8 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
? locationOptions.filter((location) => location.value !== "phone")
: locationOptions
}
isSearchable={false}
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
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);
@@ -1,16 +1,16 @@
import { ClockIcon } from "@heroicons/react/outline";
import { RescheduleResponse } from "pages/api/book/request-reschedule";
import React, { useState, Dispatch, SetStateAction } from "react";
import { useMutation } from "react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { TextArea } from "@calcom/ui/form/fields";
import * as fetchWrapper from "@lib/core/http/fetch-wrapper";
import { trpc } from "@lib/trpc";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -58,7 +58,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<ClockIcon className="m-auto h-6 w-6"></ClockIcon>
<Icon.FiClock className="m-auto h-6 w-6" />
</div>
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
@@ -1,14 +1,17 @@
import { ChevronDownIcon, PlusIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import { SchedulingType } from "@prisma/client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
import classNames from "@calcom/lib/classNames";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import { Button } from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
@@ -19,11 +22,11 @@ import Dropdown, {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import { Icon } from "@calcom/ui/Icon";
import { Form, InputLeading, TextAreaField, TextField } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import { slugify } from "@lib/slugify";
import { trpc } from "@lib/trpc";
import Avatar from "@components/ui/Avatar";
import * as RadioArea from "@components/ui/form/radio-area";
@@ -36,7 +39,7 @@ export interface EventTypeParent {
image?: string | null;
}
interface Props {
interface CreateEventTypeBtnProps {
// set true for use on the team settings page
canAddEvents: boolean;
// set true when in use on the team settings page
@@ -45,7 +48,7 @@ interface Props {
options: EventTypeParent[];
}
export default function CreateEventTypeButton(props: Props) {
export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
const { t } = useLocale();
const router = useRouter();
@@ -141,41 +144,15 @@ export default function CreateEventTypeButton(props: Props) {
<Dialog
name="new-eventtype"
clearQueryParamsOnClose={["eventPage", "teamId", "type", "description", "title", "length", "slug"]}>
{!hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={PlusIcon}
disabled={!props.canAddEvents}>
{t("new_event_type_btn")}
</Button>
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button EndIcon={ChevronDownIcon}>{t("new_event_type_btn")}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("new_event_subtitle")}</DropdownMenuLabel>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{props.options.map((option) => (
<DropdownMenuItem
key={option.slug}
className="cursor-pointer px-3 py-2 hover:bg-neutral-100 focus:outline-none"
onSelect={() => openModal(option)}>
<Avatar
alt={option.name || ""}
imageSrc={option.image}
size={6}
className="inline ltr:mr-2 rtl:ml-2"
/>
{option.name ? option.name : option.slug}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</Dropdown>
)}
<CreateEventTypeTrigger
hasTeams={hasTeams}
canAddEvents={props.canAddEvents}
isIndividualTeam={props.isIndividualTeam}
openModal={openModal}
options={props.options}
/>
<DialogContent className="overflow-y-auto">
<DialogContent>
<div className="mb-4">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{teamId ? t("add_new_team_event_type") : t("add_new_event_type")}
@@ -257,7 +234,7 @@ export default function CreateEventTypeButton(props: Props) {
<RadioArea.Group
{...register("schedulingType")}
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
className="relative mt-1 flex space-x-6 rounded-sm shadow-sm rtl:space-x-reverse">
className="relative mt-1 flex space-x-6 rounded-sm rtl:space-x-reverse">
<RadioArea.Item
value={SchedulingType.COLLECTIVE}
defaultChecked={type === SchedulingType.COLLECTIVE}
@@ -289,3 +266,83 @@ export default function CreateEventTypeButton(props: Props) {
</Dialog>
);
}
type CreateEventTypeTrigger = {
isIndividualTeam?: boolean;
// EventTypeParent can be a profile (as first option) or a team for the rest.
options: EventTypeParent[];
hasTeams: boolean;
// set true for use on the team settings page
canAddEvents: boolean;
openModal: (option: EventTypeParent) => void;
};
export function CreateEventTypeTrigger(props: CreateEventTypeTrigger) {
const { t } = useLocale();
return (
<>
{!props.hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => props.openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={Icon.FiPlus}
disabled={!props.canAddEvents}>
{t("new_event_type_btn")}
</Button>
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button EndIcon={Icon.FiChevronDown}>{t("new_event_type_btn")}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("new_event_subtitle")}</DropdownMenuLabel>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{props.options.map((option) => (
<CreateEventTeamsItem
key={option.slug}
option={option}
openModal={() => props.openModal(option)}
/>
))}
</DropdownMenuContent>
</Dropdown>
)}
</>
);
}
function CreateEventTeamsItem(props: {
openModal: (option: EventTypeParent) => void;
option: EventTypeParent;
}) {
const session = useSession();
const membershipQuery = trpc.useQuery([
"viewer.teams.getMembershipbyUser",
{
memberId: session.data?.user.id as number,
teamId: props.option.teamId as number,
},
]);
const isDisabled = membershipQuery.data?.role === "MEMBER";
return (
<DropdownMenuItem
key={props.option.slug}
className={classNames(
"cursor-pointer px-3 py-2 focus:outline-none",
isDisabled ? "cursor-default !text-gray-300" : "hover:bg-neutral-100"
)}
disabled={isDisabled}
onSelect={() => props.openModal(props.option)}>
<Avatar
alt={props.option.name || ""}
imageSrc={props.option.image}
size={6}
className="inline ltr:mr-2 rtl:ml-2"
/>
{props.option.name ? props.option.name : props.option.slug}
</DropdownMenuItem>
);
}
@@ -1,11 +1,3 @@
import {
ClipboardCheckIcon,
ClockIcon,
CreditCardIcon,
RefreshIcon,
UserIcon,
UsersIcon,
} from "@heroicons/react/solid";
import { Prisma, SchedulingType } from "@prisma/client";
import { useMemo } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
@@ -13,6 +5,7 @@ import { FormattedNumber, IntlProvider } from "react-intl";
import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { Icon } from "@calcom/ui/Icon";
import classNames from "@lib/classNames";
@@ -44,34 +37,34 @@ export const EventTypeDescription = ({ eventType, className }: EventTypeDescript
{eventType.description.length > 100 && "..."}
</h2>
)}
<ul className="mt-2 flex flex-wrap sm:flex-nowrap">
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<ClockIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
{eventType.length}m
<ul className="mt-2 flex flex-wrap space-x-1 sm:flex-nowrap ">
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiClock className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
{eventType.length} {t("minutes")}
</li>
{eventType.schedulingType ? (
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<UsersIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiUsers className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
{eventType.schedulingType === SchedulingType.ROUND_ROBIN && t("round_robin")}
{eventType.schedulingType === SchedulingType.COLLECTIVE && t("collective")}
</li>
) : (
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<UserIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiUser className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
{t("1_on_1")}
</li>
)}
{recurringEvent?.count && recurringEvent.count > 0 && (
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<RefreshIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiRefreshCw className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
{t("repeats_up_to", {
count: recurringEvent.count,
})}
</li>
)}
{eventType.price > 0 && (
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<CreditCardIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiCreditCard className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
<IntlProvider locale="en">
<FormattedNumber
value={eventType.price / 100.0}
@@ -82,8 +75,8 @@ export const EventTypeDescription = ({ eventType, className }: EventTypeDescript
</li>
)}
{eventType.requiresConfirmation && (
<li className="mr-4 mb-1 flex items-center whitespace-nowrap">
<ClipboardCheckIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
<li className="mb-1 flex items-center whitespace-nowrap rounded-sm bg-gray-100 px-1 py-px text-xs text-gray-800 dark:bg-gray-900 dark:text-white">
<Icon.FiCheckSquare className="mr-1.5 inline h-3 w-3" aria-hidden="true" />
{t("requires_confirmation")}
</li>
)}
@@ -0,0 +1,12 @@
export type EventTypeDescriptionSafeProps = {
eventType: { description: string | null };
};
export const EventTypeDescriptionSafeHTML = ({ eventType }: EventTypeDescriptionSafeProps) => {
const props: JSX.IntrinsicElements["div"] = { suppressHydrationWarning: true };
// @ts-expect-error: @see packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts
if (eventType.description) props.dangerouslySetInnerHTML = { __html: eventType.descriptionAsSafeHTML };
return <div {...props} />;
};
export default EventTypeDescriptionSafeHTML;
@@ -4,7 +4,7 @@ import { UseFormReturn } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Frequency } from "@calcom/prisma/zod-utils";
import { RecurringEvent } from "@calcom/types/Calendar";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
import Select from "@components/ui/form/Select";
@@ -82,7 +82,7 @@ export default function RecurringEventController({
type="number"
min="1"
max="20"
className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
className="block w-16 rounded-sm border-gray-300 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
defaultValue={recurringEventState.interval}
onChange={(event) => {
const newVal = {
@@ -97,7 +97,7 @@ export default function RecurringEventController({
options={recurringEventFreqOptions}
value={recurringEventFreqOptions[recurringEventState.freq]}
isSearchable={false}
className="w-18 block min-w-0 rounded-sm sm:text-sm"
className="w-18 block min-w-0 rounded-sm text-sm"
onChange={(event) => {
const newVal = {
...recurringEventState,
@@ -114,7 +114,7 @@ export default function RecurringEventController({
type="number"
min="1"
max="20"
className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
className="block w-16 rounded-sm border-gray-300 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
defaultValue={recurringEventState.count}
onChange={(event) => {
const newVal = {

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