Compare commits

...
69 Commits
Author SHA1 Message Date
Alex van Andel 25d684ed3e v2.1.4
release.yml / release (push) Canceled after 0s
2022-11-09 16:29:23 +00:00
Alex van Andel 195cb84a8b Disable ADMIN role when password requirements aren't met (#5445) 2022-11-09 16:23:39 +00:00
4466c4ad4b Reverted Sentry for debug purposes (#5425)
* Reverted Sentry for debug purposes

* Downgraded yarn.lock

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-09 16:10:31 +00:00
4b532646cb fix: back url (#5414)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-09 15:57:23 +00:00
Udit Takkar 6d42fdb71c chore: fix typo (#5432) 2022-11-09 15:45:42 +00:00
Bailey Pumfleet 1b23671812 Update password length requirement to 8 characters (#5438) 2022-11-09 12:42:30 +00:00
Leo GiovanettiandAlex van Andel 7d1fb7c659 Fixing count and redundant checks for recurring (#5426)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-08 20:59:44 +00:00
zomars 97b99deb5d Adds release action 2022-11-08 13:32:18 -07:00
zomars 4803c956dc DB previews take 2 2022-11-08 13:27:55 -07:00
zomars 8ff5d476d9 Sync submodules 2022-11-08 10:48:54 -07:00
Alex van Andel 4421981da7 v2.1.3 2022-11-08 16:55:55 +00:00
Udit TakkarandAlex van Andel 87ffedbebd fix: use hidden (#5422)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-08 16:50:13 +00:00
Udit TakkarandAlex van Andel 2ba5e6cdd4 fix: update form methods when username is updated (#5403)
* fix: update form methods when username is updated

* Using RHF to decrease state logic in profile.tsx for UsernameAvailability (#5405)

* Type fixes & fixed onboarding

* Removed undefined allowed value + removed invalidation

* No longer need usernameRef.current.value

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-08 16:36:14 +00:00
Alex van Andel 69100a0fe8 Hotfix foundConflict = false on init 2022-11-08 16:12:31 +00:00
Colin Griffin cde02e1a47 Check for existence of NEXTAUTH_URL before overwriting (#5419)
* check for existence of NEXTAUTH_URL before overwriting

* add comment
2022-11-08 15:04:18 +00:00
deeb8f38e5 Support header in Routing Form CSV (#5133)
* Dont actually delete fields and now add headers to CSV which is possible

* Fix TS issues

* Handle cases due to soft deletion

* Add submission time as a column

* Fix bug with fields getting deleted

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-08 14:21:53 +00:00
Hariom Balharaandkodiakhq[bot] 85d7341668 Downgrade plugin to match vite version (#5410)
* Downgrade plugin to match vite version

* Update yarn.lock

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-08 08:51:24 +00:00
+1 8593da4e25 [CAL-106] /onboarding - UI issues & small improvements (#5396)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-08 07:49:44 +00:00
Alex van Andel 8b5d57c573 Cleanup + migrate away from process.browser (#5408)
* Cleanup + migrate away from process.browser

* Bring back HeadSeo in ui/v2
2022-11-08 08:36:59 +01:00
Alex van Andel 7e19e0d9e2 Add data?.isDefault to disabled={} prop (#5407) 2022-11-08 00:09:57 +00:00
Alex van Andel 47a663f5a9 Fail lint check on error (#5398) 2022-11-07 14:12:12 -07:00
Leo GiovanettiandPeer Richelsen e5ba861621 Sendgrid app and code simplification (#5269)
* Sendgrid app and code simplification

* Applying app-store-cli + impl

* Fixing types

* Adding features to readme

* Fixing unit tests

* A few last tweaks regarding UX and env vars

* Applying feedback

* Using calcom icons

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-07 14:52:03 -03:00
Alex van Andel 82a6ab7957 Circumvents Dayjs issue by going native (#5363) 2022-11-07 15:16:35 +00:00
Alex van Andel 22dad1ae7c Fix lint error that made its way into main 2022-11-07 10:37:50 +00:00
github-actions[bot]andCrowdin Bot f1208eadc0 New Crowdin translations by Github Action (#5391)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-11-06 19:07:40 +00:00
Om RayandPeer Richelsen d9a867bbde Change Zap automation UI issues (#5355)
* changed styles for Zap automation ui to match figma

* Increased margintop value

* increase margintop value to mt-4

* fixed some styling issues

* fixed issue with buttons being at different heights

* changed vertical positioning of text and increased min height

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-06 16:09:10 +00:00
Hariom Balharaandkodiakhq[bot] 7625950950 Remove unsupported operators by JSOnLogic (#5394)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-06 09:33:44 -05:00
bf68ec1ad5 use LOGO constant instead of fixed string in OgImages (#5234)
* use LOGO constant instead of fixed string in OgImages

* fixed linting error

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-05 20:20:59 +00:00
Om RayandPeer Richelsen 0fa7982084 Guests will be able to add their own location for in person meetings (#5282)
* Guests will be able to add their own location or in person meetings

* fix type error

* fixed issue with attendees location not showing on success page

* google meet booking will now go through, removed unnecessary change

* filter out attendee address when editing location

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-05 20:10:10 +00:00
Nafees NazikandPeer Richelsen a419613d29 chore: convert anchor tag to p tag (#5370)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-05 19:26:48 +00:00
alannnc dff08dc5b9 fix/google-calendar-removes-attendees-when-seats-5336 (#5366)
* Adding missing code that copied booking attendees to event attendees

* Clean up code

* Fix types

* Fix types

* Doing deep clone for sending seats email
2022-11-05 18:58:35 +00:00
alannncandAlex van Andel 7517feb62a fix/success-page-seats-description-5338 (#5390)
* Add validations for server side loading of bookingInfo for seats event types

* Fix prop used for validating if booking has seats enabled

* Fix types

* Removed frontend filter for seatsShowAttendees as not needed anymore

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-05 14:47:29 -04:00
5954144b98 Make sure body being jsoned is fresh (#5371)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-05 18:13:34 +00:00
Hariom BalharaandAlex van Andel a8c4a9c4c2 Fix viewer.me avatar size (#5392)
* Fix viewer.me avatar size

* Fix for i18n-next typecheck

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-05 18:00:10 +00:00
Alex van Andel b1bef50ba8 Downgrade vite (#5389) 2022-11-05 11:40:00 +05:30
Alex van Andel 5030245ad5 /settings -> /settings/.. redirect in next.config.js is server only (#5379) 2022-11-04 16:30:12 -04:00
zomars 2aa84cd5ce Sync package.json 2022-11-04 12:15:12 -07:00
Alex van Andel 324794e36c Fixed the profile page (#5378) 2022-11-04 13:25:07 -04:00
Leo GiovanettiandAlex van Andel 25e3e31045 Improving recurring info in bookings (#5253)
* Improving recurring info in bookings

* Adding fixes to success page, it was invalid on custom tz

* Remove dead code

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-04 12:43:02 -04:00
+6 9d1a0637f4 [CAL-153] Wrong redirect when clicking user or teams avatar in event types (#5348)
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: MuriloAmarals <muralha2000@gmail.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: MuriloAmarals <muralha2000@gmail.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
2022-11-04 15:54:45 +00:00
b5b41da183 Cleaning up storybook files (#5290)
* storybook v2 init

* Merge config into storybook vite build

* Remove path

* Storybook config tweaks

* Added styles and settings for storybook v2, and started working on button documentation and examples.

* Badges + flex wrap on mobile

* Breadcrumbs+button+avatar

* Checkbox

* Input + moving files around

* WIP table

* WIP table grid

* Replaced imports for new components.

* Added first steps for varianttable.

* Small alignment fix.

* Custom Args Table - With scrollbar

* Adding table to components that need it + darkmode

* Add intro

* Fix types

* Remove V1 storybook and replace with V2

* Fix badge type error

* Fixed storybook dependencies

* Added cover image to storybook

* Remove vita from ts config, we dont use vite.

* Fixed button import.

* Explained postcss pseudo plugin.

* Fixed badge import.

* Add Avatar Stories

* ButtonGroup Stories

* Fixed imports

* Add checkbox stories

* Add  exports for differnt types of inputs

* Fix form and text area input

* Fix mass import errors

Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-04 15:40:46 +00:00
Udit TakkarandAlex van Andel acad0619f5 fix: callback url (#5372)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-04 15:20:17 +00:00
Alex van Andel db9911a264 Bugfix/5271 wrong availability displayed on nov 6 (#5365)
* Fixes the localisation issue with recurring events

* Implement DST as Dayjs doesn't

* Fixed generated booking URL when different TZ is set

* manually apply DST offset to times

* Fix type error
2022-11-04 10:59:38 -04:00
Alex van Andel 328a354f4d Check for working hours + rename isAvailable (#5342)
* Check for working hours + rename isAvailable

* Return did not make it all the way back to callee

* Removed redundant break clause

* Fixes forEach return
2022-11-04 11:36:11 +00:00
Om Rayandalannnc eb14c1b796 When using seats, other guests (name, email) are no longer always listed on the success page (#5361)
* fixed incorrect ternary operator

* Update apps/web/pages/success.tsx

Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-03 21:43:37 -07:00
Nafees Nazikandkodiakhq[bot] 179a9b7139 feat: Improve edit availability workflow (#5159)
* feat: return data fromsetupDefaultSchedule

* feat: update returned data of "schedule.update" resolver as same as "schedule" query resolver.

* feat: set query data from updated results and invalidate prev default availability if it's changed.

* chore: add comments

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-03 20:58:52 +00:00
Omar López 3f486a9b99 Revert "Fix (#5293)" (#5359)
This reverts commit f934dc6f4f.
2022-11-03 13:29:14 -07:00
Alex van Andelandalannnc b0f2ce4818 Fixes recurring hard crash in two places to ensure rrule gets valid parameters (#5350)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-03 19:14:19 +00:00
Hariom Balharaandkodiakhq[bot] d7e9bb10b8 Routing Forms - UI Fixes (#5353)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-03 15:17:52 +00:00
4c24614e06 fix: use matcher in middleware (#5304)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-03 14:51:43 +00:00
6a002b900f Send Email to Owner on Form Submission (#5261)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-03 20:10:03 +05:30
Hariom BalharaandAlex van Andel d751cca0f4 Add "Choose common schedule toggle" to team events (#5343)
* Add Choose common schedule togglee

* Fix types

* Add translations

* Improve variable name

* Move setting to config so that all such lightweight boolean settings can exist here

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

Co-authored-by: Alex van Andel <me@alexvanandel.com>

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-03 14:24:07 +00:00
Udit Takkarandkodiakhq[bot] 328f00a9b5 fix: add hour12 (#5349)
* fix: add hour12

* fix: timeformat variable

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-03 12:28:39 +00:00
alannncandHariom Balhara e6a5fe179b feature/sentry.io for web (#5326)
* Initial commits that install sentry.io on web app

* Updates yarn.lock

* Update yarn.lock

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-11-03 11:28:52 +00:00
Carina WollendorferandCarinaWolli b629333eb8 fix dynamic location variable (#5345)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-03 10:40:42 +01:00
06eca4b28a Make SMS workflow actions only available for teams plan (#5330)
* fix issue when updating workflow

* make SMS actions pro feature

* Improved clean up on inline functions

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
2022-11-03 09:24:41 +01:00
f0ca797902 New Crowdin translations by Github Action (#5323)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-02 11:17:43 +00:00
+6 3efd062dbf /getting-started/setup-availability the table of days breaks depending on the language (#5104)
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: MuriloAmarals <muralha2000@gmail.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Grace Nshokano <grace.devolop@gmail.com>
Co-authored-by: MuriloAmarals <muralha2000@gmail.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-02 11:17:19 +00:00
0959cf7cd5 fix saving issue (#5328)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-02 10:06:08 +00:00
sean-brydon f34d6e3c17 Buffer limits fix (#5248)
* Buffer limits + remove redundant tests

* Fixing buffer

* Compound

* Afterbuffer fix for no event afterbuffer set

* Bug fixes

* Buffer includes eventType before
2022-11-02 09:40:30 +00:00
Leo Giovanetti edabf27929 Duplicate meeting within non-traditional calendars (#5208)
* Filter calendars and don't return

* Adding comment

* Applying feedback
2022-11-01 16:07:28 +00:00
Alex van Andelandkodiakhq[bot] 98f6fd836e Fixed description by manually implementing asSafeHTML (#5321)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-01 15:42:48 +00:00
Udit Takkarandkodiakhq[bot] a8710ce625 fix: add dark mode (#5318)
* fix: add dark mode

* chore: padding

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-01 14:14:18 +00:00
3043294a08 New Crowdin translations by Github Action (#5306)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-01 14:00:19 +00:00
66c2537f9f fix: use timeformat from getMe query (#5314)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-11-01 13:59:44 +00:00
sean-brydonandkodiakhq[bot] b3d74e575a Provide update feedback (#5319)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-01 13:56:46 +00:00
sean-brydon 190d0435e0 Improve error handling (#5316) 2022-11-01 13:45:02 +00:00
Jeroen Reumkens d0338dc71b 🧹 Cleaned up older v1 components for availability, settings and auth. (#5288)
* Cleaned up older v1 components for availability, settings and auth.

* Updated path.
2022-11-01 13:29:01 +00:00
Hariom Balharaandkodiakhq[bot] 2df8f4f145 Fix profile page data reset on re-focus of window (#5311)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-01 18:22:32 +05:30
379 changed files with 4245 additions and 5301 deletions
+4 -1
View File
@@ -77,7 +77,7 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
SEND_FEEDBACK_EMAIL=
# Sengrid
# Used for email reminders in workflows
# Used for email reminders in workflows and internal sync services
SENDGRID_API_KEY=
SENDGRID_EMAIL=
@@ -135,5 +135,8 @@ NEXT_PUBLIC_TEAM_IMPERSONATION=false
# Close.com internal CRM
CLOSECOM_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Sendgrid internal email sender
SENDGRID_API_KEY=
@@ -5,7 +5,7 @@ name: 'Chromatic'
# Event for the workflow
on:
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
paths:
+1 -1
View File
@@ -1,6 +1,6 @@
name: Check types
on:
pull_request:
pull_request_target:
branches:
- main
paths:
+37
View File
@@ -0,0 +1,37 @@
# .github/workflows/preview.yml
name: Preview Environment
env:
SNAPLET_ACCESS_TOKEN: ${{ secrets.SNAPLET_ACCESS_TOKEN }}
SNAPLET_PROJECT_ID: ${{ secrets.SNAPLET_PROJECT_ID }}
VERCEL_ACCESS_TOKEN: ${{ secrets.VERCEL_ACCESS_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
pull_request:
types: [opened, synchronize, closed]
branches:
- main
jobs:
db-preview-deploy:
if: ${{ github.event.action == 'opened' || github.event.action == 'synchronize' }}
runs-on: ubuntu-latest
steps:
- id: snaplet
uses: snaplet/action@v1
- uses: snaplet/vercel-action@v1
with:
env: |
DATABASE_URL=${{ steps.snaplet.outputs.database-url }}
db-preview-delete:
if: ${{ github.event.action == 'closed' }}
runs-on: ubuntu-latest
steps:
- uses: snaplet/action@v1
with:
delete: true
- uses: snaplet/vercel-action@v1
with:
delete: true
+1 -1
View File
@@ -2,7 +2,7 @@ name: E2E App-Store Apps
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
paths-ignore:
+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
@@ -2,7 +2,7 @@ name: E2E test
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
paths:
+1 -1
View File
@@ -1,6 +1,6 @@
name: "Pull Request Labeler"
on:
- pull_request
- pull_request_target
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+5 -2
View File
@@ -1,6 +1,6 @@
name: Lint
on:
pull_request:
pull_request_target:
branches:
- main
paths:
@@ -30,7 +30,10 @@ jobs:
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Lint
- name: Test Code Linting
run: yarn lint
- name: Save Code Linting Reports
run: yarn lint:report
continue-on-error: true
+11
View File
@@ -0,0 +1,11 @@
on:
push:
# Pattern matched against refs/tags
tags:
- "*" # Push events to every tag not containing /
jobs:
release:
runs-on: ubuntu-latest
steps:
- run: curl -X POST -d {} ${{ secrets.VERCEL_VERSIONED_DEPLOY_HOOK }}
+1 -1
View File
@@ -2,7 +2,7 @@ name: Unit tests
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request: # So we can test on forks
pull_request_target: # So we can test on forks
branches:
- main
paths:
+4
View File
@@ -50,6 +50,10 @@
"description": "Sendgrid api key. Used for email reminders in workflows",
"value": ""
},
"SENDGRID_SYNC_API_KEY": {
"description": "Sendgrid internal sync service",
"value": ""
},
"SENDGRID_EMAIL": {
"description": "Sendgrid email. Used for email reminders in workflows",
"value": ""
+1 -1
View File
@@ -17,7 +17,7 @@
"license": "MIT",
"dependencies": {
"iframe-resizer-react": "^1.1.0",
"next": "^12.2.5",
"next": "^12.3.1",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^18.2.0",
Executable → Regular
+19 -33
View File
@@ -1,38 +1,24 @@
# 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/
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
# debug
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
pnpm-debug.log*
lerna-debug.log*
# local env files
.env*.local
node_modules
dist
dist-ssr
*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
storybook-static/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+9
View File
@@ -0,0 +1,9 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
i18n.use(initReactI18next).init({
resources: [],
debug: true,
});
export default i18n;
+14 -20
View File
@@ -1,34 +1,29 @@
const path = require("path");
module.exports = {
stories: ["../stories/**/*.stories.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"],
stories: [
"../intro.stories.mdx",
"../../../packages/ui/components/**/*.stories.mdx",
"../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-addon-designs",
"@storybook/addon-a11y",
"storybook-addon-next",
"storybook-react-i18next",
{
name: "storybook-addon-next",
options: {
nextConfigPath: path.resolve(__dirname, "../../web/next.config.js"),
},
},
],
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"];
config.resolve.alias = {
...config.resolve.alias,
"@/interfaces": path.resolve(__dirname, "../interfaces"),
};
/**
* 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}}
*/
staticDirs: ["../public"],
webpackFinal: async (config, { configType }) => {
config.resolve.fallback = {
fs: false,
assert: false,
@@ -55,7 +50,6 @@ module.exports = {
vm: false,
zlib: false,
};
return config;
},
};
@@ -1,4 +1,5 @@
<script>
window.global = window;
window.isEmbed = ()=> {
return location.search.includes("embed=")
}
@@ -1,14 +1,9 @@
import { RouterContext } from "next/dist/shared/lib/router-context";
import * as NextImage from "next/image";
import { addDecorator } from "@storybook/react";
import { I18nextProvider } from "react-i18next";
import "../styles/globals.css";
const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, "default", {
configurable: true,
value: (props) => <OriginalNextImage {...props} unoptimized />,
});
import "../styles/storybook-styles.css";
import i18n from "./i18next";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
@@ -18,11 +13,10 @@ export const parameters = {
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
},
};
addDecorator((storyFn) => <I18nextProvider i18n={i18n}>{storyFn()}</I18nextProvider>);
window.getEmbedNamespace = () => {
const url = new URL(document.URL);
const namespace = url.searchParams.get("embed");
-21
View File
@@ -1,21 +0,0 @@
# 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
```
@@ -0,0 +1,23 @@
import { ArgsTable } from "@storybook/addon-docs";
import { SortType } from "@storybook/components";
import { PropDescriptor } from "@storybook/store";
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore storybook addon types component as any so we have to do
type Component = any;
type BaseProps = {
include?: PropDescriptor;
exclude?: PropDescriptor;
sort?: SortType;
};
type OfProps = BaseProps & {
of: "." | "^" | Component;
};
export function CustomArgsTable({ of, sort }: OfProps) {
return (
<div className="custom-args-wrapper">
<ArgsTable of={of} sort={sort} />
</div>
);
}
+31
View File
@@ -0,0 +1,31 @@
import { classNames } from "@calcom/lib";
interface ExampleProps {
children: React.ReactNode;
title: string;
}
export const Example = ({ children, title }: ExampleProps) => {
return (
<div className="examples-item">
<span className="examples-item-title">{title}</span>
<div className="examples-item-content">{children}</div>
</div>
);
};
interface ExamplesProps {
children: React.ReactNode;
title: string;
footnote?: React.ReactNode;
dark?: boolean;
}
export const Examples = ({ children, title, footnote = null, dark }: ExamplesProps) => {
return (
<div className={classNames("examples", dark && "dark")}>
<h2 className="examples-title">{title}</h2>
<div className="examples-content">{children}</div>
{!!footnote && <div className="examples-footnote">{footnote}</div>}
</div>
);
};
+5
View File
@@ -0,0 +1,5 @@
export const Note = ({ children }: { children: React.ReactNode }) => (
<div className="story-note">
<div>{children}</div>
</div>
);
+21
View File
@@ -0,0 +1,21 @@
export const Title = ({
title,
suffix,
subtitle,
offset,
}: {
title: string;
suffix?: string;
subtitle?: string;
offset?: boolean;
}) => {
return (
<div className={`story-title ${offset && "offset"}`}>
<h1>
{title}
{suffix && <span>{suffix}</span>}
</h1>
{subtitle && <p>{subtitle}</p>}
</div>
);
};
@@ -0,0 +1,80 @@
import React, { ReactElement, ReactNode } from "react";
import { classNames } from "@calcom/lib";
export function VariantsTable({
children,
titles,
isDark,
columnMinWidth = 150,
}: {
children: ReactElement<RowProps> | ReactElement<RowProps>[];
titles: string[];
isDark?: boolean;
// Mainly useful on mobile, so components don't get squeesed
columnMinWidth?: number;
}) {
const columns = React.Children.toArray(children) as ReactElement<RowProps>[];
return (
<div
className={classNames(
isDark &&
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#22252A]"
)}>
<div className="z-1 relative mx-auto w-full max-w-[1200px] overflow-auto pr-8 pt-6">
<table>
<RowTitles titles={titles} />
{columns.map((column) => (
<tr className="p-2 pr-6 pb-6" key={column.props.variant}>
<th
className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]"
key={column.props.variant}>
{column.props.variant}
</th>
{React.Children.count(column.props.children) &&
React.Children.map(column.props.children, (cell) => (
<td className="p-2 pr-6 pb-6" style={{ minWidth: `${columnMinWidth}px` }}>
{cell}
</td>
))}
</tr>
))}
</table>
</div>
{!isDark && (
<div data-mode="dark">
<VariantsTable titles={titles} isDark columnMinWidth={columnMinWidth}>
{children}
</VariantsTable>
</div>
)}
</div>
);
}
interface RowProps {
variant: string;
children: ReactNode;
}
/**
* There are two reasons we have this "empty" wrapper component:
* 1. In order to have an isolate group per variant, which we iterate through in the table component.
* 2. To have a way to pass the variant.
*/
export function VariantRow({ children }: RowProps) {
return <>{children}</>;
}
export function RowTitles({ titles }: { titles: string[] }) {
return (
<tr>
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" />
{titles.map((title) => (
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
{title}
</th>
))}
</tr>
);
}
+5
View File
@@ -0,0 +1,5 @@
export * from "./Examples";
export * from "./Note";
export * from "./VariantsTable";
export * from "./Title";
export * from "./CustomArgsTable";
+11
View File
@@ -0,0 +1,11 @@
import { Meta } from '@storybook/addon-docs';
<Meta title="Introduction" />
<div className="text-center flex flex-col items-center">
<h1 style={{marginBottom: '24px', marginTop: '36px'}}>Welcome to Cal.com UI</h1>
<p>This is the beginning of our storybook impovments to match Figma as close as possible. Like our Figma library, we will be adding more components as we go along.</p>
<p>Our <a href="https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/%E2%9D%96-Cal-DS" target="_blank">Figma</a> library is avalible for anyone to view and use. If you have any questions or concerns, please reach out to the design team.</p>
</div>
<img src="/sb-cover.jpg" />
-7
View File
@@ -1,7 +0,0 @@
const withTM = require("next-transpile-modules")(["@calcom/dayjs", "@calcom/ui"]);
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = withTM(nextConfig);
+28 -40
View File
@@ -1,18 +1,13 @@
{
"name": "@calcom/storybook",
"version": "0.1.0",
"private": true,
"version": "0.0.0",
"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"
"build": "build-storybook"
},
"dependencies": {
"@calcom/config": "*",
"@calcom/dayjs": "*",
"@calcom/ui": "*",
"@radix-ui/react-avatar": "^1.0.0",
@@ -25,42 +20,35 @@
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^12.2.5",
"next-transpile-modules": "^9.0.0",
"next": "^12.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-hot-toast": "^2.3.0"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.18.10",
"@calcom/config": "*",
"@storybook/addon-a11y": "^6.5.10",
"@storybook/addon-actions": "^6.5.10",
"@storybook/addon-essentials": "^6.5.10",
"@storybook/addon-interactions": "^6.5.10",
"@storybook/addon-links": "^6.5.10",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-vite": "^0.2.2",
"@storybook/builder-webpack5": "^6.5.10",
"@storybook/manager-webpack5": "^6.5.10",
"@storybook/react": "^6.5.10",
"@tailwindcss/line-clamp": "^0.4.0",
"@types/node": "16.9.1",
"@babel/core": "^7.19.6",
"@storybook/addon-actions": "^6.5.13",
"@storybook/addon-essentials": "^6.5.13",
"@storybook/addon-interactions": "^6.5.13",
"@storybook/addon-links": "^6.5.13",
"@storybook/builder-vite": "^0.2.4",
"@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13",
"@storybook/testing-library": "^0.0.13",
"@types/react": "^18.0.17",
"@types/react-dom": "18.0.4",
"autoprefixer": "^10.4.8",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.1.0",
"autoprefixer": "^10.4.12",
"babel-loader": "^8.2.5",
"chromatic": "^6.7.4",
"eslint": "^8.22.0",
"postcss": "^8.4.16",
"postcss-loader": "^7.0.0",
"fs": "^0.0.1-security",
"postcss": "^8.4.18",
"postcss-pseudo-companion-classes": "^0.1.1",
"rollup-plugin-polyfill-node": "^0.10.2",
"storybook-addon-designs": "^6.3.1",
"storybook-addon-next": "^1.6.7",
"tailwindcss": "^3.1.8",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.7.4"
},
"readme": "ERROR: No README data found!",
"_id": "@calcom/storybook@0.1.0"
"storybook-addon-next": "^1.6.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"typescript": "^4.7.4",
"vite": "^3.1.0"
}
}
-9
View File
@@ -1,9 +0,0 @@
import type { AppProps } from "next/app";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
-63
View File
@@ -1,63 +0,0 @@
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;
+16
View File
@@ -2,5 +2,21 @@ module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
/**
* We use this postcss plugin to create custom css classes for
* any pseudo classes (eg hover, focus) mentioned below.
* This way you can eg show a hover state in Storybook by adding
* this classname .sb-pseudo--hover to the item.
*
* These styles will only be added in storybook, and will NOT
* end up in the final css bundle of apps using the components.
*/
"postcss-pseudo-companion-classes": {
prefix: "sb-pseudo--",
// We have to keep a restrictTo list here, because otherwise
// this library will have issues processing tailwind's \: prefixed classes
// and start adding dots everywhere, breaking this functionality.
restrictTo: [":hover", ":focus"],
},
},
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

-4
View File
@@ -1,4 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-26
View File
@@ -1,26 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import Avatar from "@calcom/ui/v2/core/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 />
</>
);
};
@@ -1,60 +0,0 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { ComponentMeta } from "@storybook/react";
import AvatarGroup from "@calcom/ui/v2/core/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 (
<TooltipProvider>
<AvatarGroup size="lg" items={IMAGES} />
</TooltipProvider>
);
};
-58
View File
@@ -1,58 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { Bell } from "react-feather";
import Badge from "@calcom/ui/v2/core/Badge";
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
@@ -1,46 +0,0 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Info } from "react-feather";
import Banner from "@calcom/ui/v2/core/banner";
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")}
/>
);
};
@@ -1,22 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import Breadcrumb, { BreadcrumbItem } from "@calcom/ui/v2/core/Breadcrumb";
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",
},
};
-98
View File
@@ -1,98 +0,0 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { Trash2 } from "react-feather";
import Button from "@calcom/ui/v2/core/Button";
export default {
title: "Button",
component: Button,
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 Button>;
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
export const All = () => (
<div>
<TooltipProvider>
<h1>Primary</h1>
<div className="flex space-x-2">
<Button aria-label="Button Text">Button Text</Button>
<Button disabled aria-label="Button Text">
Button Text
</Button>
</div>
<h1>Secondary</h1>
<div className="flex space-x-2">
<Button color="secondary" aria-label="Button Text">
Button Text
</Button>
<Button disabled color="secondary" aria-label="Button Text">
Button Text
</Button>
<Button size="icon" color="secondary" StartIcon={Trash2} aria-label="Button Text" />
</div>
<h1>Minimal</h1>
<div className="flex">
<Button color="minimal" aria-label="Button Text">
Button Text
</Button>
<Button disabled color="minimal" aria-label="Button Text">
Button Text
</Button>
<Button size="icon" color="minimal" StartIcon={Trash2} aria-label="Button Text" />
</div>
<h1>Destructive</h1>
<Button size="icon" color="destructive" StartIcon={Trash2} aria-label="Button Text" />
<h1>Tooltip</h1>
<Button
tooltip="Deletes EventTypes"
size="icon"
color="destructive"
StartIcon={Trash2}
aria-label="Button Text"
/>
</TooltipProvider>
</div>
);
export const Default = Template.bind({});
Default.args = {
color: "primary",
children: "Button Text",
};
export const Disabled = Template.bind({});
Disabled.args = {
...Default.args,
disabled: true,
};
export const Loading = Template.bind({});
Loading.args = {
...Default.args,
loading: true,
};
export const Icon = Template.bind({});
Icon.args = {
color: "secondary",
StartIcon: Trash2,
size: "icon",
};
@@ -1,41 +0,0 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { ComponentMeta } from "@storybook/react";
import { ArrowLeft, ArrowRight, Clipboard, Navigation2, Trash2 } from "react-feather";
import Button from "@calcom/ui/v2/core/Button";
import ButtonGroup from "@calcom/ui/v2/core/ButtonGroup";
export default {
title: "Button Group",
component: ButtonGroup,
decorators: [
(Story) => (
<TooltipProvider>
<Story />
</TooltipProvider>
),
],
} 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" />
<Button StartIcon={Navigation2} size="icon" color="secondary" />
<Button StartIcon={Clipboard} size="icon" color="secondary" />
</ButtonGroup>
<ButtonGroup combined>
<Button StartIcon={ArrowLeft} size="icon" color="secondary" />
<Button StartIcon={ArrowRight} size="icon" color="secondary" />
<Button StartIcon={ArrowRight} href="/" size="icon" color="secondary" />
</ButtonGroup>
</div>
);
-79
View File
@@ -1,79 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { useState } from "react";
import Card, { BaseCardProps } from "@calcom/ui/v2/core/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>
);
};
const sidebarCardProps: BaseCardProps = {
variant: "SidebarCard",
thumbnailUrl: "https://img.youtube.com/vi/60HJt8DOVNo/0.jpg",
mediaLink: "https://www.youtube.com/watch?v=60HJt8DOVNo",
title: "Dynamic boooking links",
description: "Booking link that allows people to quickly schedule meetings.",
learnMore: {
href: "https://cal.com/blog/cal-v-1-9",
text: "Learn more",
},
};
export const SidebarCard = () => {
const [visible, setVisible] = useState(true); // save state in localStorage, cookie or db
return (
<header className="w-full max-w-[225px] bg-gray-100 p-3">
{visible && (
<div>
<Card {...sidebarCardProps} actionButton={{ onClick: () => setVisible(false), child: "Dismiss" }} />
</div>
)}
</header>
);
};
@@ -1,31 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import Checkbox from "@calcom/ui/v2/core/form/Checkbox";
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
@@ -1,208 +0,0 @@
import { useState } from "react";
import ColorPicker from "@calcom/ui/v2/core/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)} />;
};
@@ -1,17 +0,0 @@
import { useState } from "react";
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
export default {
title: "Datepicker",
component: DatePicker,
};
export const Default = () => {
const [selected, setSelected] = useState<Date>(new Date());
return (
<div style={{ width: "455px" }}>
<DatePicker selected={selected} onChange={setSelected} locale="en" />
</div>
);
};
@@ -1,45 +0,0 @@
import { ComponentMeta, ComponentStory } from "@storybook/react";
import "@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css";
import { useState } from "react";
import DateRangePicker from "@calcom/ui/v2/core/form/date-range-picker/DateRangePicker";
import "@calcom/ui/v2/core/form/date-range-picker/styles.css";
function getISOLocalDate(date) {
const year = padStart(date.getFullYear(), 4);
const month = padStart(date.getMonth() + 1);
const day = padStart(date.getDate());
return [`${year}-${month}-${day}`];
}
function padStart(num, val = 2) {
const numStr = `${num}`;
if (numStr.length >= val) {
return num;
}
return `0000${numStr}`.slice(-val);
}
export default {
title: "Date Range Picker",
component: DateRangePicker,
} as ComponentMeta<typeof DateRangePicker>;
export const Default: ComponentStory<typeof DateRangePicker> = () => {
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
return (
<div>
<DateRangePicker
startDate={getISOLocalDate(startDate) as unknown as Date}
endDate={getISOLocalDate(endDate) as unknown as Date}
onDatesChange={({ startDate, endDate }) => {
setStartDate(startDate);
setEndDate(endDate);
}}
/>
</div>
);
};
@@ -1,26 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { Bell } from "react-feather";
import EmptyScreen from "@calcom/ui/v2/core/EmptyScreen";
export default {
title: "pattern/Empty Screen",
component: EmptyScreen,
decorators: [
(Story) => (
<div className="flex items-center justify-center">
<Story />
</div>
),
],
} as ComponentMeta<typeof EmptyScreen>;
export const Default = () => (
<EmptyScreen
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 "
buttonText="Veniam ut ipsum"
buttonOnClick={() => console.log("Button Clicked")}
/>
);
@@ -1,27 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { useState } from "react";
import FormStep from "@calcom/ui/v2/core/form/FormStep";
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>
);
};
@@ -1,39 +0,0 @@
// Disabling until we figure out what is happening with the RouterMock.
import { ComponentMeta } from "@storybook/react";
import { HorizontalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabItem";
import HorizontalTabs from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabs";
export default {
title: "Horizontal Tabs",
component: HorizontalTabs,
} as ComponentMeta<typeof HorizontalTabs>;
const HorizontalTabsPropsDefault: HorizontalTabItemProps[] = [
{
name: "Tab One",
href: "/tab-one",
},
{
name: "Tab Two",
href: "/tab-two",
},
{
name: "Tab Disabled",
href: "/tab-disabled",
disabled: true,
},
];
export const Default = () => (
<div className="w-full p-4">
<HorizontalTabs tabs={HorizontalTabsPropsDefault} />
</div>
);
Default.parameters = {
nextRouter: {
path: "/[page]",
asPath: "/tab-one",
},
};
-75
View File
@@ -1,75 +0,0 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { Copy } from "react-feather";
import { TextAreaField, TextField, PasswordField } from "@calcom/ui/v2/core/form/fields";
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
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()} />;
export const PasswordInput: ComponentStory<typeof PasswordField> = () => (
<TooltipProvider>
<PasswordField />
</TooltipProvider>
);
-50
View File
@@ -1,50 +0,0 @@
import { useState } from "react";
import { Info } from "react-feather";
import { Button, Dialog, DialogContent, DialogTrigger, TextField } from "@calcom/ui/v2";
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>
);
};
@@ -1,24 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { Toaster } from "react-hot-toast";
import { Button, showToast } from "@calcom/ui/v2";
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>
);
@@ -1,52 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { Search } from "react-feather";
import { Button, TextField } from "@calcom/ui/v2";
import PageHeader from "@calcom/ui/v2/core/PageHeader";
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
@@ -1,19 +0,0 @@
import * as Radio from "@calcom/ui/v2/core/form/radio-area/Radio";
import { RadioField } from "@calcom/ui/v2/core/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>
);
};
-33
View File
@@ -1,33 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { Select } from "@calcom/ui/v2";
import MultiDropdownSelect from "@calcom/ui/v2/modules/event-types/MultiDropdownSelect";
export default {
title: "Form/Select",
component: Select,
} as ComponentMeta<typeof Select>;
const singleOptions = [
{
label: "Select",
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: minutes + " " + "minutes",
value: minutes,
})),
];
const multiOptions = [
{
label: "Select",
value: 0,
},
...[5, 10, 15, 20, 30, 45, 60, 90, 120].map((minutes) => ({
label: minutes + " " + "minutes",
value: minutes,
})),
];
export const Single = () => <Select options={singleOptions} />;
export const Multi = () => <MultiDropdownSelect options={multiOptions} />;
-15
View File
@@ -1,15 +0,0 @@
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>
);
@@ -1,67 +0,0 @@
// 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} />
</div>
);
export const Active = () => (
<div className="h-20 w-full bg-gray-100 p-4">
<VerticalTabItem {...TabItemProps} />
</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 />
</div>
);
export const NoIconNoInfo = () => (
<div className="h-20 w-full bg-gray-100 p-4">
<VerticalTabItem name="Event Setup" href="/settings/event" />
</div>
);
export const IconNoInfo = () => (
<div className="h-20 w-full bg-gray-100 p-4">
<VerticalTabItem name="Event Setup" href="/settings/event" icon={Link} />
</div>
);
export const IconNoInfoActive = () => (
<div className="h-20 w-full bg-gray-100 p-4">
<VerticalTabItem name="Event Setup" href="/settings/events" icon={Link} />
</div>
);
IconNoInfoActive.paramaters = {
nextRouter: {
path: "/settings/[tab]",
asPath: "/settings/events",
},
};
@@ -1,20 +0,0 @@
import { ComponentMeta } from "@storybook/react";
import { ToggleGroup } from "@calcom/ui/v2/core/form/ToggleGroup";
export default {
title: "Toggle Group",
component: ToggleGroup,
} as ComponentMeta<typeof ToggleGroup>;
export const Default = () => {
return (
<ToggleGroup
defaultValue="12"
options={[
{ value: "12", label: "12h" },
{ value: "24", label: "24h" },
]}
/>
);
};
@@ -1,85 +0,0 @@
// 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/core/navigation/tabs/VerticalTabs";
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",
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

+1 -10
View File
@@ -2,13 +2,4 @@
@tailwind components;
@tailwind utilities;
@import "../../../packages/ui/v2/core/form/date-range-picker/styles.css";
@import "../../../node_modules/@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css";
:root {
--brand-color: #292929;
--brand-text-color: #ffffff;
--brand-color-dark-mode: #fafafa;
--brand-text-color-dark-mode: #292929;
font-family: 'Inter', sans-serif;
}
@import "../../../packages/ui/styles/shared-globals.css"
+189
View File
@@ -0,0 +1,189 @@
.sbdocs {
font-family: 'Inter var' !important;
padding: 0!important;
}
#docs-root {
overflow: hidden;
}
.sbdocs.sbdocs-h1 {
font-family: "Cal Sans", "sans-serif" !important;
font-weight: 600;
font-size: 36px;
margin: 0 0 82px 0;
}
.sbdocs.sbdocs-h2 {
font-size: 20px;
font-weight: 500;
border: none;
border-top: 2px solid rgba(0, 0, 0, 0.12);
padding-top: 12px!important;
padding-bottom: 12px!important;
margin: 82px 0 0 0;
}
.sbdocs.sbdocs-p {
max-width: 560px;
margin: 0 0 12px 0;
}
.sbdocs.sbdocs-content {
width: 1200px;
max-width: 100%;
}
/** Docs table **/
.custom-args-wrapper{
max-height: 600px;
overflow-y: scroll;
overflow-x: hidden;
margin-bottom: 1rem;
}
.docblock-argstable-body {
box-shadow: none!important;
font-size: 14px;
}
/* .docblock-argstable-head tr th, */
.docblock-argstable-body tr td {
/* padding-left: 0!important; */
padding: 20px !important;
}
/** Column titles **/
.docblock-argstable-body tr td > span,
.docblock-argstable-head tr th > span {
color: #525252 !important;
font-weight: 500 !important;
}
.docblock-argstable-body div p,
.docblock-argstable-body div span {
color: #8F8F8F !important;
}
/** Custom components **/
.story-title {
margin: 0 0 82px 0;
}
.story-title.offset {
margin-top: 100px;
}
.story-title h1 {
font-family: "Cal Sans", "sans-serif" !important;
font-weight: 600;
font-size: 36px;
}
.story-title h1 span {
color: #9CA3AF;
font-family: 'Inter var';
font-weight: normal;
display: inline-block;
margin-left: 8px;
}
.story-title p {
color: #111827;
font-size: 16px;
}
.examples {
background-color: #F9FAFB;
padding: 20px;
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.examples-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 30px 0;
}
.examples-title {
color: #8F8F8F;
font-size: 14px;
margin-bottom: 30px;
}
.examples-item-title {
font-size: 12px;
color: #8F8F8F;
margin-bottom: 12px;
display: block;
}
.examples-item {
margin: 0 20px;
}
.examples-footnote p,
.examples-footnote ul,
.examples-footnote li {
color: #8F8F8F;
font-size: 14px;
}
.examples-footnote ul {
padding-left: 8px;
}
.examples-footnote li {
margin: 0!important;
}
.story-note {
background-color: #F9FAFB;
font-size: 14px;
padding: 20px;
margin-bottom: 12px;
border-radius: 8px;
}
.story-note > div {
max-width: 600px;
}
/** SB Docs Preview **/
.sbdocs-preview {
width: 100vw;
left: calc((100vw - 1200px) / -2);
box-shadow: none !important;
margin: 0 !important;
border: none !important;
border-radius: none!important;
}
.docs-story > div:first-child {
padding: 0!important;
margin: 0!important;
}
.docs-story .innerZoomElementWrapper > div {
border: none !important;
}
.sb-main-padded {
padding: 0!important;
}
@media screen and (max-width: 1200px) {
.sbdocs-preview {
left: -24px;
width: calc(100vw + 48px);
padding-left: 12px;
padding-right: 12px;
}
.sbdocs.sbdocs-content {
padding: 24px !important;
}
}
+5 -165
View File
@@ -1,167 +1,7 @@
const base = require("@calcom/config/tailwind-preset");
/** @type {import('tailwindcss').Config} */
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",
}),
},
},
...base,
darkMode: ["class", '[data-mode="dark"]'],
content: [...base.content, "../../packages/ui/**/*.{js,ts,jsx,tsx,mdx}", "./stories/**/*.{js,ts,tsx,jsx}"],
};
Executable → Regular
View File
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
}
}
+2 -2
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^12.2.5",
"next": "^12.3.1",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -23,7 +23,7 @@
"devDependencies": {
"@types/node": "16.9.1",
"@types/react": "^18.0.17",
"@types/react-dom": "18.0.4",
"@types/react-dom": "^18.0.6",
"typescript": "^4.7.4"
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ import { App as AppType } from "@calcom/types/App";
import { Button, SkeletonButton } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import Badge from "@calcom/ui/v2/core/Badge";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
import DisconnectIntegration from "@components/integrations/DisconnectIntegration";
+7 -3
View File
@@ -6,8 +6,9 @@ import { components, ControlProps } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { Button, HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
import { HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
@@ -56,10 +57,13 @@ const goto = (router: NextRouter, searchParams: Record<string, string>) => {
};
const removeQueryParams = (router: NextRouter, queryParams: string[]) => {
const params = new URLSearchParams(window.location.search);
queryParams.forEach((param) => {
delete router.query[param];
params.delete(param);
});
router.push(`${router.asPath.split("?")[0]}?${router.query.toString()}`);
router.push(`${router.asPath.split("?")[0]}?${params.toString()}`);
};
/**
+2 -1
View File
@@ -10,8 +10,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { showToast, SkeletonText } from "@calcom/ui/v2";
import { Button, SkeletonButton, Shell } from "@calcom/ui/v2";
import { SkeletonButton, Shell } from "@calcom/ui/v2";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import HeadSeo from "@components/seo/head-seo";
@@ -8,7 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import { Alert, Button, EmptyScreen } from "@calcom/ui/v2";
import { Button } from "@calcom/ui/components";
import { Alert, EmptyScreen } from "@calcom/ui/v2";
import { List } from "@calcom/ui/v2/core/List";
import { ShellSubHeading } from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
@@ -5,8 +5,8 @@ import useApp from "@calcom/lib/hooks/useApp";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { showToast } from "@calcom/ui/v2";
import Button from "@calcom/ui/v2/core/Button";
/**
* Use this component to allow installing an app from anywhere on the app.
@@ -1,86 +0,0 @@
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
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 showToast from "@calcom/ui/v2/core/notifications";
import { HttpError } from "@lib/core/http/error";
export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) {
const router = useRouter();
const { t } = useLocale();
const form = useForm<{
name: string;
}>();
const { register } = form;
const createMutation = trpc.useMutation("viewer.availability.schedule.create", {
onSuccess: async ({ schedule }) => {
await router.push("/availability/" + schedule.id);
showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success");
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
if (err.data?.code === "UNAUTHORIZED") {
const message = `${err.data.code}: You are not able to create this event`;
showToast(message, "error");
}
},
});
return (
<Dialog name={name} clearQueryParamsOnClose={["copy-schedule-id"]}>
<DialogTrigger asChild>
<Button data-testid={name} StartIcon={Icon.FiPlus}>
{t("new_schedule_btn")}
</Button>
</DialogTrigger>
<DialogContent>
<div className="mb-8">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{t("add_new_schedule")}
</h3>
</div>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-2">
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("name")}
</label>
<div className="mt-1">
<input
type="text"
id="name"
required
className="block w-full rounded-sm border-gray-300 text-sm"
placeholder={t("default_schedule_name")}
{...register("name")}
/>
</div>
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
</div>
</Form>
</DialogContent>
</Dialog>
);
}
@@ -1,350 +0,0 @@
/**
* @deprecated
* use Component in "/packages/features/schedules/components/Schedule";
**/
import classNames from "classnames";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
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, { 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";
import useMeQuery from "@lib/hooks/useMeQuery";
import { TimeRange } from "@lib/types/schedule";
import Select from "@components/ui/form/Select";
/** Begin Time Increments For Select */
const increment = 15;
type Option = {
readonly label: string;
readonly value: number;
};
/**
* Creates an array of times on a 15 minute interval from
* 00:00:00 (Start of day) to
* 23:45:00 (End of day with enough time for 15 min booking)
*/
const useOptions = () => {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const { timeFormat } = query.data || { timeFormat: null };
const [filteredOptions, setFilteredOptions] = useState<Option[]>([]);
const options = useMemo(() => {
const end = dayjs().utc().endOf("day");
let t: Dayjs = dayjs().utc().startOf("day");
const options: Option[] = [];
while (t.isBefore(end)) {
options.push({
value: t.toDate().valueOf(),
label: dayjs(t)
.utc()
.format(timeFormat === 12 ? "h:mma" : "HH:mm"),
});
t = t.add(increment, "minutes");
}
return options;
}, [timeFormat]);
const filter = useCallback(
({ offset, limit, current }: { offset?: ConfigType; limit?: ConfigType; current?: ConfigType }) => {
if (current) {
const currentOption = options.find((option) => option.value === dayjs(current).toDate().valueOf());
if (currentOption) setFilteredOptions([currentOption]);
} else
setFilteredOptions(
options.filter((option) => {
const time = dayjs(option.value);
return (!limit || time.isBefore(limit)) && (!offset || time.isAfter(offset));
})
);
},
[options]
);
return { options: filteredOptions, filter };
};
type TimeRangeFieldProps = {
name: string;
className?: string;
};
const LazySelect = ({
value,
min,
max,
...props
}: Omit<Props<Option, false, GroupBase<Option>>, "value"> & {
value: ConfigType;
min?: ConfigType;
max?: ConfigType;
}) => {
// Lazy-loaded options, otherwise adding a field has a noticable redraw delay.
const { options, filter } = useOptions();
useEffect(() => {
filter({ current: value });
}, [filter, value]);
return (
<Select
options={options}
onMenuOpen={() => {
if (min) filter({ offset: min });
if (max) filter({ limit: max });
}}
value={options.find((option) => option.value === dayjs(value).toDate().valueOf())}
onMenuClose={() => filter({ current: value })}
{...props}
/>
);
};
const TimeRangeField = ({ name, className }: TimeRangeFieldProps) => {
const { watch } = useFormContext();
const minEnd = watch(`${name}.start`);
const maxStart = watch(`${name}.end`);
return (
<div className={classNames("flex flex-grow items-center space-x-3", className)}>
<Controller
name={`${name}.start`}
render={({ field: { onChange, value } }) => {
return (
<LazySelect
className="w-[120px]"
value={value}
max={maxStart}
onChange={(option) => {
onChange(new Date(option?.value as number));
}}
/>
);
}}
/>
<span>-</span>
<Controller
name={`${name}.end`}
render={({ field: { onChange, value } }) => (
<LazySelect
className="flex-grow sm:w-[120px]"
value={value}
min={minEnd}
onChange={(option) => {
onChange(new Date(option?.value as number));
}}
/>
)}
/>
</div>
);
};
type ScheduleBlockProps = {
day: number;
weekday: string;
name: string;
};
const CopyTimes = ({ disabled, onApply }: { disabled: number[]; onApply: (selected: number[]) => void }) => {
const [selected, setSelected] = useState<number[]>([]);
const { i18n, t } = useLocale();
return (
<div className="m-4 space-y-2 py-4">
<p className="h6 text-xs font-medium uppercase text-neutral-400">Copy times to</p>
<ol className="space-y-2">
{weekdayNames(i18n.language).map((weekday, num) => (
<li key={weekday}>
<label className="flex w-full items-center justify-between">
<span>{weekday}</span>
<input
value={num}
defaultChecked={disabled.includes(num)}
disabled={disabled.includes(num)}
onChange={(e) => {
if (e.target.checked && !selected.includes(num)) {
setSelected(selected.concat([num]));
} else if (!e.target.checked && selected.includes(num)) {
setSelected(selected.slice(selected.indexOf(num), 1));
}
}}
type="checkbox"
className="inline-block rounded-sm border-gray-300 text-neutral-900 focus:ring-neutral-500 disabled:text-neutral-400"
/>
</label>
</li>
))}
</ol>
<div className="pt-2">
<Button className="w-full justify-center" color="primary" onClick={() => onApply(selected)}>
{t("apply")}
</Button>
</div>
</div>
);
};
/**
* @deprecated
* use Component in "/packages/features/schedules/components/Schedule";
**/
export const DayRanges = ({
name,
defaultValue = [defaultDayRange],
}: {
name: string;
defaultValue?: TimeRange[];
}) => {
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,
});
useEffect(() => {
if (defaultValue.length && !fields.length) {
replace(defaultValue);
}
}, [replace, defaultValue, fields.length]);
const handleAppend = () => {
// FIXME: Fix type-inference, can't get this to work. @see https://github.com/react-hook-form/react-hook-form/issues/4499
const nextRangeStart = dayjs((fields[fields.length - 1] as unknown as TimeRange).end);
const nextRangeEnd = dayjs(nextRangeStart).add(1, "hour");
if (nextRangeEnd.isBefore(nextRangeStart.endOf("day"))) {
return append({
start: nextRangeStart.toDate(),
end: nextRangeEnd.toDate(),
});
}
};
return (
<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 space-x-1 sm:flex-grow-0">
<TimeRangeField name={`${name}.${index}`} />
<Button
type="button"
size="icon"
color="minimal"
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">
<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>
<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)]}
onApply={(selected) =>
selected.forEach((day) => {
// TODO: Figure out why this is different?
// console.log(watcher, fields);
setValue(name.substring(0, name.lastIndexOf(".") + 1) + day, watcher);
})
}
/>
</DropdownMenuContent>
</Dropdown>
</div>
)}
</div>
))}
</div>
);
};
const ScheduleBlock = ({ name, day, weekday }: ScheduleBlockProps) => {
const { t } = useLocale();
const form = useFormContext();
const watchAvailable = form.watch(`${name}.${day}`, []);
return (
<fieldset className="relative flex flex-col justify-between space-y-2 py-5 sm:flex-row sm:space-y-0">
<label
className={classNames(
"flex space-x-2 rtl:space-x-reverse",
!watchAvailable.length ? "w-full" : "w-1/3"
)}>
<div className={classNames(!watchAvailable.length ? "w-1/3" : "w-full")}>
<input
type="checkbox"
checked={watchAvailable.length}
onChange={(e) => {
form.setValue(`${name}.${day}`, e.target.checked ? [defaultDayRange] : []);
}}
className="inline-block rounded-sm border-gray-300 text-neutral-900 focus:ring-neutral-500"
/>
<span className="ml-2 inline-block text-sm capitalize">{weekday}</span>
</div>
{!watchAvailable.length && (
<div className="flex-grow text-right text-sm text-gray-500 sm:flex-shrink">
{t("no_availability")}
</div>
)}
</label>
{!!watchAvailable.length && (
<div className="flex-grow">
<DayRanges name={`${name}.${day}`} defaultValue={[]} />
</div>
)}
</fieldset>
);
};
/**
* @deprecated
* use Component in "/packages/features/schedules/components/Schedule";
**/
const Schedule = ({ name }: { name: string }) => {
const { i18n } = useLocale();
return (
<fieldset className="divide-y divide-gray-200">
{weekdayNames(i18n.language).map((weekday, num) => (
<ScheduleBlock key={num} name={name} weekday={weekday} day={num} />
))}
</fieldset>
);
};
export default Schedule;
@@ -1,83 +0,0 @@
/**
* @deprecated modifications to this file should be v2 only
* Use `/packages/features/schedules/components/ScheduleListItem.tsx` instead
*/
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 { Icon } from "@calcom/ui/Icon";
/**
* @deprecated modifications to this file should be v2 only
* Use `/packages/features/schedules/components/ScheduleListItem.tsx` instead
*/
export function ScheduleListItem({
schedule,
deleteFunction,
isDeleting = false,
}: {
schedule: inferQueryOutput<"viewer.availability.list">["schedules"][number];
deleteFunction: ({ scheduleId }: { scheduleId: number }) => void;
isDeleting: boolean;
}) {
const { t, i18n } = useLocale();
return (
<li key={schedule.id}>
<div className="flex items-center justify-between py-5 hover:bg-neutral-50 ltr:pl-4 rtl:pr-4 sm:ltr:pl-0 sm:rtl:pr-0">
<div className="group flex w-full items-center justify-between hover:bg-neutral-50 sm:px-6">
<Link href={"/availability/" + schedule.id}>
<a className="flex-grow truncate text-sm" title={schedule.name}>
<div>
<span className="truncate font-medium text-neutral-900">{schedule.name}</span>
{schedule.isDefault && (
<span className="ml-2 inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800">
{t("default")}
</span>
)}
</div>
<p className="mt-1 text-xs text-neutral-500">
{schedule.availability.map((availability: Availability) => (
<Fragment key={availability.id}>
{availabilityAsString(availability, { locale: i18n.language })}
<br />
</Fragment>
))}
</p>
</a>
</Link>
</div>
<Dropdown>
<DropdownMenuTrigger className="group mr-5 h-10 w-10 border border-transparent p-0 text-neutral-500 hover:border-gray-200">
<Icon.FiMoreHorizontal className="h-5 w-5 group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<Button
disabled={isDeleting}
onClick={() => {
deleteFunction({
scheduleId: schedule.id,
});
}}
type="button"
color="warn"
className="w-full font-normal"
StartIcon={isDeleting ? undefined : Icon.FiTrash}
loading={isDeleting}>
{isDeleting ? t("deleting") : t("delete_schedule")}
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
</li>
);
}
@@ -1,8 +1,9 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui/v2";
import classNames from "@lib/classNames";
/** @deprecated Use `apps/web/components/v2/availability/SkeletonLoader.tsx` */
function SkeletonLoader() {
return (
<ul className="animate-pulse divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
@@ -20,26 +21,30 @@ 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 width="32" height="2" />
<SkeletonText className="h-4 w-32" />
<SkeletonText className="h-2 w-32" />
</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 className="h-6 w-12" />
</div>
</div>
</li>
);
}
export const AvailabilitySelectSkeletonLoader = () => {
export const SelectSkeletonLoader = ({ className }: { className?: string }) => {
return (
<li className="group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3">
<li
className={classNames(
"group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3",
className
)}>
<div className="flex-grow truncate text-sm">
<div className="flex justify-between">
<SkeletonText width="32" height="4" />
<SkeletonText width="4" height="4" />
<SkeletonText className="h-4 w-32" />
<SkeletonText className="h-4 w-4" />
</div>
</div>
</li>
@@ -24,14 +24,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
alt={`${eventLocationType.label} icon`}
/>
<Tooltip content={locationKeyToString(location)}>
<a
target="_blank"
href={locationKeyToString(location) ?? "/"}
className="truncate"
key={location.type}
rel="noreferrer">
{locationKeyToString(location)}
</a>
<p className="truncate">{locationKeyToString(location)}</p>
</Tooltip>
</div>
);
@@ -83,7 +83,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
query: {
...router.query,
date: dayjs(slot.time).format(),
date: dayjs.utc(slot.time).tz(timeZone()).format(),
type: eventTypeId,
slug: eventTypeSlug,
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
+41 -34
View File
@@ -11,15 +11,14 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { inferQueryInput, inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import Badge from "@calcom/ui/v2/core/Badge";
import Button from "@calcom/ui/v2/core/Button";
import MeetingTimeInTimezones from "@calcom/ui/v2/core/MeetingTimeInTimezones";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import showToast from "@calcom/ui/v2/core/notifications";
import useMeQuery from "@lib/hooks/useMeQuery";
import { extractRecurringDates } from "@lib/parseDate";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
@@ -31,14 +30,14 @@ type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringBookings: inferQueryOutput<"viewer.bookings">["recurringInfo"];
recurringInfo: inferQueryOutput<"viewer.bookings">["recurringInfo"][number] | undefined;
};
function BookingListItem(booking: BookingItemProps) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
const { t, i18n } = useLocale();
const { t } = useLocale();
const utils = trpc.useContext();
const router = useRouter();
const [rejectionReason, setRejectionReason] = useState<string>("");
@@ -176,16 +175,11 @@ function BookingListItem(booking: BookingItemProps) {
setLocationMutation.mutate({ bookingId: booking.id, newLocation });
};
// Extract recurring dates is intensive to run, so use useMemo.
// Calculate the booking date(s) and setup recurring event data to show
// @FIXME: This is importing the RRULE library which is already heavy. Find out a more optimal way do this.
const [recurringStrings, recurringDates] = useMemo(() => {
if (booking.recurringBookings !== undefined && booking.eventType.recurringEvent?.freq !== undefined) {
return extractRecurringDates(booking, user?.timeZone, i18n);
}
return [[], []];
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user?.timeZone, i18n.language, booking.recurringBookings]);
// Getting accepted recurring dates to show
const recurringDates = booking.recurringInfo?.bookings[BookingStatus.ACCEPTED]
.concat(booking.recurringInfo?.bookings[BookingStatus.CANCELLED])
.concat(booking.recurringInfo?.bookings[BookingStatus.PENDING])
.sort((date1: Date, date2: Date) => date1.getTime() - date2.getTime());
const location = booking.location || "";
@@ -275,13 +269,11 @@ function BookingListItem(booking: BookingItemProps) {
attendees={booking.attendees}
/>
</div>
{isPending && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
{t("unconfirmed")}
</Badge>
)}
{booking.eventType?.team && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="gray">
{booking.eventType.team.name}
@@ -292,10 +284,11 @@ function BookingListItem(booking: BookingItemProps) {
{t("pending_payment")}
</Badge>
)}
<div className="mt-2 text-sm text-gray-400">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
</div>
{recurringDates !== undefined && (
<div className="mt-2 text-sm text-gray-400">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
</div>
)}
</div>
</td>
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
@@ -331,9 +324,11 @@ function BookingListItem(booking: BookingItemProps) {
{t("pending_payment")}
</Badge>
)}
<div className="text-sm text-gray-400 sm:hidden">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
</div>
{recurringDates !== undefined && (
<div className="text-sm text-gray-400 sm:hidden">
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
</div>
)}
</div>
<div className="cursor-pointer py-4">
@@ -403,11 +398,16 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
const { t } = useLocale();
const now = new Date();
const recurringCount = recurringDates.filter((date) => {
return date >= now;
return (
date >= now &&
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(date.toDateString())
);
}).length;
return (
(booking.recurringBookings &&
(booking.recurringInfo &&
booking.eventType?.recurringEvent?.freq &&
(booking.listingStatus === "recurring" ||
booking.listingStatus === "unconfirmed" ||
@@ -415,13 +415,20 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
<div className="underline decoration-gray-400 decoration-dashed underline-offset-2">
<div className="flex">
<Tooltip
content={recurringDates.map((aDate, key) => (
<p key={key} className={classNames(recurringDates[key] < now && "line-through")}>
{formatTime(booking.startTime, user?.timeFormat, user?.timeZone)}
{" - "}
{dayjs(aDate).format("D MMMM YYYY")}
</p>
))}>
content={recurringDates.map((aDate, key) => {
const pastOrCancelled =
aDate < now ||
booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(aDate.toDateString());
return (
<p key={key} className={classNames(pastOrCancelled && "line-through")}>
{formatTime(aDate, user?.timeFormat, user?.timeZone)}
{" - "}
{dayjs(aDate).format("D MMMM YYYY")}
</p>
);
})}>
<div className="text-gray-600 dark:text-white">
<Icon.FiRefreshCcw
strokeWidth="3"
@@ -435,7 +442,7 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
: getEveryFreqFor({
t,
recurringEvent: booking.eventType.recurringEvent,
recurringCount,
recurringCount: booking.recurringInfo.count,
})}
</p>
</div>
@@ -1,8 +1,7 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui/v2";
/** @deprecated Use `apps/web/components/v2/bookings/SkeletonLoader.tsx` */
function SkeletonLoader() {
return (
<ul className="animate-pulse divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:overflow-hidden">
@@ -21,15 +20,15 @@ function SkeletonItem() {
<div className="flex-grow truncate text-sm">
<div className="flex">
<div className="flex flex-col space-y-2">
<SkeletonText width="16" height="5" />
<SkeletonText width="32" height="4" />
<SkeletonText className="h-5 w-16" />
<SkeletonText className="h-4 w-32" />
</div>
</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="16" height="6" />
<SkeletonText width="32" height="6" />
<SkeletonText className="h-6 w-16" />
<SkeletonText className="h-6 w-32" />
</div>
</div>
</li>
@@ -31,7 +31,6 @@ import {
} from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { formatTime } from "@calcom/lib/date-fns";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -40,9 +39,10 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { Button } from "@calcom/ui/components";
import AddressInput from "@calcom/ui/form/AddressInputLazy";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { EmailInput, Form } from "@calcom/ui/form/fields";
import { Button } from "@calcom/ui/v2";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
@@ -68,6 +68,8 @@ type BookingFormValues = {
notes?: string;
locationType?: EventLocationType["type"];
guests?: string[];
address?: string;
attendeeAddress?: string;
phone?: string;
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
customInputs?: {
@@ -269,6 +271,7 @@ const BookingPage = ({
.refine((val) => isValidPhoneNumber(val))
.optional()
.nullable(),
attendeeAddress: z.string().optional().nullable(),
smsReminderNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
@@ -297,10 +300,10 @@ const BookingPage = ({
const selectedLocation = getEventLocationType(selectedLocationType);
const AttendeeInput =
selectedLocation?.attendeeInputType === "text"
? "input"
: selectedLocation?.attendeeInputType === "phone"
selectedLocation?.attendeeInputType === "phone"
? PhoneInput
: selectedLocation?.attendeeInputType === "attendeeAddress"
? AddressInput
: null;
// Calculate the booking date(s)
@@ -356,6 +359,7 @@ const BookingPage = ({
location: getEventLocationValue(locations, {
type: booking.locationType ? booking.locationType : selectedLocationType || "",
phone: booking.phone,
attendeeAddress: booking.attendeeAddress,
}),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
@@ -386,6 +390,7 @@ const BookingPage = ({
location: getEventLocationValue(locations, {
type: (booking.locationType ? booking.locationType : selectedLocationType) || "",
phone: booking.phone,
attendeeAddress: booking.attendeeAddress,
}),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
@@ -497,26 +502,17 @@ const BookingPage = ({
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) &&
`${formatTime(dayjs(date).toDate(), user?.timeFormat, user?.timeZone)}, ${dayjs(
date
).format("dddd, D MMMM YYYY")}`}
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringDates.slice(0, 5).map((aDate, key) => {
return (
<p key={key}>{`${formatTime(aDate, user?.timeFormat, user?.timeZone)}, ${dayjs(
aDate
).format("dddd, D MMMM YYYY")}`}</p>
);
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringDates.slice(5).map((aDate, key) => (
<p key={key}>{`${formatTime(aDate, user?.timeFormat, user?.timeZone)}, ${dayjs(
aDate
).format("dddd, D MMMM YYYY")}`}</p>
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
{t("plus_more", { count: recurringStrings.length - 5 })}
@@ -657,16 +653,39 @@ const BookingPage = ({
{AttendeeInput && (
<div className="mb-4">
<label
htmlFor="phone"
htmlFor={
selectedLocationType === LocationType.Phone
? "phone"
: selectedLocationType === LocationType.AttendeeInPerson
? "attendeeAddress"
: ""
}
className="block text-sm font-medium text-gray-700 dark:text-white">
{t("phone_number")}
{selectedLocationType === LocationType.Phone
? t("phone_number")
: selectedLocationType === LocationType.AttendeeInPerson
? t("Address")
: ""}
</label>
<div className="mt-1">
<AttendeeInput<BookingFormValues>
control={bookingForm.control}
name="phone"
bookingForm={bookingForm}
name={
selectedLocationType === LocationType.Phone
? "phone"
: selectedLocationType === LocationType.AttendeeInPerson
? "attendeeAddress"
: ""
}
placeholder={t(selectedLocation?.attendeeInputPlaceholder || "")}
id="phone"
id={
selectedLocationType === LocationType.Phone
? "phone"
: selectedLocationType === LocationType.AttendeeInPerson
? "attendeeAddress"
: ""
}
required
disabled={disableInput}
/>
@@ -293,7 +293,10 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
defaultValue={selection}
options={
booking
? locationOptions.filter((location) => location.value !== "phone")
? locationOptions.filter(
(location) =>
location.value !== "phone" && location.value !== "attendeeInPerson"
)
: locationOptions
}
isSearchable
@@ -4,8 +4,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import Button from "@calcom/ui/v2/core/Button";
import showToast from "@calcom/ui/v2/core/notifications";
interface IRescheduleDialog {
@@ -1,7 +1,7 @@
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import Button from "@calcom/ui/v2/core/Button";
import { Button } from "@calcom/ui/components/button";
interface ICalendarItem {
title: string;
@@ -1,7 +1,4 @@
import { DotsHorizontalIcon } from "@heroicons/react/solid";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/v2";
import { CalendarSwitch } from "./CalendarSwitch";
@@ -7,8 +7,8 @@ import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc, TRPCClientErrorLike } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/form/fields";
import { Button } from "@calcom/ui/v2";
interface ISetupAvailabilityProps {
nextStep: () => void;
@@ -6,7 +6,9 @@ import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button, showToast, TextArea } from "@calcom/ui/v2";
import { Button } from "@calcom/ui/components";
import { TextArea } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/v2";
import ImageUploader from "@calcom/ui/v2/core/ImageUploader";
import { AvatarSSR } from "@components/ui/AvatarSSR";
@@ -100,8 +102,7 @@ const UserProfile = (props: IUserProfile) => {
return (
<form onSubmit={onSubmit}>
<p className="font-cal text-sm">{t("profile_picture")}</p>
<div className="mt-4 flex flex-row items-center justify-start rtl:justify-end">
<div className="flex flex-row items-center justify-start rtl:justify-end">
{user && <AvatarSSR user={user} alt="Profile picture" className="h-16 w-16" />}
<input
ref={avatarRef}
@@ -116,7 +117,7 @@ const UserProfile = (props: IUserProfile) => {
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("upload")}
buttonMsg={t("add_profile_photo")}
handleAvatarChange={(newAvatar) => {
avatarRef.current.value = newAvatar;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
@@ -1,13 +1,13 @@
import { ArrowRightIcon } from "@heroicons/react/outline";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import { Button } from "@calcom/ui/v2";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
@@ -16,21 +16,22 @@ interface IUserSettingsProps {
nextStep: () => void;
}
type FormData = {
name: string;
};
const UserSettings = (props: IUserSettingsProps) => {
const { user, nextStep } = props;
const { t } = useLocale();
const [selectedTimeZone, setSelectedTimeZone] = useState(user.timeZone ?? dayjs.tz.guess());
const { register, handleSubmit, formState } = useForm<FormData>({
const {
register,
handleSubmit,
formState: { errors },
control,
} = useForm({
defaultValues: {
name: user?.name || undefined,
name: user?.name || "",
username: user?.username || "",
},
reValidateMode: "onChange",
});
const { errors } = formState;
const defaultOptions = { required: true, maxLength: 255 };
const utils = trpc.useContext();
@@ -52,22 +53,27 @@ const UserSettings = (props: IUserSettingsProps) => {
timeZone: selectedTimeZone,
});
});
const [currentUsername, setCurrentUsername] = useState(user.username || undefined);
const [inputUsernameValue, setInputUsernameValue] = useState(currentUsername);
const usernameRef = useRef<HTMLInputElement>(null!);
const [currentUsername, setCurrentUsername] = useState(user.username || "");
return (
<form onSubmit={onSubmit}>
<div className="space-y-6">
{/* Username textfield */}
<UsernameAvailability
readonly={true}
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={inputUsernameValue}
usernameRef={usernameRef}
setInputUsernameValue={setInputUsernameValue}
user={user}
<Controller
control={control}
name="username"
render={({ field: { value, ref, onChange } }) => (
<UsernameAvailability
readonly={true}
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
user={user}
/>
)}
/>
{/* Full name textfield */}
@@ -1,6 +1,6 @@
import { ReactNode } from "react";
import Badge from "@calcom/ui/Badge";
import { Badge } from "@calcom/ui/components/badge";
function pluralize(opts: { num: number; plural: string; singular: string }) {
if (opts.num === 0) {
@@ -5,8 +5,8 @@ import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { PasswordField } from "@calcom/ui/components/form";
import { Form, Label } from "@calcom/ui/form/fields";
import { PasswordField } from "@calcom/ui/v2/core/form/fields";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,7 +1,7 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import Badge from "@calcom/ui/Badge";
import Button from "@calcom/ui/Button";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
@@ -5,7 +5,7 @@ import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Form } from "@calcom/ui/v2/core/form/fields";
import { Form } from "@calcom/ui/components/form";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,7 +1,7 @@
import { useState } from "react";
import Badge from "@calcom/ui/Badge";
import Button from "@calcom/ui/Button";
import { Badge } from "@calcom/ui/components/badge";
import { useLocale } from "@lib/hooks/useLocale";
+2 -2
View File
@@ -8,10 +8,10 @@ import {
constructMeetingImage,
MeetingImageProps,
} from "@calcom/lib/OgImages";
import { truncate, truncateOnWord } from "@calcom/lib/text";
import { getBrowserInfo } from "@calcom/lib/browser/browser.utils";
import { truncateOnWord } from "@calcom/lib/text";
import { getSeoImage, seoConfig } from "@lib/config/next-seo.config";
import { getBrowserInfo } from "@lib/core/browser/browser.utils";
export type HeadSeoProps = {
title: string;
@@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import Badge from "@calcom/ui/v2/core/Badge";
import { Badge } from "@calcom/ui/components/badge";
import Switch from "@calcom/ui/v2/core/Switch";
import showToast from "@calcom/ui/v2/core/notifications";
@@ -3,10 +3,10 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/v2/core/Button";
import { Button } from "@calcom/ui/components/button";
import { Form, Label } from "@calcom/ui/components/form";
import { PasswordField } from "@calcom/ui/components/form";
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { Form, Label } from "@calcom/ui/v2/core/form/fields";
import { PasswordField } from "@calcom/ui/v2/core/form/fields";
import TwoFactor from "@components/auth/TwoFactor";

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