Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66d47974cb | ||
|
|
3556dda252 | ||
|
|
c2e91b8378 | ||
|
|
4fe69b5864 | ||
|
|
95ce3b215d | ||
|
|
007f3319a0 | ||
|
|
9441da9d4e | ||
|
|
1510740cb2 | ||
|
|
8da68cc1fe | ||
|
|
6dbc8dd820 | ||
|
|
4ada428bcb | ||
|
|
6adaf0d3e8 | ||
|
|
c9e6acfa69 | ||
|
|
2272da5ee7 | ||
|
|
79b3875af3 | ||
|
|
d16b6fce8f | ||
|
|
40b35bdbe4 | ||
|
|
0af76ec5ab | ||
|
|
f7b20da640 | ||
|
|
eedf6913b8 | ||
|
|
78393a2b8b | ||
|
|
b3d64104b0 | ||
|
|
9266ba1e48 | ||
|
|
ec212e4317 | ||
|
|
02d4f0542a | ||
|
|
0aae07ae2f | ||
|
|
80ab6c9c98 | ||
|
|
86ba0766fc | ||
|
|
0adde17f38 | ||
|
|
9e4700f9df | ||
|
|
f4a87c66b3 | ||
|
|
0c06f1f6cb | ||
|
|
b731dfd421 | ||
|
|
0b704b9ed4 | ||
|
|
a0ece0d77e | ||
|
|
7839381911 | ||
|
|
6a976f5b42 | ||
|
|
8718905474 | ||
|
|
ff968d3867 | ||
|
|
8f5e6370b0 | ||
|
|
0a0c400333 | ||
|
|
0eb05aa540 | ||
|
|
efe18ebdcd | ||
|
|
ce097b89ba | ||
|
|
bb0e6389fa | ||
|
|
19a7cd1a50 | ||
|
|
49bcd5fec7 | ||
|
|
000a6e0d35 | ||
|
|
52936f60b4 | ||
|
|
2d6eebcd8a | ||
|
|
cfa6a5b996 | ||
|
|
7413ba6aa0 | ||
|
|
e9cbe5d353 | ||
|
|
d8bc9acbbc | ||
|
|
4f0e843ac6 | ||
|
|
02f1c50bb2 | ||
|
|
5952a38ab9 | ||
|
|
e5fb19163e | ||
|
|
79f34a4fc7 | ||
|
|
3e90c08d55 | ||
|
|
697d8eca43 | ||
|
|
5cecf3104f | ||
|
|
a3a6caa6dd | ||
|
|
d3586c9039 | ||
|
|
71eafa287a | ||
|
|
828ef8580e | ||
|
|
a348c3a20a | ||
|
|
3c68c7f7cb | ||
|
|
820b2da16d | ||
|
|
6bdb2b94eb | ||
|
|
3c3db0b3a6 | ||
|
|
4748641c22 | ||
|
|
804787f2a3 | ||
|
|
b7c3b31e0c | ||
|
|
ab6781cd72 | ||
|
|
b070cfb227 | ||
|
|
5acb3a09e5 | ||
|
|
69c9f690cb | ||
|
|
d2a12ea834 | ||
|
|
16f24b4831 | ||
|
|
a3eeeab259 | ||
|
|
f69c254fbc | ||
|
|
f0f6bd4846 | ||
|
|
8816a18854 | ||
|
|
4e529e5c6c | ||
|
|
e521bf6f0b | ||
|
|
a91a0f86e0 | ||
|
|
f654003f95 | ||
|
|
4cc8a6fbf4 | ||
|
|
5167608404 | ||
|
|
9c93685431 | ||
|
|
a380087c9b | ||
|
|
3559e0d082 | ||
|
|
4323b0c2d1 | ||
|
|
3a800f7511 | ||
|
|
c203e5a38b | ||
|
|
3f5b3f06a2 | ||
|
|
d52635efbb | ||
|
|
4add4df08e | ||
|
|
df6d7e8df7 | ||
|
|
dc61640118 | ||
|
|
361108246e | ||
|
|
a5e25b9b28 | ||
|
|
ccff528765 | ||
|
|
24a1ef6721 | ||
|
|
b89919129e | ||
|
|
cbf3edefc7 | ||
|
|
909ae5f2fb | ||
|
|
0bbe5173dc | ||
|
|
84ad133077 | ||
|
|
43647a797f | ||
|
|
f469a9c127 | ||
|
|
212f7a374f |
No files matched your search
@@ -26,11 +26,13 @@ Fixes # (issue)
|
|||||||
- [ ] Test B
|
- [ ] Test B
|
||||||
|
|
||||||
## Mandatory Tasks
|
## Mandatory Tasks
|
||||||
|
|
||||||
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
|
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
<!-- Please remove all the irrelevant bullets to your PR -->
|
<!-- Please remove all the irrelevant bullets to your PR -->
|
||||||
|
|
||||||
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
|
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
|
||||||
- My code doesn't follow the style guidelines of this project
|
- My code doesn't follow the style guidelines of this project
|
||||||
- I haven't commented my code, particularly in hard-to-understand areas
|
- I haven't commented my code, particularly in hard-to-understand areas
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v7
|
- uses: actions/stale@v7
|
||||||
with:
|
with:
|
||||||
|
ascending: true
|
||||||
days-before-issue-stale: 60
|
days-before-issue-stale: 60
|
||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
days-before-pr-stale: 14
|
days-before-pr-stale: 14
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ env:
|
|||||||
# INPUT_ENV_MS_GRAPH_CLIENT_SECRET: xxx
|
# INPUT_ENV_MS_GRAPH_CLIENT_SECRET: xxx
|
||||||
# INPUT_ENV_ZOOM_CLIENT_ID: xxx
|
# INPUT_ENV_ZOOM_CLIENT_ID: xxx
|
||||||
# INPUT_ENV_ZOOM_CLIENT_SECRET: xxx
|
# INPUT_ENV_ZOOM_CLIENT_SECRET: xxx
|
||||||
|
INPUT_ENV_SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
INPUT_ENV_SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
INPUT_ENV_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
INPUT_ENV_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
INPUT_ENV_TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
INPUT_ENV_TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,15 @@ jobs:
|
|||||||
app-store:
|
app-store:
|
||||||
- 'apps/web/**'
|
- 'apps/web/**'
|
||||||
- 'packages/app-store/**'
|
- 'packages/app-store/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
embed:
|
embed:
|
||||||
- 'apps/web/**'
|
- 'apps/web/**'
|
||||||
- 'packages/embeds/**'
|
- 'packages/embeds/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
embed-react:
|
embed-react:
|
||||||
- 'apps/web/**'
|
- 'apps/web/**'
|
||||||
- 'packages/embeds/**'
|
- 'packages/embeds/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
name: Create env file
|
name: Create env file
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ on:
|
|||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate-pr:
|
validate-pr:
|
||||||
@@ -17,5 +17,31 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v5
|
- uses: amannn/action-semantic-pull-request@v5
|
||||||
|
id: lint_pr_title
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: marocchino/sticky-pull-request-comment@v2
|
||||||
|
# When the previous steps fails, the workflow would stop. By adding this
|
||||||
|
# condition you can continue the execution with the populated error message.
|
||||||
|
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||||
|
with:
|
||||||
|
header: pr-title-lint-error
|
||||||
|
message: |
|
||||||
|
Hey there and thank you for opening this pull request! 👋🏼
|
||||||
|
|
||||||
|
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
|
||||||
|
```
|
||||||
|
${{ steps.lint_pr_title.outputs.error_message }}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Delete a previous comment when the issue has been resolved
|
||||||
|
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||||
|
uses: marocchino/sticky-pull-request-comment@v2
|
||||||
|
with:
|
||||||
|
header: pr-title-lint-error
|
||||||
|
message: |
|
||||||
|
Thank you for following the naming conventions! 🙏
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: "Welcome new contributors"
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: opened
|
||||||
|
pull_request:
|
||||||
|
types: opened
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
welcome-message:
|
||||||
|
name: Welcoming New Users
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: semantics
|
||||||
|
if: github.event.action == 'opened'
|
||||||
|
steps:
|
||||||
|
- uses: actions/first-interaction@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
pr-message: |-
|
||||||
|
Thank you for making your first Pull Request and taking the time to improve Cal.com ! ❤️🎉
|
||||||
|
Feel free to join the conversation at [slack](https://calendso.slack.com/)
|
||||||
|
issue-message: |
|
||||||
|
Thank you for opening your first issue, one of our team members will review it as soon as it possible. ❤️🎉
|
||||||
Vendored
+2
-1
@@ -8,6 +8,7 @@
|
|||||||
"ban.spellright", // Spell check for docs
|
"ban.spellright", // Spell check for docs
|
||||||
"stripe.vscode-stripe", // stripe VSCode extension
|
"stripe.vscode-stripe", // stripe VSCode extension
|
||||||
"Prisma.prisma", // syntax|format|completion for prisma
|
"Prisma.prisma", // syntax|format|completion for prisma
|
||||||
"rebornix.project-snippets" // Share useful snippets between collaborators
|
"rebornix.project-snippets", // Share useful snippets between collaborators
|
||||||
|
"inlang.vs-code-extension" // improved i18n DX
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
|
|||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
|
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee and https://github.com/calcom/cal.com/tree/main/apps/api/ directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
|
||||||
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
|
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
|
||||||
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||||
|
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ Here is what you need to be able to run Cal.com.
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/calcom/cal.com.git
|
git clone https://github.com/calcom/cal.com.git
|
||||||
```
|
```
|
||||||
> If you are on windows, run the following command on `gitbash` with admin privileges: <br>
|
|
||||||
```git clone -c core.symlinks=true https://github.com/calcom/cal.com.git``` <br>
|
> If you are on windows, run the following command on `gitbash` with admin privileges: <br> > `git clone -c core.symlinks=true https://github.com/calcom/cal.com.git` <br>
|
||||||
See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
|
> See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
|
||||||
|
|
||||||
1. Go to the project folder
|
1. Go to the project folder
|
||||||
|
|
||||||
@@ -158,6 +158,7 @@ yarn dx
|
|||||||
```sh
|
```sh
|
||||||
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Gitpod Setup
|
#### Gitpod Setup
|
||||||
|
|
||||||
1. Click the button below to open this project in Gitpod.
|
1. Click the button below to open this project in Gitpod.
|
||||||
@@ -166,8 +167,6 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/calcom/cal.com)
|
[](https://gitpod.io/#https://github.com/calcom/cal.com)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Manual setup
|
#### Manual setup
|
||||||
|
|
||||||
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
|
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
|
||||||
@@ -175,6 +174,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||
```
|
```
|
||||||
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
|
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick local DB</summary>
|
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick local DB</summary>
|
||||||
|
|
||||||
@@ -188,11 +188,12 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||

|

|
||||||
|
|
||||||
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
||||||
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
||||||
|
|
||||||
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
|
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
|
||||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
@@ -212,6 +213,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||
```sh
|
```sh
|
||||||
yarn workspace @calcom/prisma db-deploy
|
yarn workspace @calcom/prisma db-deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -319,7 +321,7 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
|||||||
### Heroku
|
### Heroku
|
||||||
|
|
||||||
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
|
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
|
||||||
<img width="185px" height="auto" src="https://herokucdn.com/deploy/button.svg" alt="Deploy">
|
<img width="185px" height="auto" src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
### Railway
|
### Railway
|
||||||
@@ -490,7 +492,9 @@ following
|
|||||||
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
|
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
|
||||||
|
|
||||||
### Obtaining Zoho Bigin Client ID and Secret
|
### Obtaining Zoho Bigin Client ID and Secret
|
||||||
|
|
||||||
[Follow these steps](./packages/app-store/zoho-bigin/)
|
[Follow these steps](./packages/app-store/zoho-bigin/)
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
### Setting up SendGrid for Email reminders
|
### Setting up SendGrid for Email reminders
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
The Cal.com Commercial License (the “Commercial License”)
|
||||||
|
Copyright (c) 2020-present Cal.com, Inc
|
||||||
|
|
||||||
|
With regard to the Cal.com Software:
|
||||||
|
|
||||||
|
This software and associated documentation files (the "Software") may only be
|
||||||
|
used in production, if you (and any entity that you represent) have agreed to,
|
||||||
|
and are in compliance with, the Cal.com Subscription Terms available
|
||||||
|
at https://cal.com/terms, or other agreements governing
|
||||||
|
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
|
||||||
|
and otherwise have a valid Cal.com Enterprise Edition subscription ("Commercial Subscription")
|
||||||
|
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
|
||||||
|
you are free to modify this Software and publish patches to the Software. You agree
|
||||||
|
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
|
||||||
|
and to all such modifications and/or patches, and all such modifications and/or
|
||||||
|
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||||
|
exploited with a valid Commercial Subscription for the correct number of hosts.
|
||||||
|
Notwithstanding the foregoing, you may copy and modify the Software for development
|
||||||
|
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
|
||||||
|
its licensors (as applicable) retain all right, title and interest in and to all such
|
||||||
|
modifications. You are not granted any other rights beyond what is expressly stated herein.
|
||||||
|
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell the Software.
|
||||||
|
|
||||||
|
This Commercial License applies only to the part of this Software that is not distributed under
|
||||||
|
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
|
||||||
|
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
|
||||||
|
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
|
||||||
|
in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall
|
||||||
|
be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
For all third party components incorporated into the Cal.com Software, those
|
||||||
|
components are licensed under the original license provided by the owner of the
|
||||||
|
applicable component.
|
||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
|
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Cal.com Public API
|
# Commercial Cal.com Public API
|
||||||
|
|
||||||
Welcome to the Public API ("/apps/api") of the Cal.com.
|
Welcome to the Public API ("/apps/api") of the Cal.com.
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"@sentry/nextjs": "^7.20.0",
|
"@sentry/nextjs": "^7.20.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "~13.2.1",
|
"next": "^13.4.6",
|
||||||
"next-api-middleware": "^1.0.1",
|
"next-api-middleware": "^1.0.1",
|
||||||
"next-axiom": "^0.16.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-swagger-doc": "^0.3.6",
|
"next-swagger-doc": "^0.3.6",
|
||||||
"next-validations": "^0.2.0",
|
"next-validations": "^0.2.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||||||
* status:
|
* status:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]'
|
* description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]'
|
||||||
|
* description:
|
||||||
|
* type: string
|
||||||
|
* description: 'Description of the meeting'
|
||||||
* examples:
|
* examples:
|
||||||
* editBooking:
|
* editBooking:
|
||||||
* value:
|
* value:
|
||||||
@@ -79,7 +82,15 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||||||
* "startTime": "2023-05-24T13:00:00.000Z",
|
* "startTime": "2023-05-24T13:00:00.000Z",
|
||||||
* "endTime": "2023-05-24T13:30:00.000Z",
|
* "endTime": "2023-05-24T13:30:00.000Z",
|
||||||
* "metadata": {},
|
* "metadata": {},
|
||||||
* "status": "CANCELLED"
|
* "status": "CANCELLED",
|
||||||
|
* "responses": {
|
||||||
|
* "email": "john.doe@example.com",
|
||||||
|
* "name": "John Doe",
|
||||||
|
* "location": {
|
||||||
|
* "optionValue": "",
|
||||||
|
* "value": "inPerson"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* 400:
|
* 400:
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
* required:
|
* required:
|
||||||
* - eventTypeId
|
* - eventTypeId
|
||||||
* - start
|
* - start
|
||||||
* - name
|
* - responses
|
||||||
* - email
|
|
||||||
* - timeZone
|
* - timeZone
|
||||||
* - language
|
* - language
|
||||||
* - metadata
|
* - metadata
|
||||||
* - customInputs
|
|
||||||
* - location
|
|
||||||
* properties:
|
* properties:
|
||||||
* eventTypeId:
|
* eventTypeId:
|
||||||
* type: integer
|
* type: integer
|
||||||
@@ -45,29 +42,33 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
* description: 'End time of the Event'
|
* description: 'End time of the Event'
|
||||||
* name:
|
* responses:
|
||||||
* type: string
|
* type: object
|
||||||
* description: 'Name of the Attendee'
|
* required:
|
||||||
* email:
|
* - name
|
||||||
* type: string
|
* - email
|
||||||
* format: email
|
* - location
|
||||||
* description: 'Email ID of the Attendee'
|
* properties:
|
||||||
|
* name:
|
||||||
|
* type: string
|
||||||
|
* description: 'Attendee full name'
|
||||||
|
* email:
|
||||||
|
* type: string
|
||||||
|
* format: email
|
||||||
|
* description: 'Attendee email address'
|
||||||
|
* location:
|
||||||
|
* type: string
|
||||||
|
* description: 'Meeting location'
|
||||||
|
* metadata:
|
||||||
|
* type: object
|
||||||
|
* properties: {}
|
||||||
|
* description: 'Any metadata associated with the booking'
|
||||||
* timeZone:
|
* timeZone:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 'TimeZone of the Attendee'
|
* description: 'TimeZone of the Attendee'
|
||||||
* language:
|
* language:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 'Language of the Attendee'
|
* description: 'Language of the Attendee'
|
||||||
* metadata:
|
|
||||||
* type: object
|
|
||||||
* properties: {}
|
|
||||||
* description: 'Any metadata associated with the booking'
|
|
||||||
* customInputs:
|
|
||||||
* type: array
|
|
||||||
* items: {}
|
|
||||||
* location:
|
|
||||||
* type: string
|
|
||||||
* description: 'Meeting location'
|
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: 'Booking event title'
|
* description: 'Booking event title'
|
||||||
@@ -96,13 +97,14 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
* "eventTypeId": 2323232,
|
* "eventTypeId": 2323232,
|
||||||
* "start": "2023-05-24T13:00:00.000Z",
|
* "start": "2023-05-24T13:00:00.000Z",
|
||||||
* "end": "2023-05-24T13:30:00.000Z",
|
* "end": "2023-05-24T13:30:00.000Z",
|
||||||
* "name": "Hello Hello",
|
* "responses":{
|
||||||
* "email": "hello@gmail.com",
|
* "name": "Hello Hello",
|
||||||
|
* "email": "hello@gmail.com",
|
||||||
|
* "metadata": {},
|
||||||
|
* "location": "Calcom HQ",
|
||||||
|
* },
|
||||||
* "timeZone": "Europe/London",
|
* "timeZone": "Europe/London",
|
||||||
* "language": "en",
|
* "language": "en",
|
||||||
* "metadata": {},
|
|
||||||
* "customInputs": [],
|
|
||||||
* "location": "Calcom HQ",
|
|
||||||
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
|
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
|
||||||
* "description": null,
|
* "description": null,
|
||||||
* "status": "PENDING",
|
* "status": "PENDING",
|
||||||
@@ -117,53 +119,50 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* examples:
|
* examples:
|
||||||
* bookings:
|
* booking created successfully example:
|
||||||
* value:
|
* value:
|
||||||
* {
|
* {
|
||||||
* "id": 11223344,
|
* "booking": {
|
||||||
* "uid": "5yUjmAYTDF6MXo98re8SkX",
|
* "id": 91,
|
||||||
* "userId": 123,
|
* "userId": 5,
|
||||||
* "eventTypeId": 2323232,
|
* "description": "",
|
||||||
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
|
* "eventTypeId": 7,
|
||||||
* "description": null,
|
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
|
||||||
* "customInputs": {},
|
* "title": "60min between Pro Example and John Doe",
|
||||||
* "responses": null,
|
* "startTime": "2023-05-25T09:30:00.000Z",
|
||||||
* "startTime": "2023-05-24T13:00:00.000Z",
|
* "endTime": "2023-05-25T10:30:00.000Z",
|
||||||
* "endTime": "2023-05-24T13:30:00.000Z",
|
* "attendees": [
|
||||||
* "location": "Calcom HQ",
|
* {
|
||||||
* "createdAt": "2023-04-19T10:17:58.580Z",
|
* "email": "john.doe@example.com",
|
||||||
* "updatedAt": null,
|
* "name": "John Doe",
|
||||||
* "status": "PENDING",
|
* "timeZone": "Asia/Kolkata",
|
||||||
* "paid": false,
|
* "locale": "en"
|
||||||
* "destinationCalendarId": 2180,
|
* }
|
||||||
* "cancellationReason": null,
|
* ],
|
||||||
* "rejectionReason": null,
|
* "user": {
|
||||||
* "dynamicEventSlugRef": null,
|
* "email": "pro@example.com",
|
||||||
* "dynamicGroupSlugRef": null,
|
* "name": "Pro Example",
|
||||||
* "rescheduled": null,
|
* "timeZone": "Asia/Kolkata",
|
||||||
* "fromReschedule": null,
|
* "locale": "en"
|
||||||
* "recurringEventId": null,
|
* },
|
||||||
* "smsReminderNumber": null,
|
* "payment": [
|
||||||
* "scheduledJobs": [],
|
* {
|
||||||
* "metadata": {},
|
* "id": 1,
|
||||||
* "isRecorded": false,
|
* "success": true,
|
||||||
* "user": {
|
* "paymentOption": "ON_BOOKING"
|
||||||
* "email": "test@cal.com",
|
* }
|
||||||
* "name": "Syed Ali Shahbaz",
|
* ],
|
||||||
* "timeZone": "Asia/Calcutta"
|
* "metadata": {},
|
||||||
* },
|
* "status": "ACCEPTED",
|
||||||
* "attendees": [
|
* "responses": {
|
||||||
* {
|
* "email": "john.doe@example.com",
|
||||||
* "id": 12345,
|
* "name": "John Doe",
|
||||||
* "email": "hello@gmail.com",
|
* "location": {
|
||||||
* "name": "Hello Hello",
|
* "optionValue": "",
|
||||||
* "timeZone": "Europe/London",
|
* "value": "inPerson"
|
||||||
* "locale": "en",
|
* }
|
||||||
* "bookingId": 11223344
|
|
||||||
* }
|
* }
|
||||||
* ],
|
* }
|
||||||
* "payment": [],
|
|
||||||
* "references": []
|
|
||||||
* }
|
* }
|
||||||
* 400:
|
* 400:
|
||||||
* description: |
|
* description: |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CAL_URL } from "@calcom/lib/constants";
|
|||||||
|
|
||||||
export default function getCalLink(eventType: {
|
export default function getCalLink(eventType: {
|
||||||
team?: { slug: string | null } | null;
|
team?: { slug: string | null } | null;
|
||||||
owner: { username: string | null } | null;
|
owner?: { username: string | null } | null;
|
||||||
users?: { username: string | null }[];
|
users?: { username: string | null }[];
|
||||||
slug: string;
|
slug: string;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ export function VariantsTable({
|
|||||||
<table>
|
<table>
|
||||||
<RowTitles titles={titles} />
|
<RowTitles titles={titles} />
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<tr className="p-2 pr-6 pb-6" key={column.props.variant}>
|
<tr className="p-2 pb-6 pr-6" key={column.props.variant}>
|
||||||
<th
|
<th
|
||||||
className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]"
|
className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]"
|
||||||
key={column.props.variant}>
|
key={column.props.variant}>
|
||||||
{column.props.variant}
|
{column.props.variant}
|
||||||
</th>
|
</th>
|
||||||
{React.Children.count(column.props.children) &&
|
{React.Children.count(column.props.children) &&
|
||||||
React.Children.map(column.props.children, (cell) => (
|
React.Children.map(column.props.children, (cell) => (
|
||||||
<td className="p-2 pr-6 pb-6" style={{ minWidth: `${columnMinWidth}px` }}>
|
<td className="p-2 pb-6 pr-6" style={{ minWidth: `${columnMinWidth}px` }}>
|
||||||
{cell}
|
{cell}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
@@ -70,9 +70,9 @@ export function VariantRow({ children }: RowProps) {
|
|||||||
export function RowTitles({ titles }: { titles: string[] }) {
|
export function RowTitles({ titles }: { titles: string[] }) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" />
|
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" />
|
||||||
{titles.map((title) => (
|
{titles.map((title) => (
|
||||||
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
|
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
|
||||||
{title}
|
{title}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
"@radix-ui/react-avatar": "^1.0.0",
|
"@radix-ui/react-avatar": "^1.0.0",
|
||||||
"@radix-ui/react-collapsible": "^1.0.0",
|
"@radix-ui/react-collapsible": "^1.0.0",
|
||||||
"@radix-ui/react-dialog": "^1.0.0",
|
"@radix-ui/react-dialog": "^1.0.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-id": "^1.0.0",
|
"@radix-ui/react-id": "^1.0.0",
|
||||||
"@radix-ui/react-popover": "^1.0.2",
|
"@radix-ui/react-popover": "^1.0.2",
|
||||||
"@radix-ui/react-radio-group": "^1.0.0",
|
"@radix-ui/react-radio-group": "^1.0.0",
|
||||||
"@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": "^13.2.1",
|
"next": "^13.4.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"storybook-addon-rtl-direction": "^0.0.19"
|
"storybook-addon-rtl-direction": "^0.0.19"
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
"@storybook/react": "^6.5.13",
|
"@storybook/react": "^6.5.13",
|
||||||
"@storybook/testing-library": "^0.0.13",
|
"@storybook/testing-library": "^0.0.13",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@vitejs/plugin-react": "^2.1.0",
|
"@vitejs/plugin-react": "^2.2.0",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"storybook-addon-designs": "^6.3.1",
|
"storybook-addon-designs": "^6.3.1",
|
||||||
"storybook-addon-next": "^1.6.9",
|
"storybook-addon-next": "^1.6.9",
|
||||||
"storybook-react-i18next": "^1.1.2",
|
"storybook-react-i18next": "^1.1.2",
|
||||||
"tailwindcss": "^3.2.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^4.1.2"
|
"vite": "^4.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,7 @@
|
|||||||
--cal-bg-success: #E2FBE8;
|
--cal-bg-success: #E2FBE8;
|
||||||
--cal-bg-attention: #FCEED8;
|
--cal-bg-attention: #FCEED8;
|
||||||
--cal-bg-error: #F9E3E2;
|
--cal-bg-error: #F9E3E2;
|
||||||
|
--cal-bg-dark-error: #752522;
|
||||||
|
|
||||||
|
|
||||||
/* Borders */
|
/* Borders */
|
||||||
@@ -216,6 +217,7 @@
|
|||||||
--cal-border: #D1D5DB;
|
--cal-border: #D1D5DB;
|
||||||
--cal-border-subtle:#E5E7EB;
|
--cal-border-subtle:#E5E7EB;
|
||||||
--cal-border-muted:#F3F4F6;
|
--cal-border-muted:#F3F4F6;
|
||||||
|
--cal-border-error: #aa2e26;
|
||||||
|
|
||||||
/* Content/Text */
|
/* Content/Text */
|
||||||
--cal-text-emphasis: #111827;
|
--cal-text-emphasis: #111827;
|
||||||
@@ -251,6 +253,7 @@
|
|||||||
--cal-bg-success: #E2FBE8;
|
--cal-bg-success: #E2FBE8;
|
||||||
--cal-bg-attention: #FCEED8;
|
--cal-bg-attention: #FCEED8;
|
||||||
--cal-bg-error: #F9E3E2;
|
--cal-bg-error: #F9E3E2;
|
||||||
|
--cal-bg-dark-error: #752522;
|
||||||
|
|
||||||
|
|
||||||
/* Borders */
|
/* Borders */
|
||||||
@@ -258,6 +261,7 @@
|
|||||||
--cal-border: #444444;
|
--cal-border: #444444;
|
||||||
--cal-border-subtle: #2B2B2B;
|
--cal-border-subtle: #2B2B2B;
|
||||||
--cal-border-muted: #1C1C1C;
|
--cal-border-muted: #1C1C1C;
|
||||||
|
--cal-border-error: #aa2e26;
|
||||||
|
|
||||||
/* Content/Text */
|
/* Content/Text */
|
||||||
--cal-text-emphasis: #F3F4F6;
|
--cal-text-emphasis: #F3F4F6;
|
||||||
|
|||||||
@@ -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": "^13.2.1",
|
"next": "^13.4.6",
|
||||||
"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",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "16.9.1",
|
"@types/node": "16.9.1",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import { components } from "react-select";
|
|||||||
|
|
||||||
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
||||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||||
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, EMBED_LIB_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||||
import {
|
import {
|
||||||
@@ -38,13 +38,21 @@ const enum Theme {
|
|||||||
dark = "dark",
|
dark = "dark",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMBED_CAL_ORIGIN = WEBAPP_URL;
|
||||||
|
const EMBED_CAL_JS_URL = `${WEBAPP_URL}/embed/embed.js`;
|
||||||
|
|
||||||
type PreviewState = {
|
type PreviewState = {
|
||||||
inline: {
|
inline: {
|
||||||
width: string;
|
width: string;
|
||||||
height: string;
|
height: string;
|
||||||
};
|
};
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
floatingPopup: Record<string, string | boolean | undefined>;
|
floatingPopup: {
|
||||||
|
config?: {
|
||||||
|
layout: BookerLayouts;
|
||||||
|
};
|
||||||
|
[key: string]: string | boolean | undefined | Record<string, string>;
|
||||||
|
};
|
||||||
elementClick: Record<string, string>;
|
elementClick: Record<string, string>;
|
||||||
palette: {
|
palette: {
|
||||||
brandColor: string;
|
brandColor: string;
|
||||||
@@ -175,14 +183,24 @@ const Codes: Record<string, Record<string, (...args: any[]) => string>> = {
|
|||||||
return code`
|
return code`
|
||||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
function MyComponent() {
|
export default function MyApp() {
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
(async function () {
|
(async function () {
|
||||||
const cal = await getCalApi();
|
const cal = await getCalApi();
|
||||||
${uiInstructionCode}
|
${uiInstructionCode}
|
||||||
})();
|
})();
|
||||||
}, [])
|
}, [])
|
||||||
return <Cal calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} />;
|
return <Cal
|
||||||
|
calLink="${calLink}"
|
||||||
|
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
||||||
|
${previewState.layout ? "config={{layout: '" + previewState.layout + "'}}" : ""}${
|
||||||
|
IS_SELF_HOSTED
|
||||||
|
? `
|
||||||
|
calOrigin="${EMBED_CAL_ORIGIN}"
|
||||||
|
calJsUrl="${EMBED_CAL_JS_URL}"`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
/>;
|
||||||
};`;
|
};`;
|
||||||
},
|
},
|
||||||
"floating-popup": ({
|
"floating-popup": ({
|
||||||
@@ -193,38 +211,60 @@ function MyComponent() {
|
|||||||
uiInstructionCode: string;
|
uiInstructionCode: string;
|
||||||
}) => {
|
}) => {
|
||||||
return code`
|
return code`
|
||||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
import { getCalApi } from "@calcom/embed-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
function MyComponent() {
|
export default function App() {
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
(async function () {
|
(async function () {
|
||||||
const cal = await getCalApi();
|
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
|
||||||
cal("floatingButton", ${floatingButtonArg});
|
cal("floatingButton", ${floatingButtonArg});
|
||||||
${uiInstructionCode}
|
${uiInstructionCode}
|
||||||
})();
|
})();
|
||||||
}, [])
|
}, [])
|
||||||
};`;
|
};`;
|
||||||
},
|
},
|
||||||
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
"element-click": ({
|
||||||
|
calLink,
|
||||||
|
uiInstructionCode,
|
||||||
|
previewState,
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
uiInstructionCode: string;
|
||||||
|
previewState: PreviewState;
|
||||||
|
}) => {
|
||||||
return code`
|
return code`
|
||||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
import { getCalApi } from "@calcom/embed-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
function MyComponent() {
|
export default function App() {
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
(async function () {
|
(async function () {
|
||||||
const cal = await getCalApi();
|
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
|
||||||
${uiInstructionCode}
|
${uiInstructionCode}
|
||||||
})();
|
})();
|
||||||
}, [])
|
}, [])
|
||||||
return <button data-cal-link="${calLink}" />;
|
return <button
|
||||||
|
data-cal-link="${calLink}"${IS_SELF_HOSTED ? `\ndata-cal-origin="${EMBED_CAL_ORIGIN}"` : ""}
|
||||||
|
${`data-cal-config='${JSON.stringify({
|
||||||
|
layout: previewState.layout,
|
||||||
|
})}'`}
|
||||||
|
>Click me</button>;
|
||||||
};`;
|
};`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HTML: {
|
HTML: {
|
||||||
inline: ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
inline: ({
|
||||||
|
calLink,
|
||||||
|
uiInstructionCode,
|
||||||
|
previewState,
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
uiInstructionCode: string;
|
||||||
|
previewState: PreviewState;
|
||||||
|
}) => {
|
||||||
return code`Cal("inline", {
|
return code`Cal("inline", {
|
||||||
elementOrSelector:"#my-cal-inline",
|
elementOrSelector:"#my-cal-inline",
|
||||||
calLink: "${calLink}"
|
calLink: "${calLink}",
|
||||||
|
layout: "${previewState.layout}"
|
||||||
});
|
});
|
||||||
|
|
||||||
${uiInstructionCode}`;
|
${uiInstructionCode}`;
|
||||||
@@ -240,8 +280,22 @@ ${uiInstructionCode}`;
|
|||||||
return code`Cal("floatingButton", ${floatingButtonArg});
|
return code`Cal("floatingButton", ${floatingButtonArg});
|
||||||
${uiInstructionCode}`;
|
${uiInstructionCode}`;
|
||||||
},
|
},
|
||||||
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
"element-click": ({
|
||||||
return code`// Important: Make sure to add \`data-cal-link="${calLink}"\` attribute to the element you want to open Cal on click
|
calLink,
|
||||||
|
uiInstructionCode,
|
||||||
|
previewState,
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
uiInstructionCode: string;
|
||||||
|
previewState: PreviewState;
|
||||||
|
}) => {
|
||||||
|
return code`
|
||||||
|
// Important: Please add following attributes to the element you want to open Cal on click
|
||||||
|
// \`data-cal-link="${calLink}"\`
|
||||||
|
// \`data-cal-config='${JSON.stringify({
|
||||||
|
layout: previewState.layout,
|
||||||
|
})}'\`
|
||||||
|
|
||||||
${uiInstructionCode}`;
|
${uiInstructionCode}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -298,6 +352,7 @@ const getEmbedTypeSpecificString = ({
|
|||||||
} else if (embedType === "floating-popup") {
|
} else if (embedType === "floating-popup") {
|
||||||
const floatingButtonArg = {
|
const floatingButtonArg = {
|
||||||
calLink,
|
calLink,
|
||||||
|
...(IS_SELF_HOSTED ? { calOrigin: EMBED_CAL_ORIGIN } : null),
|
||||||
...previewState.floatingPopup,
|
...previewState.floatingPopup,
|
||||||
};
|
};
|
||||||
return frameworkCodes[embedType]({
|
return frameworkCodes[embedType]({
|
||||||
@@ -710,13 +765,13 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
},
|
},
|
||||||
theme: Theme.auto,
|
theme: Theme.auto,
|
||||||
|
layout: BookerLayouts.MONTH_VIEW,
|
||||||
floatingPopup: {},
|
floatingPopup: {},
|
||||||
elementClick: {},
|
elementClick: {},
|
||||||
hideEventTypeDetails: false,
|
hideEventTypeDetails: false,
|
||||||
palette: {
|
palette: {
|
||||||
brandColor: "#000000",
|
brandColor: "#000000",
|
||||||
},
|
},
|
||||||
layout: BookerLayouts.MONTH_VIEW,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
@@ -778,8 +833,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
name: "ui",
|
name: "ui",
|
||||||
arg: {
|
arg: {
|
||||||
theme: previewState.theme,
|
theme: previewState.theme,
|
||||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
|
||||||
layout: previewState.layout,
|
layout: previewState.layout,
|
||||||
|
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||||
styles: {
|
styles: {
|
||||||
branding: {
|
branding: {
|
||||||
...previewState.palette,
|
...previewState.palette,
|
||||||
@@ -1000,7 +1055,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
||||||
<div className="whitespace-nowrap">Text color</div>
|
<div className="whitespace-nowrap">Text color</div>
|
||||||
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
|
<div className="mb-6 mt-2 w-40 xl:mt-0 xl:w-full">
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
className="w-[130px]"
|
className="w-[130px]"
|
||||||
popoverAlign="start"
|
popoverAlign="start"
|
||||||
@@ -1102,8 +1157,15 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setPreviewState((previewState) => {
|
setPreviewState((previewState) => {
|
||||||
|
const config = {
|
||||||
|
...(previewState.floatingPopup.config ?? {}),
|
||||||
|
layout: option.value,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
...previewState,
|
...previewState,
|
||||||
|
floatingPopup: {
|
||||||
|
config,
|
||||||
|
},
|
||||||
layout: option.value,
|
layout: option.value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
|||||||
isCurrent
|
isCurrent
|
||||||
? "text-emphasis border-gray-900"
|
? "text-emphasis border-gray-900"
|
||||||
: "hover:border-default hover:text-default text-subtle border-transparent",
|
: "hover:border-default hover:text-default text-subtle border-transparent",
|
||||||
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
|
"group inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
aria-current={isCurrent ? "page" : undefined}>
|
aria-current={isCurrent ? "page" : undefined}>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ const Component = ({
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
|
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
|
||||||
{hasDescriptionItems && (
|
{hasDescriptionItems && (
|
||||||
<div className="align-center bg-subtle mb-4 -ml-4 -mr-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:mb-8 md:-ml-8 md:-mr-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
|
<div className="align-center bg-subtle -ml-4 -mr-4 mb-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:-ml-8 md:-mr-8 md:mb-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
|
||||||
{descriptionItems ? (
|
{descriptionItems ? (
|
||||||
descriptionItems.map((descriptionItem, index) =>
|
descriptionItems.map((descriptionItem, index) =>
|
||||||
typeof descriptionItem === "object" ? (
|
typeof descriptionItem === "object" ? (
|
||||||
@@ -260,7 +260,7 @@ const Component = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("contact")}</h4>
|
<h4 className="text-emphasis mb-2 mt-8 font-semibold ">{t("contact")}</h4>
|
||||||
<ul className="prose-sm -ml-1 -mr-1 leading-5">
|
<ul className="prose-sm -ml-1 -mr-1 leading-5">
|
||||||
{docs && (
|
{docs && (
|
||||||
<li>
|
<li>
|
||||||
@@ -269,7 +269,7 @@ const Component = ({
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-emphasis text-sm font-normal no-underline hover:underline"
|
className="text-emphasis text-sm font-normal no-underline hover:underline"
|
||||||
href={docs}>
|
href={docs}>
|
||||||
<BookOpen className="text-subtle mr-1 -mt-1 inline h-4 w-4" />
|
<BookOpen className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
|
||||||
{t("documentation")}
|
{t("documentation")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -281,7 +281,7 @@ const Component = ({
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-emphasis font-normal no-underline hover:underline"
|
className="text-emphasis font-normal no-underline hover:underline"
|
||||||
href={website}>
|
href={website}>
|
||||||
<ExternalLink className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
<ExternalLink className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||||
{website.replace("https://", "")}
|
{website.replace("https://", "")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -293,7 +293,7 @@ const Component = ({
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-emphasis font-normal no-underline hover:underline"
|
className="text-emphasis font-normal no-underline hover:underline"
|
||||||
href={"mailto:" + email}>
|
href={"mailto:" + email}>
|
||||||
<Mail className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
<Mail className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||||
|
|
||||||
{email}
|
{email}
|
||||||
</a>
|
</a>
|
||||||
@@ -306,7 +306,7 @@ const Component = ({
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-emphasis font-normal no-underline hover:underline"
|
className="text-emphasis font-normal no-underline hover:underline"
|
||||||
href={tos}>
|
href={tos}>
|
||||||
<File className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
<File className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||||
{t("terms_of_service")}
|
{t("terms_of_service")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -318,7 +318,7 @@ const Component = ({
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-emphasis font-normal no-underline hover:underline"
|
className="text-emphasis font-normal no-underline hover:underline"
|
||||||
href={privacy}>
|
href={privacy}>
|
||||||
<Shield className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
<Shield className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||||
{t("privacy_policy")}
|
{t("privacy_policy")}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
{emptyStore ? (
|
{emptyStore ? (
|
||||||
<EmptyScreen
|
<EmptyScreen
|
||||||
Icon={AlertCircle}
|
Icon={AlertCircle}
|
||||||
headline={t("no_apps")}
|
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
|
||||||
description={isAdmin ? "You can enable apps in the settings" : ""}
|
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
|
||||||
buttonText={isAdmin ? t("apps_settings") : ""}
|
buttonText={isAdmin ? t("apps_settings") : ""}
|
||||||
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
|
||||||
|
|
||||||
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
|
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Tooltip } from "@calcom/ui";
|
import { Tooltip } from "@calcom/ui";
|
||||||
@@ -23,20 +21,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateAbleKeys = [
|
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
|
||||||
"in_person_attendee_address",
|
|
||||||
"in_person",
|
|
||||||
"attendee_phone_number",
|
|
||||||
"link_meeting",
|
|
||||||
"organizer_phone_number",
|
|
||||||
];
|
|
||||||
|
|
||||||
const locationKey = z.string().default("").parse(locationKeyToString(location));
|
|
||||||
const translatedLocation = location.type.startsWith("integrations:")
|
|
||||||
? eventLocationType.label
|
|
||||||
: translateAbleKeys.includes(locationKey)
|
|
||||||
? t(locationKey)
|
|
||||||
: locationKey;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const BookingDescription: FC<Props> = (props) => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
truncateAfter={3}
|
truncateAfter={3}
|
||||||
/>
|
/>
|
||||||
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">
|
<h2 className="text-default mb-2 mt-1 break-words text-sm font-medium ">
|
||||||
{eventType.team?.parent?.name} {profile.name}
|
{eventType.team?.parent?.name} {profile.name}
|
||||||
</h2>
|
</h2>
|
||||||
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
|
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const BookingDescriptionPayment = (props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
{paymentAppData.paymentOption === "HOLD" ? (
|
{paymentAppData.paymentOption === "HOLD" ? (
|
||||||
<>{props.t("no_show_fee_amount", params)}</>
|
<>{props.t("no_show_fee_amount", params)}</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
</td>
|
</td>
|
||||||
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
|
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
|
||||||
{/* Time and Badges for mobile */}
|
{/* Time and Badges for mobile */}
|
||||||
<div className="w-full pt-4 pb-2 sm:hidden">
|
<div className="w-full pb-2 pt-4 sm:hidden">
|
||||||
<div className="flex w-full items-center justify-between sm:hidden">
|
<div className="flex w-full items-center justify-between sm:hidden">
|
||||||
<div className="text-emphasis text-sm leading-6">{startTime}</div>
|
<div className="text-emphasis text-sm leading-6">{startTime}</div>
|
||||||
<div className="text-subtle pr-2 text-sm">
|
<div className="text-subtle pr-2 text-sm">
|
||||||
@@ -423,7 +423,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
<span> </span>
|
<span> </span>
|
||||||
|
|
||||||
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
|
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
|
||||||
<Badge className="ms-2 me-2 hidden sm:inline-flex" variant="orange">
|
<Badge className="me-2 ms-2 hidden sm:inline-flex" variant="orange">
|
||||||
{t("pending_payment")}
|
{t("pending_payment")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function CancelBooking(props: Props) {
|
|||||||
placeholder={t("cancellation_reason_placeholder")}
|
placeholder={t("cancellation_reason_placeholder")}
|
||||||
value={cancellationReason}
|
value={cancellationReason}
|
||||||
onChange={(e) => setCancellationReason(e.target.value)}
|
onChange={(e) => setCancellationReason(e.target.value)}
|
||||||
className="mt-2 mb-4 w-full "
|
className="mb-4 mt-2 w-full "
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
||||||
@@ -93,7 +93,7 @@ export default function CancelBooking(props: Props) {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
method: "DELETE",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status >= 200 && res.status < 300) {
|
if (res.status >= 200 && res.status < 300) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function SkeletonItem() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
|
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
|
||||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||||
<SkeletonText className="h-6 w-16" />
|
<SkeletonText className="h-6 w-16" />
|
||||||
<SkeletonText className="h-6 w-32" />
|
<SkeletonText className="h-6 w-32" />
|
||||||
|
|||||||
@@ -191,9 +191,9 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{!rescheduleUid && eventType.recurringEvent && (
|
{!rescheduleUid && eventType.recurringEvent && (
|
||||||
<div className="flex items-start text-sm font-medium">
|
<div className="flex items-start text-sm font-medium">
|
||||||
<RefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
<RefreshCcw className="float-left ml-[2px] mt-[7px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
<p className="-ml-2 mb-1 inline px-2 py-1">
|
||||||
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{paymentAppData.price > 0 && (
|
{paymentAppData.price > 0 && (
|
||||||
<p className="-ml-2 px-2 text-sm font-medium">
|
<p className="-ml-2 px-2 text-sm font-medium">
|
||||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
{paymentAppData.paymentOption === "HOLD" ? (
|
{paymentAppData.paymentOption === "HOLD" ? (
|
||||||
<>
|
<>
|
||||||
{t("no_show_fee_amount", {
|
{t("no_show_fee_amount", {
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ const BookingPage = ({
|
|||||||
)}>
|
)}>
|
||||||
<div className="sm:flex">
|
<div className="sm:flex">
|
||||||
{showEventTypeDetails && (
|
{showEventTypeDetails && (
|
||||||
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
<div className="sm:border-subtle text-default flex flex-col px-6 pb-0 pt-6 sm:w-1/2 sm:border-r sm:pb-6">
|
||||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||||
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
||||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||||
@@ -561,7 +561,7 @@ const BookingPage = ({
|
|||||||
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
|
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
|
||||||
return <p key={key}>{timeFormatted}</p>;
|
return <p key={key}>{timeFormatted}</p>;
|
||||||
})}
|
})}
|
||||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
|
{!rescheduleUid && eventType?.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
||||||
@@ -577,11 +577,11 @@ const BookingPage = ({
|
|||||||
</div>
|
</div>
|
||||||
{booking?.startTime && rescheduleUid && (
|
{booking?.startTime && rescheduleUid && (
|
||||||
<div>
|
<div>
|
||||||
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
|
<p className="mb-2 mt-8 text-sm " data-testid="former_time_p">
|
||||||
{t("former_time")}
|
{t("former_time")}
|
||||||
</p>
|
</p>
|
||||||
<p className="line-through ">
|
<p className="line-through ">
|
||||||
<Calendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<Calendar className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
{isClientTimezoneAvailable &&
|
{isClientTimezoneAvailable &&
|
||||||
typeof booking.startTime === "string" &&
|
typeof booking.startTime === "string" &&
|
||||||
parseDateTimeWithTimeZone(booking.startTime, i18n.language, timezone, {
|
parseDateTimeWithTimeZone(booking.startTime, i18n.language, timezone, {
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
|||||||
|
|
||||||
{booking && (
|
{booking && (
|
||||||
<>
|
<>
|
||||||
<p className="text-emphasis mt-6 mb-2 ml-1 text-sm font-bold">{t("current_location")}:</p>
|
<p className="text-emphasis mb-2 ml-1 mt-6 text-sm font-bold">{t("current_location")}:</p>
|
||||||
<p className="text-emphasis mb-2 ml-1 text-sm">
|
<p className="text-emphasis mb-2 ml-1 text-sm">
|
||||||
{getHumanReadableLocationValue(booking.location, t)}
|
{getHumanReadableLocationValue(booking.location, t)}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
|||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
<DialogHeader title={t("send_reschedule_request")} />
|
<DialogHeader title={t("send_reschedule_request")} />
|
||||||
<p className="text-subtle text-sm">{t("reschedule_modal_description")}</p>
|
<p className="text-subtle text-sm">{t("reschedule_modal_description")}</p>
|
||||||
<p className="text-emphasis mt-6 mb-2 text-sm font-bold">
|
<p className="text-emphasis mb-2 mt-6 text-sm font-bold">
|
||||||
{t("reason_for_reschedule_request")}
|
{t("reason_for_reschedule_request")}
|
||||||
<span className="text-subtle font-normal"> (Optional)</span>
|
<span className="text-subtle font-normal"> (Optional)</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const ErrorDebugPanel: React.FC<{ error: Props["error"]; children?: never }> = (
|
|||||||
{debugMap.map(([key, value]) => {
|
{debugMap.map(([key, value]) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
return (
|
return (
|
||||||
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
|
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||||
<dt className="text-emphasis text-sm font-bold">{key}</dt>
|
<dt className="text-emphasis text-sm font-bold">{key}</dt>
|
||||||
<dd className="text-emphasis mt-1 text-sm sm:col-span-2 sm:mt-0">{value}</dd>
|
<dd className="text-emphasis mt-1 text-sm sm:col-span-2 sm:mt-0">{value}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
|
|||||||
backgroundImage: "url(/calendar-preview.svg)",
|
backgroundImage: "url(/calendar-preview.svg)",
|
||||||
}}>
|
}}>
|
||||||
<div className="m-auto flex items-center justify-center self-stretch">
|
<div className="m-auto flex items-center justify-center self-stretch">
|
||||||
<div className="bg-subtle mt-3 ml-11 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
|
<div className="bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
|
||||||
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
|
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
|
||||||
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
|
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const generateHashedLink = (id: number) => {
|
|||||||
|
|
||||||
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
||||||
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
||||||
|
const { data: user } = trpc.viewer.me.useQuery();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const bookingFields: Prisma.JsonObject = {};
|
const bookingFields: Prisma.JsonObject = {};
|
||||||
|
|
||||||
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||||
|
const selectedThemeIsDark =
|
||||||
|
user?.theme === "dark" ||
|
||||||
|
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
eventType.bookingFields.forEach(({ name }) => {
|
eventType.bookingFields.forEach(({ name }) => {
|
||||||
bookingFields[name] = name + " input";
|
bookingFields[name] = name + " input";
|
||||||
@@ -153,7 +157,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
color="minimal"
|
color="minimal"
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="edit custom name"
|
aria-label="edit custom name"
|
||||||
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
|
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
|
||||||
onClick={() => setShowEventNameTip((old) => !old)}>
|
onClick={() => setShowEventNameTip((old) => !old)}>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -162,7 +166,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
</div>
|
</div>
|
||||||
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
|
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
|
||||||
<div>
|
<div>
|
||||||
<BookerLayoutSelector fallbackToUserSettings />
|
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-subtle" />
|
<hr className="border-subtle" />
|
||||||
<FormBuilder
|
<FormBuilder
|
||||||
@@ -216,7 +220,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
onChange(e ? value : "");
|
onChange(e ? value : "");
|
||||||
}}>
|
}}>
|
||||||
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
|
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
|
||||||
<div className="lg:-ml-2 lg:-mb-2">
|
<div className="lg:-mb-2 lg:-ml-2">
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
className="w-full"
|
||||||
label={t("redirect_success_booking")}
|
label={t("redirect_success_booking")}
|
||||||
@@ -268,7 +272,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
color="minimal"
|
color="minimal"
|
||||||
size="sm"
|
size="sm"
|
||||||
type="button"
|
type="button"
|
||||||
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
|
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
|
||||||
aria-label="copy link"
|
aria-label="copy link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(placeholderHashedLink);
|
navigator.clipboard.writeText(placeholderHashedLink);
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="space-y-4 lg:space-y-8">
|
<div className="space-y-4 lg:space-y-8">
|
||||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Label htmlFor="beforeBufferTime">
|
<Label htmlFor="beforeBufferTime">
|
||||||
{t("before_event")}
|
{t("before_event")}
|
||||||
@@ -245,7 +245,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Label htmlFor="minimumBookingNotice">
|
<Label htmlFor="minimumBookingNotice">
|
||||||
{t("minimum_booking_notice")}
|
{t("minimum_booking_notice")}
|
||||||
@@ -416,7 +416,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{period.type === "RANGE" && (
|
{period.type === "RANGE" && (
|
||||||
<div className="ms-2 me-2 inline-flex space-x-2 rtl:space-x-reverse">
|
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||||
<Controller
|
<Controller
|
||||||
name="periodDates"
|
name="periodDates"
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
@@ -437,7 +437,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{period.suffix ? <span className="ms-2 me-2"> {period.suffix}</span> : null}
|
{period.suffix ? <span className="me-2 ms-2"> {period.suffix}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ export const EventSetupTab = (
|
|||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={`${location.type}${index}`}
|
key={`${location.type}${index}`}
|
||||||
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
|
className="border-default text-default mb-2 h-9 rounded-md border px-2 py-1.5 hover:cursor-pointer">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img
|
<img
|
||||||
@@ -298,7 +298,7 @@ export const EventSetupTab = (
|
|||||||
)}
|
)}
|
||||||
alt={`${eventLocationType.label} logo`}
|
alt={`${eventLocationType.label} logo`}
|
||||||
/>
|
/>
|
||||||
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
|
<span className="ms-1 line-clamp-1 text-sm">{eventLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
@@ -332,7 +332,7 @@ export const EventSetupTab = (
|
|||||||
location.type === MeetLocationType && destinationCalendar?.integration !== "google_calendar"
|
location.type === MeetLocationType && destinationCalendar?.integration !== "google_calendar"
|
||||||
) && (
|
) && (
|
||||||
<div className="text-default flex text-sm">
|
<div className="text-default flex text-sm">
|
||||||
<Check className="mt-0.5 mr-1.5 h-2 w-2.5" />
|
<Check className="mr-1.5 mt-0.5 h-2 w-2.5" />
|
||||||
<Trans i18nKey="event_type_requres_google_cal">
|
<Trans i18nKey="event_type_requres_google_cal">
|
||||||
<p>
|
<p>
|
||||||
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
|
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ function EventTypeSingleLayout({
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
htmlFor="hiddenSwitch"
|
htmlFor="hiddenSwitch"
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ function SkeletonItem() {
|
|||||||
<div className="">
|
<div className="">
|
||||||
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
|
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
|
||||||
<li className="flex items-center whitespace-nowrap">
|
<li className="flex items-center whitespace-nowrap">
|
||||||
<Clock className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
|
<Clock className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
|
||||||
<SkeletonText className="h-4 w-12" />
|
<SkeletonText className="h-4 w-12" />
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center whitespace-nowrap">
|
<li className="flex items-center whitespace-nowrap">
|
||||||
<User className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
|
<User className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
|
||||||
<SkeletonText className="h-4 w-12" />
|
<SkeletonText className="h-4 w-12" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
|
|||||||
|
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
|
||||||
|
|
||||||
import TwoFactor from "@components/auth/TwoFactor";
|
import TwoFactor from "@components/auth/TwoFactor";
|
||||||
|
|
||||||
@@ -75,14 +75,12 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
|
|||||||
{...form.register("password")}
|
{...form.register("password")}
|
||||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||||
/>
|
/>
|
||||||
<Label className="mt-4"> {t("2fa_code")}</Label>
|
|
||||||
|
|
||||||
<TwoFactor center={false} />
|
<TwoFactor center={false} />
|
||||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
<Button type="submit" className="ms-2 me-2" disabled={isDisabling}>
|
<Button type="submit" className="me-2 ms-2" disabled={isDisabling}>
|
||||||
{t("disable")}
|
{t("disable")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="secondary" onClick={onCancel}>
|
<Button color="secondary" onClick={onCancel}>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonati
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
|
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
|
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
|
||||||
|
|||||||
@@ -178,19 +178,19 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
|
|||||||
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="ms-2 me-2"
|
className="me-2 ms-2"
|
||||||
onClick={handleSetup}
|
onClick={handleSetup}
|
||||||
disabled={password.length === 0 || isSubmitting}>
|
disabled={password.length === 0 || isSubmitting}>
|
||||||
{t("continue")}
|
{t("continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</WithStep>
|
</WithStep>
|
||||||
<WithStep step={SetupStep.DisplayQrCode} current={step}>
|
<WithStep step={SetupStep.DisplayQrCode} current={step}>
|
||||||
<Button type="submit" className="ms-2 me-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
<Button type="submit" className="me-2 ms-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||||
{t("continue")}
|
{t("continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</WithStep>
|
</WithStep>
|
||||||
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
||||||
<Button type="submit" className="ms-2 me-2" disabled={isSubmitting}>
|
<Button type="submit" className="me-2 ms-2" disabled={isSubmitting}>
|
||||||
{t("enable")}
|
{t("enable")}
|
||||||
</Button>
|
</Button>
|
||||||
</WithStep>
|
</WithStep>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
|
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">{t("2fa")}</h2>
|
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">{t("2fa")}</h2>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const TwoFactorModalHeader = ({ title, description }: { title: string; descripti
|
|||||||
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<Shield className="text-inverted h-6 w-6" />
|
<Shield className="text-inverted h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
|
||||||
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
|
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
|
|||||||
|
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
|
||||||
|
|
||||||
import TwoFactor from "@components/auth/TwoFactor";
|
import TwoFactor from "@components/auth/TwoFactor";
|
||||||
|
|
||||||
@@ -82,15 +82,13 @@ const DisableTwoFactorAuthModal = ({
|
|||||||
{...form.register("password")}
|
{...form.register("password")}
|
||||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||||
/>
|
/>
|
||||||
<Label className="mt-4"> {t("2fa_code")}</Label>
|
|
||||||
|
|
||||||
<TwoFactor center={false} />
|
<TwoFactor center={false} />
|
||||||
|
|
||||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
<Button type="submit" className="ms-2 me-2" data-testid="disable-2fa" disabled={isDisabling}>
|
<Button type="submit" className="me-2 ms-2" data-testid="disable-2fa" disabled={isDisabling}>
|
||||||
{t("disable")}
|
{t("disable")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="secondary" onClick={onCancel}>
|
<Button color="secondary" onClick={onCancel}>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
|||||||
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="ms-2 me-2"
|
className="me-2 ms-2"
|
||||||
onClick={handleSetup}
|
onClick={handleSetup}
|
||||||
disabled={password.length === 0 || isSubmitting}>
|
disabled={password.length === 0 || isSubmitting}>
|
||||||
{t("continue")}
|
{t("continue")}
|
||||||
@@ -187,13 +187,13 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
data-testid="goto-otp-screen"
|
data-testid="goto-otp-screen"
|
||||||
className="ms-2 me-2"
|
className="me-2 ms-2"
|
||||||
onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||||
{t("continue")}
|
{t("continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</WithStep>
|
</WithStep>
|
||||||
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
||||||
<Button type="submit" className="ms-2 me-2" data-testid="enable-2fa" disabled={isSubmitting}>
|
<Button type="submit" className="me-2 ms-2" data-testid="enable-2fa" disabled={isSubmitting}>
|
||||||
{t("enable")}
|
{t("enable")}
|
||||||
</Button>
|
</Button>
|
||||||
</WithStep>
|
</WithStep>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
|
|||||||
<Label htmlFor="username" className={classNames(longWebsiteUrl && "mb-0")}>
|
<Label htmlFor="username" className={classNames(longWebsiteUrl && "mb-0")}>
|
||||||
<span className="block">{t("username")}</span>
|
<span className="block">{t("username")}</span>
|
||||||
{longWebsiteUrl && (
|
{longWebsiteUrl && (
|
||||||
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 py-1 px-3">
|
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 px-3 py-1">
|
||||||
{process.env.NEXT_PUBLIC_WEBSITE_URL}
|
{process.env.NEXT_PUBLIC_WEBSITE_URL}
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const StepDone = (props: {
|
|||||||
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
|
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-[420px] text-center">
|
<div className="max-w-[420px] text-center">
|
||||||
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
|
<h2 className="mb-1 mt-6 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||||||
alt={member.name || ""}
|
alt={member.name || ""}
|
||||||
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
||||||
/>
|
/>
|
||||||
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
<section className="mt-2 line-clamp-4 w-full space-y-1">
|
||||||
<p className="text-default font-medium">{member.name}</p>
|
<p className="text-default font-medium">{member.name}</p>
|
||||||
<div className="line-clamp-3 text-subtle overflow-ellipsis text-sm font-normal">
|
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
|
||||||
{!isBioEmpty ? (
|
{!isBioEmpty ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const EditableHeading = function EditableHeading({
|
|||||||
<label className="min-w-8 relative inline-block">
|
<label className="min-w-8 relative inline-block">
|
||||||
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
||||||
{!isEditing && isReady && (
|
{!isEditing && isReady && (
|
||||||
<Edit2 className=" text-subtle group-hover:text-subtle ml-1 -mt-px inline h-3 w-3" />
|
<Edit2 className=" text-subtle group-hover:text-subtle -mt-px ml-1 inline h-3 w-3" />
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
{...passThroughProps}
|
{...passThroughProps}
|
||||||
@@ -31,7 +31,7 @@ const EditableHeading = function EditableHeading({
|
|||||||
value={value}
|
value={value}
|
||||||
required
|
required
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
"text-emphasis hover:text-default focus:text-emphasis absolute left-0 top-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||||
)}
|
)}
|
||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default function InfoBadge({ content }: { content: string }) {
|
|||||||
<>
|
<>
|
||||||
<Tooltip side="top" content={content}>
|
<Tooltip side="top" content={content}>
|
||||||
<span title={content}>
|
<span title={content}>
|
||||||
<Info className="text-subtle relative top-px left-1 right-1 mt-px h-4 w-4" />
|
<Info className="text-subtle relative left-1 right-1 top-px mt-px h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function ModalContainer(
|
|||||||
}>
|
}>
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
|
||||||
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
|
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
|||||||
}}
|
}}
|
||||||
data-testid="username-input"
|
data-testid="username-input"
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-0 right-2 flex flex-row">
|
<div className="absolute right-2 top-0 flex flex-row">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mx-2 py-2",
|
"mx-2 py-2",
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
|
|||||||
|
|
||||||
const ActionButtons = () => {
|
const ActionButtons = () => {
|
||||||
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
|
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
|
||||||
<div className="ms-2 me-2 flex flex-row space-x-2">
|
<div className="me-2 ms-2 flex flex-row space-x-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setOpenDialogSaveUsername(true)}
|
onClick={() => setOpenDialogSaveUsername(true)}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ interface UsernameAvailabilityFieldProps {
|
|||||||
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
|
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
|
||||||
return organization
|
return organization
|
||||||
? organization.slug
|
? organization.slug
|
||||||
? `${organization.slug}.${subdomainSuffix()}`
|
? `${organization.slug}.${subdomainSuffix()}/`
|
||||||
: organization.metadata && organization.metadata.requestedSlug
|
: organization.metadata && organization.metadata.requestedSlug
|
||||||
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}`
|
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}/`
|
||||||
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
|
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
|
||||||
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
|
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import type { GetServerSidePropsContext } from "next";
|
|||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import type { Maybe } from "@calcom/trpc/server";
|
import type { Maybe } from "@calcom/trpc/server";
|
||||||
|
|
||||||
import { i18n } from "../../../next-i18next.config";
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { i18n } = require("@calcom/config/next-i18next.config");
|
||||||
|
|
||||||
export function getLocaleFromHeaders(req: IncomingMessage): string {
|
export function getLocaleFromHeaders(req: IncomingMessage): string {
|
||||||
let preferredLocale: string | null | undefined;
|
let preferredLocale: string | null | undefined;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { collectEvents } from "next-collect/server";
|
|||||||
import type { NextMiddleware } from "next/server";
|
import type { NextMiddleware } from "next/server";
|
||||||
import { NextResponse, userAgent } from "next/server";
|
import { NextResponse, userAgent } from "next/server";
|
||||||
|
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { isIpInBanlist } from "@calcom/lib/getIP";
|
import { isIpInBanlist } from "@calcom/lib/getIP";
|
||||||
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
|
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
|
||||||
@@ -11,20 +10,11 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
|
|||||||
const middleware: NextMiddleware = async (req) => {
|
const middleware: NextMiddleware = async (req) => {
|
||||||
const url = req.nextUrl;
|
const url = req.nextUrl;
|
||||||
const requestHeaders = new Headers(req.headers);
|
const requestHeaders = new Headers(req.headers);
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? "");
|
|
||||||
/**
|
/**
|
||||||
* We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages
|
* We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages
|
||||||
* Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests)
|
* Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Make sure we are in the presence of an organization
|
|
||||||
if (isValidOrgDomain && url.pathname === "/") {
|
|
||||||
// In the presence of an organization, cover its profile page at "/"
|
|
||||||
// rewrites for org profile page using team profile page
|
|
||||||
url.pathname = `/org/${currentOrgDomain}`;
|
|
||||||
return NextResponse.rewrite(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIpInBanlist(req) && url.pathname !== "/api/nope") {
|
if (isIpInBanlist(req) && url.pathname !== "/api/nope") {
|
||||||
// DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback
|
// DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback
|
||||||
req.nextUrl.pathname = "/api/nope";
|
req.nextUrl.pathname = "/api/nope";
|
||||||
@@ -83,26 +73,6 @@ const middleware: NextMiddleware = async (req) => {
|
|||||||
requestHeaders.set("x-csp-enforce", "true");
|
requestHeaders.set("x-csp-enforce", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidOrgDomain) {
|
|
||||||
// Match /:slug to determine if it corresponds to org subteam slug or org user slug
|
|
||||||
const slugs = /^\/([^/]+)(\/[^/]+)?$/.exec(url.pathname);
|
|
||||||
// In the presence of an organization, if not team profile, a user or team is being accessed
|
|
||||||
if (slugs) {
|
|
||||||
const [_, teamName, eventType] = slugs;
|
|
||||||
// Fetch the corresponding subteams for the entered organization
|
|
||||||
const getSubteams = await fetch(`${WEBAPP_URL}/api/organizations/${currentOrgDomain}/subteams`);
|
|
||||||
if (getSubteams.ok) {
|
|
||||||
const data = await getSubteams.json();
|
|
||||||
// Treat entered slug as a team if found in the subteams fetched
|
|
||||||
if (data.slugs.includes(teamName)) {
|
|
||||||
// Rewriting towards /team/:slug to bring up the team profile within the org
|
|
||||||
url.pathname = `/team/${teamName}${eventType ?? ""}`;
|
|
||||||
return NextResponse.rewrite(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next({
|
return NextResponse.next({
|
||||||
request: {
|
request: {
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
|
|||||||
+71
-27
@@ -1,10 +1,10 @@
|
|||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
const glob = require("glob");
|
|
||||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
const { i18n } = require("./next-i18next.config");
|
const { i18n } = require("./next-i18next.config");
|
||||||
|
const { pages } = require("./pages");
|
||||||
|
|
||||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||||
@@ -71,6 +71,13 @@ const informAboutDuplicateTranslations = () => {
|
|||||||
|
|
||||||
informAboutDuplicateTranslations();
|
informAboutDuplicateTranslations();
|
||||||
|
|
||||||
|
const getSubdomain = () => {
|
||||||
|
const _url = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL);
|
||||||
|
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-]+)\.[\w-]+\.\w+)$/);
|
||||||
|
//console.log(_url.hostname, _url.hostname.match(regex));
|
||||||
|
return _url.hostname.match(regex)?.groups?.subdomain || null;
|
||||||
|
};
|
||||||
|
|
||||||
const plugins = [];
|
const plugins = [];
|
||||||
if (process.env.ANALYZE === "true") {
|
if (process.env.ANALYZE === "true") {
|
||||||
// only load dependency if env `ANALYZE` was set
|
// only load dependency if env `ANALYZE` was set
|
||||||
@@ -82,16 +89,19 @@ if (process.env.ANALYZE === "true") {
|
|||||||
|
|
||||||
plugins.push(withAxiom);
|
plugins.push(withAxiom);
|
||||||
|
|
||||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||||
const pages = glob
|
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||||
.map((filename) =>
|
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||||
filename
|
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||||
.substr(6)
|
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
|
||||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
|
||||||
.replace(/\/.*/, "")
|
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
|
||||||
)
|
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||||
|
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||||
|
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type/embed`;
|
||||||
|
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
@@ -183,12 +193,43 @@ const nextConfig = {
|
|||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
const defaultSubdomain = getSubdomain();
|
||||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";
|
||||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
|
||||||
const userTypeRouteRegExp = `/:user((?!${pages.join("|")})[^/]*)/:type`;
|
|
||||||
|
|
||||||
let rewrites = [
|
const beforeFiles = [
|
||||||
|
{
|
||||||
|
has: [
|
||||||
|
{
|
||||||
|
type: "host",
|
||||||
|
value: `^(?<orgSlug>${subdomain})\\..*`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
source: "/",
|
||||||
|
destination: "/team/:orgSlug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
has: [
|
||||||
|
{
|
||||||
|
type: "host",
|
||||||
|
value: `^(?<orgSlug>${subdomain})\\..*`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`,
|
||||||
|
destination: "/org/:orgSlug/:user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
has: [
|
||||||
|
{
|
||||||
|
type: "host",
|
||||||
|
value: `^(?<orgSlug>${subdomain}[^.]+)\\..*`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
source: `/:user((?!${pages.join("|")}|_next|public))/:path*`,
|
||||||
|
destination: "/:user/:path*",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let afterFiles = [
|
||||||
{
|
{
|
||||||
source: "/org/:slug",
|
source: "/org/:slug",
|
||||||
destination: "/team/:slug",
|
destination: "/team/:slug",
|
||||||
@@ -232,12 +273,12 @@ const nextConfig = {
|
|||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type",
|
source: teamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type",
|
destination: "/new-booker/team/:slug/:type",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/d/:link/:slug",
|
source: privateLinkRouteRegExp,
|
||||||
destination: "/new-booker/d/:link/:slug",
|
destination: "/new-booker/d/:link/:slug",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
@@ -245,12 +286,12 @@ const nextConfig = {
|
|||||||
// Keep cookie based booker enabled to test new-booker embed in production
|
// Keep cookie based booker enabled to test new-booker embed in production
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
source: embedUserTypeRouteRegExp,
|
||||||
destination: "/new-booker/:user/:type/embed",
|
destination: "/new-booker/:user/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type/embed",
|
source: embedTeamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type/embed",
|
destination: "/new-booker/team/:slug/:type/embed",
|
||||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||||
},
|
},
|
||||||
@@ -268,14 +309,14 @@ const nextConfig = {
|
|||||||
// Enable New Booker for all Embed Requests
|
// Enable New Booker for all Embed Requests
|
||||||
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
|
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
|
||||||
console.log("Enabling New Booker for Embed");
|
console.log("Enabling New Booker for Embed");
|
||||||
rewrites.push(
|
afterFiles.push(
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
source: embedUserTypeRouteRegExp,
|
||||||
destination: "/new-booker/:user/:type/embed",
|
destination: "/new-booker/:user/:type/embed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type/embed",
|
source: embedTeamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type/embed",
|
destination: "/new-booker/team/:slug/:type/embed",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -285,24 +326,27 @@ const nextConfig = {
|
|||||||
// Enable New Booker for All but embed Requests
|
// Enable New Booker for All but embed Requests
|
||||||
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
|
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
|
||||||
console.log("Enabling New Booker for Non-Embed");
|
console.log("Enabling New Booker for Non-Embed");
|
||||||
rewrites.push(
|
afterFiles.push(
|
||||||
...[
|
...[
|
||||||
{
|
{
|
||||||
source: userTypeRouteRegExp,
|
source: userTypeRouteRegExp,
|
||||||
destination: "/new-booker/:user/:type",
|
destination: "/new-booker/:user/:type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/team/:slug/:type",
|
source: teamTypeRouteRegExp,
|
||||||
destination: "/new-booker/team/:slug/:type",
|
destination: "/new-booker/team/:slug/:type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: "/d/:link/:slug",
|
source: privateLinkRouteRegExp,
|
||||||
destination: "/new-booker/d/:link/:slug",
|
destination: "/new-booker/d/:link/:slug",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return rewrites;
|
return {
|
||||||
|
beforeFiles,
|
||||||
|
afterFiles,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "3.0.2",
|
"version": "3.0.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"@radix-ui/react-avatar": "^1.0.0",
|
"@radix-ui/react-avatar": "^1.0.0",
|
||||||
"@radix-ui/react-collapsible": "^1.0.0",
|
"@radix-ui/react-collapsible": "^1.0.0",
|
||||||
"@radix-ui/react-dialog": "^1.0.0",
|
"@radix-ui/react-dialog": "^1.0.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-id": "^1.0.0",
|
"@radix-ui/react-id": "^1.0.0",
|
||||||
"@radix-ui/react-popover": "^1.0.2",
|
"@radix-ui/react-popover": "^1.0.2",
|
||||||
"@radix-ui/react-radio-group": "^1.0.0",
|
"@radix-ui/react-radio-group": "^1.0.0",
|
||||||
@@ -88,12 +88,12 @@
|
|||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"next": "^13.2.1",
|
"next": "^13.4.6",
|
||||||
"next-auth": "^4.20.1",
|
"next-auth": "^4.20.1",
|
||||||
"next-axiom": "^0.16.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-collect": "^0.2.1",
|
"next-collect": "^0.2.1",
|
||||||
"next-i18next": "^11.3.0",
|
"next-i18next": "^13.2.2",
|
||||||
"next-seo": "^4.26.0",
|
"next-seo": "^6.0.0",
|
||||||
"next-themes": "^0.2.0",
|
"next-themes": "^0.2.0",
|
||||||
"nodemailer": "^6.7.8",
|
"nodemailer": "^6.7.8",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
"react-hook-form": "^7.43.3",
|
"react-hook-form": "^7.43.3",
|
||||||
"react-hot-toast": "^2.3.0",
|
"react-hot-toast": "^2.3.0",
|
||||||
"react-intl": "^5.25.1",
|
"react-intl": "^5.25.1",
|
||||||
"react-live-chat-loader": "^2.7.3",
|
"react-live-chat-loader": "^2.8.1",
|
||||||
"react-multi-email": "^0.5.3",
|
"react-multi-email": "^0.5.3",
|
||||||
"react-phone-input-2": "^2.15.1",
|
"react-phone-input-2": "^2.15.1",
|
||||||
"react-phone-number-input": "^3.2.7",
|
"react-phone-number-input": "^3.2.7",
|
||||||
@@ -162,7 +162,6 @@
|
|||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"detect-port": "^1.3.0",
|
"detect-port": "^1.3.0",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"mockdate": "^3.0.5",
|
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"msw": "^0.42.3",
|
"msw": "^0.42.3",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.18",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
const glob = require("glob");
|
||||||
|
|
||||||
|
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||||
|
const pages = glob
|
||||||
|
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||||
|
.map((filename) =>
|
||||||
|
filename
|
||||||
|
.substr(6)
|
||||||
|
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||||
|
.replace(/\/.*/, "")
|
||||||
|
)
|
||||||
|
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||||
|
|
||||||
|
exports.pages = pages;
|
||||||
+91
-43
@@ -3,22 +3,37 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { orgDomainConfig, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { HeadSeo } from "@calcom/ui";
|
import { HeadSeo } from "@calcom/ui";
|
||||||
import { BookOpen, Check, ChevronRight, FileText, Slack } from "@calcom/ui/components/icon";
|
import { BookOpen, Check, ChevronRight, FileText, Slack, Shield } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
import { ssgInit } from "@server/lib/ssg";
|
||||||
|
|
||||||
|
enum pageType {
|
||||||
|
ORG = "org",
|
||||||
|
TEAM = "team",
|
||||||
|
USER = "user",
|
||||||
|
OTHER = "other",
|
||||||
|
}
|
||||||
|
|
||||||
export default function Custom404() {
|
export default function Custom404() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [username] = router.asPath.replace("%20", "-").split(/[?#]/);
|
const [username, setUsername] = useState<string>("");
|
||||||
|
const [currentPageType, setCurrentPageType] = useState<pageType>(pageType.USER);
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
|
{
|
||||||
|
title: "Enterprise",
|
||||||
|
description: "Learn more about organizations and subdomains in our enterprise plan.",
|
||||||
|
icon: Shield,
|
||||||
|
href: `${WEBSITE_URL}/enterprise`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("documentation"),
|
title: t("documentation"),
|
||||||
description: t("documentation_description"),
|
description: t("documentation_description"),
|
||||||
@@ -33,10 +48,29 @@ export default function Custom404() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const [url, setUrl] = useState(`${WEBSITE_URL}/signup?username=`);
|
const [url, setUrl] = useState(`${WEBSITE_URL}/signup`);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
|
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host);
|
||||||
}, [username]);
|
const [routerUsername] = router.asPath.replace("%20", "-").split(/[?#]/);
|
||||||
|
if (!isValidOrgDomain || !currentOrgDomain) {
|
||||||
|
const splitPath = routerUsername.split("/");
|
||||||
|
if (splitPath[1] === "team" && splitPath.length === 3) {
|
||||||
|
// Accessing a non-existent team
|
||||||
|
setUsername(splitPath[2]);
|
||||||
|
setCurrentPageType(pageType.TEAM);
|
||||||
|
setUrl(`${WEBSITE_URL}/signup?callbackUrl=settings/teams/new%3Fslug%3D${username.replace("/", "")}`);
|
||||||
|
} else {
|
||||||
|
setUsername(routerUsername);
|
||||||
|
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUsername(currentOrgDomain);
|
||||||
|
setCurrentPageType(pageType.ORG);
|
||||||
|
setUrl(
|
||||||
|
`${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${username.replace("/", "")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const isSuccessPage = router.asPath.startsWith("/booking");
|
const isSuccessPage = router.asPath.startsWith("/booking");
|
||||||
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
|
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
|
||||||
@@ -59,7 +93,7 @@ export default function Custom404() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="min-h-screen bg-white px-4" data-testid="404-page">
|
<div className="min-h-screen bg-white px-4" data-testid="404-page">
|
||||||
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
|
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm font-semibold uppercase tracking-wide text-black">{t("error_404")}</p>
|
<p className="text-sm font-semibold uppercase tracking-wide text-black">{t("error_404")}</p>
|
||||||
<h1 className="font-cal mt-2 text-4xl font-extrabold text-gray-900 sm:text-5xl">
|
<h1 className="font-cal mt-2 text-4xl font-extrabold text-gray-900 sm:text-5xl">
|
||||||
@@ -80,6 +114,8 @@ export default function Custom404() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!username) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeadSeo
|
<HeadSeo
|
||||||
@@ -91,7 +127,7 @@ export default function Custom404() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="bg-default min-h-screen px-4" data-testid="404-page">
|
<div className="bg-default min-h-screen px-4" data-testid="404-page">
|
||||||
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
|
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
|
||||||
{isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? (
|
{isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
@@ -202,13 +238,13 @@ export default function Custom404() {
|
|||||||
<h1 className="font-cal text-emphasis mt-2 text-4xl font-extrabold sm:text-5xl">
|
<h1 className="font-cal text-emphasis mt-2 text-4xl font-extrabold sm:text-5xl">
|
||||||
{isSuccessPage ? "Booking not found" : t("page_doesnt_exist")}
|
{isSuccessPage ? "Booking not found" : t("page_doesnt_exist")}
|
||||||
</h1>
|
</h1>
|
||||||
{isSubpage ? (
|
{isSubpage && currentPageType !== pageType.TEAM ? (
|
||||||
<span className="mt-2 inline-block text-lg ">
|
<span className="mt-2 inline-block text-lg ">
|
||||||
{t("check_spelling_mistakes_or_go_back")}
|
{t("check_spelling_mistakes_or_go_back")}
|
||||||
</span>
|
</span>
|
||||||
) : isCalcom ? (
|
) : isCalcom ? (
|
||||||
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
|
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
|
||||||
{t("the_username")}{" "}
|
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
|
||||||
<strong className="text-blue-500">
|
<strong className="text-blue-500">
|
||||||
{new URL(WEBSITE_URL).hostname}
|
{new URL(WEBSITE_URL).hostname}
|
||||||
{username}
|
{username}
|
||||||
@@ -217,18 +253,17 @@ export default function Custom404() {
|
|||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<span className="mt-2 inline-block text-lg">
|
<span className="mt-2 inline-block text-lg">
|
||||||
{t("the_username")}{" "}
|
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
|
||||||
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
|
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
|
||||||
{t("is_still_available")}
|
{t("is_still_available")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
|
{((!isSubpage && isCalcom) ||
|
||||||
{t("popular_pages")}
|
currentPageType === pageType.ORG ||
|
||||||
</h2>
|
currentPageType === pageType.TEAM) && (
|
||||||
{!isSubpage && isCalcom && (
|
<ul role="list" className="my-4">
|
||||||
<ul role="list" className="mt-4">
|
|
||||||
<li className="border-2 border-green-500 px-4 py-2">
|
<li className="border-2 border-green-500 px-4 py-2">
|
||||||
<a
|
<a
|
||||||
href={url}
|
href={url}
|
||||||
@@ -245,11 +280,20 @@ export default function Custom404() {
|
|||||||
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
||||||
<span className="focus:outline-none">
|
<span className="focus:outline-none">
|
||||||
<span className="absolute inset-0" aria-hidden="true" />
|
<span className="absolute inset-0" aria-hidden="true" />
|
||||||
{t("register")} <strong className="text-green-500">{username}</strong>
|
{t("register")}{" "}
|
||||||
|
<strong className="text-green-500">{`${
|
||||||
|
currentPageType === pageType.TEAM
|
||||||
|
? `${new URL(WEBSITE_URL).host}/team/`
|
||||||
|
: ""
|
||||||
|
}${username}${
|
||||||
|
currentPageType === pageType.ORG ? `.${subdomainSuffix()}` : ""
|
||||||
|
}`}</strong>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-subtle text-base">{t("claim_username_and_schedule_events")}</p>
|
<p className="text-subtle text-base">
|
||||||
|
{t(`404_claim_entity_${currentPageType.toLowerCase()}`)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 self-center">
|
<div className="flex-shrink-0 self-center">
|
||||||
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
||||||
@@ -258,33 +302,37 @@ export default function Custom404() {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
|
||||||
<ul role="list" className="border-subtle divide-subtle mt-4 divide-y">
|
{t("popular_pages")}
|
||||||
{links.map((link, linkIdx) => (
|
</h2>
|
||||||
<li key={linkIdx} className="px-4 py-2">
|
<ul role="list" className="border-subtle divide-subtle divide-y">
|
||||||
<Link
|
{links
|
||||||
href={link.href}
|
.filter((_, idx) => currentPageType === pageType.ORG || idx !== 0)
|
||||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
.map((link, linkIdx) => (
|
||||||
<div className="flex-shrink-0">
|
<li key={linkIdx} className="px-4 py-2">
|
||||||
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
<Link
|
||||||
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
|
href={link.href}
|
||||||
</span>
|
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||||
</div>
|
<div className="flex-shrink-0">
|
||||||
<div className="min-w-0 flex-1">
|
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
||||||
<h3 className="text-emphasis text-base font-medium">
|
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
|
||||||
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
|
||||||
<span className="absolute inset-0" aria-hidden="true" />
|
|
||||||
{link.title}
|
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</div>
|
||||||
<p className="text-subtle text-base">{link.description}</p>
|
<div className="min-w-0 flex-1">
|
||||||
</div>
|
<h3 className="text-emphasis text-base font-medium">
|
||||||
<div className="flex-shrink-0 self-center">
|
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
||||||
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
<span className="absolute inset-0" aria-hidden="true" />
|
||||||
</div>
|
{link.title}
|
||||||
</Link>
|
</span>
|
||||||
</li>
|
</h3>
|
||||||
))}
|
<p className="text-subtle text-base">{link.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 self-center">
|
||||||
|
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
<li className="px-4 py-2">
|
<li className="px-4 py-2">
|
||||||
<a
|
<a
|
||||||
href={JOIN_SLACK}
|
href={JOIN_SLACK}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function Error500() {
|
|||||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||||
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
||||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">It's not you, it's us.</h2>
|
<h2 className="text-emphasis mt-6 text-2xl font-medium">It's not you, it's us.</h2>
|
||||||
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
|
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
|
||||||
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
|
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
|
||||||
away for you.
|
away for you.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ import PageWrapper from "@components/PageWrapper";
|
|||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
export type UserPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||||
|
export function UserPage(props: UserPageProps) {
|
||||||
const {
|
const {
|
||||||
users,
|
users,
|
||||||
profile,
|
profile,
|
||||||
@@ -102,6 +103,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||||
const query = { ...router.query };
|
const query = { ...router.query };
|
||||||
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
||||||
|
delete query.orgSlug;
|
||||||
const nameOrUsername = user.name || user.username || "";
|
const nameOrUsername = user.name || user.username || "";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -132,7 +134,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
shouldAlignCentrally ? "mx-auto" : "",
|
shouldAlignCentrally ? "mx-auto" : "",
|
||||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
|
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
|
||||||
"max-w-3xl py-24 px-4"
|
"max-w-3xl px-4 py-24"
|
||||||
)}>
|
)}>
|
||||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
@@ -207,8 +209,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.isBookingPage = true;
|
UserPage.isBookingPage = true;
|
||||||
User.PageWrapper = PageWrapper;
|
UserPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
||||||
return (
|
return (
|
||||||
@@ -360,6 +362,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
const safeBio = markdownToSafeHTML(user.bio) || "";
|
const safeBio = markdownToSafeHTML(user.bio) || "";
|
||||||
|
|
||||||
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
users,
|
users,
|
||||||
@@ -385,3 +388,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default UserPage;
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { getStaticProps, getStaticPaths, default } from "./index";
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* This page is empty for the user, it is used only to take advantage of the
|
|
||||||
* caching system that NextJS uses SSG pages.
|
|
||||||
* TODO: Redirect to user profile on browser
|
|
||||||
*/
|
|
||||||
import type { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import getCalendarsEvents from "@calcom/core/getCalendarsEvents";
|
|
||||||
import dayjs from "@calcom/dayjs";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
|
|
||||||
const paramsSchema = z.object({ user: z.string(), month: z.string() });
|
|
||||||
export const getStaticProps: GetStaticProps<
|
|
||||||
{ results: Awaited<ReturnType<typeof getCalendarsEvents>> },
|
|
||||||
{ user: string }
|
|
||||||
> = async (context) => {
|
|
||||||
const { user: username, month } = paramsSchema.parse(context.params);
|
|
||||||
const userWithCredentials = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
credentials: true,
|
|
||||||
selectedCalendars: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Subtract 11 hours from the start date to avoid problems in UTC- time zones.
|
|
||||||
const startDate = dayjs.utc(month, "YYYY-MM").startOf("day").subtract(11, "hours").format();
|
|
||||||
// Add 14 hours from the start date to avoid problems in UTC+ time zones.
|
|
||||||
const endDate = dayjs.utc(month, "YYYY-MM").endOf("month").add(14, "hours").format();
|
|
||||||
try {
|
|
||||||
const results = userWithCredentials?.credentials
|
|
||||||
? await getCalendarsEvents(
|
|
||||||
userWithCredentials?.credentials,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
userWithCredentials?.selectedCalendars
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: { results, date: new Date().toISOString() },
|
|
||||||
revalidate: 1,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
let message = "Unknown error while fetching calendar";
|
|
||||||
if (error instanceof Error) message = error.message;
|
|
||||||
console.error(error, message);
|
|
||||||
return {
|
|
||||||
props: { results: [], date: new Date().toISOString(), message },
|
|
||||||
revalidate: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = () => {
|
|
||||||
return {
|
|
||||||
paths: [],
|
|
||||||
fallback: "blocking",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
type Props = InferGetStaticPropsType<typeof getStaticProps>;
|
|
||||||
const CalendarCache = (props: Props) =>
|
|
||||||
process.env.NODE_ENV === "development" ? <pre>{JSON.stringify(props, null, " ")}</pre> : <div />;
|
|
||||||
|
|
||||||
export default CalendarCache;
|
|
||||||
@@ -5,8 +5,7 @@ import { z } from "zod";
|
|||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { sendPasswordResetEmail } from "@calcom/emails";
|
import { sendPasswordResetEmail } from "@calcom/emails";
|
||||||
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import rateLimiter from "@calcom/lib/rateLimit";
|
|
||||||
import { defaultHandler } from "@calcom/lib/server";
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
@@ -34,15 +33,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// 10 requests per minute
|
// 10 requests per minute
|
||||||
|
|
||||||
const limiter = await rateLimiter();
|
await checkRateLimitAndThrowError({
|
||||||
const limit = await limiter({
|
rateLimitingType: "core",
|
||||||
identifier: ip,
|
identifier: ip,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!limit.success) {
|
|
||||||
throw new Error(ErrorCode.RateLimitExceeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const maybeUser = await prisma.user.findUnique({
|
const maybeUser = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import notEmpty from "@calcom/lib/notEmpty";
|
import notEmpty from "@calcom/lib/notEmpty";
|
||||||
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
const selectedCalendarSelectSchema = z.object({
|
||||||
|
integration: z.string(),
|
||||||
|
externalId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getServerSession({ req, res });
|
const session = await getServerSession({ req, res });
|
||||||
|
|
||||||
@@ -32,18 +37,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
const { credentials, ...user } = userWithCredentials;
|
const { credentials, ...user } = userWithCredentials;
|
||||||
|
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
|
const { integration, externalId } = selectedCalendarSelectSchema.parse(req.body);
|
||||||
await prisma.selectedCalendar.upsert({
|
await prisma.selectedCalendar.upsert({
|
||||||
where: {
|
where: {
|
||||||
userId_integration_externalId: {
|
userId_integration_externalId: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
integration: req.body.integration,
|
integration,
|
||||||
externalId: req.body.externalId,
|
externalId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
integration: req.body.integration,
|
integration,
|
||||||
externalId: req.body.externalId,
|
externalId,
|
||||||
},
|
},
|
||||||
// already exists
|
// already exists
|
||||||
update: {},
|
update: {},
|
||||||
@@ -52,12 +58,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === "DELETE") {
|
if (req.method === "DELETE") {
|
||||||
|
const { integration, externalId } = selectedCalendarSelectSchema.parse(req.query);
|
||||||
await prisma.selectedCalendar.delete({
|
await prisma.selectedCalendar.delete({
|
||||||
where: {
|
where: {
|
||||||
userId_integration_externalId: {
|
userId_integration_externalId: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
externalId: req.body.externalId,
|
externalId,
|
||||||
integration: req.body.integration,
|
integration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -65,10 +72,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
res.status(200).json({ message: "Calendar Selection Saved" });
|
res.status(200).json({ message: "Calendar Selection Saved" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["DELETE", "POST"].includes(req.method)) {
|
|
||||||
await revalidateCalendarCache(res.revalidate, `${session?.user?.username}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
const selectedCalendarIds = await prisma.selectedCalendar.findMany({
|
const selectedCalendarIds = await prisma.selectedCalendar.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
|
|||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
|
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
|
|
||||||
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";
|
||||||
|
|
||||||
@@ -62,14 +61,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
|
|
||||||
if (typeof handler === "function") {
|
if (typeof handler === "function") {
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
if (appName.includes("calendar") && req.session?.user?.username) {
|
|
||||||
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await defaultIntegrationAddHandler({ user: req.session?.user, ...handler });
|
await defaultIntegrationAddHandler({ user: req.session?.user, ...handler });
|
||||||
if (handler.appType.includes("calendar") && req.session?.user?.username) {
|
|
||||||
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
|
|
||||||
}
|
|
||||||
redirectUrl = handler.redirect?.url || getInstalledAppPath(handler);
|
redirectUrl = handler.redirect?.url || getInstalledAppPath(handler);
|
||||||
res.json({ url: redirectUrl, newTab: handler.redirect?.newTab });
|
res.json({ url: redirectUrl, newTab: handler.redirect?.newTab });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
|
|
||||||
|
|
||||||
const querySchema = z.object({
|
|
||||||
username: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This endpoint revalidates users calendar cache several months ahead
|
|
||||||
* Can be used as webhook
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
const { username } = querySchema.parse(req.query);
|
|
||||||
try {
|
|
||||||
await revalidateCalendarCache(res.revalidate, username);
|
|
||||||
|
|
||||||
return res.status(200).json({ revalidated: true });
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(500).send({ message: "Error revalidating" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { GetStaticPropsContext } from "next";
|
import type { GetStaticPropsContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -21,7 +22,7 @@ export default function Error() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { error } = querySchema.parse(router.query);
|
const { error } = querySchema.parse(router.query);
|
||||||
const isTokenVerificationError = error?.toLowerCase() === "verification";
|
const isTokenVerificationError = error?.toLowerCase() === "verification";
|
||||||
let errorMsg = <SkeletonText />;
|
let errorMsg: string | ReactElement = <SkeletonText />;
|
||||||
if (router.isReady) {
|
if (router.isReady) {
|
||||||
errorMsg = isTokenVerificationError ? t("token_invalid_expired") : t("error_during_login");
|
errorMsg = isTokenVerificationError ? t("token_invalid_expired") : t("error_during_login");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export default function Availability() {
|
|||||||
</ConfirmationDialogContent>
|
</ConfirmationDialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
|
<div className="flex h-9 flex-row items-center justify-between px-4 py-2 hover:bg-gray-100">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
htmlFor="hiddenSwitch"
|
htmlFor="hiddenSwitch"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
||||||
import Shell from "@calcom/features/shell/Shell";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
@@ -20,6 +21,8 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
|||||||
|
|
||||||
const meQuery = trpc.viewer.me.useQuery();
|
const meQuery = trpc.viewer.me.useQuery();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
|
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
|
||||||
onMutate: async ({ scheduleId }) => {
|
onMutate: async ({ scheduleId }) => {
|
||||||
await utils.viewer.availability.list.cancel();
|
await utils.viewer.availability.list.cancel();
|
||||||
@@ -67,6 +70,19 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({
|
||||||
|
onSuccess: async ({ schedule }) => {
|
||||||
|
await router.push(`/availability/${schedule.id}`);
|
||||||
|
showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success");
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
const message = `${err.statusCode}: ${err.message}`;
|
||||||
|
showToast(message, "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Adds smooth delete button - item fades and old item slides into place
|
// Adds smooth delete button - item fades and old item slides into place
|
||||||
|
|
||||||
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
|
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
|
||||||
@@ -96,6 +112,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
|||||||
isDeletable={schedules.length !== 1}
|
isDeletable={schedules.length !== 1}
|
||||||
updateDefault={updateMutation.mutate}
|
updateDefault={updateMutation.mutate}
|
||||||
deleteFunction={deleteMutation.mutate}
|
deleteFunction={deleteMutation.mutate}
|
||||||
|
duplicateFunction={duplicateMutation.mutate}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ import type { Prisma } from "@calcom/prisma/client";
|
|||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme } from "@calcom/ui";
|
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme, Alert } from "@calcom/ui";
|
||||||
import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/components/icon";
|
import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/components/icon";
|
||||||
|
import { AlertCircle } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { timeZone } from "@lib/clock";
|
import { timeZone } from "@lib/clock";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
@@ -127,6 +128,10 @@ export default function Success(props: SuccessProps) {
|
|||||||
? props?.bookingInfo?.attendees?.[0]?.name
|
? props?.bookingInfo?.attendees?.[0]?.name
|
||||||
: "Nameless";
|
: "Nameless";
|
||||||
|
|
||||||
|
const attendees = props?.bookingInfo?.attendees;
|
||||||
|
|
||||||
|
const isGmail = !!attendees.find((attendee) => attendee.email.includes("gmail.com"));
|
||||||
|
|
||||||
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
@@ -306,7 +311,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{userIsOwner && !isEmbed && (
|
{userIsOwner && !isEmbed && (
|
||||||
<div className="mt-2 ml-4 -mb-4">
|
<div className="-mb-4 ml-4 mt-2">
|
||||||
<Link
|
<Link
|
||||||
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
||||||
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
||||||
@@ -321,7 +326,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
shouldAlignCentrally ? "text-center" : "",
|
shouldAlignCentrally ? "text-center" : "",
|
||||||
"flex items-end justify-center px-4 pt-4 pb-20 sm:block sm:p-0"
|
"flex items-end justify-center px-4 pb-20 pt-4 sm:block sm:p-0"
|
||||||
)}>
|
)}>
|
||||||
<div
|
<div
|
||||||
className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : " inset-0")}
|
className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : " inset-0")}
|
||||||
@@ -330,7 +335,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||||
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
|
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
|
||||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
"px-8 pb-4 pt-5 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||||
)}
|
)}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
@@ -356,7 +361,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
{needsConfirmation && !isCancelled && <Calendar className="text-emphasis h-5 w-5" />}
|
{needsConfirmation && !isCancelled && <Calendar className="text-emphasis h-5 w-5" />}
|
||||||
{isCancelled && <X className="h-5 w-5 text-red-600" />}
|
{isCancelled && <X className="h-5 w-5 text-red-600" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 mb-8 text-center last:mb-0">
|
<div className="mb-8 mt-6 text-center last:mb-0">
|
||||||
<h3
|
<h3
|
||||||
className="text-emphasis text-2xl font-semibold leading-6"
|
className="text-emphasis text-2xl font-semibold leading-6"
|
||||||
data-testid={isCancelled ? "cancelled-headline" : ""}
|
data-testid={isCancelled ? "cancelled-headline" : ""}
|
||||||
@@ -394,7 +399,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
{(isCancelled || reschedule) && cancellationReason && (
|
{(isCancelled || reschedule) && cancellationReason && (
|
||||||
<>
|
<>
|
||||||
<div className="font-medium">
|
<div className="font-medium">
|
||||||
{isCancelled ? t("reason") : t("reschedule_reason_success_page")}
|
{isCancelled ? t("reason") : t("reschedule_reason")}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 mb-6 last:mb-0">{cancellationReason}</div>
|
<div className="col-span-2 mb-6 last:mb-0">{cancellationReason}</div>
|
||||||
</>
|
</>
|
||||||
@@ -615,7 +620,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
className="border-subtle text-default mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
className="border-subtle text-default mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<svg
|
<svg
|
||||||
className="mr-1 -mt-1.5 inline-block h-4 w-4"
|
className="-mt-1.5 mr-1 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">
|
||||||
@@ -642,7 +647,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
className="text-default border-subtle mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
className="text-default border-subtle mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<svg
|
<svg
|
||||||
className="mr-1 -mt-1.5 inline-block h-4 w-4"
|
className="-mt-1.5 mr-1 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">
|
||||||
@@ -659,7 +664,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
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="-mt-1.5 mr-1 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>
|
||||||
@@ -705,6 +710,26 @@ export default function Success(props: SuccessProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{isGmail && (
|
||||||
|
<Alert
|
||||||
|
className="main -mb-20 mt-4 inline-block text-left sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
|
||||||
|
severity="warning"
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold">{t("google_new_spam_policy")}</p>
|
||||||
|
<span className="underline">
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://cal.com/blog/google-s-new-spam-policy-may-be-affecting-your-invitations">
|
||||||
|
{t("resolve")}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
CustomIcon={AlertCircle}
|
||||||
|
customIconColor="text-attention dark:text-orange-200"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -168,18 +168,14 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (err.data?.code === "UNAUTHORIZED") {
|
if (err.data?.code === "UNAUTHORIZED") {
|
||||||
message = `${err.data.code}: You are not able to update this event`;
|
message = `${err.data.code}: ${t("error_event_type_unauthorized_update")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.data?.code === "PARSE_ERROR" || err.data?.code === "BAD_REQUEST") {
|
if (err.data?.code === "PARSE_ERROR" || err.data?.code === "BAD_REQUEST") {
|
||||||
message = `${err.data.code}: ${err.message}`;
|
message = `${err.data.code}: ${t(err.message)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message) {
|
showToast(message ? t(message) : t(err.message), "error");
|
||||||
showToast(message, "error");
|
|
||||||
} else {
|
|
||||||
showToast(err.message, "error");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
|
|||||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||||
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
|
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
|
||||||
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
|
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
|
||||||
import { OrganizationEventTypeFilter } from "@calcom/features/eventtypes/components/OrganizationEventTypeFilter";
|
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
|
||||||
|
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
|
||||||
import Shell from "@calcom/features/shell/Shell";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -47,7 +48,6 @@ import {
|
|||||||
HeadSeo,
|
HeadSeo,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Label,
|
Label,
|
||||||
VerticalDivider,
|
|
||||||
Alert,
|
Alert,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import {
|
import {
|
||||||
@@ -383,7 +383,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
<div className="group flex w-full max-w-full items-center justify-between overflow-hidden px-4 py-4 sm:px-6">
|
<div className="group flex w-full max-w-full items-center justify-between overflow-hidden px-4 py-4 sm:px-6">
|
||||||
{!(firstItem && firstItem.id === type.id) && (
|
{!(firstItem && firstItem.id === type.id) && (
|
||||||
<button
|
<button
|
||||||
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -ml-4 -mt-4 mb-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||||
onClick={() => moveEventType(index, -1)}>
|
onClick={() => moveEventType(index, -1)}>
|
||||||
<ArrowUp className="h-5 w-5" />
|
<ArrowUp className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -391,7 +391,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
|
|
||||||
{!(lastItem && lastItem.id === type.id) && (
|
{!(lastItem && lastItem.id === type.id) && (
|
||||||
<button
|
<button
|
||||||
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] -ml-4 mt-8 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||||
onClick={() => moveEventType(index, 1)}>
|
onClick={() => moveEventType(index, 1)}>
|
||||||
<ArrowDown className="h-5 w-5" />
|
<ArrowDown className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -401,7 +401,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||||
{type.team && !isManagedEventType && (
|
{type.team && !isManagedEventType && (
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
className="relative top-1 right-3"
|
className="relative right-3 top-1"
|
||||||
size="sm"
|
size="sm"
|
||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
items={type.users.map(
|
items={type.users.map(
|
||||||
@@ -415,12 +415,12 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
)}
|
)}
|
||||||
{isManagedEventType && (
|
{isManagedEventType && (
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
className="relative top-1 right-3"
|
className="relative right-3 top-1"
|
||||||
size="sm"
|
size="sm"
|
||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
items={type.children
|
items={type.children
|
||||||
.flatMap((ch) => ch.users)
|
.flatMap((ch) => ch.users)
|
||||||
.map((user: User) => ({
|
.map((user: Pick<User, "name" | "username">) => ({
|
||||||
alt: user.name || "",
|
alt: user.name || "",
|
||||||
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
||||||
title: user.name || "",
|
title: user.name || "",
|
||||||
@@ -640,7 +640,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{!isManagedEventType && (
|
{!isManagedEventType && (
|
||||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
htmlFor="hiddenSwitch"
|
htmlFor="hiddenSwitch"
|
||||||
@@ -728,10 +728,10 @@ const EventTypeListHeading = ({
|
|||||||
{profile?.name || ""}
|
{profile?.name || ""}
|
||||||
</Link>
|
</Link>
|
||||||
{membershipCount && teamId && (
|
{membershipCount && teamId && (
|
||||||
<span className="text-subtle ms-2 me-2 relative -top-px text-xs">
|
<span className="text-subtle relative -top-px me-2 ms-2 text-xs">
|
||||||
<Link href={`/settings/teams/${teamId}/members`}>
|
<Link href={`/settings/teams/${teamId}/members`}>
|
||||||
<Badge variant="gray">
|
<Badge variant="gray">
|
||||||
<Users className="mr-1 -mt-px inline h-3 w-3" />
|
<Users className="-mt-px mr-1 inline h-3 w-3" />
|
||||||
{membershipCount}
|
{membershipCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -747,7 +747,7 @@ const EventTypeListHeading = ({
|
|||||||
</div>
|
</div>
|
||||||
{!profile?.slug && !!teamId && (
|
{!profile?.slug && !!teamId && (
|
||||||
<button onClick={() => publishTeamMutation.mutate({ teamId })}>
|
<button onClick={() => publishTeamMutation.mutate({ teamId })}>
|
||||||
<Badge variant="gray" className="mb-1 -ml-2">
|
<Badge variant="gray" className="-ml-2 mb-1">
|
||||||
{t("upgrade")}
|
{t("upgrade")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</button>
|
</button>
|
||||||
@@ -800,8 +800,7 @@ const CTA = () => {
|
|||||||
const Actions = () => {
|
const Actions = () => {
|
||||||
return (
|
return (
|
||||||
<div className="hidden items-center md:flex">
|
<div className="hidden items-center md:flex">
|
||||||
<OrganizationEventTypeFilter />
|
<TeamsFilter popoverTriggerClassNames="mb-0" showVerticalDivider={true} />
|
||||||
<VerticalDivider />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -834,9 +833,6 @@ const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any);
|
|
||||||
|
|
||||||
const EventTypesPage = () => {
|
const EventTypesPage = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -862,6 +858,10 @@ const EventTypesPage = () => {
|
|||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
const filters = getTeamsFiltersFromQuery(router.query);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any, filters && { filters });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -879,47 +879,55 @@ const EventTypesPage = () => {
|
|||||||
CTA={<CTA />}>
|
CTA={<CTA />}>
|
||||||
<WithQuery
|
<WithQuery
|
||||||
customLoader={<SkeletonLoader />}
|
customLoader={<SkeletonLoader />}
|
||||||
success={({ data }) => (
|
success={({ data }) => {
|
||||||
<>
|
const isFilteredByOnlyOneItem =
|
||||||
{data.eventTypeGroups.length > 1 ? (
|
(filters?.teamIds?.length === 1 || filters?.userIds?.length === 1) &&
|
||||||
<>
|
data.eventTypeGroups.length === 1;
|
||||||
{isMobile ? (
|
|
||||||
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
|
|
||||||
) : (
|
|
||||||
data.eventTypeGroups.map((group: EventTypeGroup, index: number) => (
|
|
||||||
<div className="flex flex-col" key={group.profile.slug}>
|
|
||||||
<EventTypeListHeading
|
|
||||||
profile={group.profile}
|
|
||||||
membershipCount={group.metadata.membershipCount}
|
|
||||||
teamId={group.teamId}
|
|
||||||
orgSlug={orgBranding?.slug}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EventTypeList
|
return (
|
||||||
types={group.eventTypes}
|
<>
|
||||||
group={group}
|
{data.eventTypeGroups.length > 1 || isFilteredByOnlyOneItem ? (
|
||||||
groupIndex={index}
|
<>
|
||||||
readOnly={group.metadata.readOnly}
|
{isMobile ? (
|
||||||
/>
|
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
|
||||||
</div>
|
) : (
|
||||||
))
|
data.eventTypeGroups.map((group: EventTypeGroup, index: number) => (
|
||||||
)}
|
<div className="flex flex-col" key={group.profile.slug}>
|
||||||
</>
|
<EventTypeListHeading
|
||||||
) : data.eventTypeGroups.length === 1 ? (
|
profile={group.profile}
|
||||||
<EventTypeList
|
membershipCount={group.metadata.membershipCount}
|
||||||
types={data.eventTypeGroups[0].eventTypes}
|
teamId={group.teamId}
|
||||||
group={data.eventTypeGroups[0]}
|
orgSlug={orgBranding?.slug}
|
||||||
groupIndex={0}
|
/>
|
||||||
readOnly={data.eventTypeGroups[0].metadata.readOnly}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<CreateFirstEventTypeView />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<EmbedDialog />
|
<EventTypeList
|
||||||
{router.query.dialog === "duplicate" && <DuplicateDialog />}
|
types={group.eventTypes}
|
||||||
</>
|
group={group}
|
||||||
)}
|
groupIndex={index}
|
||||||
|
readOnly={group.metadata.readOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
data.eventTypeGroups.length === 1 && (
|
||||||
|
<EventTypeList
|
||||||
|
types={data.eventTypeGroups[0].eventTypes}
|
||||||
|
group={data.eventTypeGroups[0]}
|
||||||
|
groupIndex={0}
|
||||||
|
readOnly={data.eventTypeGroups[0].metadata.readOnly}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
|
||||||
|
|
||||||
|
<EmbedDialog />
|
||||||
|
{router.query.dialog === "duplicate" && <DuplicateDialog />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default function InsightsPage() {
|
|||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<FiltersProvider>
|
<FiltersProvider>
|
||||||
<div className="mb-8 mt-0 ml-auto flex w-full flex-wrap justify-between md:-mt-8">
|
<div className="mb-8 ml-auto mt-0 flex w-full flex-wrap justify-between md:-mt-8">
|
||||||
<Heading />
|
<Heading />
|
||||||
<Filters />
|
<Filters />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function MaintenancePage() {
|
|||||||
</Head>
|
</Head>
|
||||||
<div className="bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
<div className="bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||||
<h1 className="text-emphasis text-2xl font-medium">{t("under_maintenance")}</h1>
|
<h1 className="text-emphasis text-2xl font-medium">{t("under_maintenance")}</h1>
|
||||||
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
|
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
|
||||||
{t("under_maintenance_description", { appName: APP_NAME })}
|
{t("under_maintenance_description", { appName: APP_NAME })}
|
||||||
</p>
|
</p>
|
||||||
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
|
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
username,
|
/** TODO: We should standarize this */
|
||||||
|
username: username.toLowerCase().replace(/( |%20)/g, "+"),
|
||||||
organization: isValidOrgDomain
|
organization: isValidOrgDomain
|
||||||
? {
|
? {
|
||||||
slug: currentOrgDomain,
|
slug: currentOrgDomain,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import PageWrapper from "@components/PageWrapper";
|
|||||||
|
|
||||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
|
||||||
return (
|
return (
|
||||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||||
<BookerSeo
|
<BookerSeo
|
||||||
@@ -29,6 +29,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
|||||||
rescheduleBooking={booking}
|
rescheduleBooking={booking}
|
||||||
isAway={away}
|
isAway={away}
|
||||||
hideBranding={isBrandingHidden}
|
hideBranding={isBrandingHidden}
|
||||||
|
isTeamEvent={isTeamEvent}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -57,6 +58,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
username: true,
|
username: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -96,9 +102,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
// We use this to both prefetch the query on the server,
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
|
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
|
||||||
|
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
@@ -114,6 +122,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
slug,
|
slug,
|
||||||
trpcState: ssr.dehydrate(),
|
trpcState: ssr.dehydrate(),
|
||||||
isBrandingHidden: user?.hideBranding,
|
isBrandingHidden: user?.hideBranding,
|
||||||
|
// Sending the team event from the server, because this template file
|
||||||
|
// is reused for both team and user events.
|
||||||
|
isTeamEvent,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
|||||||
rescheduleBooking={booking}
|
rescheduleBooking={booking}
|
||||||
isAway={away}
|
isAway={away}
|
||||||
hideBranding={isBrandingHidden}
|
hideBranding={isBrandingHidden}
|
||||||
|
isTeamEvent
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
@@ -71,7 +72,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
// We use this to both prefetch the query on the server,
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
|
const eventData = await ssr.viewer.public.event.fetch({
|
||||||
|
username: teamSlug,
|
||||||
|
eventSlug: meetingSlug,
|
||||||
|
isTeamEvent: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (!eventData) {
|
if (!eventData) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { getServerSideProps, default } from "../../../[user]/[type]";
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
import type { UserPageProps } from "../../../[user]";
|
||||||
|
import UserPage, { getServerSideProps as GSSUserPage } from "../../../[user]";
|
||||||
|
import type { TeamPageProps } from "../../../team/[slug]";
|
||||||
|
import TeamPage, { getServerSideProps as GSSTeamPage } from "../../../team/[slug]";
|
||||||
|
|
||||||
|
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||||
|
const team = await prisma.team.findFirst({
|
||||||
|
where: {
|
||||||
|
slug: ctx.query.user as string,
|
||||||
|
parentId: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
slug: ctx.query.orgSlug as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (team) {
|
||||||
|
return GSSTeamPage({ ...ctx, query: { slug: ctx.query.user } });
|
||||||
|
}
|
||||||
|
return GSSUserPage({ ...ctx, query: { user: ctx.query.user } });
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = UserPageProps | TeamPageProps;
|
||||||
|
|
||||||
|
export default function Page(props: Props) {
|
||||||
|
if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps);
|
||||||
|
return UserPage(props as UserPageProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { getServerSideProps, default } from "../../team/[slug]";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { getServerSideProps, default } from "../../../../team/[slug]/[type]";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { getServerSideProps, default } from "../../../../team/[slug]";
|
||||||
@@ -25,7 +25,7 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
|
|||||||
<h2 className="font-medium">{title}</h2>
|
<h2 className="font-medium">{title}</h2>
|
||||||
<p>{description}</p>
|
<p>{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
|
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pl-3 sm:pt-0">{children}</div>
|
||||||
</section>
|
</section>
|
||||||
<hr className="border-subtle" />
|
<hr className="border-subtle" />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
|
|||||||
<div>
|
<div>
|
||||||
{isLoading ? null : data?.length ? (
|
{isLoading ? null : data?.length ? (
|
||||||
<>
|
<>
|
||||||
<div className="mt-6 mb-8 rounded-md border">
|
<div className="mb-8 mt-6 rounded-md border">
|
||||||
{data.map((apiKey, index) => (
|
{data.map((apiKey, index) => (
|
||||||
<ApiKeyListItem
|
<ApiKeyListItem
|
||||||
key={apiKey.id}
|
key={apiKey.id}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<Meta title={title} description={description} />
|
<Meta title={title} description={description} />
|
||||||
<div className="mt-6 mb-8 space-y-6">
|
<div className="mb-8 mt-6 space-y-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||||
@@ -70,6 +70,13 @@ const AppearanceView = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedTheme = formMethods.watch("theme");
|
||||||
|
const selectedThemeIsDark =
|
||||||
|
selectedTheme === "dark" ||
|
||||||
|
(selectedTheme === "" &&
|
||||||
|
typeof document !== "undefined" &&
|
||||||
|
document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formState: { isSubmitting, isDirty },
|
formState: { isSubmitting, isDirty },
|
||||||
reset,
|
reset,
|
||||||
@@ -144,6 +151,7 @@ const AppearanceView = () => {
|
|||||||
|
|
||||||
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
|
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
|
||||||
<BookerLayoutSelector
|
<BookerLayoutSelector
|
||||||
|
isDark={selectedThemeIsDark}
|
||||||
name="metadata.defaultBookerLayouts"
|
name="metadata.defaultBookerLayouts"
|
||||||
title={t("bookerlayout_user_settings_title")}
|
title={t("bookerlayout_user_settings_title")}
|
||||||
description={t("bookerlayout_user_settings_description")}
|
description={t("bookerlayout_user_settings_description")}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import PageWrapper from "@components/PageWrapper";
|
|||||||
const SkeletonLoader = () => {
|
const SkeletonLoader = () => {
|
||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<div className="mt-6 mb-8 space-y-6">
|
<div className="mb-8 mt-6 space-y-6">
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<Meta title={title} description={description} />
|
<Meta title={title} description={description} />
|
||||||
<div className="divide-subtle mt-6 mb-8 space-y-6">
|
<div className="divide-subtle mb-8 mt-6 space-y-6">
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<Meta title={title} description={description} />
|
<Meta title={title} description={description} />
|
||||||
<div className="mt-6 mb-8 space-y-6">
|
<div className="mb-8 mt-6 space-y-6">
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<Meta title={title} description={description} />
|
<Meta title={title} description={description} />
|
||||||
<div className="mt-6 mb-8 space-y-6">
|
<div className="mb-8 mt-6 space-y-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<SkeletonAvatar className="h-12 w-12 px-4" />
|
<SkeletonAvatar className="me-4 h-16 w-16 px-4" />
|
||||||
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
||||||
</div>
|
</div>
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal";
|
|||||||
const SkeletonLoader = () => {
|
const SkeletonLoader = () => {
|
||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
<div className="mt-6 mb-8 space-y-6">
|
<div className="mb-8 mt-6 space-y-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||||
<SkeletonText className="h-8 w-full" />
|
<SkeletonText className="h-8 w-full" />
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import Team from "@components/team/screens/Team";
|
|||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }: TeamPageProps) {
|
function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }: TeamPageProps) {
|
||||||
useTheme(team.theme);
|
useTheme(team.theme);
|
||||||
const showMembers = useToggleQuery("members");
|
const showMembers = useToggleQuery("members");
|
||||||
@@ -170,7 +171,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
|
|||||||
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
|
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pt-12 pb-12">
|
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
|
||||||
<div className="mx-auto mb-8 max-w-3xl text-center">
|
<div className="mx-auto mb-8 max-w-3xl text-center">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
|
|||||||
<>
|
<>
|
||||||
<aside
|
<aside
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"no-scrollbar fixed top-0 left-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
|
"no-scrollbar fixed left-0 top-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
|
||||||
open ? "translate-x-0" : "-translate-x-[232px]"
|
open ? "translate-x-0" : "-translate-x-[232px]"
|
||||||
)}>
|
)}>
|
||||||
<main className="prose-sm prose max-w-64 prose-a:text-white prose-h3:text-white prose-h3:font-cal scroll-bar scrollbar-track-w-20 w-full overflow-scroll overflow-x-hidden border-r border-gray-300/20 bg-black/80 p-4 text-white shadow-sm backdrop-blur-lg">
|
<main className="prose-sm prose max-w-64 prose-a:text-white prose-h3:text-white prose-h3:font-cal scroll-bar scrollbar-track-w-20 w-full overflow-scroll overflow-x-hidden border-r border-gray-300/20 bg-black/80 p-4 text-white shadow-sm backdrop-blur-lg">
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
|
|||||||
<HeadSeo title="Meeting Unavailable" description="Meeting Unavailable" />
|
<HeadSeo title="Meeting Unavailable" description="Meeting Unavailable" />
|
||||||
<main className="mx-auto my-24 max-w-3xl">
|
<main className="mx-auto my-24 max-w-3xl">
|
||||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
|
||||||
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
|
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
|
||||||
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
||||||
​
|
​
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
|
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="modal-headline">
|
aria-labelledby="modal-headline">
|
||||||
@@ -38,12 +38,12 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
|
|||||||
This meeting is in the past.
|
This meeting is in the past.
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 border-t border-b py-4">
|
<div className="mt-4 border-b border-t py-4">
|
||||||
<h2 className="font-cal text-default mb-2 text-center text-lg font-medium">
|
<h2 className="font-cal text-default mb-2 text-center text-lg font-medium">
|
||||||
{props.booking.title}
|
{props.booking.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-subtle text-center">
|
<p className="text-subtle text-center">
|
||||||
<Calendar className="mr-1 -mt-1 inline-block h-4 w-4" />
|
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
|
||||||
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
|
|||||||
<>
|
<>
|
||||||
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
|
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
|
||||||
<p className="text-subtle text-center">
|
<p className="text-subtle text-center">
|
||||||
<Calendar className="mr-1 -mt-1 inline-block h-4 w-4" />
|
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
|
||||||
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<svg width="208" height="120" viewBox="0 0 208 120" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g clip-path="url(#clip0_5291_194335)">
|
||||||
|
<rect width="208" height="120" rx="6" fill="url(#paint0_radial_5291_194335)"/>
|
||||||
|
<rect x="25.4497" y="14.6206" width="157.101" height="90.7591" rx="5.35267" fill="#1A1A1A"/>
|
||||||
|
<circle cx="35.5909" cy="24.7618" r="2.67634" fill="#2B2B2B"/>
|
||||||
|
<rect x="40.0518" y="23.8696" width="14.2738" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="31.0068" width="28.5476" height="2.67634" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="32.9146" y="37.2515" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="42.604" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="47.957" width="21.4107" height="1.78422" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="32.9146" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="40.0518" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="47.1885" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="54.3252" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="61.4619" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="68.5991" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="75.7363" y="58.6621" width="5.35267" height="1.78422" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="47.1885" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="68.5991" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="64.0146" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="68.5991" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="72.936" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="61.4619" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#575757"/>
|
||||||
|
<rect x="68.5991" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="75.7363" y="81.8569" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="32.9146" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#1C1C1C"/>
|
||||||
|
<rect x="40.0518" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="47.1885" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="54.3252" y="90.7783" width="5.35267" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<path d="M89.1177 18.5171V101.484H90.9019V18.5171H89.1177Z" fill="#1C1C1C" mask="url(#path-3-inside-1_5291_194335)"/>
|
||||||
|
<rect x="96.5796" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="96.5796" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="47.0645" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="96.5796" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="125.127" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="47.0649" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="125.127" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="22.0854" width="21.4107" height="3.56845" rx="0.892112" fill="#444444"/>
|
||||||
|
<rect x="153.675" y="29.2227" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="38.1436" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="47.0645" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="55.9858" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="64.9067" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect x="153.675" y="73.8281" width="21.4107" height="5.35267" rx="0.892112" fill="#2B2B2B"/>
|
||||||
|
<rect opacity="0.2" x="0.666992" y="-1" width="208" height="120" fill="url(#pattern0)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_5291_194335" transform="scale(0.00480769 0.00833333)"/>
|
||||||
|
</pattern>
|
||||||
|
<radialGradient id="paint0_radial_5291_194335" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(207.334 -1.47576e-05) rotate(145.398) scale(465.773 828.954)">
|
||||||
|
<stop stop-color="#282E39"/>
|
||||||
|
<stop offset="1" stop-color="#D4D4D5"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_5291_194335">
|
||||||
|
<rect width="208" height="120" rx="6" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
<image id="image0_5291_194335" width="208" height="120" xlink:href="data:image/png;base64,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 truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
<svg width="208" height="120" viewBox="0 0 208 120" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g clip-path="url(#clip0_5291_194117)">
|
||||||
|
<rect width="208" height="120" rx="6" fill="url(#paint0_radial_5291_194117)"/>
|
||||||
|
<rect x="22" y="24" width="164" height="72" rx="6" fill="#1A1A1A"/>
|
||||||
|
<circle cx="33" cy="35" r="3" fill="#2B2B2B"/>
|
||||||
|
<rect x="38" y="34" width="16" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="42" width="24" height="3" rx="1" fill="#444444"/>
|
||||||
|
<rect x="30" y="49" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="55" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="61" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="30" y="67" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<path d="M61 28V92H63V28H61Z" fill="#1C1C1C" mask="url(#path-4-inside-1_5291_194117)"/>
|
||||||
|
<rect x="70" y="32" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="70" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="80" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="90" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="100" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="110" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="120" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="130" y="38" width="8" height="2" rx="1" fill="#444444"/>
|
||||||
|
<rect x="90" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="120" y="44" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="44" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="56" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="120" y="56" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="56" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="68" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="110" y="68" width="8" height="8" rx="1" fill="#575757"/>
|
||||||
|
<rect x="120" y="68" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="130" y="68" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="70" y="80" width="8" height="8" rx="1" fill="#1C1C1C"/>
|
||||||
|
<rect x="80" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="90" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="100" y="80" width="8" height="8" rx="1" fill="#2B2B2B"/>
|
||||||
|
<path d="M145 28V92H147V28H145Z" fill="#1C1C1C" mask="url(#path-13-inside-2_5291_194117)"/>
|
||||||
|
<rect x="154" y="32" width="24" height="2" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="38" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="47" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="56" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="65" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="74" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect x="154" y="83" width="24" height="5" rx="1" fill="#2B2B2B"/>
|
||||||
|
<rect opacity="0.2" width="208" height="120" fill="url(#pattern0)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_5291_194117" transform="scale(0.00480769 0.00833333)"/>
|
||||||
|
</pattern>
|
||||||
|
<radialGradient id="paint0_radial_5291_194117" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(207.334 -1.47576e-05) rotate(145.398) scale(465.773 828.954)">
|
||||||
|
<stop stop-color="#282E39"/>
|
||||||
|
<stop offset="1" stop-color="#D4D4D5"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_5291_194117">
|
||||||
|
<rect width="208" height="120" rx="6" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
<image id="image0_5291_194117" width="208" height="120" xlink:href="data:image/png;base64,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 truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 16 KiB |
Loaded 100 of 345 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user