170 Commits
v1.3 ... v1.4

Author SHA1 Message Date
Peer Richelsen
9831845d27 add zendesk (#2156)
* MVP zendesk provider

* hide zendesk on mobile

* hide zendesk chat bubble on mobile too

* made mobile selector more robust

* made user menu full width

* removed zendesk react and instead use Nextjs Script

* updated NEXT_PUBLIC_ZENDESK_KEY env in example
2022-03-15 22:46:14 +00:00
Bailey Pumfleet
04cd821a57 Remove font weights from Cal Sans text (#2158) 2022-03-15 09:59:04 -07:00
Bailey Pumfleet
7f270649b4 Escape curly braces in Webhooks doc 2022-03-15 16:35:24 +00:00
Bailey Pumfleet
fae714bceb Add a fallback if name is null (#2157) 2022-03-15 15:42:59 +00:00
Joe Au-Yeung
47c2fc3d89 Add contributing to app store doc - V2 (#2118)
* Add contributing to app store docs

* Edit file structure diagram and fix build

* Add doc to meta

* Wrap example variabels in code block

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-15 15:17:56 +00:00
Agusti Fernandez
53b202790e Feature: pre-fill name and email if user loggedIn in booking page (#2131)
* feat: pre-fill name and email if user loggedIn in booking page

* feat: add name to next-auth autoMergeIdentiteies response

* fix: Update booking page so if you're in your own booking, it doesn't prefill

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-15 14:39:20 +00:00
Syed Ali Shahbaz
9b1031d009 Added 3 digit hex compatibility for Custom Brand colors (#2152)
* Added 3 digit hex compatibility

* fix for compatibility
2022-03-15 10:45:08 +00:00
Omar López
b25e6c25aa Adds missing required main file for EE (#2146)
* Adds missing required main file

* Lint fixes
2022-03-14 20:39:37 +01:00
milospuac
4083ebd591 Update webhooks.mdx (#2144)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-14 13:24:34 +00:00
Bailey Pumfleet
f2e0f00f93 Fix error.svg URL 2022-03-14 11:59:05 +00:00
Bailey Pumfleet
fd6b2c57cb Revert "Bugfix/login failure due email capitalisation (#1884)" (#2143)
This reverts commit 3d685eb4ae.
2022-03-14 11:53:13 +00:00
Jonathan Ng
3d685eb4ae Bugfix/login failure due email capitalisation (#1884)
* convert the email to lowercase

this code will prevent the email check from failing if the user email contain capitalisation.

* Updated Readme to contain warning.

* Revert "Updated Readme to contain warning."

This reverts commit c406587c73c07c613d1c8091bfdd17bd691b00e8.

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-14 11:45:22 +00:00
Syed Ali Shahbaz
c9fb82a7e6 added event trigger to discord payload (#2109)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-14 11:27:28 +00:00
Peer Richelsen
eaf19d1d23 Update .kodiak.toml (#2142) 2022-03-14 11:21:16 +00:00
github-actions[bot]
7e6e6b6d6b New Crowdin translations by Github Action (#2140)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-14 11:08:29 +00:00
Alex van Andel
3a5522cf0e Renames 'version' to full package.json import (#2132) 2022-03-14 10:20:40 +00:00
Hariom Balhara
6377377c4d Fix Broken user dropdown (#2138)
* Fix Broken user dropdown

* Add unnecessary removal

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-14 10:15:00 +00:00
github-actions[bot]
62be5b561e New Crowdin translations by Github Action (#2126)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-14 10:12:20 +00:00
Leo Giovanetti
424482646f Removing headlessui used in menus (#2127) 2022-03-13 22:09:39 +00:00
sean-brydon
f0b1767b3c Link/In person location (#2104) 2022-03-13 15:56:56 +00:00
github-actions[bot]
3e3e802b28 New Crowdin translations by Github Action (#2124)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-13 12:14:33 +00:00
github-actions[bot]
da9f49341f New Crowdin translations by Github Action (#2123)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-13 10:11:40 +00:00
github-actions[bot]
1ab9728fa0 New Crowdin translations by Github Action (#2121)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-13 09:58:11 +00:00
Peer Richelsen
5b4cebac16 added day, version, plan and self host (sh) or hosted (h) into left sidebar footer (#2111) 2022-03-13 00:07:21 +00:00
Syed Ali Shahbaz
788e2acaff Fix for buffer not considering custom interval slots and event duration for slots when using custom intervals (#2079)
* modified buffer checks

* added custom interval consideration in getSlots fn

* further getslot call fixes

* added check for end of day availability slots

* removed debug remnants

* moved slot filtering into a function

* improved readability of code

* improved readability

* extracted getFilteredTimes outside useSlot

* added a buffer test

* added another buffer test

* edge case fix for eod availability and test fix

* removed unnecessary comments

* verbose comment

* fixed eod logic and updated expected test value

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-12 06:52:27 +00:00
github-actions[bot]
ada3317ba5 New Crowdin translations by Github Action (#2099)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-12 00:18:58 +00:00
Joe Au-Yeung
bb73e30b17 Revert "Add contributing to app store docs (#2113)" (#2115)
This reverts commit 006645b279.
2022-03-12 00:15:36 +00:00
Joe Au-Yeung
006645b279 Add contributing to app store docs (#2113)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-11 15:01:25 +00:00
Syed Ali Shahbaz
ec06f645bd added null eventtypeid fix (#2108) 2022-03-11 09:00:25 +00:00
sean-brydon
21bc4f9386 Radix UI Switch (#2075)
* Radix UI Switch

Making all switches consistent - using Raxix UI design instead of headless

* Moving Switch Component to Monorepo

* Update apps/web/components/booking/TimeOptions.tsx

* Fix stripe data import

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alan <alannnc@gmail.com>
2022-03-11 00:26:42 +00:00
Omar López
45f8d2d230 Type fixes (#2107)
* Update viewer.tsx

* Type fix
2022-03-10 22:29:34 +00:00
Omar López
d98731d50a Update viewer.tsx (#2106) 2022-03-10 15:13:26 -07:00
Joe Au-Yeung
ec97971e7d Fixed API call route (#2105) 2022-03-10 16:15:45 -05:00
Peer Richelsen
0b83133155 added emptyscreen for away mode profile (#2093)
* added emptyscreen for away mode profile

* eplaced shadow with border

* added snooze emoji

* Remove modified yarn.lock file from PR

* space between smiley and text

Co-authored-by: Peer Richelsen <peer@Peers-MacBook-Pro-2.local>
Co-authored-by: Peer Richelsen <peer@hey.com>
2022-03-10 19:21:27 +00:00
Omar López
5625cf226b Stripe to monorepo (#2063)
* downgrade func

* fix security hole lol

* fix query conditions

* - set to trial not free
- auto create stripe customer if missing
- fix production check

* Extracts downgrade logic to script, fixes ts-node conflicts with prisma

* Adds trialEndsAt field to users

* Updates trial/downgrade logic

* Typo

* Legibility fixes

* Update team-billing.ts

* Legibility improvements

* Updates illegal logic

* WIP

* WIP migrating stripe to package

* Update website

* Import fixes

* Import fixes

* Fixes to downgrade script

* Check for premium usernames before downgrading

* Fixed formatting

* Delete deploy-env.sh

* Locks dayjs to 1.10.6

* Type fixes

* Seems like we're stuck with dayjs 1.10.4

* Script fixes

* Adds first name to dump

* Loop fix

Co-authored-by: Jamie <ijamespine@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-09 15:56:05 -07:00
Peer Richelsen
adbae64619 fixed tablet view of event-types (#2098)
Co-authored-by: Peer Richelsen <peer@hey.com>
2022-03-09 22:17:46 +00:00
github-actions[bot]
ecf352ce00 New Crowdin translations by Github Action (#2095)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-09 20:46:56 +00:00
Omar López
e53648d218 Linting (#2083)
* Runs yarn format

* Update website

* Web lint is failing

* Formatting

* Adds cache for dependency install

* Adds linting reports for GH

* Fixes artifact upload

* Caching tests

* Merge reports on GH actions

* Linting

* Fix prettier plugin conflicts

* Dep fixes

* Moves tailwind to monorepo package (#2091)

* New Crowdin translations by Github Action (#2077)

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

* fix redirection to 404 page on login (#2086)

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

* fix: update contributing (#2084)

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>

* Formatting

* Update website

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Louis <48682663+louis-27@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-09 17:52:48 +00:00
github-actions[bot]
9da761b21c New Crowdin translations by Github Action (#2094)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-09 17:06:46 +00:00
Louis
202db9315f fix: update contributing (#2084)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-09 14:47:29 +00:00
Hariom Balhara
89f86e2c84 fix redirection to 404 page on login (#2086)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-09 14:47:02 +00:00
github-actions[bot]
0f27385c17 New Crowdin translations by Github Action (#2077)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-08 23:34:22 +00:00
Omar López
622d0fd0bc Moves tailwind to monorepo package (#2091) 2022-03-08 15:51:53 -07:00
Omar López
5908e5b14b E2E fixes (#2092) 2022-03-08 15:40:31 -07:00
Agusti Fernandez
4908b6fd01 hotfix: organizer needs to confirm booking, not attendee (#2090)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-03-08 10:08:35 -07:00
Omar López
b93f87af14 Upgrades prisma to 3.10 (#1978)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-07 18:30:28 +00:00
Mayowa Ojo
71c9a7b931 Feature/send opt in booking email (#2048)
* Added attendee request email template

* send attendee request email

* Added booking_submitted_subject message

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-07 18:18:23 +00:00
Louis Haftmann
b143498393 fix: disregarding already booked spots or blocked calendar times (#2029)
* fix: double booking

* fix: update double-booking error response code

* fix: update double-booking error response code

* test: add test

* fix: check availability before creating booking

* Update apps/web/playwright/booking-pages.test.ts

* Update yarn.lock

* Restored missing fix

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-03-07 10:55:24 -07:00
Demian Caldelas
322a845a17 Fix prisma setup on e2e tests (#2070)
Co-authored-by: Omar López <zomars@me.com>
2022-03-07 16:35:12 +00:00
sean-brydon
3bcc4b86e5 Back Button Events - [2058] (#2074)
* Back Button Events - [2058]

* Fixing URL

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-07 08:48:51 -07:00
Alex van Andel
3a67ae6d1f Added named dialog to replace new=1 (#2076)
(cherry picked from commit d6dee7a2c95e6959c0e59b58371fa43b0fa98212)
2022-03-06 23:06:18 +00:00
Syed Ali Shahbaz
8c4eed2bbc Add "light-brand" and "dark-brand" colors (add a second color picker) (#2028)
* init dark brand color addition

* added dark mode css vars

* added contrast brand colors

* minor fixes

* added dark branding to loader, button

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-05 15:37:46 +00:00
Hariom Balhara
ce0c8347fb [Perf Improvement] Booking Page score should be in green now (#2057)
* Avoid crypto to land in the browser

* Avoid prefetching as it has Crypto code bundled in AvatarGroup

* Use md5 directly if avatar not available

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-05 15:28:24 +00:00
sean-brydon
91b732ff1c Fixing Mobile UI - Event Types (#2065)
* Event Types - Mobile Ui

* Removing Daily video from default list

Added this for testing purposes

* Removing ZOD + mt on clock

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-05 14:42:43 +00:00
Peer Richelsen
a311f6bf4b sync website (#2062) 2022-03-04 13:28:09 -07:00
github-actions[bot]
04f9b93ceb New Crowdin translations by Github Action (#2061)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-04 19:41:28 +00:00
Omar López
190cc8caf6 Ensures prisma schemas are generated and formatted post-install (#2060) 2022-03-04 10:43:40 -07:00
Omar López
b6a20cc4d7 Fixes upgrade for users without customer id (#2059) 2022-03-04 16:38:42 +00:00
Syed Ali Shahbaz
eeb0cd7e4d Set buffer time before/after event type (#2015)
* before and after buffer added to handleAvailableSlots function

* --WIP

* added migration

* pull buffer data from DB

* cleanup

* added buffer input in form

* removed unused functions in controller field

* improved the buffer time check

* fixed default value and added preceding event afterbuffer consideration

* fixed e2e test issue

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-04 10:19:03 +00:00
Joe Au-Yeung
b77923fc65 Bugfix/opt in booking shown on attendee booking page (#2026) 2022-03-04 10:04:05 +00:00
hariombalhara
6687544e66 Enable Data Fetch logging with log=1 param (#2042) 2022-03-04 10:03:10 +00:00
github-actions[bot]
7384675b6b New Crowdin translations by Github Action (#2055)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-04 10:02:14 +00:00
sean-brydon
87e3c8d4a5 Update Booking Page A11ly (#2053) 2022-03-04 08:50:00 +00:00
github-actions[bot]
e23f9330d3 New Crowdin translations by Github Action (#2049)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-03 20:28:04 +00:00
Peer Richelsen
759bb67077 updated team billing english translation 2022-03-03 20:19:12 +00:00
Omar López
0a8509d721 Admin/team billing downgrader (#2040)
* downgrade func

* fix security hole lol

* fix query conditions

* - set to trial not free
- auto create stripe customer if missing
- fix production check

* Extracts downgrade logic to script, fixes ts-node conflicts with prisma

* Adds trialEndsAt field to users

* Updates trial/downgrade logic

* Typo

* Legibility fixes

* Update team-billing.ts

* Legibility improvements

Co-authored-by: Jamie <ijamespine@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-03 19:29:19 +00:00
sean-brydon
f4b6a16a9e Enable Hotreload for packages (#2051)
Co-authored-by: Omar López <zomars@me.com>
2022-03-03 12:19:25 -07:00
Louis Haftmann
6e8fbc280f fix: HTTP 404 with uppercase username (#2045)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-03-03 16:42:30 +00:00
Syed Ali Shahbaz
52e6711d51 Fixes incorrect Webhook Component title (#2047)
* fixed incorrect Webhook Component title

* fixed subtitle

* fixed lint

* lint fix
2022-03-03 14:16:07 +00:00
Syed Ali Shahbaz
0fb44887e3 Fix missing phone data from location after booking (#1968) 2022-03-03 09:57:59 +00:00
Thang Vu
b376ebae25 chore: update correct link for docs (#2030) 2022-03-03 09:55:26 +00:00
Krunal Shah
49ddd6cb59 Update CONTRIBUTING.md (#2036) 2022-03-03 09:54:51 +00:00
Krunal Shah
c437f15868 fix: jitsi email does not contain meeting link (#2004) 2022-03-03 09:54:19 +00:00
alannnc
15052c8b48 fix/booking-first-day-of-next-mont (#2037)
* fix/booking-first-day-of-next-mont

* Change minimum wait time for booking next month first day test

* Change minimum wait time for booking next month first day test

* Change minimum wait time for booking next month first day test

* Added Todo note for future improvement

* Apply same timeout change in others change of month action test
2022-03-02 15:51:46 -07:00
github-actions[bot]
800002222b New Crowdin translations by Github Action (#2034)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-02 21:26:01 +00:00
Omar López
e3283baa0e Update turbo.json (#2039) 2022-03-02 13:28:57 -07:00
Omar López
382d56ab54 Revert "Revert "Webhooks to support event type association"" (#2033)
* Revert "Revert "Webhooks to support event type association (#1889)" (#2032)"

This reverts commit 71e74b8320.

* Fixes turbo DB deploy

* Update turbo.json
2022-03-02 09:24:57 -07:00
github-actions[bot]
c93e8774c9 New Crowdin translations by Github Action (#2031)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-02 16:10:16 +00:00
Omar López
71e74b8320 Revert "Webhooks to support event type association (#1889)" (#2032)
This reverts commit d338504856.
2022-03-02 08:49:35 -07:00
Syed Ali Shahbaz
d338504856 Webhooks to support event type association (#1889)
* --init database and queries

* fixed type check

* added webhook api for event types

* added webhook list in team event

* delete, edit webhook in team event

* updated webhook subtext for event type

* added discord integration to event type webhook

* check fix

* consistency

* minor code improvement

* lint fix

* Adds missing zod schemas

* requested changes pt1 --WIP

* requested changes pt2 --WIP

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-03-02 08:30:13 -07:00
Krunal Shah
af793e9e81 docs: add contributing guide (#2021)
* docs: add contributing guide

* Update apps/docs/pages/contributing.mdx

* Update apps/docs/pages/contributing.mdx

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-01 19:27:36 +00:00
github-actions[bot]
6caf09e3e7 New Crowdin translations by Github Action (#2024)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-01 19:22:58 +00:00
github-actions[bot]
b6742c4c4a New Crowdin translations by Github Action (#2019)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-01 19:19:31 +00:00
github-actions[bot]
fcdd2ab81b New Crowdin translations by Github Action (#2017)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-01 11:04:36 +00:00
Krunal Shah
8fd976f5c7 fix: docs title showing untitled (#2006)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-03-01 11:03:51 +00:00
github-actions[bot]
521b63e732 New Crowdin translations by Github Action (#2007)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-01 09:49:54 +00:00
Peer Richelsen
4890e6b5b9 Update README.md 2022-02-28 23:07:10 +00:00
alannnc
9851c8f526 fix/secret-premade-event-hidden (#1999)
* fix/secret-premade-event-hidden

* fix/secret-premade-event-hidden: change hidden to partial in zod custom

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-02-28 21:23:17 +00:00
Bailey Pumfleet
7826a34b00 Let users set 12/24 hour time format (#2002) 2022-02-28 16:24:47 +00:00
Syed Ali Shahbaz
2559873b2c hotfix ambiguous cancel dialog (#2001) 2022-02-28 10:15:37 +00:00
hariombalhara
cc90cf0b72 [Bugfix] Booking Date Picker - First few dates are rendered fast, but later dates are all rendered in UI at once. (#1989) 2022-02-28 09:16:43 +00:00
github-actions[bot]
8717d96d0a New Crowdin translations by Github Action (#1996)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-27 23:12:35 +00:00
alannnc
c8ba5e1aa1 fix/slots-calculate-hours (#1994)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-27 22:51:41 +00:00
Agusti Fernandez
eb59908c84 Fixes undefined bug by replacing BASE_URL with NEXT_PUBLIC_BASE_URL in Shell and signup. (#1991)
* Replace BASE_URL with NEXT_PUBLIC_BASE_URL in Shell

* Replace BASE_URL with NEXT_PUBLIC_BASE_URL in apps/web/pages/auth/signup

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-27 22:34:08 +00:00
Peer Richelsen
981fb9c5be fixed dialog button on mobile (#1995)
Co-authored-by: Peer Richelsen <peeroke@richelsen.net>
2022-02-27 21:03:56 +00:00
github-actions[bot]
ca29940ea5 New Crowdin translations by Github Action (#1975)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-27 20:18:43 +00:00
hariombalhara
cf186e58bd [Perf Improvement] Event Booking Date Picker (#1980)
* Memoize and remove repeat calls of functions

* Better fn names

* Remove unnecessary code change

* Process dates asyncly

* Avoid waste work

* Add comments

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-27 00:19:50 +00:00
Omar López
3bae13eea8 Website update (#1986) 2022-02-26 12:29:48 -07:00
Agusti Fernandez
5d4cbe37eb Fixes connectionIntegration undefined bug (#1987)
* Fixes connectionIntegration undefined bug

* fixes adding NEXT_PUBLIC_BASE_URL to consts and using that

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-02-26 12:27:52 -07:00
Omar López
48f969eae5 Update vercel.sh (#1983) 2022-02-25 13:13:56 -07:00
Leo Giovanetti
546f627177 Fix missing zero-padding on troubleshoot (#1974)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-24 12:36:02 -07:00
Omar López
c6169607ae Adds script to regulate staging deploys (#1979) 2022-02-24 12:17:49 -07:00
Omar López
795423ae55 Locks Node version to 14 (#1977) 2022-02-24 11:39:01 -07:00
github-actions[bot]
00e3b970d6 New Crowdin translations by Github Action (#1973)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-24 15:07:39 +00:00
Demian Caldelas
9e786b9133 Fix db-studio calling a missing script (#1966)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-02-24 13:58:27 +00:00
Krunal Shah
2941ad334c fix: twitter handle in seo (#1967) 2022-02-24 13:55:34 +00:00
Omar López
cefd0cfb16 Added API private submodule (#1958) 2022-02-24 01:14:37 +00:00
alannnc
f8aa274b07 Fix billing portal for users without stripeCustomerId (#1936) 2022-02-23 22:51:10 +00:00
Omar López
3d2fd28214 Prod fix! (#1964)
* Adds website as a submodule

* Added website to monorepo

* Adds script to depliy submodules on Vercel

* Updates website

* Updates vercel script

* Sets default branch name as main

* Update website

* Cleanup git logs in Vercel

* Update website

* Update website

* Prisma build cache improvements

* Update website
2022-02-23 13:58:38 -07:00
Omar López
fa66448f89 Prisma cache improvements (#1963)
* Adds website as a submodule

* Added website to monorepo

* Adds script to depliy submodules on Vercel

* Updates website

* Updates vercel script

* Sets default branch name as main

* Update website

* Cleanup git logs in Vercel

* Update website

* Update website

* Prisma build cache improvements
2022-02-23 13:44:30 -07:00
Omar López
73cdf5dda5 Migrate website to monorepo (#1962)
* Adds website as a submodule

* Added website to monorepo

* Adds script to depliy submodules on Vercel

* Updates website

* Updates vercel script

* Sets default branch name as main

* Update website

* Cleanup git logs in Vercel

* Update website

* Update website
2022-02-23 20:35:23 +00:00
Demian Caldelas
eac2e4e53e Silence trpc logging by default (#1949)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-23 16:37:41 +00:00
Demian Caldelas
d9d95ba17c Test/get availability from schedule unit test (#1951)
* Fix typo in SetTimesModal

* Remove db dependency to run unit tests

* Add unit test for getAvailabilityFromSchedule
2022-02-23 16:16:04 +00:00
Alex van Andel
3bca9687d0 Delete .vercelignore (#1957)
Poof!
2022-02-23 15:58:08 +00:00
Peer Richelsen
b91dfe7595 Fix/border avatargroup (#1956)
* adding border to avatargroup based on the parent background color

* fixed border

Co-authored-by: Peer Richelsen <peeroke@richelsen.net>
2022-02-23 15:29:40 +00:00
Peer Richelsen
9e89f954e8 adding border to avatargroup based on the parent background color (#1954) 2022-02-23 13:55:59 +00:00
Agusti Fernandez
b860a79d59 Detect users browser locale for time format 12/24 hours (#1900)
* fix: adds new isBrowserLocal24h timeFormat util, uses in BookingPage

* fix: adds new time format locale detector in available times

* fix: removes 24h clock from availabletimes

* chore: move timeFormat to lib util, add to payment page

* chore: remove commented out is24h

* fix: adds timeFormat to success page

* fix: adds timeFormat to cancel page

* fix: adds timeFormat to video meeting ended/not started pages

* fix: removes added typo in success page

* fix: reverts back to use of is24h Switch in available times / time options, renames timeFormat to detectBrowserTimeFormat to avoid collisions

* fix: moves use uf isBrowserLocal24h() to clock util initClock() itself, by calling it only if no localStorage settings are set

* chore: move back timeFormat props to line it was so no change

* chore: remove empty line in timeOptions

* chore: move back timeFormat where it was in TimeOptions props

* chore: add back empty line before selectedTimeZone return

* fix: reverts back to use of is24h in payments page

* feat: adds browser locale as default in payment page in case there's no settings set by the customer

* feat: adds browser locale as default in success page

* fix: deconstruct props so eslint passes

* fix: lint issue for extra empty line in meeting-ended uid page

Co-authored-by: Agusti Fernandez <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-23 12:37:15 +00:00
github-actions[bot]
5eca42bb45 New Crowdin translations by Github Action (#1952)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-23 12:01:55 +00:00
Peer Richelsen
5cf67fdbaa fixed avatar group and tooltip (#1950) 2022-02-23 11:09:22 +00:00
Demian Caldelas
ac0c3bdfb9 Fix dynamic min/max values for schedule form (#1940)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-02-23 00:23:52 +00:00
Alex van Andel
652c2e342f Second go at removing Zod generated classes from our repo (#1946)
* Second go at removing Zod generated classes from our repo

* Directly reference the _EventTypeModel
2022-02-22 23:19:22 +00:00
Agusti Fernandez
ecbdfea818 Adds BASE_URL to connectIntegrations (#1883) 2022-02-22 21:23:55 +00:00
github-actions[bot]
0846d0666b New Crowdin translations by Github Action (#1926)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-22 20:15:56 +00:00
Alex van Andel
a704f1ed0a Always disconnect + remove redundant success message (#1945) 2022-02-22 12:04:55 -07:00
Omar López
97550a39f3 Contributing guide (#1930)
* WIP

* Updates contributing guide

* Update PULL_REQUEST_TEMPLATE.md
2022-02-22 12:33:23 +00:00
Miguel Nieto A
e36428de5d Docs/prisma commands on docs (#1875)
* Update prisma commands

Prisma commands have changed due to the new project structure

* Set the type of code block to have code block style

* Apply required changes

* Update README
2022-02-21 20:47:50 +00:00
Agusti Fernandez
373bc1660c Adds BASE_URL to callbackUrl in signup page and Shell component (#1882)
* add WEBSITE_URL to callbackUrl in signup page and Shell component

* fix: WEBSITE_URL -> BASE_URL, login missing do in another pr maybe

Co-authored-by: Agusti Fernandez <agusti@colony.io>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Agusti Fernandez <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-21 13:09:56 -07:00
alannnc
9863178025 fix/update-days-schedule (#1931)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-02-21 12:08:36 -07:00
Omar López
50c75da5e0 Build fixes (#1929)
* Build fixes

* Fixes type error
2022-02-21 18:45:35 +00:00
Juan Esteban Nieto Cifuentes
7585e9b32e Fix URL by removing slash and backslash (#1733)
* Fix URl by removing slash and backslash

* Implement slugify

* Add data type

* Fixing folder structure

* Solve zod-utils conflict
2022-02-21 09:53:16 -07:00
Omar López
95b3397e42 Add ui package for reusable components (#1916)
* Add ui package for reusable components

* Add fallback

* Type fixes

* Type fixes

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-21 09:41:25 -07:00
Demian Caldelas
b3ada7c25c Handy shortcut to start Prisma Studio (#1921)
* Add fast access to start Prisma Studio

* Updates docs

* Minor fixes in docs

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-21 16:02:09 +00:00
Peer Richelsen
0142a1502f Update README.md 2022-02-20 16:48:48 +00:00
Demian Caldelas
8996c168ca Refresh the shouldOnboard state in the Shell after onboarding (#1918)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-20 14:07:15 +00:00
github-actions[bot]
4d14809ecf New Crowdin translations by Github Action (#1922)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-20 13:34:22 +00:00
alannnc
6749b887dd fix/bookings-order (#1920) 2022-02-19 18:00:35 -07:00
Lola
08e6059c8d Removing room entry buffer before a Daily.co video call (#1902)
* initial

* scale plan instructions in the readme and removed the buffer for entering a video call

* readme update for Daily Scale Plan users

Co-authored-by: Lola-Ojabowale <lola.ojabowale@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-19 00:39:10 +00:00
buschco
b9dd90b998 include rescheduleUid webhook payload (#1551) (#1584)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-19 00:09:07 +00:00
github-actions[bot]
bced10eab1 New Crowdin translations by Github Action (#1915)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-18 23:59:29 +00:00
Omar López
deeafb21a5 Upgrades prisma to latest version (#1894)
* Upgrades prisma

* Extends Stripe paid booking timeout

* Stripe test fixes

* Disables cache for db-reset

* Disabled cache from db-seed

* Avatar fixes

* Fixes paid booking test once and for all

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-18 15:16:53 -07:00
Peer Richelsen
81b4443fc2 changed text of share button 2022-02-18 20:01:24 +00:00
Syed Ali Shahbaz
4f89205dd4 Adds discord webhook support (#1886)
* --init

* cleanup

* removed extra spacing

* changed onchange handler name

* modified payload

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-18 17:49:39 +00:00
Juan Esteban Nieto Cifuentes
75d19e0e7d Add Web Share in mobile (#1810)
* Add navigator.share in mobile

* Validate navigator.share in useEffect

* Add new Button with navigator.share condition

* Add new Icons

* Solve check types
2022-02-18 10:34:53 -07:00
Bailey Pumfleet
c8ae414ecf Add 500 error page (#1910)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-02-18 10:33:18 -07:00
Omar López
e7dc2d3d7a Upgrade to next 12.1 (#1904)
* Upgrades next to 12.1

* Fixes build

* Updaters e2e test pipelines

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-18 10:32:59 -07:00
Deepak Prabhakara
5b66c1f986 Fix/sso username (#1897)
* username slug should be lowercase

* if logging in via a non-CAL identity provider then show username during onboarding

* type fix

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-02-18 17:32:12 +00:00
github-actions[bot]
0b4f771462 New Crowdin translations by Github Action (#1891)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-18 17:02:15 +00:00
Omar López
944a3c02ce Eslint fixes (#1898)
* Eslint fixes

* Docs build fixes
2022-02-18 16:53:45 +00:00
Peer Richelsen
5185704a38 added swedish (#1911) 2022-02-18 15:51:46 +00:00
Omar López
152bb57bc1 Revert "Tweak/gitignore prisma zod (#1905)" (#1906)
This reverts commit 15bfeb30d7.
2022-02-17 18:11:33 -07:00
Omar López
15bfeb30d7 Tweak/gitignore prisma zod (#1905)
* Extracts ignored createEventTypeBaseInput

* Adds postinstall script
2022-02-17 17:25:19 -07:00
Omar López
0bf3818b30 Revert "Upgrades next to 12.1 (#1895)" (#1903)
This reverts commit ede0e98e1f.
2022-02-17 16:58:16 -07:00
Omar López
ede0e98e1f Upgrades next to 12.1 (#1895)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-17 22:29:54 +00:00
Omar López
d702bcf0a3 Regenerates zod files (#1896) 2022-02-17 11:26:53 -07:00
Demian Caldelas
2194b92fdf Turbo fixes. Run apps (web/docs) scoped instead of concurrently (#1887)
* Improving dx for running apps from turbo

* Assign a fixed port to serve docs

Co-authored-by: Omar López <zomars@me.com>
2022-02-17 08:37:35 -07:00
Peer Richelsen
2c51fd77a0 wip (#1890)
Co-authored-by: Peer Richelsen <peeroke@richelsen.net>
2022-02-17 11:28:49 +00:00
Alex van Andel
f8b908500f Updated attendees to auto-accept and setting reminders to useDefault (#1880) 2022-02-16 18:21:51 -07:00
github-actions[bot]
ac0840c802 New Crowdin translations by Github Action (#1879)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-16 22:42:27 +00:00
Francisco Lourenço
392aac2c6e Replace whatsmybrowser.org with Bird Eats Bug (#1787)
Noticed that you guys are [already suggesting](https://github.com/calcom/cal.com/issues/1562#issuecomment-1022707225) users to provide Bird recordings, so thought it could be useful here as well :)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-16 22:30:57 +00:00
github-actions[bot]
4236288d32 New Crowdin translations by Github Action (#1874) 2022-02-16 16:18:55 +00:00
hariombalhara
4693cbba4f Feature: Instant Theme Change, without refresh [Booking Pages Only] (#1807)
* Avoid Theme Flicker. Render Server Side

* Add back isReady implementation

* Use shorter syntax for Tag

* Listen to changes in pref-color-scheme and act

* Uglify Theme Applier code

* Resolve conflicts

* Add comments

* Appropriate function name

* Move uglify-js to dependencies

* Remove uglify-js

* Fix commnt

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-16 15:53:26 +00:00
hariombalhara
34c5360a4d Remove intercom from public booking pages (#1835)
* Remove intercom from public pages

* remove from success and cancel pages as well

* remove from Reschedule page as well

* Fix comment

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-02-16 08:26:48 -07:00
Syed Ali Shahbaz
228dea1308 Hotfix for success page email input and button (#1854)
* hotfix

* cleanup

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-16 11:33:16 +00:00
Omar López
69dd6fe7d4 Adds testing mail credentials (#1865)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-16 10:47:23 +00:00
Omar López
7a1e250016 Test possible fix for Vercel builds (#1859) 2022-02-16 10:41:17 +00:00
Miguel Nieto A
00d6495752 Fix prisma commands in README file (#1843) 2022-02-16 10:36:37 +00:00
github-actions[bot]
bebc119c13 New Crowdin translations by Github Action (#1873)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-02-16 10:35:07 +00:00
Frank Greco Jr
717d9a512d hide additional notes in organizer scheduled email if empty (#1870)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-16 10:32:13 +00:00
Bruno Wego
9814914b83 fix(github): wrong config yml file name in issue template (#1867)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-02-16 10:31:28 +00:00
Maximous Black
693dc6d9ce fix: prisma schema directory in heroku config (#1872) 2022-02-16 10:30:23 +00:00
287 changed files with 8953 additions and 3635 deletions

1
.eslintrc.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require("./packages/config/eslint-preset");

View File

@@ -22,6 +22,6 @@ Any other relevant information. For example, why do you consider this a bug and
### Technical details
- Browser version: You can use https://www.whatsmybrowser.org/ to find this out.
- Browser version, screen recording, console logs, network requests: You can make a recording with [Bird Eats Bug](https://birdeatsbug.com/).
- Node.js version
- Anything else that you think could be an issue.

View File

@@ -2,4 +2,4 @@ blank_issues_enabled: false
contact_links:
- name: Questions
url: https://cal.com/slack
about: Ask a general question about the project on our Slack workspace
about: Ask a general question about the project on our Slack workspace

View File

@@ -20,12 +20,15 @@ Fixes # (issue)
- [ ] Test A
- [ ] Test B
## Checklist:
## Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code and corrected any misspellings
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
<!-- Please remove all the irrelevant bullets to your PR -->
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't performed a self-review of my own code and corrected any misspellings
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my PR needs changes to the documentation
- I haven't checked if my changes generate no new warnings
- I haven't added tests that prove my fix is effective or that my feature works
- I haven't checked if new and existing unit tests pass locally with my changes

View File

@@ -27,10 +27,11 @@ jobs:
SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
SAML_ADMINS: pro@example.com
# NEXTAUTH_URL: xxx
# EMAIL_FROM: xxx
# EMAIL_SERVER_HOST: xxx
# EMAIL_SERVER_PORT: xxx
# EMAIL_SERVER_USER: xxx
EMAIL_FROM: e2e@cal.com
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD }}
# MS_GRAPH_CLIENT_ID: xxx
# MS_GRAPH_CLIENT_SECRET: xxx
# ZOOM_CLIENT_ID: xxx

View File

@@ -15,10 +15,30 @@ jobs:
- name: Use Node.js 14.x
uses: actions/setup-node@v2
with:
version: 14.x
node-version: 14.x
cache: "yarn"
cache-dependency-path: yarn.lock
- name: Install deps
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn
- name: Lint
run: yarn lint
run: yarn lint:report
continue-on-error: true
- name: Merge lint reports
run: jq -s '[.[]]|flatten' lint-results/*.json &> lint-results/eslint_report.json
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@1.2.0
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
report-json: "lint-results/eslint_report.json"
- name: Upload ESLint report
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: lint-results
path: lint-results

12
.gitignore vendored
View File

@@ -59,5 +59,15 @@ yarn-error.log*
# Typescript
tsconfig.tsbuildinfo
# turbo
.turbo
.turbo
# Prisma-Zod
packages/prisma/zod/*.ts
# Builds
dist
# Linting
lint-results

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "apps/api"]
path = apps/api
url = git@github.com:calcom/api.git
[submodule "apps/website"]
path = apps/website
url = git@github.com:calcom/website.git

6
.husky/post-receive Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "Updating submodules recursively"
pwd
git submodule update --init --recursive

View File

@@ -4,5 +4,5 @@ version = 1
autoupdate_label = "♻️ autoupdate"
[approve]
auto_approve_usernames = ["dependabot", "PeerRich", "baileypumfleet"]
auto_approve_usernames = ["dependabot"]

View File

@@ -1,13 +1 @@
module.exports = {
bracketSpacing: true,
bracketSameLine: true,
singleQuote: false,
jsxSingleQuote: false,
trailingComma: "es5",
semi: true,
printWidth: 110,
arrowParens: "always",
importOrder: ["^@(calcom|ee)/(.*)$", "^@lib/(.*)$", "^@components/(.*)$", "^@(server|trpc)/(.*)$", "^[./]"],
importOrderSeparation: true,
plugins: [require("./merged-prettier-plugin")],
};
module.exports = require("./packages/config/prettier-preset");

View File

@@ -1 +0,0 @@
.github

View File

@@ -7,6 +7,6 @@
"bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
"ban.spellright", // Spell check for docs
"stripe.vscode-stripe", // stripe VSCode extension
"Prisma.prisma" // syntax|format|completion for prisma
"Prisma.prisma" // syntax|format|completion for prisma
]
}

79
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,79 @@
# Contributing to Cal.com
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## Developing
The development branch is `main`. This is the branch that all pull
requests should be made against. The changes on the `main`
branch are tagged into a release monthly.
To develop locally:
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your
own GitHub account and then
[clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
2. Create a new branch:
```sh
git checkout -b MY_BRANCH_NAME
```
3. Install yarn:
```sh
npm install -g yarn
```
4. Install the dependencies with:
```sh
yarn
```
5. Start developing and watch for code changes:
```sh
yarn dev
```
## Building
You can build the project with:
```bash
yarn build
```
Please be sure that you can make a full production build before pushing code.
## Testing
More info on how to add new tests coming soon.
### Running tests
This will run and test all flows in multiple Chromium windows to verify that no critical flow breaks:
```sh
yarn test-e2e
```
## Linting
To check the formatting of your code:
```sh
yarn lint
```
If you get errors, be sure to fix them before committing.
## Making a Pull Request
- Be sure to [check the "Allow edits from maintainers" option](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) while creating you PR.
- If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. Se more about [Linking a pull request to an issue
](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
- Be sure to fill the PR Template accordingly.

View File

@@ -68,7 +68,7 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
Cal officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
![cal-star-github](https://user-images.githubusercontent.com/8019099/116010176-5d9c9900-a615-11eb-92d0-aa0e892f7056.gif)
![cal-star-github](https://user-images.githubusercontent.com/8019099/154853944-a9e3c999-3da3-4048-b149-b4f73893c6fb.gif)
<!-- GETTING STARTED -->
@@ -124,6 +124,14 @@ Here is what you need to be able to run Cal.
yarn dx
```
#### Development tip
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `apps/web/.env` to get logging information for all the queries and mutations driven by **trpc**.
```sh
echo 'NEXT_PUBLIC_DEBUG=1' >> apps/web/.env
```
#### Manual setup
1. Configure environment variables in the .env file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
@@ -163,13 +171,13 @@ yarn dx
1. Set up the database using the Prisma schema (found in `apps/web/prisma/schema.prisma`)
```sh
npx prisma migrate deploy
yarn workspace @calcom/prisma db-deploy
```
1. Run (in development mode)
```sh
yarn dev --scope=@calcom/web
yarn dev
```
#### Setting up your first user
@@ -177,12 +185,12 @@ yarn dx
1. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content:
```sh
npx prisma studio
yarn db-studio
```
1. Click on the `User` model to add a new user record.
1. Fill out the fields `email`, `username`, `password`, and set `metadata` to empty `{}` (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user.
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `prisma/schema.prisma` file.
> New users are set on a `TRIAL` plan by default. You might want to adjust this behavior to your needs in the `apps/web/prisma/schema.prisma` file.
1. Open a browser to [http://localhost:3000](http://localhost:3000) and login with your just created, first user.
### E2E-Testing
@@ -210,7 +218,7 @@ yarn workspace @calcom/web playwright-report
In a development environment, run:
```sh
npx prisma migrate dev
yarn workspace @calcom/prisma db-migrate
```
(this can clear your development database in some cases)
@@ -218,7 +226,7 @@ yarn workspace @calcom/web playwright-report
In a production environment, run:
```sh
npx prisma migrate deploy
yarn workspace @calcom/prisma db-deploy
```
3. Check the `.env.example` and compare it to your current `.env` file. In case there are any fields not present
@@ -233,14 +241,14 @@ yarn workspace @calcom/web playwright-report
4. Start the server. In a development environment, just do:
```sh
yarn dev --scope=@calcom/web
yarn dev
```
For a production build, run for example:
```sh
yarn build --scope=@calcom/web
yarn start --scope=@calcom/web
yarn build
yarn start
```
5. Enjoy the new version.
@@ -272,20 +280,17 @@ You can deploy Cal on [Railway](https://railway.app/) using the button above. Th
## Roadmap
See the [open issues](https://github.com/calcom/cal.com/issues) for a list of proposed features (and known issues).
See the [roadmap project](https://github.com/orgs/calcom/projects/1) for a list of proposed features (and known issues). You can change the view to see planned tagged releases.
<!-- CONTRIBUTING -->
## Contributing
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
Please see our [contributing guide](/CONTRIBUTING.md).
1. Fork the project
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Make your changes
4. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
5. Push to the branch (`git push origin feature/AmazingFeature`)
6. Open a pull request
### Good First Issues
We have a list of [good first issues](https://github.com/calcom/cal.com/labels/✅%20good%20first%20issue) that contain bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
## Integrations
@@ -334,6 +339,7 @@ Contributions are what make the open source community such an amazing place to b
2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
3. Copy your API key.
4. Now paste the API key to your .env file into the `DAILY_API_KEY` field in your .env file.
5. If you have the [Daily Scale Plan](https://www.daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
<!-- LICENSE -->

View File

@@ -21,6 +21,6 @@
"JWT_SECRET": "secret"
},
"scripts": {
"postdeploy": "cd apps/web && npx prisma migrate deploy"
"postdeploy": "cd packages/prisma && npx prisma migrate deploy"
}
}

1
apps/api Submodule

Submodule apps/api added at 378cbf8f3a

View File

@@ -1 +0,0 @@
module.exports = require("@calcom/config/eslint-preset");

5
apps/docs/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const withNextra = require("nextra")({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.js",

View File

@@ -4,17 +4,24 @@
"description": "",
"main": "index.js",
"scripts": {
"dev": "next",
"start": "next start",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"dev": "PORT=4000 next",
"lint": "next lint",
"lint:report": "eslint . --format json --output-file ../../lint-results/docs.json",
"start": "PORT=4000 next start",
"build": "next build"
},
"author": "Cal.com, Inc.",
"license": "MIT",
"dependencies": {
"next": "^12.0.9",
"next": "^12.1.0",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@calcom/config": "*",
"eslint": "^8.10.0"
}
}

View File

@@ -1,4 +1,5 @@
import "nextra-theme-docs/style.css";
import "./style.css";
export default function Nextra({ Component, pageProps }) {

View File

@@ -1,3 +1,7 @@
---
title: Availability
---
# Availability
## Setting your availability

View File

@@ -1,3 +1,7 @@
---
title: Billing
---
# Billing
## How the trial works
You are given FREE access for 14 days of our PRO subscription, you can use this to test and try out our product and see if it works for you. No credit card is required to sign up and you decide if you want to upgrade to a PRO subscription afterwards.
@@ -26,7 +30,7 @@ We've reserved a ton of premium usernames, such as short handles or first names
Some users may not be able to access Billing as their billing email is different to their account email. If this is the case, you can change the email associated with your account in [Profile Settings](https://app.cal.com/settings/profile).
## Subscription for each team member
If your team requires multiple event types then each team member has to be subscribed to our paid plan. If that is something that isnt necessary for your team, you can proceed with your FREE plan.
If your team requires multiple event types then each team member has to be subscribed to our paid plan. If that is something that isnt necessary for your team, you can proceed with your FREE plan.
## Discount for non-profits and students
We offer 50% for non-profit organizations and students. Just raise a ticket with our support team and submit the necessary proof of status.

View File

@@ -1,3 +1,7 @@
---
title: Bookings
---
# Bookings
## What can you do on the bookings page?

View File

@@ -0,0 +1,83 @@
---
title: Contributing
---
# Contributing
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## Developing
The development branch is `main`. This is the branch that all pull
requests should be made against. The changes on the `main`
branch are tagged into a release monthly.
To develop locally:
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your
own GitHub account and then
[clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
2. Create a new branch:
```sh
git checkout -b MY_BRANCH_NAME
```
3. Install yarn:
```sh
npm install -g yarn
```
4. Install the dependencies with:
```sh
yarn
```
5. Start developing and watch for code changes:
```sh
yarn dev
```
## Building
You can build the project with:
```bash
yarn build
```
Please be sure that you can make a full production build before pushing code.
## Testing
More info on how to add new tests coming soon.
### Running tests
This will run and test all flows in multiple Chromium windows to verify that no critical flow breaks:
```sh
yarn test-e2e
```
## Linting
To check the formatting of your code:
```sh
yarn lint
```
If you get errors, be sure to fix them before comitting.
## Making a Pull Request
- Be sure to [check the "Allow edits from maintainers" option](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) while creating you PR.
- If your PR refers to or fixes an issue, be sure to add `refs #XXX` or `fixes #XXX` to the PR description. Replacing `XXX` with the respective issue number. Se more about [Linking a pull request to an issue
](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
- Be sure to fill the PR Template accordingly.

View File

@@ -1,4 +1,8 @@
import Callout from 'nextra-theme-docs/callout';
---
title: Adding CSS
---
import Callout from "nextra-theme-docs/callout";
# Adding CSS
@@ -30,4 +34,4 @@ import "../styles/your-new-stylesheet.css";
These styles will apply to all pages and components in your application.
</Callout>
Due to the global nature of stylesheets, and to avoid conflicts, you may **only import them inside `pages/_app.tsx`**.
Due to the global nature of stylesheets, and to avoid conflicts, you may **only import them inside `pages/_app.tsx`**.

View File

@@ -0,0 +1,45 @@
---
title: Contributing to App Store
---
# Contributing to the App Store
Since Cal.com is open source we encourage developers to create new apps for others to use. This guide is to help you get started.
## Structure
All apps can be found under `packages/app-store`. In this folder is `_example` which shows the general structure of an app.
```sh
├──_example
| ├──index.ts
| ├──package.json
| ├──.env.example
|
| ├──api
| | ├──example.ts
| | ├──index.ts
|
| ├──lib
| | ├──adaptor.ts
| | ├──index.ts
|
| ├──static
| | ├──icon.svg
```
## Getting Started
In the `package.json` name your package appropriately and list the dependencies needed for the package.
Next in the `.env.example` specify the environmental variables (ex. auth token, API secrets) that your app will need. In a comment add a link to instructions on how to obtain the credentials. Create a `.env` with your the filled in environmental variables.
In `index.js` fill out the meta data that will be rendered on the app page. Under `packages/app-store/index.ts`, import your app and add it under `appStore`. Your app should now appear in the app store.
Under the `/api` folder, this is where any API calls that are associated with your app will be handled. Since cal.com uses Next.js we use dynamic API routes. In this example if we want to hit `/api/example.ts` the route would be `{BASE_URL}/api/integrations/_example/example`. Export your endpoints in an `index.ts` file under `/api` folder and import them in your main `index.ts` file.
The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file.
The `/static` folder is where your assets live.
If you need any help feel free to join us on [Slack](https://cal.com/slack)

View File

@@ -1,3 +1,7 @@
---
title: Code styling
---
# Code Styling
Keeping our code styles consistent is key to making the repository easy to read and work with.
@@ -12,4 +16,4 @@ Calendso uses the ESLint and Prettier formatting tools, and the repository comes
We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for all JavaScript and Typescript code.
## HTML & CSS
We use the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) for any HTML and CSS markup. However, exceptions to the HTML guide apply where JSX differentiates from standard HTML.
We use the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) for any HTML and CSS markup. However, exceptions to the HTML guide apply where JSX differentiates from standard HTML.

View File

@@ -1,9 +1,9 @@
{
"migrations": "Migrations",
"pre-fill": "Pre-fill fields",
"code-styling": "Code styling",
"project-structure": "Project structure",
"pull-requests": "Pull requests",
"adding-css": "Adding CSS"
}
"migrations": "Migrations",
"pre-fill": "Pre-fill fields",
"code-styling": "Code styling",
"project-structure": "Project structure",
"pull-requests": "Pull requests",
"adding-css": "Adding CSS",
"app-store": "Contributing to App Store"
}

View File

@@ -1,5 +1,9 @@
---
title: Migrations
---
# Database Migrations
As described in the [upgrade guide](https://docs.cal.com/self-hosting/upgrading.md), you should use the `npx prisma migrate dev` or `npx prisma migrate deploy` command to update the database.
As described in the [upgrade guide](https://docs.cal.com/self-hosting/upgrade), you should use the `yarn workspace @calcom/prisma db-migrate` or `yarn workspace @calcom/prisma db-deploy` command to update the database.
We use database migrations in order to handle changes to the database schema in a more secure and stable way. This is actually very common. The thing is that when just changing the schema in `schema.prisma` without creating migrations, the update to the newer database schema can damage or delete all data in production mode, since the system sometimes doesn't know how to transform the data from A to B. Using migrations, each step is reproducable, transparent and can be undone in a simple way.
@@ -7,8 +11,8 @@ We use database migrations in order to handle changes to the database schema in
If you are modifying the codebase and make a change to the `schema.prisma` file, you must create a migration.
To create a migration for your previously changed `schema.prisma`, simply run the following:
```
npx prisma migrate dev
```sh
yarn workspace @calcom/prisma db-migrate
```
Now, you must create a short name for your migration to describe what changed (for example, "user_add_email_verified"). Then just add and commit it with the corresponding code that uses your new database schema.
@@ -21,7 +25,7 @@ Always keep an eye on what migrations Prisma is generating. Prisma often happily
## Error: The database schema is not empty
Prisma uses a database called `_prisma_migrations` to keep track of which migrations have been applied and which haven't. If your local migrations database doesn't match up with what's in the actual database, then Prisma will throw the following error:
```
```text
Error: P3005
The database schema for `localhost:5432` is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
@@ -30,8 +34,8 @@ The database schema for `localhost:5432` is not empty. Read more about how to ba
In order to fix this, we need to tell Prisma which migrations have already been applied.
This can be done by running the following command, replacing `migration_name` with each migration that you have already applied:
```
npx prisma migrate resolve --applied migration_name
```sh
yarn prisma migrate resolve --applied migration_name
```
You will need to run the command for each migration that you want to mark as applied.
You will need to run the command for each migration that you want to mark as applied.

View File

@@ -1,3 +1,7 @@
---
title: Pre-fill fields
---
# Pre-fill fields
You can pre-fill a number of fields on the booking form by using their corresponding URL parameters. This can include the users name, email, or guests to be added to the booking.
@@ -17,4 +21,4 @@ Guests can also be added to the link, there is also no limit to the amount of gu
These should be added to your link like this:
```text
guest=guest1@example.com&guest=guest2@example.com
```
```

View File

@@ -1,3 +1,7 @@
---
title: Project structure
---
# Project Structure
This page gives an overview of how the codebase is structured so you can easily dive into the Cal.com code.

View File

@@ -1,3 +1,7 @@
---
title: Pull requests
---
# Pull Requests
## Requirements

View File

@@ -1,3 +1,7 @@
---
title: Event Types
---
# Event Types
Event types allow you to create different events for different occasions when booking a time with you in your calendar. These can be named differently, have different time durations and the choice of platform can change.
@@ -13,7 +17,7 @@ Event types allow you to create different events for different occasions when bo
## Editing event types
1. Go to [Your Event Types](https://app.cal.com/event-types).
2. Click anywhere within the box of the event you would like to edit.
2. Click anywhere within the box of the event you would like to edit.
(From here you can edit the basic settings of your event)
3. To get the advanced options, at the bottom of your event setting click 'Show Advanced Settings'
4. After you have finished editing the event type, scroll to the bottom of your page and select 'Update'
@@ -27,7 +31,7 @@ Event types allow you to create different events for different occasions when bo
## How to block a time slot before/after a meeting
You can block out a time frame in your calendar only after the meeting. You can do this by selecting `Show advanced settings` of your Event Type. The setting is labeled `Time-slot intervals`.
## Setting up specific availability for each type of Event
## Setting up specific availability for each type of Event
Head to `Show advanced settings` of your event. At the bottom you can set up specific availability for different Event Types.
## Availability not showing on a certain day in your calendar

View File

@@ -1,3 +1,7 @@
---
title: FAQs
---
# Frequently asked questions
## Does Cal.com support a custom domain?
@@ -7,7 +11,7 @@ This is possible with our self-hosted option.
As it stands this is currently not possible. We always keep an eye on the limitations like these that our users point to us. Weve had requests in the past for the multi-booking feature and this is on our priority list.
## How to quickly block further bookings?
1. Click on the lower left corner of your dashboard where your username is displayed.
1. Click on the lower left corner of your dashboard where your username is displayed.
2. That initates a dropdown menu. Click on `Set youself as away`.
This is a method to disable your Cal.com account which won't allow any bookings once initiated. However, bookings made before turning on *away mode* will still be booked.

View File

@@ -1,4 +1,8 @@
import Callout from 'nextra-theme-docs/callout';
---
title: Import
---
import Callout from "nextra-theme-docs/callout";
# Import data from other scheduling tools
@@ -27,4 +31,4 @@ The following steps will help you retrieve your SavvyCal access token, which you
4. Click to copy the token, and then paste the token into the Cal.com importer
<Callout>
Even though we don't store your access token, you can press the trash icon to revoke the access token from the **Developers** tab once the import is complete.
</Callout>
</Callout>

View File

@@ -1,4 +1,8 @@
import Bleed from 'nextra-theme-docs/bleed'
---
title: Home
---
import Bleed from "nextra-theme-docs/bleed";
# Cal.com Documentation

View File

@@ -1,3 +1,7 @@
---
title: Google
---
# Google Calendar
The Google Calendar integration checks for availability in your Google Calendars and creates bookings for you.
@@ -27,10 +31,10 @@ To remove a product from your account that isn't listed in your Google Account,
## Where to find the Google Meet integration?
Google Meet is a part of the Google Calendar integration and it should be available once you've added your Google Calendar. Just select Google Meet as location for your Event Type:
Google Meet is a part of the Google Calendar integration and it should be available once you've added your Google Calendar. Just select Google Meet as location for your Event Type:
1. Go to your `Event Types`.
2. Click on the `Location` drop-down menu.
3. Select Google Meet as the location of your meeting.
3. Select Google Meet as the location of your meeting.
Once your Event Type slot is booked, it will automatically generate the Google Meet link for the meeting.
Once your Event Type slot is booked, it will automatically generate the Google Meet link for the meeting.

View File

@@ -1,3 +1,7 @@
---
title: Introduction
---
# Integrations
## Connecting new calendars
@@ -13,5 +17,5 @@
If you have two or more integrated calendars and you want your events to show in only one, you can define a primary calendar like this:
1. Go to your [Integrations](https://app.cal.com/integrations) page.
2. Next to your `Calendars` you will see a dropdown that says `Create events on:`.
3. Select your primary calendar.
2. Next to your `Calendars` you will see a dropdown that says `Create events on:`.
3. Select your primary calendar.

View File

@@ -1,3 +1,7 @@
---
title: Microsoft
---
# Outlook/Microsoft 365
The Outlook integration enables you to use your outlook.com or Microsoft 365 account to use for conflict checking and event bookings.
@@ -17,4 +21,4 @@ The top part of permissions window shows what you personally consented to. Examp
You can revoke any of the permissions you consented to by selecting `Revoke Permissions`, however removing a permission may break some of the apps functionality. If you have problems after you remove permissions or accounts, contact your organization's Helpdesk for additional assistance.
If you require more help, head over the Microsoft Documentation Page about [Managing Applications](https://docs.microsoft.com/en-us/azure/active-directory/user-help/my-applications-portal-permissions-saved-accounts)
If you require more help, head over the Microsoft Documentation Page about [Managing Applications](https://docs.microsoft.com/en-us/azure/active-directory/user-help/my-applications-portal-permissions-saved-accounts)

View File

@@ -1,3 +1,7 @@
---
title: Stripe
---
# Stripe Payments
The Stripe integration allows users to add payments to their bookings.

View File

@@ -1,3 +1,7 @@
## Do you have a Zapier integration?
---
title: Zapier
---
We are currently working on it, but it isnt live just yet. Until then, you can use our Webhooks integration and use Zapier's “Webhooks by Zapier”.
## Do you have a Zapier integration?
We are currently working on it, but it isnt live just yet. Until then, you can use our Webhooks integration and use Zapier's “Webhooks by Zapier”.

View File

@@ -1,4 +1,5 @@
---
title: Zoom
sidebar_position: 3
---

View File

@@ -11,5 +11,6 @@
"import": "Import",
"billing": "Billing",
"developer": "Developer",
"contributing": "Contributing",
"faq": "FAQs"
}

View File

@@ -1,3 +1,7 @@
---
title: Docker
---
# Docker
The Docker configuration for Cal is an effort powered by people within the community. Cal does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
@@ -15,7 +19,7 @@ Make sure you have `docker` & `docker-compose` installed on the server / system.
```bash
docker pull calendso/calendso
```
or
### Option #2: Cloning
@@ -30,12 +34,12 @@ or
3. Build and start calendso
```
```bash
docker-compose up --build
```
4. Start prisma studio
```
```bash
docker-compose exec calendso -- npx prisma studio
```
5. Open a browser to [port 5555](http://localhost:5555) on your localhost to look at or modify the database content.

View File

@@ -1,3 +1,7 @@
---
title: Installation
---
# Installation
To get a local copy up and running, please follow these simple steps.
@@ -52,62 +56,63 @@ yarn dx
1. Configure database in the `packages/prisma/.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
```text
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
```
```text
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
```
<details>
<details>
<summary>
If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB
using Heroku
</summary>
<summary>
If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB
using Heroku
</summary>
1. Create a free account with [Heroku](https://www.heroku.com/).
1. Create a free account with [Heroku](https://www.heroku.com/).
2. Create a new app.
2. Create a new app.
<img
width="306"
alt="Create an App"
src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png"
/>
<img
width="306"
alt="Create an App"
src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png"
/>
3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database.
![image](https://user-images.githubusercontent.com/16905768/115323232-a53ba480-a17f-11eb-98db-58e2f8c52426.png)
3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database.
![image](https://user-images.githubusercontent.com/16905768/115323232-a53ba480-a17f-11eb-98db-58e2f8c52426.png)
4. Once you clicked on `Configure Add-ons`, click on `Find more add-ons` and search for `postgres`. One of the options will be `Heroku Postgres` - click on that option.
![image](https://user-images.githubusercontent.com/16905768/115323126-5beb5500-a17f-11eb-8030-7380310807a9.png)
4. Once you clicked on `Configure Add-ons`, click on `Find more add-ons` and search for `postgres`. One of the options will be `Heroku Postgres` - click on that option.
![image](https://user-images.githubusercontent.com/16905768/115323126-5beb5500-a17f-11eb-8030-7380310807a9.png)
5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`.
5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`.
<img
width="512"
alt="Submit Order Form"
src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png"
/>
<img
width="512"
alt="Submit Order Form"
src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png"
/>
6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`.
![image](https://user-images.githubusercontent.com/16905768/115323367-e92ea980-a17f-11eb-9ff4-dec95f2ec349.png)
6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`.
![image](https://user-images.githubusercontent.com/16905768/115323367-e92ea980-a17f-11eb-9ff4-dec95f2ec349.png)
7. In `Settings`, copy your URI to your Cal.com .env file and replace the `postgresql://<user>:<pass>@<db-host>:<db-port>` with it.
![image](https://user-images.githubusercontent.com/16905768/115323556-4591c900-a180-11eb-9808-2f55d2aa3995.png)
![image](https://user-images.githubusercontent.com/16905768/115323697-7a9e1b80-a180-11eb-9f08-a742b1037f90.png)
7. In `Settings`, copy your URI to your Cal.com .env file and replace the `postgresql://<user>:<pass>@<db-host>:<db-port>` with it.
![image](https://user-images.githubusercontent.com/16905768/115323556-4591c900-a180-11eb-9808-2f55d2aa3995.png)
![image](https://user-images.githubusercontent.com/16905768/115323697-7a9e1b80-a180-11eb-9f08-a742b1037f90.png)
8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com/).
</details>
8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com/).
</details>
1. Set a 32 character random string in your `apps/web/.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
```sh
npx prisma migrate deploy
yarn workspace @calcom/prisma db-deploy
```
1. Run (in development mode)
```sh
yarn dev --scope=@calcom/web
yarn dev
```
### Setting up your first user
@@ -115,7 +120,7 @@ yarn dx
1. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content:
```sh
npx prisma studio
yarn db-studio
```
1. Click on the `User` model to add a new user record.

View File

@@ -1,25 +1,29 @@
---
title: Upgrade
---
# Upgrading
**Warning**: When performing database migrations, you may lose data if the migration is not done properly.
1. Pull the current version:
```
```sh
git pull
```
2. Apply database migrations by running <b>one of</b> the following commands:
In a development environment, run:
```
npx prisma migrate dev
```sh
yarn workspace @calcom/prisma db-migrate
```
(this can clear your development database in some cases)
In a production environment, run:
```
npx prisma migrate deploy
```sh
yarn workspace @calcom/prisma db-deploy
```
3. Check the `.env.example` and compare it to your current `.env` file. In case there are any fields not present
@@ -27,17 +31,17 @@
For the current version, especially check if the variable `BASE_URL` is present and properly set in your environment, for example:
```
```text
BASE_URL='https://yourdomain.com'
```
4. Start the server. In a development environment, just do:
```
```sh
yarn dev
```
For a production build, run for example:
```
```sh
yarn build
yarn start
```
5. Enjoy the new version.
5. Enjoy the new version.

View File

@@ -1,3 +1,7 @@
---
title: Vercel
---
# Vercel
## Requirements
@@ -22,15 +26,15 @@ You need a PostgresDB database hosted somewhere. [Heroku](https://www.heroku.com
yarn install
```
4. Set up the database using the Prisma schema (found in `prisma/schema.prisma`)
4. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
```sh
npx prisma migrate deploy
yarn workspace @calcom/prisma db-deploy
```
5. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content:
```
npx prisma studio
yarn db-studio
```
6. Click on the `User` model to add a new user record.
7. Fill out the fields (remembering to encrypt your password with [BCrypt](https://bcrypt-generator.com/)) and click `Save 1 Record` to create your first user.
@@ -42,5 +46,5 @@ You need a PostgresDB database hosted somewhere. [Heroku](https://www.heroku.com
1. Import from your forked repository
1. Set the Environment Variables
1. Set the root directory to `apps/web`
1. Override the build command to `cd ../.. && npx turbo run build --scope=@calcom/web --include-dependencies --no-deps`
1. Override the build command to `cd ../.. && yarn build`
1. Hit Deploy

View File

@@ -1,3 +1,7 @@
---
title: Settings
---
# Settings
## Setting up or making changes to your Profile
@@ -21,7 +25,7 @@
4. Click the button 'Save' located to the bottom right of your new password.
5. You have now successfully changed your password!
## Change your email
## Change your email
Go to [Profile Settings](https://app.cal.com/settings/profile). There, you will see the email associated with your account which you can then update. Youd just need to log out and back in to see the change take effect.
@@ -49,4 +53,4 @@ You can delete your account from within the [Settings](https://app.cal.com/setti
## How to change the language
Go to your [Profile Settings](https://app.cal.com/settings/profile). Under `Language` you will see the dropdown menu and you can use it to select your desired language.
Go to your [Profile Settings](https://app.cal.com/settings/profile). Under `Language` you will see the dropdown menu and you can use it to select your desired language.

View File

@@ -1,3 +1,7 @@
---
title: Teams
---
# Teams
## How do I create a new team?
@@ -25,7 +29,7 @@ Creating a team will allow you to create new event types for the team, invite te
## How do I add and remove a description of my team?
1. Go to [Your Teams Settings](https://app.cal.com/settings/teams) and select the team you wish to edit.
2. Located below your team name entry box is a large text box labeled 'About',
2. Located below your team name entry box is a large text box labeled 'About',
## How do I upload my team logo?
@@ -50,4 +54,4 @@ Your team has now successfully been deleted.
## Where can I find my team's Event Types?
Once you open `Event Types` on your dashboard, you will find your team's Event Types below your individual ones.
Once you open `Event Types` on your dashboard, you will find your team's Event Types below your individual ones.

View File

@@ -1,4 +1,9 @@
---
title: Webhooks
---
# Webhooks
## Create a new Webhook
1. Go to [Your Integrations](https://app.cal.com/integrations).
@@ -19,22 +24,75 @@
3. Press the button and from here your webhook will no longer work and be deleted.
## Webhook metadata
Metadata is a way to pass extra information to Cal.com about a booking that is returned through a webhook.
### Example
The best way to explain this is with an example. Let's say you're a bank, and people register an account on your website, but you want them to book an onboarding call with your team to get set up. You could send them to your Cal.com booking link as part of your onboarding process, but when the webhook is returned, it may be difficult to match up which user booked a meeting with the user's account in your own database. Hence, you can pass a `user_id` value for instance as a URL parameter, which makes no difference to the booking process, but when the webhook is returned, you will receive the metadata as part of the webhook payload.
Metadata is passed as a URL parameter on top of your booking link and follows the following syntax:
```text
metadata[key_name]=value
```
For example, if your booking link is `cal.com/rick/quick-chat`, you can pass a user ID of 123 like so:
```text
cal.com/rick/quick-chat?metadata[user_id]=123
```
As a result, the webhook will be returned in this format:
```text
{ <other event details>, metadata: { user_id: 123 } }
```
\{ <other event details>, metadata: \{ user_id: 123 \} \}
```
## Custom Webhooks template variable list
Customizable webhooks are a great way reduce the development effort and in many cases remove the need for a developer to build an additional integration service. Using a custom template you can easily decide what data you receive in your webhook endpoint, manage the payload and setup related workflows accordingly. Heres a breakdown of the payload that you would receive via an incoming webhook.
### Webhook structure
| Variable | Type | Description |
| ------------------- | -------- | -------------------------------------------------------------------------------------- |
| triggerEvent | String | The name of the trigger event [BOOKING_CREATED, BOOKING_RESHEDULED, BOOKING_CANCELLED] |
| createdAt | String | The time of the webhook trigger |
| type | String | The event-type slug |
| title | String | The event-type name |
| startTime | String | The event's start time |
| endTime | String | The event's end time |
| description? | String | The event's description as described in the event type |
| location? | String | Location of the event |
| organizer | Person | The organizer of the event |
| attendees | Person[] | The event booker & any guests |
| uid? | String | The UID of the booking |
| resheduleUid? | String | The UID for the rescheduling |
| cancellationReason? | String | Reason for cancellation |
| rejectionReason? | String | Reason for rejection |
| team?.name | String | Name of the team booked |
| team?.members | String[] | Members of the team booked |
### Person structure
| Variable | Type | Description |
| --------------- | ------ | --------------------------------------------------------------------- |
| name | String | Name of the individual |
| email | String | Email of the individual |
| timeZone | String | Timezone of the individual ("America/New_York", "Asia/Kolkata", etc.) |
| language.locale | String | Locale of the individual ("en", "fr", etc.) |
### Example usage of variables for custom template:
```sh
\{
"content": "A new event has been scheduled",
"type": "\{\{type\}\}",
"name": "\{\{title\}\}",
"organizer": "\{\{organizer.name\}\}",
"booker": "\{\{attendees.0.name\}\}"
\}
```

View File

@@ -1,12 +1,8 @@
export default {
github: 'https://github.com/calcom/docs',
docsRepositoryBase: 'https://github.com/calcom/docs/blob/master',
titleSuffix: ' | Cal.com',
logo: (
<h4 className="m-0">
Cal.com
</h4>
),
const themeConfig = {
github: "https://github.com/calcom/cal.com",
docsRepositoryBase: "https://github.com/calcom/cal.com/blob/main/apps/docs/pages",
titleSuffix: " | Cal.com",
logo: <h4 className="m-0">Cal.com</h4>,
head: (
<>
<meta name="msapplication-TileColor" content="#ffffff" />
@@ -29,23 +25,9 @@ export default {
<meta name="og:image" content="https://cal.com/og-image.png" />
<meta name="apple-mobile-web-app-title" content="Cal.com Docs" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#ff0000" />
@@ -56,8 +38,8 @@ export default {
prevLinks: true,
nextLinks: true,
footer: true,
footerEditLink: 'Edit this page on GitHub',
footerText: (
<>© {new Date().getFullYear()} Cal.com, Inc. All rights reserved.</>
),
}
footerEditLink: "Edit this page on GitHub",
footerText: <>© {new Date().getFullYear()} Cal.com, Inc. All rights reserved.</>,
};
export default themeConfig;

5
apps/docs/tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "@calcom/tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View File

@@ -99,3 +99,6 @@ CALENDSO_ENCRYPTION_KEY=
# Intercom Config
NEXT_PUBLIC_INTERCOM_APP_ID=
# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=

View File

@@ -1 +0,0 @@
module.exports = require("@calcom/config/eslint-preset");

View File

@@ -1 +0,0 @@
export * from "@calcom/prisma/client";

View File

@@ -18,7 +18,7 @@ export default function AddToHomescreen() {
<div className="rounded-lg p-2 shadow-lg sm:p-3" style={{ background: "#2F333D" }}>
<div className="flex flex-wrap items-center justify-between">
<div className="flex w-0 flex-1 items-center">
<span className="bg-brand text-brandcontrast flex rounded-lg bg-opacity-30 p-2">
<span className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast flex rounded-lg bg-opacity-30 p-2">
<svg
className="h-7 w-7 fill-current text-indigo-500"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -2,6 +2,7 @@ import { useEffect } from "react";
const brandColor = "#292929";
const brandTextColor = "#ffffff";
const darkBrandColor = "#fafafa";
export function colorNameToHex(color: string) {
const colors = {
@@ -174,8 +175,24 @@ function hexToRGB(hex: string) {
return [parseInt(color.slice(0, 2), 16), parseInt(color.slice(2, 4), 16), parseInt(color.slice(4, 6), 16)];
}
function getContrastingTextColor(bgColor: string | null): string {
bgColor = bgColor == "" || bgColor == null ? brandColor : bgColor;
function normalizeHexCode(hex: string | null, dark: boolean) {
if (!hex) {
return !dark ? brandColor : darkBrandColor;
}
hex = hex.replace("#", "");
if (hex.length === 3) {
hex = hex
.split("")
.map(function (hex) {
return hex + hex;
})
.join("");
}
return hex;
}
function getContrastingTextColor(bgColor: string | null, dark: boolean): string {
bgColor = bgColor == "" || bgColor == null ? (dark ? darkBrandColor : brandColor) : bgColor;
const rgb = hexToRGB(bgColor);
const whiteContrastRatio = computeContrastRatio(rgb, [255, 255, 255]);
const blackContrastRatio = computeContrastRatio(rgb, [41, 41, 41]); //#292929
@@ -191,18 +208,41 @@ export function isValidHexCode(val: string | null) {
return false;
}
export function fallBackHex(val: string | null): string {
export function fallBackHex(val: string | null, dark: boolean): string {
if (val) if (colorNameToHex(val)) return colorNameToHex(val) as string;
return brandColor;
return dark ? darkBrandColor : brandColor;
}
const BrandColor = ({ val = brandColor }: { val: string | undefined | null }) => {
const BrandColor = ({
lightVal = brandColor,
darkVal = darkBrandColor,
}: {
lightVal: string | undefined | null;
darkVal: string | undefined | null;
}) => {
// convert to 6 digit equivalent if 3 digit code is entered
lightVal = normalizeHexCode(lightVal, false);
darkVal = normalizeHexCode(darkVal, true);
// ensure acceptable hex-code
val = isValidHexCode(val) ? (val?.indexOf("#") === 0 ? val : "#" + val) : fallBackHex(val);
lightVal = isValidHexCode(lightVal)
? lightVal?.indexOf("#") === 0
? lightVal
: "#" + lightVal
: fallBackHex(lightVal, false);
darkVal = isValidHexCode(darkVal)
? darkVal?.indexOf("#") === 0
? darkVal
: "#" + darkVal
: fallBackHex(darkVal, true);
useEffect(() => {
document.documentElement.style.setProperty("--brand-color", val);
document.documentElement.style.setProperty("--brand-text-color", getContrastingTextColor(val));
}, [val]);
document.documentElement.style.setProperty("--brand-color", lightVal);
document.documentElement.style.setProperty("--brand-text-color", getContrastingTextColor(lightVal, true));
document.documentElement.style.setProperty("--brand-color-dark-mode", darkVal);
document.documentElement.style.setProperty(
"--brand-text-color-dark-mode",
getContrastingTextColor(darkVal, true)
);
}, [lightVal, darkVal]);
return null;
};

View File

@@ -1,11 +1,55 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { ReactNode } from "react";
import { useRouter } from "next/router";
import React, { ReactNode, useState } from "react";
export type DialogProps = React.ComponentProps<typeof DialogPrimitive["Root"]>;
export type DialogProps = React.ComponentProps<typeof DialogPrimitive["Root"]> & {
name?: string;
clearQueryParamsOnClose?: string[];
};
export function Dialog(props: DialogProps) {
const { children, ...other } = props;
const router = useRouter();
const { children, name, ...dialogProps } = props;
// only used if name is set
const [open, setOpen] = useState(!!dialogProps.open);
if (name) {
const clearQueryParamsOnClose = ["dialog", ...(props.clearQueryParamsOnClose || [])];
dialogProps.onOpenChange = (open) => {
if (props.onOpenChange) {
props.onOpenChange(open);
}
// toggles "dialog" query param
if (open) {
router.query["dialog"] = name;
} else {
clearQueryParamsOnClose.forEach((queryParam) => {
delete router.query[queryParam];
});
}
router.push(
{
pathname: router.pathname,
query: {
...router.query,
},
},
undefined,
{ shallow: true }
);
setOpen(open);
};
// handles initial state
if (!open && router.query["dialog"] === name) {
setOpen(true);
}
// allow overriding
if (!("open" in dialogProps)) {
dialogProps.open = open;
}
}
return (
<DialogPrimitive.Root {...other}>
<DialogPrimitive.Root {...dialogProps}>
<DialogPrimitive.Overlay className="fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
{children}
</DialogPrimitive.Root>

View File

@@ -119,7 +119,7 @@ export default function ImageUploader({
<DialogContent>
<div className="mb-4 sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<h3 className="font-cal text-lg font-bold leading-6 text-gray-900" id="modal-title">
<h3 className="font-cal text-lg leading-6 text-gray-900" id="modal-title">
{t("upload_target", { target })}
</h3>
</div>

View File

@@ -1,7 +1,7 @@
export default function Loader() {
return (
<div className="loader border-brand dark:border-white">
<span className="loader-inner bg-brand dark:bg-white"></span>
<div className="loader border-brand dark:border-darkmodebrand">
<span className="loader-inner bg-brand dark:bg-darkmodebrand"></span>
</div>
);
}

View File

@@ -19,9 +19,11 @@ import { Toaster } from "react-hot-toast";
import LicenseBanner from "@ee/components/LicenseBanner";
import TrialBanner from "@ee/components/TrialBanner";
import HelpMenuItemDynamic from "@ee/lib/intercom/HelpMenuItemDynamic";
import IntercomMenuItem from "@ee/lib/intercom/IntercomMenuItem";
import ZendeskMenuItem from "@ee/lib/zendesk/ZendeskMenuItem";
import classNames from "@lib/classNames";
import { NEXT_PUBLIC_BASE_URL } from "@lib/config/constants";
import { shouldShowOnboarding } from "@lib/getting-started";
import { useLocale } from "@lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
@@ -37,6 +39,7 @@ import Dropdown, {
DropdownMenuTrigger,
} from "@components/ui/Dropdown";
import pkg from "../package.json";
import { useViewerI18n } from "./I18nLanguageHandler";
import Logo from "./Logo";
import Button from "./ui/Button";
@@ -61,7 +64,7 @@ function useRedirectToLoginIfUnauthenticated() {
router.replace({
pathname: "/auth/login",
query: {
callbackUrl: `${location.pathname}${location.search}`,
callbackUrl: `${NEXT_PUBLIC_BASE_URL}/${location.pathname}${location.search}`,
},
});
}
@@ -79,11 +82,11 @@ function useRedirectToOnboardingIfNeeded() {
const user = query.data;
const [isRedirectingToOnboarding, setRedirecting] = useState(false);
useEffect(() => {
if (user && shouldShowOnboarding(user)) {
setRedirecting(true);
}
user && setRedirecting(shouldShowOnboarding(user));
}, [router, user]);
useEffect(() => {
if (isRedirectingToOnboarding) {
router.replace({
@@ -191,7 +194,7 @@ export default function Shell(props: {
}
return (
<>
<CustomBranding val={user?.brandColor} />
<CustomBranding lightVal={user?.brandColor} darkVal={user?.darkBrandColor} />
<HeadSeo
title={pageTitle ?? "Cal.com"}
description={props.subtitle ? props.subtitle?.toString() : ""}
@@ -246,7 +249,7 @@ export default function Shell(props: {
</nav>
</div>
<TrialBanner />
<div className="m-2 rounded-sm p-2 pt-2 pr-2 hover:bg-gray-100">
<div className="rounded-sm pt-2 pb-2 pl-3 pr-2 hover:bg-gray-100 lg:mx-2 lg:pl-2">
<span className="hidden lg:inline">
<UserDropdown />
</span>
@@ -254,6 +257,11 @@ export default function Shell(props: {
<UserDropdown small />
</span>
</div>
<small style={{ fontSize: "0.5rem" }} className="mx-3 mt-1 mb-2 hidden opacity-50 lg:block">
&copy; {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"}
{process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? "h" : "sh"}
<span className="lowercase">-{user && user.plan}</span>
</small>
</div>
</div>
</div>
@@ -302,9 +310,7 @@ export default function Shell(props: {
<div className="block min-h-[80px] justify-between px-4 sm:flex sm:px-6 md:px-8">
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
<div className="mb-8 w-full">
<h1 className="font-cal mb-1 text-xl font-bold tracking-wide text-gray-900">
{props.heading}
</h1>
<h1 className="font-cal mb-1 text-xl text-gray-900">{props.heading}</h1>
<p className="text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{props.subtitle}</p>
</div>
<div className="mb-4 flex-shrink-0">{props.CTA}</div>
@@ -379,7 +385,7 @@ function UserDropdown({ small }: { small?: boolean }) {
<img
className="rounded-full"
src={
(process.env.NEXT_PUBLIC_APP_URL || process.env.BASE_URL) +
(process.env.NEXT_PUBLIC_APP_URL || process.env.NEXT_PUBLIC_BASE_URL) +
"/" +
user?.username +
"/avatar.png"
@@ -411,7 +417,7 @@ function UserDropdown({ small }: { small?: boolean }) {
)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuContent portalled={true}>
<DropdownMenuItem>
<a
onClick={() => {
@@ -454,7 +460,7 @@ function UserDropdown({ small }: { small?: boolean }) {
viewBox="0 0 2447.6 2452.5"
className={classNames(
"text-gray-500 group-hover:text-gray-700",
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-2 rtl:ml-2"
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-4 rtl:ml-4"
)}
xmlns="http://www.w3.org/2000/svg">
<g clipRule="evenodd" fillRule="evenodd">
@@ -484,7 +490,8 @@ function UserDropdown({ small }: { small?: boolean }) {
<MapIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("visit_roadmap")}
</a>
</DropdownMenuItem>
<HelpMenuItemDynamic />
<IntercomMenuItem />
<ZendeskMenuItem />
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuItem>
<a

View File

@@ -14,6 +14,8 @@ import Loader from "@components/Loader";
type AvailableTimesProps = {
timeFormat: string;
minimumBookingNotice: number;
beforeBufferTime: number;
afterBufferTime: number;
eventTypeId: number;
eventLength: number;
slotInterval: number | null;
@@ -33,6 +35,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
timeFormat,
users,
schedulingType,
beforeBufferTime,
afterBufferTime,
}) => {
const { t, i18n } = useLocale();
const router = useRouter();
@@ -45,6 +49,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
schedulingType,
users,
minimumBookingNotice,
beforeBufferTime,
afterBufferTime,
eventTypeId,
});
@@ -95,7 +101,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
<Link href={bookingUrl}>
<a
className={classNames(
"text-primary-500 hover:bg-brand hover:text-brandcontrast dark:hover:bg-brand dark:hover:text-brandcontrast mb-2 block rounded-sm border bg-white py-4 font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
"text-primary-500 hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast mb-2 block rounded-sm border bg-white py-4 font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
brand === "#fff" || brand === "#ffffff" ? "border-brandcontrast" : "border-brand"
)}
data-testid="time">

View File

@@ -9,6 +9,7 @@ import { useLocale } from "@lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@components/Dialog";
import { useMeQuery } from "@components/Shell";
import { TextArea } from "@components/form/fields";
import Button from "@components/ui/Button";
import TableActions, { ActionType } from "@components/ui/TableActions";
@@ -16,6 +17,9 @@ import TableActions, { ActionType } from "@components/ui/TableActions";
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
function BookingListItem(booking: BookingItem) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
const { t, i18n } = useLocale();
const utils = trpc.useContext();
const [rejectionReason, setRejectionReason] = useState<string>("");
@@ -120,7 +124,8 @@ function BookingListItem(booking: BookingItem) {
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
{dayjs(booking.startTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")} -{" "}
{dayjs(booking.endTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
</div>
</td>
<td className={"flex-1 py-4 ltr:pl-4 rtl:pr-4" + (booking.rejected ? " line-through" : "")}>
@@ -165,7 +170,9 @@ function BookingListItem(booking: BookingItem) {
<td className="whitespace-nowrap py-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4">
{isUpcoming && !isCancelled ? (
<>
{!booking.confirmed && !booking.rejected && <TableActions actions={pendingActions} />}
{!booking.confirmed && !booking.rejected && user!.id === booking.user!.id && (
<TableActions actions={pendingActions} />
)}
{booking.confirmed && !booking.rejected && <TableActions actions={bookedActions} />}
{!booking.confirmed && booking.rejected && (
<div className="text-sm text-gray-500">{t("rejected")}</div>

View File

@@ -4,11 +4,13 @@ import dayjs, { Dayjs } from "dayjs";
import dayjsBusinessTime from "dayjs-business-time";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { useEffect, useMemo, useState } from "react";
import { memoize } from "lodash";
import { useEffect, useMemo, useRef, useState } from "react";
import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
import { weekdayNames } from "@lib/core/i18n/weekday";
import { doWorkAsync } from "@lib/doWorkAsync";
import { useLocale } from "@lib/hooks/useLocale";
import getSlots from "@lib/slots";
import { WorkingHours } from "@lib/types/schedule";
@@ -51,10 +53,8 @@ function isOutOfBounds(
switch (periodType) {
case PeriodType.ROLLING: {
const periodRollingEndDay = periodCountCalendarDays
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dayjs().utcOffset(date.utcOffset()).add(periodDays!, "days").endOf("day")
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dayjs().utcOffset(date.utcOffset()).addBusinessTime(periodDays!, "days").endOf("day");
? dayjs().utcOffset(date.utcOffset()).add(periodDays!, "days").endOf("day")
: dayjs().utcOffset(date.utcOffset()).addBusinessTime(periodDays!, "days").endOf("day");
return date.endOf("day").isAfter(periodRollingEndDay);
}
@@ -89,7 +89,13 @@ function DatePicker({
const [month, setMonth] = useState<string>("");
const [year, setYear] = useState<string>("");
const [isFirstMonth, setIsFirstMonth] = useState<boolean>(false);
const [daysFromState, setDays] = useState<
| {
disabled: Boolean;
date: number;
}[]
| null
>(null);
useEffect(() => {
if (!browsingDate || (date && browsingDate.utcOffset() !== date?.utcOffset())) {
setBrowsingDate(date || dayjs().tz(timeZone()));
@@ -101,13 +107,57 @@ function DatePicker({
setMonth(browsingDate.toDate().toLocaleString(i18n.language, { month: "long" }));
setYear(browsingDate.format("YYYY"));
setIsFirstMonth(browsingDate.startOf("month").isBefore(dayjs()));
setDays(null);
}
}, [browsingDate, i18n.language]);
const days = useMemo(() => {
const isDisabled = (
day: number,
{
browsingDate,
periodType,
periodStartDate,
periodEndDate,
periodCountCalendarDays,
periodDays,
eventLength,
minimumBookingNotice,
workingHours,
}
) => {
const date = browsingDate.startOf("day").date(day);
return (
isOutOfBounds(date, {
periodType,
periodStartDate,
periodEndDate,
periodCountCalendarDays,
periodDays,
}) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
minimumBookingNotice,
workingHours,
eventLength,
}).length
);
};
const isDisabledRef = useRef(
memoize(isDisabled, (day, { browsingDate }) => {
// Make a composite cache key
return day + "_" + browsingDate.toString();
})
);
const days = (() => {
if (!browsingDate) {
return [];
}
if (daysFromState) {
return daysFromState;
}
// Create placeholder elements for empty days in first week
let weekdayOfFirst = browsingDate.date(1).day();
if (weekStart === "Monday") {
@@ -117,33 +167,49 @@ function DatePicker({
const days = Array(weekdayOfFirst).fill(null);
const isDisabled = (day: number) => {
const date = browsingDate.startOf("day").date(day);
return (
isOutOfBounds(date, {
periodType,
periodStartDate,
periodEndDate,
periodCountCalendarDays,
periodDays,
}) ||
!getSlots({
inviteeDate: date,
frequency: eventLength,
minimumBookingNotice,
workingHours,
}).length
);
};
const isDisabledMemoized = isDisabledRef.current;
const daysInMonth = browsingDate.daysInMonth();
const daysInitialOffset = days.length;
// Build UI with All dates disabled
for (let i = 1; i <= daysInMonth; i++) {
days.push({ disabled: isDisabled(i), date: i });
days.push({
disabled: true,
date: i,
});
}
// Update dates with their availability
doWorkAsync({
batch: 1,
name: "DatePicker",
length: daysInMonth,
callback: (i: number, isLast) => {
let day = i + 1;
days[daysInitialOffset + i] = {
disabled: isDisabledMemoized(day, {
browsingDate,
periodType,
periodStartDate,
periodEndDate,
periodCountCalendarDays,
periodDays,
eventLength,
minimumBookingNotice,
workingHours,
}),
date: day,
};
},
batchDone: () => {
setDays([...days]);
},
});
return days;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [browsingDate]);
})();
if (!browsingDate) {
return <Loader />;
@@ -213,7 +279,7 @@ function DatePicker({
"hover:border-brand hover:border dark:hover:border-white",
day.disabled ? "cursor-default font-light text-gray-400 hover:border-0" : "font-medium",
date && date.isSame(browsingDate.date(day.date), "day")
? "bg-brand text-brandcontrast"
? "bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast"
: !day.disabled
? " bg-gray-100 dark:bg-gray-600 dark:text-white"
: ""

View File

@@ -1,9 +1,8 @@
// TODO: replace headlessui with radix-ui
import { Switch } from "@headlessui/react";
import { FC, useEffect, useState } from "react";
import TimezoneSelect, { ITimezoneOption } from "react-timezone-select";
import classNames from "@lib/classNames";
import Switch from "@calcom/ui/Switch";
import { useLocale } from "@lib/hooks/useLocale";
import { is24h, timeZone } from "../../lib/clock";
@@ -13,7 +12,7 @@ type Props = {
onToggle24hClock: (is24hClock: boolean) => void;
};
const TimeOptions: FC<Props> = (props) => {
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone }) => {
const [selectedTimeZone, setSelectedTimeZone] = useState("");
const [is24hClock, setIs24hClock] = useState(false);
const { t } = useLocale();
@@ -25,44 +24,28 @@ const TimeOptions: FC<Props> = (props) => {
useEffect(() => {
if (selectedTimeZone && timeZone() && selectedTimeZone !== timeZone()) {
props.onSelectTimeZone(timeZone(selectedTimeZone));
onSelectTimeZone(timeZone(selectedTimeZone));
}
}, [selectedTimeZone]);
}, [selectedTimeZone, onSelectTimeZone]);
const handle24hClockToggle = (is24hClock: boolean) => {
setIs24hClock(is24hClock);
props.onToggle24hClock(is24h(is24hClock));
onToggle24hClock(is24h(is24hClock));
};
return selectedTimeZone !== "" ? (
<div className="max-w-80 absolute z-10 w-full rounded-sm border border-gray-200 bg-white px-4 py-2 dark:border-0 dark:bg-gray-700">
<div className="mb-4 flex">
<div className="w-1/2 font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
<div className="w-1/2">
<Switch.Group as="div" className="flex items-center justify-end">
<Switch.Label as="span" className="ltr:mr-3">
<span className="text-sm text-gray-500 dark:text-white">{t("am_pm")}</span>
</Switch.Label>
<Switch
checked={is24hClock}
onChange={handle24hClockToggle}
className={classNames(
is24hClock ? "bg-brand text-brandcontrast" : "bg-gray-200 dark:bg-gray-600",
"relative inline-flex h-5 w-8 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
)}>
<span className="sr-only">{t("use_setting")}</span>
<span
aria-hidden="true"
className={classNames(
is24hClock ? "translate-x-3" : "translate-x-0",
"pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
)}
/>
</Switch>
<Switch.Label as="span" className="ltr:ml-3 rtl:mr-3">
<span className="text-sm text-gray-500 dark:text-white">{t("24_h")}</span>
</Switch.Label>
</Switch.Group>
<div className="font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
<div className="ml-auto flex items-center">
<label className="ltl:mr-3 mr-2 align-text-top text-sm font-medium text-neutral-700 ltr:ml-3 rtl:mr-3 dark:text-white">
{t("am_pm")}
</label>
<Switch
name="24hClock"
label={t("24_h")}
defaultChecked={is24hClock}
onCheckedChange={handle24hClockToggle}
/>
</div>
</div>
<TimezoneSelect

View File

@@ -1,5 +1,12 @@
// Get router variables
import { ChevronDownIcon, ChevronUpIcon, ClockIcon, CreditCardIcon, GlobeIcon } from "@heroicons/react/solid";
import {
ArrowLeftIcon,
ChevronDownIcon,
ChevronUpIcon,
ClockIcon,
CreditCardIcon,
GlobeIcon,
} from "@heroicons/react/solid";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useContracts } from "contexts/contractsContext";
import dayjs, { Dayjs } from "dayjs";
@@ -11,10 +18,12 @@ import { FormattedNumber, IntlProvider } from "react-intl";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { BASE_URL } from "@lib/config/constants";
import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { detectBrowserTimeFormat } from "@lib/timeFormat";
import CustomBranding from "@components/CustomBranding";
import AvailableTimes from "@components/booking/AvailableTimes";
@@ -32,7 +41,7 @@ dayjs.extend(customParseFormat);
type Props = AvailabilityTeamPageProps | AvailabilityPageProps;
const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
const AvailabilityPage = ({ profile, eventType, workingHours, previousPage }: Props) => {
const router = useRouter();
const { rescheduleUid } = router.query;
const { isReady, Theme } = useTheme(profile.theme);
@@ -62,11 +71,13 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
}, [router.query.date]);
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
const [timeFormat, setTimeFormat] = useState("h:mma");
const [timeFormat, setTimeFormat] = useState(detectBrowserTimeFormat);
const telemetry = useTelemetry();
useEffect(() => {
handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
}, [telemetry]);
@@ -107,7 +118,7 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
username={profile.slug || undefined}
// avatar={profile.image || undefined}
/>
<CustomBranding val={profile.brandColor} />
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
<div>
<main
className={
@@ -120,6 +131,7 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
<div className="block p-4 sm:p-8 md:hidden">
<div className="flex items-center">
<AvatarGroup
border="border-2 dark:border-gray-900 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
@@ -164,10 +176,11 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
<div className="px-4 sm:flex sm:p-4 sm:py-5">
<div
className={
"hidden pr-8 sm:border-r sm:dark:border-gray-800 md:block " +
"hidden pr-8 sm:border-r sm:dark:border-gray-800 md:flex md:flex-col " +
(selectedDate ? "sm:w-1/3" : "sm:w-1/2")
}>
<AvatarGroup
border="border-2 dark:border-gray-900 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
@@ -207,6 +220,15 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
<TimezoneDropdown />
<p className="mt-3 mb-8 text-gray-600 dark:text-gray-200">{eventType.description}</p>
{previousPage === `${BASE_URL}/${profile.slug}` && (
<div className="flex h-full flex-col justify-end">
<ArrowLeftIcon
className="h-4 w-4 text-black transition-opacity hover:cursor-pointer dark:text-white"
onClick={() => router.back()}
/>
<p className="sr-only">Go Back</p>
</div>
)}
</div>
<DatePicker
date={selectedDate}
@@ -236,6 +258,8 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => {
date={selectedDate}
users={eventType.users}
schedulingType={eventType.schedulingType ?? null}
beforeBufferTime={eventType.beforeEventBuffer}
afterBufferTime={eventType.afterEventBuffer}
/>
)}
</div>

View File

@@ -2,6 +2,7 @@ import { CalendarIcon, ClockIcon, CreditCardIcon, ExclamationIcon } from "@heroi
import { EventTypeCustomInputType } from "@prisma/client";
import { useContracts } from "contexts/contractsContext";
import dayjs from "dayjs";
import { useSession } from "next-auth/react";
import dynamic from "next/dynamic";
import Head from "next/head";
import { useRouter } from "next/router";
@@ -12,7 +13,7 @@ import { ReactMultiEmail } from "react-multi-email";
import { useMutation } from "react-query";
import { v4 as uuidv4 } from "uuid";
import { createPaymentLink } from "@ee/lib/stripe/client";
import { createPaymentLink } from "@calcom/stripe/client";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
@@ -24,6 +25,7 @@ import createBooking from "@lib/mutations/bookings/create-booking";
import { parseZone } from "@lib/parseZone";
import slugify from "@lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { detectBrowserTimeFormat } from "@lib/timeFormat";
import CustomBranding from "@components/CustomBranding";
import { EmailInput, Form } from "@components/form/fields";
@@ -50,25 +52,24 @@ type BookingFormValues = {
};
};
const BookingPage = (props: BookingPageProps) => {
const BookingPage = ({ eventType, booking, profile }: BookingPageProps) => {
const { t, i18n } = useLocale();
const router = useRouter();
const { contracts } = useContracts();
const { eventType } = props;
const { data: session } = useSession();
useEffect(() => {
if (eventType.metadata.smartContractAddress) {
const eventOwner = eventType.users[0];
if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */
router.replace(`/${eventOwner.username}`);
}
}, [contracts, eventType.metadata.smartContractAddress, router]);
const mutation = useMutation(createBooking, {
onSuccess: async ({ attendees, paymentUid, ...responseData }) => {
onSuccess: async (responseData) => {
const { attendees, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
@@ -84,9 +85,6 @@ const BookingPage = (props: BookingPageProps) => {
if (!location) {
return;
}
if (location === "integrations:jitsi") {
return "https://meet.jit.si/cal/" + uuidv4();
}
if (location.includes("integration")) {
return t("web_conferencing_details_to_follow");
}
@@ -97,8 +95,8 @@ const BookingPage = (props: BookingPageProps) => {
pathname: "/success",
query: {
date,
type: props.eventType.id,
user: props.profile.slug,
type: eventType.id,
user: profile.slug,
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
@@ -109,20 +107,18 @@ const BookingPage = (props: BookingPageProps) => {
});
const rescheduleUid = router.query.rescheduleUid as string;
const { isReady, Theme } = useTheme(props.profile.theme);
const { isReady, Theme } = useTheme(profile.theme);
const date = asStringOrNull(router.query.date);
const timeFormat = asStringOrNull(router.query.clock) === "24h" ? "H:mm" : "h:mma";
const [guestToggle, setGuestToggle] = useState(props.booking && props.booking.attendees.length > 1);
const [guestToggle, setGuestToggle] = useState(booking && booking.attendees.length > 1);
const eventTypeDetail = { isWeb3Active: false, ...props.eventType };
const eventTypeDetail = { isWeb3Active: false, ...eventType };
type Location = { type: LocationType; address?: string };
// it would be nice if Prisma at some point in the future allowed for Json<Location>; as of now this is not the case.
const locations: Location[] = useMemo(
() => (props.eventType.locations as Location[]) || [],
[props.eventType.locations]
() => (eventType.locations as Location[]) || [],
[eventType.locations]
);
useEffect(() => {
@@ -146,15 +142,15 @@ const BookingPage = (props: BookingPageProps) => {
[LocationType.Huddle01]: "Huddle01 Video",
[LocationType.Tandem]: "Tandem Video",
};
const loggedInIsOwner = eventType.users[0].name === session?.user.name;
const defaultValues = () => {
if (!rescheduleUid) {
return {
name: (router.query.name as string) || "",
email: (router.query.email as string) || "",
name: loggedInIsOwner ? "" : session?.user?.name || (router.query.name as string) || "",
email: loggedInIsOwner ? "" : session?.user?.email || (router.query.email as string) || "",
notes: (router.query.notes as string) || "",
guests: ensureArray(router.query.guest) as string[],
customInputs: props.eventType.customInputs.reduce(
customInputs: eventType.customInputs.reduce(
(customInputs, input) => ({
...customInputs,
[input.id]: router.query[slugify(input.label)],
@@ -163,17 +159,17 @@ const BookingPage = (props: BookingPageProps) => {
),
};
}
if (!props.booking || !props.booking.attendees.length) {
if (!booking || !booking.attendees.length) {
return {};
}
const primaryAttendee = props.booking.attendees[0];
const primaryAttendee = booking.attendees[0];
if (!primaryAttendee) {
return {};
}
return {
name: primaryAttendee.name || "",
email: primaryAttendee.email || "",
guests: props.booking.attendees.slice(1).map((attendee) => attendee.email),
guests: booking.attendees.slice(1).map((attendee) => attendee.email),
};
};
@@ -213,7 +209,7 @@ const BookingPage = (props: BookingPageProps) => {
if (!date) return "No date";
const parsedZone = parseZone(date);
if (!parsedZone?.isValid()) return "Invalid date";
const formattedTime = parsedZone?.format(timeFormat);
const formattedTime = parsedZone?.format(detectBrowserTimeFormat);
return formattedTime + ", " + dayjs(date).toDate().toLocaleString(i18n.language, { dateStyle: "full" });
};
@@ -239,7 +235,6 @@ const BookingPage = (props: BookingPageProps) => {
let web3Details;
if (eventTypeDetail.metadata.smartContractAddress) {
web3Details = {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
userWallet: window.web3.currentProvider.selectedAddress,
userSignature: contracts[(eventTypeDetail.metadata.smartContractAddress || null) as number],
@@ -250,18 +245,18 @@ const BookingPage = (props: BookingPageProps) => {
...booking,
web3Details,
start: dayjs(date).format(),
end: dayjs(date).add(props.eventType.length, "minute").format(),
eventTypeId: props.eventType.id,
end: dayjs(date).add(eventType.length, "minute").format(),
eventTypeId: eventType.id,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
user: router.query.user,
location: getLocationValue(booking.locationType ? booking : { locationType: selectedLocation }),
location: getLocationValue(
booking.locationType ? booking : { ...booking, locationType: selectedLocation }
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
label: props.eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
value: booking.customInputs![inputId],
})),
});
@@ -274,52 +269,51 @@ const BookingPage = (props: BookingPageProps) => {
<title>
{rescheduleUid
? t("booking_reschedule_confirmation", {
eventTypeTitle: props.eventType.title,
profileName: props.profile.name,
eventTypeTitle: eventType.title,
profileName: profile.name,
})
: t("booking_confirmation", {
eventTypeTitle: props.eventType.title,
profileName: props.profile.name,
eventTypeTitle: eventType.title,
profileName: profile.name,
})}{" "}
| Cal.com
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<CustomBranding val={props.profile.brandColor} />
<main className=" mx-auto my-0 max-w-3xl rounded-sm sm:my-24 sm:border sm:dark:border-gray-600">
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
<main className="mx-auto my-0 max-w-3xl rounded-sm sm:my-24 sm:border sm:dark:border-gray-600">
{isReady && (
<div className="overflow-hidden border border-gray-200 bg-white dark:border-0 dark:bg-neutral-900 sm:rounded-sm">
<div className="px-4 py-5 sm:flex sm:p-4">
<div className="sm:w-1/2 sm:border-r sm:dark:border-gray-800">
<AvatarGroup
border="border-2 border-white dark:border-gray-900"
size={14}
items={[{ image: props.profile.image || "", alt: props.profile.name || "" }].concat(
props.eventType.users
.filter((user) => user.name !== props.profile.name)
items={[{ image: profile.image || "", alt: profile.name || "" }].concat(
eventType.users
.filter((user) => user.name !== profile.name)
.map((user) => ({
image: user.avatar || "",
alt: user.name || "",
}))
)}
/>
<h2 className="font-cal mt-2 font-medium text-gray-500 dark:text-gray-300">
{props.profile.name}
</h2>
<h2 className="font-cal mt-2 font-medium text-gray-500 dark:text-gray-300">{profile.name}</h2>
<h1 className="mb-4 text-3xl font-semibold text-gray-800 dark:text-white">
{props.eventType.title}
{eventType.title}
</h1>
<p className="mb-2 text-gray-500">
<ClockIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
{props.eventType.length} {t("minutes")}
{eventType.length} {t("minutes")}
</p>
{props.eventType.price > 0 && (
{eventType.price > 0 && (
<p className="mb-1 -ml-2 px-2 py-1 text-gray-500">
<CreditCardIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={props.eventType.price / 100.0}
value={eventType.price / 100.0}
style="currency"
currency={props.eventType.currency.toUpperCase()}
currency={eventType.currency.toUpperCase()}
/>
</IntlProvider>
</p>
@@ -333,7 +327,7 @@ const BookingPage = (props: BookingPageProps) => {
{t("requires_ownership_of_a_token") + " " + eventType.metadata.smartContractAddress}
</p>
)}
<p className="mb-8 text-gray-600 dark:text-white">{props.eventType.description}</p>
<p className="mb-8 text-gray-600 dark:text-white">{eventType.description}</p>
</div>
<div className="sm:w-1/2 sm:pl-8 sm:pr-4">
<Form form={bookingForm} handleSubmit={bookEvent}>
@@ -348,7 +342,7 @@ const BookingPage = (props: BookingPageProps) => {
name="name"
id="name"
required
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder={t("example_name")}
/>
</div>
@@ -363,7 +357,7 @@ const BookingPage = (props: BookingPageProps) => {
<EmailInput
{...bookingForm.register("email")}
required
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder="you@example.com"
/>
</div>
@@ -397,13 +391,18 @@ const BookingPage = (props: BookingPageProps) => {
{t("phone_number")}
</label>
<div className="mt-1">
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<PhoneInput name="phone" placeholder={t("enter_phone_number")} id="phone" required />
<PhoneInput
// @ts-expect-error
control={bookingForm.control}
name="phone"
placeholder={t("enter_phone_number")}
id="phone"
required
/>
</div>
</div>
)}
{props.eventType.customInputs
{eventType.customInputs
.sort((a, b) => a.id - b.id)
.map((input) => (
<div className="mb-4" key={input.id}>
@@ -421,7 +420,7 @@ const BookingPage = (props: BookingPageProps) => {
})}
id={"custom_" + input.id}
rows={3}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder={input.placeholder}
/>
)}
@@ -432,7 +431,7 @@ const BookingPage = (props: BookingPageProps) => {
required: input.required,
})}
id={"custom_" + input.id}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder={input.placeholder}
/>
)}
@@ -443,7 +442,7 @@ const BookingPage = (props: BookingPageProps) => {
required: input.required,
})}
id={"custom_" + input.id}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder=""
/>
)}
@@ -467,7 +466,7 @@ const BookingPage = (props: BookingPageProps) => {
)}
</div>
))}
{!props.eventType.disableGuests && (
{!eventType.disableGuests && (
<div className="mb-4">
{!guestToggle && (
<label
@@ -525,7 +524,7 @@ const BookingPage = (props: BookingPageProps) => {
{...bookingForm.register("notes")}
id="notes"
rows={3}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white sm:text-sm"
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-black dark:text-white dark:selection:bg-green-500 sm:text-sm"
placeholder={t("share_additional_notes")}
/>
</div>
@@ -539,7 +538,9 @@ const BookingPage = (props: BookingPageProps) => {
</div>
</Form>
{mutation.isError && (
<div className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4">
<div
data-testid="booking-fail"
className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<ExclamationIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />

View File

@@ -52,15 +52,13 @@ export default function ConfirmationDialogContent(props: PropsWithChildren<Confi
</div>
)}
<div>
<DialogPrimitive.Title className="font-cal text-xl font-bold text-gray-900">
{title}
</DialogPrimitive.Title>
<DialogPrimitive.Title className="font-cal text-xl text-gray-900">{title}</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-neutral-500">
{children}
</DialogPrimitive.Description>
</div>
</div>
<div className="mt-5 gap-x-2 sm:mt-8 sm:flex sm:flex-row-reverse">
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
<DialogClose onClick={onConfirm} asChild>
{confirmBtn || <Button color="primary">{confirmBtnText}</Button>}
</DialogClose>

View File

@@ -1,17 +1,17 @@
import { ChevronDownIcon, PlusIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod/dist/zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { SchedulingType } from "@prisma/client";
import { useRouter } from "next/router";
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
import { createEventTypeInput } from "@calcom/prisma/zod/eventtypeCustom";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { HttpError } from "@lib/core/http/error";
import { useLocale } from "@lib/hooks/useLocale";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import showToast from "@lib/notification";
import { slugify } from "@lib/slugify";
import { trpc } from "@lib/trpc";
import { Dialog, DialogClose, DialogContent } from "@components/Dialog";
@@ -48,7 +48,6 @@ interface Props {
export default function CreateEventTypeButton(props: Props) {
const { t } = useLocale();
const router = useRouter();
const modalOpen = useToggleQuery("new");
// URL encoded params
const teamId: number | undefined =
@@ -67,7 +66,7 @@ export default function CreateEventTypeButton(props: Props) {
useEffect(() => {
const subscription = watch((value, { name, type }) => {
if (name === "title" && type === "change") {
if (value.title) setValue("slug", value.title.replace(/\s+/g, "-").toLowerCase());
if (value.title) setValue("slug", slugify(value.title));
else setValue("slug", "");
}
});
@@ -94,44 +93,33 @@ export default function CreateEventTypeButton(props: Props) {
// inject selection data into url for correct router history
const openModal = (option: EventTypeParent) => {
// setTimeout fixes a bug where the url query params are removed immediately after opening the modal
setTimeout(() => {
router.push(
{
pathname: router.pathname,
query: {
...router.query,
new: "1",
eventPage: option.slug,
teamId: option.teamId || undefined,
},
},
undefined,
{ shallow: true }
);
});
};
// remove url params after close modal to reset state
const closeModal = () => {
router.replace({
pathname: router.pathname,
query: { id: router.query.id || undefined },
});
const query = {
...router.query,
dialog: "new-eventtype",
eventPage: option.slug,
teamId: option.teamId,
};
if (!option.teamId) {
delete query.teamId;
}
router.push(
{
pathname: router.pathname,
query,
},
undefined,
{ shallow: true }
);
};
return (
<Dialog
open={modalOpen.isOn}
onOpenChange={(isOpen) => {
if (!isOpen) closeModal();
}}>
<Dialog name="new-eventtype" clearQueryParamsOnClose={["eventPage", "teamId"]}>
{!hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={PlusIcon}
{...(props.canAddEvents ? { href: modalOpen.hrefOn } : { disabled: true })}>
disabled={!props.canAddEvents}>
{t("new_event_type_btn")}
</Button>
) : (

View File

@@ -1,6 +1,8 @@
import React, { Fragment } from "react";
import { useMutation } from "react-query";
import Switch from "@calcom/ui/Switch";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import showToast from "@lib/notification";
@@ -11,7 +13,6 @@ import { List } from "@components/List";
import { ShellSubHeading } from "@components/Shell";
import { Alert } from "@components/ui/Alert";
import Button from "@components/ui/Button";
import Switch from "@components/ui/Switch";
import ConnectIntegration from "./ConnectIntegrations";
import DisconnectIntegration from "./DisconnectIntegration";

View File

@@ -2,6 +2,7 @@ import type { IntegrationOAuthCallbackState } from "pages/api/integrations/types
import { useState } from "react";
import { useMutation } from "react-query";
import { NEXT_PUBLIC_BASE_URL } from "@lib/config/constants";
import { AddAppleIntegrationModal } from "@lib/integrations/calendar/components/AddAppleIntegration";
import { AddCalDavIntegrationModal } from "@lib/integrations/calendar/components/AddCalDavIntegration";
@@ -17,7 +18,7 @@ export default function ConnectIntegration(props: {
const mutation = useMutation(async () => {
const state: IntegrationOAuthCallbackState = {
returnTo: location.pathname + location.search,
returnTo: NEXT_PUBLIC_BASE_URL + location.pathname + location.search,
};
const stateStr = encodeURIComponent(JSON.stringify(state));
const searchParams = `?state=${stateStr}`;

View File

@@ -73,7 +73,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
type="text"
id="label"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
@@ -89,7 +89,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
<input
type="text"
id="placeholder"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>
@@ -120,11 +120,11 @@ const CustomInputTypeForm: FC<Props> = (props) => {
value={selectedCustomInput?.id || -1}
{...register("id", { valueAsNumber: true })}
/>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit">{t("save")}</Button>
<div className="mt-5 flex space-x-2 sm:mt-4">
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}
</Button>
<Button type="submit">{t("save")}</Button>
</div>
</form>
);

View File

@@ -4,7 +4,7 @@ import React from "react";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return (
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-brand text-brandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<ShieldCheckIcon className="h-6 w-6 text-black" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">

View File

@@ -64,7 +64,7 @@ export default function MemberInvitationModal(props: { team: TeamWithMembers | n
<div className="inline-block transform rounded-lg bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-brand text-brandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<UserIcon className="text-brandcontrast h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">

View File

@@ -76,7 +76,7 @@ export default function TeamCreate(props: Props) {
/>
</div>
{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div className="mt-5 flex flex-row-reverse sm:mt-4">
<button type="submit" className="btn btn-primary">
{t("create_team")}
</button>

View File

@@ -99,7 +99,7 @@ export default function TeamListItem(props: Props) {
</>
)}
{!isInvitee && (
<div className="flex space-x-2 rtl:space-x-reverse">
<div className="flex rtl:space-x-reverse">
<TeamRole role={team.role} />
<Tooltip content={t("copy_link_team")}>

View File

@@ -89,7 +89,7 @@ export default function TeamSettings(props: Props) {
name="" // typescript requires name but we don't want component to render name label
id="team-url"
addOnLeading={
<span className="inline-flex items-center rounded-l-sm border border-r-0 border-gray-300 bg-gray-50 px-3 text-gray-500 sm:text-sm">
<span className="inline-flex items-center rounded-l-sm border border-r-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-500">
{process.env.NEXT_PUBLIC_APP_URL}/{"team/"}
</span>
}

View File

@@ -21,7 +21,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
<img className="mx-auto h-6" src="/calendso-logo-white-word.svg" alt="Cal.com Logo" />
)}
{props.heading && (
<h2 className="font-cal mt-6 text-center text-3xl font-bold text-neutral-900">{props.heading}</h2>
<h2 className="font-cal mt-6 text-center text-3xl text-neutral-900">{props.heading}</h2>
)}
</div>
{props.loading && (

View File

@@ -36,7 +36,7 @@ export default function Avatar(props: AvatarProps) {
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="bg-brand text-brandcontrast rounded-sm p-2 text-sm shadow-sm">
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white shadow-sm">
<Tooltip.Arrow />
{title}
</Tooltip.Content>

View File

@@ -4,9 +4,8 @@ import classNames from "@lib/classNames";
import Avatar from "@components/ui/Avatar";
// import * as Tooltip from "@radix-ui/react-tooltip";
export type AvatarGroupProps = {
border?: string; // this needs to be the color of the parent container background, i.e.: border-white dark:border-gray-900
size: number;
truncateAfter?: number;
items: {
@@ -18,44 +17,23 @@ export type AvatarGroupProps = {
};
export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
/* const truncatedAvatars: string[] =
props.items.length > props.truncateAfter
? props.items
.slice(props.truncateAfter)
.map((item) => item.title)
.filter(Boolean)
: [];*/
return (
<ul className={classNames("-rtl:space-x-reverse flex space-x-2 overflow-hidden", props.className)}>
<ul className={classNames(props.className)}>
{props.items.slice(0, props.truncateAfter).map((item, idx) => {
if (item.image != null) {
return (
<li key={idx} className="inline-block">
<Avatar imageSrc={item.image} title={item.title} alt={item.alt || ""} size={props.size} />
<li key={idx} className="-mr-2 inline-block">
<Avatar
className={props.border}
imageSrc={item.image}
title={item.title}
alt={item.alt || ""}
size={props.size}
/>
</li>
);
}
})}
{/*props.items.length > props.truncateAfter && (
<li className="relative inline-block">
<Tooltip.Tooltip delayDuration="300">
<Tooltip.TooltipTrigger className="cursor-default">
<span className="w-16 absolute bottom-1.5 border-2 border-gray-300 flex-inline items-center text-white pt-4 text-2xl top-0 rounded-full block bg-neutral-600">+1</span>
</Tooltip.TooltipTrigger>
{truncatedAvatars.length !== 0 && (
<Tooltip.Content className="p-2 text-sm text-white rounded-sm shadow-sm bg-brand">
<Tooltip.Arrow />
<ul>
{truncatedAvatars.map((title) => (
<li key={title}>{title}</li>
))}
</ul>
</Tooltip.Content>
)}
</Tooltip.Tooltip>
</li>
)*/}
</ul>
);
};

View File

@@ -1,7 +1,6 @@
import { User } from "@prisma/client";
import classNames from "@lib/classNames";
import { defaultAvatarSrc } from "@lib/profile";
export type AvatarProps = {
user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string };
@@ -11,6 +10,11 @@ export type AvatarProps = {
alt: string;
};
// defaultAvatarSrc from profile.tsx can't be used as it imports crypto
function defaultAvatarSrc({ md5 }) {
return `https://www.gravatar.com/avatar/${md5}?s=160&d=identicon&r=PG`;
}
// An SSR Supported version of Avatar component.
// FIXME: title support is missing
export function AvatarSSR(props: AvatarProps) {

View File

@@ -1,137 +1,3 @@
import Link, { LinkProps } from "next/link";
import React, { forwardRef } from "react";
import classNames from "@lib/classNames";
import { SVGComponent } from "@lib/types/SVGComponent";
export type ButtonBaseProps = {
color?: "primary" | "secondary" | "minimal" | "warn";
size?: "base" | "sm" | "lg" | "fab" | "icon";
loading?: boolean;
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
StartIcon?: SVGComponent;
EndIcon?: SVGComponent;
shallow?: boolean;
};
export type ButtonProps = ButtonBaseProps &
(
| (Omit<JSX.IntrinsicElements["a"], "href"> & { href: LinkProps["href"] })
| (JSX.IntrinsicElements["button"] & { href?: never })
);
export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonProps>(function Button(
props: ButtonProps,
forwardedRef
) {
const {
loading = false,
color = "primary",
size = "base",
StartIcon,
EndIcon,
shallow,
// attributes propagated from `HTMLAnchorProps` or `HTMLButtonProps`
...passThroughProps
} = props;
// Buttons are **always** disabled if we're in a `loading` state
const disabled = props.disabled || loading;
// If pass an `href`-attr is passed it's `<a>`, otherwise it's a `<button />`
const isLink = typeof props.href !== "undefined";
const elementType = isLink ? "a" : "button";
const element = React.createElement(
elementType,
{
...passThroughProps,
disabled,
ref: forwardedRef,
className: classNames(
// base styles independent what type of button it is
"inline-flex items-center",
// different styles depending on size
size === "sm" && "px-3 py-2 text-sm leading-4 font-medium rounded-sm",
size === "base" && "px-3 py-2 text-sm font-medium rounded-sm",
size === "lg" && "px-4 py-2 text-base font-medium rounded-sm",
size === "icon" &&
"group p-2 border rounded-sm border-transparent text-neutral-400 hover:border-gray-200 transition",
// turn button into a floating action button (fab)
size === "fab" ? "fixed" : "relative",
size === "fab" && "justify-center bottom-20 right-8 rounded-full p-4 w-14 h-14",
// different styles depending on color
color === "primary" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "border border-transparent dark:text-brandcontrast text-brandcontrast bg-brand dark:bg-brand hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900"),
color === "secondary" &&
(disabled
? "border border-gray-200 text-gray-400 bg-white"
: "border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 hover:text-gray-900 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900 dark:bg-transparent dark:text-white dark:border-gray-800 dark:hover:bg-gray-900"),
color === "minimal" &&
(disabled
? "text-gray-400 bg-transparent"
: "text-gray-700 bg-transparent hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-gray-100 focus:ring-neutral-500"),
color === "warn" &&
(disabled
? "text-gray-400 bg-transparent"
: "text-gray-700 bg-transparent hover:text-red-700 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-red-50 focus:ring-red-500"),
// set not-allowed cursor if disabled
loading ? "cursor-wait" : disabled ? "cursor-not-allowed" : "",
props.className
),
// if we click a disabled button, we prevent going through the click handler
onClick: disabled
? (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
}
: props.onClick,
},
<>
{StartIcon && (
<StartIcon
className={classNames(
"inline",
size === "icon" ? "h-5 w-5 " : "-ml-1 h-5 w-5 ltr:mr-2 rtl:ml-2 rtl:ml-2 rtl:-mr-1"
)}
/>
)}
{props.children}
{loading && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
<svg
className={classNames(
"mx-4 h-5 w-5 animate-spin",
color === "primary" ? "text-white dark:text-black" : "text-black"
)}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
)}
{EndIcon && <EndIcon className="-mr-1 inline h-5 w-5 ltr:ml-2 rtl:mr-2" />}
</>
);
return props.href ? (
<Link passHref href={props.href} shallow={shallow && shallow}>
{element}
</Link>
) : (
element
);
});
export default Button;
// TODO: Remove this file once every Button is imported from `@calcom/ui`
export * from "@calcom/ui/Button";
export { default } from "@calcom/ui/Button";

View File

@@ -25,8 +25,11 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
({ children, ...props }, forwardedRef) => {
return (
<DropdownMenuPrimitive.Content
portalled={props.portalled}
{...props}
className="z-10 mt-1 w-48 origin-top-right rounded-sm bg-white text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
className={`${
props.portalled ? `` : `md:-ml-[55px]`
} z-10 mt-1 -ml-0 w-full origin-top-right rounded-sm bg-white text-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}
ref={forwardedRef}>
{children}
</DropdownMenuPrimitive.Content>

View File

@@ -1,11 +1,15 @@
import { Menu, Transition } from "@headlessui/react";
import { DotsHorizontalIcon } from "@heroicons/react/solid";
import React, { FC, Fragment } from "react";
import React, { FC } from "react";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { SVGComponent } from "@lib/types/SVGComponent";
import Dropdown, {
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from "@components/ui/Dropdown";
import Button from "./Button";
export type ActionType = {
@@ -38,57 +42,28 @@ const TableActions: FC<Props> = ({ actions }) => {
</Button>
))}
</div>
<Menu as="div" className="inline-block text-left lg:hidden ">
{({ open }) => (
<>
<div>
<Menu.Button className="mt-1 border border-transparent p-2 text-neutral-400 hover:border-gray-200">
<span className="sr-only">{t("open_options")}</span>
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="absolute right-0 mt-2 w-56 origin-top-right divide-y divide-neutral-100 rounded-sm bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
{actions.map((action) => {
const Element = typeof action.onClick === "function" ? "span" : "a";
return (
<Menu.Item key={action.id} disabled={action.disabled}>
{({ active }) => (
<Element
href={action.href}
onClick={action.onClick}
className={classNames(
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
"group flex items-center px-4 py-2 text-sm font-medium"
)}>
<action.icon
className="h-5 w-5 text-neutral-400 group-hover:text-neutral-500 ltr:mr-3"
aria-hidden="true"
/>
{action.label}
</Element>
)}
</Menu.Item>
);
})}
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
<div className="inline-block text-left lg:hidden">
<Dropdown>
<DropdownMenuTrigger className="h-[38px] w-[38px] cursor-pointer rounded-sm border border-transparent text-neutral-500 hover:border-gray-300 hover:text-neutral-900">
<DotsHorizontalIcon className="h-5 w-5 group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent portalled>
{actions.map((action) => (
<DropdownMenuItem key={action.id}>
<Button
type="button"
color="minimal"
className="w-full font-normal"
href={action.href}
StartIcon={action.icon}
onClick={action.onClick}>
{action.label}
</Button>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</Dropdown>
</div>
</>
);
};

View File

@@ -4,7 +4,7 @@ import React from "react";
import { TextProps } from "../Text";
const Headline: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames("font-cal text-xl font-bold text-gray-900 dark:text-white", props?.className);
const classes = classnames("font-cal text-xl text-gray-900 dark:text-white", props?.className);
return <p className={classes}>{props?.text || props.children}</p>;
};

View File

@@ -33,7 +33,7 @@ export const WeekdaySelect = (props: WeekdaySelectProps) => {
toggleDay(idx);
}}
className={`
bg-brand text-brandcontrast
bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast
h-10 w-10 rounded px-3 py-1 focus:outline-none
${activeDays[idx + 1] ? "rounded-r-none" : ""}
${activeDays[idx - 1] ? "rounded-l-none" : ""}

View File

@@ -62,7 +62,9 @@ export type ColorPickerProps = {
};
const ColorPicker = (props: ColorPickerProps) => {
const init = !isValidHexCode(props.defaultValue) ? fallBackHex(props.defaultValue) : props.defaultValue;
const init = !isValidHexCode(props.defaultValue)
? fallBackHex(props.defaultValue, false)
: props.defaultValue;
const [color, setColor] = useState(init);
const [isOpen, toggle] = useState(false);
const popover = useRef() as React.MutableRefObject<HTMLInputElement>;

View File

@@ -1,18 +1,24 @@
import React from "react";
import BasePhoneInput, { Props as PhoneInputProps } from "react-phone-number-input";
import { Control } from "react-hook-form";
import BasePhoneInput, { Props } from "react-phone-number-input/react-hook-form";
import "react-phone-number-input/style.css";
import classNames from "@lib/classNames";
import { Optional } from "@lib/types/utils";
export const PhoneInput = (
props: Optional<PhoneInputProps<React.InputHTMLAttributes<HTMLInputElement>>, "onChange">
) => (
type PhoneInputProps = {
value: string;
id: string;
placeholder: string;
required: boolean;
};
export const PhoneInput = ({ control, name, ...rest }: Props<PhoneInputProps>) => (
<BasePhoneInput
{...props}
{...rest}
name={name}
control={control}
className={classNames(
"border-1 focus-within:border-brand block w-full rounded-sm border border-gray-300 py-px px-3 shadow-sm ring-black focus-within:ring-1 dark:border-black dark:bg-black dark:text-white",
props.className
"border-1 focus-within:border-brand block w-full rounded-sm border border-gray-300 py-px px-3 shadow-sm ring-black focus-within:ring-1 dark:border-black dark:bg-black dark:text-white"
)}
onChange={() => {
/* DO NOT REMOVE: Callback required by PhoneInput, comment added to satisfy eslint:no-empty-function */

View File

@@ -10,6 +10,7 @@ import { weekdayNames } from "@lib/core/i18n/weekday";
import { useLocale } from "@lib/hooks/useLocale";
import { TimeRange } from "@lib/types/schedule";
import { useMeQuery } from "@components/Shell";
import Button from "@components/ui/Button";
import Select from "@components/ui/form/Select";
@@ -46,6 +47,10 @@ type TimeRangeFieldProps = {
};
const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
// Lazy-loaded options, otherwise adding a field has a noticable redraw delay.
const [options, setOptions] = useState<Option[]>([]);
const [selected, setSelected] = useState<number | undefined>();
@@ -57,7 +62,9 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
const getOption = (time: ConfigType) => ({
value: dayjs(time).toDate().valueOf(),
label: dayjs(time).utc().format("HH:mm"),
label: dayjs(time)
.utc()
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm"),
// .toLocaleTimeString(i18n.language, { minute: "numeric", hour: "numeric" }),
});
@@ -82,7 +89,7 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
handleSelected(value);
return (
<Select
className="w-[6rem]"
className="w-30"
options={options}
onFocus={() => setOptions(timeOptions())}
onBlur={() => setOptions([])}
@@ -100,7 +107,7 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
name={`${name}.end`}
render={({ field: { onChange, value } }) => (
<Select
className="w-[6rem]"
className="w-30"
options={options}
onFocus={() => setOptions(timeOptions({ selected }))}
onBlur={() => setOptions([])}

View File

@@ -1,11 +1,15 @@
import { ClockIcon } from "@heroicons/react/outline";
import { useRef } from "react";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { useRef, useState } from "react";
import { useLocale } from "@lib/hooks/useLocale";
import showToast from "@lib/notification";
import Button from "@components/ui/Button";
dayjs.extend(customParseFormat);
interface SetTimesModalProps {
startTime: number;
endTime: number;
@@ -21,6 +25,11 @@ export default function SetTimesModal(props: SetTimesModalProps) {
const startMinsRef = useRef<HTMLInputElement>(null!);
const endHoursRef = useRef<HTMLInputElement>(null!);
const endMinsRef = useRef<HTMLInputElement>(null!);
const [endMinuteDisable, setEndMinuteDisable] = useState(false);
const [maximumStartTime, setMaximumStartTime] = useState({ hour: endHours, minute: 59 });
const [minimumEndTime, setMinimumEndTime] = useState({ hour: startHours, minute: 59 });
const STEP = 15;
const isValidTime = (startTime: number, endTime: number) => {
if (new Date(startTime) > new Date(endTime)) {
@@ -34,6 +43,48 @@ export default function SetTimesModal(props: SetTimesModalProps) {
return true;
};
// compute dynamic range for minimum and maximum allowed hours/minutes.
const setEdgeTimes = (
(step) =>
(
startHoursRef: React.MutableRefObject<HTMLInputElement>,
startMinsRef: React.MutableRefObject<HTMLInputElement>,
endHoursRef: React.MutableRefObject<HTMLInputElement>,
endMinsRef: React.MutableRefObject<HTMLInputElement>
) => {
//parse all the refs
const startHour = parseInt(startHoursRef.current.value);
let startMinute = parseInt(startMinsRef.current.value);
const endHour = parseInt(endHoursRef.current.value);
let endMinute = parseInt(endMinsRef.current.value);
//convert to dayjs object
const startTime = dayjs(`${startHour}:${startMinute}`, "hh:mm");
const endTime = dayjs(`${endHour}:${endMinute}`, "hh:mm");
//compute minimin and maximum allowed
const maximumStartTime = endTime.subtract(step, "minute");
const maximumStartHour = maximumStartTime.hour();
const maximumStartMinute = startHour === endHour ? maximumStartTime.minute() : 59;
const minimumEndTime = startTime.add(step, "minute");
const minimumEndHour = minimumEndTime.hour();
const minimumEndMinute = startHour === endHour ? minimumEndTime.minute() : 0;
//check allow min/max minutes when the end/start hour matches
if (startHoursRef.current.value === endHoursRef.current.value) {
if (parseInt(startMinsRef.current.value) >= maximumStartMinute)
startMinsRef.current.value = maximumStartMinute.toString();
if (parseInt(endMinsRef.current.value) <= minimumEndMinute)
endMinsRef.current.value = minimumEndMinute.toString();
}
//save into state
setMaximumStartTime({ hour: maximumStartHour, minute: maximumStartMinute });
setMinimumEndTime({ hour: minimumEndHour, minute: minimumEndMinute });
}
)(STEP);
return (
<div
className="fixed inset-0 z-50 overflow-y-auto"
@@ -65,7 +116,7 @@ export default function SetTimesModal(props: SetTimesModalProps) {
</div>
<div className="mb-4 flex">
<label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("start_time")}</label>
<div>
<div className="w-1/6">
<label htmlFor="startHours" className="sr-only">
{t("hours")}
</label>
@@ -73,17 +124,18 @@ export default function SetTimesModal(props: SetTimesModalProps) {
ref={startHoursRef}
type="number"
min="0"
max="23"
maxLength={2}
max={maximumStartTime.hour}
minLength={2}
name="hours"
id="startHours"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="9"
defaultValue={startHours}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
<span className="mx-2 pt-1">:</span>
<div>
<div className="w-1/6">
<label htmlFor="startMinutes" className="sr-only">
{t("minutes")}
</label>
@@ -91,27 +143,28 @@ export default function SetTimesModal(props: SetTimesModalProps) {
ref={startMinsRef}
type="number"
min="0"
max="59"
step="15"
max={maximumStartTime.minute}
step={STEP}
maxLength={2}
name="minutes"
id="startMinutes"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="30"
defaultValue={startMinutes}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
</div>
<div className="flex">
<label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("end_time")}</label>
<div>
<div className="w-1/6">
<label htmlFor="endHours" className="sr-only">
{t("hours")}
</label>
<input
ref={endHoursRef}
type="number"
min="0"
min={minimumEndTime.hour}
max="24"
maxLength={2}
name="hours"
@@ -119,25 +172,32 @@ export default function SetTimesModal(props: SetTimesModalProps) {
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="17"
defaultValue={endHours}
onChange={(e) => {
if (endHoursRef.current.value === "24") endMinsRef.current.value = "0";
setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef);
setEndMinuteDisable(endHoursRef.current.value === "24");
}}
/>
</div>
<span className="mx-2 pt-1">:</span>
<div>
<div className="w-1/6">
<label htmlFor="endMinutes" className="sr-only">
{t("minutes")}
</label>
<input
ref={endMinsRef}
type="number"
min="0"
min={minimumEndTime.minute}
max="59"
maxLength={2}
step="15"
step={STEP}
name="minutes"
id="endMinutes"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="30"
defaultValue={endMinutes}
disabled={endMinuteDisable}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
</div>

View File

@@ -0,0 +1,163 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import Switch from "@calcom/ui/Switch";
import { useLocale } from "@lib/hooks/useLocale";
import showToast from "@lib/notification";
import { trpc } from "@lib/trpc";
import { WEBHOOK_TRIGGER_EVENTS } from "@lib/webhooks/constants";
import customTemplate, { hasTemplateIntegration } from "@lib/webhooks/integrationTemplate";
import { DialogFooter } from "@components/Dialog";
import { FieldsetLegend, Form, InputGroupBox, TextArea, TextField } from "@components/form/fields";
import Button from "@components/ui/Button";
import { TWebhook } from "@components/webhook/WebhookListItem";
import WebhookTestDisclosure from "@components/webhook/WebhookTestDisclosure";
export default function WebhookDialogForm(props: {
eventTypeId?: number;
defaultValues?: TWebhook;
handleClose: () => void;
}) {
const { t } = useLocale();
const utils = trpc.useContext();
const handleSubscriberUrlChange = (e) => {
form.setValue("subscriberUrl", e.target.value);
if (hasTemplateIntegration({ url: e.target.value })) {
setUseCustomPayloadTemplate(true);
form.setValue("payloadTemplate", customTemplate({ url: e.target.value }));
}
};
const {
defaultValues = {
id: "",
eventTriggers: WEBHOOK_TRIGGER_EVENTS,
subscriberUrl: "",
active: true,
payloadTemplate: null,
} as Omit<TWebhook, "userId" | "createdAt" | "eventTypeId">,
} = props;
const [useCustomPayloadTemplate, setUseCustomPayloadTemplate] = useState(!!defaultValues.payloadTemplate);
const form = useForm({
defaultValues,
});
return (
<Form
data-testid="WebhookDialogForm"
form={form}
handleSubmit={async (event) => {
const e = { ...event, eventTypeId: props.eventTypeId };
if (!useCustomPayloadTemplate && event.payloadTemplate) {
event.payloadTemplate = null;
}
if (event.id) {
await utils.client.mutation("viewer.webhook.edit", e);
await utils.invalidateQueries(["viewer.webhook.list"]);
showToast(t("webhook_updated_successfully"), "success");
} else {
await utils.client.mutation("viewer.webhook.create", e);
await utils.invalidateQueries(["viewer.webhook.list"]);
showToast(t("webhook_created_successfully"), "success");
}
props.handleClose();
}}
className="space-y-4">
<input type="hidden" {...form.register("id")} />
<fieldset className="space-y-2">
<InputGroupBox className="border-0 bg-gray-50">
<Controller
control={form.control}
name="active"
render={({ field }) => (
<Switch
label={field.value ? t("webhook_enabled") : t("webhook_disabled")}
defaultChecked={field.value}
onCheckedChange={(isChecked) => {
form.setValue("active", isChecked);
}}
/>
)}
/>
</InputGroupBox>
</fieldset>
<TextField
label={t("subscriber_url")}
{...form.register("subscriberUrl")}
required
type="url"
onChange={handleSubscriberUrlChange}
/>
<fieldset className="space-y-2">
<FieldsetLegend>{t("event_triggers")}</FieldsetLegend>
<InputGroupBox className="border-0 bg-gray-50">
{WEBHOOK_TRIGGER_EVENTS.map((key) => (
<Controller
key={key}
control={form.control}
name="eventTriggers"
render={({ field }) => (
<Switch
label={t(key.toLowerCase())}
defaultChecked={field.value.includes(key)}
onCheckedChange={(isChecked) => {
const value = field.value;
const newValue = isChecked ? [...value, key] : value.filter((v) => v !== key);
form.setValue("eventTriggers", newValue, {
shouldDirty: true,
});
}}
/>
)}
/>
))}
</InputGroupBox>
</fieldset>
<fieldset className="space-y-2">
<FieldsetLegend>{t("payload_template")}</FieldsetLegend>
<div className="space-x-3 text-sm rtl:space-x-reverse">
<label>
<input
className="text-neutral-900 focus:ring-neutral-500"
type="radio"
name="useCustomPayloadTemplate"
onChange={(value) => setUseCustomPayloadTemplate(!value.target.checked)}
defaultChecked={!useCustomPayloadTemplate}
/>{" "}
Default
</label>
<label>
<input
className="text-neutral-900 focus:ring-neutral-500"
onChange={(value) => setUseCustomPayloadTemplate(value.target.checked)}
name="useCustomPayloadTemplate"
type="radio"
defaultChecked={useCustomPayloadTemplate}
/>{" "}
Custom
</label>
</div>
{useCustomPayloadTemplate && (
<TextArea
{...form.register("payloadTemplate")}
defaultValue={useCustomPayloadTemplate && (defaultValues.payloadTemplate || "")}
rows={3}
/>
)}
</fieldset>
<WebhookTestDisclosure />
<DialogFooter>
<Button type="button" color="secondary" onClick={props.handleClose} tabIndex={-1}>
{t("cancel")}
</Button>
<Button type="submit" loading={form.formState.isSubmitting}>
{t("save")}
</Button>
</DialogFooter>
</Form>
);
}

View File

@@ -0,0 +1,103 @@
import classNames from "classnames";
import Image from "next/image";
import { useState } from "react";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import { trpc } from "@lib/trpc";
import { Dialog, DialogContent } from "@components/Dialog";
import { List, ListItem, ListItemText, ListItemTitle } from "@components/List";
import { ShellSubHeading } from "@components/Shell";
import Button from "@components/ui/Button";
import WebhookDialogForm from "@components/webhook/WebhookDialogForm";
import WebhookListItem, { TWebhook } from "@components/webhook/WebhookListItem";
export type WebhookListContainerType = {
title: string;
subtitle: string;
eventTypeId?: number;
};
export default function WebhookListContainer(props: WebhookListContainerType) {
const { t } = useLocale();
const query = props.eventTypeId
? trpc.useQuery(["viewer.webhook.list", { eventTypeId: props.eventTypeId }], {
suspense: true,
})
: trpc.useQuery(["viewer.webhook.list"], {
suspense: true,
});
const [newWebhookModal, setNewWebhookModal] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [editing, setEditing] = useState<TWebhook | null>(null);
return (
<QueryCell
query={query}
success={({ data }) => (
<>
<ShellSubHeading className="mt-10" title={props.title} subtitle={props.subtitle} />
<List>
<ListItem className={classNames("flex-col")}>
<div
className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
<Image width={40} height={40} src="/integrations/webhooks.svg" alt="Webhooks" />
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">Webhooks</ListItemTitle>
<ListItemText component="p">{t("automation")}</ListItemText>
</div>
<div>
<Button
color="secondary"
onClick={() => setNewWebhookModal(true)}
data-testid="new_webhook">
{t("new_webhook")}
</Button>
</div>
</div>
</ListItem>
</List>
{data.length ? (
<List>
{data.map((item) => (
<WebhookListItem
key={item.id}
webhook={item}
onEditWebhook={() => {
setEditing(item);
setEditModalOpen(true);
}}
/>
))}
</List>
) : null}
{/* New webhook dialog */}
<Dialog open={newWebhookModal} onOpenChange={(isOpen) => !isOpen && setNewWebhookModal(false)}>
<DialogContent>
<WebhookDialogForm
eventTypeId={props.eventTypeId}
handleClose={() => setNewWebhookModal(false)}
/>
</DialogContent>
</Dialog>
{/* Edit webhook dialog */}
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
<DialogContent>
{editing && (
<WebhookDialogForm
key={editing.id}
eventTypeId={props.eventTypeId || undefined}
handleClose={() => setEditModalOpen(false)}
defaultValues={editing}
/>
)}
</DialogContent>
</Dialog>
</>
)}
/>
);
}

View File

@@ -0,0 +1,93 @@
import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { Dialog, DialogTrigger } from "@components/Dialog";
import { ListItem } from "@components/List";
import { Tooltip } from "@components/Tooltip";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import Button from "@components/ui/Button";
export type TWebhook = inferQueryOutput<"viewer.webhook.list">[number];
export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhook: () => void }) {
const { t } = useLocale();
const utils = trpc.useContext();
const deleteWebhook = trpc.useMutation("viewer.webhook.delete", {
async onSuccess() {
await utils.invalidateQueries(["viewer.webhook.list"]);
},
});
return (
<ListItem className="-mt-px flex w-full p-4">
<div className="flex w-full justify-between">
<div className="flex max-w-full flex-col truncate">
<div className="flex space-y-1">
<span
className={classNames(
"truncate text-sm",
props.webhook.active ? "text-neutral-700" : "text-neutral-200"
)}>
{props.webhook.subscriberUrl}
</span>
</div>
<div className="mt-2 flex">
<span className="flex flex-col space-x-2 space-y-1 text-xs sm:flex-row sm:space-y-0 sm:rtl:space-x-reverse">
{props.webhook.eventTriggers.map((eventTrigger, ind) => (
<span
key={ind}
className={classNames(
"w-max rounded-sm px-1 text-xs ",
props.webhook.active ? "bg-blue-100 text-blue-700" : "bg-blue-50 text-blue-200"
)}>
{t(`${eventTrigger.toLowerCase()}`)}
</span>
))}
</span>
</div>
</div>
<div className="flex">
<Tooltip content={t("edit_webhook")}>
<Button
onClick={() => props.onEditWebhook()}
color="minimal"
size="icon"
StartIcon={PencilAltIcon}
className="ml-4 w-full self-center p-2"></Button>
</Tooltip>
<Dialog>
<Tooltip content={t("delete_webhook")}>
<DialogTrigger asChild>
<Button
onClick={(e) => {
e.stopPropagation();
}}
color="minimal"
size="icon"
StartIcon={TrashIcon}
className="ml-2 w-full self-center p-2"></Button>
</DialogTrigger>
</Tooltip>
<ConfirmationDialogContent
variety="danger"
title={t("delete_webhook")}
confirmBtnText={t("confirm_delete_webhook")}
cancelBtnText={t("cancel")}
onConfirm={() =>
deleteWebhook.mutate({
id: props.webhook.id,
eventTypeId: props.webhook.eventTypeId || undefined,
})
}>
{t("delete_webhook_confirmation_message")}
</ConfirmationDialogContent>
</Dialog>
</div>
</div>
</ListItem>
);
}

View File

@@ -0,0 +1,64 @@
import { ChevronRightIcon, SwitchHorizontalIcon } from "@heroicons/react/solid";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import { useState } from "react";
import { useWatch } from "react-hook-form";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import showToast from "@lib/notification";
import { trpc } from "@lib/trpc";
import { InputGroupBox } from "@components/form/fields";
import Button from "@components/ui/Button";
export default function WebhookTestDisclosure() {
const subscriberUrl: string = useWatch({ name: "subscriberUrl" });
const payloadTemplate = useWatch({ name: "payloadTemplate" }) || null;
const { t } = useLocale();
const [open, setOpen] = useState(false);
const mutation = trpc.useMutation("viewer.webhook.testTrigger", {
onError(err) {
showToast(err.message, "error");
},
});
return (
<Collapsible open={open} onOpenChange={() => setOpen(!open)}>
<CollapsibleTrigger type="button" className={"flex w-full cursor-pointer"}>
<ChevronRightIcon className={`${open ? "rotate-90 transform" : ""} h-5 w-5 text-neutral-500`} />
<span className="text-sm font-medium text-gray-700">{t("webhook_test")}</span>
</CollapsibleTrigger>
<CollapsibleContent>
<InputGroupBox className="space-y-0 border-0 px-0">
<div className="flex justify-between bg-gray-50 p-2">
<h3 className="self-center text-gray-700">{t("webhook_response")}</h3>
<Button
StartIcon={SwitchHorizontalIcon}
type="button"
color="minimal"
disabled={mutation.isLoading}
onClick={() => mutation.mutate({ url: subscriberUrl, type: "PING", payloadTemplate })}>
{t("ping_test")}
</Button>
</div>
<div className="border-8 border-gray-50 p-2 text-gray-500">
{!mutation.data && <em>{t("no_data_yet")}</em>}
{mutation.status === "success" && (
<>
<div
className={classNames(
"ml-auto w-max px-2 py-1 text-xs",
mutation.data.ok ? "bg-green-50 text-green-500" : "bg-red-50 text-red-500"
)}>
{mutation.data.ok ? t("success") : t("failed")}
</div>
<pre className="overflow-x-auto">{JSON.stringify(mutation.data, null, 4)}</pre>
</>
)}
</div>
</InputGroupBox>
</CollapsibleContent>
</Collapsible>
);
}

View File

@@ -38,7 +38,6 @@ export function ContractsProvider({ children }: Props) {
return (
<>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<ContractsContext.Provider value={value}>{children}</ContractsContext.Provider>
</>

View File

@@ -13,9 +13,11 @@ const TrialBanner = () => {
if (!user || user.plan !== "TRIAL") return null;
const trialDaysLeft = dayjs(user.createdDate)
.add(TRIAL_LIMIT_DAYS + 1, "day")
.diff(dayjs(), "day");
const trialDaysLeft = user.trialEndsAt
? dayjs(user.trialEndsAt).add(1, "day").diff(dayjs(), "day")
: dayjs(user.createdDate)
.add(TRIAL_LIMIT_DAYS + 1, "day")
.diff(dayjs(), "day");
return (
<div

View File

@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { stringify } from "querystring";
import React, { SyntheticEvent, useEffect, useState } from "react";
import { PaymentData } from "@ee/lib/stripe/server";
import { PaymentData } from "@calcom/stripe/server";
import { useLocale } from "@lib/hooks/useLocale";

View File

@@ -8,12 +8,13 @@ import Head from "next/head";
import React, { FC, useEffect, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import getStripe from "@calcom/stripe/client";
import PaymentComponent from "@ee/components/stripe/Payment";
import getStripe from "@ee/lib/stripe/client";
import { PaymentPageProps } from "@ee/pages/payment/[uid]";
import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme";
import { isBrowserLocale24h } from "@lib/timeFormat";
dayjs.extend(utc);
dayjs.extend(toArray);
@@ -21,7 +22,7 @@ dayjs.extend(timezone);
const PaymentPage: FC<PaymentPageProps> = (props) => {
const { t } = useLocale();
const [is24h, setIs24h] = useState(false);
const [is24h, setIs24h] = useState(isBrowserLocale24h());
const [date, setDate] = useState(dayjs.utc(props.booking.startTime));
const { isReady, Theme } = useTheme(props.profile.theme);

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