Compare commits

..
349 Commits
Author SHA1 Message Date
Keith Williams 5109075494 v3.8.9
release.yml / release (push) Canceled after 0s
2024-03-12 16:31:33 -03:00
Keith Williams 52791599e1 Revert "fix: view team bookings (#13591)" (#14066)
This reverts commit 4585199b5e.
2024-03-12 19:01:41 +00:00
Anwar SadathandAlex van Andel 54db9a42a9 fix: Create team events using admin token (#14044)
* fix: Create team events using admin token

* Revert `import {` to `import type {`

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-12 16:39:01 +00:00
Crowdin Bot ce204d7a98 New Crowdin translations by Github Action 2024-03-12 16:27:29 +00:00
Candous 44170b267f chore: add pt-BR translations (#14022) 2024-03-12 16:22:15 +00:00
cb30e5f1e0 fix: metadata for seats in webhooks (#14025)
* add metadata to seats for workflows and webhooks

* add test

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-12 16:15:19 +00:00
d6d2aae67f fix event length in webhooks payload (#14039)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-12 16:07:07 +00:00
Keith Williams b06e46342c v3.8.8
release.yml / release (push) Canceled after 0s
2024-03-12 13:03:00 -03:00
Crowdin Bot 4cf482f860 New Crowdin translations by Github Action 2024-03-12 16:00:52 +00:00
f2478ea7d3 feat: new privacy settings for orgs (#13693)
* moved privacy setting to new tab

* i18n

* added more privacy switches

* fix: hide org teams and members

* fix: use i18n

* fix: privacy page

* feat: also hide team mebers when org is private

* chore: remove log

* fix type error

* chore: type err

* chore: feedback

* chore: type err

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-03-12 15:56:43 +00:00
0e110022fb fix: open reporting in new tab full screen (#13659)
* open reporting in new tab full screen

* nit

* fixed type

* Fix failing test

* Update packages/app-store/routing-forms/pages/reporting/[...appPages].tsx

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-03-12 11:46:24 -04:00
Somay Chauhan ee80577cc3 fix: username inconsistency (#14034)
* fix: username inconsistency

* added comment

* code refactor
2024-03-12 15:37:53 +00:00
Udit Takkar 8402dfa3a2 fix: display all responses (#13999)
* fix: display all responses

* fix: type err

* chore: rename variable
2024-03-12 15:30:02 +00:00
Somay ChauhanandUdit Takkar 4585199b5e fix: view team bookings (#13591)
* fix: view team bookings

* fix: edge case- common user across 2 different teams

* @SomayChauhan fix: edge case- common user across 2 different teams v2.0

* feat: added booking list test for collective event-types

* feat: added booking list test for round-robin event-types

* feat: added test for managed event types bookings

* feat: added test for individual events bookings

* refactor code

* added comments

* added isFixed to collective event hosts

* better function naming

* code refactor

* fix: assertBookingVisibleFor ignoring today's meetings

* fix: handling case for organizations

* fix: non org bookings-list test

* feat: added tests for organisations booking list

* added comments for better clarity

* booking-list in org tests

* uncommented doOnOrgDomain

* removed extra selectors

* fix: failing tests

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-12 11:54:07 -03:00
Udit TakkarandJoe Au-Yeung ae2446f42f fix: pending payment in mobile (#14011)
* fix: pending payment in mobile

* fix: pending paymeny badge

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-03-12 14:36:25 +00:00
Crowdin Bot 2f6d109a70 New Crowdin translations by Github Action 2024-03-12 14:30:43 +00:00
Udit Takkar 7ff884ca7d chore: remove idle state from test (#14063) 2024-03-12 14:26:10 +00:00
9230b2d2ca feat: update event types for default availability change (#13964)
* feat: update event types for default availability change

* chore: add toast

---------

Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-12 13:42:57 +00:00
Albert Borsos 1a645f3110 fix: update to cancel_event dictionary key in an event related context (#14047)
to fix context for other languages than english
2024-03-12 11:48:39 +00:00
4eb11e76ea fix: Booking confirmation: Cancelled event icon regression (#14018)
* fix: #14016

* fix: #14016

* Update yarn.lock

* fix: #14016

* fix: cancelled icon

* chore: remove svg

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-03-12 10:08:12 +00:00
Alex van Andel e3a5ce5f6e fix: Timezone Select invalid labels as DST unsupported in spacetime (#14056)
* Fix: Timezone Select invalid labels as DST unsupported in spacetime

* Missed a _ -> (empty string) replace causing invalid TZs

* fix: getTimezone tests didnt support DST
2024-03-12 04:58:52 +00:00
Crowdin Bot 12e171823b New Crowdin translations by Github Action 2024-03-12 00:14:16 +00:00
3c723d6f5b feat: Managed Events V2 (#12320)
* init

* Fix event limit interlinked switches

* wip

* WIP

* fix bugs

* lock hashed link until further notice

* lock private url for managed type to stop confusion

* fix recurring

* prettier fix

* early review fixes ...

* --WIP to send only changed form fields in updateMutation

* WIP with some type fixes

* Revert "WIP with some type fixes"

This reverts commit 00f10b772d6d08af17e7c9bd2d3601d93035d9b3.

* post merge conflict resolution fixes

* further type fix

* fixed test

* fixing e2e tests

* fix test --WIP

* attempt fix test & type --WIP

* fix duplicate locale en entry

* fix locked state persistence

* adds private URL locked/unlocked functionality

* fix tests

* Fix issue where locked labels showed up in non-managed events

* adds e2e test step

* update new test

* minor fixes

* fix test

* address changes request Part 1

fixed width on the lock badge
members default location as default selection
bugfix for offset toggle duplicate within itself

* fixes locked/unlocked label for child event types

* enable choice of destination calendar for children

* Fixes width for simple lock badge

* fix type

* fixes workflows list and apps for managed event type

* restricts creation of managed event types to EE only

* further fixes --WIP

* fix unit test for handleChildrenEventTypes

* fix test --WIP

* fix type err --WIP

* fix type err in test

* fix childevent payload

* fixes hashedLink bug

* fix test step title

* lock workflow create button when workflow is locked

* fix workflow detail

* Don't rely on parameter ordering, use object instead

* Removed console.log

* Missed one

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-12 00:10:08 +00:00
Udit Takkar 1482d9a9af fix: duplicate dialog (#14055) 2024-03-11 15:16:32 +00:00
Udit Takkar 0efe25feb4 fix: org banner (#14041)
* fix: org banner

* fix: type error
2024-03-11 12:16:27 +00:00
Crowdin Bot 602192e026 New Crowdin translations by Github Action 2024-03-11 10:06:54 +00:00
Joe Au-Yeung aeb54e6401 fix: Dsync bugs (#14027)
* Fix dsync create mutation

* Small fixes

* Don't throw error when deleting dsync data

* Change to deleteMany
2024-03-11 10:02:06 +00:00
Peer Richelsen b8c1d623f3 Update Tips.tsx (#14038) 2024-03-08 12:45:49 -03:00
sean-brydonandPeer Richelsen 30e4d769e3 feat: gtm push signup event (#14031)
* feat: gtm push signup event

* chore: fire only if gtm id is defined

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-03-08 15:13:29 +00:00
3babfa7cf3 feat: add pending to role filter (#14013)
* feat: add pending to role filter

* fix: logic

* chore: remove log

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-08 15:09:45 +00:00
sean-brydonandAlex van Andel e45ee8fab3 fix: update handler (#14032)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-08 14:41:05 +00:00
Alex van Andel 3c96e122b4 fix: Broken timezone-select.test.tsx after DST change (#14035)
* fix: Broken timezone-select.test.tsx after DST change

* Surpress minor lint error
2024-03-08 13:37:53 +00:00
Crowdin Bot f21a915814 New Crowdin translations by Github Action 2024-03-08 10:50:48 +00:00
Udit Takkar 3467e2272f fix: seed organization (#14030) 2024-03-08 10:47:11 +00:00
Omar López 844592e15e v3.8.7
release.yml / release (push) Canceled after 0s
2024-03-07 16:21:10 -07:00
Crowdin Bot bdacb18d5c New Crowdin translations by Github Action 2024-03-07 23:20:39 +00:00
1b5d50c45c feat: SCIM Group Provisioning To Teams Within An Org (#13850)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Kiran K <mailtokirankk@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-03-07 16:16:53 -07:00
Omar López 1c39dbad3d fix: avatar upload flakyness (#14021) 2024-03-07 19:14:06 +00:00
Alex van AndelandOmar López e60454750e fix: Implement new avatars in insights (#13996)
* fix: Implement new avatars in insights

* fix: Exposed type errors, previously hidden by any,any type

---------

Co-authored-by: Omar López <zomars@me.com>
2024-03-07 17:00:11 +00:00
Omar LópezandPeer Richelsen 286f696302 build: adds action to manually remove faulty cache entries (#14017)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-03-07 13:53:57 -03:00
Crowdin Bot 8deaea3edd New Crowdin translations by Github Action 2024-03-07 16:39:18 +00:00
Udit TakkarandErik 7d497a621f fix: duplicate event types (#14007)
* fix: duplicate event types

* test: add test for duplicate event type

* fix: type error

* chore: pass error message

---------

Co-authored-by: Erik <erik@erosemberg.com>
2024-03-07 16:34:42 +00:00
Somay Chauhan 5c8e0483b1 fix: add future team members toggle (#14008)
* fix: add future team members toggle

fix: add future team members toggle

* code refactor

* removed updateNewTeamMemberEventTypes for regularUsers
2024-03-07 16:18:45 +00:00
Omar López 3cefa94a94 Update base.json (#13997) 2024-03-07 11:38:40 -03:00
Kedar Choudharyandsean-brydon 30549d5c14 This commit fixes #13928 (#13962)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-03-07 14:07:33 +00:00
Crowdin Bot b87972399e New Crowdin translations by Github Action 2024-03-07 13:38:37 +00:00
sean-brydonandUdit Takkar 0784df6a31 fix: slugify from creating double+ dashes (#14002)
* fix: slugify

* fix: strip emojis

* fix: remove emojis

* fix: remove emojis

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-07 13:34:18 +00:00
fc062878ed add videoCallUrl from link based video apps to metadata (#13995)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-07 04:03:09 +00:00
Crowdin Bot 33c1d80a42 New Crowdin translations by Github Action 2024-03-06 23:38:10 +00:00
1064e1181d feat: SCIM 2.0 Automatic User provisioning (#11169)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-06 16:34:21 -07:00
Peer Richelsen dfd683ffc4 feat: added banner for orgs (#13644)
Use this banner to test https://pbs.twimg.com/profile_banners/1063089191565905920/1685635865/1500x500

- [x] add backend
- [x] add file upload (require 1500x500px) in Organization/Appearance
- [x] for now, apply this to all Org event types. later we could think about sub-team overwrites, but for now org is enough


1) Upload Banner Screen

<img width="1512" alt="Screenshot 2024-02-15 at 10 26 36 PM" src="https://github.com/calcom/cal.com/assets/53316345/3c7417ee-7ef7-4138-b5f8-38f255a407fa">

2) 

<img width="1512" alt="Screenshot 2024-02-15 at 10 27 02 PM" src="https://github.com/calcom/cal.com/assets/53316345/a74dffbf-3727-4b2f-82d5-fefd55c12901">


3) Go to Org Team Event Types

<img width="1512" alt="Screenshot 2024-02-15 at 10 27 21 PM" src="https://github.com/calcom/cal.com/assets/53316345/5f188bd7-8a66-4da7-be73-1b5e178b5b3b">


__________________________________________________

DESIGNS


![CleanShot 2024-02-12 at 15 02 37@2x](https://github.com/calcom/cal.com/assets/8019099/85602120-1ced-4627-ae7c-375d33ccf1e9)


__


![CleanShot 2024-02-12 at 14 55 23@2x](https://github.com/calcom/cal.com/assets/8019099/e8a9e9c3-4e01-4a07-a2e0-7259027feb36)


![CleanShot 2024-02-12 at 14 55 34@2x](https://github.com/calcom/cal.com/assets/8019099/56500809-6c6e-49a6-9229-43190dd711b7)

mobile:
![CleanShot 2024-02-12 at 14 56 00@2x](https://github.com/calcom/cal.com/assets/8019099/f90b58b2-3104-4c00-bc18-a2a54ac8e7e7)
2024-03-06 16:18:56 -07:00
Amit Sharma 0972307998 fix: unable to remove single recurring instance from gCal (#13960) 2024-03-06 13:46:58 -05:00
Keith Williams 157f633ef2 chore: Reduce insights range to 7 days (#13991) 2024-03-06 13:02:02 +00:00
Peer RichelsenandAlex van Andel cc1c3b210a chore: added new .env for script tags (#13765)
* added new .env for script tags

* Update turbo.json

* removed meticulous

* added body script .env

* nit

* fixed injection

* Update apps/web/app/layout.tsx

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

* Ensure NEXT_PUBLIC_BODY_SCRIPTS is casted to bool

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-06 12:00:19 +00:00
Crowdin Bot fcfd47e7d2 New Crowdin translations by Github Action 2024-03-06 10:14:02 +00:00
Udit Takkar 284c4c9084 fix: Email Embed for team event types (#13990)
* fix: embeds for team event types

* chore: remove unused
2024-03-06 07:05:39 -03:00
Swain MolsterandCarina Wollendorfer 9fd3594f6c fix: add the X-Cal-Signature-256 header to MEETING_ENDED webhook requests (#13986)
* add the X-Cal-Signature-256 header to MEETING_ENDED webhook requests

* take care of null value for appId

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-03-06 00:29:30 +00:00
Udit Takkar 531371bf55 fix: team event type error when when no one is assigned (#13888)
* fix: team event when when no one is assigned

* chore: test

* chore: unfo

* chore: remove host while creating

* fix: unit tests

* fix: event in booking form

* chore: undo tests

* fix: type

* fix: move it inside
2024-03-05 16:42:35 -03:00
Morgan 99fd4fe7a4 v3.8.6
release.yml / release (push) Canceled after 0s
2024-03-05 16:20:21 -03:00
a677d0d2c4 fix: Organization Migration - Prevent the redirected username from being claimed by others. (#13675)
* Check for migrated username

* Add e2e for reserved username due to migration

* Add username change test

* Update username.ts

* fixed type checks

* Refactor to use isOrganization

* Typing

* Typing

* Removed the isOrganization function

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-03-05 17:08:44 +00:00
Crowdin Bot d8cee20d0c New Crowdin translations by Github Action 2024-03-05 14:04:30 +00:00
a7ce12e22f fix: event type redirect for orgs in preview test (routing-forms) (#13686)
* fix event type url for orgs

* fix type errors

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-03-05 13:55:50 +00:00
Crowdin Bot 88b67a8121 New Crowdin translations by Github Action 2024-03-05 11:59:20 +00:00
Crowdin Bot 7e2a1a23b9 New Crowdin translations by Github Action 2024-03-05 11:55:45 +00:00
Crowdin Bot a115a3458e New Crowdin translations by Github Action 2024-03-05 11:51:32 +00:00
Crowdin Bot ae503acccc New Crowdin translations by Github Action 2024-03-05 11:48:07 +00:00
Crowdin Bot 61e7c89f36 New Crowdin translations by Github Action 2024-03-05 11:44:09 +00:00
Crowdin Bot 60ba7a5ca2 New Crowdin translations by Github Action 2024-03-05 11:40:01 +00:00
Crowdin Bot bfa718e193 New Crowdin translations by Github Action 2024-03-05 11:36:19 +00:00
Crowdin Bot b29a52d0b0 New Crowdin translations by Github Action 2024-03-05 11:31:48 +00:00
Crowdin Bot c21d9d08ab New Crowdin translations by Github Action 2024-03-05 11:27:28 +00:00
Crowdin Bot 615e1dcfe8 New Crowdin translations by Github Action 2024-03-05 11:23:48 +00:00
Crowdin Bot a24f9b76f6 New Crowdin translations by Github Action 2024-03-05 11:19:36 +00:00
Crowdin Bot 62f8ac1c6d New Crowdin translations by Github Action 2024-03-05 11:15:32 +00:00
PritamandJoe Au-Yeung 09b91d5a7e fix: Bug with recurring bookings with seats enabled (#13840)
* fix CAL-3165

* fix wrong change

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-03-05 11:09:35 +00:00
01ac2e640a feat: improvement org member list (#13859)
* fix: sticky action bar and align checkbox

* feat: make teams filterable and clickable, total members

* fix: update members count

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-03-05 11:09:14 +00:00
Udit TakkarandJoe Au-Yeung e39bf37f91 chore: remove CF-region (#13828)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-03-05 02:42:56 +05:30
Joe Au-YeungandKeith Williams 1f845944ac chore: Add credential sync secret when requesting a token (#13907)
* Rename .env variables

* Remove instances of old variable names

* Add credential sync secret to request for new token

* Update invalid secret message

Co-authored-by: Keith Williams <keithwillcode@gmail.com>

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-03-04 19:02:48 +00:00
Keith Williams 7f53db601d chore: Add index to Team.parentId (#13950) 2024-03-04 15:19:24 -03:00
Keith Williams 1cc9f1d25d chore: Increase API rate limit (#13970) 2024-03-04 16:44:39 +00:00
Udit Takkar c6ee482478 fix: email embed for org (#13917)
* fix: email embed for org

* chore: use orgslig

* chore: remove unused

* fix: org details

* test: add tests

* chore: type err

* fix: import missing

* fix: check timeslots
2024-03-04 15:48:13 +00:00
sean-brydonandAlex van Andel 5292dd2e66 fix: add org settings into seed (#13968)
* fix: add org settings into seed

* fix: Invalid seeded team event (missing schedulingType)

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-03-04 15:11:10 +00:00
Hiten Vats f59d62f03c refactor: Removed use of depracted functions from langchain (#13953) 2024-03-04 14:38:36 +00:00
Crowdin Bot cb02268e7f New Crowdin translations by Github Action 2024-03-04 14:09:47 +00:00
Peer Richelsen e33453c50d hide terms and privacy on self-hosted (#13966) 2024-03-04 14:04:18 +00:00
Crowdin Bot 15ffeec745 New Crowdin translations by Github Action 2024-03-04 12:48:39 +00:00
Crowdin Bot b7252ed79c New Crowdin translations by Github Action 2024-03-04 12:45:40 +00:00
Crowdin Bot 9b04df9bbe New Crowdin translations by Github Action 2024-03-04 12:41:18 +00:00
Crowdin Bot 657be16a4e New Crowdin translations by Github Action 2024-03-04 12:37:08 +00:00
Crowdin Bot b0afb15939 New Crowdin translations by Github Action 2024-03-04 12:33:30 +00:00
Vikas VermaandVikas Verma 3a9b8a9bf6 fix: url input field inconsistent (#13956)
* fix:Url Input feild Inconsistent UI/UX #13955

* Lint issue fixes

* Update UsernameTextfield.tsx

* Update UsernameTextfield.tsx

Lint issue fix

---------

Co-authored-by: Vikas Verma <inboxvikasverma@gmail.com>
2024-03-04 12:29:35 +00:00
92a919069d feat: Implement secondary email (#13678)
* Implement secondary email

* Fix already existing tests failing

* Added tests for secondary email feature

* Skip email verification if user is changing the primary email to a verified secondary email

* Fix type errors in tests

* Fix email becoming unverified when switched between primary and secondary email

* Added a check to prevent prisma error from throwing up due to duplicate records

* Improved error handling when adding a secondary email

* Add test for resend verification email flow for secondary emails and validation of invite link

* Add a new column to link secondary emails with verification tokens

* Fix failing to update email to an unverified secondary email of the same user

* Fix failing tests

* Change text of resend verification email

* Add ability to use the verified secondary emails to get the event details to

* Fix type errors

* Fix failing e2e tests

* Fix failing unit tests

* Fix failing type checks

* Fix secondary verification email subject

* Fix failing e2e tests

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-03-02 22:29:40 +00:00
zomars 0071d16575 Update yarn.lock 2024-03-01 13:26:00 -07:00
Joe Au-Yeung 23d22ee7ea Add missing lock file (#13949) 2024-03-01 14:28:10 -05:00
3a4f944aac feat: jelly integration (#13929)
* v1

* don't need to search DB for user

* return early if no user

* use one deleteMany to ensure only 1 credential

* rm expiration

* fix API request

* proper type

* Update packages/app-store/jelly/lib/VideoApiAdapter.ts

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

* Update packages/app-store/jelly/lib/VideoApiAdapter.ts

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

* Update packages/app-store/jelly/lib/VideoApiAdapter.ts

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

* Update packages/app-store/jelly/api/callback.ts

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

* Fix callback URL

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-03-01 16:45:41 +00:00
Crowdin Bot 3eadae0aad New Crowdin translations by Github Action 2024-03-01 15:53:12 +00:00
Crowdin Bot 0cc2fe2d7f New Crowdin translations by Github Action 2024-03-01 15:42:22 +00:00
0970c77009 fix: Revert -> "Revert "chore: Chore team metadata table + isOrganization migration (#13700)
* Revert "Revert "chore: Chore team metadata table + isOrganization migration from metadata (#12828)""

This reverts commit 2408338ed4.

* Remove constraint slug,isOrganization and reset migration

* fix: conflicts

* change schema to bust cache

* Fix issues reported by TS

* change schema to bust cache

* Review fixes

* Colaesce for orgAutoAcceptEmail as well

* Fix missing negation

---------

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-03-01 15:04:28 +00:00
David Somers 5b733765f4 Update api image Dockerfile (#13890)
- Add DATABASE_URL and DATABASE_DIRECT_URL, used during build
- Copy tests folder, referenced by code run during build.
2024-03-01 09:18:02 -03:00
Yash RajandCarina Wollendorfer 83fe4d6625 fix: font bold font semibold (#13939)
* Fixed issue#13879 changed font-bold to font-semibold with font-cal

* Revert yarn.lock changes

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-03-01 06:53:16 -03:00
Joe Au-Yeungandzomars 91beeeb525 fix: Google Profile Picture Update Race Condition (#13711)
* Minor fixes

* Discard changes to packages/app-store/ics-feedcalendar/config.json

* Add comment

* refactor: denesting and conditional profile update

---------

Co-authored-by: zomars <zomars@me.com>
2024-02-29 20:01:13 +00:00
81c0b1ffe9 fix: broken google account avatar fix (#13892)
* fix: broken google account avatar fix

* fix: use zod for checking absolute url

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-29 19:59:23 +00:00
Hitesh SisaraandUdit Takkar aedc88f975 feat: show top banner for broken integrations [Cal 3125] (#13720)
* added InvalidAppCredentialBanner

* Create InvalidAppCredentialsBanner.tsx

* Update getUserTopBanners.handler.ts

* Create checkForInvalidAppCredentials.ts

* Update common.json

* Update common.json

* Update getUserTopBanners.handler.ts

* Update checkForInvalidAppCredentials.ts

* also included team's credentials

* reverse yarnlock changes

* fix .env

* fixed types

* chore: translation

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-29 19:58:46 +00:00
sean-brydonandOmar López 67256b5b6e fix: toLowercase on emailChangeWaitingForVerification (#13940)
Co-authored-by: Omar López <zomars@me.com>
2024-02-29 19:43:48 +00:00
Carina WollendorferandCarinaWolli ef02d64f90 fix: workfow e2e tests (#13938)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-29 11:16:36 -07:00
Omar López 8685862617 v3.8.5
release.yml / release (push) Canceled after 0s
2024-02-29 10:26:06 -07:00
zomars 7422488b5d Update yarn.lock 2024-02-28 15:51:40 -07:00
zomars c3e1481d6a Update yarn.lock 2024-02-28 15:41:35 -07:00
Rushikesh a1d1883b34 Large padding on small screens for embedded booking widgets. (#13855) 2024-02-28 14:17:06 +00:00
Crowdin Bot f2ae94a190 New Crowdin translations by Github Action 2024-02-28 14:12:14 +00:00
Alex van Andel b82a597645 fix: invalid type check occupied seats (#13830)
* fix: Type check failure in occupiedSeats

* fix: Invalid handling of occupied seats hidden by earlier error
2024-02-28 11:07:34 -03:00
sean-brydonandUdit Takkar 5ba0ee8e85 fix: impersonation detecting orgs correctly. (#13889)
* feat:fix correctly detect impersonation organization

* chore: remove logs

* Update packages/features/ee/impersonation/lib/ImpersonationProvider.ts

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-28 12:16:46 +00:00
PritamandUdit Takkar 1463f97f14 fix: Currency Not Reflecting Correctly #13845 (#13847)
* fix currency problem

* fix type errors

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-28 17:14:26 +05:30
Nikhil Anand 34fea35e83 fix: text overflowing in username change popup #13846 (#13911) 2024-02-28 15:06:41 +05:30
Alex van AndelandUdit Takkar 2d75048037 fix: Test flakiness due to unfilled name/email (#13900)
* fix: Test flakiness due to unfilled name/email

* Better toast-success checks

* Revert "test: Create E2E tests for bookings with custom/required Phone + other questions (#11502)"

This reverts commit 614741d207.

* Revert "test: Create E2E tests for bookings with custom/required Multiple Emails + other questions  (teste2e-multipleEmailQuestion) (#11565)"

This reverts commit d1d50b0d91.

* Revert "test: Create E2E tests for bookings with custom/required Long Text + other questions (teste2e-longTextQuestion) (#11559)"

This reverts commit 96810b5ba1.

* Revert "test: Check the webhook event tab and your funtionalities (#12433)"

This reverts commit 9db9204088.

* Re-instate regularBookings

* Fixtures used by other tests

* Removed dead code from regularBookings.ts

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-28 06:07:32 +00:00
Carina WollendorferandCarinaWolli b549735334 santize right before sending mail (#13876)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-27 17:57:26 -03:00
Anwar SadathandCarinaWolli 86c379817e fix: Fix calendar event included in the workflow, doesn't show the meeting link (#13872)
* Fix calendar event included in the workflow, doesn't show the meeting link

* Fix type error

* fix public video call url

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-27 15:50:40 +00:00
Udit Takkar 5d1976295c fix: hover state (#13862)
release.yml / release (push) Canceled after 0s
2024-02-27 14:32:59 +00:00
Morgan e9b1fa1f50 v3.8.4 2024-02-27 16:31:14 +02:00
Udit Takkar 80080dda7a fix: missing userPrimaryEmail in workflows (#13875)
* fix: missing userPrimaryEmail in workflows

* chore

* fix: type err
2024-02-27 09:17:34 -05:00
Udit Takkar 27c25e1f95 chore: use transaction (#13874)
* fix: use transaction

* chore
2024-02-27 10:17:46 -03:00
Shyam Raghuwanshi 858be0f5d8 fix ics-feedcalendar logo not showing (#13886) 2024-02-27 11:43:25 +00:00
Hariom Balhara 68d0090f50 Make sure that state doesnt reset (#13880) 2024-02-27 07:51:11 -03:00
Udit Takkar a43fa13b11 fix: infinite rendering in event setup tab (#13877) 2024-02-26 22:18:58 +00:00
Pritam 2bf6da30b3 fix periodDays dirtystate (#13851) 2024-02-26 19:17:53 +00:00
Alex van Andel d85e0b51e4 fix: Prevent duplicate bookings with idempotency key (#13865)
* fix: Prevent duplicate bookings with idempotency key

* Add idempotencyKey to builder.ts type fix

* Add tests for new idempotency key (booking create)

* Add endTime to idempotency key, it should be possible to book different durations

* Move reschedule before the create so the idempotency key is unset
2024-02-26 18:33:22 +00:00
Carina WollendorferandCarinaWolli 55a317d677 fix: video call url in event location for recurring events (#13842)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-26 10:44:07 -05:00
Crowdin Bot 5fff8fc58c New Crowdin translations by Github Action 2024-02-26 14:36:48 +00:00
Udit Takkar 0d68767d0a fix: seed script (#13868) 2024-02-26 14:31:25 +00:00
Shyam Raghuwanshi 2051e87436 fix: username overflow (#13832) 2024-02-23 23:01:43 +00:00
Udit Takkar 77a157a12b fix: show go back button after expiry (#13833) 2024-02-23 22:20:46 +00:00
Omar López c4f25f2c44 build: deploy script for gradual migration (#13834) 2024-02-23 12:38:13 -07:00
Peer Richelsen d967facb63 feat: demodesk app (#13733) 2024-02-23 10:14:37 -05:00
c6dad39fb0 test: Create E2E tests for Analytics Apps (teste2e-analyticsApps) (#13695)
* refactor

* refactor

* refactor

* refactor

* fix failing check

* Update apps.ts

* fix failing check

* fix failing check

* fix failing check

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2024-02-22 21:54:49 -03:00
zomars eec7f4d7d4 Update yarn.lock 2024-02-22 15:00:52 -07:00
Udit Takkar 95d5824ba9 fix: og image (#13829) 2024-02-22 10:46:33 -07:00
Somay Chauhan 5f95a4d041 fix: New Team Members Join Org as OWNER/ADMIN (#13822) 2024-02-22 11:46:28 -05:00
Keith Williams 8e97444129 v3.8.3
release.yml / release (push) Canceled after 0s
2024-02-22 10:16:18 -03:00
Crowdin Bot 8d8264b5f5 New Crowdin translations by Github Action 2024-02-22 12:48:57 +00:00
2ad81221d0 chore: Added a period after undone (#13824)
* Added period after undone

* chore: undo yarn.lock

* Revert yarn.lock

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-22 09:44:43 -03:00
3d1ac4523f fix: Update credentialId when null for a given selectedCalendar (#13735)
* fix: Update credentialId when null for a given selectedCalendar

* Add returnTo return path

* chore

* chore: undo return

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-22 12:05:46 +00:00
Alex van Andel b38807cb0f fix: Type check failure in occupiedSeats (#13805) 2024-02-22 10:27:55 +00:00
Peer Richelsen 11fb387370 feat: let org admins change usernames (#13384) 2024-02-22 07:48:46 +00:00
Crowdin Bot 1546f9115d New Crowdin translations by Github Action 2024-02-22 07:42:13 +00:00
Peer RichelsenandUdit Takkar 40ecd5dd9d chore: removing wrong translations about {{consoleUrl}} (#13802)
* removing wrong translations

* fixed source string

* revert: eng translation

* chore: remove translation

* chore

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-22 07:38:47 +00:00
Omar López ac6ac14a23 fix: more og avatar fixes (#13817) 2024-02-22 07:25:31 +00:00
Bailey Pumfleet c6534160e4 chore: Shorten "aplicaciones instaladas" to "apps instaladas" (#13806) 2024-02-22 07:21:43 +00:00
Keith Williams aa2db562b8 fix: Bookings API (#13815) 2024-02-21 16:05:53 -07:00
Carina WollendorferandCarinaWolli b7ed314be2 fix: log more info on sendEmail error (#13813)
* add from and subject to error message

* remove comment

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-21 20:59:45 +00:00
Udit TakkarandCarinaWolli d102268b33 fix: sdk action events (#13755)
* fix: sdk action events

* fix: type error

* fix: type err

* fix: reschedule event

* chore: remove timezone

* fix: feedback

* chore: use bookingInfo

* fix type error

* fix type errror

* add cancellation reason

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-21 14:25:24 -05:00
Somay ChauhanandUdit Takkar f4397d6593 fix: error on signup page while inviting a member to an org (#13807)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-21 18:49:51 +00:00
Keith Williams 192603a1fa fix: No available users found near midnight (#13801)
* fix: No available users found near midnight

* Update date-ranges.ts
2024-02-21 10:58:38 -07:00
Keith Williams a3d969680f chore: Including original start and end dates from request in log (#13800) 2024-02-21 13:22:42 -03:00
Crowdin Bot adc3b3aec6 New Crowdin translations by Github Action 2024-02-21 15:27:50 +00:00
sean-brydonandUdit Takkar 92286e2453 feat: signup redirect query param after onboarding (#13761)
* feat:redirect - if signup url has url to goto

* fix: dont throw if url is invalid - just dont set

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-21 20:53:09 +05:30
51b2a15cb0 feat: Settings for dashboard theme that is separate from system (#13779)
* feat(db): add new col: user appTheme

* feat(event-types page): apply appTheme color

* add appTheme for trpc

* feat: mutate main page theme

* feat: add english translation for app theme update section

* modify keys in common (en)

* add woring

* Revert yarn.lock to its state before unintended changes

* feat(i18n): add back the i18n string (for dark/light mode switching)

* refactor: refactor ThemeLabel

* fix: add new user field "appTheme" to test files

* chore: modify TODO comment

* chore: update common.json (en) for "theme"

since we had appTheme now, I think it's better to update the wording here to avoid confusion

* fix: update button's data-testid to fix e2e test error

* chore: remove comment

* fix: fix brand-color-not-apply bug

* solve type error

* fix: fix type error

* fix: use correct storageKey for booker / dashboard

* fix: tidy up

* fix: skeleton

---------

Co-authored-by: swh00tw <a6140000@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-21 13:02:40 +00:00
Morgan fa9a099dd6 fix: prefillFormParams guest type (#13798) 2024-02-21 12:36:48 +00:00
ErikandOmar López 83ceb46cca chore: Refactor feature flags (#13734)
Co-authored-by: Omar López <zomars@me.com>
2024-02-20 21:49:48 -03:00
Keith Williams 75d5576e17 v3.8.2
release.yml / release (push) Canceled after 0s
2024-02-20 21:41:13 -03:00
Keith Williams 0a6dde4fcc chore: Add extra logging for no available users error (#13791)
* chore: Add extra logging for no available users error

* Changed warn to info
2024-02-20 23:51:31 +00:00
Omar López 73b5839284 fix: og images avatars (#13790)
* fix og images avatars

* Update OgImages.tsx

* More fixes

* Update getPublicEvent.ts

* Update HeadSeo.tsx
2024-02-20 23:04:59 +00:00
Peer Richelsen 1a42b94b13 feat: added linears google calendar app (#13789) 2024-02-20 20:08:08 +00:00
Erik acb910f680 v3.8.1
release.yml / release (push) Canceled after 0s
2024-02-20 15:20:09 -03:00
Crowdin Bot 38e857f9a9 New Crowdin translations by Github Action 2024-02-20 15:30:02 +00:00
Crowdin Bot b8149dee36 New Crowdin translations by Github Action 2024-02-20 15:25:22 +00:00
Crowdin Bot f583ef40f2 New Crowdin translations by Github Action 2024-02-20 15:19:54 +00:00
Keith WilliamsandOmar López 71574488c5 fix: No available users found (#13738)
* fix: No available users found

* Update getUserAvailability.ts

* Update getUserAvailability.ts

* Update handleNewBooking.ts

* Reusing the parsed dates throughout the full stack

* Fixed typo

* Added unit tests for stringToDayjs

* Fixing types

* Reverted the getSchedule utc change since that endpoint takes in UTC formatted dates but with a given timezone

* chore: Add test replicating 'no available users' error

* Test is passing

* Name clarifications

* Test clean up

* Update packages/features/bookings/lib/handleNewBooking.ts

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

* Update handleNewBooking.ts

* Fixed issue where date overrides ending at midnight don't allow bookings

* Removed comment

---------

Co-authored-by: Omar López <zomars@me.com>
2024-02-20 12:15:05 -03:00
Omar López 008e0e43a2 chore: Drop deprecated password column (#13698) 2024-02-20 14:52:51 +00:00
Crowdin Bot da22eda662 New Crowdin translations by Github Action 2024-02-20 10:27:49 +00:00
sean-brydonandUdit Takkar f94d696885 fix: text-overflow (#13775)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-20 10:23:49 +00:00
Crowdin Bot 9e84eb1b2c New Crowdin translations by Github Action 2024-02-19 21:01:08 +00:00
zomars 4c549811a1 chore: Add explainer to RawHtml 2024-02-19 13:57:01 -07:00
Hariom Balhara 05502f0cfb fix: Old username link on org user's profile. (#13753) 2024-02-19 19:09:11 +00:00
mokkin e436b8e06b fix: correct German translation (#13742) 2024-02-19 19:07:18 +00:00
Crowdin Bot 8146cf8c93 New Crowdin translations by Github Action 2024-02-19 16:55:08 +00:00
Henrik KleeandKeith Williams c05f595ef0 reimplement #12681 (#13639)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-19 13:51:09 -03:00
Crowdin Bot 0a795f5363 New Crowdin translations by Github Action 2024-02-16 17:53:19 +00:00
5b50a469b2 fix: double bookings on seated event (#13406)
* fix: double bookings on seated event

* test: correcting handleSeats.test.ts

fix: seat rescheduling

* Add test for booking w/o bookingUid in req

* Add back `bookingUid`

* add 'reqBookingUid' parameter

* Add `reqBookingUid`

* Fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-02-16 12:48:57 -05:00
Alex van Andel c08365f09c feat: Implement orgs/teams logoUrls (#13699)
* feat: Implement orgs/teams logoUrls

* Use getAvatarUrl in team/[slug]

* Extract uploadLogo to standalone file for reuse

* Allow removing the team/organisation logo

* fix: API build fail due to server-only, removed
2024-02-16 10:35:53 -03:00
Shyam Raghuwanshi 477e102388 feat: Split-large-code-file-functions (#13592)
* Split large code file functions

* move the files from root into lib
2024-02-16 10:32:00 -03:00
Udit Takkar e1b10b4ec5 fix: update email flaky test (#13727) 2024-02-16 10:29:08 -03:00
Hariom BalharaandUdit Takkar ff6db3c572 fix: team invite signup not working (#13717)
* Fix team invite signup not working

* Add unit test

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-16 12:50:07 +00:00
Crowdin Bot 4fa3fdef97 New Crowdin translations by Github Action 2024-02-16 12:35:41 +00:00
Hariom Balhara f29f6632e8 fix: Managed children event listing for organization members (#13722)
* Fetch event-types by userId for childEvents and filter out those events that dont belong to the user

* Add test
2024-02-16 09:32:05 -03:00
Crowdin Bot 36747988e6 New Crowdin translations by Github Action 2024-02-16 11:41:38 +00:00
Morgan 3dd312b88b chore: booker web wrapper (#13676)
* chore: booker web wrapper

* fixup! chore: booker web wrapper

* fixup! fixup! chore: booker web wrapper

* fixup! fixup! fixup! chore: booker web wrapper

* fixup! fixup! fixup! fixup! chore: booker web wrapper

* fix: CreateANewOrganizationForm verify dialog

* fix: EventBooker not rerendering when it should

* fix: init booker store properly

* fixup! fix: init booker store properly

* fix: useMemo EventBooker

* fix: remove unnecessary file
2024-02-16 11:38:12 +00:00
Crowdin Bot 3c9e9329b0 New Crowdin translations by Github Action 2024-02-16 10:58:45 +00:00
Udit Takkar 8617f43a8e chore: add toast and tooltip (#13724)
* chore: add toast and tooltop

* chore: update text
2024-02-16 10:54:31 +00:00
Crowdin Bot 5f0eb652b0 New Crowdin translations by Github Action 2024-02-16 08:35:42 +00:00
JackandUdit Takkar 26d7301885 fix: [CAL-3141] (+1) badge not aligned (#13718)
* Fixes [CAL-3141] (+1) badge not aligned

* fix: centering

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-16 08:32:46 +00:00
Crowdin Bot ffc4d22ff1 New Crowdin translations by Github Action 2024-02-15 22:47:32 +00:00
ee286a600f refactor: Only send dirty fields for event type update call (#13292)
* init

* typefix maybe

* fix type

* fix e2e

* Extract delete dialog to its own function to optimise rerender

* wip

* Delete isEqual

* adds eslint tsignore comment

* only update changed fields

* fix type err

* --WIP

* fix type err isLoading—>isPending

* isLoading—>isPending

* post conflict resoution fix

* shouldDirty minimumBookingNotice

* adds shouldDirty to setValues

* reset form to ensure updated default values

* shouldDirty changes and fixed deep check of dirty

* --WIP

* should dirty recurring

* shouldDirty team tab

* multiple-fixes

* adding console log to test something in e2e

* shouldDirty apps tab

* fixes managed event type children association

* fixed managed events update

* dirty managed fix

* fixes some onChange versus setValue and periodCountCalendarDays

* Seeing different behaviour explicitly unwrapping formState

* only mutate if at least 1 field dirty

* fix type error

* bunch of fixes

* --WIP

* update only dirtyFields for Managed Events existing children

* Fixes update old managed event type tests --WIP

* Fix unit tests

A bunch of unit test fixes which started failing
due to update fields only requiring actual updated
fields. some other small fixes also included

* type fix

* remove unnecessary comment

* Adds dirtyField to instant event

* fix: add shouldDirty

* fixes availability and team tabs, and disable save button unless dirty

* fix for hosts and bookerLayout

* add shouldDirty when resetting hosts after changing schedulingType

* add missing shouldDirty to useEventTypeDestinationCalendarEmail

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-15 22:44:20 +00:00
Udit Takkar 72c493ee21 chore: add instruction for #12821 (#13713)
release.yml / release (push) Canceled after 0s
2024-02-16 01:22:12 +05:30
Alex van Andel 5bd0428d18 v3.8.0
release.yml / release (push) Canceled after 0s
2024-02-15 15:54:47 +00:00
c7089b9db6 feat: store event-type filters to localStorage (#13407)
* Updated useTypedQuery to store values to localstorage

* removed the redundant function params

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-02-15 10:03:42 -05:00
Morgan 81ad00a816 fix: safely delete userPassword (#13705)
* fix: safely delete userPassword

* fixup! fix: safely delete userPassword

* fixup! Merge branch 'main' into fix-safely-delete-userpassword
2024-02-15 13:19:31 +00:00
Udit TakkarandMorgan b5730da56d fix: password reset when provider is changed (#13706)
* fix: password reset when provider is changed

* chore: remove log

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-02-15 13:19:11 +00:00
Zeeshan BhatiandJoe Au-Yeung 9455d14ad5 feat: Add Google Profile Photo when connecting Google Calendar (#13627)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-15 11:51:24 +00:00
LAVIN RAJ MOHANandUdit Takkar a056160404 fix(fix/null-safety):#13673 (#13674)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-15 07:17:19 +00:00
Omar López 6d53cd84f8 v3.7.19
release.yml / release (push) Canceled after 0s
2024-02-14 14:19:56 -07:00
Omar López 30cb8f745d revert: Revert UserPassword (#13680) (#13685)
* Revert "revert: UserPassword (#13680)"

This reverts commit 0a68c6d048.

* fix: avoid updates for possible missing passwords
2024-02-14 16:53:27 +00:00
bafa3c97ac fix: Organizations Admin improvements (#13656)
* Admin improvements

* chore: improve spacing

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-14 10:34:45 -03:00
Alex van Andel bb48ccbad9 v3.7.18
release.yml / release (push) Canceled after 0s
2024-02-14 12:42:33 +00:00
Alex van Andel 0a68c6d048 revert: UserPassword (#13680) 2024-02-14 12:36:29 +00:00
Peer RichelsenandHariom bb9f8e5d38 fix: dont link to org url inside embeds (#13668)
* dont link to org url inside embeds

* Add comment

* Fix type issue

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-02-14 11:31:24 +05:30
Carina WollendorferandCarinaWolli c99bb190f1 fix: route always selects 'custom' after saving an event redirect (#13665)
* fix to not always switch to custom on fresh load

* type fix

* type fix

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-13 22:08:03 +00:00
Omar López f0d2a45fdf v3.7.17
release.yml / release (push) Canceled after 0s
2024-02-13 14:58:51 -07:00
zomars e5f9315ee6 fix: faulty redirect 2024-02-13 12:45:21 -07:00
zomars f7782b2080 fix: hotfix failed migration 2024-02-13 12:16:38 -07:00
Omar López e1f2dbab84 fix: amend user password migration (#13662) 2024-02-13 18:40:50 +00:00
Adam Azzam c2edbbcea7 Update SettingsToggle.tsx (#13660) 2024-02-13 22:54:27 +05:30
c7e78cc181 fix: AI build (#13657)
* fix: AI build

* Updated zod to 3.22.4

* yarn.lock

* Updated yarn.lock to prevent next/tw downgrade

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-02-13 16:10:08 +00:00
7ba1557d1d fix: use destination calendar email (#12821)
* fix: use destination calendar email

* fix: success page

* feat: add primary email in destination calendar

* fix: set destination bug

* chore: fix tests

* test: add test for different email

* wip: save progress

* feat: add switch and email in booking

* fix: use userPrimaryEmail

* chore: fix type errr

* chore: update buileder

* fix: type err in advanced tab

* fix: unit tests

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

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

* fix: cancel, request reschedule, paymeny

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-02-13 10:05:00 -05:00
Suyash SrivastavaandPeer Richelsen d5b45b757a chore: Removed Skiff from the App Store (#13650)
* rule for Prisma that includes using only "true" and not a field selector

* Removed Skiff from the app store

* Apply suggestions from code review

* Delete packages/eslint-plugin/src/rules/disallow-prisma-include-only-true-not-field-selector.ts

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
2024-02-13 11:13:12 +00:00
bb3b262bd7 fix: Add location to ics file (#13646)
* Add location to ics file

* Add tests

* fix: type err

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-13 16:35:04 +05:30
d4f41e803a fix: Collective Events: Duration parameter in link not working (#13523)
* set the selectedDuration in store to duration passed in query

* Pass durationConfig prop to Booker component in /team/[slig]/[type] pages

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-02-13 12:23:27 +05:30
74eafa59e1 fix: loading animation of update buttons (#13131)
* fixed loading animation of update buttons

* Update BookerLayoutSelector.tsx

* fix: update button animation in settings appearance page

* Update appearance.tsx

* Update appearance.tsx

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-13 06:32:21 +00:00
Udit Takkarandzomars e6cd016cad fix: usage of CAL_URL (#13326)
Co-authored-by: zomars <zomars@me.com>
2024-02-13 06:03:45 +00:00
Omar López 7b0d5959cb refactor: migrates user password to it's own table (#13628) 2024-02-12 14:13:21 -07:00
Alex van Andel 7702ded85e v3.7.16
release.yml / release (push) Canceled after 0s
2024-02-12 18:31:59 +00:00
Alex van Andel 5acb3e8f97 fix: Missing date ranges when specifying a specific slot time (#13645) 2024-02-12 18:25:49 +00:00
sean-brydon 41046aaf27 fix: use org logo for sub teams (#13632)
* fix: use org logo for sub teams

* chore: remove unneeded type
2024-02-12 12:59:25 -03:00
Hariom Balhara 7cffb30daa fix: Errors due to namespacing of embed (#13637) 2024-02-12 07:46:08 -07:00
Alex van Andel 26bacb6c8e fix: Regenerated lock file 2024-02-12 13:38:13 +00:00
Crowdin Bot 01b5c1f11e New Crowdin translations by Github Action 2024-02-11 14:32:05 +00:00
Anik Dhabal Babu f5ba04599b fix: unable to setup localy (#13634) 2024-02-11 14:28:40 +00:00
Brace Sproul 994e602c8d refactor: Bump LangChain to 0.1 & update other LangChain imports (#13622) 2024-02-10 19:30:42 +00:00
Carina WollendorferandCarinaWolli a776c72d48 fix: improvements for variables in routing-from routes (#13624)
* add variable description

* type fixes

* type fixes

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-10 19:28:59 +00:00
sean-brydon 6adb5b413d fix: troubleshooter scroll (#13631) 2024-02-10 19:26:21 +00:00
alannnc b183a5779a fix: oncancel modal action week layout (#13625) 2024-02-09 23:02:05 +00:00
alannnc 16c1cf7b23 fix ooo section to use settings layout (#13623) 2024-02-09 19:57:08 -03:00
Alex van Andel cf4ee142de v3.7.15
release.yml / release (push) Canceled after 0s
2024-02-09 17:46:11 +00:00
Keith Williams a5d99bbec9 chore: Remove platform from label ignore list (#13618) 2024-02-09 12:42:56 -03:00
Crowdin Bot 5d0468a986 New Crowdin translations by Github Action 2024-02-09 14:11:25 +00:00
Hariom Balhara 7fef9db4ce fix: Reduce payload even further. (#13599)
* Revert "Revert event-type profile based querying (#13588)"

This reverts commit c0c40185fc.

* Add huge event-types seeder

* Fix missing where causing wrong event-types for user
2024-02-09 09:07:26 -05:00
Carina WollendorferandCarinaWolli 296a367955 feat: round-robin priority ranking (#13566)
* improve design of assignment tab

* design fix

* add prioity dialog

* implement UI

* add backend to save priorities

* add priorty to rr algorithm + tests

* fix defaulValue for select + added translations

* add toggle for round robin fixed hosts

* don't show priorities for fixed hosts

* type fixes

* code clean up and type fixes

* clean up getValues and setValue

* code clean up

* fix tests

* code clean up

* fix undefined check

* allow null for priority

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-09 12:12:06 +00:00
Crowdin Bot 1ff2d51ee5 New Crowdin translations by Github Action 2024-02-09 10:17:14 +00:00
Morgan 092cb0f691 fix: organizer_default_conferencing_app translatable key in app location (#13614) 2024-02-09 10:13:30 +00:00
zomars 0c74a98721 upgrades copycat 2024-02-08 14:11:24 -07:00
Omar LópezandKeith Williams 2082a4f4ed chore: upgrades boxyhq jackson (#13477)
* chore: upgrades boxyhq jackson

* Update jackson.ts

reduces bundle size

* Update yarn.lock

* Update apps/web/next.config.js

* Upgrades again

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-08 17:51:29 -03:00
Anant JainandUdit Takkar f06ef84f61 fix: Fixes teams UI load issue (#13593)
* fixes teams uiload issue

* revert: yarn.lock

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-02-08 18:58:21 +00:00
Udit Takkar 1080d7f88f fix: update valid_for_secs (#13604) 2024-02-08 18:46:02 +00:00
4ebd52c9af test: Create unit tests for the questions (teste2e-multiSelectQuestion) (#11569)
* Remove unnecessary changes

* add changes

* Requested changes

* Requested changes

* FIx failing tests

* FIx failing tests

* Update regularBookings.ts

* FIx failing tests

* Refactor

* add unit tests for all questions

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@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: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-08 15:00:19 -03:00
Morgan e0ac95578c chore: refactor handle new reccuring booking (#13597)
* chore: refactor handle new reccuring booking

* fixup! chore: refactor handle new reccuring booking

* fixup! fixup! chore: refactor handle new reccuring booking
2024-02-08 16:59:49 +00:00
Hariom BalharaandJoe Au-Yeung 473012f2c0 fix: organization user shouldn't be asked for payment for a premium username (#13535)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-08 16:46:21 +00:00
Hariom BalharaandJoe Au-Yeung 1293588ac4 test: Bookings: Add more automated tests for organization (#13576)
* Avoid selecting unused props

* Add automated tests

* Add existing user invite and booking

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-08 16:20:00 +00:00
Hariom Balhara c0c40185fc Revert event-type profile based querying (#13588) 2024-02-08 09:40:17 -03:00
3348e7be2f fix: prevent duplicate calendar account linking (#13310)
* fix: prevent signing up multiple times from same account

* revert: lark calendar changes

* credential clean up if duplicate

* fix code duplication and check before credential creation

* feat: useRouterQuery allow unset queryParam

* feat: showToast on account duplication attempt

* Small tweak to copy

* Updated other calendars not to use checkDuplicateCalendar

* Add account already linked to apps/installed/calendar

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-08 10:52:32 +00:00
97dcb2a2a9 fix: add missing title (#13307)
* add missing title

* add

* update

* update

* update

* fix lint

* Update AppsLayout.tsx

* update

* Update bookings-listing-view.tsx

* update

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-07 17:49:46 -03:00
Udit Takkar 5dad78f8fe fix: 2FA OTP flaky test (#13583)
* fix: 2FA OTP flaky test

* chore: change timeout
2024-02-07 19:02:23 +00:00
zomars 88cc83a0aa Update transform.ts 2024-02-07 10:57:06 -07:00
zomars 9dddf5e525 fix: docker's toomanyrequests error 2024-02-07 10:33:47 -07:00
Lauris Skraucis 9df9f9bb6b refactor: trpc get schedule handler (#13586) 2024-02-07 09:43:19 -07:00
LaneandLane c4803be323 fix #13527 (#13579)
Co-authored-by: Lane <>
2024-02-07 14:08:06 +00:00
Hariom BalharaandUdit Takkar 34d360a4ec fix: Reduce payload in event-type/getByViewer (#13580)
* Avoid selecting unused props

* Reduce payload

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-07 09:40:45 -03:00
0e6f44e2b9 feat: email confirmation on change (#13443)
* feat:show old and new email in confirmation dialog

* fix: update i18n

* chore: update styles

* wip: change email upon verification

* fix: email being changed w/o need to logout

* chore: tidy up - remove update sessison (WIP) will try do this serverside

* wip: try new approach of serverside call to nextjs then client update

* fix: tests

* fix: update without logout

* i18n: use i18n in toast

* fix: locale ready toast

* fix: restore email api template

* fix: revert changes from wrong branch

* fix: restore yarn.lock

* feat: happy e2e path

* fix: verification disabled e2e tests

* fix:move toast

* chore: cleanup early return

* fix: await input selector

* Update apps/web/pages/auth/verify-email-change.tsx

* fix: feedback

* fix: update the way we update session

* fix: reset password -> email verified

* fix: email change toast failure

* tests: add tests for error path

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-07 10:31:00 +00:00
Somay ChauhanandKeith Williams e377997d49 fix: filtering events bug in bookings page (#13447)
* fix: filtering events bug in bookings page

* fix: handle edge case if we change hosts in event-settings/assignment

* update: handled edge case

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-07 10:01:48 +00:00
Somay ChauhanandKeith Williams a0451e34c1 fix: Ensure event type duplication respects private link setting (#13438)
* fix: Ensure event type duplication respects private link setting

* chode refactor

* Fix: Handler throwing error on creating duplicate of a collective event having private-link switched on.

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-07 09:30:28 +01:00
Omar López d2b4760e3a refactor: removed isNotAnApiCall (#13568) 2024-02-07 04:43:36 -03:00
8469c91973 feat: integrate formbricks for team disband action (#13362)
* feat: integrate formmbricks for team disband action

* fix: attribute typing & duplicate action

* fix: track action uniformly across repo

* fix: move action tracking on success of team disband mutation

* fix: yarn lock file

* feat: bump formbricks/js to latest version

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-07 04:14:26 -03:00
Keith Williams 0990549e7f perf: Don't process date overrides outside of requested boundaries (#13495)
* perf: Don't process date overrides outside of requested boundaries

* Added ability to skip override calculations when not used

* Only instantiating utc date for overrides once

* Removed performance tracing

* Cleanup

* Fixed broken tests

* Fix type

* In process of fixing types

* Types

* Fixed types

* Removed unnecessary performance.now() call

* Changed override date check to use isBetween

* Attempting to fix availability test

* Put code back to test availability

* Put back isBetween because confirmed it wasn't triggering the new test failures

* Removed date check to see if this is breaking it

* Put the code back since the test failed without it too

* Skipping test to unblock pipeline unless research is done

* Removed steps to test

* Removing the piece around the troubleshooter

* 1 more try

* Adding an extra wait

* Took out the boolean check to see if that's the issue

* Put back the check

* Trying to isolate the problem

* Changed how the test loads the troubleshooter

* Changed to OR to capture range better

* Using the proper date instead of 1/1/1970

* Update availability.e2e.ts
2024-02-07 03:21:43 +00:00
Omar López e884348807 chore: adds failsafe auto migrations back (#13569) 2024-02-06 22:59:01 -03:00
zomars 5684ce2d28 Update action.yml
Updates actions
2024-02-06 17:39:19 -07:00
zomars bd8ac12f14 Update yarn.lock 2024-02-06 17:15:21 -07:00
Keith Williams 512628e8ab perf: Load bookings used for limits in bulk (#13474) 2024-02-06 16:56:05 -07:00
Keith Williams 903a154dbc v3.7.14
release.yml / release (push) Canceled after 0s
2024-02-06 19:52:05 -03:00
Carina WollendorferandCarinaWolli 1a3d54a0d0 fix: org user event types with same event slug (#13571)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-02-06 22:50:02 +00:00
Udit Takkar a5970cb1a8 fix: 2FA falky test (#13567)
release.yml / release (push) Canceled after 0s
* fix: 2FA falky test

* chore: increasing limit
2024-02-06 20:07:09 +00:00
Keith Williams 925dc04f1c v3.7.13 2024-02-06 17:07:05 -03:00
271f31730d fix: recurring round robin events (#13471)
* use same lucky user + check availability

* add tests for recurring round robin events

* only use luckyUsers for recurring bookings

* don't check availability for all recurring dates

* code clean up

* pass schedulingType

* fix type error

* add availability check for fixed hosts + test

* fix type error

* implement feedback

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-02-06 18:18:42 +00:00
Udit Takkar 684f4a795c fix: locale flaky tests (#13564) 2024-02-06 16:52:21 +00:00
836429348c fix: 'Invalid event length' error for 1 hour dynamic events (#13522)
* fix #13507 'Invalid event length' when creating dynamic events with a 1 hour duration

* Fix failing e2e tests by dynamically injecting lengths for dynamic events

--amend 1: misc indentation cleanup
--amend 2: Added condition to only update event metadata when slug is dynamic and multipleDuration is empty

* Override the multipleDuration property while constructing the dynamicEvent object itself

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-02-06 15:55:04 +00:00
Udit TakkarandKeith Williams a55138348f fix: Flakiness in location tests (#13557)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-06 11:54:35 -03:00
66b5dfd796 fix: payload cleanup (#13552)
* fix: payload cleanup

* Update eventType.ts

* Send whitelisted fields everywhere

* Removed a user select

* Removed extra usage of users

* Put back user field with fewer selects

* Added user pick

* Removed more users selects

* Another select removal

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-06 11:20:08 -03:00
bba68e09a9 fix: add emailVerified in seed script (#13530)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-02-06 10:33:47 -03:00
Hariom Balhara bab182960f Fix admin being able to update owner details (#13534) 2024-02-06 12:52:40 +00:00
Udit TakkarandKeith Williams f09153b3bb fix: don't display setup page icon for these apps (#13537)
* fix: don't display setup page icon for these apps

* chore: make it optional

* chore: feedback

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-06 04:10:22 +00:00
3e492eb17b fix: event-type listing not showing existing event-types when an existing user is invited to an organization (#13524)
* Fix event-type listing not working

* Automatic reload of the app when being added to a different organization

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-06 03:52:24 +00:00
Omar López fe5fe8d53b v3.7.12
release.yml / release (push) Canceled after 0s
2024-02-05 13:25:43 -07:00
Omar LópezandKeith Williams 0cc5937104 chore: uses safeGet to be more explicit (#13543)
* chore: uses safeGet to be more explicit

* Update apps/web/middleware.ts

* Update apps/web/middleware.ts

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-05 20:20:18 +00:00
Joe Au-Yeung fde3b49126 Add null upId and profile (#13545) 2024-02-05 19:54:42 +00:00
Keith Williams 556787b93b perf: yarn patch dayjs (#13542)
* perf: Use overriden tz function

* Renamed .js to .ts

* Trying to use a type file for timezone

* perf: Yarn patch dayjs

* Fixed import casing
2024-02-05 19:53:15 +00:00
Peer Richelsen dcb244d7be fixed france i18n (#13536) 2024-02-05 18:05:02 +00:00
Alex van AndelandKeith Williams 8d7fb75f71 fix: Refactored availability tests for less flakiness (#13518)
* fix: Refactored availability tests for less flakiness

* Fixed type error

* Removed redundant locator

* fix: Commit updated yarn.lock file

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-05 16:00:49 +00:00
4fe75771b8 fix: Broken routing form public links(old links) on migrating the owner user to an organization (#13416)
* Fix routing-form access restrictions

* Update packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx

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

* Fix routing form access issue for users who are never migrated

* Improve comment

* wip

* Fix event-type listing not working

* Automatic reload of the app when being added to a different organization

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-02-05 15:51:30 +00:00
Alex van Andel 63f537a08f fix: wrap isSignupDisabled in try/catch 2024-02-05 14:26:40 +00:00
Crowdin Bot bed593c835 New Crowdin translations by Github Action 2024-02-04 19:22:35 +00:00
Alex / KATT f367c5ed3d chore: use one yarn version (#13517) 2024-02-04 19:19:46 +00:00
zomars af1382c5ba feat: adds signup disabling middleware 2024-02-03 14:47:51 -07:00
Tangerine Kugelmann eeb0fc795d chore: create security.txt (#13454) 2024-02-03 14:15:37 +00:00
4aa040d96e feat: routing form fields as variables in route (#13519)
* add UI for setting up custom event typ url

* extract variables when processing route

* remove console.log

* fix some UI issues

* code clean up

* add translation

* fix type error

* Update packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx

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

* make variable check case insensitive

* add function

* code clean up

* code clean up

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-02-02 19:25:37 -03:00
12466fb064 feat: New Organization Schema along with the ability to invite existing users to an organization (#13002)
* Add profile table

Copy to profile table

Update schema

Migrations

* wip

* Get switcher working

* Add organization seeder

* wip

* Remove dead code

* Fix unit tests

* relevantProfile -> profile

* cache bust

* One more comment

* Squash migrations

* Copy to profile table

* Update schema

* Fix existing user invite flow

* Add profile table

* Copy to profile table

* Update schema

* Format

* Add profile table

Copy to profile table

Update schema

Migrations

* Format

* wip

Get switcher working

Add organization seeder

wip

Remove dead code

Fix unit tests

relevantProfile -> profile

cache bust

One more comment

Squash migrations

Copy to profile table

Update schema

Fix existing user invite flow

Add profile table

Copy to profile table

Update schema

chore: small improvements to signup (#13227)

* removed features on mobile, better background color

* nit

* dark mode nits

New Crowdin translations by Github Action

fix: event reschedule workflow trigger (#13122)

* fix event reschedule workflow trigger

* uncomment previously failed test

* review fix

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>

chore: added community support to help (#13213)

refactor: Abstract `handleSeats` from `handleNewBooking` (#12062)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Erik <erik@erosemberg.com>

fix: Dynamic group booking  org-migration redirect (#13223)

* fix: Dynamic group booking redirect

* Fix flaky private team test

test: Check availability tab and all your functions (teste2e-availability) (#12434)

* add changes

* add changes

* add changes

* add changes

* Requested changes

* Change way to grab element

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

fix: Reschedule a cancelled booking (#13170)

* fix: Reschedule a cancelled booking

* canceled instead of cancelled

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

v3.7.0

test: In Team Event type when location is Organizer Default App and U… (#12939)

* test: In Team Event type when location is Organizer Default App and User Metadata is emoty

* chore: use variable

* fix: type error

test: add test for invalid cal video (#13103)

* test: add test for invalid cal video

* chore: add confirm

* chore: fix type error

* chore: update code

* test: add unit test

* chore: type error

* chore

* chore: type

* chore: fix test

* chore

---------

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

chore: add error message for no availability (#13230)

* chore: add error message for no default user availability

* chore: check only availability

* chore: change message

* chore: add eventType

fix: Team revert with members (#13228)

refactor: Refactor seats logic (#12905)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

* Move handleSeats

* Abstract lastAttendeeDeleteBooking

* Create function for rescheduling seated events

* Fix imports on reschedule seats function

* Fix imports

* Import handleSeats function

* Fix rescheduleUid type

* Refactor owner reschedule to new time slot

* Refactor combine two booking times together

* Reschedule as an attendee

* Refactor createNewSeat

* Remove old handleSeats

* Remove lastAttendeeDeleteBooking from handleNewBooking

* Test for new attendee right params are passed

* Unit test params for reschedule

* Typo fix

* Clean up

* Create new seat test

* Test when attendee already signs up for booking

* Type fix

* Test reschedule move attendee to existing booking

* On reschedule create new booking

* Test on last attendee cancel booking

* Owner reschedule to new time slot

* Owner rescheduling, merge two bookings together

* Test: when merging more than available seats, then fail

* Test: fail when event is full

* Remove duplicate E2E tests

* Clean up

* Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent`

* Refactor `calcAppsStatus`

* Assign `evt` to resutl of `addVideoCallDataToEvent`

* Use prisma.transaction when moving attendees

* Clean create seat call

* Use ErrorCode enum

* Use attendeeRescheduledSeatedBooking function

* Await function

* Prevent double triggering of workflows

* Use inviteeToAdd in createNewSeat

* Remove unused error code

* Remove old handleSeats file

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Erik <erik@erosemberg.com>

feat: Allow admins to filter bookings only by attendee emails (#13236)

* chore: Allow admins to filter bookings only by attendee emails

* Fix type error

Fixed [CAL-2930] Org branding isnt centered in shell #13225 (#13237)

Add profile table

Copy to profile table

Update schema

Migrations

Format

* wip

* Add profile relation everywhere

* Fix schema

* Fix invite handler

* Add profileId during eventTypec reation

* Get dashboard working with profile relatioon

* Add workflow create repository

* Add profileId to host

* Add profileId to credential table

* Add destinationCalendar repository

* Add profileId to membership

* Revert all profile related changes

* Add acme and dunder

* Fix membership profileId usgae

* Various fixes

* Some renaming

* Fix failing tests

* Debugging next-aut

* Consolidate migrations

* Testing fixes

* Fix unit tests

* Self review feedback

* PR feedback

* Fix orgMigration as well

* Add profile schema and copy users to it

* Remove migrations from here

* Fix type issues

* Fix type error after main merge

* Remove movedFromUserId

* Add missing migration file

* Self review addressed

* Joe's review addressed

* Fix profile switcher

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-02 21:34:13 +00:00
Carina Wollendorfer 42a3448abb fix translation (#13516) 2024-02-02 16:01:28 +00:00
Peer RichelsenandKeith Williams 92ff49f6a1 replaced border-neutral-300 with border-subtle (#13500)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-02 15:49:41 +00:00
Udit Takkar c4aa555610 fix: remove check for location (#13493) 2024-02-02 15:41:27 +00:00
Somay Chauhan a45d77de51 fix: initialized flags on event-types page (#13469)
* fix: initialized flags on event-types page

* changes

* chore: removed `managed-event-types` flag as no longer needed

* revert: `20230404202721_add_feature_flag_managed_event_types` migration
2024-02-02 15:35:00 +00:00
ErikandKeith Williams 0421dd401a feat: API wrapper for invite members. (#13496)
* chore: API

* chore: remove

* chore: Update

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-02-02 12:27:17 -03:00
Hariom Balhara 094189fc3b Correcly identify the user during signup (#13506) 2024-02-02 15:18:00 +00:00
Abhijeet Singhandsupalarry 677a964d4f feat: Added new API route to get event-types using slug (#13374)
* added new api route /event-types/bySlug/{slug}

* refactor: schema query slug

* refactor: event-types/bySlug to event-types/by-slug route

* feat: allow admins to fetch event types on behalf of others

* fix: auth middleware

* added slug query param to main event-type api route

* removed the /by-slug route

* refactor logic

* remove unwanted changes

* updated swagger api doc

---------

Co-authored-by: supalarry <lauris.skraucis@gmail.com>
2024-02-02 15:10:43 +00:00
sean-brydon 2357c919c1 fix: weekly view booker blocked slots (#13511) 2024-02-02 15:08:24 +00:00
Hichem Fantar 27b7cccfa6 fix: syntax error in WordPress plugin (#13482) 2024-02-02 14:46:46 +00:00
Crowdin Bot ab2354f2a4 New Crowdin translations by Github Action 2024-02-02 14:31:56 +00:00
Alex van Andel 538e5017c5 fix: Skip booking limits for now (#13510) 2024-02-02 11:27:36 -03:00
2e3060b738 docs: Adds deployment guides for AWS, GCP and Azure (#12656)
* Deployment Steps

* Update deployment.md

* Deployment steps

* Update deployment.md

* Deployment Steps

* Update deployment.md

* Update yarn.lock

* Update yarn.lock

* Update and rename deployment.md to README.md

---------

Co-authored-by: Nathaniel-Nirmal <na453@cornell.edu>
Co-authored-by: Sriya Challa <100821752+sriyachalla@users.noreply.github.com>
Co-authored-by: Nathaniel-Nirmal <142919730+Nathaniel-Nirmal@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2024-01-31 11:24:45 +00:00
Peer Richelsen e9d9aebd88 chore: cal.ai try out new gpt-4-0125-preview (#13461) 2024-01-31 06:42:25 -03:00
Omar López 7d1c69801e chore: optimizing bundle size (#13473)
* WIP

* Tree shaking
2024-01-30 19:49:29 -03:00
Peer Richelsen 4235b32349 chore: added 90 min to + group links (#13475) 2024-01-30 20:28:48 +00:00
Omar López 2ca031e25c v3.7.11
release.yml / release (push) Canceled after 0s
2024-01-30 09:33:30 -07:00
Omar López 9f734b8964 fix: tailwind config for web modules (#13466) 2024-01-30 15:26:41 +00:00
Peer Richelsen d6927c5049 chore: fixed theme colors (#13463)
the palette has changed so the theme has to be adjusted for pwa mode
2024-01-30 12:04:57 +00:00
Keith Williams 910f5f906f v3.7.10
release.yml / release (push) Canceled after 0s
2024-01-30 08:27:06 -03:00
Peer RichelsenandHariom 71f955e256 feat: salesroom app (#13277)
* added salesroom app

* fixes to screenshots

* new screenshots and description

* Add missing update to yarn.lock

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-01-30 11:25:29 +00:00
sean-brydon 5e60654aa9 fix: make show more banner static > absolute to allow embed auto height (#13457)
* fix: make show more banner static > abosulte to allow embed auto height

* fix: hide on mobile
2024-01-30 08:48:08 +00:00
Somay Chauhan b6175b3dde fix: Pay to book for an event that no longer has payments (#13432) 2024-01-29 22:44:45 +00:00
a0a731b357 chore: adds future opt-in banner for ADMINS only (#13315)
* chore: adds future opt-in banner for ADMINS only

* Adds banner to shell

* Update teams.e2e.ts

* fix: e2e ab tests

* Update ab-tests-redirect.e2e.ts

* Discard changes to apps/web/playwright/ab-tests-redirect.e2e.ts

* Discard changes to apps/web/playwright/app-store.e2e.ts

* Discard changes to apps/web/playwright/event-types.e2e.ts

* Discard changes to apps/web/playwright/payment.e2e.ts

* Discard changes to apps/web/playwright/settings-admin.e2e.ts

* Discard changes to apps/web/playwright/teams.e2e.ts

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-29 13:20:01 -07:00
Omar López fdaa6961f8 fix: Adds missing rate limiting headers (#13430) 2024-01-29 12:26:11 -07:00
Benny Joo d91e74ec13 chore: module based file structure for /video group (#13453)
* chore: module based file structure for /video group

* fix

* fix
2024-01-29 18:59:24 +00:00
Keith Williams 0af780aac9 fix: before/after event buffers not rendering correctly (#13451) 2024-01-29 15:29:26 +00:00
41b0589ab0 fix: seated event custom questions (#12779)
* fix: seated event custom questions

* updating tests & code refactor

* addition of prisma custom json schema and type fixes

* attendeeName parsing refactor

* response parsing fix

* response parsing fix

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-29 14:37:52 +00:00
Somay ChauhanandUdit Takkar 00e96ac7ad fix: show leave site alert for instant meetings only (#13448)
* fix: leave site alert

* fix: only use expiry time

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-01-29 13:40:47 +00:00
Keith Williams 63feb721ea fix: Deployments on main (#13450) 2024-01-29 10:00:53 -03:00
Somay ChauhanandUdit Takkar 43c1bd63ca fix: destination calendar selection resetting to default on page reload (#13440)
* fix: destination calendar selection resetting to default on page reload

* fix: duplicate destination calendar settings on duplicate event

* fix: zod error in test

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-01-29 10:03:26 +00:00
8bc265faaa fix: When declining OAuth return to previous page (#13189)
* Get `onErrorReturnTo` URL

* If no code return to previous page

* On invalid permissions, return to previous page

* When not enough permissions given, invalidate the credential created

* Type fix

* Stripe go back to previous page on cancel

* Clean up console.logs

* Add early returns when redirecting

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-01-29 05:51:53 -03:00
Morgan f33ea91ab0 chore: move getFixedT to web (#13446) 2024-01-29 08:44:15 +00:00
cc0ff17d97 test: Create E2E tests for a collective event type (teste2e-bookCollective) (#12565)
* refactor

* add localize / refactor

* add changes

* fix conflicts

* replace text by testId

* refactor

* Update regularBookings.ts

* fix conflicts

* fix failing check

* fix failing test

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2024-01-28 20:55:17 -03:00
Crowdin Bot 4d128971bb New Crowdin translations by Github Action 2024-01-28 21:09:13 +00:00
Udit Takkar f9f82283fb chore: add logs for debugging (#13441) 2024-01-28 21:06:44 +00:00
Crowdin Bot 111135a605 New Crowdin translations by Github Action 2024-01-28 08:13:15 +00:00
Somay Chauhan 04e577582d fix: copy private link tooltip (#13437) 2024-01-28 08:10:10 +00:00
neo773 6643d3c20b fix: prefill avatar when signing up (#13427) 2024-01-27 14:24:31 +00:00
Somay Chauhan 3c42301c19 fix: "Hide notes in calendar" keeps reverting to off despite saving (#13434) 2024-01-27 12:04:15 +00:00
Somay Chauhan 20517be280 fix: app crash on clicking on an event (#13435) 2024-01-27 12:03:23 +00:00
fa45a85088 Create RTL tests for TimezoneSelect component (#10765)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-01-27 07:59:44 -03:00
Benny Jooandzomars c839e40a3e chore: [app-router-migration 26] Migrate "/[user]" page (#13057)
Co-authored-by: zomars <zomars@me.com>
2024-01-27 10:57:43 +00:00
9422e28598 test: check all advanced options in a managed event-type (teste2e-managedOptions) (#12294)
* check all advanced options in a managed event-type

* Delete apps/web/playwright/booking/managedBookingAllQuestion.e2e.ts

* Delete apps/web/playwright/managedBooking/allQuestions.e2e.ts

* Refactor

* Refactor

* check all advanced options in a managed event-type

* check all advanced options in a managed event-type

* Update regularBookings.ts

* Update regularBookings.ts

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@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>
2024-01-27 07:50:38 -03:00
d804b3947d chore: [app-router-migration 36] Migrate /org/* page group (#13202)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Pradumn Kumar <pradumn@tealfeed.com>
Co-authored-by: zomars <zomars@me.com>
2024-01-26 13:06:02 -07:00
David SomersandKeith Williams 3fa710fc1c Fix typo in webhook delete response (#13409)
It's a webhook, not a schedule.

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-26 19:29:24 +00:00
f9359aaa7b feat: added ability to assign event to all members (#13219)
* feat: added ability to assign event to all members

* fix: types

* chore: incorporate assignment for collective and round robin event sas well

* fix: update event types query

* feat: round robin hosts

* fix: update

* fix: update

* fix: assignAllTeamMembers issue

* design fixes

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-26 17:37:38 +00:00
Carina WollendorferandCarinaWolli 7c7ca79d48 remove duplicate confirmBooking (#13423)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-26 16:27:18 +00:00
DmytroHryshyn 753cb5803d chore: module based file structure: event types (#13404) 2024-01-25 18:37:26 -07:00
Omar López 210bc9fc8c chore: Proposal for new module based file structure (#13396) 2024-01-25 17:31:08 -07:00
Crowdin Bot 3d8b5390df New Crowdin translations by Github Action 2024-01-25 22:30:50 +00:00
zomars a477f337dc fix: login flaky test 2024-01-25 15:27:45 -07:00
9db9204088 test: Check the webhook event tab and your funtionalities (#12433)
* Check the webhook event tab and your funtionalities

* Check the webhook event tab and your funtionalities

* add changes

* add changes

* add changes

* add changes

* add changes

* add changes

* fix tests

* Refactor

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-25 15:50:11 -03:00
Crowdin Bot fe46b81c63 New Crowdin translations by Github Action 2024-01-25 17:54:39 +00:00
Keith Williams 17db644be1 v3.7.9
release.yml / release (push) Canceled after 0s
2024-01-25 14:51:08 -03:00
e4037be79a feat: Limit the amount of days to load for a schedule (#13282)
* feat: Disallow next month prefetching in booker

* renamed to include NEXT_PUBLIC

* fixed boolean expression

* Now using a variable length of days to load

* Type fixes

* Now opening all dates to be clickable

* Added turbo.json var and updated example env

* Fixed issue with selectedDate being null when first loading the page

* Updated description of variable

* Made constant for the booking days

* Type fix

* Setting the days to load for weekly view to 7

* feat: add ui and ability to fetch more on click

* feat: add ui and ability to fetch more on click

* fix: loader

* fix: banner detection

* Update packages/features/bookings/Booker/Booker.tsx

* fix: types

* Not showing HavingTroubleFindingTime component when the schedule is loading

* Only enabled on monthly view

* Correctly load initial date

* Type fix

* Fixed bugs around triggering multiple requests

* Added case for when there is no selectedDate but the month has been specified

* Types

* More types

* Check for undefined

* More typing

* More typing

---------

Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-01-25 14:48:46 -03:00
DmytroHryshyn ebe6a60366 fix insights page (#13390) 2024-01-24 14:22:09 -07:00
zomars fa9c6ea0ef fix: flaky test 2024-01-24 14:21:06 -07:00
877 changed files with 30143 additions and 14622 deletions
+9 -4
View File
@@ -23,6 +23,9 @@ CALCOM_LICENSE_KEY=
# - DATABASE ************************************************************************************************
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
# Needed to run migrations while using a connection pooler like PgBouncer
# Use the same one as DATABASE_URL if you're not using a connection pooler
DATABASE_DIRECT_URL="postgresql://postgres:@localhost:5450/calendso"
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
INSIGHTS_DATABASE_URL=
@@ -134,8 +137,8 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME=
NEXT_PUBLIC_SENTRY_DSN=
# Formbricks Experience Management Integration
FORMBRICKS_HOST_URL=https://app.formbricks.com
FORMBRICKS_ENVIRONMENT_ID=
NEXT_PUBLIC_FORMBRICKS_HOST_URL=https://app.formbricks.com
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=
FORMBRICKS_FEEDBACK_SURVEY_ID=
# AvatarAPI
@@ -236,6 +239,8 @@ CSP_POLICY=
EDGE_CONFIG=
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD=0 # Override the booker to only load X number of days worth of data
# Control time intervals on a user's Schedule availability
NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL=
@@ -289,9 +294,9 @@ E2E_TEST_CALCOM_GCAL_KEYS=
# Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations
# Under settings/admin/apps ensure that all app secrets are set the same as the parent application
# You can use: `openssl rand -base64 32` to generate one
CALCOM_WEBHOOK_SECRET=""
CALCOM_CREDENTIAL_SYNC_SECRET=""
# This is the header name that will be used to verify the webhook secret. Should be in lowercase
CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret"
CALCOM_CREDENTIAL_SYNC_SECRET_HEADER_NAME="calcom-webhook-secret"
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
# Key should match on Cal.com and your application
# must be 24 bytes for AES256 encryption algorithm
+1
View File
@@ -18,6 +18,7 @@ runs:
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
key-2: ${{ github.event.pull_request.number || github.ref }}
DATABASE_URL: ${{ inputs.DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ inputs.DATABASE_URL }}
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
@@ -0,0 +1,17 @@
name: Manually Delete BuildJet Cache
on:
workflow_dispatch:
inputs:
cache_key:
description: "BuildJet Cache Key to Delete"
required: true
type: string
jobs:
manually-delete-buildjet-cache:
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: buildjet/cache-delete@v1
with:
cache_key: ${{ inputs.cache_key }}
+8
View File
@@ -11,6 +11,9 @@ jobs:
services:
postgres:
image: postgres:12.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
@@ -18,6 +21,9 @@ jobs:
- 5432:5432
mailhog:
image: mailhog/mailhog:v1.0.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
ports:
- 8025:8025
- 1025:1025
@@ -39,6 +45,7 @@ jobs:
- uses: ./.github/actions/cache-db
env:
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
@@ -50,6 +57,7 @@ jobs:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
+4
View File
@@ -11,6 +11,9 @@ jobs:
services:
postgres:
image: postgres:12.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
@@ -41,6 +44,7 @@ jobs:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
+7
View File
@@ -11,6 +11,9 @@ jobs:
services:
postgres:
image: postgres:12.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
@@ -18,6 +21,9 @@ jobs:
- 5432:5432
mailhog:
image: mailhog/mailhog:v1.0.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
ports:
- 8025:8025
- 1025:1025
@@ -44,6 +50,7 @@ jobs:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
+7
View File
@@ -11,6 +11,9 @@ jobs:
services:
postgres:
image: postgres:12.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
@@ -18,6 +21,9 @@ jobs:
- 5432:5432
mailhog:
image: mailhog/mailhog:v1.0.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
ports:
- 8025:8025
- 1025:1025
@@ -42,6 +48,7 @@ jobs:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
with:
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
organization-name: calcom
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
apply-labels-from-issue:
runs-on: ubuntu-latest
@@ -7,6 +7,7 @@ env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
+4
View File
@@ -7,6 +7,7 @@ env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_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 }}
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
@@ -41,6 +42,9 @@ jobs:
services:
postgres:
image: postgres:12.1
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
+226 -56
View File
@@ -1,9 +1,10 @@
// This transform config was generated by Snaplet.
// Snaplet found fields that may contain personally identifiable information (PII)
// and used that to populate this file.
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path=".snaplet/snaplet.d.ts" />
// This config was generated by Snaplet make sure to check it over before using it.
import { copycat as c } from "@snaplet/copycat";
import { defineConfig } from "snaplet";
import type { Transform } from "./structure";
// c.setHashKey(REPLACE_ME_WITH_YOUR_HASH_KEY);
function hasStringProp<T extends string>(x: unknown, key: T): x is { [key in T]: string } {
return !!x && typeof x === "object" && key in x;
@@ -41,57 +42,226 @@ function replaceSensitiveKeys(record: object) {
const generateUsername = (x: string) => `${c.firstName(x)}-${c.lastName(x)}${c.int(x, { min: 2, max: 99 })}`;
const config: Transform = () => ({
public: {
ApiKey: ({ row }) => ({
hashedKey: c.uuid(row.hashedKey),
}),
App: ({ row }) => ({
keys: replaceSensitiveKeys(row.keys),
}),
Attendee: ({ row }) => ({
email: c.email(row.email),
name: c.fullName(row.name),
timeZone: c.timezone(row.timeZone),
}),
Credential: ({ row }) => ({
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
}),
EventType: ({ row }) => ({
slug: generateSlug(row.slug),
timeZone: c.timezone(row.timeZone),
eventName: c.words(row.eventName, { max: 3 }),
}),
ResetPasswordRequest: ({ row }) => ({
email: c.email(row.email),
}),
Schedule: ({ row }) => ({
timeZone: c.timezone(row.timeZone),
}),
Team: ({ row }) => ({
bio: c.sentence(row.bio),
name: c.words(row.name, { max: 2 }),
slug: generateSlug(row.slug),
}),
users: ({ row }) =>
row.role !== "ADMIN"
? {
bio: c.sentence(row.bio),
email: c.email(row.email),
name: c.fullName(row.name),
password: c.password(row.password),
timeZone: c.timezone(row.timeZone),
username: generateUsername(row.username),
}
: row,
VerificationToken: ({ row }) => ({
token: c.uuid(row.token),
}),
Account: ({ row }) => ({
access_token: c.uuid(row.access_token),
refresh_token: c.uuid(row.refresh_token),
}),
export default defineConfig({
transform: {
$mode: "unsafe",
public: {
Account({ row }) {
return {
refresh_token: c.uuid(row.refresh_token),
access_token: c.uuid(row.access_token),
expires_at: c.int(row.expires_at, {
min: 0,
max: Math.pow(4, 8) - 1,
}),
token_type: c.uuid(row.token_type),
id_token: c.uuid(row.id_token),
};
},
ApiKey: ({ row }) => ({
hashedKey: c.uuid(row.hashedKey),
note: c.fullName(row.note),
createdAt: c.dateString(row.createdAt, {
minYear: 2020,
}),
}),
App: ({ row }) => ({
keys: replaceSensitiveKeys(row.keys),
}),
App_RoutingForms_Form({ row }) {
return {
name: c.fullName(row.name),
};
},
Attendee: ({ row }) => ({
email: c.email(row.email),
name: c.fullName(row.name),
timeZone: c.timezone(row.timeZone),
locale: c.fullName(row.locale),
}),
Availability({ row }) {
return {
startTime: c
.dateString(row.startTime, {
minYear: 2020,
})
.slice(11, 19),
endTime: c
.dateString(row.endTime, {
minYear: 2020,
})
.slice(11, 19),
};
},
Booking({ row }) {
return {
title: c.fullName(row.title),
startTime: c.dateString(row.startTime, {
minYear: 2020,
}),
endTime: c.dateString(row.endTime, {
minYear: 2020,
}),
location: c.sentence(row.location),
metadata: {
[c.word(row.metadata)]: c.words(row.metadata),
},
};
},
Credential: ({ row }) => ({
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
}),
EventType: ({ row }) => ({
slug: generateSlug(row.slug),
timeZone: c.timezone(row.timeZone),
eventName: c.words(row.eventName, { max: 3 }),
currency: c.sentence(row.currency),
}),
EventTypeCustomInput({ row }) {
return {
label: c.fullName(row.label),
};
},
Feature({ row }) {
return {
slug: c.uuid(row.slug),
};
},
InstantMeetingToken({ row }) {
return {
token: c.uuid(row.token),
};
},
OAuthClient({ row }) {
return {
name: c.fullName(row.name),
};
},
OutOfOfficeEntry({ row }) {
return {
start: c.dateString(row.start, {
minYear: 2020,
}),
};
},
Payment({ row }) {
return {
amount: c.int(row.amount, {
min: 0,
max: Math.pow(4, 8) - 1,
}),
currency: c.sentence(row.currency),
data: {
[c.word(row.data)]: c.words(row.data),
},
};
},
ResetPasswordRequest: ({ row }) => ({
email: c.email(row.email),
}),
Schedule: ({ row }) => ({
name: c.fullName(row.name),
timeZone: c.timezone(row.timeZone),
}),
Session({ row }) {
return {
sessionToken: c.uuid(row.sessionToken),
};
},
Team: ({ row }) => ({
bio: c.sentence(row.bio),
name: c.words(row.name, { max: 2 }),
slug: generateSlug(row.slug),
timeZone: c.timezone(row.timeZone),
logoUrl: c.username(row.logoUrl),
}),
TempOrgRedirect({ row }) {
return {
from: c.dateString(row.from, {
maxYear: 1999,
}),
toUrl: c.city(row.toUrl),
};
},
VerificationToken({ row }) {
return {
id: c
.int(row.id, {
min: 1,
max: Math.pow(4, 8) - 1,
})
.toString(),
identifier: c.uuid(row.identifier),
token: c.uuid(row.token),
expires: c.dateString(row.expires, {
minYear: 2020,
}),
};
},
VerifiedNumber({ row }) {
return {
phoneNumber: c.phoneNumber(row.phoneNumber),
};
},
Webhook({ row }) {
return {
subscriberUrl: c.url(row.subscriberUrl),
secret: c.streetAddress(row.secret),
};
},
WebhookScheduledTriggers({ row }) {
return {
jobName: c.fullName(row.jobName),
payload: c.password(row.payload),
};
},
WorkflowStep({ row }) {
return {
sendTo: c.oneOf(row.sendTo, [
"Man",
"Woman",
"Transgender",
"Non-binary/non-conforming",
"Not specified",
]),
sender: c.oneOf(row.sender, [
"Man",
"Woman",
"Transgender",
"Non-binary/non-conforming",
"Not specified",
]),
};
},
users: ({ row }) =>
row.role !== "ADMIN"
? {
bio: c.sentence(row.bio),
email: c.email(row.email),
name: c.fullName(row.name),
password: c.password(row.password),
timeZone: c.timezone(row.timeZone),
username: generateUsername(row.username),
metadata: {
[c.word(row.metadata)]: c.words(row.metadata),
},
}
: row,
},
},
subset: {
enabled: true,
version: "3",
targets: [
{
table: "public.users",
rowLimit: 100,
},
],
keepDisconnectedTables: false,
followNullableRelations: true,
maxCyclesLoop: 0,
eager: false,
taskSortAlgorithm: "children",
},
});
export default config;
+4
View File
@@ -0,0 +1,4 @@
Contact: security@cal.com
Preferred-Languages: en
Canonical: https://cal.com/.well-known/security.txt
Policy: https://github.com/calcom/cal.com/security/policy
@@ -0,0 +1,8 @@
diff --git a/plugin/timezone.js b/plugin/timezone.js
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
--- a/plugin/timezone.js
+++ b/plugin/timezone.js
@@ -1 +1 @@
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
\ No newline at end of file
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
@@ -0,0 +1,8 @@
diff --git a/plugin/timezone.js b/plugin/timezone.js
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
--- a/plugin/timezone.js
+++ b/plugin/timezone.js
@@ -1 +1 @@
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
\ No newline at end of file
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
+34 -6
View File
@@ -8,14 +8,14 @@ test.describe("Org", () => {
const response = await page.goto("https://i.cal.com/embed");
expect(response?.status()).toBe(200);
await page.screenshot({ path: "screenshot.jpg" });
await expectPageToBeServerSideRendered(page);
await expectPageToBeRenderedWithEmbedSsr(page);
});
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/peer/embed");
expect(response?.status()).toBe(200);
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
await expectPageToBeServerSideRendered(page);
await expectPageToBeRenderedWithEmbedSsr(page);
});
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
@@ -23,14 +23,14 @@ test.describe("Org", () => {
expect(response?.status()).toBe(200);
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
await expectPageToBeServerSideRendered(page);
await expectPageToBeRenderedWithEmbedSsr(page);
});
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
const response = await page.goto("https://i.cal.com/sales/embed");
expect(response?.status()).toBe(200);
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
await expectPageToBeServerSideRendered(page);
await expectPageToBeRenderedWithEmbedSsr(page);
});
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
@@ -38,9 +38,10 @@ test.describe("Org", () => {
expect(response?.status()).toBe(200);
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
await expectPageToBeServerSideRendered(page);
await expectPageToBeRenderedWithEmbedSsr(page);
});
});
test.describe("Dynamic Group Booking", () => {
test("Dynamic Group booking link should load", async ({ page }) => {
const users = [
@@ -63,12 +64,39 @@ test.describe("Org", () => {
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
});
});
test("Organization Homepage - Has Engineering and Marketing Teams", async ({ page }) => {
const response = await page.goto("https://i.cal.com");
expect(response?.status()).toBe(200);
await expect(page.locator("text=Cal.com")).toBeVisible();
await expect(page.locator("text=Engineering")).toBeVisible();
await expect(page.locator("text=Marketing")).toBeVisible();
});
test.describe("Browse the Engineering Team", async () => {
test("By User Navigation", async ({ page }) => {
await page.goto("https://i.cal.com");
await page.click('text="Engineering"');
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
});
test("By /team/engineering", async ({ page }) => {
await page.goto("https://i.cal.com/team/engineering");
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
});
test("By /engineering", async ({ page }) => {
await page.goto("https://i.cal.com/engineering");
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
});
});
});
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
async function expectPageToBeServerSideRendered(page: Page) {
async function expectPageToBeRenderedWithEmbedSsr(page: Page) {
expect(
await page.evaluate(() => {
//@ts-expect-error - __NEXT_DATA__ is a global variable defined by Next.js
return window.__NEXT_DATA__.props.pageProps.isEmbed;
})
).toBe(true);
+4 -2
View File
@@ -5,12 +5,14 @@
"author": "Cal.com Inc.",
"dependencies": {
"@calcom/prisma": "*",
"@langchain/core": "^0.1.26",
"@langchain/openai": "^0.0.14",
"@t3-oss/env-nextjs": "^0.6.1",
"langchain": "^0.0.131",
"langchain": "^0.1.17",
"mailparser": "^3.6.5",
"next": "^13.5.4",
"supports-color": "8.1.1",
"zod": "^3.22.2"
"zod": "^3.22.4"
},
"devDependencies": {
"@types/mailparser": "^3.4.0"
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import type { UserList } from "~/src/types/user";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "~/src/env.mjs";
+1 -1
View File
@@ -1,4 +1,4 @@
import { DynamicStructuredTool } from "langchain/tools";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
import { env } from "../env.mjs";
+65 -59
View File
@@ -1,5 +1,6 @@
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { env } from "../env.mjs";
import createBookingIfAvailable from "../tools/createBooking";
@@ -13,7 +14,7 @@ import type { User, UserList } from "../types/user";
import type { WorkingHours } from "../types/workingHours";
import now from "./now";
const gptModel = "gpt-4-0613";
const gptModel = "gpt-4-0125-preview";
/**
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
@@ -47,67 +48,72 @@ const agent = async (
/**
* Initialize the agent executor with arguments.
*/
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentArgs: {
prefix: `You are Cal.ai - a bleeding edge scheduling assistant that interfaces via email.
Make sure your final answers are definitive, complete and well formatted.
Sometimes, tools return errors. In this case, try to handle the error intelligently or ask the user for more information.
Tools will always handle times in UTC, but times sent to users should be formatted per that user's timezone.
In responses to users, always summarize necessary context and open the door to follow ups. For example "I have booked your chat with @username for 3pm on Wednesday, December 20th, 2023 EST. Please let me know if you need to reschedule."
If you can't find a referenced user, ask the user for their email or @username. Make sure to specify that usernames require the @username format. Users don't know other users' userIds.
const prompt =
ChatPromptTemplate.fromTemplate(`You are Cal.ai - a bleeding edge scheduling assistant that interfaces via email.
Make sure your final answers are definitive, complete and well formatted.
Sometimes, tools return errors. In this case, try to handle the error intelligently or ask the user for more information.
Tools will always handle times in UTC, but times sent to users should be formatted per that user's timezone.
In responses to users, always summarize necessary context and open the door to follow ups. For example "I have booked your chat with @username for 3pm on Wednesday, December 20th, 2023 EST. Please let me know if you need to reschedule."
If you can't find a referenced user, ask the user for their email or @username. Make sure to specify that usernames require the @username format. Users don't know other users' userIds.
The primary user's id is: ${userId}
The primary user's username is: ${user.username}
The current time in the primary user's timezone is: ${now(user.timeZone, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
The primary user's time zone is: ${user.timeZone}
The primary user's event types are: ${user.eventTypes
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
.join("\n")}
The primary user's working hours are: ${user.workingHours
.map(
(w: WorkingHours) =>
`Days: ${w.days.join(", ")}, Start Time (minutes in UTC): ${
w.startTime
}, End Time (minutes in UTC): ${w.endTime};`
)
.join("\n")}
${
users.length
? `The email references the following @usernames and emails: ${users
.map(
(u) =>
`${
(u.id ? `, id: ${u.id}` : "id: (non user)") +
(u.username
? u.type === "fromUsername"
? `, username: @${u.username}`
: ", username: REDACTED"
: ", (no username)") +
(u.email
? u.type === "fromEmail"
? `, email: ${u.email}`
: ", email: REDACTED"
: ", (no email)")
};`
)
.join("\n")}`
: ""
}`);
const agent = await createOpenAIFunctionsAgent({
llm: model,
prompt,
tools,
});
The primary user's id is: ${userId}
The primary user's username is: ${user.username}
The current time in the primary user's timezone is: ${now(user.timeZone, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
The primary user's time zone is: ${user.timeZone}
The primary user's event types are: ${user.eventTypes
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
.join("\n")}
The primary user's working hours are: ${user.workingHours
.map(
(w: WorkingHours) =>
`Days: ${w.days.join(", ")}, Start Time (minutes in UTC): ${
w.startTime
}, End Time (minutes in UTC): ${w.endTime};`
)
.join("\n")}
${
users.length
? `The email references the following @usernames and emails: ${users
.map(
(u) =>
`${
(u.id ? `, id: ${u.id}` : "id: (non user)") +
(u.username
? u.type === "fromUsername"
? `, username: @${u.username}`
: ", username: REDACTED"
: ", (no username)") +
(u.email
? u.type === "fromEmail"
? `, email: ${u.email}`
: ", email: REDACTED"
: ", (no email)")
};`
)
.join("\n")}`
: ""
}
`,
},
agentType: "openai-functions",
const executor = new AgentExecutor({
agent,
tools,
returnIntermediateSteps: env.NODE_ENV === "development",
verbose: env.NODE_ENV === "development",
});
const result = await executor.call({ input });
const result = await executor.invoke({ input });
const { output } = result;
return output;
+2 -2
View File
@@ -1,4 +1,4 @@
import * as Sentry from "@sentry/nextjs";
import { captureException as SentryCaptureException } from "@sentry/nextjs";
import type { NextMiddleware } from "next-api-middleware";
import { redactError } from "@calcom/lib/redactError";
@@ -9,7 +9,7 @@ export const captureErrors: NextMiddleware = async (_req, res, next) => {
// middleware and the API route handler
await next();
} catch (error) {
Sentry.captureException(error);
SentryCaptureException(error);
const redactedError = redactError(error);
if (redactedError instanceof Error) {
res.status(400).json({ message: redactedError.message, error: redactedError });
+6
View File
@@ -1,6 +1,7 @@
import type { NextMiddleware } from "next-api-middleware";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { API_KEY_RATE_LIMIT } from "@calcom/lib/rateLimit";
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
@@ -9,6 +10,11 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", API_KEY_RATE_LIMIT);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
});
await next();
+1
View File
@@ -62,6 +62,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true,
assignAllTeamMembers: true,
})
.merge(
z.object({
@@ -0,0 +1,7 @@
import { z } from "zod";
import { baseApiParams } from "./baseApiParams";
export const schemaQuerySlug = baseApiParams.extend({
slug: z.string().optional(),
});
+1 -1
View File
@@ -1,5 +1,5 @@
import tzdata from "tzdata";
import * as z from "zod";
import { z } from "zod";
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), {
+4
View File
@@ -74,6 +74,7 @@ export const schemaUserBaseBodyParams = User.pick({
timeZone: true,
weekStart: true,
theme: true,
appTheme: true,
defaultScheduleId: true,
locale: true,
hideBranding: true,
@@ -100,6 +101,7 @@ const schemaUserEditParams = z.object({
hideBranding: z.boolean().optional(),
timeZone: timeZone.optional(),
theme: z.nativeEnum(theme).optional().nullable(),
appTheme: z.nativeEnum(theme).optional().nullable(),
timeFormat: z.nativeEnum(timeFormat).optional(),
defaultScheduleId: z
.number()
@@ -122,6 +124,7 @@ const schemaUserCreateParams = z.object({
hideBranding: z.boolean().optional(),
timeZone: timeZone.optional(),
theme: z.nativeEnum(theme).optional().nullable(),
appTheme: z.nativeEnum(theme).optional().nullable(),
timeFormat: z.nativeEnum(timeFormat).optional(),
defaultScheduleId: z
.number()
@@ -159,6 +162,7 @@ export const schemaUserReadPublic = User.pick({
weekStart: true,
endTime: true,
bufferTime: true,
appTheme: true,
theme: true,
defaultScheduleId: true,
locale: true,
+1 -1
View File
@@ -41,6 +41,6 @@
"typescript": "^4.9.4",
"tzdata": "^1.0.30",
"uuid": "^8.3.2",
"zod": "^3.22.2"
"zod": "^3.22.4"
}
}
+2
View File
@@ -197,6 +197,7 @@ async function handler(req: NextApiRequest) {
dateTo,
eventTypeId,
userId,
returnDateOverrides: true,
});
const team = await prisma.team.findUnique({
where: { id: teamId },
@@ -232,6 +233,7 @@ async function handler(req: NextApiRequest) {
dateFrom,
dateTo,
eventTypeId,
returnDateOverrides: true,
}),
};
});
+2 -1
View File
@@ -1,5 +1,6 @@
import type { NextApiRequest } from "next";
import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
import { defaultResponder } from "@calcom/lib/server";
@@ -206,7 +207,7 @@ async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
if (isAdmin) req.userId = req.body.userId || userId;
return await handleNewBooking(req);
return await handleNewBooking(req, getBookingDataSchemaForApi);
}
export default defaultResponder(handler);
@@ -51,7 +51,6 @@ export async function getHandler(req: NextApiRequest) {
include: {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
+14 -3
View File
@@ -5,6 +5,7 @@ import { defaultResponder } from "@calcom/lib/server";
import type { PrismaClient } from "@calcom/prisma";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQuerySlug } from "~/lib/validations/shared/querySlug";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
import getCalLink from "./_utils/getCalLink";
@@ -22,6 +23,12 @@ import getCalLink from "./_utils/getCalLink";
* type: string
* required: true
* description: Your API Key
* - in: query
* name: slug
* schema:
* type: string
* required: false
* description: Slug to filter event types by
* tags:
* - event-types
* externalDocs:
@@ -35,16 +42,20 @@ import getCalLink from "./_utils/getCalLink";
* description: No event types were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, prisma } = req;
const { userId, prisma, isAdmin } = req;
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
const { slug } = schemaQuerySlug.parse(req.query);
const shouldUseUserId = !isAdmin || !slug || !!req.query.userId;
// When user is admin and no query params are provided we should return all event types.
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
const data = await prisma.eventType.findMany({
where: {
userId: { in: userIds },
userId: shouldUseUserId ? { in: userIds } : undefined,
slug: slug, // slug will be undefined if not provided in query
},
include: {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
@@ -3,13 +3,17 @@ import type { z } from "zod";
import { HttpError } from "@calcom/lib/http-error";
import type { schemaEventTypeBaseBodyParams } from "~/lib/validations/event-type";
import type { schemaEventTypeCreateBodyParams } from "~/lib/validations/event-type";
export default async function checkTeamEventEditPermission(
req: NextApiRequest,
body: Pick<z.infer<typeof schemaEventTypeBaseBodyParams>, "teamId">
body: Pick<z.infer<typeof schemaEventTypeCreateBodyParams>, "teamId" | "userId">
) {
const { prisma, userId } = req;
const { prisma, isAdmin } = req;
let userId = req.userId;
if (isAdmin && body.userId) {
userId = body.userId;
}
if (body.teamId) {
const membership = await prisma.membership.findFirst({
where: {
@@ -1,4 +1,4 @@
import { CAL_URL } from "@calcom/lib/constants";
import { WEBSITE_URL } from "@calcom/lib/constants";
export default function getCalLink(eventType: {
team?: { slug: string | null } | null;
@@ -6,7 +6,7 @@ export default function getCalLink(eventType: {
users?: { username: string | null }[];
slug: string;
}) {
return `${CAL_URL}/${
return `${WEBSITE_URL}/${
eventType?.team
? `team/${eventType?.team?.slug}`
: eventType?.owner
+72
View File
@@ -0,0 +1,72 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
import { ZInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
import type { UserProfile } from "@calcom/types/UserProfile";
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const data = ZInviteMemberInputSchema.parse(req.body);
await checkPermissions(req, data);
async function sessionGetter() {
return {
user: {
id: req.userId,
username: "",
profile: {
id: null,
organizationId: null,
organization: null,
username: "",
upId: "",
} satisfies UserProfile,
},
hasValidLicense: true,
expires: "",
upId: "",
};
}
const ctx = await createContext({ req, res }, sessionGetter);
try {
const caller = viewerTeamsRouter.createCaller(ctx);
await caller.inviteMember({
role: data.role,
language: data.language,
teamId: data.teamId,
usernameOrEmail: data.usernameOrEmail,
isOrg: data.isOrg,
});
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
} catch (cause) {
if (cause instanceof TRPCError) {
const statusCode = getHTTPStatusCodeFromError(cause);
throw new HttpError({ statusCode, message: cause.message });
}
throw cause;
}
}
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
const { userId, isAdmin, prisma } = req;
if (isAdmin) return;
// To prevent auto-accepted invites, limit it to ADMIN users
if (!isAdmin && "accepted" in body)
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
// Only team OWNERS and ADMINS can add other members
const membership = await prisma.membership.findFirst({
where: { userId, teamId: body.teamId, role: { in: ["ADMIN", "OWNER"] } },
});
if (!membership) throw new HttpError({ statusCode: 403, message: "You can't add members to this team" });
}
export default defaultResponder(postHandler);
+9
View File
@@ -0,0 +1,9 @@
import { defaultHandler } from "@calcom/lib/server";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
export default withMiddleware()(
defaultHandler({
POST: import("./_post"),
})
);
@@ -59,7 +59,6 @@ async function getHandler(req: NextApiRequest) {
include: {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
@@ -5,6 +5,7 @@ import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
import type { UserProfile } from "@calcom/types/UserProfile";
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
@@ -21,9 +22,17 @@ const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
id: req.userId,
username: "" /* Not used in this context */,
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
profile: {
id: null,
organizationId: null,
organization: null,
username: "",
upId: "",
} satisfies UserProfile,
},
hasValidLicense: true /* To comply with TS signature */,
expires: "" /* Not used in this context */,
upId: "",
};
}
/** @see https://trpc.io/docs/server-side-calls */
+1 -1
View File
@@ -39,7 +39,7 @@ export async function deleteHandler(req: NextApiRequest) {
const { prisma, query } = req;
const { id } = schemaQueryIdAsString.parse(query);
await prisma.webhook.delete({ where: { id } });
return { message: `Schedule with id: ${id} deleted successfully` };
return { message: `Webhook with id: ${id} deleted successfully` };
}
export default defaultResponder(deleteHandler);
+2 -2
View File
@@ -1,5 +1,5 @@
import * as Sentry from "@sentry/nextjs";
import { init as SentryInit } from "@sentry/nextjs";
Sentry.init({
SentryInit({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
});
+2 -2
View File
@@ -1,6 +1,6 @@
import * as Sentry from "@sentry/nextjs";
import { init as SentryInit } from "@sentry/nextjs";
Sentry.init({
SentryInit({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
});
+1
View File
@@ -24,6 +24,7 @@
"@types/node": "16.9.1",
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@types/swagger-ui-react": "^4.18.3",
"typescript": "^4.9.4"
}
}
+2 -1
View File
@@ -1,9 +1,10 @@
import "highlight.js/styles/default.css";
import { type AppProps } from "next/app";
import "swagger-ui-react/swagger-ui.css";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
+1 -4
View File
@@ -1,9 +1,8 @@
import dynamic from "next/dynamic";
import type { SwaggerUI } from "swagger-ui-react";
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
ssr: false,
});
@@ -17,8 +16,6 @@ export default function APIDocs() {
requestSnippets={requestSnippets}
plugins={[SnippedGenerator]}
tryItOutEnabled={true}
syntaxHighlight={true}
enableCORS={false} // Doesn't seem to work either
docExpansion="list"
filter={true}
/>
+2 -3
View File
@@ -3,6 +3,8 @@ import type { NextMiddleware, NextRequest } from "next/server";
import { NextResponse, URLPattern } from "next/server";
import z from "zod";
import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME } from "@calcom/lib/constants";
const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
@@ -25,9 +27,6 @@ const ROUTES: [URLPattern, boolean][] = [
enabled,
]);
const FUTURE_ROUTES_OVERRIDE_COOKIE_NAME = "x-calcom-future-routes-override";
const FUTURE_ROUTES_ENABLED_COOKIE_NAME = "x-calcom-future-routes-enabled";
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
export const abTestMiddlewareFactory =
+22 -1
View File
@@ -1,12 +1,33 @@
import { type TFunction } from "i18next";
import i18next from "i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { headers } from "next/headers";
import { constructGenericImage } from "@calcom/lib/OgImages";
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
import { getFixedT } from "@calcom/lib/server/getFixedT";
//@ts-expect-error no type definitions
import config from "@calcom/web/next-i18next.config";
import { preparePageMetadata } from "@lib/metadata";
const create = async (locale: string, ns: string) => {
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
const _i18n = i18next.createInstance();
_i18n.init({
lng: locale,
resources: _nextI18Next?.initialI18nStore,
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
});
return _i18n;
};
const getFixedT = async (locale: string, ns: string) => {
const i18n = await create(locale, ns);
return i18n.getFixedT(locale, ns);
};
export const _generateMetadata = async (
getTitle: (t: TFunction<string, undefined>) => string,
getDescription: (t: TFunction<string, undefined>) => string
@@ -0,0 +1,12 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import LegacyPage from "~/users/views/users-type-public-view";
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
@@ -0,0 +1,45 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { headers, cookies } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import LegacyPage from "~/users/views/users-type-public-view";
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Record<string, string | string[]>;
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const { eventData, booking, user, slug } = props;
const rescheduleUid = booking?.uid;
const { trpc } = await import("@calcom/trpc");
const { data: event } = trpc.viewer.public.event.useQuery(
{ username: user, eventSlug: slug, isTeamEvent: false, org: eventData.entity.orgSlug ?? null },
{ refetchOnWindowFocus: false }
);
const profileName = event?.profile?.name ?? "";
const title = event?.title ?? "";
return await _generateMetadata(
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({
getData,
Page: LegacyPage,
getLayout: null,
})<"P">;
+12
View File
@@ -0,0 +1,12 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import LegacyPage from "~/users/views/users-public-view";
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
+33
View File
@@ -0,0 +1,33 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { headers, cookies } from "next/headers";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import LegacyPage from "~/users/views/users-public-view";
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Record<string, string | string[]>;
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const { profile, markdownStrippedBio } = props;
return await _generateMetadata(
() => profile.name,
() => markdownStrippedBio
);
};
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
@@ -1,9 +1,10 @@
import { type PageProps, getServerSideProps } from "@pages/booking/[uid]";
import OldPage from "@pages/booking/[uid]/embed";
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import OldPage from "~/bookings/views/bookings-single-view";
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
+3 -2
View File
@@ -1,4 +1,3 @@
import OldPage, { type PageProps } from "@pages/booking/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
@@ -8,9 +7,11 @@ import { cookies, headers } from "next/headers";
import { BookingStatus } from "@calcom/prisma/enums";
import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import OldPage from "~/bookings/views/bookings-single-view";
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
@@ -1,15 +1,13 @@
import Page from "@pages/bookings/[status]";
import { withAppDirSsg } from "app/WithAppDirSsg";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import type { InferGetStaticPropsType } from "next";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import { APP_NAME } from "@calcom/lib/constants";
import { getStaticProps } from "@lib/bookings/[status]/getStaticProps";
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
import { validStatuses } from "~/bookings/lib/validStatuses";
import Page from "~/bookings/views/bookings-listing-view";
import { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps";
type Y = InferGetStaticPropsType<typeof getStaticProps>;
const getData = withAppDirSsg<Y>(getStaticProps);
@@ -24,6 +22,6 @@ export const generateStaticParams = async () => {
return validStatuses.map((status) => ({ status }));
};
export default WithLayout({ getLayout, getData, Page })<"P">;
export default WithLayout({ getLayout: null, getData, Page })<"P">;
export const dynamic = "force-static";
@@ -0,0 +1,32 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { cookies, headers } from "next/headers";
import { APP_NAME } from "@calcom/lib/constants";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import EventTypePageWrapper from "~/event-types/views/event-types-single-view";
import { getServerSideProps } from "~/event-types/views/event-types-single-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const { eventType } = await getData(legacyCtx);
return await _generateMetadata(
(t) => `${eventType.title} | ${t("event_type")} | ${APP_NAME}`,
() => ""
);
};
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getLayout: null, getData, Page: EventTypePageWrapper })<"P">;
+2 -3
View File
@@ -1,8 +1,7 @@
import EventTypes from "@pages/event-types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import EventTypes from "~/event-types/views/event-types-listing-view";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -10,4 +9,4 @@ export const generateMetadata = async () =>
(t) => t("event_types_page_subtitle")
);
export default WithLayout({ getLayout, Page: EventTypes })<"P">;
export default WithLayout({ getLayout: null, Page: EventTypes })<"P">;
+3 -4
View File
@@ -1,10 +1,9 @@
import LegacyPage, { getServerSideProps } from "@pages/insights/index";
import LegacyPage from "@pages/insights/index";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import { getServerSideProps } from "@lib/insights/getServerSideProps";
import { type inferSSRProps } from "@lib/types/inferSSRProps";
export const generateMetadata = async () =>
@@ -15,4 +14,4 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
export default WithLayout({ getLayout, getData, Page: LegacyPage });
export default WithLayout({ getLayout: null, getData, Page: LegacyPage });
@@ -0,0 +1,41 @@
import { type PageProps } from "@pages/team/[slug]";
import EmbedPage from "@pages/team/[slug]/embed";
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const teamName = props.team.name || "Nameless Team";
return await _generateMetadata(
() => teamName,
() => teamName
);
};
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({
Page: EmbedPage,
getData: getEmbedData,
getLayout: null,
isBookingPage: true,
})<"P">;
@@ -0,0 +1,37 @@
import TeamPage, { type PageProps } from "@pages/team/[slug]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const teamName = props.team.name || "Nameless Team";
return await _generateMetadata(
() => teamName,
() => teamName
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({
Page: TeamPage,
getData,
getLayout: null,
isBookingPage: true,
})<"P">;
@@ -0,0 +1,44 @@
import TypePage, { type PageProps } from "@pages/team/[slug]/[type]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
const { eventData, user, slug, booking } = props;
const entity = eventData.entity;
const { trpc } = await import("@calcom/trpc");
const { data: event } = trpc.viewer.public.event.useQuery(
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
{ refetchOnWindowFocus: false }
);
const profileName = event?.profile?.name ?? "";
const title = event?.title ?? "";
return await _generateMetadata(
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({
Page: TypePage,
getData,
getLayout: null,
isBookingPage: true,
})<"P">;
@@ -0,0 +1,37 @@
import TeamPage, { type PageProps } from "@pages/team/[slug]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const teamName = props.team.name || "Nameless Team";
return await _generateMetadata(
() => teamName,
() => teamName
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({
Page: TeamPage,
getData,
getLayout: null,
isBookingPage: true,
})<"P">;
@@ -16,7 +16,8 @@ export const generateMetadata = async ({
}) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
const { entity, user, slug, booking } = props;
const { eventData, user, slug, booking } = props;
const entity = eventData.entity;
const { trpc } = await import("@calcom/trpc");
const { data: event } = trpc.viewer.public.event.useQuery(
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
+1 -3
View File
@@ -3,8 +3,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import { getServerSideProps } from "@lib/teams/getServerSideProps";
export const generateMetadata = async () =>
@@ -13,4 +11,4 @@ export const generateMetadata = async () =>
(t) => t("create_manage_teams_collaborative")
);
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout, Page })<"P">;
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout: null, Page })<"P">;
+4 -4
View File
@@ -1,11 +1,11 @@
import Page, { type JoinCallPageProps } from "@pages/video/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { APP_NAME } from "@calcom/lib/constants";
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
import VideosSingleView from "~/videos/views/videos-single-view";
import { getServerSideProps, type PageProps } from "~/videos/views/videos-single-view.getServerSideProps";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -13,6 +13,6 @@ export const generateMetadata = async () =>
(t) => t("quick_video_meeting")
);
const getData = withAppDirSsr<JoinCallPageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: VideosSingleView, getLayout: null })<"P">;
@@ -1,9 +1,9 @@
import Page from "@pages/video/meeting-ended/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
import MeetingEnded from "~/videos/views/videos-meeting-ended-single-view";
import { getServerSideProps } from "~/videos/views/videos-meeting-ended-single-view.getServerSideProps";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -13,4 +13,4 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: MeetingEnded, getLayout: null })<"P">;
@@ -1,4 +1,3 @@
import Page from "@pages/video/meeting-not-started/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
@@ -6,7 +5,8 @@ import { WithLayout } from "app/layoutHOC";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
import MeetingNotStarted from "~/videos/views/videos-meeting-not-started-single-view";
import { getServerSideProps } from "~/videos/views/videos-meeting-not-started-single-view.getServerSideProps";
export const generateMetadata = async ({ params }: PageProps) => {
const booking = await prisma.booking.findUnique({
@@ -24,4 +24,4 @@ export const generateMetadata = async ({ params }: PageProps) => {
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export default WithLayout({ getData, Page: MeetingNotStarted, getLayout: null })<"P">;
@@ -1,10 +1,11 @@
import LegacyPage from "@pages/video/no-meeting-found";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import type { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
import NoMeetingFound from "~/videos/views/videos-no-meeting-found-single-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("no_meeting_found"),
@@ -20,4 +21,4 @@ const getData = async (context: GetServerSidePropsContext) => {
};
};
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
export default WithLayout({ getData, Page: NoMeetingFound, getLayout: null })<"P">;
+1 -2
View File
@@ -1,7 +1,6 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import LegacyPage from "@calcom/features/ee/workflows/pages/index";
export const generateMetadata = async () =>
@@ -10,4 +9,4 @@ export const generateMetadata = async () =>
(t) => t("workflows_to_automate_notifications")
);
export default WithLayout({ getLayout, Page: LegacyPage })<"P">;
export default WithLayout({ getLayout: null, Page: LegacyPage })<"P">;
+17 -7
View File
@@ -2,11 +2,9 @@ import { dir } from "i18next";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import { headers, cookies } from "next/headers";
import Script from "next/script";
import React from "react";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { prepareRootMetadata } from "@lib/metadata";
@@ -18,6 +16,7 @@ const calFont = localFont({
variable: "--font-cal",
preload: true,
display: "block",
weight: "600",
});
export const generateMetadata = () =>
@@ -69,11 +68,13 @@ export default async function RootLayout({ children }: { children: React.ReactNo
style={embedColorScheme ? { colorScheme: embedColorScheme as string } : undefined}
data-nextjs-router="app">
<head nonce={nonce}>
{!IS_PRODUCTION && process.env.VERCEL_ENV === "preview" && (
// eslint-disable-next-line @next/next/no-sync-scripts
<Script
data-project-id="KjpMrKTnXquJVKfeqmjdTffVPf1a6Unw2LZ58iE4"
src="https://snippet.meticulous.ai/v1/stagingMeticulousSnippet.js"
{!!process.env.NEXT_PUBLIC_HEAD_SCRIPTS && (
<script
nonce={nonce}
id="injected-head-scripts"
dangerouslySetInnerHTML={{
__html: process.env.NEXT_PUBLIC_HEAD_SCRIPTS,
}}
/>
)}
<style>{`
@@ -97,6 +98,15 @@ export default async function RootLayout({ children }: { children: React.ReactNo
}
: {}
}>
{!!process.env.NEXT_PUBLIC_BODY_SCRIPTS && (
<script
nonce={nonce}
id="injected-head-scripts"
dangerouslySetInnerHTML={{
__html: process.env.NEXT_PUBLIC_BODY_SCRIPTS,
}}
/>
)}
{children}
</body>
</html>
+21
View File
@@ -24,6 +24,7 @@ const calFont = localFont({
variable: "--font-cal",
preload: true,
display: "swap",
weight: "600",
});
function PageWrapper(props: AppProps) {
@@ -75,6 +76,26 @@ function PageWrapper(props: AppProps) {
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
{process.env.NEXT_PUBLIC_GTM_ID ? (
<>
<Script
id="gtm-init-script"
dangerouslySetInnerHTML={{
__html: `(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true; j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', '${process.env.NEXT_PUBLIC_GTM_ID}');`,
}}
/>
<noscript
dangerouslySetInnerHTML={{
__html: `<iframe src="https://www.googletagmanager.com/ns.html?id=${process.env.NEXT_PUBLIC_GTM_ID}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`,
}}
/>
</>
) : null}
<style jsx global>{`
:root {
--font-inter: ${interFont.style.fontFamily};
+13 -2
View File
@@ -5,7 +5,7 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { getEventLocationTypeFromApp, type EventLocationType } from "@calcom/app-store/locations";
import type { CredentialOwner } from "@calcom/app-store/types";
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
import { BulkEditDefaultConferencingModal } from "@calcom/features/eventtypes/components/BulkEditDefaultConferencingModal";
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/enums";
import { trpc, type RouterOutputs } from "@calcom/trpc";
@@ -154,6 +154,12 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
});
const { t } = useLocale();
const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation({
onSuccess: () => {
utils.viewer.getUsersDefaultConferencingApp.invalidate();
setBulkUpdateModal(false);
},
});
return (
<>
<List className={listClassName}>
@@ -173,7 +179,12 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
)}
{bulkUpdateModal && (
<BulkEditDefaultConferencingModal open={bulkUpdateModal} setOpen={setBulkUpdateModal} />
<BulkEditDefaultForEventsModal
bulkUpdateFunction={updateLocationsMutation.mutate}
open={bulkUpdateModal}
setOpen={setBulkUpdateModal}
isPending={updateLocationsMutation.isPending}
/>
)}
</>
);
@@ -1,5 +1,5 @@
import Link from "next/link";
import { Fragment } from "react";
import { Fragment, useEffect } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
@@ -15,10 +15,12 @@ import {
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
Label,
showToast,
} from "@calcom/ui";
import { Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import AppListCard from "@components/AppListCard";
import AdditionalCalendarSelector from "@components/apps/AdditionalCalendarSelector";
@@ -182,6 +184,15 @@ function ConnectedCalendarsList(props: Props) {
export function CalendarListContainer(props: { heading?: boolean; fromOnboarding?: boolean }) {
const { t } = useLocale();
const { heading = true, fromOnboarding } = props;
const { error, setQuery: setError } = useRouterQuery("error");
useEffect(() => {
if (error === "account_already_linked") {
showToast(t(error), "error", { id: error });
setError(undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const utils = trpc.useContext();
const onChanged = () =>
Promise.allSettled([
@@ -2,7 +2,7 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import { CAL_URL } from "@calcom/lib/constants";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { AppFrontendPayload } from "@calcom/types/App";
@@ -125,7 +125,7 @@ export const InstallAppButtonChild = ({
StartIcon={(props) => (
<Avatar
alt={team.logo || ""}
imageSrc={team.logo || `${CAL_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
imageSrc={team.logo || `${WEBAPP_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
size="sm"
{...props}
/>
@@ -9,7 +9,11 @@ export default function InstalledAppsLayout({
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
return (
<Shell {...rest} hideHeadingOnMobile>
<Shell
{...rest}
title="Installed Apps"
description="Manage your installed apps or change settings"
hideHeadingOnMobile>
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
{children}
</AppCategoryNavigation>
@@ -162,7 +162,7 @@ function BookingListItem(booking: BookingItemProps) {
let bookedActions: ActionType[] = [
{
id: "cancel",
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel"),
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel_event"),
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
href: `/booking/${booking.uid}?cancel=true${
@@ -297,6 +297,8 @@ function BookingListItem(booking: BookingItemProps) {
},
];
const showPendingPayment = paymentAppData.enabled && booking.payment.length && !booking.paid;
return (
<>
<RescheduleDialog
@@ -463,7 +465,7 @@ function BookingListItem(booking: BookingItemProps) {
{booking.eventType.team.name}
</Badge>
)}
{!!booking?.eventType?.price && !booking.paid && (
{showPendingPayment && (
<Badge className="ltr:mr-2 rtl:ml-2 sm:hidden" variant="orange">
{t("pending_payment")}
</Badge>
@@ -490,8 +492,8 @@ function BookingListItem(booking: BookingItemProps) {
{title}
<span> </span>
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
<Badge className="me-2 ms-2 hidden sm:inline-flex" variant="orange">
{showPendingPayment && (
<Badge className="hidden sm:inline-flex" variant="orange">
{t("pending_payment")}
</Badge>
)}
+20 -1
View File
@@ -1,6 +1,7 @@
import { useRouter } from "next/navigation";
import { useCallback, useState } from "react";
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
@@ -23,13 +24,22 @@ type Props = {
theme: string | null;
allRemainingBookings: boolean;
seatReferenceUid?: string;
bookingCancelledEventProps: {
booking: unknown;
organizer: {
name: string;
email: string;
timeZone?: string;
};
eventType: unknown;
};
};
export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const { booking, allRemainingBookings, seatReferenceUid } = props;
const { booking, allRemainingBookings, seatReferenceUid, bookingCancelledEventProps } = props;
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
@@ -97,8 +107,17 @@ export default function CancelBooking(props: Props) {
method: "POST",
});
const bookingWithCancellationReason = {
...(bookingCancelledEventProps.booking as object),
cancellationReason,
} as unknown;
if (res.status >= 200 && res.status < 300) {
// tested by apps/web/playwright/booking-pages.e2e.ts
sdkActionManager?.fire("bookingCancelled", {
...bookingCancelledEventProps,
booking: bookingWithCancellationReason,
});
router.refresh();
} else {
setLoading(false);
@@ -1,10 +1,9 @@
import { InfoIcon } from "lucide-react";
import dynamic from "next/dynamic";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import type { z } from "zod";
import type { EventNameObjectType } from "@calcom/core/event";
@@ -16,22 +15,26 @@ import {
allowDisablingAttendeeConfirmationEmails,
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import type { EditableSchema } from "@calcom/features/form-builder/schema";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
import { APP_NAME, IS_VISUAL_REGRESSION_TESTING, WEBSITE_URL } from "@calcom/lib/constants";
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { Prisma } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
Button,
Badge,
CheckboxField,
Label,
SelectField,
SettingsToggle,
showToast,
Switch,
TextField,
Tooltip,
} from "@calcom/ui";
@@ -41,31 +44,24 @@ import RequiresConfirmationController from "./RequiresConfirmationController";
const CustomEventTypeModal = dynamic(() => import("@components/eventtype/CustomEventTypeModal"));
const generateHashedLink = (id: number) => {
const translator = short();
const seed = `${id}:${new Date().getTime()}`;
const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL));
return uid;
};
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
const { data: user } = trpc.viewer.me.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const [useEventTypeDestinationCalendarEmail, setUseEventTypeDestinationCalendarEmail] = useState(
formMethods.getValues("useEventTypeDestinationCalendarEmail")
);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const bookingFields: Prisma.JsonObject = {};
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
const selectedThemeIsDark =
user?.theme === "dark" ||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
formMethods.getValues().bookingFields.forEach(({ name }) => {
bookingFields[name] = `${name} input`;
});
@@ -82,7 +78,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const [requiresConfirmation, setRequiresConfirmation] = useState(
formMethods.getValues("requiresConfirmation")
);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
const placeholderHashedLink = `${WEBSITE_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const noShowFeeEnabled =
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
@@ -90,6 +86,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
useEffect(() => {
!hashedUrl && setHashedUrl(generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formMethods.getValues("users"), hashedUrl, team?.id]);
const toggleGuests = (enabled: boolean) => {
@@ -107,15 +104,16 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
};
}
return field;
})
}),
{ shouldDirty: true }
);
};
const { shouldLockDisableProps } = useLockedFieldsManager(
formMethods.getValues(),
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { isChildrenManagedEventType, isManagedEventType, shouldLockDisableProps } = useLockedFieldsManager({
eventType,
translate: t,
formMethods,
});
const eventNamePlaceholder = getEventName({
...eventNameObject,
eventName: formMethods.watch("eventName"),
@@ -123,8 +121,24 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const successRedirectUrlLocked = shouldLockDisableProps("successRedirectUrl");
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
const requiresBookerEmailVerificationProps = shouldLockDisableProps("requiresBookerEmailVerification");
const hideCalendarNotesLocked = shouldLockDisableProps("hideCalendarNotes");
const lockTimeZoneToggleOnBookingPageLocked = shouldLockDisableProps("lockTimeZoneToggleOnBookingPage");
const closeEventNameTip = () => setShowEventNameTip(false);
const displayDestinationCalendarSelector =
!!connectedCalendarsQuery.data?.connectedCalendars.length && (!team || isChildrenManagedEventType);
const verifiedSecondaryEmails = [
{
label: user?.email || "",
value: -1,
},
...(user?.secondaryEmails || [])
.filter((secondaryEmail) => !!secondaryEmail.emailVerified)
.map((secondaryEmail) => ({ label: secondaryEmail.email, value: secondaryEmail.id })),
];
const selectedSecondaryEmailId = formMethods.getValues("secondaryEmailId") || -1;
return (
<div className="flex flex-col space-y-4">
@@ -134,7 +148,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
* This will fallback to each user selected destination calendar.
*/}
<div className="border-subtle space-y-6 rounded-lg border p-6">
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
{displayDestinationCalendarSelector && (
<div className="flex flex-col">
<div className="flex justify-between">
<div>
@@ -172,6 +186,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Button
color="minimal"
size="sm"
{...(shouldLockDisableProps("eventName").disabled ? { disabled: true } : {})}
aria-label="edit custom name"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
onClick={() => setShowEventNameTip((old) => !old)}>
@@ -180,10 +195,51 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
/>
</div>
{displayDestinationCalendarSelector && (
<div className="w-full">
<Switch
tooltip={t("reconnect_calendar_to_use")}
label={
<>
{t("display_add_to_calendar_organizer")}
<InfoIcon className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md" />
</>
}
checked={useEventTypeDestinationCalendarEmail}
onCheckedChange={(val) => {
setUseEventTypeDestinationCalendarEmail(val);
formMethods.setValue("useEventTypeDestinationCalendarEmail", val, { shouldDirty: true });
if (val) {
showToast(t("reconnect_calendar_to_use"), "warning");
}
}}
/>
</div>
)}
{!useEventTypeDestinationCalendarEmail && verifiedSecondaryEmails.length > 0 && (
<div className="w-full">
<SelectField
label={t("send_event_details_to")}
placeholder={
selectedSecondaryEmailId === -1 && (
<span className="text-default min-w-0 overflow-hidden truncate whitespace-nowrap">
<Badge variant="blue">{t("default")}</Badge> {user?.email || ""}
</span>
)
}
onChange={(option) =>
formMethods.setValue("secondaryEmailId", option?.value, { shouldDirty: true })
}
value={verifiedSecondaryEmails.find(
(secondaryEmail) =>
selectedSecondaryEmailId !== -1 && secondaryEmail.value === selectedSecondaryEmailId
)}
options={verifiedSecondaryEmails}
/>
</div>
)}
</div>
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} isOuterBorder={true} />
<div className="border-subtle space-y-6 rounded-lg border p-6">
<FormBuilder
title={t("booking_questions_title")}
@@ -198,7 +254,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}}
/>
</div>
<RequiresConfirmationController
eventType={eventType}
seatsEnabled={seatsEnabled}
@@ -206,7 +261,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
<Controller
name="requiresBookerEmailVerification"
render={({ field: { value, onChange } }) => (
@@ -215,14 +269,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
title={t("requires_booker_email_verification")}
{...shouldLockDisableProps("requiresBookerEmailVerification")}
data-testid="requires-booker-email-verification"
{...requiresBookerEmailVerificationProps}
description={t("description_requires_booker_email_verification")}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
)}
/>
<Controller
name="hideCalendarNotes"
render={({ field: { value, onChange } }) => (
@@ -230,15 +284,15 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
labelClassName="text-sm"
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
data-testid="disable-notes"
title={t("disable_notes")}
{...shouldLockDisableProps("hideCalendarNotes")}
{...hideCalendarNotesLocked}
description={t("disable_notes_description")}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
)}
/>
<Controller
name="successRedirectUrl"
render={({ field: { value, onChange } }) => (
@@ -252,6 +306,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)}
childrenClassName="lg:ml-0"
title={t("redirect_success_booking")}
data-testid="redirect-success-booking"
{...successRedirectUrlLocked}
description={t("redirect_url_description")}
checked={redirectUrlVisible}
@@ -266,6 +321,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
labelSrOnly
disabled={successRedirectUrlLocked.disabled}
placeholder={t("external_redirect_url")}
data-testid="external-redirect-url"
required={redirectUrlVisible}
type="text"
{...formMethods.register("successRedirectUrl")}
@@ -274,7 +330,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
className={classNames(
"p-1 text-sm text-orange-600",
formMethods.getValues("successRedirectUrl") ? "block" : "hidden"
)}>
)}
data-testid="redirect-url-warning">
{t("redirect_url_warning")}
</div>
</div>
@@ -282,7 +339,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</>
)}
/>
<SettingsToggle
labelClassName="text-sm"
toggleSwitchAtTheEnd={true}
@@ -295,58 +351,60 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
title={t("enable_private_url")}
Badge={
<a
data-testid="hashedLinkCheck-info"
target="_blank"
rel="noreferrer"
href="https://cal.com/docs/core-features/event-types/single-use-private-links">
<Info className="ml-1.5 h-4 w-4 cursor-pointer" />
</a>
}
{...shouldLockDisableProps("hashedLinkCheck")}
{...shouldLockDisableProps("hashedLink")}
description={t("private_link_description", { appName: APP_NAME })}
checked={hashedLinkVisible}
onCheckedChange={(e) => {
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
formMethods.setValue("hashedLink", e ? hashedUrl : undefined, { shouldDirty: true });
setHashedLinkVisible(e);
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
{!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={
formMethods.getValues("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 (formMethods.getValues("hashedLink")) {
showToast(t("private_link_copied"), "success");
} else {
showToast(t("enabled_after_update_description"), "warning");
}
}}>
<Copy className="h-4 w-4" />
</Button>
</Tooltip>
}
/>
)}
</div>
{!isManagedEventType && (
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
{!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={
formMethods.getValues("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 (formMethods.getValues("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>
<Controller
name="seatsPerTimeSlotEnabled"
render={({ field: { value, onChange } }) => (
@@ -369,10 +427,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
// Enabling seats will disable guests and requiring confirmation until fully supported
if (e) {
toggleGuests(false);
formMethods.setValue("requiresConfirmation", false);
formMethods.setValue("requiresConfirmation", false, { shouldDirty: true });
setRequiresConfirmation(false);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2, {
shouldDirty: true,
});
} else {
formMethods.setValue("seatsPerTimeSlot", null);
toggleGuests(true);
@@ -398,12 +458,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
onChange={(e) => {
onChange(Math.abs(Number(e.target.value)));
}}
data-testid="seats-per-time-slot"
/>
<div className="mt-4">
<Controller
name="seatsShowAttendees"
render={({ field: { value, onChange } }) => (
<CheckboxField
data-testid="show-attendees"
description={t("show_attendees")}
disabled={seatsLocked.disabled}
onChange={(e) => onChange(e)}
@@ -442,10 +504,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
title={t("lock_timezone_toggle_on_booking_page")}
{...shouldLockDisableProps("lockTimeZoneToggleOnBookingPage")}
{...lockTimeZoneToggleOnBookingPageLocked}
description={t("description_lock_timezone_toggle_on_booking_page")}
checked={value}
onCheckedChange={(e) => onChange(e)}
data-testid="lock-timezone-toggle"
/>
)}
/>
@@ -489,7 +552,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
{showEventNameTip && (
<CustomEventTypeModal
close={closeEventNameTip}
setValue={(val: string) => formMethods.setValue("eventName", val)}
setValue={(val: string) => formMethods.setValue("eventName", val, { shouldDirty: true })}
defaultValue={formMethods.getValues("eventName")}
placeHolder={eventNamePlaceholder}
event={eventNameObject}
+40 -19
View File
@@ -1,6 +1,6 @@
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useFormContext } from "react-hook-form";
import type { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
@@ -8,6 +8,7 @@ import { EventTypeAppCard } from "@calcom/app-store/_components/EventTypeAppCard
import type { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import type { EventTypeAppsList } from "@calcom/app-store/utils";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, Alert } from "@calcom/ui";
@@ -31,10 +32,14 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const allAppsData = formMethods.watch("metadata")?.apps || {};
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
formMethods.setValue("metadata", {
...formMethods.getValues("metadata"),
apps: _allAppsData,
});
formMethods.setValue(
"metadata",
{
...formMethods.getValues("metadata"),
apps: _allAppsData,
},
{ shouldDirty: true }
);
};
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
@@ -70,11 +75,13 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
};
};
const { shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
const { shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager({
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
translate: t,
formMethods,
});
const appsDisableProps = shouldLockDisableProps("apps", { simple: true });
const lockedText = appsDisableProps.isLocked ? "locked" : "unlocked";
const appsWithTeamCredentials = eventTypeApps?.items.filter((app) => app.teams.length) || [];
const cardsForAppsWithTeams = appsWithTeamCredentials.map((app) => {
@@ -93,7 +100,6 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
app={app}
eventType={eventType}
eventTypeFormMetadata={eventTypeFormMetadata}
{...shouldLockDisableProps("apps")}
/>
);
}
@@ -117,7 +123,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
}}
eventType={eventType}
eventTypeFormMetadata={eventTypeFormMetadata}
{...shouldLockDisableProps("apps")}
disabled={shouldLockDisableProps("apps").disabled}
/>
);
}
@@ -129,12 +135,28 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
<>
<div>
<div className="before:border-0">
{isManagedEventType && (
{(isManagedEventType || isChildrenManagedEventType) && (
<Alert
severity="neutral"
severity={appsDisableProps.isLocked ? "neutral" : "green"}
className="mb-2"
title={t("locked_for_members")}
message={t("locked_apps_description")}
title={
<Trans i18nKey={`${lockedText}_${isManagedEventType ? "for_members" : "by_team_admins"}`}>
{lockedText[0].toUpperCase()}
{lockedText.slice(1)} {isManagedEventType ? "for members" : "by team admins"}
</Trans>
}
actions={<div className="flex h-full items-center">{appsDisableProps.LockedIcon}</div>}
message={
<Trans
i18nKey={`apps_${lockedText}_${
isManagedEventType ? "for_members" : "by_team_admins"
}_description`}>
{isManagedEventType ? "Members" : "You"}{" "}
{appsDisableProps.isLocked
? "will be able to see the active apps but will not be able to edit any app settings"
: "will be able to see the active apps and will be able to edit any app settings"}
</Trans>
}
/>
)}
{!isPending && !installedApps?.length ? (
@@ -143,9 +165,9 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
headline={t("empty_installed_apps_headline")}
description={t("empty_installed_apps_description")}
buttonRaw={
isChildrenManagedEventType && !isManagedEventType ? (
appsDisableProps.disabled ? (
<Button StartIcon={Lock} color="secondary" disabled>
{t("locked_by_admin")}
{t("locked_by_team_admin")}
</Button>
) : (
<Button target="_blank" color="secondary" href="/apps">
@@ -170,13 +192,12 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
app={app}
eventType={eventType}
eventTypeFormMetadata={eventTypeFormMetadata}
{...shouldLockDisableProps("apps")}
/>
);
})}
</div>
</div>
{!shouldLockDisableProps("apps").disabled && (
{!appsDisableProps.disabled && (
<div className="bg-muted mt-6 rounded-md p-8">
{!isPending && notInstalledApps?.length ? (
<>
@@ -1,4 +1,4 @@
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetup } from "pages/event-types/[type]";
import { useState, memo, useEffect } from "react";
import { Controller, useFormContext } from "react-hook-form";
import type { OptionProps, SingleValueProps } from "react-select";
@@ -6,6 +6,7 @@ import { components } from "react-select";
import dayjs from "@calcom/dayjs";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { AvailabilityOption, FormValues } from "@calcom/features/eventtypes/lib/types";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
@@ -17,13 +18,6 @@ import { ExternalLink, Globe } from "@calcom/ui/components/icon";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
export type AvailabilityOption = {
label: string;
value: number;
isDefault: boolean;
isManaged?: boolean;
};
const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const { label, isDefault, isManaged = false } = props.data;
const { t } = useLocale();
@@ -160,12 +154,10 @@ EventTypeScheduleDetails.displayName = "EventTypeScheduleDetails";
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
const { t } = useLocale();
const { shouldLockIndicator, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { watch, setValue, getValues } = useFormContext<FormValues>();
const formMethods = useFormContext<FormValues>();
const { shouldLockIndicator, shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } =
useLockedFieldsManager({ eventType, translate: t, formMethods });
const { watch, setValue, getValues } = formMethods;
const watchSchedule = watch("schedule");
const [options, setOptions] = useState<AvailabilityOption[]>([]);
@@ -230,7 +222,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
);
setValue("availability", value);
setValue("availability", value, { shouldDirty: true });
},
[data]
);
@@ -238,7 +230,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
useEffect(() => {
if (!availabilityValue?.value) return;
setValue("schedule", availabilityValue.value);
setValue("schedule", availabilityValue.value, { shouldDirty: true });
}, [availabilityValue, setValue]);
return (
@@ -246,7 +238,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
<div className="border-subtle rounded-t-md border p-6">
<label htmlFor="availability" className="text-default mb-2 block text-sm font-medium leading-none">
{t("availability")}
{shouldLockIndicator("availability")}
{(isManagedEventType || isChildrenManagedEventType) && shouldLockIndicator("availability")}
</label>
{isPending && <SelectSkeletonLoader />}
{!isPending && (
@@ -257,10 +249,11 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
<Select
placeholder={t("select")}
options={options}
isDisabled={shouldLockDisableProps("availability").disabled}
isSearchable={false}
onChange={(selected) => {
field.onChange(selected?.value || null);
if (selected?.value) setValue("availability", selected);
if (selected?.value) setValue("availability", selected, { shouldDirty: true });
}}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
value={availabilityValue}
@@ -298,7 +291,7 @@ const UseCommonScheduleSettingsToggle = ({ eventType }: { eventType: EventTypeSe
onCheckedChange={(checked) => {
onChange(!checked);
if (!checked) {
setValue("schedule", null);
setValue("schedule", null, { shouldDirty: true });
}
}}
title={t("choose_common_schedule_team_event")}
+118 -45
View File
@@ -1,12 +1,14 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import type { FormValues } from "pages/event-types/[type]";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import type { Key } from "react";
import React, { useEffect, useState } from "react";
import type { UseFormRegisterReturn } from "react-hook-form";
import { Controller, useFormContext } from "react-hook-form";
import type { SingleValue } from "react-select";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { classNames } from "@calcom/lib";
import type { DurationType } from "@calcom/lib/convertToNewDurationType";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
@@ -61,7 +63,8 @@ const MinimumBookingNoticeInput = React.forwardRef<
minimumBookingNoticeDisplayValues.type,
"minutes",
minimumBookingNoticeDisplayValues.value
)
),
{ shouldDirty: true }
);
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
@@ -107,7 +110,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
);
});
export const EventLimitsTab = () => {
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const { t, i18n } = useLocale();
const formMethods = useFormContext<FormValues>();
@@ -128,6 +131,18 @@ export const EventLimitsTab = () => {
const watchPeriodType = formMethods.watch("periodType");
const { shouldLockIndicator, shouldLockDisableProps } = useLockedFieldsManager({
eventType,
translate: t,
formMethods,
});
const bookingLimitsLocked = shouldLockDisableProps("bookingLimits");
const durationLimitsLocked = shouldLockDisableProps("durationLimits");
const onlyFirstAvailableSlotLocked = shouldLockDisableProps("onlyShowFirstAvailableSlot");
const periodTypeLocked = shouldLockDisableProps("periodType");
const offsetStartLockedProps = shouldLockDisableProps("offsetStart");
const optionsPeriod = [
{ value: 0, label: t("business_days") },
{ value: 1, label: t("calendar_days") },
@@ -146,9 +161,12 @@ export const EventLimitsTab = () => {
<div className="border-subtle space-y-6 rounded-lg border p-6">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="beforeBufferTime">{t("before_event")}</Label>
<Label htmlFor="beforeBufferTime">
{t("before_event")}
{shouldLockIndicator("beforeBufferTime")}
</Label>
<Controller
name="beforeBufferTime"
name="beforeEventBuffer"
render={({ field: { onChange, value } }) => {
const beforeBufferOptions = [
{
@@ -166,6 +184,7 @@ export const EventLimitsTab = () => {
onChange={(val) => {
if (val) onChange(val.value);
}}
isDisabled={shouldLockDisableProps("beforeBufferTime").disabled}
defaultValue={
beforeBufferOptions.find((option) => option.value === value) || beforeBufferOptions[0]
}
@@ -176,9 +195,12 @@ export const EventLimitsTab = () => {
/>
</div>
<div className="w-full">
<Label htmlFor="afterBufferTime">{t("after_event")}</Label>
<Label htmlFor="afterBufferTime">
{t("after_event")}
{shouldLockIndicator("afterBufferTime")}
</Label>
<Controller
name="afterBufferTime"
name="afterEventBuffer"
render={({ field: { onChange, value } }) => {
const afterBufferOptions = [
{
@@ -196,6 +218,7 @@ export const EventLimitsTab = () => {
onChange={(val) => {
if (val) onChange(val.value);
}}
isDisabled={shouldLockDisableProps("afterBufferTime").disabled}
defaultValue={
afterBufferOptions.find((option) => option.value === value) || afterBufferOptions[0]
}
@@ -208,11 +231,20 @@ export const EventLimitsTab = () => {
</div>
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="minimumBookingNotice">{t("minimum_booking_notice")}</Label>
<MinimumBookingNoticeInput {...formMethods.register("minimumBookingNotice")} />
<Label htmlFor="minimumBookingNotice">
{t("minimum_booking_notice")}
{shouldLockIndicator("minimumBookingNotice")}
</Label>
<MinimumBookingNoticeInput
disabled={shouldLockDisableProps("minimumBookingNotice").disabled}
{...formMethods.register("minimumBookingNotice")}
/>
</div>
<div className="w-full">
<Label htmlFor="slotInterval">{t("slot_interval")}</Label>
<Label htmlFor="slotInterval">
{t("slot_interval")}
{shouldLockIndicator("slotInterval")}
</Label>
<Controller
name="slotInterval"
render={() => {
@@ -229,8 +261,11 @@ export const EventLimitsTab = () => {
return (
<Select
isSearchable={false}
isDisabled={shouldLockDisableProps("slotInterval").disabled}
onChange={(val) => {
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null, {
shouldDirty: true,
});
}}
defaultValue={
slotIntervalOptions.find(
@@ -254,15 +289,20 @@ export const EventLimitsTab = () => {
toggleSwitchAtTheEnd={true}
labelClassName="text-sm"
title={t("limit_booking_frequency")}
{...bookingLimitsLocked}
description={t("limit_booking_frequency_description")}
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("bookingLimits", {
PER_DAY: 1,
});
formMethods.setValue(
"bookingLimits",
{
PER_DAY: 1,
},
{ shouldDirty: true }
);
} else {
formMethods.setValue("bookingLimits", {});
formMethods.setValue("bookingLimits", {}, { shouldDirty: true });
}
}}
switchContainerClassName={classNames(
@@ -271,7 +311,12 @@ export const EventLimitsTab = () => {
)}
childrenClassName="lg:ml-0">
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<IntervalLimitsManager propertyName="bookingLimits" defaultLimit={1} step={1} />
<IntervalLimitsManager
disabled={bookingLimitsLocked.disabled}
propertyName="bookingLimits"
defaultLimit={1}
step={1}
/>
</div>
</SettingsToggle>
);
@@ -279,7 +324,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="onlyShowFirstAvailableSlot"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = value;
return (
<SettingsToggle
@@ -288,8 +333,9 @@ export const EventLimitsTab = () => {
title={t("limit_booking_only_first_slot")}
description={t("limit_booking_only_first_slot_description")}
checked={isChecked}
{...onlyFirstAvailableSlotLocked}
onCheckedChange={(active) => {
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
onChange(active ?? false);
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
@@ -301,7 +347,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="durationLimits"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0;
return (
<SettingsToggle
@@ -314,20 +360,22 @@ export const EventLimitsTab = () => {
childrenClassName="lg:ml-0"
title={t("limit_total_booking_duration")}
description={t("limit_total_booking_duration_description")}
{...durationLimitsLocked}
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("durationLimits", {
onChange({
PER_DAY: 60,
});
} else {
formMethods.setValue("durationLimits", {});
onChange({});
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<IntervalLimitsManager
propertyName="durationLimits"
defaultLimit={60}
disabled={durationLimitsLocked.disabled}
step={15}
textFieldSuffix={t("minutes")}
/>
@@ -338,7 +386,7 @@ export const EventLimitsTab = () => {
/>
<Controller
name="periodType"
render={({ field: { value } }) => {
render={({ field: { onChange, value } }) => {
const isChecked = value && value !== "UNLIMITED";
return (
@@ -352,13 +400,23 @@ export const EventLimitsTab = () => {
childrenClassName="lg:ml-0"
title={t("limit_future_bookings")}
description={t("limit_future_bookings_description")}
{...periodTypeLocked}
checked={isChecked}
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
onCheckedChange={(bool) => {
if (bool && !formMethods.getValues("periodDays")) {
formMethods.setValue("periodDays", 30, { shouldDirty: true });
}
return onChange(bool ? "ROLLING" : "UNLIMITED");
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<RadioGroup.Root
value={watchPeriodType}
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
{PERIOD_TYPES.map((period) => {
onValueChange={(val) =>
formMethods.setValue("periodType", val as PeriodType, { shouldDirty: true })
}>
{PERIOD_TYPES.filter((opt) =>
periodTypeLocked.disabled ? watchPeriodType === opt.type : true
).map((period) => {
if (period.type === "UNLIMITED") return null;
return (
<div
@@ -367,12 +425,14 @@ export const EventLimitsTab = () => {
watchPeriodType === "UNLIMITED" && "pointer-events-none opacity-30"
)}
key={period.type}>
<RadioGroup.Item
id={period.type}
value={period.type}
className="min-w-4 bg-default border-default flex h-4 w-4 cursor-pointer items-center rounded-full border focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="after:bg-inverted relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full" />
</RadioGroup.Item>
{!periodTypeLocked.disabled && (
<RadioGroup.Item
id={period.type}
value={period.type}
className="min-w-4 bg-default border-default flex h-4 w-4 cursor-pointer items-center rounded-full border focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="after:bg-inverted relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full" />
</RadioGroup.Item>
)}
{period.prefix ? <span>{period.prefix}&nbsp;</span> : null}
{period.type === "ROLLING" && (
@@ -382,23 +442,29 @@ export const EventLimitsTab = () => {
type="number"
className="border-default my-0 block w-16 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
placeholder="30"
disabled={periodTypeLocked.disabled}
{...formMethods.register("periodDays", { valueAsNumber: true })}
/>
<Select
options={optionsPeriod}
isSearchable={false}
onChange={(opt) =>
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
}
isDisabled={periodTypeLocked.disabled}
onChange={(opt) => {
formMethods.setValue(
"periodCountCalendarDays",
opt?.value === 1 ? true : false,
{ shouldDirty: true }
);
}}
name="periodCoundCalendarDays"
value={optionsPeriod.find((opt) => {
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0);
})}
defaultValue={optionsPeriod.find(
(opt) =>
opt.value ===
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0)
)}
/>
</div>
@@ -407,12 +473,13 @@ export const EventLimitsTab = () => {
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
<Controller
name="periodDates"
render={() => (
render={({ field: { onChange } }) => (
<DateRangePicker
startDate={formMethods.getValues("periodDates").startDate}
endDate={formMethods.getValues("periodDates").endDate}
disabled={periodTypeLocked.disabled}
onDatesChange={({ startDate, endDate }) => {
formMethods.setValue("periodDates", {
onChange({
startDate,
endDate,
});
@@ -442,11 +509,12 @@ export const EventLimitsTab = () => {
childrenClassName="lg:ml-0"
title={t("offset_toggle")}
description={t("offset_toggle_description")}
{...offsetStartLockedProps}
checked={offsetToggle}
onCheckedChange={(active) => {
setOffsetToggle(active);
if (!active) {
formMethods.setValue("offsetStart", 0);
formMethods.setValue("offsetStart", 0, { shouldDirty: true });
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -577,11 +645,16 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
);
if (!rest || !currentKeys.length) return;
//currentDurationLimits is always defined so can be casted
// @ts-expect-error FIXME Fix these typings
setValue(propertyName, {
...watchIntervalLimits,
[rest.value]: defaultLimit,
});
setValue(
propertyName,
// @ts-expect-error FIXME Fix these typings
{
...watchIntervalLimits,
[rest.value]: defaultLimit,
},
{ shouldDirty: true }
);
};
return (
@@ -611,7 +684,7 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
)}
onLimitChange={(intervalLimitKey, val) =>
// @ts-expect-error FIXME Fix these typings
setValue(`${propertyName}.${intervalLimitKey}`, val)
setValue(`${propertyName}.${intervalLimitKey}`, val, { shouldDirty: true })
}
onDelete={(intervalLimitKey) => {
const current = currentIntervalLimits;
+58 -43
View File
@@ -2,7 +2,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { ErrorMessage } from "@hookform/error-message";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
import { Controller, useFormContext, useFieldArray } from "react-hook-form";
import type { MultiValue } from "react-select";
@@ -11,7 +11,8 @@ import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationType, MeetLocationType } 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 { CAL_URL } from "@calcom/lib/constants";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { slugify } from "@calcom/lib/slugify";
@@ -83,7 +84,7 @@ const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
return mounted ? (
<Editor
getText={() => md.render(formMethods.getValues("description") || "")}
setText={(value: string) => formMethods.setValue("description", turndown(value))}
setText={(value: string) => formMethods.setValue("description", turndown(value), { shouldDirty: true })}
excludedToolbarItems={["blockType"]}
placeholder={t("quick_video_meeting")}
editable={isEditable}
@@ -142,12 +143,7 @@ export const EventSetupTab = (
);
const { isChildrenManagedEventType, isManagedEventType, shouldLockIndicator, shouldLockDisableProps } =
useLockedFieldsManager(
formMethods.getValues(),
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
useLockedFieldsManager({ eventType, translate: t, formMethods });
const Locations = () => {
const { t } = useLocale();
const {
@@ -183,7 +179,6 @@ export const EventSetupTab = (
index: number;
}) => {
const { eventLocationType, index, ...remainingProps } = props;
if (eventLocationType?.organizerInputType === "text") {
const { defaultValue, ...rest } = remainingProps;
@@ -200,6 +195,7 @@ export const EventSetupTab = (
required
onChange={onChange}
value={value}
{...(shouldLockDisableProps("locations").disabled ? { disabled: true } : {})}
className="my-0"
{...rest}
/>
@@ -218,6 +214,7 @@ export const EventSetupTab = (
return (
<PhoneInput
required
disabled={shouldLockDisableProps("locations").disabled}
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
name={`locations[${index}].${eventLocationType.defaultValueVariable}`}
value={value}
@@ -233,7 +230,10 @@ export const EventSetupTab = (
};
const [showEmptyLocationSelect, setShowEmptyLocationSelect] = useState(false);
const [selectedNewOption, setSelectedNewOption] = useState<SingleValueLocationOption | null>(null);
const defaultInitialLocation = defaultValue || null;
const [selectedNewOption, setSelectedNewOption] = useState<SingleValueLocationOption | null>(
defaultInitialLocation
);
return (
<div className="w-full">
@@ -288,16 +288,18 @@ export const EventSetupTab = (
}
}}
/>
<button
data-testid={`delete-locations.${index}.type`}
className="min-h-9 block h-9 px-2"
type="button"
onClick={() => remove(index)}
aria-label={t("remove")}>
<div className="h-4 w-4">
<X className="border-l-1 hover:text-emphasis text-subtle h-4 w-4" />
</div>
</button>
{!(shouldLockDisableProps("locations").disabled && isChildrenManagedEventType) && (
<button
data-testid={`delete-locations.${index}.type`}
className="min-h-9 block h-9 px-2"
type="button"
onClick={() => remove(index)}
aria-label={t("remove")}>
<div className="h-4 w-4">
<X className="border-l-1 hover:text-emphasis text-subtle h-4 w-4" />
</div>
</button>
)}
</div>
{eventLocationType?.organizerInputType && (
@@ -318,7 +320,7 @@ export const EventSetupTab = (
/>
</div>
<ErrorMessage
errors={formMethods.formState.errors.locations?.[index]}
errors={formMethods.formState.errors?.locations?.[index]}
name={eventLocationType.defaultValueVariable}
className="text-error my-1 ml-6 text-sm"
as="div"
@@ -329,6 +331,7 @@ export const EventSetupTab = (
<CheckboxField
name={`locations[${index}].displayLocationPublicly`}
data-testid="display-location"
disabled={shouldLockDisableProps("locations").disabled}
defaultChecked={defaultLocation?.displayLocationPublicly}
description={t("display_location_label")}
onChange={(e) => {
@@ -401,7 +404,7 @@ export const EventSetupTab = (
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
Change it{" "}
<Link
href={`${CAL_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
href={`${WEBAPP_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
className="underline">
here.
</Link>{" "}
@@ -412,12 +415,13 @@ export const EventSetupTab = (
{isChildrenManagedEventType && !locationAvailable && locationDetails && (
<p className="pl-1 text-sm leading-none text-red-600">
{t("app_not_connected", { appName: locationDetails.name })}{" "}
<a className="underline" href={`${CAL_URL}/apps/${locationDetails.slug}`}>
<a className="underline" href={`${WEBAPP_URL}/apps/${locationDetails.slug}`}>
{t("connect_now")}
</a>
</p>
)}
{validLocations.length > 0 && !isManagedEventType && !isChildrenManagedEventType && (
{validLocations.length > 0 && !shouldLockDisableProps("locations").disabled && (
// && !isChildrenManagedEventType : Add this to hide add-location button only when location is disabled by Admin
<li>
<Button
data-testid="add-location"
@@ -444,9 +448,11 @@ export const EventSetupTab = (
const lengthLockedProps = shouldLockDisableProps("length");
const descriptionLockedProps = shouldLockDisableProps("description");
const urlLockedProps = shouldLockDisableProps("slug");
const titleLockedProps = shouldLockDisableProps("title");
const urlPrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<div>
@@ -455,20 +461,22 @@ export const EventSetupTab = (
<TextField
required
label={t("title")}
{...shouldLockDisableProps("title")}
{...(isManagedEventType || isChildrenManagedEventType ? titleLockedProps : {})}
defaultValue={eventType.title}
{...formMethods.register("title")}
/>
<div>
<Label>
<Label htmlFor="editor">
{t("description")}
{shouldLockIndicator("description")}
{(isManagedEventType || isChildrenManagedEventType) && shouldLockIndicator("description")}
</Label>
<DescriptionEditor isEditable={!descriptionLockedProps.disabled} />
</div>
<TextField
required
label={t("URL")}
{...shouldLockDisableProps("slug")}
{...(isManagedEventType || isChildrenManagedEventType ? urlLockedProps : {})}
defaultValue={eventType.slug}
addOnLeading={
<>
{urlPrefix}/
@@ -497,6 +505,7 @@ export const EventSetupTab = (
defaultValue={selectedMultipleDuration}
name="metadata.multipleDuration"
isSearchable={false}
isDisabled={lengthLockedProps.disabled}
className="h-auto !min-h-[36px] text-sm"
options={multipleDurationOptions}
value={selectedMultipleDuration}
@@ -511,16 +520,16 @@ export const EventSetupTab = (
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
if (newOptions.length > 0) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
} else {
setDefaultDuration(null);
}
}
if (newOptions.length === 1 && defaultDuration === null) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
}
formMethods.setValue("metadata.multipleDuration", values);
formMethods.setValue("metadata.multipleDuration", values, { shouldDirty: true });
}}
/>
</div>
@@ -541,7 +550,7 @@ export const EventSetupTab = (
setDefaultDuration(
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
);
if (option) formMethods.setValue("length", option.value);
if (option) formMethods.setValue("length", option.value, { shouldDirty: true });
}}
/>
</div>
@@ -550,7 +559,8 @@ export const EventSetupTab = (
<TextField
required
type="number"
{...lengthLockedProps}
data-testid="duration"
{...(isManagedEventType || isChildrenManagedEventType ? lengthLockedProps : {})}
label={t("duration")}
defaultValue={formMethods.getValues("length") ?? 15}
{...formMethods.register("length")}
@@ -570,27 +580,32 @@ export const EventSetupTab = (
setMultipleDuration(undefined);
setSelectedMultipleDuration([]);
setDefaultDuration(null);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length);
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
formMethods.setValue("length", eventType.length, { shouldDirty: true });
} else {
setMultipleDuration([]);
formMethods.setValue("metadata.multipleDuration", []);
formMethods.setValue("length", 0);
formMethods.setValue("metadata.multipleDuration", [], { shouldDirty: true });
formMethods.setValue("length", 0, { shouldDirty: true });
}
}}
/>
</div>
)}
</div>
<div className="border-subtle rounded-lg border p-6">
<div>
<Skeleton as={Label} loadingClassName="w-16">
<Skeleton as={Label} loadingClassName="w-16" htmlFor="locations">
{t("location")}
{/*improve shouldLockIndicator function to also accept eventType and then conditionally render
based on Managed Event type or not.*/}
{shouldLockIndicator("locations")}
</Skeleton>
<Controller name="locations" render={() => <Locations />} />
<Controller
name="locations"
control={formMethods.control}
defaultValue={eventType.locations || []}
render={() => <Locations />}
/>
</div>
</div>
</div>
+361 -96
View File
@@ -1,17 +1,18 @@
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";
import type { EventTypeSetupProps, Host } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import type { ComponentProps, Dispatch, SetStateAction } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import type { Options } from "react-select";
import type { CheckedSelectOption } from "@calcom/features/eventtypes/components/CheckedTeamSelect";
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums";
import { Label, Select } from "@calcom/ui";
import { Label, Select, SettingsToggle } from "@calcom/ui";
interface IUserToValue {
id: number | null;
@@ -21,6 +22,13 @@ interface IUserToValue {
email: string;
}
type TeamMember = {
value: string;
label: string;
avatar: string;
email: string;
};
const mapUserToValue = ({ id, name, username, avatar, email }: IUserToValue, pendingString: string) => ({
value: `${id || ""}`,
label: `${name || email || ""}${!username ? ` (${pendingString})` : ""}`,
@@ -97,6 +105,43 @@ const ChildrenEventTypesList = ({
);
};
const AssignAllTeamMembers = ({
assignAllTeamMembers,
setAssignAllTeamMembers,
onActive,
onInactive,
}: {
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
onActive: () => void;
onInactive?: () => void;
}) => {
const { t } = useLocale();
const { setValue } = useFormContext<FormValues>();
return (
<Controller<FormValues>
name="assignAllTeamMembers"
render={() => (
<SettingsToggle
title={t("automatically_add_all_team_members")}
labelClassName="mt-0.5 font-normal"
checked={assignAllTeamMembers}
onCheckedChange={(active) => {
setValue("assignAllTeamMembers", active, { shouldDirty: true });
setAssignAllTeamMembers(active);
if (active) {
onActive();
} else if (!!onInactive) {
onInactive();
}
}}
/>
)}
/>
);
};
const CheckedHostField = ({
labelText,
placeholder,
@@ -107,18 +152,18 @@ const CheckedHostField = ({
helperText,
...rest
}: {
labelText: string;
labelText?: string;
placeholder: string;
isFixed: boolean;
value: { isFixed: boolean; userId: number }[];
onChange?: (options: { isFixed: boolean; userId: number }[]) => void;
value: Host[];
onChange?: (options: Host[]) => 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">
<div className="flex flex-col rounded-md">
<div>
<Label>{labelText}</Label>
{labelText ? <Label>{labelText}</Label> : <></>}
<CheckedTeamSelect
isOptionDisabled={(option) => !!value.find((host) => host.userId.toString() === option.value)}
onChange={(options) => {
@@ -127,23 +172,22 @@ const CheckedHostField = ({
options.map((option) => ({
isFixed,
userId: parseInt(option.value, 10),
priority: option.priority ?? 2,
}))
);
}}
value={(value || [])
.filter(({ isFixed: _isFixed }) => isFixed === _isFixed)
.map(
(host) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
options.find((member) => member.value === host.userId.toString())!
)
.map((host) => {
const option = options.find((member) => member.value === host.userId.toString());
return option ? { ...option, priority: host.priority ?? 2, isFixed } : options[0];
})
.filter(Boolean)}
controlShouldRenderValue={false}
options={options}
placeholder={placeholder}
{...rest}
/>
{helperText && <p className="text-subtle text-sm">{helperText}</p>}
</div>
</div>
);
@@ -161,76 +205,255 @@ const FixedHostHelper = (
</Trans>
);
const FixedHosts = ({
teamMembers,
value,
onChange,
assignAllTeamMembers,
setAssignAllTeamMembers,
isRoundRobinEvent = false,
}: {
value: Host[];
onChange: (hosts: Host[]) => void;
teamMembers: TeamMember[];
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
isRoundRobinEvent?: boolean;
}) => {
const { t } = useLocale();
const { getValues, setValue } = useFormContext<FormValues>();
const hasActiveFixedHosts = isRoundRobinEvent && getValues("hosts").some((host) => host.isFixed);
const [isDisabled, setIsDisabled] = useState(hasActiveFixedHosts);
return (
<div className="mt-5 rounded-lg">
{!isRoundRobinEvent ? (
<>
<div className="border-subtle mt-5 rounded-t-md border p-6 pb-5">
<Label className="mb-1 text-sm font-semibold">{t("fixed_hosts")}</Label>
<p className="text-subtle max-w-full break-words text-sm leading-tight">{FixedHostHelper}</p>
</div>
<div className="border-subtle rounded-b-md border border-t-0">
<AddMembersWithSwitch
teamMembers={teamMembers}
value={value}
onChange={onChange}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
automaticAddAllEnabled={!isRoundRobinEvent}
isFixed={true}
onActive={() =>
setValue(
"hosts",
teamMembers.map((teamMember) => ({
isFixed: true,
userId: parseInt(teamMember.value, 10),
priority: 2,
})),
{ shouldDirty: true }
)
}
/>
</div>
</>
) : (
<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("fixed_hosts")}
description={FixedHostHelper}
checked={isDisabled}
labelClassName="text-sm"
descriptionClassName=" text-sm text-subtle"
onCheckedChange={(checked) => {
if (!checked) {
const rrHosts = getValues("hosts")
.filter((host) => !host.isFixed)
.sort((a, b) => (b.priority ?? 2) - (a.priority ?? 2));
setValue("hosts", rrHosts, { shouldDirty: true });
}
setIsDisabled(checked);
}}
childrenClassName="lg:ml-0">
<div className="border-subtle flex flex-col gap-6 rounded-bl-md rounded-br-md border border-t-0">
<AddMembersWithSwitch
teamMembers={teamMembers}
value={value}
onChange={onChange}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
automaticAddAllEnabled={!isRoundRobinEvent}
isFixed={true}
onActive={() =>
setValue(
"hosts",
teamMembers.map((teamMember) => ({
isFixed: true,
userId: parseInt(teamMember.value, 10),
priority: 2,
})),
{ shouldDirty: true }
)
}
/>
</div>
</SettingsToggle>
)}
</div>
);
};
const AddMembersWithSwitch = ({
teamMembers,
value,
onChange,
assignAllTeamMembers,
setAssignAllTeamMembers,
automaticAddAllEnabled,
onActive,
isFixed,
}: {
value: Host[];
onChange: (hosts: Host[]) => void;
teamMembers: TeamMember[];
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
automaticAddAllEnabled: boolean;
onActive: () => void;
isFixed: boolean;
}) => {
const { t } = useLocale();
const { setValue } = useFormContext<FormValues>();
return (
<div className="rounded-md ">
<div className="flex flex-col rounded-md px-6 pb-2 pt-6">
{automaticAddAllEnabled ? (
<div className="mb-2">
<AssignAllTeamMembers
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
onActive={onActive}
onInactive={() => setValue("hosts", [], { shouldDirty: true })}
/>
</div>
) : (
<></>
)}
{!assignAllTeamMembers || !automaticAddAllEnabled ? (
<CheckedHostField
value={value}
onChange={onChange}
isFixed={isFixed}
options={teamMembers.sort(sortByLabel)}
placeholder={t("add_attendees")}
/>
) : (
<></>
)}
</div>
</div>
);
};
const RoundRobinHosts = ({
teamMembers,
value,
onChange,
assignAllTeamMembers,
setAssignAllTeamMembers,
}: {
value: { isFixed: boolean; userId: number }[];
onChange: (hosts: { isFixed: boolean; userId: number }[]) => void;
teamMembers: {
value: string;
label: string;
avatar: string;
email: string;
}[];
value: Host[];
onChange: (hosts: Host[]) => void;
teamMembers: TeamMember[];
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
}) => {
const { t } = useLocale();
const { setValue } = useFormContext<FormValues>();
return (
<>
<CheckedHostField
options={teamMembers.sort(sortByLabel)}
isFixed={true}
onChange={(changeValue) => {
onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]);
}}
value={value}
placeholder={t("add_fixed_hosts")}
labelText={t("fixed_hosts")}
helperText={FixedHostHelper}
/>
<CheckedHostField
options={teamMembers.sort(sortByLabel)}
onChange={(changeValue) => onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue])}
value={value}
isFixed={false}
placeholder={t("add_attendees")}
labelText={t("round_robin_hosts")}
helperText={t("round_robin_helper")}
/>
</>
<div className="rounded-lg ">
<div className="border-subtle mt-5 rounded-t-md border p-6 pb-5">
<Label className="mb-1 text-sm font-semibold">{t("round_robin_hosts")}</Label>
<p className="text-subtle max-w-full break-words text-sm leading-tight">{t("round_robin_helper")}</p>
</div>
<div className="border-subtle rounded-b-md border border-t-0">
<AddMembersWithSwitch
teamMembers={teamMembers}
value={value}
onChange={onChange}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
automaticAddAllEnabled={true}
isFixed={false}
onActive={() =>
setValue(
"hosts",
teamMembers
.map((teamMember) => ({
isFixed: false,
userId: parseInt(teamMember.value, 10),
priority: 2,
}))
.sort((a, b) => b.priority - a.priority),
{ shouldDirty: true }
)
}
/>
</div>
</div>
);
};
const ChildrenEventTypes = ({
childrenEventTypeOptions,
assignAllTeamMembers,
setAssignAllTeamMembers,
}: {
childrenEventTypeOptions: ReturnType<typeof mapMemberToChildrenOption>[];
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
}) => {
const { setValue } = useFormContext<FormValues>();
return (
<Controller<FormValues>
name="children"
render={({ field: { onChange, value } }) => (
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
)}
/>
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
<div className="flex flex-col gap-4">
<AssignAllTeamMembers
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
onActive={() => setValue("children", childrenEventTypeOptions, { shouldDirty: true })}
/>
{!assignAllTeamMembers ? (
<Controller<FormValues>
name="children"
render={({ field: { onChange, value } }) => (
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
)}
/>
) : (
<></>
)}
</div>
</div>
);
};
const Hosts = ({
teamMembers,
assignAllTeamMembers,
setAssignAllTeamMembers,
}: {
teamMembers: {
value: string;
label: string;
avatar: string;
email: string;
}[];
teamMembers: TeamMember[];
assignAllTeamMembers: boolean;
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
}) => {
const { t } = useLocale();
const {
control,
resetField,
setValue,
getValues,
formState: { submitCount },
} = useFormContext<FormValues>();
@@ -250,10 +473,12 @@ const Hosts = ({
initialValue.current = { hosts: getValues("hosts"), schedulingType, submitCount };
return;
}
resetField("hosts", {
defaultValue: initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
});
}, [schedulingType, resetField, getValues, submitCount]);
setValue(
"hosts",
initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
{ shouldDirty: true }
);
}, [schedulingType, setValue, getValues, submitCount]);
return (
<Controller<FormValues>
@@ -261,26 +486,39 @@ const Hosts = ({
render={({ field: { onChange, value } }) => {
const schedulingTypeRender = {
COLLECTIVE: (
<CheckedHostField
<FixedHosts
teamMembers={teamMembers}
value={value}
onChange={onChange}
isFixed={true}
options={teamMembers.sort(sortByLabel)}
placeholder={t("add_attendees")}
labelText={t("team")}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
/>
),
ROUND_ROBIN: (
<>
<RoundRobinHosts teamMembers={teamMembers} onChange={onChange} value={value} />
{/*<TextField
required
type="number"
label={t("minimum_round_robin_hosts_count")}
defaultValue={1}
{...formMethods.register("minimumHostCount")}
addOnSuffix={<>{t("hosts")}</>}
/>*/}
<FixedHosts
teamMembers={teamMembers}
value={value}
onChange={(changeValue) => {
onChange([...value.filter((host: Host) => !host.isFixed), ...changeValue]);
}}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
isRoundRobinEvent={true}
/>
<RoundRobinHosts
teamMembers={teamMembers}
value={value}
onChange={(changeValue) => {
onChange(
[...value.filter((host: Host) => host.isFixed), ...changeValue].sort(
(a, b) => b.priority - a.priority
)
);
}}
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
/>
</>
),
MANAGED: <></>,
@@ -320,34 +558,61 @@ export const EventTeamTab = ({
.filter(pendingMembers)
.map((member) => mapUserToValue(member, t("pending")));
const childrenEventTypeOptions = teamMembers.filter(pendingMembers).map((member) => {
return mapMemberToChildrenOption(member, eventType.slug, t("pending"));
return mapMemberToChildrenOption(
{ ...member, eventTypes: member.eventTypes.filter((et) => et !== eventType.slug) },
eventType.slug,
t("pending")
);
});
const isManagedEventType = eventType.schedulingType === SchedulingType.MANAGED;
const { getValues, setValue } = useFormContext<FormValues>();
const [assignAllTeamMembers, setAssignAllTeamMembers] = useState<boolean>(
getValues("assignAllTeamMembers") ?? false
);
return (
<div>
{team && !isManagedEventType && (
<div className="space-y-5">
<div className="flex flex-col">
<Label>{t("scheduling_type")}</Label>
<Controller<FormValues>
name="schedulingType"
render={({ field: { value, onChange } }) => (
<Select
options={schedulingTypeOptions}
value={schedulingTypeOptions.find((opt) => opt.value === value)}
className="w-full"
onChange={(val) => {
onChange(val?.value);
}}
/>
)}
/>
<>
<div className="border-subtle flex flex-col rounded-md">
<div className="border-subtle rounded-t-md border p-6 pb-5">
<Label className="mb-1 text-sm font-semibold">{t("assignment")}</Label>
<p className="text-subtle max-w-full break-words text-sm leading-tight">
{t("assignment_description")}
</p>
</div>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<Label>{t("scheduling_type")}</Label>
<Controller<FormValues>
name="schedulingType"
render={({ field: { value, onChange } }) => (
<Select
options={schedulingTypeOptions}
value={schedulingTypeOptions.find((opt) => opt.value === value)}
className="w-full"
onChange={(val) => {
onChange(val?.value);
setValue("assignAllTeamMembers", false, { shouldDirty: true });
setAssignAllTeamMembers(false);
}}
/>
)}
/>
</div>
</div>
<Hosts teamMembers={teamMembersOptions} />
</div>
<Hosts
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
teamMembers={teamMembersOptions}
/>
</>
)}
{team && isManagedEventType && (
<ChildrenEventTypes childrenEventTypeOptions={childrenEventTypeOptions} />
<ChildrenEventTypes
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
childrenEventTypeOptions={childrenEventTypeOptions}
/>
)}
</div>
);
@@ -2,18 +2,20 @@ import { Webhook as TbWebhook } from "lucide-react";
import type { TFunction } from "next-i18next";
import { Trans } from "next-i18next";
import { useRouter } from "next/navigation";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useMemo, useState, Suspense } from "react";
import type { UseFormReturn } from "react-hook-form";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { EventTypeEmbedButton, EventTypeEmbedDialog } from "@calcom/features/embed/EventTypeEmbed";
import type { FormValues, AvailabilityOption } from "@calcom/features/eventtypes/lib/types";
import Shell from "@calcom/features/shell/Shell";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import type { DialogProps } from "@calcom/ui";
import {
Button,
ButtonGroup,
@@ -51,8 +53,6 @@ import {
Loader,
} from "@calcom/ui/components/icon";
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
type Props = {
children: React.ReactNode;
eventType: EventTypeSetupProps["eventType"];
@@ -133,6 +133,60 @@ function getNavigation({
];
}
function DeleteDialog({
isManagedEvent,
eventTypeId,
open,
onOpenChange,
}: { isManagedEvent: string; eventTypeId: number } & Pick<DialogProps, "open" | "onOpenChange">) {
const utils = trpc.useContext();
const { t } = useLocale();
const router = useRouter();
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
onSuccess: async () => {
await utils.viewer.eventTypes.invalidate();
showToast(t("event_type_deleted_successfully"), "success");
router.push("/event-types");
onOpenChange?.(false);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
onOpenChange?.(false);
} else if (err instanceof TRPCClientError) {
showToast(err.message, "error");
}
},
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t(`delete${isManagedEvent}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => {
e.preventDefault();
deleteMutation.mutate({ id: eventTypeId });
}}>
<p className="mt-5">
<Trans
i18nKey={`delete${isManagedEvent}_event_type_description`}
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
<ul>
<li>Members assigned to this event type will also have their event types deleted.</li>
<li>Anyone who they&apos;ve shared their link with will no longer be able to book using it.</li>
</ul>
</Trans>
</p>
</ConfirmationDialogContent>
</Dialog>
);
}
function EventTypeSingleLayout({
children,
eventType,
@@ -149,9 +203,8 @@ function EventTypeSingleLayout({
bookerUrl,
activeWebhooksNumber,
}: Props) {
const utils = trpc.useContext();
const { t } = useLocale();
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const hasPermsToDelete =
@@ -160,30 +213,12 @@ function EventTypeSingleLayout({
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
isUserOrganizationAdmin;
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
onSuccess: async () => {
await utils.viewer.eventTypes.invalidate();
showToast(t("event_type_deleted_successfully"), "success");
router.push("/event-types");
setDeleteDialogOpen(false);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
setDeleteDialogOpen(false);
} else if (err instanceof TRPCClientError) {
showToast(err.message, "error");
}
},
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager({
eventType,
translate: t,
formMethods,
});
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
formMethods.getValues(),
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const length = formMethods.watch("length");
const multipleDuration = formMethods.watch("metadata")?.multipleDuration;
@@ -308,7 +343,7 @@ function EventTypeSingleLayout({
id="hiddenSwitch"
checked={!formMethods.watch("hidden")}
onCheckedChange={(e) => {
formMethods.setValue("hidden", !e);
formMethods.setValue("hidden", !e, { shouldDirty: true });
}}
/>
</div>
@@ -352,7 +387,7 @@ function EventTypeSingleLayout({
StartIcon={Code}
color="secondary"
variant="icon"
namespace={eventType.slug}
namespace=""
tooltip={t("embed")}
tooltipSide="bottom"
tooltipOffset={4}
@@ -424,7 +459,7 @@ function EventTypeSingleLayout({
id="hiddenSwitch"
checked={!formMethods.watch("hidden")}
onCheckedChange={(e) => {
formMethods.setValue("hidden", !e);
formMethods.setValue("hidden", !e, { shouldDirty: true });
}}
/>
</div>
@@ -435,6 +470,7 @@ function EventTypeSingleLayout({
className="ml-4 lg:ml-0"
type="submit"
loading={isUpdateMutationLoading}
disabled={!formMethods.formState.isDirty}
data-testid="update-eventtype"
form="event-type-form">
{t("save")}
@@ -467,31 +503,13 @@ function EventTypeSingleLayout({
</div>
</div>
</Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t(`delete${isManagedEvent}_event_type`)}
confirmBtnText={t(`confirm_delete_event_type`)}
loadingText={t(`confirm_delete_event_type`)}
onConfirm={(e) => {
e.preventDefault();
deleteMutation.mutate({ id: formMethods.getValues("id") });
}}>
<p className="mt-5">
<Trans
i18nKey={`delete${isManagedEvent}_event_type_description`}
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
<ul>
<li>Members assigned to this event type will also have their event types deleted.</li>
<li>
Anyone who they&apos;ve shared their link with will no longer be able to book using it.
</li>
</ul>
</Trans>
</p>
</ConfirmationDialogContent>
</Dialog>
<DeleteDialog
eventTypeId={eventType.id}
isManagedEvent={isManagedEvent}
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
/>
<EventTypeEmbedDialog />
</Shell>
);
@@ -4,8 +4,10 @@ import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext } from "react-hook-form";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { WebhookForm } from "@calcom/features/webhooks/components";
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
@@ -20,6 +22,7 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
const { t } = useLocale();
const utils = trpc.useContext();
const formMethods = useFormContext<FormValues>();
const { data: webhooks } = trpc.viewer.webhook.list.useQuery({ eventTypeId: eventType.id });
@@ -96,11 +99,11 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
);
};
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager(
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager({
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
translate: t,
formMethods,
});
const webhookLockedStatus = shouldLockDisableProps("webhooks");
return (
@@ -163,7 +166,7 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
buttonRaw={
isChildrenManagedEventType && !isManagedEventType ? (
<Button StartIcon={Lock} color="secondary" disabled>
{t("locked_by_admin")}
{t("locked_by_team_admin")}
</Button>
) : (
<NewWebhookButton />
@@ -1,11 +1,12 @@
import type { Webhook } from "@prisma/client";
import { useSession } from "next-auth/react";
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetup } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { WebhookForm } from "@calcom/features/webhooks/components";
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
@@ -33,11 +34,7 @@ export default function InstantEventController({
const [instantEventState, setInstantEventState] = useState<boolean>(eventType?.isInstantEvent ?? false);
const formMethods = useFormContext<FormValues>();
const { shouldLockDisableProps } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { shouldLockDisableProps } = useLockedFieldsManager({ eventType, translate: t, formMethods });
const instantLocked = shouldLockDisableProps("isInstantEvent");
@@ -81,10 +78,10 @@ export default function InstantEventController({
data-testid="instant-event-check"
onCheckedChange={(e) => {
if (!e) {
formMethods.setValue("isInstantEvent", false);
formMethods.setValue("isInstantEvent", false, { shouldDirty: true });
setInstantEventState(false);
} else {
formMethods.setValue("isInstantEvent", true);
formMethods.setValue("isInstantEvent", true, { shouldDirty: true });
setInstantEventState(true);
}
}}>
@@ -104,6 +101,8 @@ export default function InstantEventController({
const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) => {
const { t } = useLocale();
const utils = trpc.useContext();
const formMethods = useFormContext<FormValues>();
const { data: webhooks } = trpc.viewer.webhook.list.useQuery({
eventTypeId: eventType.id,
eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING],
@@ -179,11 +178,11 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
);
};
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager(
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager({
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
formMethods,
translate: t,
});
const webhookLockedStatus = shouldLockDisableProps("webhooks");
return (
@@ -1,8 +1,9 @@
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetup } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext } from "react-hook-form";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Frequency } from "@calcom/prisma/zod-utils";
@@ -31,11 +32,7 @@ export default function RecurringEventController({
value: value.toString(),
}));
const { shouldLockDisableProps } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { shouldLockDisableProps } = useLockedFieldsManager({ eventType, translate: t, formMethods });
const recurringLocked = shouldLockDisableProps("recurringEvent");
@@ -66,7 +63,7 @@ export default function RecurringEventController({
data-testid="recurring-event-check"
onCheckedChange={(e) => {
if (!e) {
formMethods.setValue("recurringEvent", null);
formMethods.setValue("recurringEvent", null, { shouldDirty: true });
setRecurringEventState(null);
} else {
const newVal = eventType.recurringEvent || {
@@ -74,7 +71,7 @@ export default function RecurringEventController({
count: 12,
freq: Frequency.WEEKLY,
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}
}}>
@@ -95,7 +92,7 @@ export default function RecurringEventController({
...recurringEventState,
interval: parseInt(event?.target.value),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -110,7 +107,7 @@ export default function RecurringEventController({
...recurringEventState,
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -129,7 +126,7 @@ export default function RecurringEventController({
...recurringEventState,
count: parseInt(event?.target.value),
};
formMethods.setValue("recurringEvent", newVal);
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
setRecurringEventState(newVal);
}}
/>
@@ -1,13 +1,14 @@
import * as RadioGroup from "@radix-ui/react-radio-group";
import type { UnitTypeLongPlural } from "dayjs";
import { Trans } from "next-i18next";
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import type { EventTypeSetup } from "pages/event-types/[type]";
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import type z from "zod";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@@ -37,16 +38,12 @@ export default function RequiresConfirmationController({
useEffect(() => {
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, { shouldDirty: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [requiresConfirmation]);
const { shouldLockDisableProps } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const { shouldLockDisableProps } = useLockedFieldsManager({ eventType, translate: t, formMethods });
const requiresConfirmationLockedProps = shouldLockDisableProps("requiresConfirmation");
const options = [
@@ -75,13 +72,14 @@ export default function RequiresConfirmationController({
)}
childrenClassName="lg:ml-0"
title={t("requires_confirmation")}
data-testid="requires-confirmation"
disabled={seatsEnabled || requiresConfirmationLockedProps.disabled}
tooltip={seatsEnabled ? t("seat_options_doesnt_support_confirmation") : undefined}
description={t("requires_confirmation_description")}
checked={requiresConfirmation}
LockedIcon={requiresConfirmationLockedProps.LockedIcon}
onCheckedChange={(val) => {
formMethods.setValue("requiresConfirmation", val);
formMethods.setValue("requiresConfirmation", val, { shouldDirty: true });
onRequiresConfirmation(val);
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
@@ -95,16 +93,19 @@ export default function RequiresConfirmationController({
}
onValueChange={(val) => {
if (val === "always") {
formMethods.setValue("requiresConfirmation", true);
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
onRequiresConfirmation(true);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, {
shouldDirty: true,
});
setRequiresConfirmationSetup(undefined);
} else if (val === "notice") {
formMethods.setValue("requiresConfirmation", true);
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
onRequiresConfirmation(true);
formMethods.setValue(
"metadata.requiresConfirmationThreshold",
requiresConfirmationSetup || defaultRequiresConfirmationSetup
requiresConfirmationSetup || defaultRequiresConfirmationSetup,
{ shouldDirty: true }
);
}
}}>
@@ -145,7 +146,8 @@ export default function RequiresConfirmationController({
});
formMethods.setValue(
"metadata.requiresConfirmationThreshold.time",
val
val,
{ shouldDirty: true }
);
}}
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
@@ -170,7 +172,8 @@ export default function RequiresConfirmationController({
});
formMethods.setValue(
"metadata.requiresConfirmationThreshold.unit",
opt?.value as UnitTypeLongPlural
opt?.value as UnitTypeLongPlural,
{ shouldDirty: true }
);
}}
defaultValue={defaultValue}
@@ -8,7 +8,6 @@ import { md } from "@calcom/lib/markdownIt";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
import type { Ensure } from "@calcom/types/utils";
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { UserAvatar } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
@@ -38,7 +37,7 @@ const UserProfile = () => {
if (context.avatar) {
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.viewer.me.refetch();
} else {
} else
try {
if (eventTypes?.length === 0) {
await Promise.all(
@@ -51,9 +50,11 @@ const UserProfile = () => {
console.error(error);
}
await utils.viewer.me.refetch();
router.push("/");
}
await utils.viewer.me.refetch();
const redirectUrl = localStorage.getItem("onBoardingRedirect");
localStorage.removeItem("onBoardingRedirect");
redirectUrl ? router.push(redirectUrl) : router.push("/");
},
onError: () => {
showToast(t("problem_saving_user_profile"), "error");
@@ -97,18 +98,10 @@ const UserProfile = () => {
},
];
const organization =
user.organization && user.organization.id
? {
...(user.organization as Ensure<typeof user.organization, "id">),
slug: user.organization.slug || null,
requestedSlug: user.organization.metadata?.requestedSlug || null,
}
: null;
return (
<form onSubmit={onSubmit}>
<div className="flex flex-row items-center justify-start rtl:justify-end">
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} organization={organization} />}
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} />}
<input
ref={avatarRef}
type="hidden"
@@ -0,0 +1,127 @@
import type { FormValues } from "@pages/settings/my-account/profile";
import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Badge,
TextField,
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Button,
InputError,
} from "@calcom/ui";
import { MoreHorizontal, Flag, Trash, Send } from "@calcom/ui/components/icon";
type CustomEmailTextFieldProps = {
formMethods: UseFormReturn<FormValues>;
formMethodFieldName: keyof FormValues;
errorMessage: string;
emailVerified: boolean;
emailPrimary: boolean;
dataTestId: string;
handleChangePrimary: () => void;
handleVerifyEmail: () => void;
handleItemDelete: () => void;
};
const CustomEmailTextField = ({
formMethods,
formMethodFieldName,
errorMessage,
emailVerified,
emailPrimary,
dataTestId,
handleChangePrimary,
handleVerifyEmail,
handleItemDelete,
}: CustomEmailTextFieldProps) => {
const { t } = useLocale();
const [inputFocus, setInputFocus] = useState(false);
return (
<>
<div
className={`border-default mt-2 flex items-center rounded-md border ${
inputFocus ? "ring-brand-default border-neutral-300 ring-2" : ""
}`}>
<TextField
{...formMethods.register(formMethodFieldName)}
label=""
containerClassName="flex flex-1 items-center"
className="mb-0 border-none outline-none focus:ring-0"
data-testid={dataTestId}
onFocus={() => setInputFocus(true)}
onBlur={() => setInputFocus(false)}
/>
<div className="flex items-center pr-2">
{emailPrimary && (
<Badge variant="blue" size="sm" data-testid={`${dataTestId}-primary-badge`}>
{t("primary")}
</Badge>
)}
{!emailVerified && (
<Badge variant="orange" size="sm" className="ml-2" data-testid={`${dataTestId}-unverified-badge`}>
{t("unverified")}
</Badge>
)}
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
StartIcon={MoreHorizontal}
variant="icon"
size="sm"
color="secondary"
className="ml-2 rounded-md"
data-testid="secondary-email-action-group-button"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<DropdownItem
StartIcon={Flag}
color="secondary"
className="disabled:opacity-40"
onClick={handleChangePrimary}
disabled={!emailVerified || emailPrimary}
data-testid="secondary-email-make-primary-button">
{t("make_primary")}
</DropdownItem>
</DropdownMenuItem>
{!emailVerified && (
<DropdownMenuItem>
<DropdownItem
StartIcon={Send}
color="secondary"
className="disabled:opacity-40"
onClick={handleVerifyEmail}
disabled={emailVerified}
data-testid="resend-verify-email-button">
{t("resend_email")}
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuItem>
<DropdownItem
StartIcon={Trash}
color="destructive"
className="disabled:opacity-40"
onClick={handleItemDelete}
disabled={emailPrimary}
data-testid="secondary-email-delete-button">
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
</div>
{errorMessage && <InputError message={errorMessage} />}
</>
);
};
export default CustomEmailTextField;
@@ -0,0 +1,31 @@
import { Trans } from "react-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui";
interface SecondaryEmailConfirmModalProps {
email: string;
onCancel: () => void;
}
const SecondaryEmailConfirmModal = ({ email, onCancel }: SecondaryEmailConfirmModalProps) => {
const { t } = useLocale();
return (
<Dialog open={true}>
<DialogContent
title={t("confirm_email")}
description={<Trans i18nKey="confirm_email_description" values={{ email }} />}
type="creation"
data-testid="secondary-email-confirm-dialog">
<DialogFooter>
<DialogClose color="primary" onClick={onCancel} data-testid="secondary-email-confirm-done-button">
{t("done")}
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default SecondaryEmailConfirmModal;
@@ -0,0 +1,77 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Dialog,
DialogContent,
DialogFooter,
DialogClose,
Button,
TextField,
Form,
InputError,
} from "@calcom/ui";
interface SecondaryEmailModalProps {
isLoading: boolean;
errorMessage?: string;
handleAddEmail: (value: { email: string }) => void;
onCancel: () => void;
clearErrorMessage: () => void;
}
const SecondaryEmailModal = ({
isLoading,
errorMessage,
handleAddEmail,
onCancel,
clearErrorMessage,
}: SecondaryEmailModalProps) => {
const { t } = useLocale();
type FormValues = {
email: string;
};
const formMethods = useForm<FormValues>({
resolver: zodResolver(
z.object({
email: z.string().email(),
})
),
});
useEffect(() => {
// We will reset the errorMessage once the user starts modifying the email
const subscription = formMethods.watch(() => clearErrorMessage());
return () => subscription.unsubscribe();
}, [formMethods.watch]);
return (
<Dialog open={true}>
<DialogContent
title={t("add_email")}
description={t("add_email_description")}
type="creation"
data-testid="secondary-email-add-dialog">
<Form form={formMethods} handleSubmit={handleAddEmail}>
<TextField
label={t("email_address")}
data-testid="secondary-email-input"
{...formMethods.register("email")}
/>
{errorMessage && <InputError message={errorMessage} />}
<DialogFooter showDivider className="mt-10">
<DialogClose onClick={onCancel}>{t("cancel")}</DialogClose>
<Button type="submit" data-testid="add-secondary-email-button" disabled={isLoading}>
{t("add_email")}
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};
export default SecondaryEmailModal;
+1 -1
View File
@@ -3,7 +3,7 @@ import classNames from "classnames";
import { signIn } from "next-auth/react";
import React from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import * as z from "zod";
import { z } from "zod";
import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
import { WEBSITE_URL } from "@calcom/lib/constants";
@@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { noop } from "lodash";
import { useCallback, useState } from "react";
import { Controller, FormProvider, useForm, useFormState } from "react-hook-form";
import * as z from "zod";
import { z } from "zod";
import { classNames } from "@calcom/lib";
import { CONSOLE_URL } from "@calcom/lib/constants";
+7 -2
View File
@@ -5,11 +5,16 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { md } from "@calcom/lib/markdownIt";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
import type { UserProfile } from "@calcom/types/UserProfile";
import { UserAvatar } from "@calcom/ui";
type TeamType = Omit<NonNullable<TeamWithMembers>, "inviteToken">;
type MembersType = TeamType["members"];
type MemberType = Pick<MembersType[number], "id" | "name" | "bio" | "username" | "organizationId"> & {
type MemberType = Pick<
MembersType[number],
"id" | "name" | "bio" | "username" | "organizationId" | "avatarUrl"
> & {
profile: Omit<UserProfile, "upId">;
safeBio: string | null;
bookerUrl: string;
};
@@ -27,7 +32,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
key={member.id}
href={{ pathname: `${member.bookerUrl}/${member.username}`, query: queryParamsToForward }}>
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
<UserAvatar size="md" user={member} />
<UserAvatar noOrganizationIndicator size="md" user={member} />
<section className="mt-2 line-clamp-4 w-full space-y-1">
<p className="text-default font-medium">{member.name}</p>
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
@@ -160,7 +160,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
<div className="bg-subtle flex w-full flex-wrap gap-6 rounded-sm px-2 py-3 text-sm">
<div>
<p className="text-subtle">{t("current_username")}</p>
<p className="text-emphasis mt-1" data-testid="current-username">
<p className="text-emphasis mt-1 break-all" data-testid="current-username">
{currentUsername}
</p>
</div>
@@ -168,7 +168,10 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
<p className="text-subtle" data-testid="new-username">
{t("new_username")}
</p>
<p className="text-emphasis mt-1">{inputUsernameValue}</p>
<p className="text-emphasis min-h-5 mt-1 max-h-12 overflow-y-auto break-all">
{inputUsernameValue}
</p>
</div>
</div>
</div>
@@ -4,7 +4,7 @@ import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import { WEBSITE_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
@@ -45,7 +45,7 @@ export const UsernameAvailabilityField = ({
const usernamePrefix = orgBranding
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
return (
<Controller
@@ -34,6 +34,7 @@ export default function LocationSelect(props: Props<LocationOption, false, Group
<Select<LocationOption>
name="location"
id="location-select"
data-testid="location-select"
components={{
Option: (props) => {
return (

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