Compare commits

..
48 Commits
Author SHA1 Message Date
Omar López 8e8c96c267 v2.4.6
release.yml / release (push) Canceled after 0s
2023-01-09 20:26:21 -07:00
github-actions[bot]andCrowdin Bot fab765c5ee New Crowdin translations by Github Action (#6349)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-09 20:25:22 -07:00
zomars 89ed07a097 Update crowdin.yml 2023-01-09 20:17:03 -07:00
zomars d7d73d2caf Update crowdin.yml 2023-01-09 20:15:53 -07:00
zomars aa92730e28 Fix labeler 2023-01-09 19:52:02 -07:00
430430e0cd Feedback of reusable wysiwyg editor (#6357)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

* center add variable dropwdown icon

* remove rounded borders in dropwdown on hover and active

* center icons

* fix tool bar items dropdown

* add constant for dynamic text variables

* remove not needed variables (added from merge)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-01-09 19:22:17 -07:00
62713940b0 Chor/v2 more tidyup - Card - link icon button - tooltip (#6312)
* Card

* Step card

* Already in components

* Link Icon Button

* Tooltip

* Fix types

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 19:22:04 -07:00
75aef09338 Google Meet - installable app (#5904)
* Abstract app category navigation

* Send key schema to frontend

Co-authored-by: Omar López <zomars@users.noreply.github.com>

* Render keys for apps on admin

* Add enabled col for apps

* Save app keys to DB

* Add checks for admin role

* Abstract setup components

* Add AdminAppsList to setup wizard

* Migrate to v10 tRPC

* Default hide keys

* Display enabled apps

* Merge branch 'main' into admin-apps-ui

* Toggle calendars

* WIP

* Add params and include AppCategoryNavigation

* Refactor getEnabledApps

* Add warning for disabling apps

* Fallback to cal video when a video app is disabled

* WIP send disabled email

* Send email to all users of  event types with payment app

* Disable Stripe when app is disabled

* Disable apps in event types

* Send email to users on disabled apps

* Send email based on what app was disabled

* WIP type fix

* Disable navigation to apps list if already setup

* UI import fixes

* Waits for session data before redirecting

* Updates admin seeded password

To comply with admin password requirements

* Update yarn.lock

* Flex fixes

* Adds admin middleware

* Clean up

* WIP

* WIP

* NTS

* Add dirName to app metadata

* Upsert app if not in db

* Upsert app if not in db

* Add dirName to app metadata

* Add keys to app packages w/ keys

* Merge with main

* Toggle show keys & on enable

* Fix empty keys

* Fix lark calendar metadata

* Fix some type errors

* Fix Lark metadata & check for category when upserting

* More type fixes

* Fix types & add keys to google cal

* WIP

* WIP

* WIP

* More type fixes

* Fix type errors

* Fix type errors

* More type fixes

* More type fixes

* More type fixes

* Feedback

* Fixes default value

* Feedback

* Migrate credential invalid col default value "false"

* Upsert app on saving keys

* Clean up

* Validate app keys on frontend

* Add nonempty to app keys schemas

* Add web3

* Listlocale filter on categories / category

* Grab app metadata via category or categories

* Show empty screen if no apps are enabled

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Replace .nonempty() w/ .min(1)

* Fix type error

* Address feedback

* Draft Google Meet install button

* Add install button and warning dialog

* WIP

* WIP

* Display warning when Meet is selected

* Display Google Meet warning on email to organizer

* Fix email

* Fix type errors

* Fix type error

* Add connected account component

* Add warning message

* Address comments

* Address feedback

* Clean up & add MeetLocationType

* Use useApp hook

* Translate to new API approach

* Remove console.log

* Refactor

* Fix missing backup Cal video link

* WIP

* Address feedback

* Update submodules

* Feedback

* Submodule sync

Co-authored-by: Omar López <zomars@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-10 02:01:57 +00:00
4e65c30e18 fix: Parameterize Stripe Payment Fee configs (#6024)
* Parameterize Stripe Payment Fee configs

* Restore constant values for Payment Fee params

* Restore constant values for all Payment Fee params

* Remove STRIPE_ prefix on payment fee vars

* Cast Payment Fee params as Number

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-09 18:50:49 -07:00
8b873e2828 Adding a bunch of performance markers (#6303)
* Adding a bunch of performance markers

* Update packages/lib/server/perfObserver.ts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 18:42:46 -07:00
zomars e70d179628 Update website 2023-01-09 18:31:38 -07:00
Nafees Nazik ca9a836300 fix: remove padding from username field (#6356) 2023-01-09 19:35:00 +01:00
Alex van Andel abdf72dc0f Fixup yarn.lock 2023-01-09 14:36:49 +00:00
dependabot[bot]andBailey Pumfleet 227cd165f6 Bump json5 from 1.0.1 to 1.0.2 (#6351)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-01-09 14:19:51 +00:00
a0135cdc59 Make WYSIWYG editor reusable (#6293)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 14:15:11 +00:00
112d504148 Chor/v2 UI tidy - Dialog & Toasts (#6307)
* Dialog Tidyup

* Move toasts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-09 13:57:12 +00:00
Peer Richelsen d275930732 link to event-types in settings to fix cal.com usage (#6348) 2023-01-09 13:15:10 +01:00
Alex van Andel 97e33bdd7e Set bg-white to secondary variant (#6339) 2023-01-08 17:50:37 +01:00
Hariom Balhara 3f13791faf Add app-store installed page navigationt ests (#6333) 2023-01-08 09:29:20 +01:00
Omar LópezandAlex van Andel 359cfc4be0 Replaces mdx-remote with markdown-it (#6326)
* Replaces mdx-remote with react-markdown

* Replaces react-markdown with already available markdown-it

* Renames readme to description

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-08 00:05:35 +00:00
Alex van AndelandPeer Richelsen 1ad6bba0ab set useQueryParam to false for app categories (#6330)
* set useQueryParam to false for app categories

* New param 'useQueryParam' for AppCategoryNavigation/AdminAppsList

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 23:21:21 +00:00
Alex van Andel 6dc4211d81 Fixes hydration error, uses useTypedQuery for tabName (#6327) 2023-01-07 22:42:30 +00:00
Alex van Andel e5f0452728 Don't initialize KBar when unauthed (#6328)
* Don't initialize KBar when unauthed

* Don't render trigger when query is undefined
2023-01-07 22:31:15 +00:00
zomars d95bba63f4 Submodule sync 2023-01-07 11:19:16 -07:00
zomars 6f48869021 Add missing slash to custom import 2023-01-07 10:17:38 -07:00
Jeff Loiselle 845bfae887 adds event types to kbar (#6210) 2023-01-07 10:06:53 -07:00
zomars 3fe02fba26 Update basic.e2e.ts 2023-01-07 09:55:30 -07:00
zomars 4ca5ca9fe2 Update hash-my-url.e2e.ts 2023-01-07 09:50:02 -07:00
Hariom BalharaandPeer Richelsen 364e187080 Fix/Next.js-13/Circular Dependency in Routing Forms (#6321)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 15:50:28 +00:00
Peer Richelsen b1193eef1e Update README.md 2023-01-07 16:27:56 +01:00
Peer Richelsen e110f12cf1 Update README.md 2023-01-07 16:26:47 +01:00
Peer Richelsen 022b513935 Update package.json 2023-01-07 11:55:50 +01:00
Jeff LoiselleandPeer Richelsen 010f39a7a4 adds render.com url constant (#6287)
* adds render.com url constant

* fixes webapp_url constant

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 10:28:02 +00:00
Peer Richelsen 6b6eed66eb fixed mobile sidebar in settings (#6317) 2023-01-07 10:10:35 +00:00
Udit Takkar c214e01f03 fix: use overflow-auto (#6295)
* fix: use overflow-auto

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

* fix: scroll issues with too many filters

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

* chore: merge conflict

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-07 11:00:11 +01:00
Carina WollendorferandCarinaWolli a28ac3634d Add start and end time of rescheduled booking to webhook payload (#6297)
* add start and end time of rescheduled booking

* fix type error

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-06 13:37:06 -07:00
zomars a0709fac88 Circular dependecy hotfix 2023-01-06 13:15:34 -07:00
Alex van Andel e5b6a3332e Feature/new design radio area group (#6289)
* Refactor of the RadioAreaGroup component

* Move RadioAreaGroup outside of v2/core

* Use useTypedQuery

* Removed unused (broken) imports

* Fix type for Select
2023-01-06 20:05:23 +00:00
Jeff Loiselle 979686920a fixes codespace login msg spelling (#6288) 2023-01-06 12:37:03 -07:00
zomars 793850f3aa Update console 2023-01-06 11:10:17 -07:00
Nafees NazikandPeer Richelsen fa9d72fc0a fix: flash of untranslated title in settings/my-account/profile (#6163)
* fix: flash of untranslated content

* feat: extract loading into a variable

* fix: use optional chaining because typescript complaining

* feat: render Meta component even if not loading for seo purpose

* fix: received an array with more than 1 element as
children warning

* fix: changes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 10:35:32 -07:00
zomars 0d485ffed6 Updates website 2023-01-06 10:18:18 -07:00
Peer Richelsen 9a2e55d528 revert version change 2023-01-06 16:59:26 +01:00
github-actions[bot]andCrowdin Bot 81ecbb86a5 New Crowdin translations by Github Action (#6308)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-06 16:58:09 +01:00
Peer Richelsen 2cd816932b Update package.json 2023-01-06 16:55:01 +01:00
Peer Richelsenandzomars d83411162d Upgrade to next 13.1 (#6265)
Co-authored-by: zomars <zomars@me.com>
2023-01-06 12:13:56 +00:00
Nafees NazikandPeer Richelsen c9c54db3a1 fix: remove duplicate font preload (#6291)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 12:35:48 +01:00
Udit Takkarandkodiakhq[bot] d100600789 feat: update team schema, update team query (#6294)
team appearance section

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-06 10:55:57 +00:00
247 changed files with 3175 additions and 2662 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev envionment.
+2
View File
@@ -102,6 +102,8 @@ STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET= STRIPE_WEBHOOK_SECRET=
STRIPE_PRIVATE_KEY= STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID= STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
PAYMENT_FEE_PERCENTAGE=
# Use for internal Public API Keys and optional # Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_ API_KEY_PREFIX=cal_
+2 -1
View File
@@ -22,6 +22,7 @@ jobs:
upload_translations: true upload_translations: true
download_translations: true download_translations: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # So required checks run on Crowdin PRs
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+2
View File
@@ -14,3 +14,5 @@ jobs:
- uses: actions/labeler@v4 - uses: actions/labeler@v4
with: with:
repo-token: "${{ secrets.GITHUB_TOKEN }}" repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
sync-labels: ""
+11
View File
@@ -315,6 +315,17 @@ Please see our [contributing guide](/CONTRIBUTING.md).
We have a list of [help wanted](https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process. We have a list of [help wanted](https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
<!-- TRANSLATIONS -->
### Translations
Don't code but still want to contribute? Join our [slack](https://cal.com/slack) and join the [#i18n channel](https://calendso.slack.com/archives/C02BY67GMMW) and let us know what language you want to translate.
![ar translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ar&style=flat&logo=crowdin&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![bg translation](https://img.shields.io/badge/dynamic/json?color=blue&label=bg&style=flat&logo=crowdin&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![cs translation](https://img.shields.io/badge/dynamic/json?color=blue&label=cs&style=flat&logo=crowdin&query=%24.progress.2.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![de translation](https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=flat&logo=crowdin&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![el translation](https://img.shields.io/badge/dynamic/json?color=blue&label=el&style=flat&logo=crowdin&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![en translation](https://img.shields.io/badge/dynamic/json?color=blue&label=en&style=flat&logo=crowdin&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![es translation](https://img.shields.io/badge/dynamic/json?color=blue&label=es&style=flat&logo=crowdin&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![es-419 translation](https://img.shields.io/badge/dynamic/json?color=blue&label=es-419&style=flat&logo=crowdin&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![fr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=flat&logo=crowdin&query=%24.progress.8.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![he translation](https://img.shields.io/badge/dynamic/json?color=blue&label=he&style=flat&logo=crowdin&query=%24.progress.9.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![hu translation](https://img.shields.io/badge/dynamic/json?color=blue&label=hu&style=flat&logo=crowdin&query=%24.progress.10.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![it translation](https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=flat&logo=crowdin&query=%24.progress.11.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![ja translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ja&style=flat&logo=crowdin&query=%24.progress.12.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![ko translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ko&style=flat&logo=crowdin&query=%24.progress.13.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![nl translation](https://img.shields.io/badge/dynamic/json?color=blue&label=nl&style=flat&logo=crowdin&query=%24.progress.14.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![no translation](https://img.shields.io/badge/dynamic/json?color=blue&label=no&style=flat&logo=crowdin&query=%24.progress.15.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![pl translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pl&style=flat&logo=crowdin&query=%24.progress.16.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![pt translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pt&style=flat&logo=crowdin&query=%24.progress.17.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![pt-BR translation](https://img.shields.io/badge/dynamic/json?color=blue&label=pt-BR&style=flat&logo=crowdin&query=%24.progress.18.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![ro translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ro&style=flat&logo=crowdin&query=%24.progress.19.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![ru translation](https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=flat&logo=crowdin&query=%24.progress.20.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![sr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=sr&style=flat&logo=crowdin&query=%24.progress.21.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-200011276-1.json) ![sv translation](https://img.shields.io/badge/dynamic/json?color=blue&label=sv&style=flat&logo=crowdin&query=%Line truncated
## Integrations ## Integrations
### Obtaining the Google API Credentials ### Obtaining the Google API Credentials
Submodule apps/api updated: 9c23a8e5ab...7aebdb8c96.
+1 -1
View File
@@ -17,7 +17,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"iframe-resizer-react": "^1.1.0", "iframe-resizer-react": "^1.1.0",
"next": "^12.3.1", "next": "^13.1.1",
"nextra": "^1.1.0", "nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2", "nextra-theme-docs": "^1.2.2",
"react": "^18.2.0", "react": "^18.2.0",
+3 -3
View File
@@ -31,7 +31,7 @@ All apps can be found under `packages/app-store`. In this folder is `_example` w
| ├──index.ts | ├──index.ts
| ├──package.json | ├──package.json
| ├──.env.example | ├──.env.example
| ├──README.mdx | ├──DESCRIPTION.md
``` ```
## Getting Started ## Getting Started
@@ -48,9 +48,9 @@ Under the `/components` folder, this is where the install button for your app sh
The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file. The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file.
On the app store page you can customize your apps description by adding a markdown file called `README.mdx`. If you do not add one then the description from you `package.json` will be used instead. On the app store page you can customize your apps description by adding a markdown file called `DESCRIPTION.md`. If you do not add one then the description from you `package.json` will be used instead.
The `/static` folder is where you can store your app icon and any images that your `README.mdx` may use. The `/static` folder is where you can store your app icon and any images that your `DESCRIPTION.md` may use.
## Adding Your App to the App Store ## Adding Your App to the App Store
To render your app on the app store page, go to `packages/app-store/index.ts`. Import your app into the file and add it to the `appStore` object. To render your app on the app store page, go to `packages/app-store/index.ts`. Import your app into the file and add it to the `appStore` object.
+1 -1
View File
@@ -20,7 +20,7 @@
"@radix-ui/react-slider": "^1.0.0", "@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0", "@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0",
"next": "^12.3.1", "next": "^13.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
+1 -1
View File
@@ -14,7 +14,7 @@
"dependencies": { "dependencies": {
"highlight.js": "^11.6.0", "highlight.js": "^11.6.0",
"isarray": "2.0.5", "isarray": "2.0.5",
"next": "^12.3.1", "next": "^13.1.1",
"openapi-snippet": "^0.13.0", "openapi-snippet": "^0.13.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
-9
View File
@@ -1,9 +0,0 @@
import { Suspense, SuspenseProps } from "react";
/**
* Wrapper around `<Suspense />` which will render the `fallback` when on server
* Can be simply replaced by `<Suspense />` once React 18 is ready.
*/
export const ClientSuspense = (props: SuspenseProps) => {
return <>{typeof window !== "undefined" ? <Suspense {...props} /> : props.fallback}</>;
};
+1 -1
View File
@@ -62,7 +62,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
const className = tab.className || ""; const className = tab.className || "";
return ( return (
<Component key={tab.name}> <Component key={tab.name}>
<Link key={tab.name} href={href} {...linkProps}> <Link key={tab.name} href={href} {...linkProps} legacyBehavior>
<a <a
onClick={onClick} onClick={onClick}
className={classNames( className={classNames(
+4 -2
View File
@@ -98,8 +98,10 @@ const Component = ({
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1> <h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
</div> </div>
<h2 className="text-sm font-medium text-gray-600"> <h2 className="text-sm font-medium text-gray-600">
<Link href={`categories/${categories[0]}`}> <Link
<a className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">{categories[0]}</a> href={`categories/${categories[0]}`}
className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">
{categories[0]}
</Link>{" "} </Link>{" "}
• {t("published_by", { author })} • {t("published_by", { author })}
</h2> </h2>
@@ -330,7 +330,10 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
})} })}
description={t(`no_category_apps_description_calendar`)} description={t(`no_category_apps_description_calendar`)}
buttonRaw={ buttonRaw={
<Button color="secondary" href="/apps/categories/calendar"> <Button
color="secondary"
data-testid="connect-calendar-apps"
href="/apps/categories/calendar">
{t(`connect_calendar_apps`)} {t(`connect_calendar_apps`)}
</Button> </Button>
} }
+23 -23
View File
@@ -114,29 +114,29 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>} {!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
</div> </div>
) : ( ) : (
<Link href={bookingUrl} prefetch={false}> <Link
<a href={bookingUrl}
className={classNames( prefetch={false}
"text-primary-500 hover:border-gray-900 hover:bg-gray-50", className={classNames(
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200", "text-primary-500 hover:border-gray-900 hover:bg-gray-50",
brand === "#fff" || brand === "#ffffff" ? "" : "" "dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200",
)} brand === "#fff" || brand === "#ffffff" ? "" : ""
data-testid="time"> )}
{dayjs(slot.time).tz(timeZone()).format(timeFormat)} data-testid="time">
{!!seatsPerTimeSlot && ( {dayjs(slot.time).tz(timeZone()).format(timeFormat)}
<p {!!seatsPerTimeSlot && (
className={`${ <p
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8 className={`${
? "text-rose-600" slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33 ? "text-rose-600"
? "text-yellow-500" : slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
: "text-emerald-400" ? "text-yellow-500"
} text-sm`}> : "text-emerald-400"
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "} } text-sm`}>
{seatsPerTimeSlot} {t("seats_available")} {slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
</p> {seatsPerTimeSlot} {t("seats_available")}
)} </p>
</a> )}
</Link> </Link>
)} )}
</div> </div>
@@ -93,10 +93,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex justify-between"> <div className="flex justify-between">
<Label>{t("add_to_calendar")}</Label> <Label>{t("add_to_calendar")}</Label>
<Link href="/apps/categories/calendar"> <Link
<a target="_blank" className="text-sm text-gray-600 hover:text-gray-900"> href="/apps/categories/calendar"
{t("add_another_calendar")} target="_blank"
</a> className="text-sm text-gray-600 hover:text-gray-900">
{t("add_another_calendar")}
</Link> </Link>
</div> </div>
<div className="-mt-1 w-full"> <div className="-mt-1 w-full">
@@ -266,12 +267,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/> />
{/*TODO: Extract it out into a component when used more than once*/} {/*TODO: Extract it out into a component when used more than once*/}
{!IS_SELF_HOSTED && ( {!IS_SELF_HOSTED && (
<Link href="https://cal.com/pricing"> <Link href="https://cal.com/pricing" target="_blank">
<a target="_blank"> <Badge variant="orange" className="ml-2">
<Badge variant="orange" className="ml-2"> Platform Only
Platform Only </Badge>
</Badge>
</a>
</Link> </Link>
)} )}
</div> </div>
@@ -1,16 +1,18 @@
import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js"; import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useState } from "react"; import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form"; import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select"; import { MultiValue } from "react-select";
import { z } from "zod"; import { z } from "zod";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations"; import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants"; import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui"; import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify"; import { slugify } from "@lib/slugify";
@@ -195,6 +197,23 @@ export const EventSetupTab = (
</li> </li>
); );
})} })}
{validLocations.some((location) => location.type === MeetLocationType) && (
<div className="flex text-sm text-gray-600">
<Icon.FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal">
<p>
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/${eventType.id}?tabName=advanced`}
className="underline">
here.
</Link>{" "}
We will fall back to Cal video if you do not change it.
</p>
</Trans>
</div>
)}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && ( {validLocations.length > 0 && validLocations.length !== locationOptions.length && (
<li> <li>
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}> <Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
@@ -326,6 +345,7 @@ export const EventSetupTab = (
<Skeleton as={Label} loadingClassName="w-16"> <Skeleton as={Label} loadingClassName="w-16">
{t("location")} {t("location")}
</Skeleton> </Skeleton>
<Controller <Controller
name="locations" name="locations"
control={formMethods.control} control={formMethods.control}
@@ -1,7 +1,7 @@
import { TFunction } from "next-i18next"; import { TFunction } from "next-i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react"; import { useMemo, useState, Suspense } from "react";
import { UseFormReturn } from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import { TbWebhook } from "react-icons/tb"; import { TbWebhook } from "react-icons/tb";
@@ -32,7 +32,6 @@ import {
VerticalTabs, VerticalTabs,
} from "@calcom/ui"; } from "@calcom/ui";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed"; import { EmbedButton, EmbedDialog } from "@components/Embed";
type Props = { type Props = {
@@ -301,7 +300,7 @@ function EventTypeSingleLayout({
</Button> </Button>
</div> </div>
}> }>
<ClientSuspense fallback={<Icon.FiLoader />}> <Suspense fallback={<Icon.FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8"> <div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block"> <div className="hidden xl:block">
<VerticalTabs <VerticalTabs
@@ -324,7 +323,7 @@ function EventTypeSingleLayout({
</div> </div>
</div> </div>
</div> </div>
</ClientSuspense> </Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent <ConfirmationDialogContent
isLoading={deleteMutation.isLoading} isLoading={deleteMutation.isLoading}
+1 -7
View File
@@ -35,13 +35,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
return ( return (
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2"> <li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
{item.href ? ( {item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
<Link href={item.href}>
<a>{avatar}</a>
</Link>
) : (
avatar
)}
</li> </li>
); );
} }
-22
View File
@@ -1,22 +0,0 @@
import Link from "next/link";
interface ILinkTextProps {
href: string;
children: React.ReactNode;
classNameChildren?: string;
target?: string;
}
/**
* This component had to be made in order to make i18n work with next/link
* @see https://github.com/i18next/react-i18next/issues/1090#issuecomment-615426145
**/
export const LinkText = (props: ILinkTextProps) => {
const { target, href, children, classNameChildren, ...moreProps } = props;
return (
<Link href={href || ""} {...moreProps}>
<a target={target || ""} className={classNameChildren}>
{children}
</a>
</Link>
);
};
+15 -20
View File
@@ -9,26 +9,21 @@ const PoweredByCal = () => {
const isEmbed = useIsEmbed(); const isEmbed = useIsEmbed();
return ( return (
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}> <div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
<Link href={POWERED_BY_URL}> <Link
<a target="_blank" className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white"> href={POWERED_BY_URL}
{t("powered_by")}{" "} target="_blank"
{ className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
// eslint-disable-next-line @next/next/no-img-element {t("powered_by")}{" "}
<img <img
className="relative -mt-px inline h-[10px] w-auto dark:hidden" className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg" src="/cal-logo-word.svg"
alt="Cal.com Logo" alt="Cal.com Logo"
/> />
} <img
{ className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
// eslint-disable-next-line @next/next/no-img-element src="/cal-logo-word-dark.svg"
<img alt="Cal.com Logo"
className="relativ -mt-px hidden h-[10px] w-auto dark:inline" />
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
}
</a>
</Link> </Link>
</div> </div>
); );
@@ -187,7 +187,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="flex justify-items-center"> <div className="flex justify-items-center">
<Label htmlFor="username">{t("username")}</Label> <Label htmlFor="username">{t("username")}</Label>
</div> </div>
<div className="mt-2 flex rounded-md"> <div className="flex rounded-md">
<span <span
className={classNames( className={classNames(
isInputUsernamePremium ? "border-1 border-orange-400 " : "", isInputUsernamePremium ? "border-1 border-orange-400 " : "",
@@ -118,7 +118,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
return ( return (
<div> <div>
<div className="mt-2 flex rounded-md"> <div className="flex rounded-md">
<div className="relative w-full"> <div className="relative w-full">
<TextField <TextField
ref={usernameRef} ref={usernameRef}
@@ -1,79 +0,0 @@
import React, { ReactNode, useState } from "react";
import classNames from "@lib/classNames";
type RadioAreaProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> & {
onChange?: (value: string) => void;
defaultChecked?: boolean;
};
const RadioArea = (props: RadioAreaProps) => {
return (
<label
className={classNames(
"border-1 block border p-4 focus:outline-none focus:ring focus:ring-neutral-500",
props.checked && "border-brand",
props.className
)}>
<input
onChange={(e) => {
if (typeof props.onChange === "function") {
props.onChange(e.target.value);
}
}}
checked={props.checked}
className="float-right ml-3 text-neutral-900 focus:ring-neutral-500"
name={props.name}
value={props.value}
type="radio"
/>
{props.children}
</label>
);
};
type ChildrenProps = {
props: RadioAreaProps;
children?: ReactNode;
};
interface RadioAreaGroupProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
children: ChildrenProps | ChildrenProps[];
name?: string;
onChange?: (value: string) => void;
}
const RadioAreaGroup = ({ children, name, onChange, ...passThroughProps }: RadioAreaGroupProps) => {
const [checkedIdx, setCheckedIdx] = useState<number | null>(null);
const changeHandler = (value: string, idx: number) => {
if (onChange) {
onChange(value);
}
setCheckedIdx(idx);
};
return (
<div {...passThroughProps}>
{(Array.isArray(children) ? children : [children]).map((child, idx: number) => {
if (checkedIdx === null && child.props.defaultChecked) {
setCheckedIdx(idx);
}
return (
<Item
{...child.props}
key={idx}
name={name}
checked={idx === checkedIdx}
onChange={(value: string) => changeHandler(value, idx)}>
{child.props.children}
</Item>
);
})}
</div>
);
};
const Item = RadioArea;
const Group = RadioAreaGroup;
export { RadioArea, RadioAreaGroup, Item, Group };
@@ -1,61 +0,0 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import React from "react";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { RadioArea, RadioAreaGroup } from "@components/ui/form/radio-area/RadioAreaGroup";
interface OptionProps
extends Pick<React.OptionHTMLAttributes<HTMLOptionElement>, "value" | "label" | "className"> {
description?: string;
}
interface RadioAreaSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onChange"> {
options: OptionProps[]; // allow options to be passed programmatically, like options={}
onChange?: (value: string) => void;
}
export const Select = function RadioAreaSelect(props: RadioAreaSelectProps) {
const { t } = useLocale();
const {
options,
disabled = !options.length, // if not explicitly disabled and the options length is empty, disable anyway
placeholder = t("select"),
} = props;
const getLabel = (value: string | ReadonlyArray<string> | number | undefined) =>
options.find((option: OptionProps) => option.value === value)?.label;
return (
<Collapsible className={classNames("w-full", props.className)}>
<CollapsibleTrigger
type="button"
disabled={disabled}
className={classNames(
"border-1 focus:ring-primary-500 mb-1 block w-full cursor-pointer rounded-sm border border-gray-300 bg-white p-2 text-left text-sm",
disabled && "cursor-default bg-gray-200 focus:ring-0 "
)}>
{getLabel(props.value) ?? placeholder}
<Icon.FiChevronDown className="float-right h-5 w-5 text-neutral-500" />
</CollapsibleTrigger>
<CollapsibleContent>
<RadioAreaGroup className="space-y-2 text-sm" name={props.name} onChange={props.onChange}>
{options.map((option) => (
<RadioArea
{...option}
key={Array.isArray(option.value) ? option.value.join(",") : `${option.value}`}
defaultChecked={props.value === option.value}>
<strong className="mb-1 block">{option.label}</strong>
<p>{option.description}</p>
</RadioArea>
))}
</RadioAreaGroup>
</CollapsibleContent>
</Collapsible>
);
};
export default Select;
@@ -1,2 +0,0 @@
export * from "./RadioAreaGroup";
export * from "./Select";
+6
View File
@@ -89,6 +89,12 @@ const nextConfig = {
eslint: { eslint: {
ignoreDuringBuilds: !!process.env.CI, ignoreDuringBuilds: !!process.env.CI,
}, },
// TODO: We need to have all components in `@calcom/ui/components` in order to use this
// modularizeImports: {
// "@calcom/ui": {
// transform: "@calcom/ui/components/{{member}}",
// },
// },
images: { images: {
unoptimized: true, unoptimized: true,
}, },
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@calcom/web", "name": "@calcom/web",
"version": "2.4.4", "version": "2.4.6",
"private": true, "private": true,
"scripts": { "scripts": {
"analyze": "ANALYZE=true next build", "analyze": "ANALYZE=true next build",
@@ -67,6 +67,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"eslint-config-next": "^13.1.1",
"googleapis": "^84.0.0", "googleapis": "^84.0.0",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
@@ -80,15 +81,14 @@
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"micro": "^9.4.1", "micro": "^9.4.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"next": "^12.3.1", "next": "^13.1.1",
"next-auth": "^4.10.3", "next-auth": "^4.10.3",
"next-axiom": "^0.10.0", "next-axiom": "^0.16.0",
"next-collect": "^0.2.1", "next-collect": "^0.2.1",
"next-i18next": "^11.3.0", "next-i18next": "^11.3.0",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.26.0", "next-seo": "^4.26.0",
"next-themes": "^0.2.0", "next-themes": "^0.2.0",
"next-transpile-modules": "^9.0.0", "next-transpile-modules": "^10.0.0",
"nock": "^13.2.8", "nock": "^13.2.8",
"nodemailer": "^6.7.8", "nodemailer": "^6.7.8",
"otplib": "^12.0.1", "otplib": "^12.0.1",
+46 -48
View File
@@ -98,26 +98,26 @@ export default function Custom404() {
<ul role="list" className="divide-y divide-gray-200 border-gray-200"> <ul role="list" className="divide-y divide-gray-200 border-gray-200">
<li className="px-4 py-2"> <li className="px-4 py-2">
<Link href={`${DEVELOPER_DOCS}/self-hosting/installation`}> <Link
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse"> href={`${DEVELOPER_DOCS}/self-hosting/installation`}
<div className="flex-shrink-0"> className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50"> <div className="flex-shrink-0">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" /> <span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{t("prisma_studio_tip")}
</span> </span>
</div> </h3>
<div className="min-w-0 flex-1"> <p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
<h3 className="text-base font-medium text-gray-900"> </div>
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2"> <div className="flex-shrink-0 self-center">
<span className="absolute inset-0" aria-hidden="true" /> <Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
{t("prisma_studio_tip")} </div>
</span>
</h3>
<p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</Link> </Link>
</li> </li>
<li className="px-4 py-2"> <li className="px-4 py-2">
@@ -167,11 +167,11 @@ export default function Custom404() {
</li> </li>
</ul> </ul>
<div className="mt-8"> <div className="mt-8">
<Link href={`${WEBSITE_URL}/enterprise`}> <Link
<a className="text-base font-medium text-black hover:text-gray-500"> href={`${WEBSITE_URL}/enterprise`}
{t("contact_sales")} className="text-base font-medium text-black hover:text-gray-500">
<span aria-hidden="true"> &rarr;</span> {t("contact_sales")}
</a> <span aria-hidden="true"> &rarr;</span>
</Link> </Link>
</div> </div>
</div> </div>
@@ -243,26 +243,26 @@ export default function Custom404() {
<ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200"> <ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200">
{links.map((link, linkIdx) => ( {links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2"> <li key={linkIdx} className="px-4 py-2">
<Link href={link.href}> <Link
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse"> href={link.href}
<div className="flex-shrink-0"> className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50"> <div className="flex-shrink-0">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" /> <span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span> </span>
</div> </h3>
<div className="min-w-0 flex-1"> <p className="text-base text-gray-500">{link.description}</p>
<h3 className="text-base font-medium text-gray-900"> </div>
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2"> <div className="flex-shrink-0 self-center">
<span className="absolute inset-0" aria-hidden="true" /> <Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
{link.title} </div>
</span>
</h3>
<p className="text-base text-gray-500">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</Link> </Link>
</li> </li>
))} ))}
@@ -313,11 +313,9 @@ export default function Custom404() {
</li> </li>
</ul> </ul>
<div className="mt-8"> <div className="mt-8">
<Link href="/"> <Link href="/" className="text-base font-medium text-black hover:text-gray-500">
<a className="text-base font-medium text-black hover:text-gray-500"> {t("or_go_back_home")}
{t("or_go_back_home")} <span aria-hidden="true"> &rarr;</span>
<span aria-hidden="true"> &rarr;</span>
</a>
</Link> </Link>
</div> </div>
</div> </div>
+34 -36
View File
@@ -1,6 +1,5 @@
import classNames from "classnames"; import classNames from "classnames";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect } from "react"; import { useEffect } from "react";
@@ -60,25 +59,26 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
key={index} key={index}
className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600"> className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600">
<Icon.FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" /> <Icon.FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
<Link href={getUsernameSlugLink({ users: props.users, slug: type.slug })}> <Link
<a className="flex justify-between px-6 py-4" data-testid="event-type-link"> href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
<div className="flex-shrink"> className="flex justify-between px-6 py-4"
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p> data-testid="event-type-link">
<EventTypeDescription className="text-sm" eventType={type} /> <div className="flex-shrink">
</div> <p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
<div className="mt-1 self-center"> <EventTypeDescription className="text-sm" eventType={type} />
<AvatarGroup </div>
border="border-2 border-white dark:border-darkgray-100" <div className="mt-1 self-center">
truncateAfter={4} <AvatarGroup
className="flex flex-shrink-0" border="border-2 border-white dark:border-darkgray-100"
size={10} truncateAfter={4}
items={props.users.map((user) => ({ className="flex flex-shrink-0"
alt: user.name || "", size={10}
image: user.avatar || "", items={props.users.map((user) => ({
}))} alt: user.name || "",
/> image: user.avatar || "",
</div> }))}
</a> />
</div>
</Link> </Link>
</li> </li>
))} ))}
@@ -175,22 +175,20 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
href={{ href={{
pathname: `/${user.username}/${type.slug}`, pathname: `/${user.username}/${type.slug}`,
query, query,
}}> }}
<a onClick={async () => {
onClick={async () => { sdkActionManager?.fire("eventTypeSelected", {
sdkActionManager?.fire("eventTypeSelected", { eventType: type,
eventType: type, });
}); }}
}} className="block w-full p-5"
className="block w-full p-5" data-testid="event-type-link">
data-testid="event-type-link"> <div className="flex flex-wrap items-center">
<div className="flex flex-wrap items-center"> <h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700"> {type.title}
{type.title} </h2>
</h2> </div>
</div> <EventTypeDescription eventType={type} />
<EventTypeDescription eventType={type} />
</a>
</Link> </Link>
</div> </div>
)) ))
-1
View File
@@ -53,7 +53,6 @@ class MyDocument extends Document<Props> {
return ( return (
<Html lang={locale} dir={dir}> <Html lang={locale} dir={dir}>
<Head> <Head>
<link rel="preload" href="/cal.ttf" as="font" type="font/ttf" crossOrigin="anonymous" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+20 -2
View File
@@ -35,6 +35,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
uid: "xxyPr4cg2xx4XoS2KeMEQy", uid: "xxyPr4cg2xx4XoS2KeMEQy",
metadata: {}, metadata: {},
recurringEvent: null, recurringEvent: null,
appsStatus: [
{
appName: "Outlook Calendar",
type: "office365_calendar",
success: 1,
failures: 0,
errors: [],
warnings: [],
},
{
appName: "Google Meet",
type: "conferencing",
success: 0,
failures: 1,
errors: [],
warnings: ["In order to use Google Meet you must set your destination calendar to a Google Calendar"],
},
],
}; };
req.statusCode = 200; req.statusCode = 200;
@@ -42,9 +60,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html"); res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate"); res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write( res.write(
renderEmail("OrganizerRequestEmail", { renderEmail("OrganizerScheduledEmail", {
attendee: evt.attendees[0],
calEvent: evt, calEvent: evt,
attendee: evt.organizer,
}) })
); );
res.end(); res.end();
+1 -1
View File
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath"; import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getSession } from "@calcom/lib/auth";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType"; import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler"; import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error"; import { HttpError } from "@lib/core/http/error";
const defaultIntegrationAddHandler = async ({ const defaultIntegrationAddHandler = async ({
+13 -25
View File
@@ -1,34 +1,18 @@
import fs from "fs"; import fs from "fs";
import matter from "gray-matter"; import matter from "gray-matter";
import MarkdownIt from "markdown-it";
import { GetStaticPaths, GetStaticPropsContext } from "next"; import { GetStaticPaths, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Image from "next/image";
import Link from "next/link";
import path from "path"; import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry"; import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import ExisitingGoogleCal from "@calcom/app-store/googlevideo/components/ExistingGoogleCal";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/apps/App"; import App from "@components/apps/App";
const components = { const md = new MarkdownIt("default", { html: true, breaks: true });
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
<Link href={href}>
<a {...otherProps} />
</Link>
),
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) { function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return ( return (
@@ -50,10 +34,15 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
email={data.email} email={data.email}
licenseRequired={data.licenseRequired} licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly} isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined} images={source.data?.items as string[] | undefined}
// tos="https://zoom.us/terms" // tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy" // privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />} body={
<>
{data.slug === "google-meet" && <ExisitingGoogleCal />}
<div dangerouslySetInnerHTML={{ __html: md.render(source.content) }} />
</>
}
/> />
); );
} }
@@ -82,7 +71,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (!singleApp) return { notFound: true }; if (!singleApp) return { notFound: true };
const appDirname = app.dirName; const appDirname = app.dirName;
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/README.mdx`); const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH); const postFilePath = path.join(README_PATH);
let source = ""; let source = "";
@@ -90,16 +79,15 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
/* If the app doesn't have a README we fallback to the package description */ /* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString(); source = fs.readFileSync(postFilePath).toString();
} catch (error) { } catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`); console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = singleApp.description; source = singleApp.description;
} }
const { content, data } = matter(source); const { content, data } = matter(source);
const mdxSource = await serialize(content, { scope: data });
return { return {
props: { props: {
source: mdxSource, source: { content, data },
data: singleApp, data: singleApp,
}, },
}; };
@@ -20,10 +20,10 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
backPath="/apps" backPath="/apps"
heading={ heading={
<> <>
<Link href="/apps"> <Link
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900"> href="/apps"
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "} className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
</a> {isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
</Link> </Link>
{category && ( {category && (
<span className="gap-1 text-gray-600"> <span className="gap-1 text-gray-600">
+17 -17
View File
@@ -11,28 +11,28 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
return ( return (
<Shell isPublic large> <Shell isPublic large>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0"> <div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link href="/apps"> <Link
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900"> href="/apps"
<Icon.FiArrowLeft className="h-4 w-4" /> className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "} <Icon.FiArrowLeft className="h-4 w-4" />
</a> {isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</Link> </Link>
</div> </div>
<div className="mb-16"> <div className="mb-16">
<div className="grid h-auto w-full grid-cols-5 gap-3"> <div className="grid h-auto w-full grid-cols-5 gap-3">
{categories.map((category) => ( {categories.map((category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}> <Link
<a key={category.name}
data-testid={`app-store-category-${category.name}`} href={"/apps/categories/" + category.name}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block"> data-testid={`app-store-category-${category.name}`}
<div className="self-center"> className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<h3 className="font-medium capitalize">{category.name}</h3> <div className="self-center">
<p className="text-sm text-gray-500"> <h3 className="font-medium capitalize">{category.name}</h3>
{t("number_apps", { count: category.count })}{" "} <p className="text-sm text-gray-500">
<Icon.FiArrowRight className="inline-block h-4 w-4" /> {t("number_apps", { count: category.count })}{" "}
</p> <Icon.FiArrowRight className="inline-block h-4 w-4" />
</div> </p>
</a> </div>
</Link> </Link>
))} ))}
</div> </div>
@@ -183,6 +183,7 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
buttonRaw={ buttonRaw={
<Button <Button
color="secondary" color="secondary"
data-testid={`connect-${variant || "other"}-apps`}
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}> href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
{t(`connect_${variant || "other"}_apps`)} {t(`connect_${variant || "other"}_apps`)}
</Button> </Button>
+1 -1
View File
@@ -40,7 +40,7 @@ export default function Error() {
</div> </div>
</div> </div>
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<Link href="/auth/login" passHref> <Link href="/auth/login" passHref legacyBehavior>
<Button className="flex w-full justify-center">{t("go_back_login")}</Button> <Button className="flex w-full justify-center">{t("go_back_login")}</Button>
</Link> </Link>
</div> </div>
+1 -1
View File
@@ -81,7 +81,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
<h2 className="text-center text-3xl font-extrabold text-gray-900">{t("request_is_expired")}</h2> <h2 className="text-center text-3xl font-extrabold text-gray-900">{t("request_is_expired")}</h2>
</div> </div>
<p>{t("request_is_expired_instructions")}</p> <p>{t("request_is_expired_instructions")}</p>
<Link href="/auth/forgot-password" passHref> <Link href="/auth/forgot-password" passHref legacyBehavior>
<button <button
type="button" type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"> className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
@@ -94,8 +94,8 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
footerText={ footerText={
!success && ( !success && (
<> <>
<Link href="/auth/login"> <Link href="/auth/login" className="font-medium text-neutral-900">
<a className="font-medium text-neutral-900">{t("back_to_signin")}</a> {t("back_to_signin")}
</Link> </Link>
</> </>
) )
+5 -4
View File
@@ -133,10 +133,11 @@ export default function Login({
/> />
<div className="relative"> <div className="relative">
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0"> <div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
<Link href="/auth/forgot-password"> <Link
<a tabIndex={-1} className="text-sm font-medium text-gray-600"> href="/auth/forgot-password"
{t("forgot")} tabIndex={-1}
</a> className="text-sm font-medium text-gray-600">
{t("forgot")}
</Link> </Link>
</div> </div>
<PasswordField <PasswordField
+1 -1
View File
@@ -27,7 +27,7 @@ export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
{ {
title: t("enable_apps"), title: t("enable_apps"),
description: t("enable_apps_description"), description: t("enable_apps_description"),
content: <AdminAppsList baseURL="/auth/setup?step=2" />, content: <AdminAppsList baseURL="/auth/setup?step=2" useQueryParam={true} />,
isLoading: false, isLoading: false,
}, },
]; ];
+52 -56
View File
@@ -353,10 +353,10 @@ export default function Success(props: SuccessProps) {
)} )}
{userIsOwner && !isEmbed && ( {userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4"> <div className="mt-2 ml-4 -mb-4">
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}> <Link
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white"> href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")} className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white">
</a> <Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</Link> </Link>
</div> </div>
)} )}
@@ -573,7 +573,9 @@ export default function Success(props: SuccessProps) {
{!props.recurringBookings && ( {!props.recurringBookings && (
<span className="text-bookinglight inline text-gray-700"> <span className="text-bookinglight inline text-gray-700">
<span className="underline"> <span className="underline">
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link> <Link href={`/reschedule/${bookingInfo?.uid}`} legacyBehavior>
{t("reschedule")}
</Link>
</span> </span>
<span className="mx-2">{t("or_lowercase")}</span> <span className="mx-2">{t("or_lowercase")}</span>
</span> </span>
@@ -633,17 +635,16 @@ export default function Success(props: SuccessProps) {
? "&recur=" + ? "&recur=" +
encodeURIComponent(new RRule(props.eventType.recurringEvent).toString()) encodeURIComponent(new RRule(props.eventType.recurringEvent).toString())
: "") : "")
}> }
<a className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white"> className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white">
<svg <svg
className="-mt-1.5 inline-block h-4 w-4" className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
<title>Google</title> <title>Google</title>
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" /> <path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
</svg> </svg>
</a>
</Link> </Link>
<Link <Link
href={ href={
@@ -657,19 +658,17 @@ export default function Success(props: SuccessProps) {
"&subject=" + "&subject=" +
eventName eventName
) + (location ? "&location=" + location : "") ) + (location ? "&location=" + location : "")
}> }
<a className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" target="_blank">
target="_blank"> <svg
<svg className="mr-1 -mt-1.5 inline-block h-4 w-4"
className="mr-1 -mt-1.5 inline-block h-4 w-4" fill="currentColor"
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <title>Microsoft Outlook</title>
<title>Microsoft Outlook</title> <path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" />
<path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" /> </svg>
</svg>
</a>
</Link> </Link>
<Link <Link
href={ href={
@@ -683,34 +682,31 @@ export default function Success(props: SuccessProps) {
"&subject=" + "&subject=" +
eventName eventName
) + (location ? "&location=" + location : "") ) + (location ? "&location=" + location : "")
}> }
<a className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" target="_blank">
target="_blank"> <svg
<svg className="mr-1 -mt-1.5 inline-block h-4 w-4"
className="mr-1 -mt-1.5 inline-block h-4 w-4" fill="currentColor"
fill="currentColor" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
viewBox="0 0 24 24"> <title>Microsoft Office</title>
<title>Microsoft Office</title> <path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" />
<path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" /> </svg>
</svg>
</a>
</Link> </Link>
<Link href={"data:text/calendar," + eventLink()}> <Link
<a href={"data:text/calendar," + eventLink()}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white" className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
download={props.eventType.title + ".ics"}> download={props.eventType.title + ".ics"}>
<svg <svg
version="1.1" version="1.1"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000" viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4"> className="mr-1 -mt-1.5 inline-block h-4 w-4">
<title>{t("other")}</title> <title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" /> <path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg> </svg>
</a>
</Link> </Link>
</div> </div>
</div> </div>
+4 -3
View File
@@ -3,7 +3,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { PeriodType, SchedulingType } from "@prisma/client"; import { PeriodType, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
@@ -15,6 +14,7 @@ import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType"; import findDurationType from "@calcom/lib/findDurationType";
import getEventTypeById from "@calcom/lib/getEventTypeById"; import getEventTypeById from "@calcom/lib/getEventTypeById";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error"; import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@@ -111,8 +111,9 @@ export type EventTypeSetupProps = RouterOutputs["viewer"]["eventTypes"]["get"];
const EventTypePage = (props: EventTypeSetupProps) => { const EventTypePage = (props: EventTypeSetupProps) => {
const { t } = useLocale(); const { t } = useLocale();
const utils = trpc.useContext(); const utils = trpc.useContext();
const router = useRouter(); const {
const { tabName } = querySchema.parse(router.query); data: { tabName },
} = useTypedQuery(querySchema);
const { data: eventTypeApps } = trpc.viewer.apps.useQuery({ const { data: eventTypeApps } = trpc.viewer.apps.useQuery({
extendsFeature: "EventType", extendsFeature: "EventType",
+55 -65
View File
@@ -64,27 +64,26 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
const { t } = useLocale(); const { t } = useLocale();
return ( return (
<Link href={`/event-types/${type.id}?tabName=setup`}> <Link
<a href={`/event-types/${type.id}?tabName=setup`}
className="flex-1 overflow-hidden pr-4 text-sm" className="flex-1 overflow-hidden pr-4 text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}> title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
<div> <div>
<span <span
className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1" className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1"
data-testid={"event-type-title-" + type.id}> data-testid={"event-type-title-" + type.id}>
{type.title} {type.title}
</span>
<small
className="hidden font-normal leading-4 text-gray-600 sm:inline"
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly")}
</span> </span>
<small )}
className="hidden font-normal leading-4 text-gray-600 sm:inline" </div>
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small> <EventTypeDescription eventType={type} />
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly") as string}
</span>
)}
</div>
<EventTypeDescription eventType={type} />
</a>
</Link> </Link>
); );
}; };
@@ -297,7 +296,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{t("hidden")} {t("hidden")}
</Badge> </Badge>
)} )}
<Tooltip content={t("show_eventtype_on_profile") as string}> <Tooltip content={t("show_eventtype_on_profile")}>
<div className="self-center rounded-md p-2 hover:bg-gray-200"> <div className="self-center rounded-md p-2 hover:bg-gray-200">
<Switch <Switch
name="Hidden" name="Hidden"
@@ -310,7 +309,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</Tooltip> </Tooltip>
<ButtonGroup combined> <ButtonGroup combined>
<Tooltip content={t("preview") as string}> <Tooltip content={t("preview")}>
<Button <Button
color="secondary" color="secondary"
target="_blank" target="_blank"
@@ -320,7 +319,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
/> />
</Tooltip> </Tooltip>
<Tooltip content={t("copy_link") as string}> <Tooltip content={t("copy_link")}>
<Button <Button
color="secondary" color="secondary"
size="icon" size="icon"
@@ -350,7 +349,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-edit-" + type.id} data-testid={"event-type-edit-" + type.id}
StartIcon={Icon.FiEdit2} StartIcon={Icon.FiEdit2}
onClick={() => router.push("/event-types/" + type.id)}> onClick={() => router.push("/event-types/" + type.id)}>
{t("edit") as string} {t("edit")}
</DropdownItem> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
@@ -359,7 +358,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy} StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}> onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string} {t("duplicate")}
</DropdownItem> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
@@ -383,7 +382,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}} }}
StartIcon={Icon.FiTrash} StartIcon={Icon.FiTrash}
className="w-full rounded-none"> className="w-full rounded-none">
{t("delete") as string} {t("delete")}
</DropdownItem> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
)} )}
@@ -402,15 +401,13 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownMenuPortal> <DropdownMenuPortal>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
<Link href={calLink}> <Link href={calLink} target="_blank">
<a target="_blank"> <Button
<Button color="minimal"
color="minimal" StartIcon={Icon.FiExternalLink}
StartIcon={Icon.FiExternalLink} className="w-full rounded-none">
className="w-full rounded-none"> {t("preview")}
{t("preview") as string} </Button>
</Button>
</a>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
@@ -424,7 +421,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
navigator.clipboard.writeText(calLink); navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success"); showToast(t("link_copied"), "success");
}}> }}>
{t("copy_link") as string} {t("copy_link")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
{isNativeShare ? ( {isNativeShare ? (
@@ -445,7 +442,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
.then(() => showToast(t("link_shared"), "success")) .then(() => showToast(t("link_shared"), "success"))
.catch(() => showToast(t("failed"), "error")); .catch(() => showToast(t("failed"), "error"));
}}> }}>
{t("share") as string} {t("share")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
) : null} ) : null}
@@ -456,7 +453,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal" color="minimal"
className="w-full rounded-none" className="w-full rounded-none"
StartIcon={Icon.FiEdit}> StartIcon={Icon.FiEdit}>
{t("edit") as string} {t("edit")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="outline-none"> <DropdownMenuItem className="outline-none">
@@ -467,7 +464,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id} data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy} StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}> onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string} {t("duplicate")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" /> <DropdownMenuSeparator className="h-px bg-gray-200" />
@@ -480,7 +477,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="destructive" color="destructive"
StartIcon={Icon.FiTrash} StartIcon={Icon.FiTrash}
className="w-full rounded-none"> className="w-full rounded-none">
{t("delete") as string} {t("delete")}
</Button> </Button>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -502,7 +499,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
e.preventDefault(); e.preventDefault();
deleteEventTypeHandler(deleteDialogTypeId); deleteEventTypeHandler(deleteDialogTypeId);
}}> }}>
{t("delete_event_type_description") as string} {t("delete_event_type_description")}
</ConfirmationDialogContent> </ConfirmationDialogContent>
</Dialog> </Dialog>
</div> </div>
@@ -517,36 +514,32 @@ const EventTypeListHeading = ({
return ( return (
<div className="mb-4 flex"> <div className="mb-4 flex">
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}> <Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a> <Avatar
<Avatar alt={profile?.name || ""}
alt={profile?.name || ""} imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined} size={8}
size={8} className="mt-1 inline ltr:mr-2 rtl:ml-2"
className="mt-1 inline ltr:mr-2 rtl:ml-2" />
/>
</a>
</Link> </Link>
<div> <div>
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}> <Link
<a className="font-bold">{profile?.name || ""}</a> href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
className="font-bold">
{profile?.name || ""}
</Link> </Link>
{membershipCount && teamId && ( {membershipCount && teamId && (
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2"> <span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
<Link href={`/settings/teams/${teamId}/members`}> <Link href={`/settings/teams/${teamId}/members`}>
<a> <Badge variant="gray">
<Badge variant="gray"> <Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" /> {membershipCount}
{membershipCount} </Badge>
</Badge>
</a>
</Link> </Link>
</span> </span>
)} )}
{profile?.slug && ( {profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`}> <Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-neutral-500">
<a className="block text-xs text-neutral-500">{`${CAL_URL?.replace("https://", "")}/${ {`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
profile.slug
}`}</a>
</Link> </Link>
)} )}
</div> </div>
@@ -581,10 +574,7 @@ const EventTypesPage = () => {
return ( return (
<div> <div>
<Shell <Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
heading={t("event_types_page_title") as string}
subtitle={t("event_types_page_subtitle") as string}
CTA={<CTA />}>
<WithQuery <WithQuery
customLoader={<SkeletonLoader />} customLoader={<SkeletonLoader />}
success={({ data }) => ( success={({ data }) => (
@@ -181,7 +181,9 @@ const ProfileView = () => {
}; };
if (isLoading || !user || isLoadingAvatar || !avatar) if (isLoading || !user || isLoadingAvatar || !avatar)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />; return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
);
const defaultValues = { const defaultValues = {
username: user.username || "", username: user.username || "",
+47 -41
View File
@@ -45,28 +45,29 @@ function TeamPage({ team }: TeamPageProps) {
"dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600", "dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600",
!isEmbed && "bg-white" !isEmbed && "bg-white"
)}> )}>
<Link href={`/team/${team.slug}/${type.slug}`}> <Link
<a className="flex justify-between px-6 py-4" data-testid="event-type-link"> href={`/team/${team.slug}/${type.slug}`}
<div className="flex-shrink"> className="flex justify-between px-6 py-4"
<div className="flex flex-wrap items-center space-x-2 rtl:space-x-reverse"> data-testid="event-type-link">
<h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2> <div className="flex-shrink">
</div> <div className="flex flex-wrap items-center space-x-2 rtl:space-x-reverse">
<EventTypeDescription className="text-sm" eventType={type} /> <h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2>
</div> </div>
<div className="mt-1 self-center"> <EventTypeDescription className="text-sm" eventType={type} />
<AvatarGroup </div>
border="border-2 border-white dark:border-darkgray-100" <div className="mt-1 self-center">
truncateAfter={4} <AvatarGroup
className="flex flex-shrink-0" border="border-2 border-white dark:border-darkgray-100"
size={10} truncateAfter={4}
items={type.users.map((user) => ({ className="flex flex-shrink-0"
alt: user.name || "", size={10}
title: user.name || "", items={type.users.map((user) => ({
image: CAL_URL + "/" + user.username + "/avatar.png" || "", alt: user.name || "",
}))} title: user.name || "",
/> image: CAL_URL + "/" + user.username + "/avatar.png" || "",
</div> }))}
</a> />
</div>
</Link> </Link>
</li> </li>
))} ))}
@@ -97,27 +98,32 @@ function TeamPage({ team }: TeamPageProps) {
{!showMembers.isOn && team.eventTypes.length > 0 && ( {!showMembers.isOn && team.eventTypes.length > 0 && (
<div className="mx-auto max-w-3xl "> <div className="mx-auto max-w-3xl ">
<EventTypes /> <EventTypes />
<div className="relative mt-12">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="dark:border-darkgray-300 w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-gray-100 px-2 text-sm text-gray-500 dark:text-white">
{t("or")}
</span>
</div>
</div>
<aside className="mt-8 flex justify-center text-center dark:text-white"> {!team.hideBookATeamMember && (
<Button <div>
color="minimal" <div className="relative mt-12">
EndIcon={Icon.FiArrowRight} <div className="absolute inset-0 flex items-center" aria-hidden="true">
className="dark:hover:bg-darkgray-200" <div className="dark:border-darkgray-300 w-full border-t border-gray-200" />
href={`/team/${team.slug}?members=1`} </div>
shallow={true}> <div className="relative flex justify-center">
{t("book_a_team_member")} <span className="dark:bg-darkgray-50 bg-gray-100 px-2 text-sm text-gray-500 dark:text-white">
</Button> {t("or")}
</aside> </span>
</div>
</div>
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
className="dark:hover:bg-darkgray-200"
href={`/team/${team.slug}?members=1`}
shallow={true}>
{t("book_a_team_member")}
</Button>
</aside>
</div>
)}
</div> </div>
)} )}
</main> </main>
+11
View File
@@ -14,6 +14,17 @@ test.describe("App Store - Authed", () => {
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible(); await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
await pro.delete(); await pro.delete();
}); });
test("Installed Apps - Navigation", async ({ page, users }) => {
const user = await users.create();
await user.login();
await page.goto("/apps/installed");
await page.waitForSelector('[data-testid="connect-calendar-apps"]');
await page.click('[data-testid="vertical-tab-payment"]');
await page.waitForSelector('[data-testid="connect-payment-apps"]');
await page.click('[data-testid="vertical-tab-automation"]');
await page.waitForSelector('[data-testid="connect-automation-apps"]');
});
}); });
test.describe("App Store - Unauthed", () => { test.describe("App Store - Unauthed", () => {
+2 -1
View File
@@ -5,7 +5,8 @@ import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth } from "./lib/testU
test.describe.configure({ mode: "parallel" }); test.describe.configure({ mode: "parallel" });
test.describe("hash my url", () => { // TODO: This test is very flaky. Feels like tossing a coin and hope that it won't fail. Needs to be revisited.
test.fixme("hash my url", () => {
test.beforeEach(async ({ users }) => { test.beforeEach(async ({ users }) => {
const user = await users.create(); const user = await users.create();
await user.login(); await user.login();
@@ -1077,13 +1077,13 @@
"new_seat_subject": "حضور جديد {{name}} لـ {{eventType}} في {{date}}", "new_seat_subject": "حضور جديد {{name}} لـ {{eventType}} في {{date}}",
"new_seat_title": "شخص ما أضاف نفسه إلى حدث", "new_seat_title": "شخص ما أضاف نفسه إلى حدث",
"variable": "متغير", "variable": "متغير",
"event_name_workflow": "اسم الحدث", "event_name_variable": "اسم الحدث",
"organizer_name_workflow": "المنظِّم", "organizer_name_variable": "المنظِّم",
"attendee_name_workflow": "الحاضر", "attendee_name_variable": "الحاضر",
"event_date_workflow": "تاريخ الحدث", "event_date_variable": "تاريخ الحدث",
"event_time_workflow": "وقت الحدث", "event_time_variable": "وقت الحدث",
"location_workflow": "الموقع", "location_variable": "الموقع",
"additional_notes_workflow": "ملاحظات إضافية", "additional_notes_variable": "ملاحظات إضافية",
"app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.", "app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
"invalid_number": "رقم الهاتف غير صالح", "invalid_number": "رقم الهاتف غير صالح",
"navigate": "تنقّل", "navigate": "تنقّل",
@@ -1387,7 +1387,7 @@
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل", "team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
"team_publish": "نشر فريق", "team_publish": "نشر فريق",
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)", "number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
"attendee_email_workflow": "اسم الحاضر", "attendee_email_variable": "اسم الحاضر",
"attendee_email_info": "البريد الإلكتروني للشخص الحجز", "attendee_email_info": "البريد الإلكتروني للشخص الحجز",
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...", "kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.", "invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nový účastník {{name}} události {{eventType}} dne {{date}}", "new_seat_subject": "Nový účastník {{name}} události {{eventType}} dne {{date}}",
"new_seat_title": "Někdo se přidal k události", "new_seat_title": "Někdo se přidal k události",
"variable": "Proměnná", "variable": "Proměnná",
"event_name_workflow": "Název události", "event_name_variable": "Název události",
"organizer_name_workflow": "Jméno organizátora", "organizer_name_variable": "Jméno organizátora",
"attendee_name_workflow": "Jméno účastníka", "attendee_name_variable": "Jméno účastníka",
"event_date_workflow": "Datum události", "event_date_variable": "Datum události",
"event_time_workflow": "Čas události", "event_time_variable": "Čas události",
"location_workflow": "Místo", "location_variable": "Místo",
"additional_notes_workflow": "Doplňující poznámky", "additional_notes_variable": "Doplňující poznámky",
"app_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.", "app_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.",
"invalid_number": "Neplatné telefonní číslo", "invalid_number": "Neplatné telefonní číslo",
"navigate": "Navigace", "navigate": "Navigace",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Tato adresa URL je už obsazená", "team_url_taken": "Tato adresa URL je už obsazená",
"team_publish": "Zveřejnit tým", "team_publish": "Zveřejnit tým",
"number_sms_notifications": "Číslo telefonu (SMS oznámení)", "number_sms_notifications": "Číslo telefonu (SMS oznámení)",
"attendee_email_workflow": "E-mail účastníka", "attendee_email_variable": "E-mail účastníka",
"attendee_email_info": "E-mail osoby provádějící rezervaci", "attendee_email_info": "E-mail osoby provádějící rezervaci",
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...", "kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.", "invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.",
@@ -1078,13 +1078,13 @@
"new_seat_subject": "Neuer Teilnehmer {{name}} beim {{eventType}} am {{date}}", "new_seat_subject": "Neuer Teilnehmer {{name}} beim {{eventType}} am {{date}}",
"new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt", "new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt",
"variable": "Variable", "variable": "Variable",
"event_name_workflow": "Event Name", "event_name_variable": "Event Name",
"organizer_name_workflow": "Organisator", "organizer_name_variable": "Organisator",
"attendee_name_workflow": "Teilnehmer", "attendee_name_variable": "Teilnehmer",
"event_date_workflow": "Event Datum", "event_date_variable": "Event Datum",
"event_time_workflow": "Event Zeit", "event_time_variable": "Event Zeit",
"location_workflow": "Ort", "location_variable": "Ort",
"additional_notes_workflow": "Zusätzliche Notizen", "additional_notes_variable": "Zusätzliche Notizen",
"app_upgrade_description": "Um diese Funktion nutzen zu können, müssen Sie ein Upgrade auf einen Pro-Account durchführen.", "app_upgrade_description": "Um diese Funktion nutzen zu können, müssen Sie ein Upgrade auf einen Pro-Account durchführen.",
"invalid_number": "Ungültige Telefonnummer", "invalid_number": "Ungültige Telefonnummer",
"navigate": "Navigieren", "navigate": "Navigieren",
@@ -1388,7 +1388,7 @@
"team_url_taken": "Diese URL ist bereits vergeben", "team_url_taken": "Diese URL ist bereits vergeben",
"team_publish": "Team veröffentlichen", "team_publish": "Team veröffentlichen",
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)", "number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
"attendee_email_workflow": "Teilnehmer E-Mail", "attendee_email_variable": "Teilnehmer E-Mail",
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person", "attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...", "kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.", "invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.",
+19 -10
View File
@@ -521,6 +521,8 @@
"change_member_role": "Change team member role", "change_member_role": "Change team member role",
"disable_cal_branding": "Disable {{appName}} branding", "disable_cal_branding": "Disable {{appName}} branding",
"disable_cal_branding_description": "Hide all {{appName}} branding from your public pages.", "disable_cal_branding_description": "Hide all {{appName}} branding from your public pages.",
"hide_book_a_team_member": "Hide Book a Team Member Button",
"hide_book_a_team_member_description": "Hide Book a Team Member Button from your public pages.",
"danger_zone": "Danger zone", "danger_zone": "Danger zone",
"back": "Back", "back": "Back",
"cancel": "Cancel", "cancel": "Cancel",
@@ -1082,13 +1084,13 @@
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}", "new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
"new_seat_title": "Someone has added themselves to an event", "new_seat_title": "Someone has added themselves to an event",
"variable": "Variable", "variable": "Variable",
"event_name_workflow": "Event name", "event_name_variable": "Event name",
"organizer_name_workflow": "Organizer", "organizer_name_variable": "Organizer",
"attendee_name_workflow": "Attendee", "attendee_name_variable": "Attendee",
"event_date_workflow": "Event date", "event_date_variable": "Event date",
"event_time_workflow": "Event time", "event_time_variable": "Event time",
"location_workflow": "Location", "location_variable": "Location",
"additional_notes_workflow": "Additional notes", "additional_notes_variable": "Additional notes",
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.", "app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
"invalid_number": "Invalid phone number", "invalid_number": "Invalid phone number",
"navigate": "Navigate", "navigate": "Navigate",
@@ -1402,7 +1404,7 @@
"team_url_taken": "This URL is already taken", "team_url_taken": "This URL is already taken",
"team_publish": "Publish team", "team_publish": "Publish team",
"number_sms_notifications": "Phone number (SMS\u00a0notifications)", "number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email", "attendee_email_variable": "Attendee email",
"attendee_email_info": "The person booking's email", "attendee_email_info": "The person booking's email",
"kbar_search_placeholder": "Type a command or search...", "kbar_search_placeholder": "Type a command or search...",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.", "invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
@@ -1454,10 +1456,11 @@
"enter_option": "Enter Option {{index}}", "enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option", "add_an_option": "Add an option",
"radio": "Radio", "radio": "Radio",
"google_meet_warning": "In order to use Google Meet you must set your destination calendar to a Google Calendar",
"individual":"Individual", "individual":"Individual",
"all_bookings_filter_label":"All Bookings", "all_bookings_filter_label":"All Bookings",
"all_users_filter_label":"All Users", "all_users_filter_label":"All Users",
"meeting_url_workflow": "Meeting url", "meeting_url_variable": "Meeting url",
"meeting_url_info": "The event meeting conference url", "meeting_url_info": "The event meeting conference url",
"date_overrides": "Date overrides", "date_overrides": "Date overrides",
"date_overrides_subtitle": "Add dates when your availability changes from your daily hours.", "date_overrides_subtitle": "Add dates when your availability changes from your daily hours.",
@@ -1488,5 +1491,11 @@
"disable_success_page":"Disable Success Page (only works if you have a redirect URL)", "disable_success_page":"Disable Success Page (only works if you have a redirect URL)",
"invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters", "invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters",
"change_password_admin": "Change Password to gain admin access", "change_password_admin": "Change Password to gain admin access",
"username_already_taken": "Username is already taken" "username_already_taken": "Username is already taken",
"requires_google_calendar": "This app requires a Google Calendar connection",
"connected_google_calendar": "You have connected a Google Calendar account.",
"using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
"continue_to_install_google_calendar": "Continue to install Google Calendar",
"install_google_meet": "Install Google Meet",
"install_google_calendar": "Install Google Calendar"
} }
+10 -10
View File
@@ -511,7 +511,7 @@
"add_team_members_description": "Invitar a otros a unirse a tu equipo", "add_team_members_description": "Invitar a otros a unirse a tu equipo",
"add_team_member": "Añadir miembro del equipo", "add_team_member": "Añadir miembro del equipo",
"invite_new_member": "Invitar a un nuevo miembro", "invite_new_member": "Invitar a un nuevo miembro",
"invite_new_member_description": "Nota: Esto costará <1>un cupo adicional ($15/m)</1> en su suscripción.", "invite_new_member_description": "Nota: Esto costará <1>una plaza adicional ($15/m)</1> en su suscripción.",
"invite_new_team_member": "Invita a alguien a tu equipo.", "invite_new_team_member": "Invita a alguien a tu equipo.",
"change_member_role": "Cambiar rol del miembro del equipo", "change_member_role": "Cambiar rol del miembro del equipo",
"disable_cal_branding": "Desactivar marca de {{appName}}", "disable_cal_branding": "Desactivar marca de {{appName}}",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nuevo asistente {{name}} en {{eventType}} el {{date}}", "new_seat_subject": "Nuevo asistente {{name}} en {{eventType}} el {{date}}",
"new_seat_title": "Alguien se ha añadido a un evento", "new_seat_title": "Alguien se ha añadido a un evento",
"variable": "Variable", "variable": "Variable",
"event_name_workflow": "Nombre del evento", "event_name_variable": "Nombre del evento",
"organizer_name_workflow": "Nombre del organizador", "organizer_name_variable": "Nombre del organizador",
"attendee_name_workflow": "Nombre del asistente", "attendee_name_variable": "Nombre del asistente",
"event_date_workflow": "Fecha del evento", "event_date_variable": "Fecha del evento",
"event_time_workflow": "Hora del evento", "event_time_variable": "Hora del evento",
"location_workflow": "Lugar", "location_variable": "Lugar",
"additional_notes_workflow": "Notas Adicionales", "additional_notes_variable": "Notas Adicionales",
"app_upgrade_description": "Para poder usar esta función, necesita actualizarse a una cuenta Pro.", "app_upgrade_description": "Para poder usar esta función, necesita actualizarse a una cuenta Pro.",
"invalid_number": "Número de teléfono no válido", "invalid_number": "Número de teléfono no válido",
"navigate": "Navegar", "navigate": "Navegar",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Esta URL ya está en uso", "team_url_taken": "Esta URL ya está en uso",
"team_publish": "Publicar equipo", "team_publish": "Publicar equipo",
"number_sms_notifications": "Número de teléfono (notificaciones por SMS)", "number_sms_notifications": "Número de teléfono (notificaciones por SMS)",
"attendee_email_workflow": "Correo electrónico del asistente", "attendee_email_variable": "Correo electrónico del asistente",
"attendee_email_info": "El correo electrónico de la persona que reserva", "attendee_email_info": "El correo electrónico de la persona que reserva",
"kbar_search_placeholder": "Escriba un comando o búsqueda...", "kbar_search_placeholder": "Escriba un comando o búsqueda...",
"invalid_credential": "¡Oh no! Parece que el permiso ha caducado o se ha revocado. Vuelva a instalarlo.", "invalid_credential": "¡Oh no! Parece que el permiso ha caducado o se ha revocado. Vuelva a instalarlo.",
@@ -1396,7 +1396,7 @@
"sender_id": "ID del remitente", "sender_id": "ID del remitente",
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)", "sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
"test_routing_form": "Prueba de formulario de ruta", "test_routing_form": "Prueba de formulario de ruta",
"test_preview": "Vista previa de la prueba", "test_preview": "Probar vista previa",
"route_to": "Ruta hacia", "route_to": "Ruta hacia",
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato", "test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
"test_routing": "Prueba de ruta", "test_routing": "Prueba de ruta",
File diff suppressed because it is too large. Load diff
@@ -1076,13 +1076,13 @@
"new_seat_subject": "משתתף/ת חדש/ה {{name}} ב{{eventType}} בתאריך {{date}}", "new_seat_subject": "משתתף/ת חדש/ה {{name}} ב{{eventType}} בתאריך {{date}}",
"new_seat_title": "מישהו הוסיף את עצמו לאירוע", "new_seat_title": "מישהו הוסיף את עצמו לאירוע",
"variable": "משתנה", "variable": "משתנה",
"event_name_workflow": "שם האירוע", "event_name_variable": "שם האירוע",
"organizer_name_workflow": "שם המארגן/ת", "organizer_name_variable": "שם המארגן/ת",
"attendee_name_workflow": "שם המשתתף/ת", "attendee_name_variable": "שם המשתתף/ת",
"event_date_workflow": "תאריך האירוע", "event_date_variable": "תאריך האירוע",
"event_time_workflow": "שעת האירוע", "event_time_variable": "שעת האירוע",
"location_workflow": "מיקום", "location_variable": "מיקום",
"additional_notes_workflow": "הערות נוספות", "additional_notes_variable": "הערות נוספות",
"app_upgrade_description": "כדי להשתמש בתכונה זו, עליך לשדרג לחשבון Pro.", "app_upgrade_description": "כדי להשתמש בתכונה זו, עליך לשדרג לחשבון Pro.",
"invalid_number": "מספר טלפון לא תקין", "invalid_number": "מספר טלפון לא תקין",
"navigate": "ניווט", "navigate": "ניווט",
@@ -1386,7 +1386,7 @@
"team_url_taken": "כתובת ה-URL הזו כבר תפוסה", "team_url_taken": "כתובת ה-URL הזו כבר תפוסה",
"team_publish": "פרסום צוות", "team_publish": "פרסום צוות",
"number_sms_notifications": "מספר טלפון (הודעות SMS)", "number_sms_notifications": "מספר טלפון (הודעות SMS)",
"attendee_email_workflow": "כתובת הדוא\"ל של המשתתף/ת", "attendee_email_variable": "כתובת הדוא\"ל של המשתתף/ת",
"attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה", "attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה",
"kbar_search_placeholder": "הקלד/י פקודה או חפש/י...", "kbar_search_placeholder": "הקלד/י פקודה או חפש/י...",
"invalid_credential": "נראה שתוקף ההרשאה פג או שההרשאה בוטלה. יש להתקין מחדש שוב.", "invalid_credential": "נראה שתוקף ההרשאה פג או שההרשאה בוטלה. יש להתקין מחדש שוב.",
@@ -212,9 +212,9 @@
"remove_app": "Alkalmazás eltávolítása", "remove_app": "Alkalmazás eltávolítása",
"yes_remove_app": "Igen, távolítsd el az alkalmazást", "yes_remove_app": "Igen, távolítsd el az alkalmazást",
"web_conference": "Online konferencia", "web_conference": "Online konferencia",
"organizer_name_workflow": "Szervező", "organizer_name_variable": "Szervező",
"location_workflow": "Helyszín", "location_variable": "Helyszín",
"additional_notes_workflow": "Egyéb jegyzetek", "additional_notes_variable": "Egyéb jegyzetek",
"organizer_name_info": "Neved", "organizer_name_info": "Neved",
"theme_light": "Világos", "theme_light": "Világos",
"theme_dark": "Sötét" "theme_dark": "Sötét"
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Nuovo partecipante {{name}} a {{eventType}} il {{date}}", "new_seat_subject": "Nuovo partecipante {{name}} a {{eventType}} il {{date}}",
"new_seat_title": "Qualcuno ha aggiunto se stesso a un evento", "new_seat_title": "Qualcuno ha aggiunto se stesso a un evento",
"variable": "Variabile", "variable": "Variabile",
"event_name_workflow": "Nome evento", "event_name_variable": "Nome evento",
"organizer_name_workflow": "Organizzatore", "organizer_name_variable": "Organizzatore",
"attendee_name_workflow": "Partecipante", "attendee_name_variable": "Partecipante",
"event_date_workflow": "Data evento", "event_date_variable": "Data evento",
"event_time_workflow": "Ora evento", "event_time_variable": "Ora evento",
"location_workflow": "Luogo", "location_variable": "Luogo",
"additional_notes_workflow": "Note aggiuntive", "additional_notes_variable": "Note aggiuntive",
"app_upgrade_description": "Per poter utilizzare questa funzionalità, è necessario passare a un account Pro.", "app_upgrade_description": "Per poter utilizzare questa funzionalità, è necessario passare a un account Pro.",
"invalid_number": "Numero di telefono non valido", "invalid_number": "Numero di telefono non valido",
"navigate": "Esplora", "navigate": "Esplora",
@@ -1387,7 +1387,7 @@
"team_url_taken": "URL già occupato", "team_url_taken": "URL già occupato",
"team_publish": "Pubblica team", "team_publish": "Pubblica team",
"number_sms_notifications": "Numero di telefono (notifiche SMS)", "number_sms_notifications": "Numero di telefono (notifiche SMS)",
"attendee_email_workflow": "E-mail partecipante", "attendee_email_variable": "E-mail partecipante",
"attendee_email_info": "E-mail della persona che prenota", "attendee_email_info": "E-mail della persona che prenota",
"kbar_search_placeholder": "Digitare un comando o eseguire una ricerca...", "kbar_search_placeholder": "Digitare un comando o eseguire una ricerca...",
"invalid_credential": "Errore! Sembra che l'autorizzazione sia scaduta o che sia stata revocata. Prova a reinstallare.", "invalid_credential": "Errore! Sembra che l'autorizzazione sia scaduta o che sia stata revocata. Prova a reinstallare.",
@@ -612,7 +612,7 @@
"monthly_other": "月", "monthly_other": "月",
"yearly_one": "年", "yearly_one": "年",
"yearly_other": "年", "yearly_other": "年",
"plus_more": "あと {{count}} 回", "plus_more": "あと {{count}} 件",
"max": "最大", "max": "最大",
"single_theme": "シングルテーマ", "single_theme": "シングルテーマ",
"brand_color": "ブランドカラー", "brand_color": "ブランドカラー",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{date}} の {{eventType}} への新規参加者 {{name}}", "new_seat_subject": "{{date}} の {{eventType}} への新規参加者 {{name}}",
"new_seat_title": "別のユーザーがイベントに自身を追加しました", "new_seat_title": "別のユーザーがイベントに自身を追加しました",
"variable": "変数", "variable": "変数",
"event_name_workflow": "イベントの名前", "event_name_variable": "イベントの名前",
"organizer_name_workflow": "主催者の名前", "organizer_name_variable": "主催者の名前",
"attendee_name_workflow": "出席者の名前", "attendee_name_variable": "出席者の名前",
"event_date_workflow": "イベントの日付", "event_date_variable": "イベントの日付",
"event_time_workflow": "イベントの時間", "event_time_variable": "イベントの時間",
"location_workflow": "場所", "location_variable": "場所",
"additional_notes_workflow": "備考", "additional_notes_variable": "備考",
"app_upgrade_description": "この機能を利用するには、Pro アカウントへのアップグレードが必要です。", "app_upgrade_description": "この機能を利用するには、Pro アカウントへのアップグレードが必要です。",
"invalid_number": "電話番号が無効です", "invalid_number": "電話番号が無効です",
"navigate": "ナビゲート", "navigate": "ナビゲート",
@@ -1386,7 +1386,7 @@
"team_url_taken": "この URL は既に使用されています", "team_url_taken": "この URL は既に使用されています",
"team_publish": "チームを公開", "team_publish": "チームを公開",
"number_sms_notifications": "電話番号(SMS 通知)", "number_sms_notifications": "電話番号(SMS 通知)",
"attendee_email_workflow": "出席者のメールアドレス", "attendee_email_variable": "出席者のメールアドレス",
"attendee_email_info": "予約者のメールアドレス", "attendee_email_info": "予約者のメールアドレス",
"kbar_search_placeholder": "コマンドを入力するか、検索...", "kbar_search_placeholder": "コマンドを入力するか、検索...",
"invalid_credential": "アクセス許可の有効期限が切れたか、取り消されたようです。もう一度インストールしてください。", "invalid_credential": "アクセス許可の有効期限が切れたか、取り消されたようです。もう一度インストールしてください。",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "신규 참석자 {{name}}님이 {{date}}, {{eventType}}에 참석", "new_seat_subject": "신규 참석자 {{name}}님이 {{date}}, {{eventType}}에 참석",
"new_seat_title": "누군가가 이벤트에 자신을 추가했습니다", "new_seat_title": "누군가가 이벤트에 자신을 추가했습니다",
"variable": "변수", "variable": "변수",
"event_name_workflow": "이벤트 이름", "event_name_variable": "이벤트 이름",
"organizer_name_workflow": "주최자", "organizer_name_variable": "주최자",
"attendee_name_workflow": "참석자", "attendee_name_variable": "참석자",
"event_date_workflow": "이벤트 날짜", "event_date_variable": "이벤트 날짜",
"event_time_workflow": "이벤트 시간", "event_time_variable": "이벤트 시간",
"location_workflow": "위치", "location_variable": "위치",
"additional_notes_workflow": "추가 참고 사항", "additional_notes_variable": "추가 참고 사항",
"app_upgrade_description": "이 기능을 사용하려면 Pro 계정으로 업그레이드해야 합니다.", "app_upgrade_description": "이 기능을 사용하려면 Pro 계정으로 업그레이드해야 합니다.",
"invalid_number": "유효하지 않은 전화 번호", "invalid_number": "유효하지 않은 전화 번호",
"navigate": "탐색하기", "navigate": "탐색하기",
@@ -1387,7 +1387,7 @@
"team_url_taken": "이미 사용 중인 URL입니다", "team_url_taken": "이미 사용 중인 URL입니다",
"team_publish": "팀 게시", "team_publish": "팀 게시",
"number_sms_notifications": "전화번호 (SMS notifications)", "number_sms_notifications": "전화번호 (SMS notifications)",
"attendee_email_workflow": "참석자 이메일", "attendee_email_variable": "참석자 이메일",
"attendee_email_info": "예약자 이메일", "attendee_email_info": "예약자 이메일",
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...", "kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
"invalid_credential": "죄송합니다! 권한이 만료되었거나 취소된 것 같습니다. 다시 설치하십시오.", "invalid_credential": "죄송합니다! 권한이 만료되었거나 취소된 것 같습니다. 다시 설치하십시오.",
+10 -10
View File
@@ -612,7 +612,7 @@
"monthly_other": "maanden", "monthly_other": "maanden",
"yearly_one": "jaar", "yearly_one": "jaar",
"yearly_other": "jaar", "yearly_other": "jaar",
"plus_more": "{{count}} meer", "plus_more": "Nog {{count}}",
"max": "Max.", "max": "Max.",
"single_theme": "Enkel Thema", "single_theme": "Enkel Thema",
"brand_color": "Merkkleur", "brand_color": "Merkkleur",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nieuwe deelnemer {{name}} op {{eventType}} om {{date}}", "new_seat_subject": "Nieuwe deelnemer {{name}} op {{eventType}} om {{date}}",
"new_seat_title": "Iemand heeft zichzelf toegevoegd aan een evenement", "new_seat_title": "Iemand heeft zichzelf toegevoegd aan een evenement",
"variable": "Variabel", "variable": "Variabel",
"event_name_workflow": "Naam gebeurtenis", "event_name_variable": "Naam gebeurtenis",
"organizer_name_workflow": "Naam organisator", "organizer_name_variable": "Naam organisator",
"attendee_name_workflow": "Deelnemer", "attendee_name_variable": "Deelnemer",
"event_date_workflow": "Datum gebeurtenis", "event_date_variable": "Datum gebeurtenis",
"event_time_workflow": "Tijd gebeurtenis", "event_time_variable": "Tijd gebeurtenis",
"location_workflow": "Locatie", "location_variable": "Locatie",
"additional_notes_workflow": "Aanvullende opmerkingen", "additional_notes_variable": "Aanvullende opmerkingen",
"app_upgrade_description": "Om deze functie te gebruiken, moet u upgraden naar een Pro-account.", "app_upgrade_description": "Om deze functie te gebruiken, moet u upgraden naar een Pro-account.",
"invalid_number": "Ongeldig telefoonnummer", "invalid_number": "Ongeldig telefoonnummer",
"navigate": "Navigeren", "navigate": "Navigeren",
@@ -1386,11 +1386,11 @@
"team_url_taken": "Deze URL is al in gebruik", "team_url_taken": "Deze URL is al in gebruik",
"team_publish": "Team publiceren", "team_publish": "Team publiceren",
"number_sms_notifications": "Telefoonnummer (sms-meldingen)", "number_sms_notifications": "Telefoonnummer (sms-meldingen)",
"attendee_email_workflow": "E-mailadres deelnemer", "attendee_email_variable": "E-mailadres deelnemer",
"attendee_email_info": "Het e-mailadres van de persoon die boekt", "attendee_email_info": "Het e-mailadres van de persoon die boekt",
"kbar_search_placeholder": "Typ een opdracht of zoek...", "kbar_search_placeholder": "Typ een opdracht of zoek...",
"invalid_credential": "Oh nee! Het lijkt erop dat de machtiging is verlopen of is ingetrokken. Installeer het opnieuw.", "invalid_credential": "Oh nee! Het lijkt erop dat de machtiging is verlopen of is ingetrokken. Installeer het opnieuw.",
"choose_common_schedule_team_event": "Kies een gemeenschappelijke planning", "choose_common_schedule_team_event": "Kies een gezamenlijke planning",
"choose_common_schedule_team_event_description": "Schakel dit in als u een gemeenschappelijke planning tussen organisatoren wilt gebruiken. Indien uitgeschakeld, wordt elke organisator geboekt op basis van zijn standaardplanning.", "choose_common_schedule_team_event_description": "Schakel dit in als u een gemeenschappelijke planning tussen organisatoren wilt gebruiken. Indien uitgeschakeld, wordt elke organisator geboekt op basis van zijn standaardplanning.",
"reason": "Reden", "reason": "Reden",
"sender_id": "Afzender-ID", "sender_id": "Afzender-ID",
@@ -1080,13 +1080,13 @@
"new_seat_subject": "Ny Deltaker {{name}} på {{eventType}} kl. {{date}}", "new_seat_subject": "Ny Deltaker {{name}} på {{eventType}} kl. {{date}}",
"new_seat_title": "Noen har lagt seg til en hendelse", "new_seat_title": "Noen har lagt seg til en hendelse",
"variable": "Variabel", "variable": "Variabel",
"event_name_workflow": "Hendelsesnavn", "event_name_variable": "Hendelsesnavn",
"organizer_name_workflow": "Arrangør", "organizer_name_variable": "Arrangør",
"attendee_name_workflow": "Deltaker", "attendee_name_variable": "Deltaker",
"event_date_workflow": "Hendelsesdato", "event_date_variable": "Hendelsesdato",
"event_time_workflow": "Tidspunkt for hendelse", "event_time_variable": "Tidspunkt for hendelse",
"location_workflow": "Sted", "location_variable": "Sted",
"additional_notes_workflow": "Tilleggsinformasjon", "additional_notes_variable": "Tilleggsinformasjon",
"app_upgrade_description": "For å bruke denne funksjonen må du oppgradere til en Pro-konto.", "app_upgrade_description": "For å bruke denne funksjonen må du oppgradere til en Pro-konto.",
"invalid_number": "Ugyldig telefonnummer", "invalid_number": "Ugyldig telefonnummer",
"navigate": "Naviger", "navigate": "Naviger",
@@ -1400,7 +1400,7 @@
"team_url_taken": "Denne URL'en er allerede tatt", "team_url_taken": "Denne URL'en er allerede tatt",
"team_publish": "Publiser team", "team_publish": "Publiser team",
"number_sms_notifications": "Telefonnummer (SMS varsler)", "number_sms_notifications": "Telefonnummer (SMS varsler)",
"attendee_email_workflow": "Deltakerens e-post", "attendee_email_variable": "Deltakerens e-post",
"attendee_email_info": "Den som booker sin e-post", "attendee_email_info": "Den som booker sin e-post",
"kbar_search_placeholder": "Skriv inn en kommando eller søk...", "kbar_search_placeholder": "Skriv inn en kommando eller søk...",
"invalid_credential": "Å nei! Det ser ut til at tillatelsen er utløpt eller tilbakekalt. Vennligst installer på nytt.", "invalid_credential": "Å nei! Det ser ut til at tillatelsen er utløpt eller tilbakekalt. Vennligst installer på nytt.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nowy uczestnik ({{name}}) wydarzenia {{eventType}} odbywającego się {{date}}", "new_seat_subject": "Nowy uczestnik ({{name}}) wydarzenia {{eventType}} odbywającego się {{date}}",
"new_seat_title": "Ktoś dodał się do wydarzenia", "new_seat_title": "Ktoś dodał się do wydarzenia",
"variable": "Zmienna", "variable": "Zmienna",
"event_name_workflow": "Nazwa wydarzenia", "event_name_variable": "Nazwa wydarzenia",
"organizer_name_workflow": "Organizator", "organizer_name_variable": "Organizator",
"attendee_name_workflow": "Uczestnik", "attendee_name_variable": "Uczestnik",
"event_date_workflow": "Data wydarzenia", "event_date_variable": "Data wydarzenia",
"event_time_workflow": "Godzina wydarzenia", "event_time_variable": "Godzina wydarzenia",
"location_workflow": "Lokalizacja", "location_variable": "Lokalizacja",
"additional_notes_workflow": "Dodatkowe uwagi", "additional_notes_variable": "Dodatkowe uwagi",
"app_upgrade_description": "Aby korzystać z tej funkcji, musisz uaktualnić do konta Pro.", "app_upgrade_description": "Aby korzystać z tej funkcji, musisz uaktualnić do konta Pro.",
"invalid_number": "Nieprawidłowy numer telefonu", "invalid_number": "Nieprawidłowy numer telefonu",
"navigate": "Nawigacja", "navigate": "Nawigacja",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Ten adres URL jest już zajęty", "team_url_taken": "Ten adres URL jest już zajęty",
"team_publish": "Opublikuj zespół", "team_publish": "Opublikuj zespół",
"number_sms_notifications": "Numer telefonu (powiadomienia SMS)", "number_sms_notifications": "Numer telefonu (powiadomienia SMS)",
"attendee_email_workflow": "Adres e-mail uczestnika", "attendee_email_variable": "Adres e-mail uczestnika",
"attendee_email_info": "Adres e-mail osoby rezerwującej", "attendee_email_info": "Adres e-mail osoby rezerwującej",
"kbar_search_placeholder": "Wpisz polecenie lub wyszukaj...", "kbar_search_placeholder": "Wpisz polecenie lub wyszukaj...",
"invalid_credential": "O nie! Wygląda na to, że uprawnienia wygasły lub zostały cofnięte. Zainstaluj aplikację ponownie.", "invalid_credential": "O nie! Wygląda na to, że uprawnienia wygasły lub zostały cofnięte. Zainstaluj aplikację ponownie.",
@@ -511,7 +511,7 @@
"add_team_members_description": "Convidar outros para se juntar à sua equipe", "add_team_members_description": "Convidar outros para se juntar à sua equipe",
"add_team_member": "Adicionar membro da equipe", "add_team_member": "Adicionar membro da equipe",
"invite_new_member": "Convidar um novo membro", "invite_new_member": "Convidar um novo membro",
"invite_new_member_description": "Observação: isso <1>custará um assento extra ($15/m)</1> na sua assinatura.", "invite_new_member_description": "Observação: isso <1>custará um lugar extra ($15/m)</1> na sua assinatura.",
"invite_new_team_member": "Convidar alguém para seu time.", "invite_new_team_member": "Convidar alguém para seu time.",
"change_member_role": "Alterar função de membro da equipe", "change_member_role": "Alterar função de membro da equipe",
"disable_cal_branding": "Remover a marca {{appName}}", "disable_cal_branding": "Remover a marca {{appName}}",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Novo participante {{name}} no {{eventType}} em {{date}}", "new_seat_subject": "Novo participante {{name}} no {{eventType}} em {{date}}",
"new_seat_title": "Alguém se adicionou a um evento", "new_seat_title": "Alguém se adicionou a um evento",
"variable": "Variável", "variable": "Variável",
"event_name_workflow": "Nome do evento", "event_name_variable": "Nome do evento",
"organizer_name_workflow": "Nome do organizador", "organizer_name_variable": "Nome do organizador",
"attendee_name_workflow": "Nome do participante", "attendee_name_variable": "Nome do participante",
"event_date_workflow": "Data do evento", "event_date_variable": "Data do evento",
"event_time_workflow": "Horário do evento", "event_time_variable": "Horário do evento",
"location_workflow": "Local", "location_variable": "Local",
"additional_notes_workflow": "Observações adicionais", "additional_notes_variable": "Observações adicionais",
"app_upgrade_description": "Para usar este recurso, atualize para uma conta Pro.", "app_upgrade_description": "Para usar este recurso, atualize para uma conta Pro.",
"invalid_number": "Número de telefone inválido", "invalid_number": "Número de telefone inválido",
"navigate": "Navegar", "navigate": "Navegar",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Este URL já está em uso", "team_url_taken": "Este URL já está em uso",
"team_publish": "Publicar equipe", "team_publish": "Publicar equipe",
"number_sms_notifications": "Número de telefone (notificações de SMS)", "number_sms_notifications": "Número de telefone (notificações de SMS)",
"attendee_email_workflow": "E-mail do participante", "attendee_email_variable": "E-mail do participante",
"attendee_email_info": "O e-mail da pessoa que fez a reserva", "attendee_email_info": "O e-mail da pessoa que fez a reserva",
"kbar_search_placeholder": "Digite um comando ou procure...", "kbar_search_placeholder": "Digite um comando ou procure...",
"invalid_credential": "Ah, não! Parece que a permissão expirou ou foi revogada. Reinstale novamente.", "invalid_credential": "Ah, não! Parece que a permissão expirou ou foi revogada. Reinstale novamente.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Novo participante {{name}} em {{eventType}} em {{date}}", "new_seat_subject": "Novo participante {{name}} em {{eventType}} em {{date}}",
"new_seat_title": "Alguém se adicionou a um evento", "new_seat_title": "Alguém se adicionou a um evento",
"variable": "Variável", "variable": "Variável",
"event_name_workflow": "Nome do evento", "event_name_variable": "Nome do evento",
"organizer_name_workflow": "Organizador", "organizer_name_variable": "Organizador",
"attendee_name_workflow": "Participante", "attendee_name_variable": "Participante",
"event_date_workflow": "Data do evento", "event_date_variable": "Data do evento",
"event_time_workflow": "Hora do evento", "event_time_variable": "Hora do evento",
"location_workflow": "Localização", "location_variable": "Localização",
"additional_notes_workflow": "Notas Adicionais", "additional_notes_variable": "Notas Adicionais",
"app_upgrade_description": "Para usar esta funcionalidade, tem de actualizar para uma conta Pro.", "app_upgrade_description": "Para usar esta funcionalidade, tem de actualizar para uma conta Pro.",
"invalid_number": "Número de telefone inválido", "invalid_number": "Número de telefone inválido",
"navigate": "Navegar", "navigate": "Navegar",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Este endereço já está a ser utilizado", "team_url_taken": "Este endereço já está a ser utilizado",
"team_publish": "Publicar equipa", "team_publish": "Publicar equipa",
"number_sms_notifications": "Número de telefone (notificações SMS)", "number_sms_notifications": "Número de telefone (notificações SMS)",
"attendee_email_workflow": "E-mail do participante", "attendee_email_variable": "E-mail do participante",
"attendee_email_info": "O e-mail do responsável pela reserva", "attendee_email_info": "O e-mail do responsável pela reserva",
"kbar_search_placeholder": "Digite um comando ou pesquise...", "kbar_search_placeholder": "Digite um comando ou pesquise...",
"invalid_credential": "Oh não! Parece que a permissão expirou ou foi revogada. Por favor, reinstale novamente.", "invalid_credential": "Oh não! Parece que a permissão expirou ou foi revogada. Por favor, reinstale novamente.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Participant nou, {{name}}, la {{eventType}} de pe {{date}}", "new_seat_subject": "Participant nou, {{name}}, la {{eventType}} de pe {{date}}",
"new_seat_title": "Cineva s-a adăugat pe sine la un eveniment", "new_seat_title": "Cineva s-a adăugat pe sine la un eveniment",
"variable": "Variabilă", "variable": "Variabilă",
"event_name_workflow": "Denumire eveniment", "event_name_variable": "Denumire eveniment",
"organizer_name_workflow": "Organizator", "organizer_name_variable": "Organizator",
"attendee_name_workflow": "Participant", "attendee_name_variable": "Participant",
"event_date_workflow": "Dată eveniment", "event_date_variable": "Dată eveniment",
"event_time_workflow": "Oră eveniment", "event_time_variable": "Oră eveniment",
"location_workflow": "Loc", "location_variable": "Loc",
"additional_notes_workflow": "Note suplimentare", "additional_notes_variable": "Note suplimentare",
"app_upgrade_description": "Pentru a utiliza această caracteristică, trebuie să faceți upgrade la un cont Pro.", "app_upgrade_description": "Pentru a utiliza această caracteristică, trebuie să faceți upgrade la un cont Pro.",
"invalid_number": "Număr de telefon nevalid", "invalid_number": "Număr de telefon nevalid",
"navigate": "Navigare", "navigate": "Navigare",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Acest URL este folosit deja", "team_url_taken": "Acest URL este folosit deja",
"team_publish": "Publicați echipa", "team_publish": "Publicați echipa",
"number_sms_notifications": "Număr de telefon (notificări SMS)", "number_sms_notifications": "Număr de telefon (notificări SMS)",
"attendee_email_workflow": "E-mail participant", "attendee_email_variable": "E-mail participant",
"attendee_email_info": "E-mailul persoanei care efectuează rezervarea", "attendee_email_info": "E-mailul persoanei care efectuează rezervarea",
"kbar_search_placeholder": "Scrie o comandă sau caută...", "kbar_search_placeholder": "Scrie o comandă sau caută...",
"invalid_credential": "Vai, nu! Se pare că permisiunea a expirat sau a fost revocată. Reinstalați din nou.", "invalid_credential": "Vai, nu! Se pare că permisiunea a expirat sau a fost revocată. Reinstalați din nou.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{eventType}}, {{date}}: новый участник {{name}}", "new_seat_subject": "{{eventType}}, {{date}}: новый участник {{name}}",
"new_seat_title": "Кто-то добавился к событию", "new_seat_title": "Кто-то добавился к событию",
"variable": "Переменная", "variable": "Переменная",
"event_name_workflow": "Название события", "event_name_variable": "Название события",
"organizer_name_workflow": "Организатор", "organizer_name_variable": "Организатор",
"attendee_name_workflow": "Участник", "attendee_name_variable": "Участник",
"event_date_workflow": "Дата события", "event_date_variable": "Дата события",
"event_time_workflow": "Время события", "event_time_variable": "Время события",
"location_workflow": "Местоположение", "location_variable": "Местоположение",
"additional_notes_workflow": "Дополнительная информация", "additional_notes_variable": "Дополнительная информация",
"app_upgrade_description": "Чтобы использовать эту функцию, необходимо перейти на аккаунт Pro.", "app_upgrade_description": "Чтобы использовать эту функцию, необходимо перейти на аккаунт Pro.",
"invalid_number": "Неверный номер телефона", "invalid_number": "Неверный номер телефона",
"navigate": "Перейти", "navigate": "Перейти",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Этот URL-адрес уже занят", "team_url_taken": "Этот URL-адрес уже занят",
"team_publish": "Опубликовать команду", "team_publish": "Опубликовать команду",
"number_sms_notifications": "Номер телефона (SMS-уведомления)", "number_sms_notifications": "Номер телефона (SMS-уведомления)",
"attendee_email_workflow": "Электронная почта участника", "attendee_email_variable": "Электронная почта участника",
"attendee_email_info": "Электронная почта участника, на которого оформляется бронирование", "attendee_email_info": "Электронная почта участника, на которого оформляется бронирование",
"kbar_search_placeholder": "Введите команду или начните поиск...", "kbar_search_placeholder": "Введите команду или начните поиск...",
"invalid_credential": "Похоже, разрешение истекло или было отозвано. Необходима переустановка.", "invalid_credential": "Похоже, разрешение истекло или было отозвано. Необходима переустановка.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Novi učesnik {{name}} na {{eventType}} dana {{date}}", "new_seat_subject": "Novi učesnik {{name}} na {{eventType}} dana {{date}}",
"new_seat_title": "Neko se dodao na događaj", "new_seat_title": "Neko se dodao na događaj",
"variable": "Promenljiva", "variable": "Promenljiva",
"event_name_workflow": "Naziv događaja", "event_name_variable": "Naziv događaja",
"organizer_name_workflow": "Organizator", "organizer_name_variable": "Organizator",
"attendee_name_workflow": "Učesnik", "attendee_name_variable": "Učesnik",
"event_date_workflow": "Datum događaja", "event_date_variable": "Datum događaja",
"event_time_workflow": "Vreme događaja", "event_time_variable": "Vreme događaja",
"location_workflow": "Lokacija", "location_variable": "Lokacija",
"additional_notes_workflow": "Dodatne beleške", "additional_notes_variable": "Dodatne beleške",
"app_upgrade_description": "Da biste koristili ovu funkciju, morate izvršiti nadogradnju na Pro nalog.", "app_upgrade_description": "Da biste koristili ovu funkciju, morate izvršiti nadogradnju na Pro nalog.",
"invalid_number": "Neispravan broj telefona", "invalid_number": "Neispravan broj telefona",
"navigate": "Navigacija", "navigate": "Navigacija",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Ovaj URL je zauzet", "team_url_taken": "Ovaj URL je zauzet",
"team_publish": "Objavite tim", "team_publish": "Objavite tim",
"number_sms_notifications": "Broj telefona (obaveštenja putem SMS-a)", "number_sms_notifications": "Broj telefona (obaveštenja putem SMS-a)",
"attendee_email_workflow": "Imejl polaznika", "attendee_email_variable": "Imejl polaznika",
"attendee_email_info": "Imejl osobe koja rezerviše", "attendee_email_info": "Imejl osobe koja rezerviše",
"kbar_search_placeholder": "Unesite komandu ili pretražite...", "kbar_search_placeholder": "Unesite komandu ili pretražite...",
"invalid_credential": "O, ne! Izgleda da je dozvola istekla ili je povučena. Instalirajte ponovo.", "invalid_credential": "O, ne! Izgleda da je dozvola istekla ili je povučena. Instalirajte ponovo.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Ny deltagare {{name}} i {{eventType}} {{date}}", "new_seat_subject": "Ny deltagare {{name}} i {{eventType}} {{date}}",
"new_seat_title": "Någon har lagt till sig själv i en händelse", "new_seat_title": "Någon har lagt till sig själv i en händelse",
"variable": "Variabel", "variable": "Variabel",
"event_name_workflow": "Händelsenamn", "event_name_variable": "Händelsenamn",
"organizer_name_workflow": "Arrangör", "organizer_name_variable": "Arrangör",
"attendee_name_workflow": "Deltagare", "attendee_name_variable": "Deltagare",
"event_date_workflow": "Händelsedatum", "event_date_variable": "Händelsedatum",
"event_time_workflow": "Händelsetid", "event_time_variable": "Händelsetid",
"location_workflow": "Plats", "location_variable": "Plats",
"additional_notes_workflow": "Ytterligare inmatning", "additional_notes_variable": "Ytterligare inmatning",
"app_upgrade_description": "För att kunna använda den här funktionen måste du uppgradera till ett Pro-konto.", "app_upgrade_description": "För att kunna använda den här funktionen måste du uppgradera till ett Pro-konto.",
"invalid_number": "Ogiltigt telefonnummer", "invalid_number": "Ogiltigt telefonnummer",
"navigate": "Navigera", "navigate": "Navigera",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Denna URL används redan", "team_url_taken": "Denna URL används redan",
"team_publish": "Publicera team", "team_publish": "Publicera team",
"number_sms_notifications": "Telefonnummer (SMS-notiser)", "number_sms_notifications": "Telefonnummer (SMS-notiser)",
"attendee_email_workflow": "Deltagarens e-postadress", "attendee_email_variable": "Deltagarens e-postadress",
"attendee_email_info": "Personens e-postadress för bokning", "attendee_email_info": "Personens e-postadress för bokning",
"kbar_search_placeholder": "Skriv ett kommando eller sök...", "kbar_search_placeholder": "Skriv ett kommando eller sök...",
"invalid_credential": "Åh nej! Det verkar som om behörigheten löpt ut eller återkallats. Installera om igen.", "invalid_credential": "Åh nej! Det verkar som om behörigheten löpt ut eller återkallats. Installera om igen.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "{{date}} tarihindeki {{eventType}} etkinliği için Yeni Katılımcı ({{name}})", "new_seat_subject": "{{date}} tarihindeki {{eventType}} etkinliği için Yeni Katılımcı ({{name}})",
"new_seat_title": "Biri kendini bir etkinliğe ekledi", "new_seat_title": "Biri kendini bir etkinliğe ekledi",
"variable": "Değişken", "variable": "Değişken",
"event_name_workflow": "Etkinlik adı", "event_name_variable": "Etkinlik adı",
"organizer_name_workflow": "Düzenleyen Kişi", "organizer_name_variable": "Düzenleyen Kişi",
"attendee_name_workflow": "Katılımcı", "attendee_name_variable": "Katılımcı",
"event_date_workflow": "Etkinlik tarihi", "event_date_variable": "Etkinlik tarihi",
"event_time_workflow": "Etkinlik saati", "event_time_variable": "Etkinlik saati",
"location_workflow": "Konum", "location_variable": "Konum",
"additional_notes_workflow": "Ek notlar", "additional_notes_variable": "Ek notlar",
"app_upgrade_description": "Bu özelliği kullanmak için Pro hesabına geçmeniz gerekiyor.", "app_upgrade_description": "Bu özelliği kullanmak için Pro hesabına geçmeniz gerekiyor.",
"invalid_number": "Geçersiz telefon numarası", "invalid_number": "Geçersiz telefon numarası",
"navigate": "Gezin", "navigate": "Gezin",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Bu URL zaten kullanılıyor", "team_url_taken": "Bu URL zaten kullanılıyor",
"team_publish": "Ekibi yayınla", "team_publish": "Ekibi yayınla",
"number_sms_notifications": "Telefon numarası (SMS bildirimleri)", "number_sms_notifications": "Telefon numarası (SMS bildirimleri)",
"attendee_email_workflow": "Katılımcı e-postası", "attendee_email_variable": "Katılımcı e-postası",
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası", "attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
"kbar_search_placeholder": "Bir komut girin veya arayın...", "kbar_search_placeholder": "Bir komut girin veya arayın...",
"invalid_credential": "Hata! İznin süresi dolmuş veya iptal edilmiş gibi görünüyor. Lütfen yeniden yükleyin.", "invalid_credential": "Hata! İznin süresi dolmuş veya iptal edilmiş gibi görünüyor. Lütfen yeniden yükleyin.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Новий учасник: {{name}} – {{eventType}}, {{date}}", "new_seat_subject": "Новий учасник: {{name}} – {{eventType}}, {{date}}",
"new_seat_title": "Хтось додав себе в захід", "new_seat_title": "Хтось додав себе в захід",
"variable": "Змінна", "variable": "Змінна",
"event_name_workflow": "Назва заходу", "event_name_variable": "Назва заходу",
"organizer_name_workflow": "Організатор", "organizer_name_variable": "Організатор",
"attendee_name_workflow": "Учасник", "attendee_name_variable": "Учасник",
"event_date_workflow": "Дата заходу", "event_date_variable": "Дата заходу",
"event_time_workflow": "Час заходу", "event_time_variable": "Час заходу",
"location_workflow": "Розташування", "location_variable": "Розташування",
"additional_notes_workflow": "Додаткові примітки", "additional_notes_variable": "Додаткові примітки",
"app_upgrade_description": "Щоб користуватися цією функцією, потрібен обліковий запис Pro.", "app_upgrade_description": "Щоб користуватися цією функцією, потрібен обліковий запис Pro.",
"invalid_number": "Недійсний номер телефону", "invalid_number": "Недійсний номер телефону",
"navigate": "Перейти", "navigate": "Перейти",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Цю URL-адресу вже зайнято", "team_url_taken": "Цю URL-адресу вже зайнято",
"team_publish": "Опублікувати команду", "team_publish": "Опублікувати команду",
"number_sms_notifications": "Номер телефону (SMS-сповіщення)", "number_sms_notifications": "Номер телефону (SMS-сповіщення)",
"attendee_email_workflow": "Адреса ел. пошти учасника", "attendee_email_variable": "Адреса ел. пошти учасника",
"attendee_email_info": "Адреса ел. пошти особи, яка бронює", "attendee_email_info": "Адреса ел. пошти особи, яка бронює",
"kbar_search_placeholder": "Введіть команду або пошуковий запис…", "kbar_search_placeholder": "Введіть команду або пошуковий запис…",
"invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Перевстановіть додаток знову.", "invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Перевстановіть додаток знову.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Người tham gia mới tên {{name}} ở {{eventType}} vào {{date}}", "new_seat_subject": "Người tham gia mới tên {{name}} ở {{eventType}} vào {{date}}",
"new_seat_title": "Ai đó đã tự thêm chính họ vào một sự kiện", "new_seat_title": "Ai đó đã tự thêm chính họ vào một sự kiện",
"variable": "Biến số", "variable": "Biến số",
"event_name_workflow": "Tên sự kiện", "event_name_variable": "Tên sự kiện",
"organizer_name_workflow": "Tên nhà tổ chức", "organizer_name_variable": "Tên nhà tổ chức",
"attendee_name_workflow": "Tên người tham dự", "attendee_name_variable": "Tên người tham dự",
"event_date_workflow": "Ngày sự kiện", "event_date_variable": "Ngày sự kiện",
"event_time_workflow": "Thời gian sự kiện", "event_time_variable": "Thời gian sự kiện",
"location_workflow": "Vị trí", "location_variable": "Vị trí",
"additional_notes_workflow": "Ghi chú bổ sung", "additional_notes_variable": "Ghi chú bổ sung",
"app_upgrade_description": "Để sử dụng tính năng này, bạn cần nâng cấp lên tài khoản Pro.", "app_upgrade_description": "Để sử dụng tính năng này, bạn cần nâng cấp lên tài khoản Pro.",
"invalid_number": "Số điện thoại không hợp lệ", "invalid_number": "Số điện thoại không hợp lệ",
"navigate": "Điều hướng", "navigate": "Điều hướng",
@@ -1386,7 +1386,7 @@
"team_url_taken": "URL này đã có người lấy rồi", "team_url_taken": "URL này đã có người lấy rồi",
"team_publish": "Đăng nhóm", "team_publish": "Đăng nhóm",
"number_sms_notifications": "Số điện thoại (thông báo SMS)", "number_sms_notifications": "Số điện thoại (thông báo SMS)",
"attendee_email_workflow": "Email người tham dự", "attendee_email_variable": "Email người tham dự",
"attendee_email_info": "Email người tham gia lịch hẹn", "attendee_email_info": "Email người tham gia lịch hẹn",
"kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...", "kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...",
"invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.", "invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "{{date}} 的 {{eventType}} 有新参与者 {{name}}", "new_seat_subject": "{{date}} 的 {{eventType}} 有新参与者 {{name}}",
"new_seat_title": "有人将自己添加到活动中", "new_seat_title": "有人将自己添加到活动中",
"variable": "变量", "variable": "变量",
"event_name_workflow": "活动名称", "event_name_variable": "活动名称",
"organizer_name_workflow": "组织者", "organizer_name_variable": "组织者",
"attendee_name_workflow": "参与者", "attendee_name_variable": "参与者",
"event_date_workflow": "活动日期", "event_date_variable": "活动日期",
"event_time_workflow": "活动时间", "event_time_variable": "活动时间",
"location_workflow": "位置", "location_variable": "位置",
"additional_notes_workflow": "附加备注", "additional_notes_variable": "附加备注",
"app_upgrade_description": "要使用此功能,您需要升级到专业版帐户。", "app_upgrade_description": "要使用此功能,您需要升级到专业版帐户。",
"invalid_number": "电话号码无效", "invalid_number": "电话号码无效",
"navigate": "导航", "navigate": "导航",
@@ -1387,7 +1387,7 @@
"team_url_taken": "此链接已被占用", "team_url_taken": "此链接已被占用",
"team_publish": "发布团队", "team_publish": "发布团队",
"number_sms_notifications": "电话号码(短信通知)", "number_sms_notifications": "电话号码(短信通知)",
"attendee_email_workflow": "参与者电子邮件", "attendee_email_variable": "参与者电子邮件",
"attendee_email_info": "预约人的电子邮件", "attendee_email_info": "预约人的电子邮件",
"kbar_search_placeholder": "输入命令或搜索...", "kbar_search_placeholder": "输入命令或搜索...",
"invalid_credential": "哦不!看起来权限已过期或被撤销。请重新安装。", "invalid_credential": "哦不!看起来权限已过期或被撤销。请重新安装。",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{date}} 的 {{eventType}} 有新與會者 {{name}}", "new_seat_subject": "{{date}} 的 {{eventType}} 有新與會者 {{name}}",
"new_seat_title": "有人將自己加入活動中", "new_seat_title": "有人將自己加入活動中",
"variable": "變數", "variable": "變數",
"event_name_workflow": "活動名稱", "event_name_variable": "活動名稱",
"organizer_name_workflow": "主辦者姓名", "organizer_name_variable": "主辦者姓名",
"attendee_name_workflow": "與會者姓名", "attendee_name_variable": "與會者姓名",
"event_date_workflow": "活動日期", "event_date_variable": "活動日期",
"event_time_workflow": "活動時間", "event_time_variable": "活動時間",
"location_workflow": "地點", "location_variable": "地點",
"additional_notes_workflow": "備註", "additional_notes_variable": "備註",
"app_upgrade_description": "您必須升級至專業版帳號才能使用此功能。", "app_upgrade_description": "您必須升級至專業版帳號才能使用此功能。",
"invalid_number": "電話號碼無效", "invalid_number": "電話號碼無效",
"navigate": "導覽", "navigate": "導覽",
@@ -1386,7 +1386,7 @@
"team_url_taken": "已有人使用此網址", "team_url_taken": "已有人使用此網址",
"team_publish": "發佈團隊", "team_publish": "發佈團隊",
"number_sms_notifications": "電話號碼 (簡訊通知)", "number_sms_notifications": "電話號碼 (簡訊通知)",
"attendee_email_workflow": "與會者電子郵件", "attendee_email_variable": "與會者電子郵件",
"attendee_email_info": "預約人電子郵件", "attendee_email_info": "預約人電子郵件",
"kbar_search_placeholder": "輸入指令或搜尋…", "kbar_search_placeholder": "輸入指令或搜尋…",
"invalid_credential": "糟糕!權限似乎過期或遭到撤銷。請重新安裝。", "invalid_credential": "糟糕!權限似乎過期或遭到撤銷。請重新安裝。",
+1 -1
View File
@@ -6,4 +6,4 @@ if [ -z "${CODESPACES}" ]; then
exit 0 exit 0
fi fi
echo "echo \"🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev envionment.\"" >> ~/.bashrc echo "printf \"\n🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev environment.\n\"" >> ~/.bashrc
+3 -3
View File
@@ -114,9 +114,9 @@ const BaseAppFork = {
}; };
fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2)); fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2));
fs.writeFileSync( fs.writeFileSync(
`${appDirPath}/README.mdx`, `${appDirPath}/DESCRIPTION.md`,
fs fs
.readFileSync(`${appDirPath}/README.mdx`) .readFileSync(`${appDirPath}/DESCRIPTION.md`)
.toString() .toString()
.replace(/_DESCRIPTION_/g, appDescription) .replace(/_DESCRIPTION_/g, appDescription)
.replace(/_APP_DIR_/g, slug) .replace(/_APP_DIR_/g, slug)
@@ -189,7 +189,7 @@ const CreateApp = ({ noDbUpdate, slug = null, editMode = false }) => {
name: "appDescription", name: "appDescription",
type: "text", type: "text",
explainer: explainer:
"A detailed description of your app. You can later modify README.mdx to add slider and other components", "A detailed description of your app. You can later modify DESCRIPTION.md to add slider and other components",
}, },
{ {
label: "Category of App", label: "Category of App",
@@ -6,5 +6,4 @@ items:
- /api/app-store/_APP_DIR_/3.jpg - /api/app-store/_APP_DIR_/3.jpg
--- ---
{description} _DESCRIPTION_
+2 -4
View File
@@ -32,10 +32,8 @@ export default function AppCard({
<div className="p-4 text-sm sm:p-8"> <div className="p-4 text-sm sm:p-8">
<div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-0"> <div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-0">
{/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */} {/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */}
<Link href={"/apps/" + app.slug}> <Link href={"/apps/" + app.slug} className="mr-3 h-auto w-10 rounded-sm">
<a className="mr-3 h-auto w-10 rounded-sm"> <img className="w-full" src={app?.logo} alt={app?.name} />
<img className="w-full" src={app?.logo} alt={app?.name} />
</a>
</Link> </Link>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-base font-semibold leading-4 text-black">{app?.name}</span> <span className="text-base font-semibold leading-4 text-black">{app?.name}</span>
@@ -12,15 +12,17 @@ const AppCategoryNavigation = ({
containerClassname, containerClassname,
className, className,
fromAdmin, fromAdmin,
useQueryParam = false,
}: { }: {
baseURL: string; baseURL: string;
children: React.ReactNode; children: React.ReactNode;
containerClassname: string; containerClassname: string;
className?: string; className?: string;
fromAdmin?: boolean; fromAdmin?: boolean;
useQueryParam?: boolean;
}) => { }) => {
const [animationRef] = useAutoAnimate<HTMLDivElement>(); const [animationRef] = useAutoAnimate<HTMLDivElement>();
const appCategories = useMemo(() => getAppCategories(baseURL), [baseURL]); const appCategories = useMemo(() => getAppCategories(baseURL, useQueryParam), [baseURL, useQueryParam]);
return ( return (
<div className={classNames("flex flex-col p-2 md:p-0 xl:flex-row", className)}> <div className={classNames("flex flex-col p-2 md:p-0 xl:flex-row", className)}>
@@ -7,7 +7,7 @@ function getHref(baseURL: string, category: string, useQueryParam: boolean) {
return useQueryParam ? `${baseUrlParsed.toString()}` : `${baseURL}/${category}`; return useQueryParam ? `${baseUrlParsed.toString()}` : `${baseURL}/${category}`;
} }
const getAppCategories = (baseURL: string, useQueryParam = true) => { const getAppCategories = (baseURL: string, useQueryParam: boolean) => {
return [ return [
{ {
name: "calendar", name: "calendar",
@@ -20,7 +20,8 @@ type CustomUseMutationOptions =
type AddAppMutationData = { setupPending: boolean } | void; type AddAppMutationData = { setupPending: boolean } | void;
type UseAddAppMutationOptions = CustomUseMutationOptions & { type UseAddAppMutationOptions = CustomUseMutationOptions & {
onSuccess: (data: AddAppMutationData) => void; onSuccess?: (data: AddAppMutationData) => void;
installGoogleVideo?: boolean;
returnTo?: string; returnTo?: string;
}; };
@@ -42,6 +43,10 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
if (type?.endsWith("_other_calendar")) { if (type?.endsWith("_other_calendar")) {
type = type.split("_other_calendar")[0]; type = type.split("_other_calendar")[0];
} }
if (options?.installGoogleVideo && type !== "google_calendar")
throw new Error("Could not install Google Meet");
const state: IntegrationOAuthCallbackState = { const state: IntegrationOAuthCallbackState = {
returnTo: returnTo:
returnTo || returnTo ||
@@ -50,6 +55,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
{ variant: variables && variables.variant, slug: variables && variables.slug }, { variant: variables && variables.variant, slug: variables && variables.slug },
location.search location.search
), ),
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
}; };
const stateStr = encodeURIComponent(JSON.stringify(state)); const stateStr = encodeURIComponent(JSON.stringify(state));
const searchParams = `?state=${stateStr}`; const searchParams = `?state=${stateStr}`;
File renamed without changes.
@@ -13,6 +13,7 @@ export const InstallAppButtonMap = {
exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")), exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")),
exchangecalendar: dynamic(() => import("./exchangecalendar/components/InstallAppButton")), exchangecalendar: dynamic(() => import("./exchangecalendar/components/InstallAppButton")),
googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")), googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")),
googlevideo: dynamic(() => import("./googlevideo/components/InstallAppButton")),
hubspot: dynamic(() => import("./hubspot/components/InstallAppButton")), hubspot: dynamic(() => import("./hubspot/components/InstallAppButton")),
huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")), huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")),
jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")), jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")),
@@ -17,6 +17,7 @@ export const apiHandlers = {
ga4: import("./ga4/api"), ga4: import("./ga4/api"),
giphy: import("./giphy/api"), giphy: import("./giphy/api"),
googlecalendar: import("./googlecalendar/api"), googlecalendar: import("./googlecalendar/api"),
googlevideo: import("./googlevideo/api"),
hubspot: import("./hubspot/api"), hubspot: import("./hubspot/api"),
huddle01video: import("./huddle01video/api"), huddle01video: import("./huddle01video/api"),
jitsivideo: import("./jitsivideo/api"), jitsivideo: import("./jitsivideo/api"),
@@ -1,8 +1,8 @@
import { App_RoutingForms_Form } from "@prisma/client"; import { App_RoutingForms_Form } from "@prisma/client";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { getQueryBuilderConfig } from "../lib/getQueryBuilderConfig";
import isRouterLinkedField from "../lib/isRouterLinkedField"; import isRouterLinkedField from "../lib/isRouterLinkedField";
import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]";
import { SerializableForm, Response } from "../types/types"; import { SerializableForm, Response } from "../types/types";
type Props = { type Props = {
@@ -14,8 +14,8 @@ import {
} from "@calcom/types/AppGetServerSideProps"; } from "@calcom/types/AppGetServerSideProps";
import { import {
Alert, Alert,
Button,
Badge, Badge,
Button,
ButtonGroup, ButtonGroup,
Dialog, Dialog,
DialogClose, DialogClose,
@@ -35,9 +35,9 @@ import {
VerticalDivider, VerticalDivider,
} from "@calcom/ui"; } from "@calcom/ui";
import { RoutingPages } from "../lib/RoutingPages";
import { getSerializableForm } from "../lib/getSerializableForm"; import { getSerializableForm } from "../lib/getSerializableForm";
import { processRoute } from "../lib/processRoute"; import { processRoute } from "../lib/processRoute";
import { RoutingPages } from "../pages/route-builder/[...appPages]";
import { Response, Route, SerializableForm } from "../types/types"; import { Response, Route, SerializableForm } from "../types/types";
import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions"; import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions";
import FormInputFields from "./FormInputFields"; import FormInputFields from "./FormInputFields";
@@ -318,9 +318,7 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
return ( return (
<div key={router.id} className="mr-2"> <div key={router.id} className="mr-2">
<Link href={`/${appUrl}/route-builder/${router.id}`}> <Link href={`/${appUrl}/route-builder/${router.id}`}>
<a> <Badge variant="gray">{router.name}</Badge>
<Badge variant="gray">{router.name}</Badge>
</a>
</Link> </Link>
</div> </div>
); );
@@ -342,9 +340,7 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
return ( return (
<div key={router.id} className="mr-2"> <div key={router.id} className="mr-2">
<Link href={`/${appUrl}/route-builder/${router.id}`}> <Link href={`/${appUrl}/route-builder/${router.id}`}>
<a> <Badge variant="default">{router.name}</Badge>
<Badge variant="default">{router.name}</Badge>
</a>
</Link> </Link>
</div> </div>
); );
@@ -399,9 +395,12 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
<div className="font-bold ">{t("route_to")}:</div> <div className="font-bold ">{t("route_to")}:</div>
<div className="mt-2"> <div className="mt-2">
{RoutingPages.map((page) => { {RoutingPages.map((page) => {
if (page.value === decidedAction.type) { if (page.value !== decidedAction.type) return null;
return <div data-testid="test-routing-result-type">{page.label}</div>; return (
} <div key={page.value} data-testid="test-routing-result-type">
{page.label}
</div>
);
})} })}
:{" "} :{" "}
{decidedAction.type === "customPageMessage" ? ( {decidedAction.type === "customPageMessage" ? (
@@ -0,0 +1,3 @@
import QueryBuilderInitialConfig from "../components/react-awesome-query-builder/config/config";
export const InitialConfig = QueryBuilderInitialConfig;
@@ -0,0 +1,16 @@
import { LocalRoute } from "../types/types";
export const RoutingPages: { label: string; value: NonNullable<LocalRoute["action"]>["type"] }[] = [
{
label: "Custom Page",
value: "customPageMessage",
},
{
label: "External Redirect",
value: "externalRedirectUrl",
},
{
label: "Event Redirect",
value: "eventTypeRedirectUrl",
},
];
@@ -0,0 +1,63 @@
import { FieldTypes } from "../pages/form-edit/[...appPages]";
import { QueryBuilderUpdatedConfig, RoutingForm } from "../types/types";
import { InitialConfig } from "./InitialConfig";
export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) {
const fields: Record<
string,
{
label: string;
type: string;
valueSources: ["value"];
fieldSettings: {
listValues?: {
value: string;
title: string;
}[];
};
}
> = {};
form.fields?.forEach((field) => {
if ("routerField" in field) {
field = field.routerField;
}
if (FieldTypes.map((f) => f.value).includes(field.type)) {
const optionValues = field.selectText?.trim().split("\n");
const options = optionValues?.map((value) => {
const title = value;
return {
value,
title,
};
});
const widget = InitialConfig.widgets[field.type];
const widgetType = widget.type;
fields[field.id] = {
label: field.label,
type: widgetType,
valueSources: ["value"],
fieldSettings: {
listValues: options,
},
// preferWidgets: field.type === "textarea" ? ["textarea"] : [],
};
} else {
throw new Error("Unsupported field type:" + field.type);
}
});
const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } };
if (forReporting) {
delete initialConfigCopy.operators.is_empty;
delete initialConfigCopy.operators.is_not_empty;
initialConfigCopy.operators.__calReporting = true;
}
// You need to provide your own config. See below 'Config format'
const config: QueryBuilderUpdatedConfig = {
...initialConfigCopy,
fields: fields,
};
return config;
}
@@ -2,9 +2,9 @@ import jsonLogic from "json-logic-js";
import { Utils as QbUtils } from "react-awesome-query-builder"; import { Utils as QbUtils } from "react-awesome-query-builder";
import { z } from "zod"; import { z } from "zod";
import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]";
import { Response, Route, SerializableForm } from "../types/types"; import { Response, Route, SerializableForm } from "../types/types";
import { zodNonRouterRoute } from "../zod"; import { zodNonRouterRoute } from "../zod";
import { getQueryBuilderConfig } from "./getQueryBuilderConfig";
import { isFallbackRoute } from "./isFallbackRoute"; import { isFallbackRoute } from "./isFallbackRoute";
import isRouter from "./isRouter"; import isRouter from "./isRouter";
import { App_RoutingForms_Form } from ".prisma/client"; import { App_RoutingForms_Form } from ".prisma/client";
@@ -24,7 +24,7 @@ import SingleForm, {
import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config"; import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config";
import "../../components/react-awesome-query-builder/styles.css"; import "../../components/react-awesome-query-builder/styles.css";
import { JsonLogicQuery } from "../../jsonLogicToPrisma"; import { JsonLogicQuery } from "../../jsonLogicToPrisma";
import { getQueryBuilderConfig } from "../route-builder/[...appPages]"; import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig";
export { getServerSideProps }; export { getServerSideProps };
@@ -1,8 +1,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import { App_RoutingForms_Form } from "@prisma/client";
import Link from "next/link"; import Link from "next/link";
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder"; import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
// types // types
import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder"; import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
@@ -21,83 +20,19 @@ import {
import SingleForm, { import SingleForm, {
getServerSidePropsForSingleFormView as getServerSideProps, getServerSidePropsForSingleFormView as getServerSideProps,
} from "../../components/SingleForm"; } from "../../components/SingleForm";
import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config";
import "../../components/react-awesome-query-builder/styles.css"; import "../../components/react-awesome-query-builder/styles.css";
import { RoutingPages } from "../../lib/RoutingPages";
import { createFallbackRoute } from "../../lib/createFallbackRoute"; import { createFallbackRoute } from "../../lib/createFallbackRoute";
import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig";
import isRouter from "../../lib/isRouter"; import isRouter from "../../lib/isRouter";
import { GlobalRoute, LocalRoute, SerializableForm, SerializableRoute } from "../../types/types"; import { GlobalRoute, LocalRoute, QueryBuilderUpdatedConfig, SerializableRoute } from "../../types/types";
import { FieldTypes } from "../form-edit/[...appPages]";
export { getServerSideProps }; export { getServerSideProps };
type RoutingForm = SerializableForm<App_RoutingForms_Form>;
const InitialConfig = QueryBuilderInitialConfig;
const hasRules = (route: Route) => { const hasRules = (route: Route) => {
if (isRouter(route)) return false; if (isRouter(route)) return false;
route.queryValue.children1 && Object.keys(route.queryValue.children1).length; route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
}; };
type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Config["fields"] };
export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) {
const fields: Record<
string,
{
label: string;
type: string;
valueSources: ["value"];
fieldSettings: {
listValues?: {
value: string;
title: string;
}[];
};
}
> = {};
form.fields?.forEach((field) => {
if ("routerField" in field) {
field = field.routerField;
}
if (FieldTypes.map((f) => f.value).includes(field.type)) {
const optionValues = field.selectText?.trim().split("\n");
const options = optionValues?.map((value) => {
const title = value;
return {
value,
title,
};
});
const widget = InitialConfig.widgets[field.type];
const widgetType = widget.type;
fields[field.id] = {
label: field.label,
type: widgetType,
valueSources: ["value"],
fieldSettings: {
listValues: options,
},
// preferWidgets: field.type === "textarea" ? ["textarea"] : [],
};
} else {
throw new Error("Unsupported field type:" + field.type);
}
});
const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } };
if (forReporting) {
delete initialConfigCopy.operators.is_empty;
delete initialConfigCopy.operators.is_not_empty;
initialConfigCopy.operators.__calReporting = true;
}
// You need to provide your own config. See below 'Config format'
const config: QueryBuilderUpdatedConfig = {
...initialConfigCopy,
fields: fields,
};
return config;
}
const getEmptyRoute = (): Exclude<SerializableRoute, GlobalRoute> => { const getEmptyRoute = (): Exclude<SerializableRoute, GlobalRoute> => {
const uuid = QbUtils.uuid(); const uuid = QbUtils.uuid();
return { return {
@@ -122,21 +57,6 @@ type Route =
}) })
| GlobalRoute; | GlobalRoute;
export const RoutingPages: { label: string; value: NonNullable<LocalRoute["action"]>["type"] }[] = [
{
label: "Custom Page",
value: "customPageMessage",
},
{
label: "External Redirect",
value: "externalRedirectUrl",
},
{
label: "Event Redirect",
value: "eventTypeRedirectUrl",
},
];
const Route = ({ const Route = ({
route, route,
routes, routes,
@@ -213,11 +133,9 @@ const Route = ({
className="mb-6"> className="mb-6">
<div className="-mt-3"> <div className="-mt-3">
<Link href={`/${appUrl}/route-builder/${route.id}`}> <Link href={`/${appUrl}/route-builder/${route.id}`}>
<a> <Badge variant="gray">
<Badge variant="gray"> <span className="font-semibold">{route.name}</span>
<span className="font-semibold">{route.name}</span> </Badge>
</Badge>
</a>
</Link> </Link>
<p className="mt-2 text-sm text-gray-500"> <p className="mt-2 text-sm text-gray-500">
Fields available in <span className="font-bold">{route.name}</span> will be added to this form. Fields available in <span className="font-bold">{route.name}</span> will be added to this form.
Loaded 100 of 247 files, more files were not shown because too many files have changed in this diff. Show more