Merge branch 'main' into rkreddy99/cal-2330
This commit is contained in:
commit
031bece4c0
|
@ -127,4 +127,12 @@ ZOHOCRM_CLIENT_ID=""
|
||||||
ZOHOCRM_CLIENT_SECRET=""
|
ZOHOCRM_CLIENT_SECRET=""
|
||||||
|
|
||||||
|
|
||||||
|
# - REVERT
|
||||||
|
# Used for the Pipedrive integration (via/ Revert (https://revert.dev))
|
||||||
|
# @see https://github.com/calcom/cal.com/#obtaining-revert-api-keys
|
||||||
|
REVERT_API_KEY=
|
||||||
|
REVERT_PUBLIC_TOKEN=
|
||||||
|
|
||||||
|
# NOTE: If you're self hosting Revert, update this URL to point to your own instance.
|
||||||
|
REVERT_API_URL=https://api.revert.dev/
|
||||||
# *********************************************************************************************************
|
# *********************************************************************************************************
|
||||||
|
|
29
.env.example
29
.env.example
|
@ -133,6 +133,11 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME=
|
||||||
# Used for capturing exceptions and logging messages
|
# Used for capturing exceptions and logging messages
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
|
||||||
|
# Formbricks Experience Management Integration
|
||||||
|
FORMBRICKS_HOST_URL=https://app.formbricks.com
|
||||||
|
FORMBRICKS_ENVIRONMENT_ID=
|
||||||
|
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
||||||
|
|
||||||
# Twilio
|
# Twilio
|
||||||
# Used to send SMS reminders in workflows
|
# Used to send SMS reminders in workflows
|
||||||
TWILIO_SID=
|
TWILIO_SID=
|
||||||
|
@ -249,6 +254,18 @@ PROJECT_ID_VERCEL=
|
||||||
TEAM_ID_VERCEL=
|
TEAM_ID_VERCEL=
|
||||||
# Get it from: https://vercel.com/account/tokens
|
# Get it from: https://vercel.com/account/tokens
|
||||||
AUTH_BEARER_TOKEN_VERCEL=
|
AUTH_BEARER_TOKEN_VERCEL=
|
||||||
|
# Add the main domain that you want to use for testing vercel domain management for organizations. This is necessary because WEBAPP_URL of local isn't a valid public domain
|
||||||
|
# Would create org1.example.com for an org with slug org1
|
||||||
|
# LOCAL_TESTING_DOMAIN_VERCEL="example.com"
|
||||||
|
|
||||||
|
## Set it to 1 if you use cloudflare to manage your DNS and would like us to manage the DNS for you for organizations
|
||||||
|
# CLOUDFLARE_DNS=1
|
||||||
|
## Get it from: https://dash.cloudflare.com/profile/api-tokens. Select Edit Zone template and choose a zone(your domain)
|
||||||
|
# AUTH_BEARER_TOKEN_CLOUDFLARE=
|
||||||
|
## Zone ID can be found in the Overview tab of your domain in Cloudflare
|
||||||
|
# CLOUDFLARE_ZONE_ID=
|
||||||
|
## It should usually work with the default value. This is the DNS CNAME record content to point to Vercel domain
|
||||||
|
# CLOUDFLARE_VERCEL_CNAME=cname.vercel-dns.com
|
||||||
|
|
||||||
# - APPLE CALENDAR
|
# - APPLE CALENDAR
|
||||||
# Used for E2E tests on Apple Calendar
|
# Used for E2E tests on Apple Calendar
|
||||||
|
@ -260,6 +277,8 @@ E2E_TEST_APPLE_CALENDAR_PASSWORD=""
|
||||||
E2E_TEST_CALCOM_QA_EMAIL="qa@example.com"
|
E2E_TEST_CALCOM_QA_EMAIL="qa@example.com"
|
||||||
# Replace with your own password
|
# Replace with your own password
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD="password"
|
E2E_TEST_CALCOM_QA_PASSWORD="password"
|
||||||
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS=
|
||||||
|
E2E_TEST_CALCOM_GCAL_KEYS=
|
||||||
|
|
||||||
# - APP CREDENTIAL SYNC ***********************************************************************************
|
# - APP CREDENTIAL SYNC ***********************************************************************************
|
||||||
# Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations
|
# Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations
|
||||||
|
@ -302,3 +321,13 @@ APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
||||||
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
||||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
||||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
||||||
|
APP_ROUTER_BOOKINGS_STATUS_ENABLED=0
|
||||||
|
APP_ROUTER_WORKFLOWS_ENABLED=0
|
||||||
|
APP_ROUTER_SETTINGS_TEAMS_ENABLED=0
|
||||||
|
APP_ROUTER_GETTING_STARTED_STEP_ENABLED=0
|
||||||
|
APP_ROUTER_APPS_ENABLED=0
|
||||||
|
APP_ROUTER_VIDEO_ENABLED=0
|
||||||
|
APP_ROUTER_TEAMS_ENABLED=0
|
||||||
|
|
||||||
|
# disable setry server source maps
|
||||||
|
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
||||||
|
|
|
@ -19,12 +19,12 @@ Fixes # (issue)
|
||||||
|
|
||||||
<!-- Please delete bullets that are not relevant. -->
|
<!-- Please delete bullets that are not relevant. -->
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] Chore (refactoring code, technical debt, workflow improvements)
|
- Chore (refactoring code, technical debt, workflow improvements)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
- [ ] Tests (Unit/Integration/E2E or any other test)
|
- Tests (Unit/Integration/E2E or any other test)
|
||||||
- [ ] This change requires a documentation update
|
- This change requires a documentation update
|
||||||
|
|
||||||
## How should this be tested?
|
## How should this be tested?
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,14 @@ runs:
|
||||||
cache-name: cache-db
|
cache-name: cache-db
|
||||||
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 }}
|
||||||
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
with:
|
with:
|
||||||
path: ${{ inputs.path }}
|
path: ${{ inputs.path }}
|
||||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}
|
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}
|
||||||
- run: yarn db-seed
|
- run: echo ${{ env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }} && yarn db-seed
|
||||||
if: steps.cache-db.outputs.cache-hit != 'true'
|
if: steps.cache-db.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Postgres Dump Backup
|
- name: Postgres Dump Backup
|
||||||
|
|
|
@ -5,7 +5,7 @@ runs:
|
||||||
steps:
|
steps:
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
uses: buildjet/cache@v2
|
uses: buildjet/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/Library/Caches/ms-playwright
|
~/Library/Caches/ms-playwright
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
name: "Apply issue labels to PR"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
label_on_pr:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: none
|
|
||||||
issues: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Apply labels from linked issue to PR
|
|
||||||
uses: actions/github-script@v5
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
async function getLinkedIssues(owner, repo, prNumber) {
|
|
||||||
const query = `query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
||||||
repository(owner: $owner, name: $repo) {
|
|
||||||
pullRequest(number: $prNumber) {
|
|
||||||
closingIssuesReferences(first: 10) {
|
|
||||||
nodes {
|
|
||||||
number
|
|
||||||
labels(first: 10) {
|
|
||||||
nodes {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const variables = {
|
|
||||||
owner: owner,
|
|
||||||
repo: repo,
|
|
||||||
prNumber: prNumber,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await github.graphql(query, variables);
|
|
||||||
return result.repository.pullRequest.closingIssuesReferences.nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pr = context.payload.pull_request;
|
|
||||||
const linkedIssues = await getLinkedIssues(
|
|
||||||
context.repo.owner,
|
|
||||||
context.repo.repo,
|
|
||||||
pr.number
|
|
||||||
);
|
|
||||||
|
|
||||||
const labelsToAdd = new Set();
|
|
||||||
for (const issue of linkedIssues) {
|
|
||||||
if (issue.labels && issue.labels.nodes) {
|
|
||||||
for (const label of issue.labels.nodes) {
|
|
||||||
labelsToAdd.add(label.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelsToAdd.size) {
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
labels: Array.from(labelsToAdd),
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ name: Check types
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max-old-space-size=8192"
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
jobs:
|
jobs:
|
||||||
check-types:
|
check-types:
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
|
|
@ -17,10 +17,11 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v7
|
- uses: actions/stale@v7
|
||||||
with:
|
with:
|
||||||
|
days-before-close: -1
|
||||||
days-before-issue-stale: 60
|
days-before-issue-stale: 60
|
||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
days-before-pr-stale: 14
|
days-before-pr-stale: 14
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: -1
|
||||||
stale-pr-message: "This PR is being marked as stale due to inactivity."
|
stale-pr-message: "This PR is being marked as stale due to inactivity."
|
||||||
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
|
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
|
||||||
operations-per-run: 100
|
operations-per-run: 100
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
name: E2E App-Store Apps
|
name: E2E App-Store Apps Tests
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
jobs:
|
jobs:
|
||||||
e2e-app-store:
|
e2e-app-store:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
@ -29,10 +30,15 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
|
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
|
E2E_TEST_CALCOM_GCAL_KEYS: ${{ secrets.E2E_TEST_CALCOM_GCAL_KEYS }}
|
||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn e2e:app-store --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
run: yarn e2e:app-store --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||||
|
@ -43,6 +49,10 @@ jobs:
|
||||||
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 }}
|
||||||
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
|
E2E_TEST_CALCOM_GCAL_KEYS: ${{ secrets.E2E_TEST_CALCOM_GCAL_KEYS }}
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
@ -65,7 +75,7 @@ jobs:
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: app-store-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
name: app-store-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
||||||
path: test-results
|
path: test-results
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
name: E2E Embed React tests and booking flow(for non-embed as well)
|
name: E2E Embed React tests and booking flow (for non-embed as well)
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
jobs:
|
jobs:
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
@ -24,7 +25,6 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
|
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
|
@ -61,7 +61,7 @@ jobs:
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: embed-react-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
name: embed-react-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
||||||
path: test-results
|
path: test-results
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
name: E2E Embed Core tests and booking flow(for non-embed as well)
|
name: E2E Embed Core tests and booking flow (for non-embed as well)
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
jobs:
|
jobs:
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
@ -29,7 +30,6 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
|
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
|
@ -65,7 +65,7 @@ jobs:
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: embed-core-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
name: embed-core-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
||||||
path: test-results
|
path: test-results
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
name: E2E test
|
name: E2E tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
jobs:
|
jobs:
|
||||||
e2e:
|
e2e:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
@ -24,11 +24,10 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
shard: [1, 2, 3, 4, 5]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
|
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
|
@ -68,7 +67,7 @@ jobs:
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
name: test-results-${{ matrix.shard }}_${{ strategy.job-total }}
|
||||||
path: test-results
|
path: test-results
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
name: "Pull Request Labeler"
|
name: "Pull Request Labeler"
|
||||||
on:
|
on:
|
||||||
- pull_request_target
|
pull_request_target:
|
||||||
|
workflow_dispatch:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
@ -16,3 +17,81 @@ jobs:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
||||||
sync-labels: ""
|
sync-labels: ""
|
||||||
|
team-labels:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: equitybee/team-label-action@main
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||||
|
organization-name: calcom
|
||||||
|
ignore-labels: "app-store, ai, authentication, automated-testing, 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"
|
||||||
|
apply-labels-from-issue:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
|
issues: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Apply labels from linked issue to PR
|
||||||
|
uses: actions/github-script@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
async function getLinkedIssues(owner, repo, prNumber) {
|
||||||
|
const query = `query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number: $prNumber) {
|
||||||
|
closingIssuesReferences(first: 10) {
|
||||||
|
nodes {
|
||||||
|
number
|
||||||
|
labels(first: 10) {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
owner: owner,
|
||||||
|
repo: repo,
|
||||||
|
prNumber: prNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await github.graphql(query, variables);
|
||||||
|
return result.repository.pullRequest.closingIssuesReferences.nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pr = context.payload.pull_request;
|
||||||
|
const linkedIssues = await getLinkedIssues(
|
||||||
|
context.repo.owner,
|
||||||
|
context.repo.repo,
|
||||||
|
pr.number
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelsToAdd = new Set();
|
||||||
|
for (const issue of linkedIssues) {
|
||||||
|
if (issue.labels && issue.labels.nodes) {
|
||||||
|
for (const label of issue.labels.nodes) {
|
||||||
|
labelsToAdd.add(label.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labelsToAdd.size) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
labels: Array.from(labelsToAdd),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
- name: Upload ESLint report
|
- name: Upload ESLint report
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: lint-results
|
name: lint-results
|
||||||
path: lint-results
|
path: lint-results
|
||||||
|
|
|
@ -2,6 +2,7 @@ name: "Next.js Bundle Analysis"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
@ -27,14 +28,14 @@ jobs:
|
||||||
npx -p nextjs-bundle-analysis@0.5.0 report
|
npx -p nextjs-bundle-analysis@0.5.0 report
|
||||||
|
|
||||||
- name: Upload bundle
|
- name: Upload bundle
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: bundle
|
name: bundle
|
||||||
path: apps/web/.next/analyze/__bundle_analysis.json
|
path: apps/web/.next/analyze/__bundle_analysis.json
|
||||||
|
|
||||||
- name: Download base branch bundle stats
|
- name: Download base branch bundle stats
|
||||||
uses: dawidd6/action-download-artifact@v2
|
uses: dawidd6/action-download-artifact@v2
|
||||||
if: success() && github.event.number
|
if: success()
|
||||||
with:
|
with:
|
||||||
workflow: nextjs-bundle-analysis.yml
|
workflow: nextjs-bundle-analysis.yml
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
branch: ${{ github.event.pull_request.base.ref }}
|
||||||
|
@ -54,39 +55,39 @@ jobs:
|
||||||
# Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
|
# Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
|
||||||
# entry in your package.json file.
|
# entry in your package.json file.
|
||||||
- name: Compare with base branch bundle
|
- name: Compare with base branch bundle
|
||||||
if: success() && github.event.number
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
cd apps/web
|
cd apps/web
|
||||||
ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
|
ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
|
||||||
|
|
||||||
- name: Get comment body
|
- name: Get comment body
|
||||||
id: get-comment-body
|
id: get-comment-body
|
||||||
if: success() && github.event.number
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
cd apps/web
|
cd apps/web
|
||||||
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
|
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
|
||||||
body="${body//'%'/'%25'}"
|
body="${body//'%'/'%25'}"
|
||||||
body="${body//$'\n'/'%0A'}"
|
body="${body//$'\n'/'%0A'}"
|
||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
echo ::set-output name=body::$body
|
echo "{name}={$body}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Find Comment
|
- name: Find Comment
|
||||||
uses: peter-evans/find-comment@v1
|
uses: peter-evans/find-comment@v2
|
||||||
if: success() && github.event.number
|
if: success()
|
||||||
id: fc
|
id: fc
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
|
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
|
||||||
|
|
||||||
- name: Create Comment
|
- name: Create Comment
|
||||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
uses: peter-evans/create-or-update-comment@v3
|
||||||
if: success() && github.event.number && steps.fc.outputs.comment-id == 0
|
if: success() && github.event.number && steps.fc.outputs.comment-id == 0
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
body: ${{ steps.get-comment-body.outputs.body }}
|
body: ${{ steps.get-comment-body.outputs.body }}
|
||||||
|
|
||||||
- name: Update Comment
|
- name: Update Comment
|
||||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
uses: peter-evans/create-or-update-comment@v3
|
||||||
if: success() && github.event.number && steps.fc.outputs.comment-id != 0
|
if: success() && github.event.number && steps.fc.outputs.comment-id != 0
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
name: Assign PR team labels
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
team-labels:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: equitybee/team-label-action@main
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
||||||
organization-name: calcom
|
|
||||||
ignore-labels: "app-store, ai, authentication, automated-testing, 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"
|
|
|
@ -4,9 +4,6 @@ on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
- ".github/CODEOWNERS"
|
|
||||||
merge_group:
|
merge_group:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
@ -15,15 +12,21 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
login:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
changes:
|
changes:
|
||||||
name: Detect changes
|
name: Detect changes
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
outputs:
|
outputs:
|
||||||
app-store: ${{ steps.filter.outputs.app-store }}
|
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
|
||||||
embed: ${{ steps.filter.outputs.embed }}
|
|
||||||
embed-react: ${{ steps.filter.outputs.embed-react }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
@ -31,78 +34,83 @@ jobs:
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
app-store:
|
has-files-requiring-all-checks:
|
||||||
- 'apps/web/**'
|
- "!(**.md|.github/CODEOWNERS)"
|
||||||
- 'packages/app-store/**'
|
|
||||||
- 'playwright.config.ts'
|
|
||||||
embed:
|
|
||||||
- 'apps/web/**'
|
|
||||||
- 'packages/embeds/**'
|
|
||||||
- 'playwright.config.ts'
|
|
||||||
embed-react:
|
|
||||||
- 'apps/web/**'
|
|
||||||
- 'packages/embeds/**'
|
|
||||||
- 'playwright.config.ts'
|
|
||||||
|
|
||||||
type-check:
|
type-check:
|
||||||
name: Type check
|
name: Type check
|
||||||
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/check-types.yml
|
uses: ./.github/workflows/check-types.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Unit tests
|
name: Unit tests
|
||||||
uses: ./.github/workflows/test.yml
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
|
uses: ./.github/workflows/unit-tests.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Linters
|
name: Linters
|
||||||
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/lint.yml
|
uses: ./.github/workflows/lint.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Production build
|
name: Production build
|
||||||
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/production-build.yml
|
uses: ./.github/workflows/production-build.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-without-database:
|
build-without-database:
|
||||||
name: Production build (without database)
|
name: Production build (without database)
|
||||||
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/production-build-without-database.yml
|
uses: ./.github/workflows/production-build-without-database.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: E2E tests
|
name: E2E tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e.yml
|
uses: ./.github/workflows/e2e.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-app-store:
|
e2e-app-store:
|
||||||
name: E2E App Store tests
|
name: E2E App Store tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-app-store.yml
|
uses: ./.github/workflows/e2e-app-store.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
name: E2E embeds tests
|
name: E2E embeds tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-embed.yml
|
uses: ./.github/workflows/e2e-embed.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-embed-react:
|
e2e-embed-react:
|
||||||
name: E2E React embeds tests
|
name: E2E React embeds tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-embed-react.yml
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
analyze:
|
analyze:
|
||||||
needs: build
|
name: Analyze Build
|
||||||
|
needs: [changes, build]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/nextjs-bundle-analysis.yml
|
uses: ./.github/workflows/nextjs-bundle-analysis.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
required:
|
required:
|
||||||
needs: [lint, type-check, test, build, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
needs: [changes, lint, type-check, test, build, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: fail if conditional jobs failed
|
- name: fail if conditional jobs failed
|
||||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
if: needs.changes.outputs.has-files-requiring-all-checks == 'true' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled'))
|
||||||
run: exit 1
|
run: exit 1
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
name: Pre-release checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
name: Detect changes
|
||||||
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
outputs:
|
||||||
|
app-store: ${{ steps.filter.outputs.app-store }}
|
||||||
|
embed: ${{ steps.filter.outputs.embed }}
|
||||||
|
embed-react: ${{ steps.filter.outputs.embed-react }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
- uses: dorny/paths-filter@v2
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
app-store:
|
||||||
|
- 'apps/web/**'
|
||||||
|
- 'packages/app-store/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
|
embed:
|
||||||
|
- 'apps/web/**'
|
||||||
|
- 'packages/embeds/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
|
embed-react:
|
||||||
|
- 'apps/web/**'
|
||||||
|
- 'packages/embeds/**'
|
||||||
|
- 'playwright.config.ts'
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Linters
|
||||||
|
uses: ./.github/workflows/lint.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Production build
|
||||||
|
uses: ./.github/workflows/production-build.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
name: E2E tests
|
||||||
|
needs: [changes, lint, build]
|
||||||
|
uses: ./.github/workflows/e2e.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e-app-store:
|
||||||
|
name: E2E App Store tests
|
||||||
|
needs: [changes, lint, build]
|
||||||
|
uses: ./.github/workflows/e2e-app-store.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e-embed:
|
||||||
|
name: E2E embeds tests
|
||||||
|
needs: [changes, lint, build]
|
||||||
|
uses: ./.github/workflows/e2e-embed.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
e2e-embed-react:
|
||||||
|
name: E2E React embeds tests
|
||||||
|
needs: [changes, lint, build]
|
||||||
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build-without-database:
|
||||||
|
name: Production build (without database)
|
||||||
|
uses: ./.github/workflows/production-build-without-database.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
required:
|
||||||
|
needs: [e2e, e2e-app-store, e2e-embed, e2e-embed-react, build-without-database]
|
||||||
|
if: always()
|
||||||
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
steps:
|
||||||
|
- name: fail if conditional jobs failed
|
||||||
|
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
||||||
|
run: exit 1
|
|
@ -9,6 +9,10 @@ env:
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_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_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
|
E2E_TEST_CALCOM_GCAL_KEYS: ${{ secrets.E2E_TEST_CALCOM_GCAL_KEYS }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
|
|
@ -11,7 +11,6 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- run: echo 'NODE_OPTIONS="--max_old_space_size=6144"' >> $GITHUB_ENV
|
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
|
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
|
||||||
- run: yarn test
|
- run: yarn test
|
|
@ -2,7 +2,7 @@
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"spellright.language": ["en"],
|
"spellright.language": ["en"],
|
||||||
|
|
|
@ -147,7 +147,7 @@ Here is what you need to be able to run Cal.com.
|
||||||
|
|
||||||
- Duplicate `.env.example` to `.env`
|
- Duplicate `.env.example` to `.env`
|
||||||
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
||||||
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
- Use `openssl rand -base64 32` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||||
|
|
||||||
5. Setup Node
|
5. Setup Node
|
||||||
If your Node version does not meet the project's requirements as instructed by the docs, "nvm" (Node Version Manager) allows using Node at the version required by the project:
|
If your Node version does not meet the project's requirements as instructed by the docs, "nvm" (Node Version Manager) allows using Node at the version required by the project:
|
||||||
|
@ -216,7 +216,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||||
|
|
||||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
||||||
|
|
||||||
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
|
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
||||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||||
|
@ -554,6 +554,10 @@ following
|
||||||
|
|
||||||
[Follow these steps](./packages/app-store/zoho-bigin/)
|
[Follow these steps](./packages/app-store/zoho-bigin/)
|
||||||
|
|
||||||
|
### Obtaining Pipedrive Client ID and Secret
|
||||||
|
|
||||||
|
[Follow these steps](./packages/app-store/pipedrive-crm/)
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
### Setting up SendGrid for Email reminders
|
### Setting up SendGrid for Email reminders
|
||||||
|
|
|
@ -41,6 +41,28 @@ test.describe("Org", () => {
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeServerSideRendered(page);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test.describe("Dynamic Group Booking", () => {
|
||||||
|
test("Dynamic Group booking link should load", async ({ page }) => {
|
||||||
|
const users = [
|
||||||
|
{
|
||||||
|
username: "peer",
|
||||||
|
name: "Peer Richelsen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: "bailey",
|
||||||
|
name: "Bailey Pumfleet",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const response = await page.goto(`http://i.cal.com/${users[0].username}+${users[1].username}`);
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Dynamic");
|
||||||
|
|
||||||
|
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[0].name);
|
||||||
|
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
||||||
|
// 2 users and 1 for the organization(2+1)
|
||||||
|
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -48,17 +48,21 @@ Here is the full architecture:
|
||||||
|
|
||||||
### Email Router
|
### Email Router
|
||||||
|
|
||||||
To expose the AI app, run `ngrok http 3005` (or the AI app's port number) in a new terminal. You may need to install [nGrok](https://ngrok.com/).
|
To expose the AI app, you can use either [Tunnelmole](https://github.com/robbie-cahill/tunnelmole-client), an open source tunnelling tool; or [nGrok](https://ngrok.com/), a popular closed source tunnelling tool.
|
||||||
|
|
||||||
|
For Tunnelmole, run `tmole 3005` (or the AI app's port number) in a new terminal. Please replace `3005` with the port number if it is different. In the output, you'll see two URLs, one http and a https (we recommend using the https url for privacy and security). To install Tunnelmole, use `curl -O https://install.tunnelmole.com/8dPBw/install && sudo bash install`. (On Windows, download [tmole.exe](https://tunnelmole.com/downloads/tmole.exe))
|
||||||
|
|
||||||
|
For nGrok, run `ngrok http 3005` (or the AI app's port number) in a new terminal. You may need to install nGrok first.
|
||||||
|
|
||||||
To forward incoming emails to the serverless function at `/agent`, we use [SendGrid's Inbound Parse](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook).
|
To forward incoming emails to the serverless function at `/agent`, we use [SendGrid's Inbound Parse](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook).
|
||||||
|
|
||||||
1. Ensure you have a [SendGrid account](https://signup.sendgrid.com/)
|
1. Ensure you have a [SendGrid account](https://signup.sendgrid.com/)
|
||||||
2. Ensure you have an authenticated domain. Go to Settings > Sender Authentication > Authenticate. For DNS host, select `I'm not sure`. Click Next and add your domain, eg. `example.com`. Choose Manual Setup. You'll be given three CNAME records to add to your DNS settings, eg. in [Vercel Domains](https://vercel.com/dashboard/domains). After adding those records, click Verify. To troubleshoot, see the [full instructions](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication).
|
2. Ensure you have an authenticated domain. Go to Settings > Sender Authentication > Authenticate. For DNS host, select `I'm not sure`. Click Next and add your domain, eg. `example.com`. Choose Manual Setup. You'll be given three CNAME records to add to your DNS settings, eg. in [Vercel Domains](https://vercel.com/dashboard/domains). After adding those records, click Verify. To troubleshoot, see the [full instructions](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication).
|
||||||
3. Authorize your domain for email with MX records: one with name `[your domain].com` and value `mx.sendgrid.net.`, and another with name `bounces.[your domain].com` and value `feedback-smtp.us-east-1.amazonses.com`, both with priority `10` if prompted.
|
3. Authorize your domain for email with MX records: one with name `[your domain].com` and value `mx.sendgrid.net.`, and another with name `bounces.[your domain].com` and value `feedback-smtp.us-east-1.amazonses.com`. Set the priority to `10` if prompted.
|
||||||
4. Go to Settings > [Inbound Parse](https://app.sendgrid.com/settings/parse) > Add Host & URL. Choose your authenticated domain.
|
4. Go to Settings > [Inbound Parse](https://app.sendgrid.com/settings/parse) > Add Host & URL. Choose your authenticated domain.
|
||||||
5. In the Destination URL field, use the nGrok URL from above along with the path, `/api/receive`, and one param, `parseKey`, which lives in [this app's .env](/apps/ai/.env.example) under `PARSE_KEY`. The full URL should look like `https://abc.ngrok.io/api/receive?parseKey=ABC-123`.
|
5. In the Destination URL field, use the Tunnelmole or ngrok URL from above along with the path, `/api/receive`, and one param, `parseKey`, which lives in [this app's .env](/apps/ai/.env.example) under `PARSE_KEY`. The full URL should look like `https://abc.tunnelmole.net/api/receive?parseKey=ABC-123` or `https://abc.ngrok.io/api/receive?parseKey=ABC-123`.
|
||||||
6. Activate "POST the raw, full MIME message".
|
6. Activate "POST the raw, full MIME message".
|
||||||
7. Send an email to `[anyUsername]@example.com`. You should see a ping on the nGrok listener and server.
|
7. Send an email to `[anyUsername]@example.com`. You should see a ping on the Tunnelmole or ngrok listener and server.
|
||||||
8. Adjust the logic in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts), save to hot-reload, and send another email to test the behaviour.
|
8. Adjust the logic in [receive/route.ts](/apps/ai/src/app/api/receive/route.ts), save to hot-reload, and send another email to test the behaviour.
|
||||||
|
|
||||||
Please feel free to improve any part of this architecture!
|
Please feel free to improve any part of this architecture!
|
||||||
|
|
|
@ -64,6 +64,25 @@ export async function patchHandler(req: NextApiRequest) {
|
||||||
where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
|
where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
|
||||||
});
|
});
|
||||||
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
|
if (!_team) throw new HttpError({ statusCode: 401, message: "Unauthorized: OWNER or ADMIN required" });
|
||||||
|
|
||||||
|
// Check if parentId is related to this user
|
||||||
|
if (data.parentId && data.parentId === teamId) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: "Bad request: Parent id cannot be the same as the team id.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.parentId) {
|
||||||
|
const parentTeam = await prisma.team.findFirst({
|
||||||
|
where: { id: data.parentId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
|
||||||
|
});
|
||||||
|
if (!parentTeam)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 401,
|
||||||
|
message: "Unauthorized: Invalid parent id. You can only use parent id of your own teams.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let paymentUrl;
|
let paymentUrl;
|
||||||
if (_team.slug === null && data.slug) {
|
if (_team.slug === null && data.slug) {
|
||||||
data.metadata = {
|
data.metadata = {
|
||||||
|
|
|
@ -68,6 +68,18 @@ async function postHandler(req: NextApiRequest) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if parentId is related to this user
|
||||||
|
if (data.parentId) {
|
||||||
|
const parentTeam = await prisma.team.findFirst({
|
||||||
|
where: { id: data.parentId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
|
||||||
|
});
|
||||||
|
if (!parentTeam)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 401,
|
||||||
|
message: "Unauthorized: Invalid parent id. You can only use parent id of your own teams.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Perhaps there is a better fix for this?
|
// TODO: Perhaps there is a better fix for this?
|
||||||
const cloneData: typeof data & {
|
const cloneData: typeof data & {
|
||||||
metadata: NonNullable<typeof data.metadata> | undefined;
|
metadata: NonNullable<typeof data.metadata> | undefined;
|
||||||
|
|
|
@ -1,212 +0,0 @@
|
||||||
// originally from in the "experimental playground for tRPC + next.js 13" repo owned by trpc team
|
|
||||||
// file link: https://github.com/trpc/next-13/blob/main/%40trpc/next-layout/createTRPCNextLayout.ts
|
|
||||||
// repo link: https://github.com/trpc/next-13
|
|
||||||
// code is / will continue to be adapted for our usage
|
|
||||||
import { dehydrate, QueryClient } from "@tanstack/query-core";
|
|
||||||
import type { DehydratedState, QueryKey } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import type { Maybe, TRPCClientError, TRPCClientErrorLike } from "@calcom/trpc";
|
|
||||||
import {
|
|
||||||
callProcedure,
|
|
||||||
type AnyProcedure,
|
|
||||||
type AnyQueryProcedure,
|
|
||||||
type AnyRouter,
|
|
||||||
type DataTransformer,
|
|
||||||
type inferProcedureInput,
|
|
||||||
type inferProcedureOutput,
|
|
||||||
type inferRouterContext,
|
|
||||||
type MaybePromise,
|
|
||||||
type ProcedureRouterRecord,
|
|
||||||
} from "@calcom/trpc/server";
|
|
||||||
|
|
||||||
import { createRecursiveProxy, createFlatProxy } from "@trpc/server/shared";
|
|
||||||
|
|
||||||
export function getArrayQueryKey(
|
|
||||||
queryKey: string | [string] | [string, ...unknown[]] | unknown[],
|
|
||||||
type: string
|
|
||||||
): QueryKey {
|
|
||||||
const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey];
|
|
||||||
const [arrayPath, input] = queryKeyArrayed;
|
|
||||||
|
|
||||||
if (!input && (!type || type === "any")) {
|
|
||||||
return Array.isArray(arrayPath) && arrayPath.length !== 0 ? [arrayPath] : ([] as unknown as QueryKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
arrayPath,
|
|
||||||
{
|
|
||||||
...(typeof input !== "undefined" && { input: input }),
|
|
||||||
...(type && type !== "any" && { type: type }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy starts
|
|
||||||
// copied from trpc/trpc repo
|
|
||||||
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L37-#L58
|
|
||||||
function transformQueryOrMutationCacheErrors<
|
|
||||||
TState extends DehydratedState["queries"][0] | DehydratedState["mutations"][0]
|
|
||||||
>(result: TState): TState {
|
|
||||||
const error = result.state.error as Maybe<TRPCClientError<any>>;
|
|
||||||
if (error instanceof Error && error.name === "TRPCClientError") {
|
|
||||||
const newError: TRPCClientErrorLike<any> = {
|
|
||||||
message: error.message,
|
|
||||||
data: error.data,
|
|
||||||
shape: error.shape,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
state: {
|
|
||||||
...result.state,
|
|
||||||
error: newError,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// copy ends
|
|
||||||
|
|
||||||
interface CreateTRPCNextLayoutOptions<TRouter extends AnyRouter> {
|
|
||||||
router: TRouter;
|
|
||||||
createContext: () => MaybePromise<inferRouterContext<TRouter>>;
|
|
||||||
transformer?: DataTransformer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type DecorateProcedure<TProcedure extends AnyProcedure> = TProcedure extends AnyQueryProcedure
|
|
||||||
? {
|
|
||||||
fetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
|
||||||
fetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
|
||||||
prefetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
|
||||||
prefetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
|
||||||
}
|
|
||||||
: never;
|
|
||||||
|
|
||||||
type OmitNever<TType> = Pick<
|
|
||||||
TType,
|
|
||||||
{
|
|
||||||
[K in keyof TType]: TType[K] extends never ? never : K;
|
|
||||||
}[keyof TType]
|
|
||||||
>;
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type DecoratedProcedureRecord<
|
|
||||||
TProcedures extends ProcedureRouterRecord,
|
|
||||||
TPath extends string = ""
|
|
||||||
> = OmitNever<{
|
|
||||||
[TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter
|
|
||||||
? DecoratedProcedureRecord<TProcedures[TKey]["_def"]["record"], `${TPath}${TKey & string}.`>
|
|
||||||
: TProcedures[TKey] extends AnyQueryProcedure
|
|
||||||
? DecorateProcedure<TProcedures[TKey]>
|
|
||||||
: never;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
type CreateTRPCNextLayout<TRouter extends AnyRouter> = DecoratedProcedureRecord<TRouter["_def"]["record"]> & {
|
|
||||||
dehydrate(): Promise<DehydratedState>;
|
|
||||||
queryClient: QueryClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStateContainer = <TRouter extends AnyRouter>(opts: CreateTRPCNextLayoutOptions<TRouter>) => {
|
|
||||||
let _trpc: {
|
|
||||||
queryClient: QueryClient;
|
|
||||||
context: inferRouterContext<TRouter>;
|
|
||||||
} | null = null;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (_trpc === null) {
|
|
||||||
_trpc = {
|
|
||||||
context: opts.createContext(),
|
|
||||||
queryClient: new QueryClient(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return _trpc;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createTRPCNextLayout<TRouter extends AnyRouter>(
|
|
||||||
opts: CreateTRPCNextLayoutOptions<TRouter>
|
|
||||||
): CreateTRPCNextLayout<TRouter> {
|
|
||||||
const getState = getStateContainer(opts);
|
|
||||||
|
|
||||||
const transformer = opts.transformer ?? {
|
|
||||||
serialize: (v) => v,
|
|
||||||
deserialize: (v) => v,
|
|
||||||
};
|
|
||||||
|
|
||||||
return createFlatProxy((key) => {
|
|
||||||
const state = getState();
|
|
||||||
const { queryClient } = state;
|
|
||||||
if (key === "queryClient") {
|
|
||||||
return queryClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "dehydrate") {
|
|
||||||
// copy starts
|
|
||||||
// copied from trpc/trpc repo
|
|
||||||
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L214-#L229
|
|
||||||
const dehydratedCache = dehydrate(queryClient, {
|
|
||||||
shouldDehydrateQuery() {
|
|
||||||
// makes sure errors are also dehydrated
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects
|
|
||||||
const dehydratedCacheWithErrors = {
|
|
||||||
...dehydratedCache,
|
|
||||||
queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors),
|
|
||||||
mutations: dehydratedCache.mutations.map(transformQueryOrMutationCacheErrors),
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => transformer.serialize(dehydratedCacheWithErrors);
|
|
||||||
}
|
|
||||||
// copy ends
|
|
||||||
|
|
||||||
return createRecursiveProxy(async (callOpts) => {
|
|
||||||
const path = [key, ...callOpts.path];
|
|
||||||
const utilName = path.pop();
|
|
||||||
const ctx = await state.context;
|
|
||||||
|
|
||||||
const caller = opts.router.createCaller(ctx);
|
|
||||||
|
|
||||||
const pathStr = path.join(".");
|
|
||||||
const input = callOpts.args[0];
|
|
||||||
|
|
||||||
if (utilName === "fetchInfinite") {
|
|
||||||
return queryClient.fetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
|
|
||||||
caller.query(pathStr, input)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utilName === "prefetch") {
|
|
||||||
return queryClient.prefetchQuery({
|
|
||||||
queryKey: getArrayQueryKey([path, input], "query"),
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await callProcedure({
|
|
||||||
procedures: opts.router._def.procedures,
|
|
||||||
path: pathStr,
|
|
||||||
rawInput: input,
|
|
||||||
ctx,
|
|
||||||
type: "query",
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utilName === "prefetchInfinite") {
|
|
||||||
return queryClient.prefetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
|
|
||||||
caller.query(pathStr, input)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryClient.fetchQuery(getArrayQueryKey([path, input], "query"), () =>
|
|
||||||
caller.query(pathStr, input)
|
|
||||||
);
|
|
||||||
}) as CreateTRPCNextLayout<TRouter>;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
import superjson from "superjson";
|
|
||||||
|
|
||||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
|
||||||
import prisma, { readonlyPrisma } from "@calcom/prisma";
|
|
||||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
|
||||||
|
|
||||||
import { createTRPCNextLayout } from "./createTRPCNextLayout";
|
|
||||||
|
|
||||||
export async function ssgInit() {
|
|
||||||
const locale = headers().get("x-locale") ?? "en";
|
|
||||||
|
|
||||||
const i18n = (await serverSideTranslations(locale, ["common"])) || "en";
|
|
||||||
|
|
||||||
const ssg = createTRPCNextLayout({
|
|
||||||
router: appRouter,
|
|
||||||
transformer: superjson,
|
|
||||||
createContext() {
|
|
||||||
return { prisma, insightsDb: readonlyPrisma, session: null, locale, i18n };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
|
|
||||||
// we can set query data directly to the queryClient
|
|
||||||
const queryKey = [
|
|
||||||
["viewer", "public", "i18n"],
|
|
||||||
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
|
|
||||||
];
|
|
||||||
|
|
||||||
ssg.queryClient.setQueryData(queryKey, { i18n });
|
|
||||||
|
|
||||||
return ssg;
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
||||||
import { headers, cookies } from "next/headers";
|
|
||||||
import superjson from "superjson";
|
|
||||||
|
|
||||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
|
||||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
|
||||||
import prisma, { readonlyPrisma } from "@calcom/prisma";
|
|
||||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
|
||||||
|
|
||||||
import { createTRPCNextLayout } from "./createTRPCNextLayout";
|
|
||||||
|
|
||||||
export async function ssrInit(options?: { noI18nPreload: boolean }) {
|
|
||||||
const req = {
|
|
||||||
headers: headers(),
|
|
||||||
cookies: cookies(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const locale = await getLocale(req);
|
|
||||||
|
|
||||||
const i18n = (await serverSideTranslations(locale, ["common", "vital"])) || "en";
|
|
||||||
|
|
||||||
const ssr = createTRPCNextLayout({
|
|
||||||
router: appRouter,
|
|
||||||
transformer: superjson,
|
|
||||||
createContext() {
|
|
||||||
return {
|
|
||||||
prisma,
|
|
||||||
insightsDb: readonlyPrisma,
|
|
||||||
session: null,
|
|
||||||
locale,
|
|
||||||
i18n,
|
|
||||||
req: req as unknown as GetServerSidePropsContext["req"],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
|
|
||||||
// we can set query data directly to the queryClient
|
|
||||||
const queryKey = [
|
|
||||||
["viewer", "public", "i18n"],
|
|
||||||
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
|
|
||||||
];
|
|
||||||
if (!options?.noI18nPreload) {
|
|
||||||
ssr.queryClient.setQueryData(queryKey, { i18n });
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.allSettled([
|
|
||||||
// So feature flags are available on first render
|
|
||||||
ssr.viewer.features.map.prefetch(),
|
|
||||||
// Provides a better UX to the users who have already upgraded.
|
|
||||||
ssr.viewer.teams.hasTeamPlan.prefetch(),
|
|
||||||
ssr.viewer.public.session.prefetch(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return ssr;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export type Params = {
|
|
||||||
[param: string]: string | string[] | undefined;
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
type EventTypesLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Layout({ children }: EventTypesLayoutProps) {
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
type EventTypesLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Layout({ children }: EventTypesLayoutProps) {
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { headers } from "next/headers";
|
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
|
||||||
|
|
||||||
type WrapperWithLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
|
|
||||||
const h = headers();
|
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Page from "@pages/settings/admin/oAuth/index";
|
|
||||||
import { _generateMetadata } from "_app/_utils";
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
() => "OAuth",
|
|
||||||
() => "Add new OAuth Clients"
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Page;
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Page from "@pages/settings/admin/index";
|
|
||||||
import { _generateMetadata } from "_app/_utils";
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
() => "Admin",
|
|
||||||
() => "admin_description"
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Page;
|
|
|
@ -1,22 +0,0 @@
|
||||||
// pages without layout (e.g., /availability/index.tsx) are supposed to go under (layout) folder
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
type WrapperWithLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
|
|
||||||
const h = headers();
|
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
// pages containing layout (e.g., /availability/[schedule].tsx) are supposed to go under (no-layout) folder
|
|
||||||
import { headers } from "next/headers";
|
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
type WrapperWithoutLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function WrapperWithoutLayout({ children }: WrapperWithoutLayoutProps) {
|
|
||||||
const h = headers();
|
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={nonce} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { headers } from "next/headers";
|
|
||||||
import { type ReactElement } from "react";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
type WrapperWithLayoutProps = {
|
|
||||||
children: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
|
|
||||||
const h = headers();
|
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
|
|
||||||
{children}
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -11,6 +11,13 @@ const ROUTES: [URLPattern, boolean][] = [
|
||||||
["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const,
|
["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const,
|
||||||
["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const,
|
["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const,
|
||||||
["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const,
|
["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const,
|
||||||
|
["/workflows/:path*", process.env.APP_ROUTER_WORKFLOWS_ENABLED === "1"] as const,
|
||||||
|
["/settings/teams/:path*", process.env.APP_ROUTER_SETTINGS_TEAMS_ENABLED === "1"] as const,
|
||||||
|
["/getting-started/:step", process.env.APP_ROUTER_GETTING_STARTED_STEP_ENABLED === "1"] as const,
|
||||||
|
["/apps", process.env.APP_ROUTER_APPS_ENABLED === "1"] as const,
|
||||||
|
["/bookings/:status", process.env.APP_ROUTER_BOOKINGS_STATUS_ENABLED === "1"] as const,
|
||||||
|
["/video/:path*", process.env.APP_ROUTER_VIDEO_ENABLED === "1"] as const,
|
||||||
|
["/teams", process.env.APP_ROUTER_TEAMS_ENABLED === "1"] as const,
|
||||||
].map(([pathname, enabled]) => [
|
].map(([pathname, enabled]) => [
|
||||||
new URLPattern({
|
new URLPattern({
|
||||||
pathname,
|
pathname,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { createHydrateClient } from "_app/_trpc/createHydrateClient";
|
import { createHydrateClient } from "app/_trpc/createHydrateClient";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
export const HydrateClient = createHydrateClient({
|
export const HydrateClient = createHydrateClient({
|
|
@ -1,6 +1,6 @@
|
||||||
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { type DehydratedState, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { HydrateClient } from "_app/_trpc/HydrateClient";
|
import { HydrateClient } from "app/_trpc/HydrateClient";
|
||||||
import { trpc } from "_app/_trpc/client";
|
import { trpc } from "app/_trpc/client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
export type Params = {
|
||||||
|
[param: string]: string | string[] | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchParams = {
|
||||||
|
[param: string]: string | string[] | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageProps = {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LayoutProps = { params: Params; children: React.ReactElement };
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null })<"L">;
|
|
@ -1,6 +1,6 @@
|
||||||
import AppPage from "@pages/apps/[slug]/index";
|
import AppPage from "@pages/apps/[slug]/index";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
|
@ -1,5 +1,5 @@
|
||||||
import SetupPage from "@pages/apps/[slug]/setup";
|
import SetupPage from "@pages/apps/[slug]/setup";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
|
@ -1,6 +1,7 @@
|
||||||
import CategoryPage from "@pages/apps/categories/[category]";
|
import CategoryPage from "@pages/apps/categories/[category]";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
|
@ -9,8 +10,6 @@ import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
import { AppCategories } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => `${APP_NAME} | ${APP_NAME}`,
|
() => `${APP_NAME} | ${APP_NAME}`,
|
||||||
|
@ -67,13 +66,6 @@ const getPageProps = async ({ params }: { params: Record<string, string | string
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
// @ts-expect-error getData arg
|
||||||
const { apps } = await getPageProps({ params });
|
export default WithLayout({ getData: getPageProps, Page: CategoryPage })<"P">;
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
|
|
||||||
<CategoryPage apps={apps} />
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
|
@ -1,13 +1,14 @@
|
||||||
import LegacyPage from "@pages/apps/categories/index";
|
import LegacyPage from "@pages/apps/categories/index";
|
||||||
import { ssrInit } from "_app/_trpc/ssrInit";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapperAppDir";
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
|
@ -16,12 +17,12 @@ export const generateMetadata = async () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getPageProps() {
|
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||||
const ssr = await ssrInit();
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
const req = { headers: headers(), cookies: cookies() };
|
const ssr = await ssrInit(ctx);
|
||||||
|
|
||||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage
|
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage
|
||||||
const session = await getServerSession({ req });
|
const session = await getServerSession({ req: ctx.req });
|
||||||
|
|
||||||
let appStore;
|
let appStore;
|
||||||
if (session?.user?.id) {
|
if (session?.user?.id) {
|
||||||
|
@ -39,18 +40,8 @@ async function getPageProps() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
||||||
dehydratedState: await ssr.dehydrate(),
|
dehydratedState: ssr.dehydrate(),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default async function Page() {
|
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
||||||
const props = await getPageProps();
|
|
||||||
const h = headers();
|
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageWrapper getLayout={null} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
|
||||||
<LegacyPage {...props} />
|
|
||||||
</PageWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import LegacyPage from "@pages/apps/installed/[category]";
|
import LegacyPage from "@pages/apps/installed/[category]";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const getPageProps = async ({ params }: { params: Record<string, string | string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
||||||
const { category } = await getPageProps({ params });
|
await getPageProps({ params });
|
||||||
|
|
||||||
return <LegacyPage />;
|
return <LegacyPage />;
|
||||||
}
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import AppsPage from "@pages/apps";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||||
|
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
import type { AppCategories } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => `Apps | ${APP_NAME}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||||
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
|
const ssr = await ssrInit(ctx);
|
||||||
|
|
||||||
|
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest
|
||||||
|
const session = await getServerSession({ req: ctx.req });
|
||||||
|
|
||||||
|
let appStore, userAdminTeams: UserAdminTeams;
|
||||||
|
if (session?.user?.id) {
|
||||||
|
userAdminTeams = await getUserAdminTeams({ userId: session.user.id, getUserInfo: true });
|
||||||
|
appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeams);
|
||||||
|
} else {
|
||||||
|
appStore = await getAppRegistry();
|
||||||
|
userAdminTeams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryQuery = appStore.map(({ categories }) => ({
|
||||||
|
categories: categories || [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const categories = categoryQuery.reduce((c, app) => {
|
||||||
|
for (const category of app.categories) {
|
||||||
|
c[category] = c[category] ? c[category] + 1 : 1;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}, {} as Record<string, number>);
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories: Object.entries(categories)
|
||||||
|
.map(([name, count]): { name: AppCategories; count: number } => ({
|
||||||
|
name: name as AppCategories,
|
||||||
|
count,
|
||||||
|
}))
|
||||||
|
.sort(function (a, b) {
|
||||||
|
return b.count - a.count;
|
||||||
|
}),
|
||||||
|
appStore,
|
||||||
|
userAdminTeams,
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, getData, Page: AppsPage });
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssgInit } from "@server/lib/ssg";
|
||||||
|
|
||||||
|
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||||
|
|
||||||
|
const querySchema = z.object({
|
||||||
|
status: z.enum(validStatuses),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => `${APP_NAME} | ${t("bookings")}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
export const generateStaticParams = async () => {
|
||||||
|
return validStatuses.map((status) => ({ status }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||||
|
const parsedParams = querySchema.safeParse(ctx.params);
|
||||||
|
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssg = await ssgInit(ctx);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: parsedParams.data.status,
|
||||||
|
dehydratedState: ssg.dehydrate(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, getData })<"L">;
|
||||||
|
|
||||||
|
export const dynamic = "force-static";
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "@pages/bookings/[status]";
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import EventTypes from "@pages/event-types";
|
import EventTypes from "@pages/event-types";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,63 @@
|
||||||
|
import LegacyPage from "@pages/getting-started/[[...step]]";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
const getData = async (ctx: ReturnType<typeof buildLegacyCtx>) => {
|
||||||
|
const req = { headers: headers(), cookies: cookies() };
|
||||||
|
|
||||||
|
//@ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest
|
||||||
|
const session = await getServerSession({ req });
|
||||||
|
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return redirect("/auth/login");
|
||||||
|
}
|
||||||
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
|
const ssr = await ssrInit(ctx);
|
||||||
|
await ssr.viewer.me.prefetch();
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: session.user.id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
completedOnboarding: true,
|
||||||
|
teams: {
|
||||||
|
select: {
|
||||||
|
accepted: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
logo: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("User from session not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.completedOnboarding) {
|
||||||
|
redirect("/event-types");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
hasPendingInvites: user.teams.find((team: any) => team.accepted === false) ?? false,
|
||||||
|
requiresLicense: false,
|
||||||
|
themeBasis: null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, Page: LegacyPage });
|
|
@ -1,5 +1,5 @@
|
||||||
import Page from "@pages/settings/admin/apps/[category]";
|
import Page from "@pages/settings/admin/apps/[category]";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import Page from "@pages/settings/admin/apps/index";
|
import Page from "@pages/settings/admin/apps/index";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import Page from "@pages/settings/admin/flags";
|
import Page from "@pages/settings/admin/flags";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import Page from "@pages/settings/admin/impersonation";
|
import Page from "@pages/settings/admin/impersonation";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null })<"L">;
|
|
@ -1,5 +1,5 @@
|
||||||
import Page from "@pages/settings/admin/oAuth/oAuthView";
|
import Page from "@pages/settings/admin/oAuth/oAuthView";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
|
@ -0,0 +1,13 @@
|
||||||
|
import LegacyPage from "@pages/settings/admin/oAuth/index";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
() => "OAuth",
|
||||||
|
() => "Add new OAuth Clients"
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, Page: LegacyPage })<"P">;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,4 +1,4 @@
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage";
|
import Page from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage";
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import LegacyPage from "@pages/settings/admin/index";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
() => "Admin",
|
||||||
|
() => "admin_description"
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, Page: LegacyPage })<"P">;
|
|
@ -1,6 +1,6 @@
|
||||||
import { getServerCaller } from "_app/_trpc/serverClient";
|
import { getServerCaller } from "app/_trpc/serverClient";
|
||||||
import { type Params } from "_app/_types";
|
import { type Params } from "app/_types";
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/users/pages/users-add-view";
|
import Page from "@calcom/features/ee/users/pages/users-add-view";
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -1,4 +1,4 @@
|
||||||
import { _generateMetadata } from "_app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/users/pages/users-listing-view";
|
import Page from "@calcom/features/ee/users/pages/users-listing-view";
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
import Page from "@calcom/features/ee/teams/pages/team-appearance-view";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("booking_appearance"),
|
||||||
|
(t) => t("appearance_team_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Page from "@pages/settings/billing/index";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("billing"),
|
||||||
|
(t) => t("team_billing_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
import Page from "@calcom/features/ee/teams/pages/team-members-view";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("team_members"),
|
||||||
|
(t) => t("members_team_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import LegacyPage, { GetLayout } from "@pages/settings/teams/[id]/onboard-members";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("add_team_members"),
|
||||||
|
(t) => t("add_team_members_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WithLayout({ Page: LegacyPage, getLayout: GetLayout })<"P">;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
import Page from "@calcom/features/ee/teams/pages/team-profile-view";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("profile"),
|
||||||
|
(t) => t("profile_team_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
import Page from "@calcom/features/ee/sso/page/teams-sso-view";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("sso_configuration"),
|
||||||
|
(t) => t("sso_configuration_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout })<"L">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import LegacyPage, { LayoutWrapper } from "@pages/settings/teams/new/index";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("create_new_team"),
|
||||||
|
(t) => t("create_new_team_description")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WithLayout({ Page: LegacyPage, getLayout: LayoutWrapper })<"P">;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
|
||||||
|
import Page from "@calcom/features/ee/teams/pages/team-listing-view";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("teams"),
|
||||||
|
(t) => t("create_manage_teams_collaborative")
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -0,0 +1,40 @@
|
||||||
|
import OldPage from "@pages/teams/index";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("teams"),
|
||||||
|
(t) => t("create_manage_teams_collaborative")
|
||||||
|
);
|
||||||
|
|
||||||
|
async function getData(context: ReturnType<typeof buildLegacyCtx>) {
|
||||||
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
|
await ssr.viewer.me.prefetch();
|
||||||
|
|
||||||
|
const session = await getServerSession({
|
||||||
|
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | (IncomingMessage & { cookies: Partial<{ [key: string]: string; }>; })'.
|
||||||
|
req: context.req,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
const token = Array.isArray(context.query.token) ? context.query.token[0] : context.query.token;
|
||||||
|
|
||||||
|
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
|
||||||
|
return redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dehydratedState: ssr.dehydrate() };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WithLayout({ getData, getLayout, Page: OldPage })<"P">;
|
|
@ -0,0 +1,105 @@
|
||||||
|
import OldPage from "@pages/video/[uid]";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
() => `${APP_NAME} Video`,
|
||||||
|
(t) => t("quick_video_meeting")
|
||||||
|
);
|
||||||
|
|
||||||
|
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
||||||
|
|
||||||
|
async function getData(context: ReturnType<typeof buildLegacyCtx>) {
|
||||||
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
uid: context.query.uid as string,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
|
uid: true,
|
||||||
|
description: true,
|
||||||
|
isRecorded: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
timeZone: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
organization: {
|
||||||
|
select: {
|
||||||
|
calVideoLogo: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
references: {
|
||||||
|
select: {
|
||||||
|
uid: true,
|
||||||
|
type: true,
|
||||||
|
meetingUrl: true,
|
||||||
|
meetingPassword: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
type: "daily_video",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking || booking.references.length === 0 || !booking.references[0].meetingUrl) {
|
||||||
|
return redirect("/video/no-meeting-found");
|
||||||
|
}
|
||||||
|
|
||||||
|
//daily.co calls have a 60 minute exit buffer when a user enters a call when it's not available it will trigger the modals
|
||||||
|
const now = new Date();
|
||||||
|
const exitDate = new Date(now.getTime() - 60 * 60 * 1000);
|
||||||
|
|
||||||
|
//find out if the meeting is in the past
|
||||||
|
const isPast = booking?.endTime <= exitDate;
|
||||||
|
if (isPast) {
|
||||||
|
return redirect(`/video/meeting-ended/${booking?.uid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookingObj = Object.assign({}, booking, {
|
||||||
|
startTime: booking.startTime.toString(),
|
||||||
|
endTime: booking.endTime.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | (IncomingMessage & { cookies: Partial<{ [key: string]: string; }>; })'.
|
||||||
|
const session = await getServerSession({ req: context.req });
|
||||||
|
|
||||||
|
// set meetingPassword to null for guests
|
||||||
|
if (session?.user.id !== bookingObj.user?.id) {
|
||||||
|
bookingObj.references.forEach((bookRef: any) => {
|
||||||
|
bookRef.meetingPassword = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
meetingUrl: bookingObj.references[0].meetingUrl ?? "",
|
||||||
|
...(typeof bookingObj.references[0].meetingPassword === "string" && {
|
||||||
|
meetingPassword: bookingObj.references[0].meetingPassword,
|
||||||
|
}),
|
||||||
|
booking: {
|
||||||
|
...bookingObj,
|
||||||
|
...(bookingObj.description && { description: md.render(bookingObj.description) }),
|
||||||
|
},
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
@ -0,0 +1,53 @@
|
||||||
|
import OldPage from "@pages/video/meeting-ended/[uid]";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
() => "Meeting Unavailable",
|
||||||
|
() => "Meeting Unavailable"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
|
uid: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
credentials: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
references: {
|
||||||
|
select: {
|
||||||
|
uid: true,
|
||||||
|
type: true,
|
||||||
|
meetingUrl: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
return redirect("/video/no-meeting-found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookingObj = Object.assign({}, booking, {
|
||||||
|
startTime: booking.startTime.toString(),
|
||||||
|
endTime: booking.endTime.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
booking: bookingObj,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error getData arg
|
||||||
|
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
@ -0,0 +1,51 @@
|
||||||
|
import OldPage from "@pages/video/meeting-not-started/[uid]";
|
||||||
|
import { type Params } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
|
type PageProps = Readonly<{
|
||||||
|
params: Params;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const generateMetadata = async ({ params }: PageProps) => {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
uid: typeof params?.uid === "string" ? params.uid : "",
|
||||||
|
},
|
||||||
|
select: bookingMinimalSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("this_meeting_has_not_started_yet"),
|
||||||
|
() => booking?.title ?? ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: {
|
||||||
|
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
||||||
|
},
|
||||||
|
select: bookingMinimalSelect,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
return redirect("/video/no-meeting-found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const bookingObj = Object.assign({}, booking, {
|
||||||
|
startTime: booking.startTime.toString(),
|
||||||
|
endTime: booking.endTime.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
booking: bookingObj,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error getData arg
|
||||||
|
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import LegacyPage from "@pages/video/no-meeting-found";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import type { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("no_meeting_found"),
|
||||||
|
(t) => t("no_meeting_found")
|
||||||
|
);
|
||||||
|
|
||||||
|
const getData = async (context: ReturnType<typeof buildLegacyCtx>) => {
|
||||||
|
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
|
@ -84,7 +84,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||||
`}</style>
|
`}</style>
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
className="dark:bg-darkgray-50 desktop-transparent bg-subtle antialiased"
|
className="dark:bg-darkgray-50 todesktop:!bg-transparent bg-subtle antialiased"
|
||||||
style={
|
style={
|
||||||
isEmbed
|
isEmbed
|
||||||
? {
|
? {
|
|
@ -0,0 +1,28 @@
|
||||||
|
import type { LayoutProps, PageProps } from "app/_types";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapperAppDir";
|
||||||
|
|
||||||
|
type WithLayoutParams<T extends Record<string, any>> = {
|
||||||
|
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
|
||||||
|
Page?: (props: T) => React.ReactElement;
|
||||||
|
getData?: (arg: ReturnType<typeof buildLegacyCtx>) => Promise<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function WithLayout<T extends Record<string, any>>({ getLayout, getData, Page }: WithLayoutParams<T>) {
|
||||||
|
return async <P extends "P" | "L">(p: P extends "P" ? PageProps : LayoutProps) => {
|
||||||
|
const h = headers();
|
||||||
|
const nonce = h.get("x-nonce") ?? undefined;
|
||||||
|
const props = getData ? await getData(buildLegacyCtx(h, cookies(), p.params)) : ({} as T);
|
||||||
|
|
||||||
|
const children = "children" in p ? p.children : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||||
|
{Page ? <Page {...props} /> : children}
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ export interface CalPageWrapper {
|
||||||
|
|
||||||
export type PageWrapperProps = Readonly<{
|
export type PageWrapperProps = Readonly<{
|
||||||
getLayout: ((page: React.ReactElement) => ReactNode) | null;
|
getLayout: ((page: React.ReactElement) => ReactNode) | null;
|
||||||
children: React.ReactElement;
|
children: React.ReactNode;
|
||||||
requiresLicense: boolean;
|
requiresLicense: boolean;
|
||||||
nonce: string | undefined;
|
nonce: string | undefined;
|
||||||
themeBasis: string | null;
|
themeBasis: string | null;
|
||||||
|
@ -62,7 +62,7 @@ function PageWrapper(props: PageWrapperProps) {
|
||||||
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
||||||
/>
|
/>
|
||||||
{getLayout(
|
{getLayout(
|
||||||
props.requiresLicense ? <LicenseRequired>{props.children}</LicenseRequired> : props.children
|
props.requiresLicense ? <LicenseRequired>{props.children}</LicenseRequired> : <>{props.children}</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</AppProviders>
|
</AppProviders>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user