Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75d5576e17 | ||
|
|
0a6dde4fcc | ||
|
|
73b5839284 | ||
|
|
1a42b94b13 | ||
|
|
acb910f680 | ||
|
|
38e857f9a9 | ||
|
|
b8149dee36 | ||
|
|
f583ef40f2 | ||
|
|
71574488c5 | ||
|
|
008e0e43a2 | ||
|
|
da22eda662 | ||
|
|
f94d696885 | ||
|
|
9e84eb1b2c | ||
|
|
4c549811a1 | ||
|
|
05502f0cfb | ||
|
|
e436b8e06b | ||
|
|
8146cf8c93 | ||
|
|
c05f595ef0 | ||
|
|
0a795f5363 | ||
|
|
5b50a469b2 | ||
|
|
c08365f09c | ||
|
|
477e102388 | ||
|
|
e1b10b4ec5 | ||
|
|
ff6db3c572 | ||
|
|
4fa3fdef97 | ||
|
|
f29f6632e8 | ||
|
|
36747988e6 | ||
|
|
3dd312b88b | ||
|
|
3c9e9329b0 | ||
|
|
8617f43a8e | ||
|
|
5f0eb652b0 | ||
|
|
26d7301885 | ||
|
|
ffc4d22ff1 | ||
|
|
ee286a600f | ||
|
|
72c493ee21 | ||
|
|
5bd0428d18 | ||
|
|
c7089b9db6 | ||
|
|
81ad00a816 | ||
|
|
b5730da56d | ||
|
|
9455d14ad5 | ||
|
|
a056160404 | ||
|
|
6d53cd84f8 | ||
|
|
30cb8f745d | ||
|
|
bafa3c97ac | ||
|
|
bb48ccbad9 | ||
|
|
0a68c6d048 | ||
|
|
bb9f8e5d38 | ||
|
|
c99bb190f1 | ||
|
|
f0d2a45fdf | ||
|
|
e5f9315ee6 | ||
|
|
f7782b2080 | ||
|
|
e1f2dbab84 | ||
|
|
c2edbbcea7 | ||
|
|
c7e78cc181 | ||
|
|
7ba1557d1d | ||
|
|
d5b45b757a | ||
|
|
bb3b262bd7 | ||
|
|
d4f41e803a | ||
|
|
74eafa59e1 | ||
|
|
e6cd016cad | ||
|
|
7b0d5959cb | ||
|
|
7702ded85e | ||
|
|
5acb3e8f97 | ||
|
|
41046aaf27 | ||
|
|
7cffb30daa | ||
|
|
26bacb6c8e | ||
|
|
01b5c1f11e | ||
|
|
f5ba04599b | ||
|
|
994e602c8d | ||
|
|
a776c72d48 | ||
|
|
6adb5b413d | ||
|
|
b183a5779a | ||
|
|
16c1cf7b23 | ||
|
|
cf4ee142de | ||
|
|
a5d99bbec9 | ||
|
|
5d0468a986 | ||
|
|
7fef9db4ce | ||
|
|
296a367955 | ||
|
|
1ff2d51ee5 | ||
|
|
092cb0f691 | ||
|
|
0c74a98721 | ||
|
|
2082a4f4ed | ||
|
|
f06ef84f61 | ||
|
|
1080d7f88f | ||
|
|
4ebd52c9af | ||
|
|
e0ac95578c | ||
|
|
473012f2c0 | ||
|
|
1293588ac4 | ||
|
|
c0c40185fc | ||
|
|
3348e7be2f | ||
|
|
97dcb2a2a9 | ||
|
|
5dad78f8fe | ||
|
|
88cc83a0aa | ||
|
|
9dddf5e525 | ||
|
|
9df9f9bb6b | ||
|
|
c4803be323 | ||
|
|
34d360a4ec | ||
|
|
0e6f44e2b9 | ||
|
|
e377997d49 | ||
|
|
a0451e34c1 | ||
|
|
d2b4760e3a | ||
|
|
8469c91973 | ||
|
|
0990549e7f | ||
|
|
e884348807 | ||
|
|
5684ce2d28 | ||
|
|
bd8ac12f14 | ||
|
|
512628e8ab | ||
|
|
903a154dbc | ||
|
|
1a3d54a0d0 | ||
|
|
a5970cb1a8 | ||
|
|
925dc04f1c | ||
|
|
271f31730d | ||
|
|
684f4a795c | ||
|
|
836429348c | ||
|
|
a55138348f | ||
|
|
66b5dfd796 | ||
|
|
bba68e09a9 | ||
|
|
bab182960f | ||
|
|
f09153b3bb | ||
|
|
3e492eb17b | ||
|
|
fe5fe8d53b | ||
|
|
0cc5937104 | ||
|
|
fde3b49126 | ||
|
|
556787b93b | ||
|
|
dcb244d7be | ||
|
|
8d7fb75f71 | ||
|
|
4fe75771b8 | ||
|
|
63f537a08f | ||
|
|
bed593c835 | ||
|
|
f367c5ed3d | ||
|
|
af1382c5ba | ||
|
|
eeb0fc795d | ||
|
|
4aa040d96e | ||
|
|
12466fb064 | ||
|
|
42a3448abb | ||
|
|
92ff49f6a1 | ||
|
|
c4aa555610 | ||
|
|
a45d77de51 | ||
|
|
0421dd401a | ||
|
|
094189fc3b | ||
|
|
677a964d4f | ||
|
|
2357c919c1 | ||
|
|
27b7cccfa6 | ||
|
|
ab2354f2a4 | ||
|
|
538e5017c5 | ||
|
|
2e3060b738 | ||
|
|
e9d9aebd88 | ||
|
|
7d1c69801e | ||
|
|
4235b32349 | ||
|
|
2ca031e25c | ||
|
|
9f734b8964 | ||
|
|
d6927c5049 | ||
|
|
910f5f906f | ||
|
|
71f955e256 | ||
|
|
5e60654aa9 | ||
|
|
b6175b3dde | ||
|
|
a0a731b357 | ||
|
|
fdaa6961f8 | ||
|
|
d91e74ec13 | ||
|
|
0af780aac9 | ||
|
|
41b0589ab0 | ||
|
|
00e96ac7ad | ||
|
|
63feb721ea | ||
|
|
43c1bd63ca | ||
|
|
8bc265faaa | ||
|
|
f33ea91ab0 | ||
|
|
cc0ff17d97 | ||
|
|
4d128971bb | ||
|
|
f9f82283fb | ||
|
|
111135a605 | ||
|
|
04e577582d | ||
|
|
6643d3c20b | ||
|
|
3c42301c19 | ||
|
|
20517be280 | ||
|
|
fa45a85088 | ||
|
|
c839e40a3e | ||
|
|
9422e28598 | ||
|
|
d804b3947d | ||
|
|
3fa710fc1c | ||
|
|
f9359aaa7b | ||
|
|
7c7ca79d48 | ||
|
|
753cb5803d | ||
|
|
210bc9fc8c | ||
|
|
3d8b5390df | ||
|
|
a477f337dc | ||
|
|
9db9204088 | ||
|
|
fe46b81c63 |
No files matched your search
+5
-2
@@ -23,6 +23,9 @@ CALCOM_LICENSE_KEY=
|
|||||||
|
|
||||||
# - DATABASE ************************************************************************************************
|
# - DATABASE ************************************************************************************************
|
||||||
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
|
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
|
||||||
|
# Needed to run migrations while using a connection pooler like PgBouncer
|
||||||
|
# Use the same one as DATABASE_URL if you're not using a connection pooler
|
||||||
|
DATABASE_DIRECT_URL="postgresql://postgres:@localhost:5450/calendso"
|
||||||
UPSTASH_REDIS_REST_URL=
|
UPSTASH_REDIS_REST_URL=
|
||||||
UPSTASH_REDIS_REST_TOKEN=
|
UPSTASH_REDIS_REST_TOKEN=
|
||||||
INSIGHTS_DATABASE_URL=
|
INSIGHTS_DATABASE_URL=
|
||||||
@@ -134,8 +137,8 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME=
|
|||||||
NEXT_PUBLIC_SENTRY_DSN=
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
|
||||||
# Formbricks Experience Management Integration
|
# Formbricks Experience Management Integration
|
||||||
FORMBRICKS_HOST_URL=https://app.formbricks.com
|
NEXT_PUBLIC_FORMBRICKS_HOST_URL=https://app.formbricks.com
|
||||||
FORMBRICKS_ENVIRONMENT_ID=
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=
|
||||||
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
||||||
|
|
||||||
# AvatarAPI
|
# AvatarAPI
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ runs:
|
|||||||
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
||||||
key-2: ${{ github.event.pull_request.number || github.ref }}
|
key-2: ${{ github.event.pull_request.number || github.ref }}
|
||||||
DATABASE_URL: ${{ inputs.DATABASE_URL }}
|
DATABASE_URL: ${{ inputs.DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ inputs.DATABASE_URL }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -39,6 +45,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
@@ -50,6 +57,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -41,6 +44,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -44,6 +50,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -42,6 +48,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||||
organization-name: calcom
|
organization-name: calcom
|
||||||
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
||||||
apply-labels-from-issue:
|
apply-labels-from-issue:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ env:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ env:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
@@ -41,6 +42,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
|||||||
+226
-56
@@ -1,9 +1,10 @@
|
|||||||
// This transform config was generated by Snaplet.
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
// Snaplet found fields that may contain personally identifiable information (PII)
|
/// <reference path=".snaplet/snaplet.d.ts" />
|
||||||
// and used that to populate this file.
|
// This config was generated by Snaplet make sure to check it over before using it.
|
||||||
import { copycat as c } from "@snaplet/copycat";
|
import { copycat as c } from "@snaplet/copycat";
|
||||||
|
import { defineConfig } from "snaplet";
|
||||||
|
|
||||||
import type { Transform } from "./structure";
|
// c.setHashKey(REPLACE_ME_WITH_YOUR_HASH_KEY);
|
||||||
|
|
||||||
function hasStringProp<T extends string>(x: unknown, key: T): x is { [key in T]: string } {
|
function hasStringProp<T extends string>(x: unknown, key: T): x is { [key in T]: string } {
|
||||||
return !!x && typeof x === "object" && key in x;
|
return !!x && typeof x === "object" && key in x;
|
||||||
@@ -41,57 +42,226 @@ function replaceSensitiveKeys(record: object) {
|
|||||||
|
|
||||||
const generateUsername = (x: string) => `${c.firstName(x)}-${c.lastName(x)}${c.int(x, { min: 2, max: 99 })}`;
|
const generateUsername = (x: string) => `${c.firstName(x)}-${c.lastName(x)}${c.int(x, { min: 2, max: 99 })}`;
|
||||||
|
|
||||||
const config: Transform = () => ({
|
export default defineConfig({
|
||||||
public: {
|
transform: {
|
||||||
ApiKey: ({ row }) => ({
|
$mode: "unsafe",
|
||||||
hashedKey: c.uuid(row.hashedKey),
|
public: {
|
||||||
}),
|
Account({ row }) {
|
||||||
App: ({ row }) => ({
|
return {
|
||||||
keys: replaceSensitiveKeys(row.keys),
|
refresh_token: c.uuid(row.refresh_token),
|
||||||
}),
|
access_token: c.uuid(row.access_token),
|
||||||
Attendee: ({ row }) => ({
|
expires_at: c.int(row.expires_at, {
|
||||||
email: c.email(row.email),
|
min: 0,
|
||||||
name: c.fullName(row.name),
|
max: Math.pow(4, 8) - 1,
|
||||||
timeZone: c.timezone(row.timeZone),
|
}),
|
||||||
}),
|
token_type: c.uuid(row.token_type),
|
||||||
Credential: ({ row }) => ({
|
id_token: c.uuid(row.id_token),
|
||||||
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
|
};
|
||||||
}),
|
},
|
||||||
EventType: ({ row }) => ({
|
ApiKey: ({ row }) => ({
|
||||||
slug: generateSlug(row.slug),
|
hashedKey: c.uuid(row.hashedKey),
|
||||||
timeZone: c.timezone(row.timeZone),
|
note: c.fullName(row.note),
|
||||||
eventName: c.words(row.eventName, { max: 3 }),
|
createdAt: c.dateString(row.createdAt, {
|
||||||
}),
|
minYear: 2020,
|
||||||
ResetPasswordRequest: ({ row }) => ({
|
}),
|
||||||
email: c.email(row.email),
|
}),
|
||||||
}),
|
App: ({ row }) => ({
|
||||||
Schedule: ({ row }) => ({
|
keys: replaceSensitiveKeys(row.keys),
|
||||||
timeZone: c.timezone(row.timeZone),
|
}),
|
||||||
}),
|
App_RoutingForms_Form({ row }) {
|
||||||
Team: ({ row }) => ({
|
return {
|
||||||
bio: c.sentence(row.bio),
|
name: c.fullName(row.name),
|
||||||
name: c.words(row.name, { max: 2 }),
|
};
|
||||||
slug: generateSlug(row.slug),
|
},
|
||||||
}),
|
Attendee: ({ row }) => ({
|
||||||
users: ({ row }) =>
|
email: c.email(row.email),
|
||||||
row.role !== "ADMIN"
|
name: c.fullName(row.name),
|
||||||
? {
|
timeZone: c.timezone(row.timeZone),
|
||||||
bio: c.sentence(row.bio),
|
locale: c.fullName(row.locale),
|
||||||
email: c.email(row.email),
|
}),
|
||||||
name: c.fullName(row.name),
|
Availability({ row }) {
|
||||||
password: c.password(row.password),
|
return {
|
||||||
timeZone: c.timezone(row.timeZone),
|
startTime: c
|
||||||
username: generateUsername(row.username),
|
.dateString(row.startTime, {
|
||||||
}
|
minYear: 2020,
|
||||||
: row,
|
})
|
||||||
VerificationToken: ({ row }) => ({
|
.slice(11, 19),
|
||||||
token: c.uuid(row.token),
|
endTime: c
|
||||||
}),
|
.dateString(row.endTime, {
|
||||||
Account: ({ row }) => ({
|
minYear: 2020,
|
||||||
access_token: c.uuid(row.access_token),
|
})
|
||||||
refresh_token: c.uuid(row.refresh_token),
|
.slice(11, 19),
|
||||||
}),
|
};
|
||||||
|
},
|
||||||
|
Booking({ row }) {
|
||||||
|
return {
|
||||||
|
title: c.fullName(row.title),
|
||||||
|
startTime: c.dateString(row.startTime, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
endTime: c.dateString(row.endTime, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
location: c.sentence(row.location),
|
||||||
|
metadata: {
|
||||||
|
[c.word(row.metadata)]: c.words(row.metadata),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Credential: ({ row }) => ({
|
||||||
|
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
|
||||||
|
}),
|
||||||
|
EventType: ({ row }) => ({
|
||||||
|
slug: generateSlug(row.slug),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
eventName: c.words(row.eventName, { max: 3 }),
|
||||||
|
currency: c.sentence(row.currency),
|
||||||
|
}),
|
||||||
|
EventTypeCustomInput({ row }) {
|
||||||
|
return {
|
||||||
|
label: c.fullName(row.label),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Feature({ row }) {
|
||||||
|
return {
|
||||||
|
slug: c.uuid(row.slug),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
InstantMeetingToken({ row }) {
|
||||||
|
return {
|
||||||
|
token: c.uuid(row.token),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
OAuthClient({ row }) {
|
||||||
|
return {
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
OutOfOfficeEntry({ row }) {
|
||||||
|
return {
|
||||||
|
start: c.dateString(row.start, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Payment({ row }) {
|
||||||
|
return {
|
||||||
|
amount: c.int(row.amount, {
|
||||||
|
min: 0,
|
||||||
|
max: Math.pow(4, 8) - 1,
|
||||||
|
}),
|
||||||
|
currency: c.sentence(row.currency),
|
||||||
|
data: {
|
||||||
|
[c.word(row.data)]: c.words(row.data),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
ResetPasswordRequest: ({ row }) => ({
|
||||||
|
email: c.email(row.email),
|
||||||
|
}),
|
||||||
|
Schedule: ({ row }) => ({
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
}),
|
||||||
|
Session({ row }) {
|
||||||
|
return {
|
||||||
|
sessionToken: c.uuid(row.sessionToken),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Team: ({ row }) => ({
|
||||||
|
bio: c.sentence(row.bio),
|
||||||
|
name: c.words(row.name, { max: 2 }),
|
||||||
|
slug: generateSlug(row.slug),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
logoUrl: c.username(row.logoUrl),
|
||||||
|
}),
|
||||||
|
TempOrgRedirect({ row }) {
|
||||||
|
return {
|
||||||
|
from: c.dateString(row.from, {
|
||||||
|
maxYear: 1999,
|
||||||
|
}),
|
||||||
|
toUrl: c.city(row.toUrl),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
VerificationToken({ row }) {
|
||||||
|
return {
|
||||||
|
id: c
|
||||||
|
.int(row.id, {
|
||||||
|
min: 1,
|
||||||
|
max: Math.pow(4, 8) - 1,
|
||||||
|
})
|
||||||
|
.toString(),
|
||||||
|
identifier: c.uuid(row.identifier),
|
||||||
|
token: c.uuid(row.token),
|
||||||
|
expires: c.dateString(row.expires, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
VerifiedNumber({ row }) {
|
||||||
|
return {
|
||||||
|
phoneNumber: c.phoneNumber(row.phoneNumber),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Webhook({ row }) {
|
||||||
|
return {
|
||||||
|
subscriberUrl: c.url(row.subscriberUrl),
|
||||||
|
secret: c.streetAddress(row.secret),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
WebhookScheduledTriggers({ row }) {
|
||||||
|
return {
|
||||||
|
jobName: c.fullName(row.jobName),
|
||||||
|
payload: c.password(row.payload),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
WorkflowStep({ row }) {
|
||||||
|
return {
|
||||||
|
sendTo: c.oneOf(row.sendTo, [
|
||||||
|
"Man",
|
||||||
|
"Woman",
|
||||||
|
"Transgender",
|
||||||
|
"Non-binary/non-conforming",
|
||||||
|
"Not specified",
|
||||||
|
]),
|
||||||
|
sender: c.oneOf(row.sender, [
|
||||||
|
"Man",
|
||||||
|
"Woman",
|
||||||
|
"Transgender",
|
||||||
|
"Non-binary/non-conforming",
|
||||||
|
"Not specified",
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
users: ({ row }) =>
|
||||||
|
row.role !== "ADMIN"
|
||||||
|
? {
|
||||||
|
bio: c.sentence(row.bio),
|
||||||
|
email: c.email(row.email),
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
password: c.password(row.password),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
username: generateUsername(row.username),
|
||||||
|
metadata: {
|
||||||
|
[c.word(row.metadata)]: c.words(row.metadata),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: row,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subset: {
|
||||||
|
enabled: true,
|
||||||
|
version: "3",
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
table: "public.users",
|
||||||
|
rowLimit: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
keepDisconnectedTables: false,
|
||||||
|
followNullableRelations: true,
|
||||||
|
maxCyclesLoop: 0,
|
||||||
|
eager: false,
|
||||||
|
taskSortAlgorithm: "children",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Contact: security@cal.com
|
||||||
|
Preferred-Languages: en
|
||||||
|
Canonical: https://cal.com/.well-known/security.txt
|
||||||
|
Policy: https://github.com/calcom/cal.com/security/policy
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
diff --git a/plugin/timezone.js b/plugin/timezone.js
|
||||||
|
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
|
||||||
|
--- a/plugin/timezone.js
|
||||||
|
+++ b/plugin/timezone.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
|
||||||
|
\ No newline at end of file
|
||||||
|
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
diff --git a/plugin/timezone.js b/plugin/timezone.js
|
||||||
|
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
|
||||||
|
--- a/plugin/timezone.js
|
||||||
|
+++ b/plugin/timezone.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
|
||||||
|
\ No newline at end of file
|
||||||
|
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
|
||||||
@@ -8,14 +8,14 @@ test.describe("Org", () => {
|
|||||||
const response = await page.goto("https://i.cal.com/embed");
|
const response = await page.goto("https://i.cal.com/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await page.screenshot({ path: "screenshot.jpg" });
|
await page.screenshot({ path: "screenshot.jpg" });
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/peer/embed");
|
const response = await page.goto("https://i.cal.com/peer/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
|
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
||||||
@@ -23,14 +23,14 @@ test.describe("Org", () => {
|
|||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
|
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/sales/embed");
|
const response = await page.goto("https://i.cal.com/sales/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
|
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
|
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
|
||||||
@@ -38,9 +38,10 @@ test.describe("Org", () => {
|
|||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Dynamic Group Booking", () => {
|
test.describe("Dynamic Group Booking", () => {
|
||||||
test("Dynamic Group booking link should load", async ({ page }) => {
|
test("Dynamic Group booking link should load", async ({ page }) => {
|
||||||
const users = [
|
const users = [
|
||||||
@@ -63,12 +64,39 @@ test.describe("Org", () => {
|
|||||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Organization Homepage - Has Engineering and Marketing Teams", async ({ page }) => {
|
||||||
|
const response = await page.goto("https://i.cal.com");
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
await expect(page.locator("text=Cal.com")).toBeVisible();
|
||||||
|
await expect(page.locator("text=Engineering")).toBeVisible();
|
||||||
|
await expect(page.locator("text=Marketing")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Browse the Engineering Team", async () => {
|
||||||
|
test("By User Navigation", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com");
|
||||||
|
await page.click('text="Engineering"');
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("By /team/engineering", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com/team/engineering");
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("By /engineering", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com/engineering");
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
|
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
|
||||||
async function expectPageToBeServerSideRendered(page: Page) {
|
async function expectPageToBeRenderedWithEmbedSsr(page: Page) {
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
|
//@ts-expect-error - __NEXT_DATA__ is a global variable defined by Next.js
|
||||||
return window.__NEXT_DATA__.props.pageProps.isEmbed;
|
return window.__NEXT_DATA__.props.pageProps.isEmbed;
|
||||||
})
|
})
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
"author": "Cal.com Inc.",
|
"author": "Cal.com Inc.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
"@langchain/core": "^0.1.26",
|
||||||
|
"@langchain/openai": "^0.0.14",
|
||||||
"@t3-oss/env-nextjs": "^0.6.1",
|
"@t3-oss/env-nextjs": "^0.6.1",
|
||||||
"langchain": "^0.0.131",
|
"langchain": "^0.1.17",
|
||||||
"mailparser": "^3.6.5",
|
"mailparser": "^3.6.5",
|
||||||
"next": "^13.5.4",
|
"next": "^13.5.4",
|
||||||
"supports-color": "8.1.1",
|
"supports-color": "8.1.1",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mailparser": "^3.4.0"
|
"@types/mailparser": "^3.4.0"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { UserList } from "~/src/types/user";
|
import type { UserList } from "~/src/types/user";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "~/src/env.mjs";
|
import { env } from "~/src/env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
import { initializeAgentExecutorWithOptions } from "langchain/agents";
|
import { initializeAgentExecutorWithOptions } from "langchain/agents";
|
||||||
import { ChatOpenAI } from "langchain/chat_models/openai";
|
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
import createBookingIfAvailable from "../tools/createBooking";
|
import createBookingIfAvailable from "../tools/createBooking";
|
||||||
@@ -13,7 +13,7 @@ import type { User, UserList } from "../types/user";
|
|||||||
import type { WorkingHours } from "../types/workingHours";
|
import type { WorkingHours } from "../types/workingHours";
|
||||||
import now from "./now";
|
import now from "./now";
|
||||||
|
|
||||||
const gptModel = "gpt-4-0613";
|
const gptModel = "gpt-4-0125-preview";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { captureException as SentryCaptureException } from "@sentry/nextjs";
|
||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { redactError } from "@calcom/lib/redactError";
|
import { redactError } from "@calcom/lib/redactError";
|
||||||
@@ -9,7 +9,7 @@ export const captureErrors: NextMiddleware = async (_req, res, next) => {
|
|||||||
// middleware and the API route handler
|
// middleware and the API route handler
|
||||||
await next();
|
await next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error);
|
SentryCaptureException(error);
|
||||||
const redactedError = redactError(error);
|
const redactedError = redactError(error);
|
||||||
if (redactedError instanceof Error) {
|
if (redactedError instanceof Error) {
|
||||||
res.status(400).json({ message: redactedError.message, error: redactedError });
|
res.status(400).json({ message: redactedError.message, error: redactedError });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
|
import { API_KEY_RATE_LIMIT } from "@calcom/lib/rateLimit";
|
||||||
|
|
||||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||||
@@ -9,6 +10,11 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
|||||||
await checkRateLimitAndThrowError({
|
await checkRateLimitAndThrowError({
|
||||||
identifier: req.query.apiKey as string,
|
identifier: req.query.apiKey as string,
|
||||||
rateLimitingType: "api",
|
rateLimitingType: "api",
|
||||||
|
onRateLimiterResponse: (response) => {
|
||||||
|
res.setHeader("X-RateLimit-Limit", API_KEY_RATE_LIMIT);
|
||||||
|
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||||
|
res.setHeader("X-RateLimit-Reset", response.reset);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
|
|||||||
bookingLimits: true,
|
bookingLimits: true,
|
||||||
onlyShowFirstAvailableSlot: true,
|
onlyShowFirstAvailableSlot: true,
|
||||||
durationLimits: true,
|
durationLimits: true,
|
||||||
|
assignAllTeamMembers: true,
|
||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { baseApiParams } from "./baseApiParams";
|
||||||
|
|
||||||
|
export const schemaQuerySlug = baseApiParams.extend({
|
||||||
|
slug: z.string().optional(),
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import tzdata from "tzdata";
|
import tzdata from "tzdata";
|
||||||
import * as z from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
|
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
|
||||||
export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), {
|
export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), {
|
||||||
|
|||||||
@@ -41,6 +41,6 @@
|
|||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"tzdata": "^1.0.30",
|
"tzdata": "^1.0.30",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +197,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
dateTo,
|
dateTo,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
userId,
|
userId,
|
||||||
|
returnDateOverrides: true,
|
||||||
});
|
});
|
||||||
const team = await prisma.team.findUnique({
|
const team = await prisma.team.findUnique({
|
||||||
where: { id: teamId },
|
where: { id: teamId },
|
||||||
@@ -232,6 +233,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
dateFrom,
|
dateFrom,
|
||||||
dateTo,
|
dateTo,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
|
returnDateOverrides: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
|
import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
|
||||||
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
const { userId, isAdmin } = req;
|
const { userId, isAdmin } = req;
|
||||||
if (isAdmin) req.userId = req.body.userId || userId;
|
if (isAdmin) req.userId = req.body.userId || userId;
|
||||||
|
|
||||||
return await handleNewBooking(req);
|
return await handleNewBooking(req, getBookingDataSchemaForApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(handler);
|
export default defaultResponder(handler);
|
||||||
@@ -51,7 +51,6 @@ export async function getHandler(req: NextApiRequest) {
|
|||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
import type { PrismaClient } from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||||
|
import { schemaQuerySlug } from "~/lib/validations/shared/querySlug";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
import getCalLink from "./_utils/getCalLink";
|
import getCalLink from "./_utils/getCalLink";
|
||||||
@@ -22,6 +23,12 @@ import getCalLink from "./_utils/getCalLink";
|
|||||||
* type: string
|
* type: string
|
||||||
* required: true
|
* required: true
|
||||||
* description: Your API Key
|
* description: Your API Key
|
||||||
|
* - in: query
|
||||||
|
* name: slug
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: Slug to filter event types by
|
||||||
* tags:
|
* tags:
|
||||||
* - event-types
|
* - event-types
|
||||||
* externalDocs:
|
* externalDocs:
|
||||||
@@ -35,16 +42,20 @@ import getCalLink from "./_utils/getCalLink";
|
|||||||
* description: No event types were found
|
* description: No event types were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, prisma } = req;
|
const { userId, prisma, isAdmin } = req;
|
||||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||||
|
const { slug } = schemaQuerySlug.parse(req.query);
|
||||||
|
const shouldUseUserId = !isAdmin || !slug || !!req.query.userId;
|
||||||
|
// When user is admin and no query params are provided we should return all event types.
|
||||||
|
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
|
||||||
const data = await prisma.eventType.findMany({
|
const data = await prisma.eventType.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: { in: userIds },
|
userId: shouldUseUserId ? { in: userIds } : undefined,
|
||||||
|
slug: slug, // slug will be undefined if not provided in query
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { WEBSITE_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;
|
||||||
@@ -6,7 +6,7 @@ export default function getCalLink(eventType: {
|
|||||||
users?: { username: string | null }[];
|
users?: { username: string | null }[];
|
||||||
slug: string;
|
slug: string;
|
||||||
}) {
|
}) {
|
||||||
return `${CAL_URL}/${
|
return `${WEBSITE_URL}/${
|
||||||
eventType?.team
|
eventType?.team
|
||||||
? `team/${eventType?.team?.slug}`
|
? `team/${eventType?.team?.slug}`
|
||||||
: eventType?.owner
|
: eventType?.owner
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||||
|
import { ZInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||||
|
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||||
|
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||||
|
|
||||||
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const data = ZInviteMemberInputSchema.parse(req.body);
|
||||||
|
await checkPermissions(req, data);
|
||||||
|
|
||||||
|
async function sessionGetter() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: req.userId,
|
||||||
|
username: "",
|
||||||
|
profile: {
|
||||||
|
id: null,
|
||||||
|
organizationId: null,
|
||||||
|
organization: null,
|
||||||
|
username: "",
|
||||||
|
upId: "",
|
||||||
|
} satisfies UserProfile,
|
||||||
|
},
|
||||||
|
hasValidLicense: true,
|
||||||
|
expires: "",
|
||||||
|
upId: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = await createContext({ req, res }, sessionGetter);
|
||||||
|
try {
|
||||||
|
const caller = viewerTeamsRouter.createCaller(ctx);
|
||||||
|
await caller.inviteMember({
|
||||||
|
role: data.role,
|
||||||
|
language: data.language,
|
||||||
|
teamId: data.teamId,
|
||||||
|
usernameOrEmail: data.usernameOrEmail,
|
||||||
|
isOrg: data.isOrg,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||||
|
} catch (cause) {
|
||||||
|
if (cause instanceof TRPCError) {
|
||||||
|
const statusCode = getHTTPStatusCodeFromError(cause);
|
||||||
|
throw new HttpError({ statusCode, message: cause.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
|
||||||
|
const { userId, isAdmin, prisma } = req;
|
||||||
|
if (isAdmin) return;
|
||||||
|
// To prevent auto-accepted invites, limit it to ADMIN users
|
||||||
|
if (!isAdmin && "accepted" in body)
|
||||||
|
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
||||||
|
// Only team OWNERS and ADMINS can add other members
|
||||||
|
const membership = await prisma.membership.findFirst({
|
||||||
|
where: { userId, teamId: body.teamId, role: { in: ["ADMIN", "OWNER"] } },
|
||||||
|
});
|
||||||
|
if (!membership) throw new HttpError({ statusCode: 403, message: "You can't add members to this team" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultResponder(postHandler);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
|
||||||
|
export default withMiddleware()(
|
||||||
|
defaultHandler({
|
||||||
|
POST: import("./_post"),
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -59,7 +59,6 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
|||||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
import { createContext } from "@calcom/trpc/server/createContext";
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||||
@@ -21,9 +22,17 @@ const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
id: req.userId,
|
id: req.userId,
|
||||||
username: "" /* Not used in this context */,
|
username: "" /* Not used in this context */,
|
||||||
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
|
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
|
||||||
|
profile: {
|
||||||
|
id: null,
|
||||||
|
organizationId: null,
|
||||||
|
organization: null,
|
||||||
|
username: "",
|
||||||
|
upId: "",
|
||||||
|
} satisfies UserProfile,
|
||||||
},
|
},
|
||||||
hasValidLicense: true /* To comply with TS signature */,
|
hasValidLicense: true /* To comply with TS signature */,
|
||||||
expires: "" /* Not used in this context */,
|
expires: "" /* Not used in this context */,
|
||||||
|
upId: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/** @see https://trpc.io/docs/server-side-calls */
|
/** @see https://trpc.io/docs/server-side-calls */
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export async function deleteHandler(req: NextApiRequest) {
|
|||||||
const { prisma, query } = req;
|
const { prisma, query } = req;
|
||||||
const { id } = schemaQueryIdAsString.parse(query);
|
const { id } = schemaQueryIdAsString.parse(query);
|
||||||
await prisma.webhook.delete({ where: { id } });
|
await prisma.webhook.delete({ where: { id } });
|
||||||
return { message: `Schedule with id: ${id} deleted successfully` };
|
return { message: `Webhook with id: ${id} deleted successfully` };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(deleteHandler);
|
export default defaultResponder(deleteHandler);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { init as SentryInit } from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
SentryInit({
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { init as SentryInit } from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
SentryInit({
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
});
|
});
|
||||||
File renamed without changes.
@@ -24,6 +24,7 @@
|
|||||||
"@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",
|
||||||
|
"@types/swagger-ui-react": "^4.18.3",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import "highlight.js/styles/default.css";
|
import "highlight.js/styles/default.css";
|
||||||
|
import { type AppProps } from "next/app";
|
||||||
import "swagger-ui-react/swagger-ui.css";
|
import "swagger-ui-react/swagger-ui.css";
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />;
|
return <Component {...pageProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import type { SwaggerUI } from "swagger-ui-react";
|
|
||||||
|
|
||||||
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
|
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
|
||||||
|
|
||||||
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
|
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -17,8 +16,6 @@ export default function APIDocs() {
|
|||||||
requestSnippets={requestSnippets}
|
requestSnippets={requestSnippets}
|
||||||
plugins={[SnippedGenerator]}
|
plugins={[SnippedGenerator]}
|
||||||
tryItOutEnabled={true}
|
tryItOutEnabled={true}
|
||||||
syntaxHighlight={true}
|
|
||||||
enableCORS={false} // Doesn't seem to work either
|
|
||||||
docExpansion="list"
|
docExpansion="list"
|
||||||
filter={true}
|
filter={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { NextMiddleware, NextRequest } from "next/server";
|
|||||||
import { NextResponse, URLPattern } from "next/server";
|
import { NextResponse, URLPattern } from "next/server";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
|
import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
const ROUTES: [URLPattern, boolean][] = [
|
const ROUTES: [URLPattern, boolean][] = [
|
||||||
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
|
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
|
||||||
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
|
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
|
||||||
@@ -25,9 +27,6 @@ const ROUTES: [URLPattern, boolean][] = [
|
|||||||
enabled,
|
enabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const FUTURE_ROUTES_OVERRIDE_COOKIE_NAME = "x-calcom-future-routes-override";
|
|
||||||
const FUTURE_ROUTES_ENABLED_COOKIE_NAME = "x-calcom-future-routes-enabled";
|
|
||||||
|
|
||||||
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
|
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
|
||||||
|
|
||||||
export const abTestMiddlewareFactory =
|
export const abTestMiddlewareFactory =
|
||||||
|
|||||||
+22
-1
@@ -1,12 +1,33 @@
|
|||||||
import { type TFunction } from "i18next";
|
import { type TFunction } from "i18next";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
import { constructGenericImage } from "@calcom/lib/OgImages";
|
import { constructGenericImage } from "@calcom/lib/OgImages";
|
||||||
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
|
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
|
||||||
import { getFixedT } from "@calcom/lib/server/getFixedT";
|
//@ts-expect-error no type definitions
|
||||||
|
import config from "@calcom/web/next-i18next.config";
|
||||||
|
|
||||||
import { preparePageMetadata } from "@lib/metadata";
|
import { preparePageMetadata } from "@lib/metadata";
|
||||||
|
|
||||||
|
const create = async (locale: string, ns: string) => {
|
||||||
|
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
|
||||||
|
|
||||||
|
const _i18n = i18next.createInstance();
|
||||||
|
_i18n.init({
|
||||||
|
lng: locale,
|
||||||
|
resources: _nextI18Next?.initialI18nStore,
|
||||||
|
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
|
||||||
|
});
|
||||||
|
return _i18n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFixedT = async (locale: string, ns: string) => {
|
||||||
|
const i18n = await create(locale, ns);
|
||||||
|
|
||||||
|
return i18n.getFixedT(locale, ns);
|
||||||
|
};
|
||||||
|
|
||||||
export const _generateMetadata = async (
|
export const _generateMetadata = async (
|
||||||
getTitle: (t: TFunction<string, undefined>) => string,
|
getTitle: (t: TFunction<string, undefined>) => string,
|
||||||
getDescription: (t: TFunction<string, undefined>) => string
|
getDescription: (t: TFunction<string, undefined>) => string
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-type-public-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { headers, cookies } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-type-public-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
|
||||||
|
const { eventData, booking, user, slug } = props;
|
||||||
|
const rescheduleUid = booking?.uid;
|
||||||
|
const { trpc } = await import("@calcom/trpc");
|
||||||
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: eventData.entity.orgSlug ?? null },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const profileName = event?.profile?.name ?? "";
|
||||||
|
const title = event?.title ?? "";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||||
|
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getData,
|
||||||
|
Page: LegacyPage,
|
||||||
|
getLayout: null,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-public-view";
|
||||||
|
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { headers, cookies } from "next/headers";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-public-view";
|
||||||
|
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
|
||||||
|
const { profile, markdownStrippedBio } = props;
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => profile.name,
|
||||||
|
() => markdownStrippedBio
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
|
||||||
|
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { type PageProps, getServerSideProps } from "@pages/booking/[uid]";
|
|
||||||
import OldPage from "@pages/booking/[uid]/embed";
|
|
||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import OldPage from "~/bookings/views/bookings-single-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
|
||||||
|
|
||||||
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import OldPage, { type PageProps } from "@pages/booking/[uid]";
|
|
||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import type { Params, SearchParams } from "app/_types";
|
import type { Params, SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
@@ -8,9 +7,11 @@ import { cookies, headers } from "next/headers";
|
|||||||
|
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import OldPage from "~/bookings/views/bookings-single-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async ({
|
export const generateMetadata = async ({
|
||||||
params,
|
params,
|
||||||
searchParams,
|
searchParams,
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import Page from "@pages/bookings/[status]";
|
|
||||||
import { withAppDirSsg } from "app/WithAppDirSsg";
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { InferGetStaticPropsType } from "next";
|
import type { InferGetStaticPropsType } from "next";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { getStaticProps } from "@lib/bookings/[status]/getStaticProps";
|
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
import Page from "~/bookings/views/bookings-listing-view";
|
||||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
import { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps";
|
||||||
|
|
||||||
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
const getData = withAppDirSsg<Y>(getStaticProps);
|
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||||
@@ -24,6 +22,6 @@ export const generateStaticParams = async () => {
|
|||||||
return validStatuses.map((status) => ({ status }));
|
return validStatuses.map((status) => ({ status }));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData, Page })<"P">;
|
export default WithLayout({ getLayout: null, getData, Page })<"P">;
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import EventTypePageWrapper from "~/event-types/views/event-types-single-view";
|
||||||
|
import { getServerSideProps } from "~/event-types/views/event-types-single-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const { eventType } = await getData(legacyCtx);
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${eventType.title} | ${t("event_type")} | ${APP_NAME}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, Page: EventTypePageWrapper })<"P">;
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import EventTypes from "@pages/event-types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import EventTypes from "~/event-types/views/event-types-listing-view";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -10,4 +9,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("event_types_page_subtitle")
|
(t) => t("event_types_page_subtitle")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default WithLayout({ getLayout, Page: EventTypes })<"P">;
|
export default WithLayout({ getLayout: null, Page: EventTypes })<"P">;
|
||||||
@@ -3,8 +3,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr";
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/insights/getServerSideProps";
|
import { getServerSideProps } from "@lib/insights/getServerSideProps";
|
||||||
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
@@ -16,4 +14,4 @@ export const generateMetadata = async () =>
|
|||||||
|
|
||||||
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
|
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData, Page: LegacyPage });
|
export default WithLayout({ getLayout: null, getData, Page: LegacyPage });
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { type PageProps } from "@pages/team/[slug]";
|
||||||
|
import EmbedPage from "@pages/team/[slug]/embed";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
const teamName = props.team.name || "Nameless Team";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => teamName,
|
||||||
|
() => teamName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: EmbedPage,
|
||||||
|
getData: getEmbedData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import TeamPage, { type PageProps } from "@pages/team/[slug]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
const teamName = props.team.name || "Nameless Team";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => teamName,
|
||||||
|
() => teamName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: TeamPage,
|
||||||
|
getData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import TypePage, { type PageProps } from "@pages/team/[slug]/[type]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
|
||||||
|
const { eventData, user, slug, booking } = props;
|
||||||
|
const entity = eventData.entity;
|
||||||
|
const { trpc } = await import("@calcom/trpc");
|
||||||
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const profileName = event?.profile?.name ?? "";
|
||||||
|
const title = event?.title ?? "";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||||
|
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: TypePage,
|
||||||
|
getData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import TeamPage, { type PageProps } from "@pages/team/[slug]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
const teamName = props.team.name || "Nameless Team";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => teamName,
|
||||||
|
() => teamName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: TeamPage,
|
||||||
|
getData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -16,7 +16,8 @@ export const generateMetadata = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
|
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
|
||||||
const { entity, user, slug, booking } = props;
|
const { eventData, user, slug, booking } = props;
|
||||||
|
const entity = eventData.entity;
|
||||||
const { trpc } = await import("@calcom/trpc");
|
const { trpc } = await import("@calcom/trpc");
|
||||||
const { data: event } = trpc.viewer.public.event.useQuery(
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr";
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/teams/getServerSideProps";
|
import { getServerSideProps } from "@lib/teams/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
@@ -13,4 +11,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("create_manage_teams_collaborative")
|
(t) => t("create_manage_teams_collaborative")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout, Page })<"P">;
|
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout: null, Page })<"P">;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import Page, { type JoinCallPageProps } from "@pages/video/[uid]";
|
|
||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
|
import VideosSingleView from "~/videos/views/videos-single-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/videos/views/videos-single-view.getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -13,6 +13,6 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("quick_video_meeting")
|
(t) => t("quick_video_meeting")
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = withAppDirSsr<JoinCallPageProps>(getServerSideProps);
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
export default WithLayout({ getData, Page: VideosSingleView, getLayout: null })<"P">;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import Page from "@pages/video/meeting-ended/[uid]";
|
|
||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
|
import MeetingEnded from "~/videos/views/videos-meeting-ended-single-view";
|
||||||
|
import { getServerSideProps } from "~/videos/views/videos-meeting-ended-single-view.getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -13,4 +13,4 @@ export const generateMetadata = async () =>
|
|||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
export default WithLayout({ getData, Page: MeetingEnded, getLayout: null })<"P">;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import Page from "@pages/video/meeting-not-started/[uid]";
|
|
||||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import type { PageProps } from "app/_types";
|
import type { PageProps } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
@@ -6,7 +5,8 @@ import { WithLayout } from "app/layoutHOC";
|
|||||||
|
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
|
import MeetingNotStarted from "~/videos/views/videos-meeting-not-started-single-view";
|
||||||
|
import { getServerSideProps } from "~/videos/views/videos-meeting-not-started-single-view.getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: PageProps) => {
|
export const generateMetadata = async ({ params }: PageProps) => {
|
||||||
const booking = await prisma.booking.findUnique({
|
const booking = await prisma.booking.findUnique({
|
||||||
@@ -24,4 +24,4 @@ export const generateMetadata = async ({ params }: PageProps) => {
|
|||||||
|
|
||||||
const getData = withAppDirSsr(getServerSideProps);
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
export default WithLayout({ getData, Page: MeetingNotStarted, getLayout: null })<"P">;
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import LegacyPage from "@pages/video/no-meeting-found";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
import NoMeetingFound from "~/videos/views/videos-no-meeting-found-single-view";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
(t) => t("no_meeting_found"),
|
(t) => t("no_meeting_found"),
|
||||||
@@ -20,4 +21,4 @@ const getData = async (context: GetServerSidePropsContext) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
export default WithLayout({ getData, Page: NoMeetingFound, getLayout: null })<"P">;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
import LegacyPage from "@calcom/features/ee/workflows/pages/index";
|
import LegacyPage from "@calcom/features/ee/workflows/pages/index";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
@@ -10,4 +9,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("workflows_to_automate_notifications")
|
(t) => t("workflows_to_automate_notifications")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default WithLayout({ getLayout, Page: LegacyPage })<"P">;
|
export default WithLayout({ getLayout: null, Page: LegacyPage })<"P">;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useEffect } from "react";
|
||||||
|
|
||||||
import { InstallAppButton } from "@calcom/app-store/components";
|
import { InstallAppButton } from "@calcom/app-store/components";
|
||||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||||
@@ -15,10 +15,12 @@ import {
|
|||||||
AppSkeletonLoader as SkeletonLoader,
|
AppSkeletonLoader as SkeletonLoader,
|
||||||
ShellSubHeading,
|
ShellSubHeading,
|
||||||
Label,
|
Label,
|
||||||
|
showToast,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Calendar } from "@calcom/ui/components/icon";
|
import { Calendar } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
|
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||||
|
|
||||||
import AppListCard from "@components/AppListCard";
|
import AppListCard from "@components/AppListCard";
|
||||||
import AdditionalCalendarSelector from "@components/apps/AdditionalCalendarSelector";
|
import AdditionalCalendarSelector from "@components/apps/AdditionalCalendarSelector";
|
||||||
@@ -182,6 +184,15 @@ function ConnectedCalendarsList(props: Props) {
|
|||||||
export function CalendarListContainer(props: { heading?: boolean; fromOnboarding?: boolean }) {
|
export function CalendarListContainer(props: { heading?: boolean; fromOnboarding?: boolean }) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { heading = true, fromOnboarding } = props;
|
const { heading = true, fromOnboarding } = props;
|
||||||
|
const { error, setQuery: setError } = useRouterQuery("error");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error === "account_already_linked") {
|
||||||
|
showToast(t(error), "error", { id: error });
|
||||||
|
setError(undefined);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const onChanged = () =>
|
const onChanged = () =>
|
||||||
Promise.allSettled([
|
Promise.allSettled([
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
|||||||
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
|
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
|
||||||
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
|
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
|
||||||
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import type { AppFrontendPayload } from "@calcom/types/App";
|
import type { AppFrontendPayload } from "@calcom/types/App";
|
||||||
@@ -125,7 +125,7 @@ export const InstallAppButtonChild = ({
|
|||||||
StartIcon={(props) => (
|
StartIcon={(props) => (
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={team.logo || ""}
|
alt={team.logo || ""}
|
||||||
imageSrc={team.logo || `${CAL_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
|
imageSrc={team.logo || `${WEBAPP_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
|
||||||
size="sm"
|
size="sm"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ export default function InstalledAppsLayout({
|
|||||||
...rest
|
...rest
|
||||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||||
return (
|
return (
|
||||||
<Shell {...rest} hideHeadingOnMobile>
|
<Shell
|
||||||
|
{...rest}
|
||||||
|
title="Installed Apps"
|
||||||
|
description="Manage your installed apps or change settings"
|
||||||
|
hideHeadingOnMobile>
|
||||||
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
|
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
|
||||||
{children}
|
{children}
|
||||||
</AppCategoryNavigation>
|
</AppCategoryNavigation>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import short from "short-uuid";
|
|
||||||
import { v5 as uuidv5 } from "uuid";
|
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
import type { EventNameObjectType } from "@calcom/core/event";
|
import type { EventNameObjectType } from "@calcom/core/event";
|
||||||
@@ -20,8 +19,8 @@ import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
|||||||
import type { EditableSchema } from "@calcom/features/form-builder/schema";
|
import type { EditableSchema } from "@calcom/features/form-builder/schema";
|
||||||
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
|
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
import { APP_NAME, IS_VISUAL_REGRESSION_TESTING, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
|
import { generateHashedLink } from "@calcom/lib/generateHashedLink";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
@@ -32,6 +31,7 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
SettingsToggle,
|
SettingsToggle,
|
||||||
showToast,
|
showToast,
|
||||||
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
@@ -41,22 +41,17 @@ import RequiresConfirmationController from "./RequiresConfirmationController";
|
|||||||
|
|
||||||
const CustomEventTypeModal = dynamic(() => import("@components/eventtype/CustomEventTypeModal"));
|
const CustomEventTypeModal = dynamic(() => import("@components/eventtype/CustomEventTypeModal"));
|
||||||
|
|
||||||
const generateHashedLink = (id: number) => {
|
|
||||||
const translator = short();
|
|
||||||
const seed = `${id}:${new Date().getTime()}`;
|
|
||||||
const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL));
|
|
||||||
return uid;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
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 { data: user } = trpc.viewer.me.useQuery();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
||||||
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
|
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
|
||||||
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
|
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
|
||||||
|
const [useEventTypeDestinationCalendarEmail, setUseEventTypeDestinationCalendarEmail] = useState(
|
||||||
|
formMethods.getValues("useEventTypeDestinationCalendarEmail")
|
||||||
|
);
|
||||||
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
|
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
|
||||||
|
|
||||||
const bookingFields: Prisma.JsonObject = {};
|
const bookingFields: Prisma.JsonObject = {};
|
||||||
@@ -65,7 +60,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const selectedThemeIsDark =
|
const selectedThemeIsDark =
|
||||||
user?.theme === "dark" ||
|
user?.theme === "dark" ||
|
||||||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
formMethods.getValues().bookingFields.forEach(({ name }) => {
|
formMethods.getValues().bookingFields.forEach(({ name }) => {
|
||||||
bookingFields[name] = `${name} input`;
|
bookingFields[name] = `${name} input`;
|
||||||
});
|
});
|
||||||
@@ -82,7 +76,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const [requiresConfirmation, setRequiresConfirmation] = useState(
|
const [requiresConfirmation, setRequiresConfirmation] = useState(
|
||||||
formMethods.getValues("requiresConfirmation")
|
formMethods.getValues("requiresConfirmation")
|
||||||
);
|
);
|
||||||
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
|
const placeholderHashedLink = `${WEBSITE_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
|
||||||
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
|
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
|
||||||
const noShowFeeEnabled =
|
const noShowFeeEnabled =
|
||||||
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
|
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
|
||||||
@@ -107,7 +101,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return field;
|
return field;
|
||||||
})
|
}),
|
||||||
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,6 +120,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
|
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
|
||||||
|
|
||||||
const closeEventNameTip = () => setShowEventNameTip(false);
|
const closeEventNameTip = () => setShowEventNameTip(false);
|
||||||
|
const displayDestinationCalendarSelector =
|
||||||
|
!!connectedCalendarsQuery.data?.connectedCalendars.length && !team;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
@@ -134,7 +131,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
* This will fallback to each user selected destination calendar.
|
* This will fallback to each user selected destination calendar.
|
||||||
*/}
|
*/}
|
||||||
<div className="border-subtle space-y-6 rounded-lg border p-6">
|
<div className="border-subtle space-y-6 rounded-lg border p-6">
|
||||||
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
|
{displayDestinationCalendarSelector && (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -180,6 +177,27 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{displayDestinationCalendarSelector && (
|
||||||
|
<div className="w-full">
|
||||||
|
<Switch
|
||||||
|
tooltip={t("reconnect_calendar_to_use")}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
{t("display_add_to_calendar_organizer")}
|
||||||
|
<InfoIcon className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
checked={useEventTypeDestinationCalendarEmail}
|
||||||
|
onCheckedChange={(val) => {
|
||||||
|
setUseEventTypeDestinationCalendarEmail(val);
|
||||||
|
formMethods.setValue("useEventTypeDestinationCalendarEmail", val, { shouldDirty: true });
|
||||||
|
if (val) {
|
||||||
|
showToast(t("reconnect_calendar_to_use"), "warning");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} isOuterBorder={true} />
|
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} isOuterBorder={true} />
|
||||||
@@ -215,6 +233,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
toggleSwitchAtTheEnd={true}
|
toggleSwitchAtTheEnd={true}
|
||||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||||
title={t("requires_booker_email_verification")}
|
title={t("requires_booker_email_verification")}
|
||||||
|
data-testid="requires-booker-email-verification"
|
||||||
{...shouldLockDisableProps("requiresBookerEmailVerification")}
|
{...shouldLockDisableProps("requiresBookerEmailVerification")}
|
||||||
description={t("description_requires_booker_email_verification")}
|
description={t("description_requires_booker_email_verification")}
|
||||||
checked={value}
|
checked={value}
|
||||||
@@ -231,6 +250,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
toggleSwitchAtTheEnd={true}
|
toggleSwitchAtTheEnd={true}
|
||||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||||
title={t("disable_notes")}
|
title={t("disable_notes")}
|
||||||
|
data-testid="disable-notes"
|
||||||
{...shouldLockDisableProps("hideCalendarNotes")}
|
{...shouldLockDisableProps("hideCalendarNotes")}
|
||||||
description={t("disable_notes_description")}
|
description={t("disable_notes_description")}
|
||||||
checked={value}
|
checked={value}
|
||||||
@@ -252,6 +272,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
)}
|
)}
|
||||||
childrenClassName="lg:ml-0"
|
childrenClassName="lg:ml-0"
|
||||||
title={t("redirect_success_booking")}
|
title={t("redirect_success_booking")}
|
||||||
|
data-testid="redirect-success-booking"
|
||||||
{...successRedirectUrlLocked}
|
{...successRedirectUrlLocked}
|
||||||
description={t("redirect_url_description")}
|
description={t("redirect_url_description")}
|
||||||
checked={redirectUrlVisible}
|
checked={redirectUrlVisible}
|
||||||
@@ -266,6 +287,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
labelSrOnly
|
labelSrOnly
|
||||||
disabled={successRedirectUrlLocked.disabled}
|
disabled={successRedirectUrlLocked.disabled}
|
||||||
placeholder={t("external_redirect_url")}
|
placeholder={t("external_redirect_url")}
|
||||||
|
data-testid="external-redirect-url"
|
||||||
required={redirectUrlVisible}
|
required={redirectUrlVisible}
|
||||||
type="text"
|
type="text"
|
||||||
{...formMethods.register("successRedirectUrl")}
|
{...formMethods.register("successRedirectUrl")}
|
||||||
@@ -274,7 +296,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
"p-1 text-sm text-orange-600",
|
"p-1 text-sm text-orange-600",
|
||||||
formMethods.getValues("successRedirectUrl") ? "block" : "hidden"
|
formMethods.getValues("successRedirectUrl") ? "block" : "hidden"
|
||||||
)}>
|
)}
|
||||||
|
data-testid="redirect-url-warning">
|
||||||
{t("redirect_url_warning")}
|
{t("redirect_url_warning")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,6 +318,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
title={t("enable_private_url")}
|
title={t("enable_private_url")}
|
||||||
Badge={
|
Badge={
|
||||||
<a
|
<a
|
||||||
|
data-testid="hashedLinkCheck-info"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
href="https://cal.com/docs/core-features/event-types/single-use-private-links">
|
href="https://cal.com/docs/core-features/event-types/single-use-private-links">
|
||||||
@@ -305,7 +329,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
description={t("private_link_description", { appName: APP_NAME })}
|
description={t("private_link_description", { appName: APP_NAME })}
|
||||||
checked={hashedLinkVisible}
|
checked={hashedLinkVisible}
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
|
formMethods.setValue("hashedLink", e ? hashedUrl : undefined, { shouldDirty: true });
|
||||||
setHashedLinkVisible(e);
|
setHashedLinkVisible(e);
|
||||||
}}>
|
}}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
@@ -369,10 +393,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
// Enabling seats will disable guests and requiring confirmation until fully supported
|
// Enabling seats will disable guests and requiring confirmation until fully supported
|
||||||
if (e) {
|
if (e) {
|
||||||
toggleGuests(false);
|
toggleGuests(false);
|
||||||
formMethods.setValue("requiresConfirmation", false);
|
formMethods.setValue("requiresConfirmation", false, { shouldDirty: true });
|
||||||
setRequiresConfirmation(false);
|
setRequiresConfirmation(false);
|
||||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
|
||||||
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
|
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue("seatsPerTimeSlot", null);
|
formMethods.setValue("seatsPerTimeSlot", null);
|
||||||
toggleGuests(true);
|
toggleGuests(true);
|
||||||
@@ -398,12 +424,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(Math.abs(Number(e.target.value)));
|
onChange(Math.abs(Number(e.target.value)));
|
||||||
}}
|
}}
|
||||||
|
data-testid="seats-per-time-slot"
|
||||||
/>
|
/>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Controller
|
<Controller
|
||||||
name="seatsShowAttendees"
|
name="seatsShowAttendees"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
|
data-testid="show-attendees"
|
||||||
description={t("show_attendees")}
|
description={t("show_attendees")}
|
||||||
disabled={seatsLocked.disabled}
|
disabled={seatsLocked.disabled}
|
||||||
onChange={(e) => onChange(e)}
|
onChange={(e) => onChange(e)}
|
||||||
@@ -446,6 +474,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
description={t("description_lock_timezone_toggle_on_booking_page")}
|
description={t("description_lock_timezone_toggle_on_booking_page")}
|
||||||
checked={value}
|
checked={value}
|
||||||
onCheckedChange={(e) => onChange(e)}
|
onCheckedChange={(e) => onChange(e)}
|
||||||
|
data-testid="lock-timezone-toggle"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -489,7 +518,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
{showEventNameTip && (
|
{showEventNameTip && (
|
||||||
<CustomEventTypeModal
|
<CustomEventTypeModal
|
||||||
close={closeEventNameTip}
|
close={closeEventNameTip}
|
||||||
setValue={(val: string) => formMethods.setValue("eventName", val)}
|
setValue={(val: string) => formMethods.setValue("eventName", val, { shouldDirty: true })}
|
||||||
defaultValue={formMethods.getValues("eventName")}
|
defaultValue={formMethods.getValues("eventName")}
|
||||||
placeHolder={eventNamePlaceholder}
|
placeHolder={eventNamePlaceholder}
|
||||||
event={eventNameObject}
|
event={eventNameObject}
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
|||||||
const allAppsData = formMethods.watch("metadata")?.apps || {};
|
const allAppsData = formMethods.watch("metadata")?.apps || {};
|
||||||
|
|
||||||
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
||||||
formMethods.setValue("metadata", {
|
formMethods.setValue(
|
||||||
...formMethods.getValues("metadata"),
|
"metadata",
|
||||||
apps: _allAppsData,
|
{
|
||||||
});
|
...formMethods.getValues("metadata"),
|
||||||
|
apps: _allAppsData,
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
|
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||||
);
|
);
|
||||||
|
|
||||||
setValue("availability", value);
|
setValue("availability", value, { shouldDirty: true });
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
@@ -238,7 +238,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!availabilityValue?.value) return;
|
if (!availabilityValue?.value) return;
|
||||||
setValue("schedule", availabilityValue.value);
|
setValue("schedule", availabilityValue.value, { shouldDirty: true });
|
||||||
}, [availabilityValue, setValue]);
|
}, [availabilityValue, setValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -260,7 +260,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
onChange={(selected) => {
|
onChange={(selected) => {
|
||||||
field.onChange(selected?.value || null);
|
field.onChange(selected?.value || null);
|
||||||
if (selected?.value) setValue("availability", selected);
|
if (selected?.value) setValue("availability", selected, { shouldDirty: true });
|
||||||
}}
|
}}
|
||||||
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
|
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
|
||||||
value={availabilityValue}
|
value={availabilityValue}
|
||||||
@@ -298,7 +298,7 @@ const UseCommonScheduleSettingsToggle = ({ eventType }: { eventType: EventTypeSe
|
|||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
onChange(!checked);
|
onChange(!checked);
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
setValue("schedule", null);
|
setValue("schedule", null, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={t("choose_common_schedule_team_event")}
|
title={t("choose_common_schedule_team_event")}
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ const MinimumBookingNoticeInput = React.forwardRef<
|
|||||||
minimumBookingNoticeDisplayValues.type,
|
minimumBookingNoticeDisplayValues.type,
|
||||||
"minutes",
|
"minutes",
|
||||||
minimumBookingNoticeDisplayValues.value
|
minimumBookingNoticeDisplayValues.value
|
||||||
)
|
),
|
||||||
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
|
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
|
||||||
|
|
||||||
@@ -146,9 +147,9 @@ export const EventLimitsTab = () => {
|
|||||||
<div className="border-subtle space-y-6 rounded-lg border p-6">
|
<div className="border-subtle space-y-6 rounded-lg border p-6">
|
||||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
<div className="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">{t("before_event")}</Label>
|
<Label htmlFor="beforeEventBuffer">{t("before_event")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="beforeBufferTime"
|
name="beforeEventBuffer"
|
||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const beforeBufferOptions = [
|
const beforeBufferOptions = [
|
||||||
{
|
{
|
||||||
@@ -176,9 +177,9 @@ export const EventLimitsTab = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Label htmlFor="afterBufferTime">{t("after_event")}</Label>
|
<Label htmlFor="afterEventBuffer">{t("after_event")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="afterBufferTime"
|
name="afterEventBuffer"
|
||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const afterBufferOptions = [
|
const afterBufferOptions = [
|
||||||
{
|
{
|
||||||
@@ -230,7 +231,9 @@ export const EventLimitsTab = () => {
|
|||||||
<Select
|
<Select
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
|
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
slotIntervalOptions.find(
|
slotIntervalOptions.find(
|
||||||
@@ -258,11 +261,15 @@ export const EventLimitsTab = () => {
|
|||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onCheckedChange={(active) => {
|
onCheckedChange={(active) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
formMethods.setValue("bookingLimits", {
|
formMethods.setValue(
|
||||||
PER_DAY: 1,
|
"bookingLimits",
|
||||||
});
|
{
|
||||||
|
PER_DAY: 1,
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue("bookingLimits", {});
|
formMethods.setValue("bookingLimits", {}, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
switchContainerClassName={classNames(
|
switchContainerClassName={classNames(
|
||||||
@@ -279,7 +286,7 @@ export const EventLimitsTab = () => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="onlyShowFirstAvailableSlot"
|
name="onlyShowFirstAvailableSlot"
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const isChecked = value;
|
const isChecked = value;
|
||||||
return (
|
return (
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
@@ -289,7 +296,7 @@ export const EventLimitsTab = () => {
|
|||||||
description={t("limit_booking_only_first_slot_description")}
|
description={t("limit_booking_only_first_slot_description")}
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onCheckedChange={(active) => {
|
onCheckedChange={(active) => {
|
||||||
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
|
onChange(active ?? false);
|
||||||
}}
|
}}
|
||||||
switchContainerClassName={classNames(
|
switchContainerClassName={classNames(
|
||||||
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
|
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
|
||||||
@@ -301,7 +308,7 @@ export const EventLimitsTab = () => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="durationLimits"
|
name="durationLimits"
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||||
return (
|
return (
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
@@ -317,11 +324,11 @@ export const EventLimitsTab = () => {
|
|||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onCheckedChange={(active) => {
|
onCheckedChange={(active) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
formMethods.setValue("durationLimits", {
|
onChange({
|
||||||
PER_DAY: 60,
|
PER_DAY: 60,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue("durationLimits", {});
|
onChange({});
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
@@ -338,7 +345,7 @@ export const EventLimitsTab = () => {
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="periodType"
|
name="periodType"
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const isChecked = value && value !== "UNLIMITED";
|
const isChecked = value && value !== "UNLIMITED";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -353,11 +360,16 @@ export const EventLimitsTab = () => {
|
|||||||
title={t("limit_future_bookings")}
|
title={t("limit_future_bookings")}
|
||||||
description={t("limit_future_bookings_description")}
|
description={t("limit_future_bookings_description")}
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
onCheckedChange={(bool) =>
|
||||||
|
// formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED", { shouldDirty: true })
|
||||||
|
onChange(bool ? "ROLLING" : "UNLIMITED")
|
||||||
|
}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
<RadioGroup.Root
|
<RadioGroup.Root
|
||||||
value={watchPeriodType}
|
value={watchPeriodType}
|
||||||
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
|
onValueChange={(val) =>
|
||||||
|
formMethods.setValue("periodType", val as PeriodType, { shouldDirty: true })
|
||||||
|
}>
|
||||||
{PERIOD_TYPES.map((period) => {
|
{PERIOD_TYPES.map((period) => {
|
||||||
if (period.type === "UNLIMITED") return null;
|
if (period.type === "UNLIMITED") return null;
|
||||||
return (
|
return (
|
||||||
@@ -388,17 +400,21 @@ export const EventLimitsTab = () => {
|
|||||||
options={optionsPeriod}
|
options={optionsPeriod}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
onChange={(opt) =>
|
onChange={(opt) =>
|
||||||
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
|
formMethods.setValue(
|
||||||
|
"periodCountCalendarDays",
|
||||||
|
opt?.value === 1 ? true : false,
|
||||||
|
{ shouldDirty: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
name="periodCoundCalendarDays"
|
name="periodCoundCalendarDays"
|
||||||
value={optionsPeriod.find((opt) => {
|
value={optionsPeriod.find((opt) => {
|
||||||
opt.value ===
|
opt.value ===
|
||||||
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
|
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0);
|
||||||
})}
|
})}
|
||||||
defaultValue={optionsPeriod.find(
|
defaultValue={optionsPeriod.find(
|
||||||
(opt) =>
|
(opt) =>
|
||||||
opt.value ===
|
opt.value ===
|
||||||
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
|
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0)
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -407,12 +423,12 @@ export const EventLimitsTab = () => {
|
|||||||
<div className="me-2 ms-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"
|
||||||
render={() => (
|
render={({ field: { onChange } }) => (
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
startDate={formMethods.getValues("periodDates").startDate}
|
startDate={formMethods.getValues("periodDates").startDate}
|
||||||
endDate={formMethods.getValues("periodDates").endDate}
|
endDate={formMethods.getValues("periodDates").endDate}
|
||||||
onDatesChange={({ startDate, endDate }) => {
|
onDatesChange={({ startDate, endDate }) => {
|
||||||
formMethods.setValue("periodDates", {
|
onChange({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
});
|
});
|
||||||
@@ -446,7 +462,7 @@ export const EventLimitsTab = () => {
|
|||||||
onCheckedChange={(active) => {
|
onCheckedChange={(active) => {
|
||||||
setOffsetToggle(active);
|
setOffsetToggle(active);
|
||||||
if (!active) {
|
if (!active) {
|
||||||
formMethods.setValue("offsetStart", 0);
|
formMethods.setValue("offsetStart", 0, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
@@ -577,11 +593,16 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
|
|||||||
);
|
);
|
||||||
if (!rest || !currentKeys.length) return;
|
if (!rest || !currentKeys.length) return;
|
||||||
//currentDurationLimits is always defined so can be casted
|
//currentDurationLimits is always defined so can be casted
|
||||||
// @ts-expect-error FIXME Fix these typings
|
|
||||||
setValue(propertyName, {
|
setValue(
|
||||||
...watchIntervalLimits,
|
propertyName,
|
||||||
[rest.value]: defaultLimit,
|
// @ts-expect-error FIXME Fix these typings
|
||||||
});
|
{
|
||||||
|
...watchIntervalLimits,
|
||||||
|
[rest.value]: defaultLimit,
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -611,7 +632,7 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
|
|||||||
)}
|
)}
|
||||||
onLimitChange={(intervalLimitKey, val) =>
|
onLimitChange={(intervalLimitKey, val) =>
|
||||||
// @ts-expect-error FIXME Fix these typings
|
// @ts-expect-error FIXME Fix these typings
|
||||||
setValue(`${propertyName}.${intervalLimitKey}`, val)
|
setValue(`${propertyName}.${intervalLimitKey}`, val, { shouldDirty: true })
|
||||||
}
|
}
|
||||||
onDelete={(intervalLimitKey) => {
|
onDelete={(intervalLimitKey) => {
|
||||||
const current = currentIntervalLimits;
|
const current = currentIntervalLimits;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { EventLocationType } from "@calcom/app-store/locations";
|
|||||||
import { getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
|
import { getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
|
||||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { md } from "@calcom/lib/markdownIt";
|
import { md } from "@calcom/lib/markdownIt";
|
||||||
import { slugify } from "@calcom/lib/slugify";
|
import { slugify } from "@calcom/lib/slugify";
|
||||||
@@ -83,7 +83,7 @@ const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
|
|||||||
return mounted ? (
|
return mounted ? (
|
||||||
<Editor
|
<Editor
|
||||||
getText={() => md.render(formMethods.getValues("description") || "")}
|
getText={() => md.render(formMethods.getValues("description") || "")}
|
||||||
setText={(value: string) => formMethods.setValue("description", turndown(value))}
|
setText={(value: string) => formMethods.setValue("description", turndown(value), { shouldDirty: true })}
|
||||||
excludedToolbarItems={["blockType"]}
|
excludedToolbarItems={["blockType"]}
|
||||||
placeholder={t("quick_video_meeting")}
|
placeholder={t("quick_video_meeting")}
|
||||||
editable={isEditable}
|
editable={isEditable}
|
||||||
@@ -401,7 +401,7 @@ export const EventSetupTab = (
|
|||||||
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.
|
||||||
Change it{" "}
|
Change it{" "}
|
||||||
<Link
|
<Link
|
||||||
href={`${CAL_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
|
href={`${WEBAPP_URL}/event-types/${formMethods.getValues("id")}?tabName=advanced`}
|
||||||
className="underline">
|
className="underline">
|
||||||
here.
|
here.
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
@@ -412,7 +412,7 @@ export const EventSetupTab = (
|
|||||||
{isChildrenManagedEventType && !locationAvailable && locationDetails && (
|
{isChildrenManagedEventType && !locationAvailable && locationDetails && (
|
||||||
<p className="pl-1 text-sm leading-none text-red-600">
|
<p className="pl-1 text-sm leading-none text-red-600">
|
||||||
{t("app_not_connected", { appName: locationDetails.name })}{" "}
|
{t("app_not_connected", { appName: locationDetails.name })}{" "}
|
||||||
<a className="underline" href={`${CAL_URL}/apps/${locationDetails.slug}`}>
|
<a className="underline" href={`${WEBAPP_URL}/apps/${locationDetails.slug}`}>
|
||||||
{t("connect_now")}
|
{t("connect_now")}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -444,9 +444,10 @@ export const EventSetupTab = (
|
|||||||
|
|
||||||
const lengthLockedProps = shouldLockDisableProps("length");
|
const lengthLockedProps = shouldLockDisableProps("length");
|
||||||
const descriptionLockedProps = shouldLockDisableProps("description");
|
const descriptionLockedProps = shouldLockDisableProps("description");
|
||||||
|
|
||||||
const urlPrefix = orgBranding
|
const urlPrefix = orgBranding
|
||||||
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
|
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
|
||||||
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
|
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -511,16 +512,16 @@ export const EventSetupTab = (
|
|||||||
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
|
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
|
||||||
if (newOptions.length > 0) {
|
if (newOptions.length > 0) {
|
||||||
setDefaultDuration(newOptions[0]);
|
setDefaultDuration(newOptions[0]);
|
||||||
formMethods.setValue("length", newOptions[0].value);
|
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
|
||||||
} else {
|
} else {
|
||||||
setDefaultDuration(null);
|
setDefaultDuration(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newOptions.length === 1 && defaultDuration === null) {
|
if (newOptions.length === 1 && defaultDuration === null) {
|
||||||
setDefaultDuration(newOptions[0]);
|
setDefaultDuration(newOptions[0]);
|
||||||
formMethods.setValue("length", newOptions[0].value);
|
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
formMethods.setValue("metadata.multipleDuration", values);
|
formMethods.setValue("metadata.multipleDuration", values, { shouldDirty: true });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -541,7 +542,7 @@ export const EventSetupTab = (
|
|||||||
setDefaultDuration(
|
setDefaultDuration(
|
||||||
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
|
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
|
||||||
);
|
);
|
||||||
if (option) formMethods.setValue("length", option.value);
|
if (option) formMethods.setValue("length", option.value, { shouldDirty: true });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -570,12 +571,12 @@ export const EventSetupTab = (
|
|||||||
setMultipleDuration(undefined);
|
setMultipleDuration(undefined);
|
||||||
setSelectedMultipleDuration([]);
|
setSelectedMultipleDuration([]);
|
||||||
setDefaultDuration(null);
|
setDefaultDuration(null);
|
||||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
|
||||||
formMethods.setValue("length", eventType.length);
|
formMethods.setValue("length", eventType.length, { shouldDirty: true });
|
||||||
} else {
|
} else {
|
||||||
setMultipleDuration([]);
|
setMultipleDuration([]);
|
||||||
formMethods.setValue("metadata.multipleDuration", []);
|
formMethods.setValue("metadata.multipleDuration", [], { shouldDirty: true });
|
||||||
formMethods.setValue("length", 0);
|
formMethods.setValue("length", 0, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Trans } from "next-i18next";
|
import { Trans } from "next-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import type { EventTypeSetupProps, FormValues, Host } from "pages/event-types/[type]";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps, Dispatch, SetStateAction } from "react";
|
||||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
import type { Options } from "react-select";
|
import type { Options } from "react-select";
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTea
|
|||||||
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
|
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { SchedulingType } from "@calcom/prisma/enums";
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { Label, Select } from "@calcom/ui";
|
import { Label, Select, SettingsToggle } from "@calcom/ui";
|
||||||
|
|
||||||
interface IUserToValue {
|
interface IUserToValue {
|
||||||
id: number | null;
|
id: number | null;
|
||||||
@@ -21,6 +21,13 @@ interface IUserToValue {
|
|||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TeamMember = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
avatar: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
const mapUserToValue = ({ id, name, username, avatar, email }: IUserToValue, pendingString: string) => ({
|
const mapUserToValue = ({ id, name, username, avatar, email }: IUserToValue, pendingString: string) => ({
|
||||||
value: `${id || ""}`,
|
value: `${id || ""}`,
|
||||||
label: `${name || email || ""}${!username ? ` (${pendingString})` : ""}`,
|
label: `${name || email || ""}${!username ? ` (${pendingString})` : ""}`,
|
||||||
@@ -97,6 +104,43 @@ const ChildrenEventTypesList = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AssignAllTeamMembers = ({
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
|
onActive,
|
||||||
|
onInactive,
|
||||||
|
}: {
|
||||||
|
assignAllTeamMembers: boolean;
|
||||||
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
|
onActive: () => void;
|
||||||
|
onInactive?: () => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { setValue } = useFormContext<FormValues>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller<FormValues>
|
||||||
|
name="assignAllTeamMembers"
|
||||||
|
render={() => (
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("automatically_add_all_team_members")}
|
||||||
|
labelClassName="mt-0.5 font-normal"
|
||||||
|
checked={assignAllTeamMembers}
|
||||||
|
onCheckedChange={(active) => {
|
||||||
|
setValue("assignAllTeamMembers", active, { shouldDirty: true });
|
||||||
|
setAssignAllTeamMembers(active);
|
||||||
|
if (active) {
|
||||||
|
onActive();
|
||||||
|
} else if (!!onInactive) {
|
||||||
|
onInactive();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const CheckedHostField = ({
|
const CheckedHostField = ({
|
||||||
labelText,
|
labelText,
|
||||||
placeholder,
|
placeholder,
|
||||||
@@ -107,18 +151,18 @@ const CheckedHostField = ({
|
|||||||
helperText,
|
helperText,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
labelText: string;
|
labelText?: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
isFixed: boolean;
|
isFixed: boolean;
|
||||||
value: { isFixed: boolean; userId: number }[];
|
value: Host[];
|
||||||
onChange?: (options: { isFixed: boolean; userId: number }[]) => void;
|
onChange?: (options: Host[]) => void;
|
||||||
options?: Options<CheckedSelectOption>;
|
options?: Options<CheckedSelectOption>;
|
||||||
helperText?: React.ReactNode | string;
|
helperText?: React.ReactNode | string;
|
||||||
} & Omit<Partial<ComponentProps<typeof CheckedTeamSelect>>, "onChange" | "value">) => {
|
} & Omit<Partial<ComponentProps<typeof CheckedTeamSelect>>, "onChange" | "value">) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted flex flex-col space-y-5 p-4">
|
<div className="flex flex-col rounded-md">
|
||||||
<div>
|
<div>
|
||||||
<Label>{labelText}</Label>
|
{labelText ? <Label>{labelText}</Label> : <></>}
|
||||||
<CheckedTeamSelect
|
<CheckedTeamSelect
|
||||||
isOptionDisabled={(option) => !!value.find((host) => host.userId.toString() === option.value)}
|
isOptionDisabled={(option) => !!value.find((host) => host.userId.toString() === option.value)}
|
||||||
onChange={(options) => {
|
onChange={(options) => {
|
||||||
@@ -127,23 +171,22 @@ const CheckedHostField = ({
|
|||||||
options.map((option) => ({
|
options.map((option) => ({
|
||||||
isFixed,
|
isFixed,
|
||||||
userId: parseInt(option.value, 10),
|
userId: parseInt(option.value, 10),
|
||||||
|
priority: option.priority ?? 2,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
value={(value || [])
|
value={(value || [])
|
||||||
.filter(({ isFixed: _isFixed }) => isFixed === _isFixed)
|
.filter(({ isFixed: _isFixed }) => isFixed === _isFixed)
|
||||||
.map(
|
.map((host) => {
|
||||||
(host) =>
|
const option = options.find((member) => member.value === host.userId.toString());
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
return option ? { ...option, priority: host.priority ?? 2, isFixed } : options[0];
|
||||||
options.find((member) => member.value === host.userId.toString())!
|
})
|
||||||
)
|
|
||||||
.filter(Boolean)}
|
.filter(Boolean)}
|
||||||
controlShouldRenderValue={false}
|
controlShouldRenderValue={false}
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{helperText && <p className="text-subtle text-sm">{helperText}</p>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -161,76 +204,255 @@ const FixedHostHelper = (
|
|||||||
</Trans>
|
</Trans>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FixedHosts = ({
|
||||||
|
teamMembers,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
|
isRoundRobinEvent = false,
|
||||||
|
}: {
|
||||||
|
value: Host[];
|
||||||
|
onChange: (hosts: Host[]) => void;
|
||||||
|
teamMembers: TeamMember[];
|
||||||
|
assignAllTeamMembers: boolean;
|
||||||
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
|
isRoundRobinEvent?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { getValues, setValue } = useFormContext<FormValues>();
|
||||||
|
|
||||||
|
const hasActiveFixedHosts = isRoundRobinEvent && getValues("hosts").some((host) => host.isFixed);
|
||||||
|
|
||||||
|
const [isDisabled, setIsDisabled] = useState(hasActiveFixedHosts);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-5 rounded-lg">
|
||||||
|
{!isRoundRobinEvent ? (
|
||||||
|
<>
|
||||||
|
<div className="border-subtle mt-5 rounded-t-md border p-6 pb-5">
|
||||||
|
<Label className="mb-1 text-sm font-semibold">{t("fixed_hosts")}</Label>
|
||||||
|
<p className="text-subtle max-w-full break-words text-sm leading-tight">{FixedHostHelper}</p>
|
||||||
|
</div>
|
||||||
|
<div className="border-subtle rounded-b-md border border-t-0">
|
||||||
|
<AddMembersWithSwitch
|
||||||
|
teamMembers={teamMembers}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
automaticAddAllEnabled={!isRoundRobinEvent}
|
||||||
|
isFixed={true}
|
||||||
|
onActive={() =>
|
||||||
|
setValue(
|
||||||
|
"hosts",
|
||||||
|
teamMembers.map((teamMember) => ({
|
||||||
|
isFixed: true,
|
||||||
|
userId: parseInt(teamMember.value, 10),
|
||||||
|
priority: 2,
|
||||||
|
})),
|
||||||
|
{ shouldDirty: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<SettingsToggle
|
||||||
|
toggleSwitchAtTheEnd={true}
|
||||||
|
title={t("fixed_hosts")}
|
||||||
|
description={FixedHostHelper}
|
||||||
|
checked={isDisabled}
|
||||||
|
labelClassName="text-sm"
|
||||||
|
descriptionClassName=" text-sm text-subtle"
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
if (!checked) {
|
||||||
|
const rrHosts = getValues("hosts")
|
||||||
|
.filter((host) => !host.isFixed)
|
||||||
|
.sort((a, b) => (b.priority ?? 2) - (a.priority ?? 2));
|
||||||
|
setValue("hosts", rrHosts, { shouldDirty: true });
|
||||||
|
}
|
||||||
|
setIsDisabled(checked);
|
||||||
|
}}
|
||||||
|
childrenClassName="lg:ml-0">
|
||||||
|
<div className="border-subtle flex flex-col gap-6 rounded-bl-md rounded-br-md border border-t-0">
|
||||||
|
<AddMembersWithSwitch
|
||||||
|
teamMembers={teamMembers}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
automaticAddAllEnabled={!isRoundRobinEvent}
|
||||||
|
isFixed={true}
|
||||||
|
onActive={() =>
|
||||||
|
setValue(
|
||||||
|
"hosts",
|
||||||
|
teamMembers.map((teamMember) => ({
|
||||||
|
isFixed: true,
|
||||||
|
userId: parseInt(teamMember.value, 10),
|
||||||
|
priority: 2,
|
||||||
|
})),
|
||||||
|
{ shouldDirty: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingsToggle>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddMembersWithSwitch = ({
|
||||||
|
teamMembers,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
|
automaticAddAllEnabled,
|
||||||
|
onActive,
|
||||||
|
isFixed,
|
||||||
|
}: {
|
||||||
|
value: Host[];
|
||||||
|
onChange: (hosts: Host[]) => void;
|
||||||
|
teamMembers: TeamMember[];
|
||||||
|
assignAllTeamMembers: boolean;
|
||||||
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
|
automaticAddAllEnabled: boolean;
|
||||||
|
onActive: () => void;
|
||||||
|
isFixed: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { setValue } = useFormContext<FormValues>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md ">
|
||||||
|
<div className="flex flex-col rounded-md px-6 pb-2 pt-6">
|
||||||
|
{automaticAddAllEnabled ? (
|
||||||
|
<div className="mb-2">
|
||||||
|
<AssignAllTeamMembers
|
||||||
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
onActive={onActive}
|
||||||
|
onInactive={() => setValue("hosts", [], { shouldDirty: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{!assignAllTeamMembers || !automaticAddAllEnabled ? (
|
||||||
|
<CheckedHostField
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
isFixed={isFixed}
|
||||||
|
options={teamMembers.sort(sortByLabel)}
|
||||||
|
placeholder={t("add_attendees")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const RoundRobinHosts = ({
|
const RoundRobinHosts = ({
|
||||||
teamMembers,
|
teamMembers,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
}: {
|
}: {
|
||||||
value: { isFixed: boolean; userId: number }[];
|
value: Host[];
|
||||||
onChange: (hosts: { isFixed: boolean; userId: number }[]) => void;
|
onChange: (hosts: Host[]) => void;
|
||||||
teamMembers: {
|
teamMembers: TeamMember[];
|
||||||
value: string;
|
assignAllTeamMembers: boolean;
|
||||||
label: string;
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
avatar: string;
|
|
||||||
email: string;
|
|
||||||
}[];
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
const { setValue } = useFormContext<FormValues>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="rounded-lg ">
|
||||||
<CheckedHostField
|
<div className="border-subtle mt-5 rounded-t-md border p-6 pb-5">
|
||||||
options={teamMembers.sort(sortByLabel)}
|
<Label className="mb-1 text-sm font-semibold">{t("round_robin_hosts")}</Label>
|
||||||
isFixed={true}
|
<p className="text-subtle max-w-full break-words text-sm leading-tight">{t("round_robin_helper")}</p>
|
||||||
onChange={(changeValue) => {
|
</div>
|
||||||
onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]);
|
<div className="border-subtle rounded-b-md border border-t-0">
|
||||||
}}
|
<AddMembersWithSwitch
|
||||||
value={value}
|
teamMembers={teamMembers}
|
||||||
placeholder={t("add_fixed_hosts")}
|
value={value}
|
||||||
labelText={t("fixed_hosts")}
|
onChange={onChange}
|
||||||
helperText={FixedHostHelper}
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
/>
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
<CheckedHostField
|
automaticAddAllEnabled={true}
|
||||||
options={teamMembers.sort(sortByLabel)}
|
isFixed={false}
|
||||||
onChange={(changeValue) => onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue])}
|
onActive={() =>
|
||||||
value={value}
|
setValue(
|
||||||
isFixed={false}
|
"hosts",
|
||||||
placeholder={t("add_attendees")}
|
teamMembers
|
||||||
labelText={t("round_robin_hosts")}
|
.map((teamMember) => ({
|
||||||
helperText={t("round_robin_helper")}
|
isFixed: false,
|
||||||
/>
|
userId: parseInt(teamMember.value, 10),
|
||||||
</>
|
priority: 2,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.priority - a.priority),
|
||||||
|
{ shouldDirty: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChildrenEventTypes = ({
|
const ChildrenEventTypes = ({
|
||||||
childrenEventTypeOptions,
|
childrenEventTypeOptions,
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
}: {
|
}: {
|
||||||
childrenEventTypeOptions: ReturnType<typeof mapMemberToChildrenOption>[];
|
childrenEventTypeOptions: ReturnType<typeof mapMemberToChildrenOption>[];
|
||||||
|
assignAllTeamMembers: boolean;
|
||||||
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { setValue } = useFormContext<FormValues>();
|
||||||
return (
|
return (
|
||||||
<Controller<FormValues>
|
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
|
||||||
name="children"
|
<div className="flex flex-col gap-4">
|
||||||
render={({ field: { onChange, value } }) => (
|
<AssignAllTeamMembers
|
||||||
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
)}
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
/>
|
onActive={() => setValue("children", childrenEventTypeOptions, { shouldDirty: true })}
|
||||||
|
/>
|
||||||
|
{!assignAllTeamMembers ? (
|
||||||
|
<Controller<FormValues>
|
||||||
|
name="children"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<ChildrenEventTypesList value={value} options={childrenEventTypeOptions} onChange={onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Hosts = ({
|
const Hosts = ({
|
||||||
teamMembers,
|
teamMembers,
|
||||||
|
assignAllTeamMembers,
|
||||||
|
setAssignAllTeamMembers,
|
||||||
}: {
|
}: {
|
||||||
teamMembers: {
|
teamMembers: TeamMember[];
|
||||||
value: string;
|
assignAllTeamMembers: boolean;
|
||||||
label: string;
|
setAssignAllTeamMembers: Dispatch<SetStateAction<boolean>>;
|
||||||
avatar: string;
|
|
||||||
email: string;
|
|
||||||
}[];
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
resetField,
|
setValue,
|
||||||
getValues,
|
getValues,
|
||||||
formState: { submitCount },
|
formState: { submitCount },
|
||||||
} = useFormContext<FormValues>();
|
} = useFormContext<FormValues>();
|
||||||
@@ -250,10 +472,12 @@ const Hosts = ({
|
|||||||
initialValue.current = { hosts: getValues("hosts"), schedulingType, submitCount };
|
initialValue.current = { hosts: getValues("hosts"), schedulingType, submitCount };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resetField("hosts", {
|
setValue(
|
||||||
defaultValue: initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
|
"hosts",
|
||||||
});
|
initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
|
||||||
}, [schedulingType, resetField, getValues, submitCount]);
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
}, [schedulingType, setValue, getValues, submitCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller<FormValues>
|
<Controller<FormValues>
|
||||||
@@ -261,26 +485,39 @@ const Hosts = ({
|
|||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const schedulingTypeRender = {
|
const schedulingTypeRender = {
|
||||||
COLLECTIVE: (
|
COLLECTIVE: (
|
||||||
<CheckedHostField
|
<FixedHosts
|
||||||
|
teamMembers={teamMembers}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
isFixed={true}
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
options={teamMembers.sort(sortByLabel)}
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
placeholder={t("add_attendees")}
|
|
||||||
labelText={t("team")}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
ROUND_ROBIN: (
|
ROUND_ROBIN: (
|
||||||
<>
|
<>
|
||||||
<RoundRobinHosts teamMembers={teamMembers} onChange={onChange} value={value} />
|
<FixedHosts
|
||||||
{/*<TextField
|
teamMembers={teamMembers}
|
||||||
required
|
value={value}
|
||||||
type="number"
|
onChange={(changeValue) => {
|
||||||
label={t("minimum_round_robin_hosts_count")}
|
onChange([...value.filter((host: Host) => !host.isFixed), ...changeValue]);
|
||||||
defaultValue={1}
|
}}
|
||||||
{...formMethods.register("minimumHostCount")}
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
addOnSuffix={<>{t("hosts")}</>}
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
/>*/}
|
isRoundRobinEvent={true}
|
||||||
|
/>
|
||||||
|
<RoundRobinHosts
|
||||||
|
teamMembers={teamMembers}
|
||||||
|
value={value}
|
||||||
|
onChange={(changeValue) => {
|
||||||
|
onChange(
|
||||||
|
[...value.filter((host: Host) => host.isFixed), ...changeValue].sort(
|
||||||
|
(a, b) => b.priority - a.priority
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
MANAGED: <></>,
|
MANAGED: <></>,
|
||||||
@@ -320,34 +557,61 @@ export const EventTeamTab = ({
|
|||||||
.filter(pendingMembers)
|
.filter(pendingMembers)
|
||||||
.map((member) => mapUserToValue(member, t("pending")));
|
.map((member) => mapUserToValue(member, t("pending")));
|
||||||
const childrenEventTypeOptions = teamMembers.filter(pendingMembers).map((member) => {
|
const childrenEventTypeOptions = teamMembers.filter(pendingMembers).map((member) => {
|
||||||
return mapMemberToChildrenOption(member, eventType.slug, t("pending"));
|
return mapMemberToChildrenOption(
|
||||||
|
{ ...member, eventTypes: member.eventTypes.filter((et) => et !== eventType.slug) },
|
||||||
|
eventType.slug,
|
||||||
|
t("pending")
|
||||||
|
);
|
||||||
});
|
});
|
||||||
const isManagedEventType = eventType.schedulingType === SchedulingType.MANAGED;
|
const isManagedEventType = eventType.schedulingType === SchedulingType.MANAGED;
|
||||||
|
const { getValues, setValue } = useFormContext<FormValues>();
|
||||||
|
const [assignAllTeamMembers, setAssignAllTeamMembers] = useState<boolean>(
|
||||||
|
getValues("assignAllTeamMembers") ?? false
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{team && !isManagedEventType && (
|
{team && !isManagedEventType && (
|
||||||
<div className="space-y-5">
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="border-subtle flex flex-col rounded-md">
|
||||||
<Label>{t("scheduling_type")}</Label>
|
<div className="border-subtle rounded-t-md border p-6 pb-5">
|
||||||
<Controller<FormValues>
|
<Label className="mb-1 text-sm font-semibold">{t("assignment")}</Label>
|
||||||
name="schedulingType"
|
<p className="text-subtle max-w-full break-words text-sm leading-tight">
|
||||||
render={({ field: { value, onChange } }) => (
|
{t("assignment_description")}
|
||||||
<Select
|
</p>
|
||||||
options={schedulingTypeOptions}
|
</div>
|
||||||
value={schedulingTypeOptions.find((opt) => opt.value === value)}
|
<div className="border-subtle rounded-b-md border border-t-0 p-6">
|
||||||
className="w-full"
|
<Label>{t("scheduling_type")}</Label>
|
||||||
onChange={(val) => {
|
<Controller<FormValues>
|
||||||
onChange(val?.value);
|
name="schedulingType"
|
||||||
}}
|
render={({ field: { value, onChange } }) => (
|
||||||
/>
|
<Select
|
||||||
)}
|
options={schedulingTypeOptions}
|
||||||
/>
|
value={schedulingTypeOptions.find((opt) => opt.value === value)}
|
||||||
|
className="w-full"
|
||||||
|
onChange={(val) => {
|
||||||
|
onChange(val?.value);
|
||||||
|
setValue("assignAllTeamMembers", false, { shouldDirty: true });
|
||||||
|
setAssignAllTeamMembers(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Hosts teamMembers={teamMembersOptions} />
|
<Hosts
|
||||||
</div>
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
teamMembers={teamMembersOptions}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{team && isManagedEventType && (
|
{team && isManagedEventType && (
|
||||||
<ChildrenEventTypes childrenEventTypeOptions={childrenEventTypeOptions} />
|
<ChildrenEventTypes
|
||||||
|
assignAllTeamMembers={assignAllTeamMembers}
|
||||||
|
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||||
|
childrenEventTypeOptions={childrenEventTypeOptions}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { SchedulingType } from "@calcom/prisma/enums";
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||||
|
import type { DialogProps } from "@calcom/ui";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
@@ -133,6 +134,60 @@ function getNavigation({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DeleteDialog({
|
||||||
|
isManagedEvent,
|
||||||
|
eventTypeId,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: { isManagedEvent: string; eventTypeId: number } & Pick<DialogProps, "open" | "onOpenChange">) {
|
||||||
|
const utils = trpc.useContext();
|
||||||
|
const { t } = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
|
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
await utils.viewer.eventTypes.invalidate();
|
||||||
|
showToast(t("event_type_deleted_successfully"), "success");
|
||||||
|
router.push("/event-types");
|
||||||
|
onOpenChange?.(false);
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
const message = `${err.statusCode}: ${err.message}`;
|
||||||
|
showToast(message, "error");
|
||||||
|
onOpenChange?.(false);
|
||||||
|
} else if (err instanceof TRPCClientError) {
|
||||||
|
showToast(err.message, "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<ConfirmationDialogContent
|
||||||
|
isPending={deleteMutation.isPending}
|
||||||
|
variety="danger"
|
||||||
|
title={t(`delete${isManagedEvent}_event_type`)}
|
||||||
|
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||||
|
loadingText={t(`confirm_delete_event_type`)}
|
||||||
|
onConfirm={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
deleteMutation.mutate({ id: eventTypeId });
|
||||||
|
}}>
|
||||||
|
<p className="mt-5">
|
||||||
|
<Trans
|
||||||
|
i18nKey={`delete${isManagedEvent}_event_type_description`}
|
||||||
|
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||||
|
<ul>
|
||||||
|
<li>Members assigned to this event type will also have their event types deleted.</li>
|
||||||
|
<li>Anyone who they've shared their link with will no longer be able to book using it.</li>
|
||||||
|
</ul>
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
</ConfirmationDialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function EventTypeSingleLayout({
|
function EventTypeSingleLayout({
|
||||||
children,
|
children,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -149,9 +204,7 @@ function EventTypeSingleLayout({
|
|||||||
bookerUrl,
|
bookerUrl,
|
||||||
activeWebhooksNumber,
|
activeWebhooksNumber,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const utils = trpc.useContext();
|
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
const hasPermsToDelete =
|
const hasPermsToDelete =
|
||||||
@@ -160,24 +213,6 @@ function EventTypeSingleLayout({
|
|||||||
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
|
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
|
||||||
isUserOrganizationAdmin;
|
isUserOrganizationAdmin;
|
||||||
|
|
||||||
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
|
|
||||||
onSuccess: async () => {
|
|
||||||
await utils.viewer.eventTypes.invalidate();
|
|
||||||
showToast(t("event_type_deleted_successfully"), "success");
|
|
||||||
router.push("/event-types");
|
|
||||||
setDeleteDialogOpen(false);
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
if (err instanceof HttpError) {
|
|
||||||
const message = `${err.statusCode}: ${err.message}`;
|
|
||||||
showToast(message, "error");
|
|
||||||
setDeleteDialogOpen(false);
|
|
||||||
} else if (err instanceof TRPCClientError) {
|
|
||||||
showToast(err.message, "error");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
||||||
formMethods.getValues(),
|
formMethods.getValues(),
|
||||||
t("locked_fields_admin_description"),
|
t("locked_fields_admin_description"),
|
||||||
@@ -308,7 +343,7 @@ function EventTypeSingleLayout({
|
|||||||
id="hiddenSwitch"
|
id="hiddenSwitch"
|
||||||
checked={!formMethods.watch("hidden")}
|
checked={!formMethods.watch("hidden")}
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
formMethods.setValue("hidden", !e);
|
formMethods.setValue("hidden", !e, { shouldDirty: true });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,7 +387,7 @@ function EventTypeSingleLayout({
|
|||||||
StartIcon={Code}
|
StartIcon={Code}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="icon"
|
variant="icon"
|
||||||
namespace={eventType.slug}
|
namespace=""
|
||||||
tooltip={t("embed")}
|
tooltip={t("embed")}
|
||||||
tooltipSide="bottom"
|
tooltipSide="bottom"
|
||||||
tooltipOffset={4}
|
tooltipOffset={4}
|
||||||
@@ -424,7 +459,7 @@ function EventTypeSingleLayout({
|
|||||||
id="hiddenSwitch"
|
id="hiddenSwitch"
|
||||||
checked={!formMethods.watch("hidden")}
|
checked={!formMethods.watch("hidden")}
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
formMethods.setValue("hidden", !e);
|
formMethods.setValue("hidden", !e, { shouldDirty: true });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,6 +470,7 @@ function EventTypeSingleLayout({
|
|||||||
className="ml-4 lg:ml-0"
|
className="ml-4 lg:ml-0"
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isUpdateMutationLoading}
|
loading={isUpdateMutationLoading}
|
||||||
|
disabled={!formMethods.formState.isDirty}
|
||||||
data-testid="update-eventtype"
|
data-testid="update-eventtype"
|
||||||
form="event-type-form">
|
form="event-type-form">
|
||||||
{t("save")}
|
{t("save")}
|
||||||
@@ -467,31 +503,13 @@ function EventTypeSingleLayout({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<DeleteDialog
|
||||||
<ConfirmationDialogContent
|
eventTypeId={eventType.id}
|
||||||
isPending={deleteMutation.isPending}
|
isManagedEvent={isManagedEvent}
|
||||||
variety="danger"
|
open={deleteDialogOpen}
|
||||||
title={t(`delete${isManagedEvent}_event_type`)}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
/>
|
||||||
loadingText={t(`confirm_delete_event_type`)}
|
|
||||||
onConfirm={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
deleteMutation.mutate({ id: formMethods.getValues("id") });
|
|
||||||
}}>
|
|
||||||
<p className="mt-5">
|
|
||||||
<Trans
|
|
||||||
i18nKey={`delete${isManagedEvent}_event_type_description`}
|
|
||||||
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
|
||||||
<ul>
|
|
||||||
<li>Members assigned to this event type will also have their event types deleted.</li>
|
|
||||||
<li>
|
|
||||||
Anyone who they've shared their link with will no longer be able to book using it.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Trans>
|
|
||||||
</p>
|
|
||||||
</ConfirmationDialogContent>
|
|
||||||
</Dialog>
|
|
||||||
<EventTypeEmbedDialog />
|
<EventTypeEmbedDialog />
|
||||||
</Shell>
|
</Shell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ export default function InstantEventController({
|
|||||||
data-testid="instant-event-check"
|
data-testid="instant-event-check"
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
formMethods.setValue("isInstantEvent", false);
|
formMethods.setValue("isInstantEvent", false, { shouldDirty: true });
|
||||||
setInstantEventState(false);
|
setInstantEventState(false);
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue("isInstantEvent", true);
|
formMethods.setValue("isInstantEvent", true, { shouldDirty: true });
|
||||||
setInstantEventState(true);
|
setInstantEventState(true);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function RecurringEventController({
|
|||||||
data-testid="recurring-event-check"
|
data-testid="recurring-event-check"
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
formMethods.setValue("recurringEvent", null);
|
formMethods.setValue("recurringEvent", null, { shouldDirty: true });
|
||||||
setRecurringEventState(null);
|
setRecurringEventState(null);
|
||||||
} else {
|
} else {
|
||||||
const newVal = eventType.recurringEvent || {
|
const newVal = eventType.recurringEvent || {
|
||||||
@@ -74,7 +74,7 @@ export default function RecurringEventController({
|
|||||||
count: 12,
|
count: 12,
|
||||||
freq: Frequency.WEEKLY,
|
freq: Frequency.WEEKLY,
|
||||||
};
|
};
|
||||||
formMethods.setValue("recurringEvent", newVal);
|
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||||
setRecurringEventState(newVal);
|
setRecurringEventState(newVal);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
@@ -95,7 +95,7 @@ export default function RecurringEventController({
|
|||||||
...recurringEventState,
|
...recurringEventState,
|
||||||
interval: parseInt(event?.target.value),
|
interval: parseInt(event?.target.value),
|
||||||
};
|
};
|
||||||
formMethods.setValue("recurringEvent", newVal);
|
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||||
setRecurringEventState(newVal);
|
setRecurringEventState(newVal);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -110,7 +110,7 @@ export default function RecurringEventController({
|
|||||||
...recurringEventState,
|
...recurringEventState,
|
||||||
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
|
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
|
||||||
};
|
};
|
||||||
formMethods.setValue("recurringEvent", newVal);
|
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||||
setRecurringEventState(newVal);
|
setRecurringEventState(newVal);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -129,7 +129,7 @@ export default function RecurringEventController({
|
|||||||
...recurringEventState,
|
...recurringEventState,
|
||||||
count: parseInt(event?.target.value),
|
count: parseInt(event?.target.value),
|
||||||
};
|
};
|
||||||
formMethods.setValue("recurringEvent", newVal);
|
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||||
setRecurringEventState(newVal);
|
setRecurringEventState(newVal);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function RequiresConfirmationController({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!requiresConfirmation) {
|
if (!requiresConfirmation) {
|
||||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
|
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [requiresConfirmation]);
|
}, [requiresConfirmation]);
|
||||||
@@ -75,13 +75,14 @@ export default function RequiresConfirmationController({
|
|||||||
)}
|
)}
|
||||||
childrenClassName="lg:ml-0"
|
childrenClassName="lg:ml-0"
|
||||||
title={t("requires_confirmation")}
|
title={t("requires_confirmation")}
|
||||||
|
data-testid="requires-confirmation"
|
||||||
disabled={seatsEnabled || requiresConfirmationLockedProps.disabled}
|
disabled={seatsEnabled || requiresConfirmationLockedProps.disabled}
|
||||||
tooltip={seatsEnabled ? t("seat_options_doesnt_support_confirmation") : undefined}
|
tooltip={seatsEnabled ? t("seat_options_doesnt_support_confirmation") : undefined}
|
||||||
description={t("requires_confirmation_description")}
|
description={t("requires_confirmation_description")}
|
||||||
checked={requiresConfirmation}
|
checked={requiresConfirmation}
|
||||||
LockedIcon={requiresConfirmationLockedProps.LockedIcon}
|
LockedIcon={requiresConfirmationLockedProps.LockedIcon}
|
||||||
onCheckedChange={(val) => {
|
onCheckedChange={(val) => {
|
||||||
formMethods.setValue("requiresConfirmation", val);
|
formMethods.setValue("requiresConfirmation", val, { shouldDirty: true });
|
||||||
onRequiresConfirmation(val);
|
onRequiresConfirmation(val);
|
||||||
}}>
|
}}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
@@ -95,16 +96,19 @@ export default function RequiresConfirmationController({
|
|||||||
}
|
}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
if (val === "always") {
|
if (val === "always") {
|
||||||
formMethods.setValue("requiresConfirmation", true);
|
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
|
||||||
onRequiresConfirmation(true);
|
onRequiresConfirmation(true);
|
||||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
|
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
setRequiresConfirmationSetup(undefined);
|
setRequiresConfirmationSetup(undefined);
|
||||||
} else if (val === "notice") {
|
} else if (val === "notice") {
|
||||||
formMethods.setValue("requiresConfirmation", true);
|
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
|
||||||
onRequiresConfirmation(true);
|
onRequiresConfirmation(true);
|
||||||
formMethods.setValue(
|
formMethods.setValue(
|
||||||
"metadata.requiresConfirmationThreshold",
|
"metadata.requiresConfirmationThreshold",
|
||||||
requiresConfirmationSetup || defaultRequiresConfirmationSetup
|
requiresConfirmationSetup || defaultRequiresConfirmationSetup,
|
||||||
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
@@ -145,7 +149,8 @@ export default function RequiresConfirmationController({
|
|||||||
});
|
});
|
||||||
formMethods.setValue(
|
formMethods.setValue(
|
||||||
"metadata.requiresConfirmationThreshold.time",
|
"metadata.requiresConfirmationThreshold.time",
|
||||||
val
|
val,
|
||||||
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
|
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
|
||||||
@@ -170,7 +175,8 @@ export default function RequiresConfirmationController({
|
|||||||
});
|
});
|
||||||
formMethods.setValue(
|
formMethods.setValue(
|
||||||
"metadata.requiresConfirmationThreshold.unit",
|
"metadata.requiresConfirmationThreshold.unit",
|
||||||
opt?.value as UnitTypeLongPlural
|
opt?.value as UnitTypeLongPlural,
|
||||||
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { md } from "@calcom/lib/markdownIt";
|
|||||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import turndown from "@calcom/lib/turndownService";
|
import turndown from "@calcom/lib/turndownService";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { Ensure } from "@calcom/types/utils";
|
|
||||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||||
import { UserAvatar } from "@calcom/ui";
|
import { UserAvatar } from "@calcom/ui";
|
||||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
@@ -97,18 +96,10 @@ const UserProfile = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const organization =
|
|
||||||
user.organization && user.organization.id
|
|
||||||
? {
|
|
||||||
...(user.organization as Ensure<typeof user.organization, "id">),
|
|
||||||
slug: user.organization.slug || null,
|
|
||||||
requestedSlug: user.organization.metadata?.requestedSlug || null,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className="flex flex-row items-center justify-start rtl:justify-end">
|
<div className="flex flex-row items-center justify-start rtl:justify-end">
|
||||||
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} organization={organization} />}
|
{user && <UserAvatar size="lg" user={user} previewSrc={imageSrc} />}
|
||||||
<input
|
<input
|
||||||
ref={avatarRef}
|
ref={avatarRef}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import classNames from "classnames";
|
|||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
import * as z from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
|
import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
|
||||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { Controller, FormProvider, useForm, useFormState } from "react-hook-form";
|
import { Controller, FormProvider, useForm, useFormState } from "react-hook-form";
|
||||||
import * as z from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { CONSOLE_URL } from "@calcom/lib/constants";
|
import { CONSOLE_URL } from "@calcom/lib/constants";
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
|||||||
import { md } from "@calcom/lib/markdownIt";
|
import { md } from "@calcom/lib/markdownIt";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||||
|
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||||
import { UserAvatar } from "@calcom/ui";
|
import { UserAvatar } from "@calcom/ui";
|
||||||
|
|
||||||
type TeamType = Omit<NonNullable<TeamWithMembers>, "inviteToken">;
|
type TeamType = Omit<NonNullable<TeamWithMembers>, "inviteToken">;
|
||||||
type MembersType = TeamType["members"];
|
type MembersType = TeamType["members"];
|
||||||
type MemberType = Pick<MembersType[number], "id" | "name" | "bio" | "username" | "organizationId"> & {
|
type MemberType = Pick<
|
||||||
|
MembersType[number],
|
||||||
|
"id" | "name" | "bio" | "username" | "organizationId" | "avatarUrl"
|
||||||
|
> & {
|
||||||
|
profile: Omit<UserProfile, "upId">;
|
||||||
safeBio: string | null;
|
safeBio: string | null;
|
||||||
bookerUrl: string;
|
bookerUrl: string;
|
||||||
};
|
};
|
||||||
@@ -27,7 +32,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||||||
key={member.id}
|
key={member.id}
|
||||||
href={{ pathname: `${member.bookerUrl}/${member.username}`, query: queryParamsToForward }}>
|
href={{ pathname: `${member.bookerUrl}/${member.username}`, query: queryParamsToForward }}>
|
||||||
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
|
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
|
||||||
<UserAvatar size="md" user={member} />
|
<UserAvatar noOrganizationIndicator size="md" user={member} />
|
||||||
<section className="mt-2 line-clamp-4 w-full space-y-1">
|
<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="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
|
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
|
||||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||||
import { CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
|
import { WEBSITE_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||||
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||||
@@ -45,7 +45,7 @@ export const UsernameAvailabilityField = ({
|
|||||||
|
|
||||||
const usernamePrefix = orgBranding
|
const usernamePrefix = orgBranding
|
||||||
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
|
? orgBranding?.fullDomain.replace(/^(https?:|)\/\//, "")
|
||||||
: `${CAL_URL?.replace(/^(https?:|)\/\//, "")}`;
|
: `${WEBSITE_URL?.replace(/^(https?:|)\/\//, "")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function LocationSelect(props: Props<LocationOption, false, Group
|
|||||||
<Select<LocationOption>
|
<Select<LocationOption>
|
||||||
name="location"
|
name="location"
|
||||||
id="location-select"
|
id="location-select"
|
||||||
|
data-testid="location-select"
|
||||||
components={{
|
components={{
|
||||||
Option: (props) => {
|
Option: (props) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
+33
-15
@@ -1,4 +1,5 @@
|
|||||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||||
|
import { bookingResponsesDbSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
@@ -115,21 +116,23 @@ export const handleSeatsEventTypeOnBooking = async (
|
|||||||
}>
|
}>
|
||||||
>,
|
>,
|
||||||
seatReferenceUid?: string,
|
seatReferenceUid?: string,
|
||||||
userId?: number
|
isHost?: boolean
|
||||||
) => {
|
) => {
|
||||||
if (eventType?.seatsPerTimeSlot !== null) {
|
bookingInfo["responses"] = {};
|
||||||
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
|
type seatAttendee = {
|
||||||
delete bookingInfo.description;
|
attendee: {
|
||||||
} else {
|
email: string;
|
||||||
return;
|
name: string;
|
||||||
}
|
};
|
||||||
// @TODO: If handling teams, we need to do more check ups for this.
|
id: number;
|
||||||
if (bookingInfo?.user?.id === userId) {
|
data: Prisma.JsonValue;
|
||||||
return;
|
bookingId: number;
|
||||||
}
|
attendeeId: number;
|
||||||
|
referenceUid: string;
|
||||||
if (!eventType.seatsShowAttendees) {
|
} | null;
|
||||||
const seatAttendee = await prisma.bookingSeat.findFirst({
|
let seatAttendee: seatAttendee = null;
|
||||||
|
if (seatReferenceUid) {
|
||||||
|
seatAttendee = await prisma.bookingSeat.findFirst({
|
||||||
where: {
|
where: {
|
||||||
referenceUid: seatReferenceUid,
|
referenceUid: seatReferenceUid,
|
||||||
},
|
},
|
||||||
@@ -142,16 +145,31 @@ export const handleSeatsEventTypeOnBooking = async (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (seatAttendee) {
|
||||||
|
const seatAttendeeData = seatAttendee.data as unknown as {
|
||||||
|
description?: string;
|
||||||
|
responses: Prisma.JsonValue;
|
||||||
|
};
|
||||||
|
bookingInfo["description"] = seatAttendeeData.description ?? null;
|
||||||
|
bookingInfo["responses"] = bookingResponsesDbSchema.parse(seatAttendeeData.responses ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventType.seatsShowAttendees && !isHost) {
|
||||||
if (seatAttendee) {
|
if (seatAttendee) {
|
||||||
const attendee = bookingInfo?.attendees?.find((a) => {
|
const attendee = bookingInfo?.attendees?.find((a) => {
|
||||||
return a.email === seatAttendee.attendee?.email;
|
return a.email === seatAttendee?.attendee?.email;
|
||||||
});
|
});
|
||||||
bookingInfo["attendees"] = attendee ? [attendee] : [];
|
bookingInfo["attendees"] = attendee ? [attendee] : [];
|
||||||
} else {
|
} else {
|
||||||
bookingInfo["attendees"] = [];
|
bookingInfo["attendees"] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // @TODO: If handling teams, we need to do more check ups for this.
|
||||||
|
// if (bookingInfo?.user?.id === userId) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
return bookingInfo;
|
return bookingInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
||||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
@@ -56,19 +57,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
|||||||
return notFound;
|
return notFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
const [user] = await UserRepository.findUsersByUsername({
|
||||||
where: {
|
usernameList: [username],
|
||||||
username,
|
orgSlug: org,
|
||||||
organization: isValidOrgDomain
|
|
||||||
? {
|
|
||||||
slug: currentOrgDomain,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
away: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const session = await getServerSession({ req: context.req, res: context.res });
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: "/auth/login",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { props: { trpcState: ssr.dehydrate() } };
|
||||||
|
};
|
||||||
@@ -11,7 +11,12 @@ export default function useRouterQuery<T extends string>(name: T) {
|
|||||||
const setQuery = useCallback(
|
const setQuery = useCallback(
|
||||||
(newValue: string | number | null | undefined) => {
|
(newValue: string | number | null | undefined) => {
|
||||||
const _searchParams = new URLSearchParams(searchParams ?? undefined);
|
const _searchParams = new URLSearchParams(searchParams ?? undefined);
|
||||||
_searchParams.set(name, newValue as string);
|
if (typeof newValue === "undefined") {
|
||||||
|
// when newValue is of type undefined, clear the search param.
|
||||||
|
_searchParams.delete(name);
|
||||||
|
} else {
|
||||||
|
_searchParams.set(name, newValue as string);
|
||||||
|
}
|
||||||
router.replace(`${pathname}?${_searchParams.toString()}`);
|
router.replace(`${pathname}?${_searchParams.toString()}`);
|
||||||
},
|
},
|
||||||
[name, pathname, router, searchParams]
|
[name, pathname, router, searchParams]
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/or
|
|||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { getServerSideProps as GSSUserTypePage } from "@lib/[user]/[type]/getServerSideProps";
|
|
||||||
import { getServerSideProps as GSSTeamTypePage } from "@lib/team/[slug]/[type]/getServerSideProps";
|
import { getServerSideProps as GSSTeamTypePage } from "@lib/team/[slug]/[type]/getServerSideProps";
|
||||||
|
|
||||||
|
import { getServerSideProps as GSSUserTypePage } from "~/users/views/users-type-public-view.getServerSideProps";
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
orgSlug: z.string().transform((s) => slugify(s)),
|
orgSlug: z.string().transform((s) => slugify(s)),
|
||||||
user: z.string(),
|
user: z.string(),
|
||||||
|
|||||||
@@ -1589,17 +1589,20 @@ async function createTeamOutsideOrg(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUserOutsideOrg(data: Omit<Prisma.UserCreateArgs["data"], "organization">) {
|
async function createUserOutsideOrg(
|
||||||
|
data: Omit<Prisma.UserCreateArgs["data"], "organization" | "movedToProfile">
|
||||||
|
) {
|
||||||
return await prismock.user.create({
|
return await prismock.user.create({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
movedToProfileId: null,
|
||||||
organizationId: null,
|
organizationId: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUserInsideTheOrg(
|
async function createUserInsideTheOrg(
|
||||||
data: Omit<Prisma.UserUncheckedCreateInput, "organization" | "organizationId" | "id">,
|
data: Omit<Prisma.UserUncheckedCreateInput, "organization" | "organizationId" | "id" | "movedToProfileId">,
|
||||||
orgId: number
|
orgId: number
|
||||||
) {
|
) {
|
||||||
const org = await prismock.team.findUnique({
|
const org = await prismock.team.findUnique({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomai
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||||
|
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Team, User } from "@calcom/prisma/client";
|
import type { Team, User } from "@calcom/prisma/client";
|
||||||
import { RedirectType } from "@calcom/prisma/client";
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
@@ -327,7 +328,7 @@ async function setOrgSlugIfNotSet(
|
|||||||
slug: string | null;
|
slug: string | null;
|
||||||
},
|
},
|
||||||
orgMetadata: {
|
orgMetadata: {
|
||||||
requestedSlug?: string | undefined;
|
requestedSlug?: string | null | undefined;
|
||||||
},
|
},
|
||||||
targetOrgId: number
|
targetOrgId: number
|
||||||
) {
|
) {
|
||||||
@@ -348,7 +349,7 @@ async function setOrgSlugIfNotSet(
|
|||||||
|
|
||||||
function assertUserPartOfOrgAndRemigrationAllowed(
|
function assertUserPartOfOrgAndRemigrationAllowed(
|
||||||
userToMoveToOrg: {
|
userToMoveToOrg: {
|
||||||
organizationId: User["organizationId"];
|
organizationId: number | null;
|
||||||
},
|
},
|
||||||
targetOrgId: number,
|
targetOrgId: number,
|
||||||
targetOrgUsername: string,
|
targetOrgUsername: string,
|
||||||
@@ -384,7 +385,7 @@ async function getTeamOrThrowError(targetOrgId: number) {
|
|||||||
|
|
||||||
function assertUserPartOfOtherOrg(
|
function assertUserPartOfOtherOrg(
|
||||||
userToMoveToOrg: {
|
userToMoveToOrg: {
|
||||||
organizationId: User["organizationId"];
|
organizationId: number | null;
|
||||||
} | null,
|
} | null,
|
||||||
userName: string | undefined,
|
userName: string | undefined,
|
||||||
userId: number | undefined,
|
userId: number | undefined,
|
||||||
@@ -594,6 +595,35 @@ async function dbMoveUserToOrg({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await prisma.profile.upsert({
|
||||||
|
create: {
|
||||||
|
uid: ProfileRepository.generateProfileUid(),
|
||||||
|
userId: userToMoveToOrg.id,
|
||||||
|
organizationId: targetOrgId,
|
||||||
|
username: targetOrgUsername,
|
||||||
|
movedFromUser: {
|
||||||
|
connect: {
|
||||||
|
id: userToMoveToOrg.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
organizationId: targetOrgId,
|
||||||
|
username: targetOrgUsername,
|
||||||
|
movedFromUser: {
|
||||||
|
connect: {
|
||||||
|
id: userToMoveToOrg.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
userId_organizationId: {
|
||||||
|
userId: userToMoveToOrg.id,
|
||||||
|
organizationId: targetOrgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveTeamsWithoutMembersToOrg({
|
async function moveTeamsWithoutMembersToOrg({
|
||||||
@@ -841,6 +871,10 @@ async function dbRemoveUserFromOrg({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ProfileRepository.deleteMany({
|
||||||
|
userIds: [userToRemoveFromOrg.id],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeMembership({
|
async function removeMembership({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { GetServerSidePropsContext } from "next";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
@@ -82,12 +82,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
entity: eventData.entity,
|
eventData: {
|
||||||
duration: getMultipleDurationValue(
|
entity: eventData.entity,
|
||||||
eventData.metadata?.multipleDuration,
|
length: eventData.length,
|
||||||
queryDuration,
|
metadata: eventData.metadata,
|
||||||
eventData.length
|
},
|
||||||
),
|
|
||||||
booking,
|
booking,
|
||||||
away: false,
|
away: false,
|
||||||
user: teamSlug,
|
user: teamSlug,
|
||||||
|
|||||||
@@ -18,9 +18,21 @@ import { ssrInit } from "@server/lib/ssr";
|
|||||||
|
|
||||||
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
|
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
|
||||||
|
|
||||||
|
const getTheLastArrayElement = (value: ReadonlyArray<string> | string | undefined): string | undefined => {
|
||||||
|
if (value === undefined || typeof value === "string") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.at(-1);
|
||||||
|
};
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug;
|
const slug = getTheLastArrayElement(context.query.slug) ?? getTheLastArrayElement(context.query.orgSlug);
|
||||||
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
|
||||||
|
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(
|
||||||
|
context.req,
|
||||||
|
context.params?.orgSlug ?? context.query?.orgSlug
|
||||||
|
);
|
||||||
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||||
|
|
||||||
// Provided by Rewrite from next.config.js
|
// Provided by Rewrite from next.config.js
|
||||||
@@ -109,7 +121,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
return {
|
return {
|
||||||
name: member.name,
|
name: member.name,
|
||||||
id: member.id,
|
id: member.id,
|
||||||
|
avatarUrl: member.avatarUrl,
|
||||||
bio: member.bio,
|
bio: member.bio,
|
||||||
|
profile: member.profile,
|
||||||
subteams: member.subteams,
|
subteams: member.subteams,
|
||||||
username: member.username,
|
username: member.username,
|
||||||
accepted: member.accepted,
|
accepted: member.accepted,
|
||||||
|
|||||||
+23
-12
@@ -11,6 +11,14 @@ import { csp } from "@lib/csp";
|
|||||||
|
|
||||||
import { abTestMiddlewareFactory } from "./abTest/middlewareFactory";
|
import { abTestMiddlewareFactory } from "./abTest/middlewareFactory";
|
||||||
|
|
||||||
|
const safeGet = async <T = any>(key: string): Promise<T | undefined> => {
|
||||||
|
try {
|
||||||
|
return get<T>(key);
|
||||||
|
} catch (error) {
|
||||||
|
// Don't crash if EDGE_CONFIG env var is missing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const middleware = async (req: NextRequest): Promise<NextResponse<unknown>> => {
|
const middleware = async (req: NextRequest): Promise<NextResponse<unknown>> => {
|
||||||
const url = req.nextUrl;
|
const url = req.nextUrl;
|
||||||
const requestHeaders = new Headers(req.headers);
|
const requestHeaders = new Headers(req.headers);
|
||||||
@@ -24,18 +32,12 @@ const middleware = async (req: NextRequest): Promise<NextResponse<unknown>> => {
|
|||||||
//
|
//
|
||||||
// - For this reason our matchers are sufficient for an app-wide maintenance page.
|
// - For this reason our matchers are sufficient for an app-wide maintenance page.
|
||||||
//
|
//
|
||||||
try {
|
// Check whether the maintenance page should be shown
|
||||||
// Check whether the maintenance page should be shown
|
const isInMaintenanceMode = await safeGet<boolean>("isInMaintenanceMode");
|
||||||
const isInMaintenanceMode = await get<boolean>("isInMaintenanceMode");
|
// If is in maintenance mode, point the url pathname to the maintenance page
|
||||||
// If is in maintenance mode, point the url pathname to the maintenance page
|
if (isInMaintenanceMode) {
|
||||||
if (isInMaintenanceMode) {
|
req.nextUrl.pathname = `/maintenance`;
|
||||||
req.nextUrl.pathname = `/maintenance`;
|
return NextResponse.rewrite(req.nextUrl);
|
||||||
return NextResponse.rewrite(req.nextUrl);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// show the default page if EDGE_CONFIG env var is missing,
|
|
||||||
// but log the error to the console
|
|
||||||
// console.error(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +62,14 @@ const middleware = async (req: NextRequest): Promise<NextResponse<unknown>> => {
|
|||||||
requestHeaders.set("x-cal-timezone", req.headers.get("x-vercel-ip-timezone") ?? "");
|
requestHeaders.set("x-cal-timezone", req.headers.get("x-vercel-ip-timezone") ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.pathname.startsWith("/api/auth/signup")) {
|
||||||
|
const isSignupDisabled = await safeGet<boolean>("isSignupDisabled");
|
||||||
|
// If is in maintenance mode, point the url pathname to the maintenance page
|
||||||
|
if (isSignupDisabled) {
|
||||||
|
return NextResponse.json({ error: "Signup is disabled" }, { status: 503 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (url.pathname.startsWith("/auth/login") || url.pathname.startsWith("/login")) {
|
if (url.pathname.startsWith("/auth/login") || url.pathname.startsWith("/login")) {
|
||||||
// Use this header to actually enforce CSP, otherwise it is running in Report Only mode on all pages.
|
// Use this header to actually enforce CSP, otherwise it is running in Report Only mode on all pages.
|
||||||
requestHeaders.set("x-csp-enforce", "true");
|
requestHeaders.set("x-csp-enforce", "true");
|
||||||
@@ -114,6 +124,7 @@ export const config = {
|
|||||||
// https://github.com/vercel/next.js/discussions/42458
|
// https://github.com/vercel/next.js/discussions/42458
|
||||||
matcher: [
|
matcher: [
|
||||||
"/:path*/embed",
|
"/:path*/embed",
|
||||||
|
"/api/auth/signup",
|
||||||
"/api/trpc/:path*",
|
"/api/trpc/:path*",
|
||||||
"/login",
|
"/login",
|
||||||
"/auth/login",
|
"/auth/login",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { getTranslations } from "@server/lib/getTranslations";
|
import { getTranslations } from "@server/lib/getTranslations";
|
||||||
|
|
||||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
status: z.enum(validStatuses),
|
status: z.enum(validStatuses),
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { Fragment, useState } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle";
|
||||||
|
import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer";
|
||||||
|
import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery";
|
||||||
|
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback";
|
||||||
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
|
||||||
|
import { Alert, Button, EmptyScreen, HorizontalTabs } from "@calcom/ui";
|
||||||
|
import { Calendar } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
|
||||||
|
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||||
|
|
||||||
|
import BookingListItem from "@components/booking/BookingListItem";
|
||||||
|
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||||
|
|
||||||
|
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
|
||||||
|
type BookingListingStatus = z.infer<NonNullable<typeof filterQuerySchema>>["status"];
|
||||||
|
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
|
||||||
|
|
||||||
|
type RecurringInfo = {
|
||||||
|
recurringEventId: string | null;
|
||||||
|
count: number;
|
||||||
|
firstDate: Date | null;
|
||||||
|
bookings: { [key: string]: Date[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
|
||||||
|
{
|
||||||
|
name: "upcoming",
|
||||||
|
href: "/bookings/upcoming",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unconfirmed",
|
||||||
|
href: "/bookings/unconfirmed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recurring",
|
||||||
|
href: "/bookings/recurring",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "past",
|
||||||
|
href: "/bookings/past",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancelled",
|
||||||
|
href: "/bookings/cancelled",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const descriptionByStatus: Record<NonNullable<BookingListingStatus>, string> = {
|
||||||
|
upcoming: "upcoming_bookings",
|
||||||
|
recurring: "recurring_bookings",
|
||||||
|
past: "past_bookings",
|
||||||
|
cancelled: "cancelled_bookings",
|
||||||
|
unconfirmed: "unconfirmed_bookings",
|
||||||
|
};
|
||||||
|
|
||||||
|
const querySchema = z.object({
|
||||||
|
status: z.enum(validStatuses),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Bookings() {
|
||||||
|
const params = useParamsWithFallback();
|
||||||
|
const { data: filterQuery } = useFilterQuery();
|
||||||
|
const { status } = params ? querySchema.parse(params) : { status: "upcoming" as const };
|
||||||
|
const { t } = useLocale();
|
||||||
|
const user = useMeQuery().data;
|
||||||
|
const [isFiltersVisible, setIsFiltersVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const query = trpc.viewer.bookings.get.useInfiniteQuery(
|
||||||
|
{
|
||||||
|
limit: 10,
|
||||||
|
filters: {
|
||||||
|
...filterQuery,
|
||||||
|
status: filterQuery.status ?? status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// first render has status `undefined`
|
||||||
|
enabled: true,
|
||||||
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Animate page (tab) transitions to look smoothing
|
||||||
|
|
||||||
|
const buttonInView = useInViewObserver(() => {
|
||||||
|
if (!query.isFetching && query.hasNextPage && query.status === "success") {
|
||||||
|
query.fetchNextPage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEmpty = !query.data?.pages[0]?.bookings.length;
|
||||||
|
|
||||||
|
const shownBookings: Record<string, BookingOutput[]> = {};
|
||||||
|
const filterBookings = (booking: BookingOutput) => {
|
||||||
|
if (status === "recurring" || status == "unconfirmed" || status === "cancelled") {
|
||||||
|
if (!booking.recurringEventId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
shownBookings[booking.recurringEventId] !== undefined &&
|
||||||
|
shownBookings[booking.recurringEventId].length > 0
|
||||||
|
) {
|
||||||
|
shownBookings[booking.recurringEventId].push(booking);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shownBookings[booking.recurringEventId] = [booking];
|
||||||
|
} else if (status === "upcoming") {
|
||||||
|
return (
|
||||||
|
dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") !==
|
||||||
|
dayjs().tz(user?.timeZone).format("YYYY-MM-DD")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
let recurringInfoToday: RecurringInfo | undefined;
|
||||||
|
|
||||||
|
const bookingsToday =
|
||||||
|
query.data?.pages.map((page) =>
|
||||||
|
page.bookings.filter((booking: BookingOutput) => {
|
||||||
|
recurringInfoToday = page.recurringInfo.find(
|
||||||
|
(info) => info.recurringEventId === booking.recurringEventId
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
dayjs(booking.startTime).tz(user?.timeZone).format("YYYY-MM-DD") ===
|
||||||
|
dayjs().tz(user?.timeZone).format("YYYY-MM-DD")
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)[0] || [];
|
||||||
|
|
||||||
|
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Shell
|
||||||
|
withoutMain={false}
|
||||||
|
hideHeadingOnMobile
|
||||||
|
heading={t("bookings")}
|
||||||
|
subtitle={t("bookings_description")}
|
||||||
|
title="Bookings"
|
||||||
|
description="Create events to share for people to book on your calendar.">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex flex-row flex-wrap justify-between">
|
||||||
|
<HorizontalTabs tabs={tabs} />
|
||||||
|
<FilterToggle setIsFiltersVisible={setIsFiltersVisible} />
|
||||||
|
</div>
|
||||||
|
<FiltersContainer isFiltersVisible={isFiltersVisible} />
|
||||||
|
<main className="w-full">
|
||||||
|
<div className="flex w-full flex-col" ref={animationParentRef}>
|
||||||
|
{query.status === "error" && (
|
||||||
|
<Alert severity="error" title={t("something_went_wrong")} message={query.error.message} />
|
||||||
|
)}
|
||||||
|
{(query.status === "pending" || query.isPaused) && <SkeletonLoader />}
|
||||||
|
{query.status === "success" && !isEmpty && (
|
||||||
|
<>
|
||||||
|
{!!bookingsToday.length && status === "upcoming" && (
|
||||||
|
<div className="mb-6 pt-2 xl:pt-0">
|
||||||
|
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
|
||||||
|
<p className="text-subtle mb-2 text-xs font-medium uppercase leading-4">{t("today")}</p>
|
||||||
|
<div className="border-subtle overflow-hidden rounded-md border">
|
||||||
|
<table className="w-full max-w-full table-fixed">
|
||||||
|
<tbody className="bg-default divide-subtle divide-y" data-testid="today-bookings">
|
||||||
|
<Fragment>
|
||||||
|
{bookingsToday.map((booking: BookingOutput) => (
|
||||||
|
<BookingListItem
|
||||||
|
key={booking.id}
|
||||||
|
loggedInUser={{
|
||||||
|
userId: user?.id,
|
||||||
|
userTimeZone: user?.timeZone,
|
||||||
|
userTimeFormat: user?.timeFormat,
|
||||||
|
userEmail: user?.email,
|
||||||
|
}}
|
||||||
|
listingStatus={status}
|
||||||
|
recurringInfo={recurringInfoToday}
|
||||||
|
{...booking}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="pt-2 xl:pt-0">
|
||||||
|
<div className="border-subtle overflow-hidden rounded-md border">
|
||||||
|
<table data-testid={`${status}-bookings`} className="w-full max-w-full table-fixed">
|
||||||
|
<tbody className="bg-default divide-subtle divide-y" data-testid="bookings">
|
||||||
|
{query.data.pages.map((page, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
|
||||||
|
const recurringInfo = page.recurringInfo.find(
|
||||||
|
(info) => info.recurringEventId === booking.recurringEventId
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<BookingListItem
|
||||||
|
key={booking.id}
|
||||||
|
loggedInUser={{
|
||||||
|
userId: user?.id,
|
||||||
|
userTimeZone: user?.timeZone,
|
||||||
|
userTimeFormat: user?.timeFormat,
|
||||||
|
userEmail: user?.email,
|
||||||
|
}}
|
||||||
|
listingStatus={status}
|
||||||
|
recurringInfo={recurringInfo}
|
||||||
|
{...booking}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="text-default p-4 text-center" ref={buttonInView.ref}>
|
||||||
|
<Button
|
||||||
|
color="minimal"
|
||||||
|
loading={query.isFetchingNextPage}
|
||||||
|
disabled={!query.hasNextPage}
|
||||||
|
onClick={() => query.fetchNextPage()}>
|
||||||
|
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{query.status === "success" && isEmpty && (
|
||||||
|
<div className="flex items-center justify-center pt-2 xl:pt-0">
|
||||||
|
<EmptyScreen
|
||||||
|
Icon={Calendar}
|
||||||
|
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
|
||||||
|
description={t("no_status_bookings_yet_description", {
|
||||||
|
status: t(status).toLowerCase(),
|
||||||
|
description: t(descriptionByStatus[status]),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Shell>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loaded 100 of 628 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user