Compare commits

..
212 Commits
Author SHA1 Message Date
Keith Williams b4b38b9bc3 v3.2.3
release.yml / release (push) Canceled after 0s
2023-08-24 17:16:02 +03:00
Crowdin Bot d32719ad8b New Crowdin translations by Github Action 2023-08-24 12:47:12 +00:00
Hariom Balhara 99d0816d9f phoneSchema missing async parsing (#10929) 2023-08-24 12:43:52 +00:00
95f4b89fe8 fix: #10783 (UI updated on API Creation Form ) (#10786)
* Fix #10783

* Update the changes #10783

* Updated the changes on the another dialog footer

* Updated the changes

---------

Co-authored-by: Hem Pun <hemthegentlmen@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-24 11:20:48 +01:00
Abhijeet SinghandAbhijeet Singh 3e63c2b717 fix: Italics not being shown in the description after saving (#10848)
* added ignore rule for em tag in turndown

* added ignore rule for em tag in turndown

---------

Co-authored-by: Abhijeet Singh <asing9829@gmail.com>
2023-08-24 11:20:00 +01:00
Omar López 42cd56578d fix: Reduced boilerplate for publicViewer router (#10849) 2023-08-24 11:19:24 +01:00
Ujwal KumarandBailey Pumfleet 31d4cb4eb7 Transform # character to - in slug (#10896)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-08-24 11:19:10 +01:00
sean-brydonandBailey Pumfleet fde3e8bea9 Invalidate and add success message (#10907)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-08-24 11:18:59 +01:00
GitStart-Cal.comandgitstart-calcom 874ad29676 chore: add Table in storybook (CALCOM-10760) (#10869)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-24 11:07:57 +01:00
Hariom Balhara 5503d9d432 perf: bookings query improvement (#10687) 2023-08-24 11:14:10 +02:00
Keith WilliamsandShivam Kalra 43b3d68447 chore: Don't render certain components when running meticulous tests (#10772)
* chore: Don't render credits when running meticulous tests

* fix: type window Meticulous

* fix: window reference error

* chore: window interface

* chore: disable tzdialog popup on meticulous

* chore: IS_VISUAL_REGRESSION_TESTING

* fix: constants

* fix: only run on preview builds

---------

Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-08-24 13:42:53 +05:30
GitStart-Cal.comandgitstart-calcom e3c747a867 test: Create unit tests for react components is packages/ui/components/navigation (#10698)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-24 13:23:54 +05:30
sean-brydonandOmar López e020a2c9a4 feat: Availability Slider enabled for teams (#10902)
Co-authored-by: Omar López <zomars@me.com>
2023-08-23 21:23:24 +00:00
Leo Giovanetti e6e6f09547 feat: admin org list now shows all, dns check added (#10875)
* chore: admin org list now shows all, dns check added

* Updating email template

* type fixes
2023-08-23 21:01:12 +00:00
Leo GiovanettiandJoe Au-Yeung 68ec946c59 fix: add org avatar to org booking pages [CAL-2333] (#10847)
* Add org avatar to org booking pages

* Using web app url for org avatars + missing i18n string

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-23 20:10:02 +00:00
Omar López 5a6e5ce427 v3.2.2
release.yml / release (push) Canceled after 0s
2023-08-23 11:49:37 -07:00
Carina WollendorferandCarinaWolli b2382211db fix: adding a new workflow step that requires kyc verification (#10912)
* fix verificaiton check when step is added

* add verify badge with dialog also when adding action

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-23 20:32:14 +02:00
Joe Au-Yeung 12148506bd fix: Remove multiple durations for seated events (#10910)
* Remove multiple durations for seated events

* Clean up
2023-08-23 18:25:44 +00:00
MontoandSMLukwiya b5666fd4c3 fix: team availability shows only to the member owning it (#10738)
Co-authored-by: SMLukwiya <sundaymorganl@gmail.com>
2023-08-23 12:27:19 -04:00
Omar López bc7da563d8 fix: Prevents mixed server and client code (#10909) 2023-08-23 16:25:43 +00:00
d3acf4160d fix: data collection for headless router (#9183)
Co-authored-by: Wesley <100464352+ologbonowiwi@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-08-23 11:12:51 -04:00
Crowdin Bot 65e0c0e4c5 New Crowdin translations by Github Action 2023-08-23 12:16:51 +00:00
nicktrn 2dda6a509b chore: cron workflow tidying (#10127) 2023-08-23 17:43:22 +05:30
Crowdin Bot 7238414864 New Crowdin translations by Github Action 2023-08-23 09:11:55 +00:00
Shivam Kalra bab41ca8a6 chore: expect emails e2e (#10881)
* refactor: emails e2e

* fix: email subject with nameless organizer

* fix: comment on NEXT_PUBLIC_IS_E2E

* chore: E2E_TEST_MAILHOG_ENABLED env

* feat: IS_MAILHOG_ENABLED

* doc: E2E_TEST_MAILHOG_ENABLED

* fix: comment

* fix: env order

* chore: add E2E_TEST_MAILHOG_ENABLED to e2e env
2023-08-23 11:08:14 +02:00
Carina WollendorferandCarinaWolli 376f023e0f fix: scheduling workflow emails (#10890)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-22 12:29:06 -07:00
zomars 0cbf525a78 fix: Disables es-419 until translated 2023-08-22 12:01:50 -07:00
zomars f56fefec4d fix: Disables es-419 until translated 2023-08-22 12:00:20 -07:00
Crowdin Bot 66eccd6dd8 New Crowdin translations by Github Action 2023-08-22 18:12:25 +00:00
Omar López c4b9a9e2f4 fix: client locale inference (#10893)
* fix: client locale inference

* Update I18nLanguageHandler.tsx

* More fixes

* Update I18nLanguageHandler.tsx

* Update I18nLanguageHandler.tsx

* Addressing feedback

* Fix: first render issues

* Update Credits.tsx

* Fixed tests

* fix: try without loose locale pick

* Revert "fix: try without loose locale pick"

This reverts commit 5ed649c13117fee8bc507098353de0f18e98d971.

* fix: sorts locales so specifics are chosen first

* fix: switches client locale matcher
2023-08-22 20:08:34 +02:00
Crowdin Bot 38921e58cd New Crowdin translations by Github Action 2023-08-22 13:46:36 +00:00
Crowdin Bot 2c9c6c77f7 New Crowdin translations by Github Action 2023-08-22 13:43:30 +00:00
Syed Ali Shahbaz 46605e049d fix: Improves the Login button for SAML/OIDC (#10885) 2023-08-22 15:39:50 +02:00
Crowdin Bot 01e1e82ced New Crowdin translations by Github Action 2023-08-22 13:20:24 +00:00
Crowdin Bot 8e17716094 New Crowdin translations by Github Action 2023-08-22 13:17:20 +00:00
Crowdin Bot 7c028b6479 New Crowdin translations by Github Action 2023-08-22 13:14:18 +00:00
Crowdin Bot cba4bae5cd New Crowdin translations by Github Action 2023-08-22 13:10:46 +00:00
Omar López 55046f3017 chore: add constant for production builds (#10762) 2023-08-22 13:07:32 +00:00
Crowdin Bot 4836621c31 New Crowdin translations by Github Action 2023-08-22 12:37:46 +00:00
Omar López 6743aa4609 fix: client locale inference (#10850) 2023-08-22 14:34:55 +02:00
Crowdin Bot bbad0fbc18 New Crowdin translations by Github Action 2023-08-22 12:32:40 +00:00
Crowdin Bot 8f6812ecb2 New Crowdin translations by Github Action 2023-08-22 12:30:01 +00:00
Crowdin Bot 303b09f341 New Crowdin translations by Github Action 2023-08-22 12:27:07 +00:00
Crowdin Bot 1f3ee41eb4 New Crowdin translations by Github Action 2023-08-22 12:24:09 +00:00
Crowdin Bot 01c711a81e New Crowdin translations by Github Action 2023-08-22 12:21:37 +00:00
Aswath SandHariom Balhara 16bb5c3384 feat: Added more query params to successRedirect page (#8271)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-08-22 12:18:36 +00:00
Crowdin Bot c55d97368e New Crowdin translations by Github Action 2023-08-22 11:09:29 +00:00
Crowdin Bot ef193aca31 New Crowdin translations by Github Action 2023-08-22 11:06:46 +00:00
Nate GriftandUdit Takkar c36a6797a9 test: added team & user having same name test (#10868)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-22 13:03:00 +02:00
f393fd4ad4 fix: Resolved Typescript Error in PR #9598 created by @vijeet-shah. (#10871)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: vijeet-shah <93502358+vijeet-shah@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-08-22 13:02:40 +02:00
Crowdin Bot 8987b9bdbc New Crowdin translations by Github Action 2023-08-22 10:33:33 +00:00
Crowdin Bot 68bf426af0 New Crowdin translations by Github Action 2023-08-22 10:30:43 +00:00
Crowdin Bot 1125900013 New Crowdin translations by Github Action 2023-08-22 10:27:47 +00:00
Crowdin Bot d272e823c3 New Crowdin translations by Github Action 2023-08-22 10:24:43 +00:00
Crowdin Bot 3ea11d4f38 New Crowdin translations by Github Action 2023-08-22 10:21:55 +00:00
Crowdin Bot b4dc64594c New Crowdin translations by Github Action 2023-08-22 10:19:05 +00:00
Crowdin Bot 6a763b3720 New Crowdin translations by Github Action 2023-08-22 10:16:14 +00:00
Crowdin Bot 252ac1c704 New Crowdin translations by Github Action 2023-08-22 10:13:06 +00:00
Crowdin Bot c51e42a061 New Crowdin translations by Github Action 2023-08-22 10:10:14 +00:00
Crowdin Bot e3f9e477fe New Crowdin translations by Github Action 2023-08-22 10:07:12 +00:00
Crowdin Bot b265970b01 New Crowdin translations by Github Action 2023-08-22 10:03:58 +00:00
Crowdin Bot 7b7b2ffc9a New Crowdin translations by Github Action 2023-08-22 10:00:41 +00:00
Crowdin Bot d019062709 New Crowdin translations by Github Action 2023-08-22 09:58:00 +00:00
Crowdin Bot ad9d728314 New Crowdin translations by Github Action 2023-08-22 09:55:10 +00:00
Crowdin Bot b564fb9a2f New Crowdin translations by Github Action 2023-08-22 09:52:15 +00:00
Crowdin Bot 9c05f6839c New Crowdin translations by Github Action 2023-08-22 09:49:20 +00:00
Crowdin Bot 4dea38f06d New Crowdin translations by Github Action 2023-08-22 09:46:30 +00:00
Crowdin Bot c4fc222204 New Crowdin translations by Github Action 2023-08-22 09:43:37 +00:00
Crowdin Bot 00c3a2dd80 New Crowdin translations by Github Action 2023-08-22 09:40:44 +00:00
Crowdin Bot 46fa0088f2 New Crowdin translations by Github Action 2023-08-22 09:37:44 +00:00
Crowdin Bot a19fd9b889 New Crowdin translations by Github Action 2023-08-22 09:34:39 +00:00
Crowdin Bot d379c10510 New Crowdin translations by Github Action 2023-08-22 09:31:11 +00:00
GitStart-Cal.comandgitstart-calcom f3a58145ee chore: add datePicker in storybook (CALCOM-10760) (#10784)
* Update datepicker.stories.mdx

* Update datepicker.stories.mdx

* Requested changes

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-22 10:27:27 +01:00
Crowdin Bot e6125dc361 New Crowdin translations by Github Action 2023-08-21 23:15:41 +00:00
zomars b78f718e9e Update yarn.lock 2023-08-21 16:12:41 -07:00
zomars 53e87f7769 fix: Removes duplicate locale keys 2023-08-21 16:11:04 -07:00
Crowdin Bot 86eb40e0fd New Crowdin translations by Github Action 2023-08-21 17:15:42 +00:00
Hariom BalharaandUdit Takkar 51caa6834a fix: Make identifier conform to RHF field requirement (#10860)
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-08-21 13:11:47 -04:00
7a7a5b5285 fix: Removing payment fee on stripe (#10652)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-21 12:33:34 +02:00
f14ffb9e44 chore: add Dialog component in storybook (#10822)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-08-21 09:58:26 +00:00
Harry Dobrev 001b524005 fix: icon in emails is considered a tracking pixel (#10807) 2023-08-21 11:48:06 +02:00
GitStart-Cal.comandgitstart-calcom a8cf4fb309 chore: add Editor in storybook (CALCOM-10760) (#10820)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-21 11:45:30 +02:00
Joe Au-Yeung 155775556d perf: GCal reduce calendar payload (#10853) 2023-08-21 11:27:18 +02:00
d66095bf9c test: adding confirmed payment test cases on Stripe (#10243)
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-21 11:12:39 +02:00
0ddab30340 chore: fix all @typescript-eslint/no-explicit-any warnings (#10656)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-08-21 11:01:56 +02:00
Moon Patel 2a9e061575 fix: Next/previous buttons on week view move 8 instead of 7 days (#10742) 2023-08-21 11:01:27 +02:00
Crowdin Bot 351dce5cfd New Crowdin translations by Github Action 2023-08-21 08:31:02 +00:00
Koushik Sai 3255d9012c fix: missing border-subtle class (#10838) 2023-08-21 09:28:34 +01:00
Crowdin Bot 226dcb3fcf New Crowdin translations by Github Action 2023-08-19 20:07:17 +00:00
Jon@1599andLeo Giovanetti f80e9b2558 feat: add basecamp integration to cal.com (#9195)
* feat: installing the app works

* Update yarn.lock

* feat: /api/callback now gets user auth info from basecamp

* feat: updated basecamp logo

* feat: added project dropdown on event apps page

* feat: basecamp event creation and deletion working

* feat: basecamp event rescheduling now works

* refactor(CalendarService): basecamp CaldendarService code clean up

* refactor: code cleanup for basecamp app API

* feat: updated event summary text sent to basecamp

* chore: updated basecamp images and contact info

* fix: fixed typescript errors and added logic to refresh tokens on event settings

* refactor(CaldendarService): used refreshAccessToken from helpers.ts instead

* chore: updated basecamp description

* fix: fixed incorrect import

* fix: accidentally deleted props to toggle app for event

* chore: updated .env.appStore.example and added README for app

* Update .env.appStore.example

Co-authored-by: Leo Giovanetti <hello@leog.me>

* feat: added basecamp userAgent in env instead of hardcoded value

* feat: updated README to include how to set basecamp user agent env

* fix: removed unused import

* feat: used URLSearchParams to construct url params

* fix: fixed typescript errors

* chore: updated README to include an example on how to set basecamp user-agent

* feat: using TRPC instead of REST

* chore: removed old projects REST code

---------

Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-08-19 17:04:56 -03:00
Keith Williamsandzomars 3f273235a6 perf: Cache translations per language/per release (#10843)
Co-authored-by: zomars <zomars@me.com>
2023-08-18 17:46:17 -07:00
zomars e8554ed5c5 fix: key warning 2023-08-18 15:50:44 -07:00
zomars a03744008c chore: Update orgs README 2023-08-18 14:26:33 -07:00
Leo GiovanettiandOmar López fe8b13885c fix: org admin has team member privileges [CAL-2338] (#10809)
Co-authored-by: Omar López <zomars@me.com>
2023-08-18 14:10:17 -07:00
Leo Giovanettiandzomars 8995dcc82a fix: org username check [CAL-2352] (#10773)
Co-authored-by: zomars <zomars@me.com>
2023-08-18 14:03:55 -07:00
zomars e8e0d03265 chore: Adds basic README for orgs 2023-08-18 12:26:59 -07:00
Crowdin Bot 56031f0358 New Crowdin translations by Github Action 2023-08-18 18:19:11 +00:00
Joe Au-Yeung 1ef0f830d3 fix: Username suffix on production [CAL-2262] (#10825) 2023-08-18 11:15:58 -07:00
Hariom Balhara 59e2f6e4a7 perf: Booker-Bundle Size Improvement - Lazy load react-select, remove lodash and Dom Purify (#10814) 2023-08-18 11:13:21 -07:00
+15 67bb2ed798 feat: Availability slider for orgs (#10740)
* Init + get timezone + offset data agh

* Add 12/24h mode - style correctly

* User users timezone + working hours. Still some stuff to figure out

* Multiple working hours

* move calc to once per day

* Demo with two users and differnt timezones

* availabillity control tab via search params

* WIP hover overlay

* THIS WORKS ISH

* fix: multiple duration getSchedule calls [CAL-2336] (#10709)

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

* New Crowdin translations by Github Action

* fix: If the input type "Name" is selected, the label can't be changed from our default label "Your Name" (#10618)

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

* New Crowdin translations by Github Action

* test: Create unit tests for react components in packages/ui/components/form/step (#10442)

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* feat: element call app added (#10585)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* New Crowdin translations by Github Action

* New Crowdin translations by Github Action

* fix: e2e test for rescheduling overlapping time (#10721)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* feat: mailhog fixture (#10606)

* feat: mailhog fixture

* fix: nodemailer to dispatch emails with e2e env

* fix: remove space from email subject

* feat: fixture getFirstEventAsOwner

* feat: assert email subjects

* fix: and enable dynamic booking test (#10642)

* fix and enable dynamic booking test

* remove page pause

---------

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

* fix: Broken team events if a user with the same name exists (#10724)

* fix: Broken team events if a user with the same name exists

* Fix tests + fix usernameList optionality

* Try to list calendars, if not continue (#10720)

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

* v3.1.9

* link to org settings (#10718)

* feat: app paypal payment (#8797)

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>

* fix: RTL issues on booking pages + email confirmation (#10526)

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

* fix: merge conflict

* Fixing org slug (#10538)

* fix: paypal build fixes

* Fix avatar for org in Shell top (#10712)

* feat: add range of dates for availability over-ride (#10462)

* feat: add range of dates for availability over-ride

* chore: changed range select to multiple select

---------

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

* fix: border issue for time slots (#10577)

Co-authored-by: Raghul D <v-raghuld@microsoft.com>

* style: Fix text wrapping issue in button (#10725)

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

* New Crowdin translations by Github Action

* fix: App Install Dropdown Sort Properly [CAL-2285] (#10672)

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

* fix: link escaping in booking page (#10360)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* chore: fix refund i18n message (#10731)

* chore: remove tailwind-scrollbar warning (#10523)

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* New Crowdin translations by Github Action

* chore: Simplified date overrides (#10728)

* chore: Simplified date overrides

* Fixed a test that had a date override that wasn't at midnight utc

* Wrote test that showed a fixed Europe/Brussels

* Lint fix

* New Crowdin translations by Github Action

* Fix offset time + fetching correct dates

* Deal with awkward minute offsets

* remove store overhead

* Format H based on tz

* Cleanup store logic

* Cleanup

* Remove comments

* Remove comments

* Remove yarn.lock

* Dark mode & v-align text fixes

* Move ButtonGroup to the left to prevent chevron from jumping

* Shift based on timezone (non-hour) and have 15min granularity in hour display background color

---------

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Zain Gulbaz <zaingulbaz8@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Suyash Srivastava <suyashsrivastava5053@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Pradumn Kumar <pradumn@tealfeed.com>
Co-authored-by: Raghul <123321540+Raghul18@users.noreply.github.com>
Co-authored-by: Raghul D <v-raghuld@microsoft.com>
Co-authored-by: ABDERRAHMANI IDRISSI HAMZA <97639117+idrissi-hamza@users.noreply.github.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: Danila <daniil.demidovich@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-18 16:32:24 +00:00
Crowdin Bot ea4618a244 New Crowdin translations by Github Action 2023-08-18 15:39:49 +00:00
Keith Williams 201a9d9356 perf: Fix slowest db query (#10842) 2023-08-18 17:36:57 +02:00
Crowdin Bot 40b0d57024 New Crowdin translations by Github Action 2023-08-18 11:01:22 +00:00
761f66fc5a Create RTL tests for Meta component (#10478)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-08-18 16:28:30 +05:30
2403d77166 test: Create unit tests for react components in packages/ui/components/createButton (#10644)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-08-18 14:22:14 +05:30
Anik Dhabal Babu b44f4d66f9 fix text align (#10799) 2023-08-18 12:03:04 +05:30
Joe Au-Yeung d178962c37 Fix readonly check (#10824) 2023-08-18 06:30:59 +02:00
Crowdin Bot cf4d2b4ef5 New Crowdin translations by Github Action 2023-08-17 18:00:26 +00:00
Hariom Balhara 3e4139ac23 perf: Lazy load libphonenumber-js only when validation is needed. (#10812) 2023-08-17 10:57:30 -07:00
Crowdin Bot 9fb736f67a New Crowdin translations by Github Action 2023-08-17 16:57:12 +00:00
Alex van Andel afc738141f v3.2.1
release.yml / release (push) Canceled after 0s
2023-08-17 17:36:48 +01:00
Syed Ali Shahbaz efa195059e feat: Add scheduleId to POST and PATCH requests in event-type endpoint (#10816)
* adds scheduleId to PATCH

* adds scheduleID to POST

* updated docs
2023-08-17 15:42:29 +00:00
nicktrn 0f2b6bbe1a fix: add 2fa and email verification grace period (#10771) 2023-08-17 16:13:04 +02:00
Alex van Andelandsean-brydon 28b94a865f perf: Create a seperate component for dependencies of BookEventForm (#10808)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-08-17 16:12:33 +02:00
GitStart-Cal.comandgitstart-calcom f45803fcfa chore: add Steps in storybook (CALCOM-10760) (#10810)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-17 09:26:08 +01:00
alannncandJoe Au-Yeung 053da1ec32 fix: currency for event types (#10792)
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2023-08-16 14:58:28 -07:00
nicktrnandSyed Ali Shahbaz f0dcfc83c8 feat: add support for apple travel time (#10660)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2023-08-16 13:32:04 -07:00
5eb3af54b3 test: add apple calendar testing (#10137)
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-08-16 12:49:10 -07:00
Crowdin Bot 3552bbc6cf New Crowdin translations by Github Action 2023-08-16 19:46:15 +00:00
Koushik Sai 028bd77c91 feat: rank apps with most installed for a chosen category (#10756) 2023-08-16 12:43:37 -07:00
Crowdin Bot 65a1aa6100 New Crowdin translations by Github Action 2023-08-16 18:06:30 +00:00
Hariom Balhara a09401c203 perf: Booker bundle size optimization - Remove sanitize-html dependency on client (#10801) 2023-08-16 11:03:21 -07:00
sean-brydon 7c23773636 Line clamp event type bio (#10795) 2023-08-16 10:59:42 +02:00
Udit TakkarandShivam Kalra 56ba8c0a07 fix: sucess page time (#10743)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
2023-08-16 13:35:31 +05:30
alannnc 5924bc0990 fix update profile input metadata (#10791) 2023-08-15 17:27:48 -07:00
+27 10ffd9bacd feat: Org user table - bulk actions (#10504)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: cherish2003 <saicherissh90@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: rkreddy99 <rreddy@e2clouds.com>
Co-authored-by: varun thummar <Varun>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Pradumn Kumar <47187878+Pradumn27@users.noreply.github.com>
Co-authored-by: Richard Poelderl <richard.poelderl@gmail.com>
Co-authored-by: mohammed hussam <hussamkhatib20@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
Co-authored-by: sydwardrae <94979838+sydwardrae@users.noreply.github.com>
Co-authored-by: Janakiram Yellapu <jyellapu@vmware.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: sajanlamsal <saznlamsal@gmail.com>
Co-authored-by: Cherish <88829894+cherish2003@users.noreply.github.com>
Co-authored-by: Danila <daniil.demidovich@gmail.com>
Co-authored-by: Neel Patel <29038590+N-NeelPatel@users.noreply.github.com>
Co-authored-by: Rama Krishna Reddy <49095575+rkreddy99@users.noreply.github.com>
Co-authored-by: Varun Thummar <110765105+VARUN949@users.noreply.github.com>
Co-authored-by: Bhargav <bhargavtenali@gmail.com>
Co-authored-by: Pratik Kumar <kpratik1929@gmail.com>
Co-authored-by: Ritesh Patil <riteshsp2000@gmail.com>
2023-08-15 14:07:38 -07:00
Leo Giovanetti 20c45b5d88 fix: vercel subdomain version (#10790)
* Standarizing org subdomain url

* fix: upgrading vercel subdomain version
2023-08-15 20:44:48 +00:00
3cab37ed8d feat: api return schedule (#10769)
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Omar López <zomars@me.com>
2023-08-15 19:18:25 +00:00
Crowdin Bot 98799b50f0 New Crowdin translations by Github Action 2023-08-15 19:02:03 +00:00
zomars 8071016848 chore: prettier 2023-08-15 11:59:16 -07:00
Crowdin Bot aa5e2ac9a5 New Crowdin translations by Github Action 2023-08-15 14:48:59 +00:00
Keith Williams 06857a4a69 perf: Remove duplicate call for user event types (#10770)
* perf: Remove duplicate call for user events

* Removed unneeded .map() call

* Remove yarn.lock change

* Renamed variable for clarity
2023-08-15 15:46:24 +01:00
Anshuman PandeyandOmar López b0d1720d92 feat: Allow / disallow SEO indexing on booking pages (#10566)
Co-authored-by: Omar López <zomars@me.com>
2023-08-15 00:44:09 +00:00
zomars e148ee7823 chore: Removes all traces of ".prisma/client" 2023-08-14 17:28:43 -07:00
Omar López bb9a039931 fix: paypal setup and key validation on server (#10761)
* fixes: paypal setup and key validation on server

* Renamed for legiblity

* Prefer early returns

* Update getEnabledAppsFromCredentials.ts

* Updated getEnabledAppsFromCredentials references

* Update getEnabledAppsFromCredentials.ts
2023-08-14 17:18:26 -07:00
GitStart-Cal.comandgitstart-calcom 053f93ea0a Extract to a separate test (#10699)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-15 00:57:35 +05:30
2a982c6b6d Fix lower text of new button availability refresh (#10701)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-08-14 14:31:09 -04:00
MehulandUdit Takkar 85490fef4d fix: 150min duration time for scheduling not possibe (#10715)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-14 14:11:34 -04:00
Crowdin Bot 57afd7a2b1 New Crowdin translations by Github Action 2023-08-14 15:05:57 +00:00
Hariom BalharaandJoe Au-Yeung 0f9c3571fe fix: Show messaging app in locations list (#10741)
* Get messaging app as well through query because a messaging app is also a location app

* Fix type error

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-14 15:02:29 +00:00
Peer Richelsen 1b3f676e57 Update package.json
release.yml / release (push) Canceled after 0s
2023-08-14 15:48:05 +02:00
Crowdin Bot 3e17b1c5ef New Crowdin translations by Github Action 2023-08-14 08:40:53 +00:00
Alex van Andel a466e71d4c chore: Simplified date overrides (#10728)
* chore: Simplified date overrides

* Fixed a test that had a date override that wasn't at midnight utc

* Wrote test that showed a fixed Europe/Brussels

* Lint fix
2023-08-14 09:38:08 +01:00
Crowdin Bot 5a7030d277 New Crowdin translations by Github Action 2023-08-14 06:44:24 +00:00
DanilaandUdit Takkar e72c25f02e chore: remove tailwind-scrollbar warning (#10523)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-14 08:41:56 +02:00
Peer Richelsen 06bc5facf3 chore: fix refund i18n message (#10731) 2023-08-13 14:26:25 +01:00
Nafees NazikandHariom Balhara 9c8dc1b92c fix: link escaping in booking page (#10360)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-08-12 21:31:43 +00:00
Joe Au-YeungandPeer Richelsen cfef92a7bc fix: App Install Dropdown Sort Properly [CAL-2285] (#10672)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-08-12 21:30:15 +00:00
Crowdin Bot 26149e2b04 New Crowdin translations by Github Action 2023-08-12 21:23:20 +00:00
ABDERRAHMANI IDRISSI HAMZAandOmar López 3ca43ce3f3 style: Fix text wrapping issue in button (#10725)
Co-authored-by: Omar López <zomars@me.com>
2023-08-12 23:21:09 +02:00
RaghulandRaghul D 7603ea6184 fix: border issue for time slots (#10577)
Co-authored-by: Raghul D <v-raghuld@microsoft.com>
2023-08-12 23:20:49 +02:00
Pradumn KumarandAlex van Andel 9c45da565e feat: add range of dates for availability over-ride (#10462)
* feat: add range of dates for availability over-ride

* chore: changed range select to multiple select

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-08-12 07:22:44 +00:00
Hariom Balhara f84f1f5dda Fix avatar for org in Shell top (#10712) 2023-08-12 00:19:27 +00:00
zomars 07f803e758 fix: paypal build fixes 2023-08-11 17:02:36 -07:00
Leo Giovanetti 268ab882cd Fixing org slug (#10538) 2023-08-11 23:57:22 +00:00
zomars 8052e6f6d4 fix: merge conflict 2023-08-11 16:56:38 -07:00
Anik Dhabal BabuandPeer Richelsen 99e9425257 fix: RTL issues on booking pages + email confirmation (#10526)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-08-11 16:39:25 -07:00
471b2687c4 feat: app paypal payment (#8797)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2023-08-11 23:29:48 +00:00
Peer Richelsen 9f7152c4eb link to org settings (#10718) 2023-08-11 21:27:29 +00:00
Alex van Andel e819a31dd6 v3.1.9
release.yml / release (push) Canceled after 0s
2023-08-11 22:22:34 +01:00
Joe Au-YeungandOmar López 8356cb7d34 Try to list calendars, if not continue (#10720)
Co-authored-by: Omar López <zomars@me.com>
2023-08-11 22:19:26 +01:00
Alex van Andel ceb18167cf fix: Broken team events if a user with the same name exists (#10724)
* fix: Broken team events if a user with the same name exists

* Fix tests + fix usernameList optionality
2023-08-11 22:06:24 +01:00
alannncandAlex van Andel 4d5697f5a5 fix: and enable dynamic booking test (#10642)
* fix and enable dynamic booking test

* remove page pause

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-08-11 13:33:24 -07:00
Shivam Kalra afe180a0ec feat: mailhog fixture (#10606)
* feat: mailhog fixture

* fix: nodemailer to dispatch emails with e2e env

* fix: remove space from email subject

* feat: fixture getFirstEventAsOwner

* feat: assert email subjects
2023-08-11 18:56:27 +00:00
Carina WollendorferandCarinaWolli 851a388853 fix: e2e test for rescheduling overlapping time (#10721)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-11 11:45:53 -07:00
Crowdin Bot 07e20144df New Crowdin translations by Github Action 2023-08-11 07:05:51 +00:00
Crowdin Bot 1535e48ce5 New Crowdin translations by Github Action 2023-08-11 07:02:52 +00:00
Suyash SrivastavaandHariom Balhara 0dce391f46 feat: element call app added (#10585)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-08-11 06:59:20 +00:00
cfe7ea45ec test: Create unit tests for react components in packages/ui/components/form/step (#10442)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-08-11 12:06:55 +05:30
Crowdin Bot c244f0fc8e New Crowdin translations by Github Action 2023-08-11 06:36:30 +00:00
Zain GulbazandCarina Wollendorfer 5301af2e9f fix: If the input type "Name" is selected, the label can't be changed from our default label "Your Name" (#10618)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-08-11 12:03:38 +05:30
Crowdin Bot a4e7ca91b5 New Crowdin translations by Github Action 2023-08-10 21:04:24 +00:00
Leo GiovanettiandOmar López 64e6e1a19a fix: multiple duration getSchedule calls [CAL-2336] (#10709)
Co-authored-by: Omar López <zomars@me.com>
2023-08-10 21:00:46 +00:00
Carina WollendorferandCarinaWolli a6daf17909 fix: allow new booking to overlap old one when rescheduling (#10424)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-10 19:07:57 +00:00
Carina WollendorferandCarinaWolli c7dfa7bc89 fix: add extra security layers for sending messages to attendees (#10636)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-10 11:52:36 -07:00
Alex van Andel 633d85e81b v3.1.8
release.yml / release (push) Canceled after 0s
2023-08-10 19:05:29 +01:00
Leo Giovanetti 49a61207d5 fix: profile banner delay for orgs + small DX chore (#10546)
* Fixing profile banner delay for orgs

* Separating into other useEffect
2023-08-10 17:50:43 +00:00
alannnc f3eb490e3b Remove bookingIds map and conditional using in: [ ] (#10655) 2023-08-10 17:36:29 +00:00
Joe Au-Yeung 2de5f57437 perf: Reduce Outlook Calendar & Availability Response Size (#10570)
* Getting availability reduce the response size

* Add list calendar filter param

* Clean up
2023-08-10 18:28:56 +01:00
Udit Takkar 8bdd835a68 fix: event type filter by team (#10632)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-08-10 18:27:02 +01:00
aku019andasishupadhyay24@gmail.com 9c7d0ea604 docs: fixed grammar in docs (#10669)
* fixed grammar and typos

* fixed grammar and typos

* docs:fixed grammar and typos

---------

Co-authored-by: asishupadhyay24@gmail.com <India@wc2019>
2023-08-10 18:23:25 +01:00
Rohit Saini a3aa53fe6e fix: #10614 - made nodemailer compatible with mailhog smtp server. (#10615)
* mailhog server auth problem solved

* put condition on transport auth

* fixe type-check error
2023-08-10 18:19:21 +01:00
57384eb921 perf: Replace un-needed context fetching (#10657)
* Replace ctx.user.credentials to fetch in their own usecases

* Remove rawavatar from return

* Remove avatar rawAvatar from handler

* Fix fallback avatars

* fix: profile.slug already includes /team

* perf: Deprecate useAvatarQuery hook

* Extract to reusable credential func

* Fix type errors for credentials

* credentialOwner was inferred incorrectly, string non-nullable

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-08-10 18:07:09 +01:00
Leo Giovanetti a153f9627d chore: Relying on CALCOM_ENV (#10098)
* Relying on CALCOM_ENV

* Restoring unneeded changes

* Relyon more on IS_PRODUCTION

* Relyon more on IS_PRODUCTION
2023-08-10 12:32:20 -04:00
GitStart-Cal.comandgitstart-calcom a54539d211 test: Create unit tests for react components in packages/ui/components/form/datepicker (#10671)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-08-10 12:31:52 -04:00
Udit TakkarandCarina Wollendorfer 8d736cfb67 chore: add helper text in round robin (#10674)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-08-10 16:30:49 +00:00
Leo Giovanetti 3c3564f884 fix: copy or view profile URL for org (#10702) 2023-08-10 17:30:33 +01:00
Alex van Andel cc75899b01 fix: count without where clause is total users regardless of selection (#10703) 2023-08-10 17:29:47 +01:00
Udit TakkarandCarinaWolli 1a818ab0c3 fix: reschedule timezone (#10697)
* fix: reschedule timezone

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

* fix: use original rescheduled booking

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

* only when organizer reschedules

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-10 17:05:35 +01:00
Joe Au-YeungandLeo Giovanetti 7dafaf1381 Fix redirect URLs (#10680)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-08-10 12:06:31 -03:00
Crowdin Bot 74f354abd2 New Crowdin translations by Github Action 2023-08-10 14:53:43 +00:00
Anik Dhabal Babu f281131bf6 fix: Improve onboarding Availability (#10685)
* improve onboarding

* Update Schedule.tsx
2023-08-10 14:49:41 +00:00
22b4bcdeeb fix: showing default apps by Cal like Cal-Video in installed apps (#10611)
Co-authored-by: Satya Nishanth <satya@Satyas-MacBook-Pro.local>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-10 16:05:26 +05:30
sean-brydon 5f59ef2acf Transform + usernames to - on token initiation (#10692) 2023-08-10 11:50:43 +02:00
sean-brydon d6d4e246f9 chore: Fix UI NITs (#10691) 2023-08-10 08:49:17 +00:00
Hariom Balharaandzomars a49c34e733 perf: Avoid unmounting of Shell on navigation and thus reduce number of paints (#10646)
Co-authored-by: zomars <zomars@me.com>
2023-08-09 15:54:51 -07:00
37ce8860b5 fix: all booking and duration limits (#10480)
Co-authored-by: rkreddy99 <rreddy@e2clouds.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-08-09 15:51:38 -07:00
Leo GiovanettiandOmar López 4dfbc46247 Fixing fetching team with parent (#10677)
Co-authored-by: Omar López <zomars@me.com>
2023-08-09 21:05:28 +00:00
Carina WollendorferandCarinaWolli 4c38ea3329 fix: DST issue in the month of DST change (#10664)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-08-09 13:31:48 -07:00
John Victor 24e60498f4 fix: add check mark to selected option in destionation calendar selector (#10583) 2023-08-09 20:23:47 +00:00
Udit Takkar fa9a8cca89 fix: add support email (#10605) 2023-08-09 15:57:22 -04:00
Crowdin Bot 38bb01a9f0 New Crowdin translations by Github Action 2023-08-09 19:49:47 +00:00
Omar López 3cf6fef159 chore: disable locale detection (#10676) 2023-08-09 12:46:02 -07:00
MehulandCarina Wollendorfer 26dac453e9 fix: Offset start times event setting resets after save (#10612)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-08-09 15:13:25 -04:00
Crowdin Bot 52db023f76 New Crowdin translations by Github Action 2023-08-09 18:36:34 +00:00
df9e02d138 fix: phone selector dropdown hidden behind logo on booking page (#10617)
Co-authored-by: mohammed gehad <mohammed.gehad.1998@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-08-09 18:32:28 +00:00
Crowdin Bot d7be40f67b New Crowdin translations by Github Action 2023-08-09 16:59:49 +00:00
Joe Au-YeungandLeo Giovanetti 68d2a97e92 fix: Orgs publishing with billing (#10662)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-08-09 12:56:53 -04:00
cde9b24a9b fix: Cleanup after removing connected calendar (#10012)
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-08-09 10:22:51 -04:00
Udit TakkarandSean Brydon cf89598279 fix: Calendar View (#10514)
Co-authored-by: Sean Brydon <sean@cal.com>
2023-08-09 12:19:01 +02:00
alannncandAlex van Andel fcd892bfa0 perf: no wait for session when calling getschedule 10552 cal 2311 (#10607)
* No batching on getting session

* Fix usePublicPage hook to use new router search params

* Move things so getSchedule data can be load as soon as we are rendering BookerComponent

* pre fetch session

* Removed custom code in favour of useTimePreferences

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-08-09 09:55:27 +01:00
Joe Au-Yeung bee5011ec1 fix/ Remove getUserAdminTeams endpoint (#10648) 2023-08-08 21:10:57 -04:00
610 changed files with 14260 additions and 5363 deletions
+9
View File
@@ -1,6 +1,7 @@
# ********** INDEX **********
#
# - APP STORE
# - BASECAMP
# - DAILY.CO VIDEO
# - GOOGLE CALENDAR/MEET/LOGIN
# - HUBSPOT
@@ -20,6 +21,14 @@
# - APP STORE **********************************************************************************************
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
# - BASECAMP
# Used to enable Basecamp integration with Cal.com
# @see https://github.com/calcom/cal.com#obtaining-basecamp-client-id-and-secret
BASECAMP3_CLIENT_ID=
BASECAMP3_CLIENT_SECRET=
BASECAMP3_USER_AGENT=
# - DAILY.CO VIDEO
# Enables Cal Video. to get your key
# 1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
+12 -2
View File
@@ -126,8 +126,7 @@ TWILIO_WHATSAPP_PHONE_NUMBER=
NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally
# Set it to "1" if you need to run E2E tests locally.
NEXT_PUBLIC_IS_E2E=
# Used for internal billing system
@@ -172,6 +171,12 @@ EMAIL_SERVER_PORT=1025
## You will need to provision an App Password.
## @see https://support.google.com/accounts/answer/185833
# EMAIL_SERVER_PASSWORD='<gmail_app_password>'
# Used for E2E for email testing
# Set it to "1" if you need to email checks in E2E tests locally
# Make sure to run mailhog container manually or with `yarn dx`
E2E_TEST_MAILHOG_ENABLED=
# **********************************************************************************************************
# Set the following value to true if you wish to enable Team Impersonation
@@ -215,3 +220,8 @@ PROJECT_ID_VERCEL=
TEAM_ID_VERCEL=
# Get it from: https://vercel.com/account/tokens
AUTH_BEARER_TOKEN_VERCEL=
# - APPLE CALENDAR
# Used for E2E tests on Apple Calendar
E2E_TEST_APPLE_CALENDAR_EMAIL=""
E2E_TEST_APPLE_CALENDAR_PASSWORD=""
@@ -5,16 +5,15 @@ on:
types:
- opened
jobs:
label_on_pr:
runs-on: ubuntu-latest
permissions:
contents: none
issues: read
pull-requests: write
steps:
- name: Apply labels from linked issue to PR
uses: actions/github-script@v5
@@ -1,7 +1,7 @@
# .github/workflows/chromatic.yml
# Workflow name
name: 'Chromatic'
name: "Chromatic"
# Event for the workflow
on:
+3 -3
View File
@@ -4,8 +4,8 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
# Runs "At every 15th minute." (see https://crontab.guru)
- cron: "*/15 * * * *"
jobs:
cron-bookingReminder:
env:
@@ -20,4 +20,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
+2 -2
View File
@@ -5,7 +5,7 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “Every month at 1st (see https://crontab.guru)
# Runs "At 00:00 on day-of-month 1." (see https://crontab.guru)
- cron: "0 0 1 * *"
jobs:
cron-downgradeUsers:
@@ -21,4 +21,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
@@ -4,8 +4,8 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
# Runs "At every 15th minute." (see https://crontab.guru)
- cron: "*/15 * * * *"
jobs:
cron-scheduleEmailReminders:
env:
@@ -20,4 +20,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
@@ -4,8 +4,8 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
# Runs "At every 15th minute." (see https://crontab.guru)
- cron: "*/15 * * * *"
jobs:
cron-scheduleSMSReminders:
env:
@@ -20,4 +20,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
@@ -4,8 +4,8 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru)
- cron: "0,15,30,45 * * * *"
# Runs "At every 15th minute." (see https://crontab.guru)
- cron: "*/15 * * * *"
jobs:
cron-scheduleWhatsappReminders:
env:
@@ -20,4 +20,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
+1 -1
View File
@@ -8,7 +8,7 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs every day (see https://crontab.guru)
# Runs "At 00:00." every day (see https://crontab.guru)
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
+2 -2
View File
@@ -5,7 +5,7 @@ on:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “Every month at 1st (see https://crontab.guru)
# Runs "At 00:00 on day-of-month 1." (see https://crontab.guru)
- cron: "0 0 1 * *"
jobs:
cron-syncAppMeta:
@@ -21,4 +21,4 @@ jobs:
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
-sSf
+3
View File
@@ -41,6 +41,9 @@ jobs:
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
+2
View File
@@ -38,6 +38,8 @@ jobs:
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
+3
View File
@@ -41,6 +41,9 @@ jobs:
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
+4 -1
View File
@@ -40,11 +40,14 @@ jobs:
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
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}}
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
@@ -7,6 +7,8 @@ env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
+2
View File
@@ -7,6 +7,8 @@ env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
+2 -2
View File
@@ -8,7 +8,7 @@ on: # yamllint disable-line rule:truthy
workflow_dispatch:
inputs:
RELEASE_TAG:
description: 'v{Major}.{Minor}.{Patch}'
description: "v{Major}.{Minor}.{Patch}"
jobs:
release:
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v3
- name: "Determine tag"
run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV"
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
- name: "Run remote release workflow"
uses: "actions/github-script@v6"
+4 -4
View File
@@ -29,11 +29,11 @@ jobs:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
@@ -41,7 +41,7 @@ jobs:
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
with:
header: pr-title-lint-error
message: |
Thank you for following the naming conventions! 🙏
Thank you for following the naming conventions! 🙏
+1
View File
@@ -1,6 +1,7 @@
name: Submodule Sync
on:
schedule:
# Runs "At minute 15 past every 4th hour." (see https://crontab.guru)
- cron: "15 */4 * * *"
workflow_dispatch: ~
jobs:
+1 -1
View File
@@ -40,4 +40,4 @@ vscode:
- bradlc.vscode-tailwindcss
- ban.spellright
- stripe.vscode-stripe
- Prisma.prisma
- Prisma.prisma
+18 -5
View File
@@ -51,9 +51,9 @@
# Scheduling infrastructure for absolutely everyone
The open source Calendly alternative. You are in charge
of your own data, workflow and appearance.
of your own data, workflow, and appearance.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customizations.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes, and even calls with our families. However, most tools are very limited in terms of control and customization.
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
@@ -98,7 +98,7 @@ Meet our sales team for any commercial inquiries.
## Stay Up-to-Date
Cal.com officially launched as v.1.0 on 15th of September 2021 and we've come a long way so far. Watch **releases** of this repository to be notified of future updates:
Cal.com officially launched as v.1.0 on the 15th of September 2021 and we've come a long way so far. Watch **releases** of this repository to be notified of future updates:
![cal-star-github](https://user-images.githubusercontent.com/8019099/154853944-a9e3c999-3da3-4048-b149-b4f73893c6fb.gif)
@@ -128,7 +128,7 @@ Here is what you need to be able to run Cal.com.
git clone https://github.com/calcom/cal.com.git
```
> If you are on windows, run the following command on `gitbash` with admin privileges: <br> > `git clone -c core.symlinks=true https://github.com/calcom/cal.com.git` <br>
> If you are on Windows, run the following command on `gitbash` with admin privileges: <br> > `git clone -c core.symlinks=true https://github.com/calcom/cal.com.git` <br>
> See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
1. Go to the project folder
@@ -221,6 +221,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
> **_NOTE:_** Required when `E2E_TEST_MAILHOG_ENABLED` is "1"
```sh
docker pull mailhog/mailhog
@@ -254,7 +255,7 @@ Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If
# In a terminal just run:
yarn test-e2e
# To open last HTML report run:
# To open the last HTML report run:
yarn playwright show-report test-results/reports/playwright-html-report
```
@@ -461,6 +462,18 @@ following
4. Now paste the API key to your `.env` file into the `DAILY_API_KEY` field in your `.env` file.
5. If you have the [Daily Scale Plan](https://daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
### Obtaining Basecamp Client ID and Secret
1. Visit the [37 Signals Integrations Dashboard](launchpad.37signals.com/integrations) and sign in.
2. Register a new application by clicking the Register one now link.
3. Fill in your company details.
4. Select Basecamp 4 as the product to integrate with.
5. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/basecamp3/callback` replacing Cal.com URL with the URI at which your application runs.
6. Click on done and copy the Client ID and secret into the `BASECAMP3_CLIENT_ID` and `BASECAMP3_CLIENT_SECRET` fields.
7. Set the `BASECAMP3_CLIENT_SECRET` env variable to `{your_domain} ({support_email})`.
For example, `Cal.com (support@cal.com)`.
### Obtaining HubSpot Client ID and Secret
1. Open [HubSpot Developer](https://developer.hubspot.com/) and sign into your account, or create a new one.
+2 -1
View File
@@ -2,6 +2,7 @@ import type { NextMiddleware } from "next-api-middleware";
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { isAdminGuard } from "~/lib/utils/isAdmin";
@@ -17,7 +18,7 @@ export const dateNotInPast = function (date: Date) {
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
const { prisma, isCustomPrisma, isAdmin } = req;
const hasValidLicense = await checkLicense(prisma);
if (!hasValidLicense && process.env.NODE_ENV !== "development")
if (!hasValidLicense && IS_PRODUCTION)
return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" });
// If the user is an admin and using a license key (from customPrisma), skip the apiKey check.
if (isCustomPrisma && isAdmin) {
+3
View File
@@ -67,6 +67,7 @@ const schemaEventTypeCreateParams = z
seatsPerTimeSlot: z.number().optional(),
seatsShowAttendees: z.boolean().optional(),
bookingFields: eventTypeBookingFields.optional(),
scheduleId: z.number().optional(),
})
.strict();
@@ -85,6 +86,7 @@ const schemaEventTypeEditParams = z
seatsPerTimeSlot: z.number().optional(),
seatsShowAttendees: z.boolean().optional(),
bookingFields: eventTypeBookingFields.optional(),
scheduleId: z.number().optional(),
})
.strict();
@@ -98,6 +100,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
position: true,
userId: true,
teamId: true,
scheduleId: true,
eventName: true,
timeZone: true,
periodType: true,
+1 -1
View File
@@ -29,7 +29,7 @@ enum locales {
RO = "ro",
NL = "nl",
PT_BR = "pt-BR",
ES_419 = "es-419",
// ES_419 = "es-419", // Disabled until Crowdin reaches at least 80% completion
KO = "ko",
JA = "ja",
PL = "pl",
+39 -3
View File
@@ -1,9 +1,11 @@
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
import { checkPermissions as canAccessTeamEventOrThrow } from "~/pages/api/teams/[teamId]/_auth-middleware";
import getCalLink from "../_utils/getCalLink";
@@ -42,7 +44,8 @@ import getCalLink from "../_utils/getCalLink";
export async function getHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const event_type = await prisma.eventType.findUnique({
const eventType = await prisma.eventType.findUnique({
where: { id },
include: {
customInputs: true,
@@ -51,10 +54,43 @@ export async function getHandler(req: NextApiRequest) {
owner: { select: { username: true, id: true } },
},
});
await checkPermissions(req, eventType);
const link = event_type ? getCalLink(event_type) : null;
const link = eventType ? getCalLink(eventType) : null;
// user.defaultScheduleId doesn't work the same for team events.
if (!eventType?.scheduleId && eventType?.userId && !eventType?.teamId) {
const user = await prisma.user.findUniqueOrThrow({
where: {
id: eventType.userId,
},
select: {
defaultScheduleId: true,
},
});
eventType.scheduleId = user.defaultScheduleId;
}
return { event_type: schemaEventTypeReadPublic.parse({ ...event_type, link }) };
// TODO: eventType when not found should be a 404
// but API consumers may depend on the {} behaviour.
return { event_type: schemaEventTypeReadPublic.parse({ ...eventType, link }) };
}
type BaseEventTypeCheckPermissions = {
userId: number | null;
teamId: number | null;
};
async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
req: NextApiRequest,
eventType: (T & Partial<Omit<T, keyof BaseEventTypeCheckPermissions>>) | null
) {
if (req.isAdmin) return true;
if (eventType?.teamId) {
req.query.teamId = String(eventType.teamId);
await canAccessTeamEventOrThrow(req, "MEMBER");
}
if (eventType?.userId === req.userId) return true; // is owner.
throw new HttpError({ statusCode: 403, message: "Forbidden" });
}
export default defaultResponder(getHandler);
@@ -52,6 +52,9 @@ import checkTeamEventEditPermission from "../_utils/checkTeamEventEditPermission
* slug:
* type: string
* description: Unique slug for the event type
* scheduleId:
* type: number
* description: The ID of the schedule for this event type
* hosts:
* type: array
* items:
+71 -18
View File
@@ -1,8 +1,8 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import type { PrismaClient } from "@calcom/prisma/client";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
@@ -35,20 +35,12 @@ import getCalLink from "./_utils/getCalLink";
* description: No event types were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma } = req;
const args: Prisma.EventTypeFindManyArgs = {
where: { userId },
};
/** Only admins can query other users */
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 401, message: "ADMIN required" });
if (isAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
args.where = { userId: { in: userIds } };
}
const { userId, prisma } = req;
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
const data = await prisma.eventType.findMany({
...args,
where: {
userId: { in: userIds },
},
include: {
customInputs: true,
team: { select: { slug: true } },
@@ -56,13 +48,74 @@ async function getHandler(req: NextApiRequest) {
owner: { select: { username: true, id: true } },
},
});
// this really should return [], but backwards compatibility..
if (data.length === 0) new HttpError({ statusCode: 404, message: "No event types were found" });
return {
event_types: data.map((eventType) => {
const link = getCalLink(eventType);
return schemaEventTypeReadPublic.parse({ ...eventType, link });
}),
event_types: (await defaultScheduleId<(typeof data)[number]>({ eventTypes: data, prisma, userIds })).map(
(eventType) => {
const link = getCalLink(eventType);
return schemaEventTypeReadPublic.parse({ ...eventType, link });
}
),
};
}
// TODO: Extract & reuse.
function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
/** Guard: Only admins can query other users */
if (!isAdmin) {
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
}
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
return Array.isArray(userIdOrUserIds) ? userIdOrUserIds : [userIdOrUserIds];
}
type DefaultScheduleIdEventTypeBase = {
scheduleId: number | null;
userId: number | null;
};
// If an eventType is given w/o a scheduleId
// Then we associate the default user schedule id to the eventType
async function defaultScheduleId<T extends DefaultScheduleIdEventTypeBase>({
prisma,
eventTypes,
userIds,
}: {
prisma: PrismaClient;
eventTypes: (T & Partial<Omit<T, keyof DefaultScheduleIdEventTypeBase>>)[];
userIds: number[];
}) {
// there is no event types without a scheduleId, skip the user query
if (eventTypes.every((eventType) => eventType.scheduleId)) return eventTypes;
const users = await prisma.user.findMany({
where: {
id: {
in: userIds,
},
},
select: {
id: true,
defaultScheduleId: true,
},
});
if (!users.length) {
return eventTypes;
}
const defaultScheduleIds = users.reduce((result, user) => {
result[user.id] = user.defaultScheduleId;
return result;
}, {} as { [x: number]: number | null });
return eventTypes.map((eventType) => {
// realistically never happens, userId should't be null on personal event types.
if (!eventType.userId) return eventType;
return {
...eventType,
scheduleId: eventType.scheduleId || defaultScheduleIds[eventType.userId],
};
});
}
export default defaultResponder(getHandler);
+4
View File
@@ -60,6 +60,9 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* hidden:
* type: boolean
* description: If the event type should be hidden from your public booking page
* scheduleId:
* type: number
* description: The ID of the schedule for this event type
* position:
* type: integer
* description: The position of the event type on the public booking page
@@ -178,6 +181,7 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* position: 0
* eventName: null
* timeZone: null
* scheduleId: 5
* periodType: UNLIMITED
* periodStartDate: 2023-02-15T08:46:16.000Z
* periodEndDate: 2023-0-15T08:46:16.000Z
@@ -59,7 +59,7 @@ async function getHandler(req: NextApiRequest) {
};
const data = await prisma.eventType.findMany(args);
return { event_types: data.map((attendee) => schemaEventTypeReadPublic.parse(attendee)) };
return { event_types: data.map((eventType) => schemaEventTypeReadPublic.parse(eventType)) };
}
export default defaultResponder(getHandler);
+1 -1
View File
@@ -40,7 +40,7 @@ export async function getHandler(req: NextApiRequest) {
// If user is not ADMIN, return only his data.
if (!isAdmin) where.id = userId;
const [total, data] = await prisma.$transaction([
prisma.user.count(),
prisma.user.count({ where }),
prisma.user.findMany({ where, take, skip }),
]);
const users = schemaUsersReadPublic.parse(data);
+3 -3
View File
@@ -1,5 +1,5 @@
# Set the version of docker compose to use
version: '3.9'
version: "3.9"
# The containers that compose the project
services:
@@ -8,8 +8,8 @@ services:
restart: always
container_name: integration-tests-prisma
ports:
- '5433:5432'
- "5433:5432"
environment:
POSTGRES_USER: prisma
POSTGRES_PASSWORD: prisma
POSTGRES_DB: tests
POSTGRES_DB: tests
+1
View File
@@ -15,6 +15,7 @@ module.exports = {
"storybook-addon-rtl-direction",
"storybook-react-i18next",
"storybook-addon-next",
"storybook-addon-next-router",
/*{
name: "storybook-addon-next",
options: {
+4 -4
View File
@@ -1,9 +1,9 @@
<script>
window.global = window;
window.isEmbed = ()=> {
return location.search.includes("embed=")
}
window.isEmbed = () => {
return location.search.includes("embed=");
};
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@500&display=swap");
</style>
+8
View File
@@ -1,4 +1,5 @@
import { addDecorator } from "@storybook/react";
import { AppRouterContext } from "next/dist/shared/lib/app-router-context";
import { I18nextProvider } from "react-i18next";
import "../styles/globals.css";
@@ -13,6 +14,13 @@ export const parameters = {
date: /Date$/,
},
},
nextRouter: {
pathname: "/",
asPath: "/",
query: {},
push() {},
Provider: AppRouterContext.Provider,
},
};
addDecorator((storyFn) => (
+16 -6
View File
@@ -1,11 +1,21 @@
import { Meta } from '@storybook/addon-docs';
import { Meta } from "@storybook/addon-docs";
<Meta title="Introduction" />
<div className="text-center flex flex-col items-center">
<h1 style={{marginBottom: '24px', marginTop: '36px'}}>Welcome to Cal.com UI</h1>
<p>This is the beginning of our storybook improvements to match Figma as close as possible. Like our Figma library, we will be adding more components as we go along.</p>
<p>Our <a href="https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/%E2%9D%96-Cal-DS" target="_blank">Figma</a> library is avalible for anyone to view and use. If you have any questions or concerns, please reach out to the design team.</p>
<div className="flex flex-col items-center text-center">
<h1 style={{ marginBottom: "24px", marginTop: "36px" }}>Welcome to Cal.com UI</h1>
<p>
This is the beginning of our storybook improvements to match Figma as close as possible. Like our Figma
library, we will be adding more components as we go along.
</p>
<p>
Our{" "}
<a href="https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/%E2%9D%96-Cal-DS" target="_blank">
Figma
</a>{" "}
library is avalible for anyone to view and use. If you have any questions or concerns, please reach out to
the design team.
</p>
</div>
<img src="/sb-cover.jpg" />
<img src="/sb-cover.jpg" />
+1
View File
@@ -23,6 +23,7 @@
"next": "^13.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook-addon-next-router": "^4.0.2",
"storybook-addon-rtl-direction": "^0.0.19"
},
"devDependencies": {
+53 -55
View File
@@ -1,8 +1,8 @@
@import url("../../../packages/features/calendars/weeklyview/styles/styles.css");
.sbdocs {
font-family: 'Inter var' !important;
padding: 0!important;
font-family: "Inter var" !important;
padding: 0 !important;
}
#docs-root {
@@ -21,8 +21,8 @@
font-weight: 500;
border: none;
border-top: 2px solid rgba(0, 0, 0, 0.12);
padding-top: 12px!important;
padding-bottom: 12px!important;
padding-top: 12px !important;
padding-bottom: 12px !important;
margin: 82px 0 0 0;
}
@@ -37,7 +37,7 @@
}
/** Docs table **/
.custom-args-wrapper{
.custom-args-wrapper {
max-height: 400px;
overflow-y: scroll;
overflow-x: hidden;
@@ -45,7 +45,7 @@
}
.docblock-argstable-body {
box-shadow: none!important;
box-shadow: none !important;
font-size: 14px;
}
@@ -64,7 +64,7 @@
.docblock-argstable-body div p,
.docblock-argstable-body div span {
color: #8F8F8F !important;
color: #8f8f8f !important;
}
/** Custom components **/
@@ -83,8 +83,8 @@
}
.story-title h1 span {
color: #9CA3AF;
font-family: 'Inter var';
color: #9ca3af;
font-family: "Inter var";
font-weight: normal;
display: inline-block;
margin-left: 8px;
@@ -96,7 +96,7 @@
}
.examples {
background-color: #F9FAFB;
background-color: #f9fafb;
padding: 20px;
display: flex;
flex-direction: column;
@@ -111,14 +111,14 @@
}
.examples-title {
color: #8F8F8F;
color: #8f8f8f;
font-size: 14px;
margin-bottom: 30px;
}
.examples-item-title {
font-size: 12px;
color: #8F8F8F;
color: #8f8f8f;
margin-bottom: 12px;
display: block;
}
@@ -130,7 +130,7 @@
.examples-footnote p,
.examples-footnote ul,
.examples-footnote li {
color: #8F8F8F;
color: #8f8f8f;
font-size: 14px;
}
@@ -139,11 +139,11 @@
}
.examples-footnote li {
margin: 0!important;
margin: 0 !important;
}
.story-note {
background-color: #F9FAFB;
background-color: #f9fafb;
font-size: 14px;
padding: 20px;
margin-bottom: 12px;
@@ -161,12 +161,12 @@
box-shadow: none !important;
margin: 0 !important;
border: none !important;
border-radius: none!important;
border-radius: none !important;
}
.docs-story > div:first-child {
padding: 0!important;
margin: 0!important;
padding: 0 !important;
margin: 0 !important;
}
.docs-story .innerZoomElementWrapper > div {
@@ -174,7 +174,7 @@
}
.sb-main-padded {
padding: 0!important;
padding: 0 !important;
}
@media screen and (max-width: 1200px) {
@@ -198,74 +198,72 @@
:root {
/* background */
--cal-bg-emphasis: #E5E7EB;
--cal-bg-emphasis: #e5e7eb;
--cal-bg: white;
--cal-bg-subtle: #F3F4F6;
--cal-bg-muted: #F9FAFB;
--cal-bg-subtle: #f3f4f6;
--cal-bg-muted: #f9fafb;
--cal-bg-inverted: #111827;
/* background -> components*/
--cal-bg-info: #DEE9FC;
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-info: #dee9fc;
--cal-bg-success: #e2fbe8;
--cal-bg-attention: #fceed8;
--cal-bg-error: #f9e3e2;
--cal-bg-dark-error: #752522;
/* Borders */
--cal-border-emphasis:#9CA3AF;
--cal-border: #D1D5DB;
--cal-border-subtle:#E5E7EB;
--cal-border-muted:#F3F4F6;
--cal-border-emphasis: #9ca3af;
--cal-border: #d1d5db;
--cal-border-subtle: #e5e7eb;
--cal-border-muted: #f3f4f6;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #111827;
--cal-text:#374151;
--cal-text-subtle:#6B7280;
--cal-text-muted:#9CA3AF;
--cal-text-inverted:white;
--cal-text: #374151;
--cal-text-subtle: #6b7280;
--cal-text-muted: #9ca3af;
--cal-text-inverted: white;
/* Content/Text -> components */
--cal-text-info:#253985;
--cal-text-success:#285231;
--cal-text-attention:#73321B;
--cal-text-error:#752522;
--cal-text-info: #253985;
--cal-text-success: #285231;
--cal-text-attention: #73321b;
--cal-text-error: #752522;
/* Brand shinanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand:#111827;
--cal-brand-emphasis:#101010;
--cal-brand: #111827;
--cal-brand-emphasis: #101010;
--cal-brand-text: white;
}
.dark {
/* background */
--cal-bg-emphasis: #2B2B2B;
--cal-bg-emphasis: #2b2b2b;
--cal-bg: #101010;
--cal-bg-subtle: #2B2B2B;
--cal-bg-muted: #1C1C1C;
--cal-bg-inverted: #F3F4F6;
--cal-bg-subtle: #2b2b2b;
--cal-bg-muted: #1c1c1c;
--cal-bg-inverted: #f3f4f6;
/* background -> components*/
--cal-bg-info: #DEE9FC;
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-info: #dee9fc;
--cal-bg-success: #e2fbe8;
--cal-bg-attention: #fceed8;
--cal-bg-error: #f9e3e2;
--cal-bg-dark-error: #752522;
/* Borders */
--cal-border-emphasis: #575757;
--cal-border: #444444;
--cal-border-subtle: #2B2B2B;
--cal-border-muted: #1C1C1C;
--cal-border-subtle: #2b2b2b;
--cal-border-muted: #1c1c1c;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #F3F4F6;
--cal-text: #D6D6D6;
--cal-text-emphasis: #f3f4f6;
--cal-text: #d6d6d6;
--cal-text-subtle: #757575;
--cal-text-muted: #575757;
--cal-text-inverted: #101010;
@@ -273,7 +271,7 @@
/* Content/Text -> components */
--cal-text-info: #253985;
--cal-text-success: #285231;
--cal-text-attention: #73321B;
--cal-text-attention: #73321b;
--cal-text-error: #752522;
/* Brand shenanigans
+13 -12
View File
@@ -2,8 +2,8 @@ html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
Droid Sans, Helvetica Neue, sans-serif;
}
a {
@@ -21,7 +21,7 @@ a {
}
.swagger-ui .opblock .opblock-summary {
display: grid;
flex-direction: column;
flex-direction: column;
}
.opblock-summary-path {
flex-shrink: 0;
@@ -43,22 +43,22 @@ a {
font-size: 22px;
}
.swagger-ui .scheme-container {
padding: 14px 0;
padding: 14px 0;
}
.swagger-ui .info {
margin: 10px 0;
margin: 10px 0;
}
.swagger-ui .auth-wrapper {
margin: 10px 0;
}
.swagger-ui .authorization__btn {
display: none;
display: none;
}
.swagger-ui .opblock {
margin: 0 0 5px;
}
button.opblock-summary-control > svg {
display: none;
display: none;
}
.swagger-ui .filter .operation-filter-input {
border: 2px solid #d8dde7;
@@ -73,15 +73,16 @@ a {
.swagger-ui .info .title small {
top: 5px;
}
.swagger-ui a.nostyle, .swagger-ui a.nostyle:visited {
.swagger-ui a.nostyle,
.swagger-ui a.nostyle:visited {
width: 100%;
}
div.request-snippets > div.curl-command > div:nth-child(1) {
overscroll-behavior: contain;
overflow-x: scroll;
}
.swagger-ui .opblock-body pre.microlight {
font-size: 9px;
.swagger-ui .opblock-body pre.microlight {
font-size: 9px;
}
.swagger-ui table tbody tr td {
padding: 0px 0 0;
@@ -91,7 +92,7 @@ a {
font-size: 12px;
}
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > tbody > tr {
display: flex;
display: flex;
width: 100vw;
flex-direction: column;
font-size: 60%;
@@ -99,4 +100,4 @@ a {
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > thead > tr {
display: none;
}
}
}
+37 -13
View File
@@ -1,27 +1,51 @@
import { lookup } from "bcp-47-match";
import { useSession } from "next-auth/react";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import { trpc } from "@calcom/trpc/react";
export function useViewerI18n() {
return trpc.viewer.public.i18n.useQuery(undefined, {
staleTime: Infinity,
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
* We intend to not cache i18n query
**/
trpc: {
context: { skipBatch: true },
},
});
function useViewerI18n(locale: string) {
return trpc.viewer.public.i18n.useQuery(
{ locale, CalComVersion: CALCOM_VERSION },
{
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
**/
trpc: {
context: { skipBatch: true },
},
}
);
}
function useClientLocale(locales: string[]) {
const session = useSession();
// If the user is logged in, use their locale
if (session.data?.user.locale) return session.data.user.locale;
// If the user is not logged in, use the browser locale
if (typeof window !== "undefined") {
// This is the only way I found to ensure the prefetched locale is used on first render
// FIXME: Find a better way to pick the best matching locale from the browser
return lookup(locales, window.navigator.language) || window.navigator.language;
}
// If the browser is not available, use English
return "en";
}
export function useClientViewerI18n(locales: string[]) {
const clientLocale = useClientLocale(locales);
return useViewerI18n(clientLocale);
}
/**
* Auto-switches locale client-side to the logged in user's preference
*/
const I18nLanguageHandler = () => {
const I18nLanguageHandler = (props: { locales: string[] }) => {
const { locales } = props;
const { i18n } = useTranslation("common");
const locale = useViewerI18n().data?.locale || i18n.language;
const locale = useClientViewerI18n(locales).data?.locale || i18n.language;
useEffect(() => {
// bail early when i18n = {}
+2 -2
View File
@@ -6,7 +6,7 @@ import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { WEBAPP_URL, IS_CALCOM } from "@calcom/lib/constants";
import { IS_CALCOM, WEBAPP_URL } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import type { AppProps } from "@lib/app-providers";
@@ -72,7 +72,7 @@ function PageWrapper(props: AppProps) {
}
{...seoConfig.defaultNextSeo}
/>
<I18nLanguageHandler />
<I18nLanguageHandler locales={props.router.locales || []} />
<Script
nonce={nonce}
id="page-status"
@@ -112,7 +112,7 @@ export const InstallAppButtonChild = ({
);
}}>
<p>
{team.name} {isInstalled && `(${t("installed")})`}
{t(team.name)} {isInstalled && `(${t("installed")})`}
</p>
</DropdownItem>
);
@@ -3,7 +3,7 @@ import { useRouter } from "next/navigation";
import type { ComponentProps } from "react";
import React from "react";
import Shell from "@calcom/features/shell/Shell";
import { ShellMain } from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen } from "@calcom/ui";
import { AlertCircle } from "@calcom/ui/components/icon";
@@ -12,7 +12,7 @@ type AppsLayoutProps = {
children: React.ReactNode;
actions?: (className?: string) => JSX.Element;
emptyStore?: boolean;
} & Omit<ComponentProps<typeof Shell>, "actions">;
} & Omit<ComponentProps<typeof ShellMain>, "actions">;
export default function AppsLayout({ children, actions, emptyStore, ...rest }: AppsLayoutProps) {
const { t } = useLocale();
@@ -22,7 +22,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
if (session.status === "loading") return <></>;
return (
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
<ShellMain {...rest} actions={actions?.("block")} hideHeadingOnMobile>
<div className="flex flex-col xl:flex-row">
<main className="w-full">
{emptyStore ? (
@@ -38,7 +38,6 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
)}
</main>
</div>
</Shell>
</ShellMain>
);
}
export const getLayout = (page: React.ReactElement) => <AppsLayout>{page}</AppsLayout>;
@@ -368,7 +368,7 @@ function BookingListItem(booking: BookingItemProps) {
{t("error_collecting_card")}
</Badge>
) : booking.paid ? (
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
<Badge className="ltr:mr-2 rtl:ml-2" variant="green" data-testid="paid_badge">
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
</Badge>
) : null}
@@ -35,6 +35,7 @@ import {
Tooltip,
} from "@calcom/ui";
import { Copy, Edit } from "@calcom/ui/components/icon";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/web/constants";
import RequiresConfirmationController from "./RequiresConfirmationController";
@@ -286,36 +287,38 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}}>
{/* Textfield has some margin by default we remove that so we can keep consitant aligment */}
<div className="lg:-ml-2">
<TextField
disabled
name="hashedLink"
label={t("private_link_label")}
data-testid="generated-hash-url"
labelSrOnly
type="text"
hint={t("private_link_hint")}
defaultValue={placeholderHashedLink}
addOnSuffix={
<Tooltip content={eventType.hashedLink ? t("copy_to_clipboard") : t("enabled_after_update")}>
<Button
color="minimal"
size="sm"
type="button"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
aria-label="copy link"
onClick={() => {
navigator.clipboard.writeText(placeholderHashedLink);
if (eventType.hashedLink) {
showToast(t("private_link_copied"), "success");
} else {
showToast(t("enabled_after_update_description"), "warning");
}
}}>
<Copy className="h-4 w-4" />
</Button>
</Tooltip>
}
/>
{!IS_VISUAL_REGRESSION_TESTING && (
<TextField
disabled
name="hashedLink"
label={t("private_link_label")}
data-testid="generated-hash-url"
labelSrOnly
type="text"
hint={t("private_link_hint")}
defaultValue={placeholderHashedLink}
addOnSuffix={
<Tooltip content={eventType.hashedLink ? t("copy_to_clipboard") : t("enabled_after_update")}>
<Button
color="minimal"
size="sm"
type="button"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
aria-label="copy link"
onClick={() => {
navigator.clipboard.writeText(placeholderHashedLink);
if (eventType.hashedLink) {
showToast(t("private_link_copied"), "success");
} else {
showToast(t("enabled_after_update_description"), "warning");
}
}}>
<Copy className="h-4 w-4" />
</Button>
</Tooltip>
}
/>
)}
</div>
</SettingsToggle>
<hr className="border-subtle" />
@@ -338,6 +341,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
toggleGuests(false);
formMethods.setValue("requiresConfirmation", false);
setRequiresConfirmation(false);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("seatsPerTimeSlot", 2);
} else {
formMethods.setValue("seatsPerTimeSlot", null);
@@ -138,7 +138,7 @@ const EventTypeScheduleDetails = memo(
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
{!!schedule?.id && !schedule.isManaged && (
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
<Button
href={`/availability/${schedule.id}`}
disabled={isLoading}
@@ -202,6 +202,15 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
isManaged: false,
});
}
// We push the selected schedule from the event type if it's not part of the list response. This happens if the user is an admin but not the schedule owner.
else if (eventType.schedule && !schedules.find((schedule) => schedule.id === eventType.schedule)) {
options.push({
value: eventType.schedule,
label: eventType.scheduleName ?? t("default_schedule_name"),
isDefault: false,
isManaged: false,
});
}
setOptions(options);
@@ -13,6 +13,7 @@ import type { DurationType } from "@calcom/lib/convertToNewDurationType";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { ascendingLimitKeys, intervalLimitKeyToUnit } from "@calcom/lib/intervalLimit";
import type { PeriodType } from "@calcom/prisma/enums";
import type { IntervalLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
@@ -462,7 +463,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
type="number"
{...offsetStartLockedProps}
label={t("offset_start")}
defaultValue={eventType.offsetStart}
{...formMethods.register("offsetStart")}
addOnSuffix={<>{t("minutes")}</>}
hint={t("offset_start_description", {
@@ -477,11 +477,9 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
type IntervalLimitsKey = keyof IntervalLimit;
const intervalOrderKeys = ["PER_DAY", "PER_WEEK", "PER_MONTH", "PER_YEAR"] as const;
const INTERVAL_LIMIT_OPTIONS = intervalOrderKeys.map((key) => ({
const INTERVAL_LIMIT_OPTIONS = ascendingLimitKeys.map((key) => ({
value: key as keyof IntervalLimit,
label: `Per ${key.split("_")[1].toLocaleLowerCase()}`,
label: `Per ${intervalLimitKeyToUnit(key)}`,
}));
type IntervalLimitItemProps = {
@@ -590,8 +588,8 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
Object.entries(currentIntervalLimits)
.sort(([limitKeyA], [limitKeyB]) => {
return (
intervalOrderKeys.indexOf(limitKeyA as IntervalLimitsKey) -
intervalOrderKeys.indexOf(limitKeyB as IntervalLimitsKey)
ascendingLimitKeys.indexOf(limitKeyA as IntervalLimitsKey) -
ascendingLimitKeys.indexOf(limitKeyB as IntervalLimitsKey)
);
})
.map(([key, value]) => {
@@ -12,6 +12,7 @@ import { z } from "zod";
import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import cx from "@calcom/lib/classNames";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -116,6 +117,8 @@ export const EventSetupTab = (
const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
const orgBranding = useOrgBranding();
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const locationOptions = props.locationOptions.map((locationOption) => {
const options = locationOption.options.filter((option) => {
@@ -129,10 +132,12 @@ export const EventSetupTab = (
};
});
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
value: mins,
label: t("multiple_duration_mins", { count: mins }),
}));
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 150, 180].map(
(mins) => ({
value: mins,
label: t("multiple_duration_mins", { count: mins }),
})
);
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
MultiValue<{
@@ -383,6 +388,9 @@ export const EventSetupTab = (
const lengthLockedProps = shouldLockDisableProps("length");
const descriptionLockedProps = shouldLockDisableProps("description");
const urlPrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<div>
@@ -411,10 +419,10 @@ export const EventSetupTab = (
defaultValue={eventType.slug}
addOnLeading={
<>
{CAL_URL?.replace(/^(https?:|)\/\//, "")}/
{urlPrefix}/
{!isManagedEventType
? team
? "team/" + team.slug
? (orgBranding ? "" : "team/") + team.slug
: eventType.users[0].username
: t("username_placeholder")}
/
@@ -501,6 +509,8 @@ export const EventSetupTab = (
<SettingsToggle
title={t("allow_booker_to_select_duration")}
checked={multipleDuration !== undefined}
disabled={seatsEnabled}
tooltip={seatsEnabled ? t("seat_options_doesnt_multiple_durations") : undefined}
onCheckedChange={() => {
if (multipleDuration !== undefined) {
setMultipleDuration(undefined);
@@ -1,3 +1,5 @@
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef } from "react";
import type { ComponentProps } from "react";
@@ -99,6 +101,7 @@ const CheckedHostField = ({
isFixed,
value,
onChange,
helperText,
...rest
}: {
labelText: string;
@@ -107,6 +110,7 @@ const CheckedHostField = ({
value: { isFixed: boolean; userId: number }[];
onChange?: (options: { isFixed: boolean; userId: number }[]) => void;
options?: Options<CheckedSelectOption>;
helperText?: React.ReactNode | string;
} & Omit<Partial<ComponentProps<typeof CheckedTeamSelect>>, "onChange" | "value">) => {
return (
<div className="bg-muted flex flex-col space-y-5 p-4">
@@ -136,11 +140,24 @@ const CheckedHostField = ({
placeholder={placeholder}
{...rest}
/>
{helperText && <p className="text-subtle text-sm">{helperText}</p>}
</div>
</div>
);
};
const FixedHostHelper = (
<Trans i18nKey="fixed_host_helper">
Add anyone who needs to attend the event.
<Link
className="underline underline-offset-2"
target="_blank"
href="https://cal.com/docs/enterprise-features/teams/round-robin-scheduling#fixed-hosts">
Learn more
</Link>
</Trans>
);
const RoundRobinHosts = ({
teamMembers,
value,
@@ -167,6 +184,7 @@ const RoundRobinHosts = ({
value={value}
placeholder={t("add_fixed_hosts")}
labelText={t("fixed_hosts")}
helperText={FixedHostHelper}
/>
<CheckedHostField
options={teamMembers.sort(sortByLabel)}
@@ -175,6 +193,7 @@ const RoundRobinHosts = ({
isFixed={false}
placeholder={t("add_attendees")}
labelText={t("round_robin_hosts")}
helperText={t("round_robin_helper")}
/>
</>
);
@@ -20,10 +20,11 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
const { t } = useLocale();
const { nextStep } = props;
const scheduleId = defaultScheduleId === null ? undefined : defaultScheduleId;
const queryAvailability = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId: defaultScheduleId! },
{ scheduleId },
{
enabled: !!defaultScheduleId,
enabled: !!scheduleId,
}
);
@@ -12,9 +12,6 @@ const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonati
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.viewer.me.invalidate();
},
async onSettled() {
await utils.viewer.public.i18n.invalidate();
},
});
return (
@@ -1,4 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
// eslint-disable-next-line no-restricted-imports
import { noop } from "lodash";
import { useCallback, useState } from "react";
import { Controller, FormProvider, useForm, useFormState } from "react-hook-form";
@@ -1,4 +1,5 @@
import classNames from "classnames";
// eslint-disable-next-line no-restricted-imports
import { debounce, noop } from "lodash";
import { useSession } from "next-auth/react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@ -50,7 +51,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
const pathname = usePathname();
const router = useRouter();
const { t } = useLocale();
const { data: session, update } = useSession();
const { update } = useSession();
const {
currentUsername,
setCurrentUsername = noop,
@@ -94,7 +95,6 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
debouncedApiCall(inputUsernameValue);
}, [debouncedApiCall, inputUsernameValue]);
const utils = trpc.useContext();
const updateUsername = trpc.viewer.updateProfile.useMutation({
onSuccess: async () => {
onSuccessMutation && (await onSuccessMutation());
@@ -104,9 +104,6 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
onError: (error) => {
onErrorMutation && onErrorMutation(error);
},
async onSettled() {
await utils.viewer.public.i18n.invalidate();
},
});
// when current username isn't set - Go to stripe to check what username he wanted to buy and was it a premium and was it paid for
@@ -1,4 +1,5 @@
import classNames from "classnames";
// eslint-disable-next-line no-restricted-imports
import { debounce, noop } from "lodash";
import { useSession } from "next-auth/react";
import type { RefCallback } from "react";
@@ -24,7 +25,7 @@ interface ICustomUsernameProps {
const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.ComponentProps<typeof TextField>>) => {
const { t } = useLocale();
const { data: session, update } = useSession();
const { update } = useSession();
const {
currentUsername,
@@ -65,8 +66,6 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
}
}, [inputUsernameValue, debouncedApiCall, currentUsername]);
const utils = trpc.useContext();
const updateUsernameMutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async () => {
onSuccessMutation && (await onSuccessMutation());
@@ -77,9 +76,6 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
onError: (error) => {
onErrorMutation && onErrorMutation(error);
},
async onSettled() {
await utils.viewer.public.i18n.invalidate();
},
});
const ActionButtons = () => {
@@ -1,35 +1,26 @@
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { PremiumTextfield } from "./PremiumTextfield";
import { UsernameTextfield } from "./UsernameTextfield";
export const UsernameAvailability = IS_SELF_HOSTED ? UsernameTextfield : PremiumTextfield;
interface UsernameAvailabilityFieldProps {
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
}
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
return organization
? organization.slug
? getOrgFullDomain(organization.slug, { protocol: false })
: organization.metadata && organization.metadata.requestedSlug
? getOrgFullDomain(organization.metadata.requestedSlug, { protocol: false })
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
}
export const getUsernameAvailabilityComponent = (isPremium: boolean) => {
if (isPremium)
return dynamic(() => import("./PremiumTextfield").then((m) => m.PremiumTextfield), { ssr: false });
return dynamic(() => import("./UsernameTextfield").then((m) => m.UsernameTextfield), { ssr: false });
};
export const UsernameAvailabilityField = ({
onSuccessMutation,
@@ -49,7 +40,12 @@ export const UsernameAvailabilityField = ({
},
});
const usernamePrefix = useUserNamePrefix(user.organization);
const UsernameAvailability = getUsernameAvailabilityComponent(!IS_SELF_HOSTED && !user.organization?.id);
const orgBranding = useOrgBranding();
const usernamePrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<Controller
@@ -65,7 +61,8 @@ export const UsernameAvailabilityField = ({
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
addOnLeading={usernamePrefix}
disabled={!!user.organization?.id}
addOnLeading={`${usernamePrefix}/`}
/>
);
}}
+1
View File
@@ -0,0 +1 @@
export const IS_VISUAL_REGRESSION_TESTING = Boolean(globalThis.window?.Meticulous?.isRunningAsTest);
+8 -11
View File
@@ -1,7 +1,6 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import type { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
import { useSession } from "next-auth/react";
import { SessionProvider, useSession } from "next-auth/react";
import { EventCollectionProvider } from "next-collect/client";
import type { SSRConfig } from "next-i18next";
import { appWithTranslation } from "next-i18next";
@@ -18,10 +17,10 @@ import { useFlags } from "@calcom/features/flags/hooks";
import { trpc } from "@calcom/trpc/react";
import { MetaProvider } from "@calcom/ui";
import usePublicPage from "@lib/hooks/usePublicPage";
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
import type { WithNonceProps } from "@lib/withNonce";
import { useViewerI18n } from "@components/I18nLanguageHandler";
import { useClientViewerI18n } from "@components/I18nLanguageHandler";
const I18nextAdapter = appWithTranslation<
NextJsAppProps<SSRConfig> & {
@@ -69,11 +68,9 @@ type AppPropsWithoutNonce = Omit<AppPropsWithChildren, "pageProps"> & {
const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
* We intend to not cache i18n query
**/
const { i18n, locale } = useViewerI18n().data ?? {
locale: "en",
};
const clientViewerI18n = useClientViewerI18n(props.router.locales || []);
const { i18n, locale } = clientViewerI18n.data || {};
const passedProps = {
...props,
@@ -247,7 +244,7 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
const AppProviders = (props: AppPropsWithChildren) => {
// No need to have intercom on public pages - Good for Page Performance
const isPublicPage = usePublicPage();
const isBookingPage = useIsBookingPage();
const { pageProps, ...rest } = props;
const { _nonce, ...restPageProps } = pageProps;
const propsWithoutNonce = {
@@ -267,7 +264,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
themeBasis={props.pageProps.themeBasis}
nonce={props.pageProps.nonce}
isThemeSupported={props.Component.isThemeSupported}
isBookingPage={props.Component.isBookingPage}
isBookingPage={props.Component.isBookingPage || isBookingPage}
router={props.router}>
<FeatureFlagsProvider>
<OrgBrandProvider>
@@ -281,7 +278,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
</EventCollectionProvider>
);
if (isPublicPage) {
if (isBookingPage) {
return RemainingProviders;
}
-53
View File
@@ -1,53 +0,0 @@
import parser from "accept-language-parser";
import type { IncomingMessage } from "http";
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import type { Maybe } from "@calcom/trpc/server";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { i18n } = require("@calcom/config/next-i18next.config");
export function getLocaleFromHeaders(req: IncomingMessage): string {
let preferredLocale: string | null | undefined;
if (req.headers["accept-language"]) {
preferredLocale = parser.pick(i18n.locales, req.headers["accept-language"]) as Maybe<string>;
}
return preferredLocale ?? i18n.defaultLocale;
}
export const getOrSetUserLocaleFromHeaders = async (
req: GetServerSidePropsContext["req"],
res: GetServerSidePropsContext["res"]
): Promise<string> => {
const { default: prisma } = await import("@calcom/prisma");
const session = await getServerSession({ req, res });
const preferredLocale = getLocaleFromHeaders(req);
if (session?.user?.id) {
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
select: {
locale: true,
},
});
if (user?.locale) {
return user.locale;
}
await prisma.user.update({
where: {
id: session.user.id,
},
data: {
locale: preferredLocale,
},
});
}
return preferredLocale;
};
+12
View File
@@ -0,0 +1,12 @@
import { usePathname, useSearchParams } from "next/navigation";
export default function useIsBookingPage() {
const pathname = usePathname();
const isBookingPage = ["/booking/", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route));
const searchParams = useSearchParams();
const userParam = searchParams.get("user");
const teamParam = searchParams.get("team");
return !!(isBookingPage || userParam || teamParam);
}
-9
View File
@@ -1,9 +0,0 @@
import { usePathname } from "next/navigation";
export default function usePublicPage() {
const pathname = usePathname();
const isPublicPage = ["/[user]", "/booking", "/cancel", "/reschedule"].find((route) =>
pathname?.startsWith(route)
);
return isPublicPage;
}
+12 -3
View File
@@ -3,6 +3,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
const os = require("os");
const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
const { version } = require("./package.json");
const { i18n } = require("./next-i18next.config");
const {
orgHostPath,
@@ -14,6 +15,9 @@ const {
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
// To be able to use the version in the app without having to import package.json
process.env.NEXT_PUBLIC_CALCOM_VERSION = version;
// So we can test deploy previews preview
if (process.env.VERCEL_URL && !process.env.NEXT_PUBLIC_WEBAPP_URL) {
process.env.NEXT_PUBLIC_WEBAPP_URL = "https://" + process.env.VERCEL_URL;
@@ -25,8 +29,10 @@ if (!process.env.NEXTAUTH_URL && process.env.NEXT_PUBLIC_WEBAPP_URL) {
if (!process.env.NEXT_PUBLIC_WEBSITE_URL) {
process.env.NEXT_PUBLIC_WEBSITE_URL = process.env.NEXT_PUBLIC_WEBAPP_URL;
}
if (process.env.CSP_POLICY === "strict" && process.env.NODE_ENV === "production") {
if (
process.env.CSP_POLICY === "strict" &&
(process.env.CALCOM_ENV === "production" || process.env.NODE_ENV === "production")
) {
throw new Error(
"Strict CSP policy(for style-src) is not yet supported in production. You can experiment with it in Dev Mode"
);
@@ -127,7 +133,10 @@ const matcherConfigUserTypeEmbedRoute = {
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
i18n: {
...i18n,
localeDetection: false,
},
productionBrowserSourceMaps: true,
/* We already do type check on GH actions */
typescript: {
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.1.7",
"version": "3.2.3",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -68,6 +68,7 @@
"@vercel/og": "^0.5.0",
"accept-language-parser": "^1.5.0",
"async": "^3.2.4",
"bcp-47-match": "^2.0.3",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dotenv-cli": "^6.0.0",
@@ -166,6 +167,7 @@
"msw": "^0.42.3",
"postcss": "^8.4.18",
"tailwindcss": "^3.3.1",
"tailwindcss-animate": "^1.0.6",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
+5 -8
View File
@@ -1,7 +1,7 @@
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, showToast } from "@calcom/ui";
import { Copy } from "@calcom/ui/components/icon";
@@ -21,14 +21,11 @@ export default function Error500() {
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
<h1 className="font-cal text-emphasis text-6xl">500</h1>
<h2 className="text-emphasis mt-6 text-2xl font-medium">It&apos;s not you, it&apos;s us.</h2>
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
away for you.
</p>
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">{t("something_went_wrong_on_our_end")}</p>
{searchParams?.get("error") && (
<div className="mb-8 flex flex-col">
<p className="text-default mb-4 max-w-2xl text-sm">
Please provide the following text when contacting support to better help you:
{t("please_provide_following_text_to_suppport")}:
</p>
<pre className="bg-emphasis text-emphasis w-full max-w-2xl whitespace-normal break-words rounded-md p-4">
{searchParams?.get("error")}
@@ -46,9 +43,9 @@ export default function Error500() {
</pre>
</div>
)}
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
<Button href="mailto:support@cal.com">{t("contact_support")}</Button>
<Button color="secondary" href="javascript:history.back()" className="ml-2">
Go back
{t("go_back")}
</Button>
</div>
</div>
+9 -1
View File
@@ -36,6 +36,7 @@ import { ssrInit } from "@server/lib/ssr";
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(profile.theme);
const { t } = useLocale();
@@ -82,6 +83,10 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
profile: { name: `${profile.name}`, image: null },
users: [{ username: `${user.username}`, name: `${user.name}` }],
}}
nextSeoProps={{
noindex: !profile.allowSEOIndexing,
nofollow: !profile.allowSEOIndexing,
}}
/>
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
@@ -93,7 +98,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
)}>
<div className="mb-8 text-center">
<Avatar imageSrc={profile.image} size="xl" alt={profile.name} />
<h1 className="font-cal text-emphasis mb-1 text-3xl">
<h1 className="font-cal text-emphasis mb-1 text-3xl" data-testid="name-title">
{profile.name}
{user.verified && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
@@ -214,6 +219,7 @@ export type UserPageProps = {
theme: string | null;
brandColor: string;
darkBrandColor: string;
allowSEOIndexing: boolean;
};
users: Pick<User, "away" | "name" | "username" | "bio" | "verified">[];
themeBasis: string | null;
@@ -276,6 +282,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
away: true,
verified: true,
allowDynamicBooking: true,
allowSEOIndexing: true,
},
});
@@ -315,6 +322,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
theme: user.theme,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
allowSEOIndexing: user.allowSEOIndexing ?? true,
};
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
+24 -3
View File
@@ -4,7 +4,11 @@ import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
import {
getBookingForReschedule,
getBookingForSeatedEvent,
getMultipleDurationValue,
} from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
@@ -26,8 +30,10 @@ export default function Type({
booking,
away,
isBrandingHidden,
isSEOIndexable,
rescheduleUid,
entity,
duration,
}: PageProps) {
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
@@ -36,6 +42,7 @@ export default function Type({
eventSlug={slug}
rescheduleUid={rescheduleUid ?? undefined}
hideBranding={isBrandingHidden}
isSEOIndexable={isSEOIndexable ?? true}
entity={entity}
/>
<Booker
@@ -45,6 +52,7 @@ export default function Type({
isAway={away}
hideBranding={isBrandingHidden}
entity={entity}
duration={duration}
/>
</main>
);
@@ -55,7 +63,7 @@ Type.PageWrapper = PageWrapper;
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
const { rescheduleUid, bookingUid } = context.query;
const { rescheduleUid, bookingUid, duration: queryDuration } = context.query;
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
@@ -111,12 +119,18 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
return {
props: {
entity: eventData.entity,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
booking,
user: usernames.join("+"),
slug,
away: false,
trpcState: ssr.dehydrate(),
isBrandingHidden: false,
isSEOIndexable: true,
themeBasis: null,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
@@ -127,7 +141,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
async function getUserPageProps(context: GetServerSidePropsContext) {
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
const username = usernames[0];
const { rescheduleUid, bookingUid } = context.query;
const { rescheduleUid, bookingUid, duration: queryDuration } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(
context.req.headers.host ?? "",
context.params?.orgSlug
@@ -143,6 +157,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
select: {
away: true,
hideBranding: true,
allowSEOIndexing: true,
},
});
@@ -177,12 +192,18 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
return {
props: {
booking,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
away: user?.away,
user: username,
slug,
entity: eventData.entity,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
isSEOIndexable: user?.allowSEOIndexing,
themeBasis: username,
bookingUid: bookingUid ? `${bookingUid}` : null,
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
+2
View File
@@ -1,3 +1,5 @@
import React from "react";
import { trpc } from "@calcom/trpc/react";
import type { AppProps } from "@lib/app-providers";
+3 -1
View File
@@ -3,6 +3,8 @@ import type { DocumentContext, DocumentProps } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";
import { z } from "zod";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { csp } from "@lib/csp";
type Props = Record<string, unknown> & DocumentProps;
@@ -49,7 +51,7 @@ class MyDocument extends Document<Props> {
<meta name="msapplication-TileColor" content="#ff0000" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f9fafb" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1C1C1C" />
{(process.env.NODE_ENV === "development" || process.env.VERCEL_ENV === "preview") && (
{!IS_PRODUCTION && process.env.VERCEL_ENV === "preview" && (
// eslint-disable-next-line @next/next/no-sync-scripts
<script
data-project-id="KjpMrKTnXquJVKfeqmjdTffVPf1a6Unw2LZ58iE4"
+18 -35
View File
@@ -8,7 +8,7 @@ import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { IS_CALCOM } from "@calcom/lib/constants";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateUsernameInOrg } from "@calcom/lib/validateUsernameInOrg";
import { validateUsernameInTeam, validateUsername } from "@calcom/lib/validateUsername";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
@@ -39,6 +39,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const username = slugify(data.username);
const userEmail = email.toLowerCase();
const validationResponse = (
incomingEmail: string,
validation: { isValid: boolean; email: string | undefined }
) => {
const { isValid, email } = validation;
if (!isValid) {
const message: string =
email !== incomingEmail ? "Username already taken" : "Email address is already registered";
return res.status(409).json({ message });
}
};
if (!username) {
res.status(422).json({ message: "Invalid username" });
return;
@@ -65,42 +78,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(401).json({ message: "Token expired" });
}
if (foundToken?.teamId) {
const isValidUsername = await validateUsernameInOrg(username, foundToken?.teamId);
if (!isValidUsername) {
return res.status(409).json({ message: "Username already taken" });
}
const teamUserValidation = await validateUsernameInTeam(username, userEmail, foundToken?.teamId);
return validationResponse(userEmail, teamUserValidation);
}
} else {
// There is an existingUser if the username matches
// OR if the email matches AND either the email is verified
// or both username and password are set
const existingUser = await prisma.user.findFirst({
where: {
OR: [
{ username },
{
AND: [
{ email: userEmail },
{
OR: [
{ emailVerified: { not: null } },
{
AND: [{ password: { not: null } }, { username: { not: null } }],
},
],
},
],
},
],
},
});
if (existingUser) {
const message: string =
existingUser.email !== userEmail ? "Username already taken" : "Email address is already registered";
return res.status(409).json({ message });
}
const userValidation = await validateUsername(username, userEmail);
return validationResponse(userEmail, userValidation);
}
const hashedPassword = await hashPassword(password);
@@ -1,10 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { authenticator } from "otplib";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/client";
@@ -69,7 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
// If user has 2fa enabled, check if body.code is correct
const isValidToken = authenticator.check(req.body.code, secret);
const isValidToken = totpAuthenticatorCheck(req.body.code, secret);
if (!isValidToken) {
return res.status(400).json({ error: ErrorCode.IncorrectTwoFactorCode });
@@ -1,9 +1,9 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { authenticator } from "otplib";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
import prisma from "@calcom/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -48,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return res.status(500).json({ error: ErrorCode.InternalServerError });
}
const isValidToken = authenticator.check(req.body.code, secret);
const isValidToken = totpAuthenticatorCheck(req.body.code, secret);
if (!isValidToken) {
return res.status(400).json({ error: ErrorCode.IncorrectTwoFactorCode });
}
+2 -1
View File
@@ -1,10 +1,11 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { renderEmail } from "@calcom/emails";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getTranslation } from "@calcom/lib/server/i18n";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (process.env.NODE_ENV !== "development") return res.write("Only for development purposes"), res.end();
if (IS_PRODUCTION) return res.write("Only for development purposes"), res.end();
const t = await getTranslation("en", "common");
const language = { translate: t, locale: "en" };
@@ -0,0 +1 @@
export { default, config } from "@calcom/features/ee/payments/api/paypal-webhook";
@@ -0,0 +1,4 @@
import appBasecamp3 from "@calcom/app-store/basecamp3/trpc-router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(appBasecamp3);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { kycVerificationRouter } from "@calcom/trpc/server/routers/viewer/kycVerification/_router";
export default createNextApiHandler(kycVerificationRouter);
+3 -2
View File
@@ -9,6 +9,7 @@ import { z } from "zod";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -38,7 +39,7 @@ const sourceSchema = z.object({
function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
// If it's not production environment, it would be a better idea to inform that the App is disabled.
if (props.isAppDisabled) {
if (process.env.NODE_ENV !== "production") {
if (!IS_PRODUCTION) {
// TODO: Improve disabled App UI. This is just a placeholder.
return (
<div className="p-2">
@@ -123,7 +124,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
const isAppAvailableInFileSystem = appMeta;
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
if (process.env.NODE_ENV !== "production" && isAppDisabled) {
if (!IS_PRODUCTION && isAppDisabled) {
return {
props: {
isAppDisabled: true as const,
@@ -40,9 +40,11 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
}>
<div className="mb-16">
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
{apps.map((app) => {
return <AppCard key={app.slug} app={app} />;
})}
{apps
.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
.map((app) => {
return <AppCard key={app.slug} app={app} />;
})}
</div>
</div>
</Shell>
+2
View File
@@ -3,6 +3,7 @@ import type { ChangeEventHandler } from "react";
import { useState } from "react";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
import { getLayout } from "@calcom/features/MainLayout";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
@@ -94,6 +95,7 @@ export default function Apps({
}
Apps.PageWrapper = PageWrapper;
Apps.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-imports
import { debounce } from "lodash";
import type { GetServerSidePropsContext } from "next";
import { getCsrfToken } from "next-auth/react";
+1 -1
View File
@@ -74,7 +74,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
<DateOverrideInputDialog
workingHours={workingHours}
excludedDates={excludedDates}
onChange={(ranges) => append({ ranges })}
onChange={(ranges) => ranges.forEach((range) => append({ ranges: [range] }))}
Trigger={
<Button color="secondary" StartIcon={Plus} data-testid="add-override">
{t("add_an_override")}
+50 -7
View File
@@ -1,13 +1,16 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { useCallback } from "react";
import { getLayout } from "@calcom/features/MainLayout";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import Shell from "@calcom/features/shell/Shell";
import { ShellMain } from "@calcom/features/shell/Shell";
import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { EmptyScreen, showToast } from "@calcom/ui";
import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui";
import { Clock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
@@ -128,17 +131,57 @@ const WithQuery = withQuery(trpc.viewer.availability.list as any);
export default function AvailabilityPage() {
const { t } = useLocale();
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
// Get a new searchParams string by merging the current
// searchParams with a provided key/value pair
const createQueryString = useCallback(
(name: string, value: string) => {
const params = new URLSearchParams(searchParams ?? undefined);
params.set(name, value);
return params.toString();
},
[searchParams]
);
return (
<div>
<Shell
<ShellMain
heading={t("availability")}
hideHeadingOnMobile
subtitle={t("configure_availability")}
CTA={<NewScheduleButton />}>
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
</Shell>
CTA={
<div className="flex gap-2">
<ToggleGroup
className="hidden md:block"
defaultValue={searchParams?.get("type") ?? "mine"}
onValueChange={(value) => {
if (!value) return;
router.push(`${pathname}?${createQueryString("type", value)}`);
}}
options={[
{ value: "mine", label: t("my_availability") },
{ value: "team", label: t("team_availability") },
]}
/>
<NewScheduleButton />
</div>
}>
{searchParams?.get("type") === "team" ? (
<AvailabilitySliderTable />
) : (
<WithQuery
success={({ data }) => <AvailabilityList {...data} />}
customLoader={<SkeletonLoader />}
/>
)}
</ShellMain>
</div>
);
}
AvailabilityPage.getLayout = getLayout;
AvailabilityPage.PageWrapper = PageWrapper;
+26 -10
View File
@@ -23,12 +23,9 @@ import {
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { SystemField } from "@calcom/features/bookings/lib/SystemField";
import { SMS_REMINDER_NUMBER_FIELD, SystemField } from "@calcom/features/bookings/lib/SystemField";
import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
import {
getBookingFieldsWithSystemFields,
SMS_REMINDER_NUMBER_FIELD,
} from "@calcom/features/bookings/lib/getBookingFields";
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
import { parseRecurringEvent } from "@calcom/lib";
import { APP_NAME } from "@calcom/lib/constants";
import {
@@ -96,7 +93,7 @@ const querySchema = z.object({
});
export default function Success(props: SuccessProps) {
const { t } = useLocale();
const { t, i18n } = useLocale();
const router = useRouter();
const routerQuery = useRouterQuery();
const pathname = usePathname();
@@ -113,7 +110,8 @@ export default function Success(props: SuccessProps) {
const attendeeTimeZone = props?.bookingInfo?.attendees.find(
(attendee) => attendee.email === email
)?.timeZone;
const tz = isSuccessBookingPage && attendeeTimeZone ? attendeeTimeZone : props.tz ? props.tz : timeZone();
const tz = props.tz ? props.tz : isSuccessBookingPage && attendeeTimeZone ? attendeeTimeZone : timeZone();
const location = props.bookingInfo.location as ReturnType<typeof getEventLocationValue>;
@@ -314,7 +312,7 @@ export default function Success(props: SuccessProps) {
<Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
<ChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
<ChevronLeft className="h-5 w-5 rtl:rotate-180" /> {t("back_to_bookings")}
</Link>
</div>
)}
@@ -394,7 +392,7 @@ export default function Success(props: SuccessProps) {
</h4>
)}
<div className="border-subtle text-default mt-8 grid grid-cols-3 border-t pt-8 text-left">
<div className="border-subtle text-default mt-8 grid grid-cols-3 border-t pt-8 text-left rtl:text-right">
{(isCancelled || reschedule) && cancellationReason && (
<>
<div className="font-medium">
@@ -480,6 +478,21 @@ export default function Success(props: SuccessProps) {
</div>
</>
)}
{props.paymentStatus && (
<>
<div className="mt-3 font-medium">
{props.paymentStatus.paymentOption === "HOLD"
? t("complete_your_booking")
: t("payment")}
</div>
<div className="col-span-2 mb-2 mt-3">
{new Intl.NumberFormat(i18n.language, {
style: "currency",
currency: props.paymentStatus.currency,
}).format(props.paymentStatus.amount / 100.0)}
</div>
</>
)}
{bookingInfo?.description && (
<>
<div className="mt-9 font-medium">{t("additional_notes")}</div>
@@ -715,7 +728,7 @@ export default function Success(props: SuccessProps) {
</div>
{isGmail && (
<Alert
className="main -mb-20 mt-4 inline-block text-left sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
className="main -mb-20 mt-4 inline-block ltr:text-left rtl:text-right sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
severity="warning"
message={
<div>
@@ -1117,6 +1130,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
select: {
success: true,
refunded: true,
currency: true,
amount: true,
paymentOption: true,
},
});
+113 -75
View File
@@ -1,16 +1,21 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import type { GetStaticPaths, GetStaticProps } from "next";
import { Fragment } from "react";
import React from "react";
import { z } from "zod";
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
import BookingLayout from "@calcom/features/bookings/layout/BookingLayout";
import { getLayout } from "@calcom/features/MainLayout";
import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer";
import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { ShellMain } from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { HorizontalTabs } from "@calcom/ui";
import type { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui";
import { Alert, Button, EmptyScreen } from "@calcom/ui";
import { Calendar } from "@calcom/ui/components/icon";
@@ -32,6 +37,28 @@ type RecurringInfo = {
bookings: { [key: string]: Date[] };
};
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
name: "upcoming",
href: "/bookings/upcoming",
},
{
name: "unconfirmed",
href: "/bookings/unconfirmed",
},
{
name: "recurring",
href: "/bookings/recurring",
},
{
name: "past",
href: "/bookings/past",
},
{
name: "cancelled",
href: "/bookings/cancelled",
},
];
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
const descriptionByStatus: Record<NonNullable<BookingListingStatus>, string> = {
@@ -112,90 +139,101 @@ export default function Bookings() {
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
return (
<BookingLayout heading={t("bookings")} subtitle={t("bookings_description")}>
<div className="flex w-full flex-col" ref={animationParentRef}>
{query.status === "error" && (
<Alert severity="error" title={t("something_went_wrong")} message={query.error.message} />
)}
{(query.status === "loading" || query.isPaused) && <SkeletonLoader />}
{query.status === "success" && !isEmpty && (
<>
{!!bookingsToday.length && status === "upcoming" && (
<div className="mb-6 pt-2 xl:pt-0">
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
<p className="text-subtle mb-2 text-xs font-medium uppercase leading-4">{t("today")}</p>
<div className="border-subtle overflow-hidden rounded-md border">
<table className="w-full max-w-full table-fixed">
<tbody className="bg-default divide-subtle divide-y" data-testid="today-bookings">
<Fragment>
{bookingsToday.map((booking: BookingOutput) => (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfoToday}
{...booking}
/>
<ShellMain hideHeadingOnMobile heading={t("bookings")} subtitle={t("bookings_description")}>
<div className="flex flex-col">
<div className="flex flex-col flex-wrap lg:flex-row">
<HorizontalTabs tabs={tabs} />
<div className="max-w-full overflow-x-auto xl:ml-auto">
<FiltersContainer />
</div>
</div>
<main className="w-full">
<div className="flex w-full flex-col" ref={animationParentRef}>
{query.status === "error" && (
<Alert severity="error" title={t("something_went_wrong")} message={query.error.message} />
)}
{(query.status === "loading" || query.isPaused) && <SkeletonLoader />}
{query.status === "success" && !isEmpty && (
<>
{!!bookingsToday.length && status === "upcoming" && (
<div className="mb-6 pt-2 xl:pt-0">
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
<p className="text-subtle mb-2 text-xs font-medium uppercase leading-4">{t("today")}</p>
<div className="border-subtle overflow-hidden rounded-md border">
<table className="w-full max-w-full table-fixed">
<tbody className="bg-default divide-subtle divide-y" data-testid="today-bookings">
<Fragment>
{bookingsToday.map((booking: BookingOutput) => (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfoToday}
{...booking}
/>
))}
</Fragment>
</tbody>
</table>
</div>
</div>
)}
<div className="pt-2 xl:pt-0">
<div className="border-subtle overflow-hidden rounded-md border">
<table className="w-full max-w-full table-fixed">
<tbody className="bg-default divide-subtle divide-y" data-testid="bookings">
{query.data.pages.map((page, index) => (
<Fragment key={index}>
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
const recurringInfo = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfo}
{...booking}
/>
);
})}
</Fragment>
))}
</Fragment>
</tbody>
</table>
</tbody>
</table>
</div>
<div className="text-default p-4 text-center" ref={buttonInView.ref}>
<Button
color="minimal"
loading={query.isFetchingNextPage}
disabled={!query.hasNextPage}
onClick={() => query.fetchNextPage()}>
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>
</>
)}
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen
Icon={Calendar}
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
description={t("no_status_bookings_yet_description", {
status: t(status).toLowerCase(),
description: t(descriptionByStatus[status]),
})}
/>
</div>
)}
<div className="pt-2 xl:pt-0">
<div className="border-subtle overflow-hidden rounded-md border">
<table className="w-full max-w-full table-fixed">
<tbody className="bg-default divide-subtle divide-y" data-testid="bookings">
{query.data.pages.map((page, index) => (
<Fragment key={index}>
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
const recurringInfo = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfo}
{...booking}
/>
);
})}
</Fragment>
))}
</tbody>
</table>
</div>
<div className="text-default p-4 text-center" ref={buttonInView.ref}>
<Button
color="minimal"
loading={query.isFetchingNextPage}
disabled={!query.hasNextPage}
onClick={() => query.fetchNextPage()}>
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>
</>
)}
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen
Icon={Calendar}
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
description={t("no_status_bookings_yet_description", {
status: t(status).toLowerCase(),
description: t(descriptionByStatus[status]),
})}
/>
</div>
)}
</main>
</div>
</BookingLayout>
</ShellMain>
);
}
Bookings.PageWrapper = PageWrapper;
Bookings.getLayout = getLayout;
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
+9 -2
View File
@@ -4,7 +4,7 @@ import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import slugify from "@calcom/lib/slugify";
@@ -26,6 +26,7 @@ export default function Type({
isBrandingHidden,
isTeamEvent,
entity,
duration,
}: PageProps) {
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
@@ -44,6 +45,7 @@ export default function Type({
hideBranding={isBrandingHidden}
isTeamEvent={isTeamEvent}
entity={entity}
duration={duration}
/>
</main>
);
@@ -54,7 +56,7 @@ Type.isBookingPage = true;
async function getUserPageProps(context: GetServerSidePropsContext) {
const { link, slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { rescheduleUid, duration: queryDuration } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const org = isValidOrgDomain ? currentOrgDomain : null;
@@ -133,6 +135,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
return {
props: {
entity: eventData.entity,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
booking,
away: user?.away,
user: username,
@@ -132,6 +132,7 @@ export type FormValues = {
bookingFields: z.infer<typeof eventTypeBookingFields>;
availability?: AvailabilityOption;
bookerLayouts: BookerLayoutSettings;
multipleDurationEnabled: boolean;
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -258,6 +259,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
startDate: periodDates.startDate,
endDate: periodDates.endDate,
},
offsetStart: eventType.offsetStart,
bookingFields: eventType.bookingFields,
periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
@@ -277,6 +279,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
.filter((slug) => slug !== eventType.slug) ?? [],
},
})),
seatsPerTimeSlotEnabled: eventType.seatsPerTimeSlot,
};
}, [eventType, periodDates, metadata]);
@@ -374,7 +377,10 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsPerTimeSlotEnabled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
minimumBookingNoticeInDurationType,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bookerLayouts,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
multipleDurationEnabled,
...input
} = values;
@@ -462,6 +468,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
seatsPerTimeSlotEnabled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
multipleDurationEnabled,
...input
} = values;
+27 -24
View File
@@ -7,6 +7,7 @@ import type { FC } from "react";
import { memo, useEffect, useState } from "react";
import { z } from "zod";
import { getLayout } from "@calcom/features/MainLayout";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
import { EventTypeEmbedButton, EventTypeEmbedDialog } from "@calcom/features/embed/EventTypeEmbed";
@@ -15,7 +16,7 @@ import CreateEventTypeDialog from "@calcom/features/eventtypes/components/Create
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
import Shell from "@calcom/features/shell/Shell";
import { ShellMain } from "@calcom/features/shell/Shell";
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -715,10 +716,7 @@ const EventTypeListHeading = ({
<Avatar
alt={profile?.name || ""}
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
imageSrc={
`${orgBranding?.fullDomain ?? WEBAPP_URL}/${teamId ? "team/" : ""}${profile.slug}/avatar.png` ||
undefined
}
imageSrc={`${orgBranding?.fullDomain ?? WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size="md"
className="mt-1 inline-flex justify-center"
/>
@@ -739,7 +737,9 @@ const EventTypeListHeading = ({
</span>
)}
{profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`} className="text-subtle block text-xs">
<Link
href={`${orgBranding ? orgBranding.fullDomain : CAL_URL}/${profile.slug}`}
className="text-subtle block text-xs">
{`${bookerUrl.replace("https://", "").replace("http://", "")}/${profile.slug}`}
</Link>
)}
@@ -832,13 +832,13 @@ const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => {
const Main = ({
status,
error,
errorMessage,
data,
filters,
}: {
status: string;
data: GetByViewerResponse;
error: any;
errorMessage?: string;
filters: ReturnType<typeof getTeamsFiltersFromQuery>;
}) => {
const isMobile = useMediaQuery("(max-width: 768px)");
@@ -850,7 +850,7 @@ const Main = ({
}
if (status === "error") {
return <Alert severity="error" title="Something went wrong" message={error.message} />;
return <Alert severity="error" title="Something went wrong" message={errorMessage} />;
}
const isFilteredByOnlyOneItem =
@@ -925,32 +925,35 @@ const EventTypesPage = () => {
if (searchParams?.get("openIntercom") === "true") {
open();
}
setShowProfileBanner(
!!orgBranding && !document.cookie.includes("calcom-profile-banner=1") && !user?.completedOnboarding
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setShowProfileBanner(
!!orgBranding && !document.cookie.includes("calcom-profile-banner=1") && !user?.completedOnboarding
);
}, [orgBranding, user]);
return (
<div>
<ShellMain
withoutSeo
heading={t("event_types_page_title")}
hideHeadingOnMobile
subtitle={t("event_types_page_subtitle")}
afterHeading={showProfileBanner && <SetupProfileBanner closeAction={closeBanner} />}
beforeCTAactions={<Actions />}
CTA={<CTA data={data} />}>
<HeadSeo
title="Event Types"
description="Create events to share for people to book on your calendar."
/>
<Shell
withoutSeo
heading={t("event_types_page_title")}
hideHeadingOnMobile
subtitle={t("event_types_page_subtitle")}
afterHeading={showProfileBanner && <SetupProfileBanner closeAction={closeBanner} />}
beforeCTAactions={<Actions />}
CTA={<CTA data={data} />}>
<Main data={data} status={status} error={error} filters={filters} />
</Shell>
</div>
<Main data={data} status={status} errorMessage={error?.message} filters={filters} />
</ShellMain>
);
};
EventTypesPage.getLayout = getLayout;
EventTypesPage.PageWrapper = PageWrapper;
export default EventTypesPage;
@@ -121,10 +121,10 @@ const OnboardingPage = () => {
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="mx-auto px-4 py-6 md:py-24">
<div className="mx-auto py-6 sm:px-4 md:py-24">
<div className="relative">
<div className="sm:mx-auto sm:w-full sm:max-w-[600px]">
<div className="mx-auto sm:max-w-[520px]">
<div className="mx-auto px-4 sm:max-w-[520px]">
<header>
<p className="font-cal mb-3 text-[28px] font-medium leading-7">
{headers[currentStepIndex]?.title || "Undefined title"}
+5 -3
View File
@@ -1,3 +1,4 @@
import { getLayout } from "@calcom/features/MainLayout";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import {
AverageEventDurationChart,
@@ -9,7 +10,7 @@ import {
} from "@calcom/features/insights/components";
import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider";
import { Filters } from "@calcom/features/insights/filters";
import Shell from "@calcom/features/shell/Shell";
import { ShellMain } from "@calcom/features/shell/Shell";
import { UpgradeTip } from "@calcom/features/tips";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -56,7 +57,7 @@ export default function InsightsPage() {
return (
<div>
<Shell hideHeadingOnMobile>
<ShellMain hideHeadingOnMobile>
<UpgradeTip
title={t("make_informed_decisions")}
description={t("make_informed_decisions_description")}
@@ -111,12 +112,13 @@ export default function InsightsPage() {
</FiltersProvider>
)}
</UpgradeTip>
</Shell>
</ShellMain>
</div>
);
}
InsightsPage.PageWrapper = PageWrapper;
InsightsPage.getLayout = getLayout;
// If feature flag is disabled, return not found on getServerSideProps
export const getServerSideProps = async () => {
@@ -0,0 +1,11 @@
import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import KYCVerificationView from "./kycVerificationView";
const KYCVerificationPage = () => <KYCVerificationView />;
KYCVerificationPage.getLayout = getLayout;
KYCVerificationPage.PageWrapper = PageWrapper;
export default KYCVerificationPage;
@@ -0,0 +1,80 @@
import { useForm } from "react-hook-form";
import { trpc } from "@calcom/trpc";
import { Meta, Form, Button, TextField, showToast } from "@calcom/ui";
type FormValues = {
name?: string | null;
};
export default function KYCVerificationView() {
const teamForm = useForm<FormValues>();
const userForm = useForm<FormValues>();
const mutation = trpc.viewer.kycVerification.verify.useMutation({
onSuccess: async (data) => {
showToast(`Successfully verified ${data.isTeam ? "team" : "user"} ${data.name}`, "success");
},
onError: (error) => {
showToast(`Verification failed: ${error.message}`, "error");
},
});
return (
<div>
<Meta
title="KYC Verification"
description="Here you can verify users and teams. This verification is needed for sending sms/whatsapp messages to attendees."
/>
<div>
<div className="mb-2 font-medium">Verify Team</div>
<Form
form={teamForm}
handleSubmit={(values) => {
mutation.mutate({
name: values.name || "",
isTeam: true,
});
}}>
<div className="flex space-x-2">
<TextField
{...teamForm.register("name")}
label=""
type="text"
id="name"
placeholder="team slug"
className="-mt-2 "
required
/>
<Button type="submit">Verify</Button>
</div>
</Form>
</div>
<div>
<div className="mb-2 mt-6 font-medium">Verify User</div>
<Form
form={userForm}
handleSubmit={(values) => {
mutation.mutate({
name: values.name || "",
isTeam: false,
});
}}>
<div className="flex space-x-2">
<TextField
{...userForm.register("name")}
label=""
type="text"
id="name"
placeholder="user name"
className="-mt-2"
required
/>
<Button type="submit">Verify</Button>
</div>
</Form>
</div>
</div>
);
}
@@ -1,9 +1,9 @@
import UnverifiedOrgsPage from "@calcom/features/ee/organizations/pages/settings/admin/unverifiedOrgPage";
import AdminOrgsPage from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = UnverifiedOrgsPage as CalPageWrapper;
const Page = AdminOrgsPage as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
<div>
{isLoading ? null : data?.length ? (
<>
<div className="mb-8 mt-6 rounded-md border">
<div className="border-subtle mb-8 mt-6 rounded-md border">
{data.map((apiKey, index) => (
<ApiKeyListItem
key={apiKey.id}
+27 -17
View File
@@ -1,3 +1,4 @@
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
@@ -20,8 +21,6 @@ import {
TimezoneSelect,
} from "@calcom/ui";
import { withQuery } from "@lib/QueryCell";
import PageWrapper from "@components/PageWrapper";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
@@ -45,11 +44,6 @@ interface GeneralViewProps {
user: RouterOutputs["viewer"]["me"];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WithQuery = withQuery(trpc.viewer.public.i18n as any, undefined, {
trpc: { context: { skipBatch: true } },
});
const GeneralQueryView = () => {
const { t } = useLocale();
@@ -58,30 +52,26 @@ const GeneralQueryView = () => {
if (!user) {
throw new Error(t("something_went_wrong"));
}
return (
<WithQuery
success={({ data }) => <GeneralView user={user} localeProp={data.locale} />}
customLoader={<SkeletonLoader title={t("general")} description={t("general_description")} />}
/>
);
return <GeneralView user={user} localeProp={user.locale} />;
};
const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
const utils = trpc.useContext();
const { t } = useLocale();
const { update } = useSession();
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async () => {
// Invalidate our previous i18n cache
await utils.viewer.public.i18n.invalidate();
onSuccess: async (res) => {
await utils.viewer.me.invalidate();
reset(getValues());
showToast(t("settings_updated_successfully"), "success");
update(res);
},
onError: () => {
showToast(t("error_updating_settings"), "error");
},
onSettled: async () => {
await utils.viewer.public.i18n.invalidate();
await utils.viewer.me.invalidate();
},
});
@@ -116,6 +106,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
label: nameOfDay(localeProp, user.weekStart === "Sunday" ? 0 : 1),
},
allowDynamicBooking: user.allowDynamicBooking ?? true,
allowSEOIndexing: user.allowSEOIndexing ?? true,
},
});
const {
@@ -124,6 +115,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
getValues,
} = formMethods;
const isDisabled = isSubmitting || !isDirty;
return (
<Form
form={formMethods}
@@ -225,6 +217,24 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
)}
/>
</div>
<div className="mt-8">
<Controller
name="allowSEOIndexing"
control={formMethods.control}
render={() => (
<SettingsToggle
title={t("seo_indexing")}
description={t("allow_seo_indexing")}
checked={formMethods.getValues("allowSEOIndexing")}
onCheckedChange={(checked) => {
formMethods.setValue("allowSEOIndexing", checked, { shouldDirty: true });
}}
/>
)}
/>
</div>
<Button
loading={mutation.isLoading}
disabled={isDisabled}
@@ -77,10 +77,9 @@ type FormValues = {
const ProfileView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: session, update } = useSession();
const { data: _session, update } = useSession();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: avatar, isLoading: isLoadingAvatar } = trpc.viewer.avatar.useQuery();
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (res) => {
showToast(t("settings_updated_successfully"), "success");
@@ -205,14 +204,14 @@ const ProfileView = () => {
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
if (isLoading || !user || isLoadingAvatar || !avatar)
if (isLoading || !user)
return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
);
const defaultValues = {
username: user.username || "",
avatar: avatar.avatar || "",
avatar: user.avatar || "",
name: user.name || "",
email: user.email || "",
bio: user.bio || "",
+31 -20
View File
@@ -14,6 +14,7 @@ import { useFlagMap } from "@calcom/features/flags/context/provider";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import slugify from "@calcom/lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
@@ -130,10 +131,11 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
<TextField
addOnLeading={
orgSlug
? getOrgFullDomain(orgSlug, { protocol: false })
? getOrgFullDomain(orgSlug, { protocol: true })
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
}
{...register("username")}
disabled={!!orgSlug}
required
/>
<EmailField
@@ -192,8 +194,6 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
};
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) {
console.log({ flag: flags["disable-signup"] });
return {
notFound: true,
};
@@ -210,6 +210,20 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
where: {
token,
},
include: {
team: {
select: {
metadata: true,
parentId: true,
parent: {
select: {
slug: true,
},
},
slug: true,
},
},
},
});
if (!verificationToken || verificationToken.expires < new Date()) {
@@ -249,23 +263,20 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
let username = guessUsernameFromEmail(verificationToken.identifier);
const orgInfo = await prisma.user.findFirst({
where: {
email: verificationToken?.identifier,
},
select: {
organization: {
select: {
slug: true,
metadata: true,
},
},
},
});
const tokenTeam = {
...verificationToken?.team,
metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata),
};
const userOrgMetadata = teamMetadataSchema.parse(orgInfo?.organization?.metadata ?? {});
// Detect if the team is an org by either the metadata flag or if it has a parent team
const isOrganization = tokenTeam.metadata?.isOrganization || tokenTeam?.parentId !== null;
// If we are dealing with an org, the slug may come from the team itself or its parent
const orgSlug = isOrganization
? tokenTeam.slug || tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug
: null;
if (!IS_SELF_HOSTED) {
// Org context shouldn't check if a username is premium
if (!IS_SELF_HOSTED && !isOrganization) {
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
const { available, suggestion } = await checkPremiumUsername(username);
@@ -278,9 +289,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
token,
prepopulateFormValues: {
email: verificationToken.identifier,
username,
username: slugify(username),
},
orgSlug: (orgInfo?.organization?.slug || userOrgMetadata?.requestedSlug) ?? null,
orgSlug,
},
};
};
+1 -1
View File
@@ -182,7 +182,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
size="lg"
/>
</div>
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider">
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
{team.parent && `${team.parent.name} `}
{teamName}
</p>
+18 -3
View File
@@ -4,7 +4,7 @@ import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
@@ -18,7 +18,16 @@ import PageWrapper from "@components/PageWrapper";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function Type({ slug, user, booking, away, isEmbed, isBrandingHidden, entity }: PageProps) {
export default function Type({
slug,
user,
booking,
away,
isEmbed,
isBrandingHidden,
entity,
duration,
}: PageProps) {
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
<BookerSeo
@@ -37,6 +46,7 @@ export default function Type({ slug, user, booking, away, isEmbed, isBrandingHid
hideBranding={isBrandingHidden}
isTeamEvent
entity={entity}
duration={duration}
/>
</main>
);
@@ -55,7 +65,7 @@ const paramsSchema = z.object({
// 2. If rescheduling, get the booking details
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { rescheduleUid, duration: queryDuration } = context.query;
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(
@@ -104,6 +114,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
entity: eventData.entity,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
booking,
away: false,
user: teamSlug,
+5 -4
View File
@@ -1,7 +1,8 @@
import type { GetServerSidePropsContext } from "next";
import { getLayout } from "@calcom/features/MainLayout";
import { TeamsListing } from "@calcom/features/ee/teams/components";
import Shell from "@calcom/features/shell/Shell";
import { ShellMain } from "@calcom/features/shell/Shell";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -17,7 +18,7 @@ function Teams() {
const [user] = trpc.viewer.me.useSuspenseQuery();
return (
<Shell
<ShellMain
heading={t("teams")}
hideHeadingOnMobile
subtitle={t("create_manage_teams_collaborative")}
@@ -33,7 +34,7 @@ function Teams() {
)
}>
<TeamsListing />
</Shell>
</ShellMain>
);
}
@@ -46,5 +47,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
Teams.requiresLicense = false;
Teams.PageWrapper = PageWrapper;
Teams.getLayout = getLayout;
export default Teams;
+6 -7
View File
@@ -1,6 +1,7 @@
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { installAppleCalendar } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
@@ -10,10 +11,9 @@ test.describe("App Store - Authed", () => {
test("Browse apple-calendar and try to install", async ({ page, users }) => {
const pro = await users.create();
await pro.apiLogin();
await page.goto("/apps/categories/calendar");
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
await page.waitForURL("/apps/apple-calendar");
await page.click('[data-testid="install-app-button"]');
await installAppleCalendar(page);
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
});
@@ -31,9 +31,8 @@ test.describe("App Store - Authed", () => {
test.describe("App Store - Unauthed", () => {
test("Browse apple-calendar and try to install", async ({ page }) => {
await page.goto("/apps/categories/calendar");
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
await page.click('[data-testid="install-app-button"]');
await installAppleCalendar(page);
await expect(page.locator(`[data-testid="login-form"]`)).toBeVisible();
});
});
+35
View File
@@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { installAppleCalendar } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());
const APPLE_CALENDAR_EMAIL = process.env.E2E_TEST_APPLE_CALENDAR_EMAIL!;
const APPLE_CALENDAR_PASSWORD = process.env.E2E_TEST_APPLE_CALENDAR_PASSWORD!;
const SHOULD_SKIP_TESTS = !APPLE_CALENDAR_EMAIL || !APPLE_CALENDAR_PASSWORD;
test.describe("Apple Calendar", () => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(SHOULD_SKIP_TESTS, "Skipping due to missing the testing credentials");
test("Should be able to install and login on Apple Calendar", async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
await installAppleCalendar(page);
await expect(page.locator('[data-testid="apple-calendar-form"]')).toBeVisible();
await page.fill('[data-testid="apple-calendar-email"]', APPLE_CALENDAR_EMAIL);
await page.fill('[data-testid="apple-calendar-password"]', APPLE_CALENDAR_PASSWORD);
await page.click('[data-testid="apple-calendar-login-button"]');
await expect(page.getByText("Apple Calendar")).toBeVisible();
await expect(page.getByText(APPLE_CALENDAR_EMAIL)).toBeVisible();
});
});
+19 -5
View File
@@ -1,39 +1,53 @@
import { expect } from "@playwright/test";
import { randomString } from "@calcom/lib/random";
import { test } from "./lib/fixtures";
import {
bookFirstEvent,
bookOptinEvent,
bookTimeSlot,
expectEmailsToHaveSubject,
selectFirstAvailableTimeSlotNextMonth,
testEmail,
testName,
} from "./lib/testUtils";
const freeUserObj = { name: `Free-user-${randomString(3)}` };
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users }) => users.deleteAll());
test.afterEach(async ({ users }) => {
await users.deleteAll();
});
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create();
const free = await users.create(freeUserObj);
await page.goto(`/${free.username}`);
});
test("cannot book same slot multiple times", async ({ page }) => {
test("cannot book same slot multiple times", async ({ page, users, emails }) => {
const [user] = users.get();
const bookerObj = { email: `testEmail-${randomString(4)}@example.com`, name: "testBooker" };
// Click first event type
await page.click('[data-testid="event-type-link"]');
await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page);
await bookTimeSlot(page, bookerObj);
// save booking url
const bookingUrl: string = page.url();
// Make sure we're navigated to the success page
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
const { title: eventTitle } = await user.getFirstEventAsOwner();
// return to same time spot booking page
await expectEmailsToHaveSubject({
emails,
organizer: user,
booker: bookerObj,
eventTitle,
});
await page.goto(bookingUrl);
// book same time spot again

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