diff --git a/.env.appStore.example b/.env.appStore.example index 2719175e4a..15f9fa42fb 100644 --- a/.env.appStore.example +++ b/.env.appStore.example @@ -37,6 +37,7 @@ BASECAMP3_USER_AGENT= DAILY_API_KEY= DAILY_SCALE_PLAN='' +DAILY_WEBHOOK_SECRET='' # - GOOGLE CALENDAR/MEET/LOGIN # Needed to enable Google Calendar integration and Login with Google @@ -126,4 +127,12 @@ ZOHOCRM_CLIENT_ID="" 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/ # ********************************************************************************************************* diff --git a/.env.example b/.env.example index c99d2dff3c..3b7cf159e6 100644 --- a/.env.example +++ b/.env.example @@ -133,6 +133,11 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME= # Used for capturing exceptions and logging messages NEXT_PUBLIC_SENTRY_DSN= +# Formbricks Experience Management Integration +FORMBRICKS_HOST_URL=https://app.formbricks.com +FORMBRICKS_ENVIRONMENT_ID= +FORMBRICKS_FEEDBACK_SURVEY_ID= + # Twilio # Used to send SMS reminders in workflows TWILIO_SID= @@ -196,6 +201,10 @@ EMAIL_SERVER_PORT=1025 # Make sure to run mailhog container manually or with `yarn dx` E2E_TEST_MAILHOG_ENABLED= +# Resend +# Send transactional email using resend +# RESEND_API_KEY= + # ********************************************************************************************************** # Set the following value to true if you wish to enable Team Impersonation @@ -245,6 +254,18 @@ PROJECT_ID_VERCEL= TEAM_ID_VERCEL= # Get it from: https://vercel.com/account/tokens 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 # Used for E2E tests on Apple Calendar @@ -256,6 +277,8 @@ E2E_TEST_APPLE_CALENDAR_PASSWORD="" E2E_TEST_CALCOM_QA_EMAIL="qa@example.com" # Replace with your own password E2E_TEST_CALCOM_QA_PASSWORD="password" +E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS= +E2E_TEST_CALCOM_GCAL_KEYS= # - APP CREDENTIAL SYNC *********************************************************************************** # Used for self-hosters that are implementing Cal.com into their applications that already have certain integrations @@ -289,4 +312,22 @@ E2E_TEST_OIDC_USER_PASSWORD= # redirected from the legacy to the future pages AB_TEST_BUCKET_PROBABILITY=50 # whether we redirect to the future/event-types from event-types or not -APP_ROUTER_EVENT_TYPES_ENABLED=1 +APP_ROUTER_EVENT_TYPES_ENABLED=0 +APP_ROUTER_SETTINGS_ADMIN_ENABLED=0 +APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0 +APP_ROUTER_APPS_SLUG_ENABLED=0 +APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0 +# whether we redirect to the future/apps/categories from /apps/categories or not +APP_ROUTER_APPS_CATEGORIES_ENABLED=0 +# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not +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 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0b19591ddd..8691d73057 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,11 +19,12 @@ Fixes # (issue) -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] Chore (refactoring code, technical debt, workflow improvements) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update +- Bug fix (non-breaking change which fixes an issue) +- Chore (refactoring code, technical debt, workflow improvements) +- New feature (non-breaking change which adds functionality) +- Breaking change (fix or feature that would cause existing functionality to not work as expected) +- Tests (Unit/Integration/E2E or any other test) +- This change requires a documentation update ## How should this be tested? diff --git a/.github/actions/cache-build/action.yml b/.github/actions/cache-build/action.yml index 1ef35a7831..62bf25dd59 100644 --- a/.github/actions/cache-build/action.yml +++ b/.github/actions/cache-build/action.yml @@ -24,6 +24,8 @@ runs: **/.turbo/** **/dist/** key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }} - - run: yarn build + - run: | + export NODE_OPTIONS="--max_old_space_size=8192" + yarn build if: steps.cache-build.outputs.cache-hit != 'true' shell: bash diff --git a/.github/actions/cache-db/action.yml b/.github/actions/cache-db/action.yml index f69fd9513a..ffc8f15097 100644 --- a/.github/actions/cache-db/action.yml +++ b/.github/actions/cache-db/action.yml @@ -17,10 +17,14 @@ runs: cache-name: cache-db key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }} key-2: ${{ github.event.pull_request.number || github.ref }} + DATABASE_URL: ${{ inputs.DATABASE_URL }} + 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: path: ${{ inputs.path }} 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' shell: bash - name: Postgres Dump Backup diff --git a/.github/actions/yarn-playwright-install/action.yml b/.github/actions/yarn-playwright-install/action.yml index 1e118e3099..601513ee76 100644 --- a/.github/actions/yarn-playwright-install/action.yml +++ b/.github/actions/yarn-playwright-install/action.yml @@ -5,7 +5,7 @@ runs: steps: - name: Cache playwright binaries id: playwright-cache - uses: buildjet/cache@v2 + uses: buildjet/cache@v3 with: path: | ~/Library/Caches/ms-playwright diff --git a/.github/workflows/apply-issue-labels-to-pr.yml b/.github/workflows/apply-issue-labels-to-pr.yml deleted file mode 100644 index 3299b591a8..0000000000 --- a/.github/workflows/apply-issue-labels-to-pr.yml +++ /dev/null @@ -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), - }); - } diff --git a/.github/workflows/check-types.yml b/.github/workflows/check-types.yml index 644ca2e9d6..85f730a3ae 100644 --- a/.github/workflows/check-types.yml +++ b/.github/workflows/check-types.yml @@ -2,7 +2,7 @@ name: Check types on: workflow_call: env: - NODE_OPTIONS: "--max-old-space-size=8192" + NODE_OPTIONS: --max-old-space-size=4096 jobs: check-types: runs-on: buildjet-4vcpu-ubuntu-2204 diff --git a/.github/workflows/cron-stale-issue.yml b/.github/workflows/cron-stale-issue.yml index 6fdc0d8057..7f66fd0d69 100644 --- a/.github/workflows/cron-stale-issue.yml +++ b/.github/workflows/cron-stale-issue.yml @@ -17,10 +17,11 @@ jobs: steps: - uses: actions/stale@v7 with: + days-before-close: -1 days-before-issue-stale: 60 days-before-issue-close: -1 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." close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued." operations-per-run: 100 diff --git a/.github/workflows/e2e-app-store.yml b/.github/workflows/e2e-app-store.yml index 90d725d96d..28d26f4e40 100644 --- a/.github/workflows/e2e-app-store.yml +++ b/.github/workflows/e2e-app-store.yml @@ -1,7 +1,8 @@ -name: E2E App-Store Apps +name: E2E App-Store Apps Tests on: workflow_call: - +env: + NODE_OPTIONS: --max-old-space-size=4096 jobs: e2e-app-store: timeout-minutes: 20 @@ -29,10 +30,15 @@ jobs: steps: - uses: actions/checkout@v3 - 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-playwright-install - 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 - name: Run Tests run: yarn e2e:app-store --shard=${{ matrix.shard }}/${{ strategy.job-total }} @@ -43,6 +49,10 @@ jobs: DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }} E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }} E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }} + 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 }} GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }} GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }} @@ -65,7 +75,7 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} - name: Upload Test Results if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: app-store-results-${{ matrix.shard }}_${{ strategy.job-total }} path: test-results diff --git a/.github/workflows/e2e-embed-react.yml b/.github/workflows/e2e-embed-react.yml index 5b356246c7..4b3dc67306 100644 --- a/.github/workflows/e2e-embed-react.yml +++ b/.github/workflows/e2e-embed-react.yml @@ -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: workflow_call: - +env: + NODE_OPTIONS: --max-old-space-size=4096 jobs: e2e-embed: timeout-minutes: 20 @@ -24,7 +25,6 @@ jobs: steps: - uses: actions/checkout@v3 - 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-playwright-install - uses: ./.github/actions/cache-db @@ -61,7 +61,7 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} - name: Upload Test Results if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: embed-react-results-${{ matrix.shard }}_${{ strategy.job-total }} path: test-results diff --git a/.github/workflows/e2e-embed.yml b/.github/workflows/e2e-embed.yml index bed5801813..e41b1aa545 100644 --- a/.github/workflows/e2e-embed.yml +++ b/.github/workflows/e2e-embed.yml @@ -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: workflow_call: - +env: + NODE_OPTIONS: --max-old-space-size=4096 jobs: e2e-embed: timeout-minutes: 20 @@ -29,7 +30,6 @@ jobs: steps: - uses: actions/checkout@v3 - 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-playwright-install - uses: ./.github/actions/cache-db @@ -65,7 +65,7 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} - name: Upload Test Results if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: embed-core-results-${{ matrix.shard }}_${{ strategy.job-total }} path: test-results diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0c084aea3c..4b3e0d94dc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,8 +1,8 @@ -name: E2E test - +name: E2E tests on: workflow_call: - +env: + NODE_OPTIONS: --max-old-space-size=4096 jobs: e2e: timeout-minutes: 20 @@ -28,7 +28,6 @@ jobs: steps: - uses: actions/checkout@v3 - 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-playwright-install - uses: ./.github/actions/cache-db @@ -68,7 +67,7 @@ jobs: TURBO_TEAM: ${{ secrets.TURBO_TEAM }} - name: Upload Test Results if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.shard }}_${{ strategy.job-total }} path: test-results diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 8c5f9c1829..e9074b895e 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,6 +1,7 @@ name: "Pull Request Labeler" on: - - pull_request_target + pull_request_target: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -16,3 +17,81 @@ jobs: repo-token: "${{ secrets.GITHUB_TOKEN }}" # https://github.com/actions/labeler/issues/442#issuecomment-1297359481 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), + }); + } diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 47a5c19102..9bb6f3bdf7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,7 @@ jobs: - name: Upload ESLint report if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: lint-results path: lint-results diff --git a/.github/workflows/nextjs-bundle-analysis.yml b/.github/workflows/nextjs-bundle-analysis.yml index 972e8af225..4c8da86217 100644 --- a/.github/workflows/nextjs-bundle-analysis.yml +++ b/.github/workflows/nextjs-bundle-analysis.yml @@ -2,6 +2,7 @@ name: "Next.js Bundle Analysis" on: workflow_call: + workflow_dispatch: push: branches: - main @@ -27,14 +28,14 @@ jobs: npx -p nextjs-bundle-analysis@0.5.0 report - name: Upload bundle - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: bundle path: apps/web/.next/analyze/__bundle_analysis.json - name: Download base branch bundle stats uses: dawidd6/action-download-artifact@v2 - if: success() && github.event.number + if: success() with: workflow: nextjs-bundle-analysis.yml 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` # entry in your package.json file. - name: Compare with base branch bundle - if: success() && github.event.number + if: success() run: | cd apps/web ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare - name: Get comment body id: get-comment-body - if: success() && github.event.number + if: success() run: | cd apps/web body=$(cat .next/analyze/__bundle_analysis_comment.txt) body="${body//'%'/'%25'}" body="${body//$'\n'/'%0A'}" body="${body//$'\r'/'%0D'}" - echo ::set-output name=body::$body + echo "{name}={$body}" >> $GITHUB_OUTPUT - name: Find Comment - uses: peter-evans/find-comment@v1 - if: success() && github.event.number + uses: peter-evans/find-comment@v2 + if: success() id: fc with: issue-number: ${{ github.event.number }} body-includes: "" - 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 with: issue-number: ${{ github.event.number }} body: ${{ steps.get-comment-body.outputs.body }} - 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 with: issue-number: ${{ github.event.number }} diff --git a/.github/workflows/pr-assign-team-label.yml b/.github/workflows/pr-assign-team-label.yml deleted file mode 100644 index ecb601f75c..0000000000 --- a/.github/workflows/pr-assign-team-label.yml +++ /dev/null @@ -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" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c792d008bf..69d08f19d0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -4,9 +4,6 @@ on: pull_request_target: branches: - main - paths-ignore: - - "**.md" - - ".github/CODEOWNERS" merge_group: workflow_dispatch: @@ -21,9 +18,7 @@ jobs: permissions: pull-requests: read outputs: - app-store: ${{ steps.filter.outputs.app-store }} - embed: ${{ steps.filter.outputs.embed }} - embed-react: ${{ steps.filter.outputs.embed-react }} + has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }} steps: - uses: actions/checkout@v3 - uses: ./.github/actions/dangerous-git-checkout @@ -31,78 +26,83 @@ jobs: 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' - + has-files-requiring-all-checks: + - "!(**.md|.github/CODEOWNERS)" type-check: name: Type check + needs: [changes] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/check-types.yml secrets: inherit test: 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 lint: name: Linters + needs: [changes] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/lint.yml secrets: inherit build: name: Production build + needs: [changes] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/production-build.yml secrets: inherit 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 secrets: inherit e2e: name: E2E tests needs: [changes, lint, build] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/e2e.yml secrets: inherit e2e-app-store: name: E2E App Store tests needs: [changes, lint, build] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/e2e-app-store.yml secrets: inherit e2e-embed: name: E2E embeds tests needs: [changes, lint, build] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/e2e-embed.yml secrets: inherit e2e-embed-react: name: E2E React embeds tests needs: [changes, lint, build] + if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }} uses: ./.github/workflows/e2e-embed-react.yml secrets: inherit 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 secrets: inherit 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() 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') + 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 diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000000..1dc28a549a --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -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 diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 3ea85d6141..4cbddfc0a5 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -9,6 +9,10 @@ env: DATABASE_URL: ${{ secrets.CI_DATABASE_URL }} E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }} E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }} + E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }} + 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_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }} NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }} diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index fcabef9abc..6a06056cbc 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -44,5 +44,5 @@ jobs: with: header: pr-title-lint-error message: | - Thank you for following the naming conventions! 🙏 Feel free to join our [discord](https://go.cal.com/discord) and post your PR link to [collect XP and win prizes!](https://cal.com/blog/community-incentives) + Thank you for following the naming conventions! 🙏 Feel free to join our [discord](https://go.cal.com/discord) and post your PR link. diff --git a/.github/workflows/test.yml b/.github/workflows/unit-tests.yml similarity index 89% rename from .github/workflows/test.yml rename to .github/workflows/unit-tests.yml index d8ca18d282..14027bf444 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/unit-tests.yml @@ -11,7 +11,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/dangerous-git-checkout - - run: echo 'NODE_OPTIONS="--max_old_space_size=6144"' >> $GITHUB_ENV - 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 - run: yarn test diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c07cd934a..c344645fab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "typescript.preferences.importModuleSpecifier": "non-relative", "spellright.language": ["en"], diff --git a/.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch b/.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch similarity index 86% rename from .yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch rename to .yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch index 7cfa242404..6b0bfb2905 100644 --- a/.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch +++ b/.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch @@ -1,15 +1,15 @@ diff --git a/index.cjs b/index.cjs -index b645707a3549fc298508726e404243499bbed499..f34b0891e99b275a9218e253f303f43d31ef3f73 100644 +index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67746ad5a4 100644 --- a/index.cjs +++ b/index.cjs @@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) { - // https://github.com/babel/babel/issues/2212#issuecomment-131827986 - // An alternative approach: - // https://www.npmjs.com/package/babel-plugin-add-module-exports --exports = module.exports = min.parsePhoneNumberFromString --exports['default'] = min.parsePhoneNumberFromString -+// exports = module.exports = min.parsePhoneNumberFromString -+// exports['default'] = min.parsePhoneNumberFromString - - // `parsePhoneNumberFromString()` named export is now considered legacy: - // it has been promoted to a default export due to being too verbose. + // https://github.com/babel/babel/issues/2212#issuecomment-131827986 + // An alternative approach: + // https://www.npmjs.com/package/babel-plugin-add-module-exports +-exports = module.exports = min.parsePhoneNumberFromString +-exports['default'] = min.parsePhoneNumberFromString ++// exports = module.exports = min.parsePhoneNumberFromString ++// exports['default'] = min.parsePhoneNumberFromString + + // `parsePhoneNumberFromString()` named export is now considered legacy: + // it has been promoted to a default export due to being too verbose. diff --git a/README.md b/README.md index a617a65b1f..2092be150a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

Cal.com (formerly Calendso)

- The open-source Calendly alternative. + The open-source Calendly successor.
Learn more »
@@ -50,7 +50,7 @@ # Scheduling infrastructure for absolutely everyone -The open source Calendly alternative. You are in charge +The open source Calendly successor. You are in charge of your own data, workflow, and appearance. Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes, and even calls with our families. However, most tools are very limited in terms of control and customization. @@ -147,7 +147,7 @@ Here is what you need to be able to run Cal.com. - 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 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 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,12 +216,11 @@ 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. - - [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) 1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`. -1. Set a 24 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one). 1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`) In a development environment, run: @@ -555,6 +554,10 @@ following [Follow these steps](./packages/app-store/zoho-bigin/) +### Obtaining Pipedrive Client ID and Secret + +[Follow these steps](./packages/app-store/pipedrive-crm/) + ## Workflows ### Setting up SendGrid for Email reminders diff --git a/__checks__/organization.spec.ts b/__checks__/organization.spec.ts index 685f97e281..4c6a047341 100644 --- a/__checks__/organization.spec.ts +++ b/__checks__/organization.spec.ts @@ -41,6 +41,28 @@ test.describe("Org", () => { 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 diff --git a/apps/ai/README.md b/apps/ai/README.md index fdba31f370..306bf07906 100644 --- a/apps/ai/README.md +++ b/apps/ai/README.md @@ -48,17 +48,21 @@ Here is the full architecture: ### 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). 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). -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. -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". -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. Please feel free to improve any part of this architecture! diff --git a/apps/api/lib/validations/event-type.ts b/apps/api/lib/validations/event-type.ts index 70823213bf..cbabad6dc4 100644 --- a/apps/api/lib/validations/event-type.ts +++ b/apps/api/lib/validations/event-type.ts @@ -60,6 +60,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({ successRedirectUrl: true, locations: true, bookingLimits: true, + onlyShowFirstAvailableSlot: true, durationLimits: true, }) .merge( @@ -147,6 +148,7 @@ export const schemaEventTypeReadPublic = EventType.pick({ seatsShowAvailabilityCount: true, bookingFields: true, bookingLimits: true, + onlyShowFirstAvailableSlot: true, durationLimits: true, }).merge( z.object({ diff --git a/apps/api/lib/validations/user.ts b/apps/api/lib/validations/user.ts index 7c595a3352..98e876c4fe 100644 --- a/apps/api/lib/validations/user.ts +++ b/apps/api/lib/validations/user.ts @@ -92,7 +92,7 @@ export const schemaUserBaseBodyParams = User.pick({ // Here we can both require or not (adding optional or nullish) and also rewrite validations for any value // for example making weekStart only accept weekdays as input const schemaUserEditParams = z.object({ - email: z.string().email(), + email: z.string().email().toLowerCase(), username: usernameSchema, weekStart: z.nativeEnum(weekdays).optional(), brandColor: z.string().min(4).max(9).regex(/^#/).optional(), @@ -114,7 +114,7 @@ const schemaUserEditParams = z.object({ // merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end. const schemaUserCreateParams = z.object({ - email: z.string().email(), + email: z.string().email().toLowerCase(), username: usernameSchema, weekStart: z.nativeEnum(weekdays).optional(), brandColor: z.string().min(4).max(9).regex(/^#/).optional(), diff --git a/apps/api/next.config.js b/apps/api/next.config.js index ba2dcbf89b..4d861ce3c3 100644 --- a/apps/api/next.config.js +++ b/apps/api/next.config.js @@ -1,6 +1,8 @@ const { withAxiom } = require("next-axiom"); +const { withSentryConfig } = require("@sentry/nextjs"); -module.exports = withAxiom({ +const plugins = [withAxiom]; +const nextConfig = { transpilePackages: [ "@calcom/app-store", "@calcom/core", @@ -66,4 +68,15 @@ module.exports = withAxiom({ ], }; }, -}); +}; + +if (!!process.env.NEXT_PUBLIC_SENTRY_DSN) { + nextConfig["sentry"] = { + autoInstrumentServerFunctions: true, + hideSourceMaps: true, + }; + + plugins.push(withSentryConfig); +} + +module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig); diff --git a/apps/api/pages/api/bookings/[id]/_auth-middleware.ts b/apps/api/pages/api/bookings/[id]/_auth-middleware.ts index c785ccc3be..0016de42e9 100644 --- a/apps/api/pages/api/bookings/[id]/_auth-middleware.ts +++ b/apps/api/pages/api/bookings/[id]/_auth-middleware.ts @@ -6,18 +6,44 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform async function authMiddleware(req: NextApiRequest) { const { userId, prisma, isAdmin, query } = req; + if (isAdmin) { + return; + } + const { id } = schemaQueryIdParseInt.parse(query); - const userWithBookings = await prisma.user.findUnique({ + const userWithBookingsAndTeamIds = await prisma.user.findUnique({ where: { id: userId }, - include: { bookings: true }, + include: { + bookings: true, + teams: { + select: { + teamId: true, + }, + }, + }, }); - if (!userWithBookings) throw new HttpError({ statusCode: 404, message: "User not found" }); + if (!userWithBookingsAndTeamIds) throw new HttpError({ statusCode: 404, message: "User not found" }); - const userBookingIds = userWithBookings.bookings.map((booking) => booking.id); + const userBookingIds = userWithBookingsAndTeamIds.bookings.map((booking) => booking.id); - if (!isAdmin && !userBookingIds.includes(id)) { - throw new HttpError({ statusCode: 401, message: "You are not authorized" }); + if (!userBookingIds.includes(id)) { + const teamBookings = await prisma.booking.findUnique({ + where: { + id: id, + eventType: { + team: { + id: { + in: userWithBookingsAndTeamIds.teams.map((team) => team.teamId), + }, + }, + }, + }, + }); + + if (!teamBookings) { + throw new HttpError({ statusCode: 401, message: "You are not authorized" }); + } } } diff --git a/apps/api/pages/api/event-types/_post.ts b/apps/api/pages/api/event-types/_post.ts index 20b138f158..6e748a1bc2 100644 --- a/apps/api/pages/api/event-types/_post.ts +++ b/apps/api/pages/api/event-types/_post.ts @@ -3,8 +3,10 @@ import type { NextApiRequest } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; +import { MembershipRole } from "@calcom/prisma/client"; import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type"; +import { canUserAccessTeamWithRole } from "~/pages/api/teams/[teamId]/_auth-middleware"; import checkParentEventOwnership from "./_utils/checkParentEventOwnership"; import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission"; @@ -316,7 +318,13 @@ async function checkPermissions(req: NextApiRequest) { statusCode: 401, message: "ADMIN required for `userId`", }); - if (!isAdmin && body.teamId) + if ( + body.teamId && + !isAdmin && + !(await canUserAccessTeamWithRole(req.prisma, req.userId, isAdmin, body.teamId, { + in: [MembershipRole.OWNER, MembershipRole.ADMIN], + })) + ) throw new HttpError({ statusCode: 401, message: "ADMIN required for `teamId`", diff --git a/apps/api/pages/api/slots/_get.ts b/apps/api/pages/api/slots/_get.ts index 6393bc79c1..61447e1751 100644 --- a/apps/api/pages/api/slots/_get.ts +++ b/apps/api/pages/api/slots/_get.ts @@ -1,5 +1,9 @@ +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; import type { NextApiRequest, NextApiResponse } from "next"; +import dayjs from "@calcom/dayjs"; +import { isSupportedTimeZone } from "@calcom/lib/date-fns"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; import { createContext } from "@calcom/trpc/server/createContext"; @@ -9,10 +13,34 @@ import { getAvailableSlots } from "@calcom/trpc/server/routers/viewer/slots/util import { TRPCError } from "@trpc/server"; import { getHTTPStatusCodeFromError } from "@trpc/server/http"; +// Apply plugins +dayjs.extend(utc); +dayjs.extend(timezone); + async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const input = getScheduleSchema.parse(req.query); - return await getAvailableSlots({ ctx: await createContext({ req, res }), input }); + const { usernameList, ...rest } = req.query; + let slugs = usernameList; + if (!Array.isArray(usernameList)) { + slugs = usernameList ? [usernameList] : []; + } + const input = getScheduleSchema.parse({ usernameList: slugs, ...rest }); + const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false; + const availableSlots = await getAvailableSlots({ ctx: await createContext({ req, res }), input }); + const slotsInProvidedTimeZone = timeZoneSupported + ? Object.keys(availableSlots.slots).reduce( + (acc: Record, date) => { + acc[date] = availableSlots.slots[date].map((slot) => ({ + ...slot, + time: dayjs(slot.time).tz(input.timeZone).format(), + })); + return acc; + }, + {} + ) + : availableSlots; + + return slotsInProvidedTimeZone; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (cause) { if (cause instanceof TRPCError) { diff --git a/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts b/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts index 48ade69952..ec172679c2 100644 --- a/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts +++ b/apps/api/pages/api/teams/[teamId]/_auth-middleware.ts @@ -27,6 +27,16 @@ export async function checkPermissions( version: req.query.version, apiKey: req.query.apiKey, }); + return canUserAccessTeamWithRole(prisma, userId, isAdmin, teamId, role); +} + +export async function canUserAccessTeamWithRole( + prisma: NextApiRequest["prisma"], + userId: number, + isAdmin: boolean, + teamId: number, + role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER +) { const args: Prisma.TeamFindFirstArgs = { where: { id: teamId } }; /** If not ADMIN then we check if the actual user belongs to team and matches the required role */ if (!isAdmin) args.where = { ...args.where, members: { some: { userId, role } } }; diff --git a/apps/api/pages/api/teams/[teamId]/_patch.ts b/apps/api/pages/api/teams/[teamId]/_patch.ts index 93d1a3a46a..e1cfe2a865 100644 --- a/apps/api/pages/api/teams/[teamId]/_patch.ts +++ b/apps/api/pages/api/teams/[teamId]/_patch.ts @@ -64,6 +64,25 @@ export async function patchHandler(req: NextApiRequest) { where: { id: teamId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } }, }); 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; if (_team.slug === null && data.slug) { data.metadata = { diff --git a/apps/api/pages/api/teams/_post.ts b/apps/api/pages/api/teams/_post.ts index 56e0820535..a018ed81d6 100644 --- a/apps/api/pages/api/teams/_post.ts +++ b/apps/api/pages/api/teams/_post.ts @@ -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? const cloneData: typeof data & { metadata: NonNullable | undefined; diff --git a/apps/api/sentry.client.config.ts b/apps/api/sentry.client.config.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/apps/api/sentry.client.config.ts @@ -0,0 +1 @@ +export {}; diff --git a/apps/api/sentry.edge.config.ts b/apps/api/sentry.edge.config.ts new file mode 100644 index 0000000000..842f09fccc --- /dev/null +++ b/apps/api/sentry.edge.config.ts @@ -0,0 +1,5 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, +}); diff --git a/apps/api/sentry.server.config.ts b/apps/api/sentry.server.config.ts new file mode 100644 index 0000000000..a429088bd9 --- /dev/null +++ b/apps/api/sentry.server.config.ts @@ -0,0 +1,6 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, +}); diff --git a/apps/storybook/.gitignore b/apps/storybook/.gitignore index 8c444bd2f4..5689902ae1 100644 --- a/apps/storybook/.gitignore +++ b/apps/storybook/.gitignore @@ -8,7 +8,9 @@ pnpm-debug.log* lerna-debug.log* node_modules -storybook-static +storybook-static/* +!storybook-static/favicon.ico +!storybook-static/sb-cover.jpg dist dist-ssr *.local diff --git a/apps/storybook/.storybook/main.js b/apps/storybook/.storybook/main.js deleted file mode 100644 index 9b4c7f600c..0000000000 --- a/apps/storybook/.storybook/main.js +++ /dev/null @@ -1,76 +0,0 @@ -const path = require("path"); - -module.exports = { - stories: [ - "../intro.stories.mdx", - "../../../packages/ui/components/**/*.stories.mdx", - "../../../packages/atoms/**/*.stories.mdx", - "../../../packages/features/**/*.stories.mdx", - "../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)", - ], - addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "storybook-addon-rtl-direction", - "storybook-react-i18next", - "storybook-addon-next", - "storybook-addon-next-router", - /*{ - name: "storybook-addon-next", - options: { - nextConfigPath: path.resolve(__dirname, "../../web/next.config.js"), - }, - },*/ - ], - framework: "@storybook/react", - core: { - builder: "webpack5", - }, - staticDirs: ["../public"], - webpackFinal: async (config, { configType }) => { - config.resolve.fallback = { - fs: false, - assert: false, - buffer: false, - console: false, - constants: false, - crypto: false, - domain: false, - events: false, - http: false, - https: false, - os: false, - path: false, - punycode: false, - process: false, - querystring: false, - stream: false, - string_decoder: false, - sys: false, - timers: false, - tty: false, - url: false, - util: false, - vm: false, - zlib: false, - }; - - config.module.rules.push({ - test: /\.css$/, - use: [ - "style-loader", - { - loader: "css-loader", - options: { - modules: true, // Enable modules to help you using className - }, - }, - ], - include: path.resolve(__dirname, "../src"), - }); - - return config; - }, - typescript: { reactDocgen: "react-docgen" }, -}; diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts new file mode 100644 index 0000000000..bc68cb15c0 --- /dev/null +++ b/apps/storybook/.storybook/main.ts @@ -0,0 +1,96 @@ +import type { StorybookConfig } from "@storybook/nextjs"; +import path, { dirname, join } from "path"; + +const config: StorybookConfig = { + stories: [ + "../intro.stories.mdx", + "../../../packages/ui/components/**/*.stories.mdx", // legacy SB6 stories + "../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)", + "../../../packages/ui/components/**/*.docs.mdx", + "../../../packages/features/**/*.stories.@(js|jsx|ts|tsx)", + "../../../packages/features/**/*.docs.mdx", + "../../../packages/atoms/**/*.stories.@(js|jsx|ts|tsx)", + "../../../packages/atoms/**/*.docs.mdx", + ], + + addons: [ + getAbsolutePath("@storybook/addon-links"), + getAbsolutePath("@storybook/addon-essentials"), + getAbsolutePath("@storybook/addon-interactions"), + getAbsolutePath("storybook-addon-rtl-direction"), + getAbsolutePath("storybook-react-i18next"), + ], + + framework: { + name: getAbsolutePath("@storybook/nextjs") as "@storybook/nextjs", + + options: { + // builder: { + // fsCache: true, + // lazyCompilation: true, + // }, + }, + }, + + staticDirs: ["../public"], + + webpackFinal: async (config, { configType }) => { + config.resolve = config.resolve || {}; + config.resolve.fallback = { + fs: false, + assert: false, + buffer: false, + console: false, + constants: false, + crypto: false, + domain: false, + events: false, + http: false, + https: false, + os: false, + path: false, + punycode: false, + process: false, + querystring: false, + stream: false, + string_decoder: false, + sys: false, + timers: false, + tty: false, + url: false, + util: false, + vm: false, + zlib: false, + }; + + config.module = config.module || {}; + config.module.rules = config.module.rules || []; + config.module.rules.push({ + test: /\.css$/, + use: [ + "style-loader", + { + loader: "css-loader", + options: { + modules: true, // Enable modules to help you using className + }, + }, + ], + include: path.resolve(__dirname, "../src"), + }); + + return config; + }, + + typescript: { reactDocgen: "react-docgen" }, + + docs: { + autodocs: true, + }, +}; + +export default config; + +function getAbsolutePath(value) { + return dirname(require.resolve(join(value, "package.json"))); +} diff --git a/apps/storybook/.storybook/preview.jsx b/apps/storybook/.storybook/preview.jsx deleted file mode 100644 index d881ca6008..0000000000 --- a/apps/storybook/.storybook/preview.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { addDecorator } from "@storybook/react"; -import { AppRouterContext } from "next/dist/shared/lib/app-router-context"; -import { I18nextProvider } from "react-i18next"; - -import "../styles/globals.css"; -import "../styles/storybook-styles.css"; -import i18n from "./i18next"; - -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - nextRouter: { - pathname: "/", - asPath: "/", - query: {}, - push() {}, - Provider: AppRouterContext.Provider, - }, - globals: { - locale: "en", - locales: { - en: "English", - fr: "Français", - }, - }, - i18n, -}; - -addDecorator((storyFn) => ( - -

{storyFn()}
- -)); - -window.getEmbedNamespace = () => { - const url = new URL(document.URL); - const namespace = url.searchParams.get("embed"); - return namespace; -}; - -window.getEmbedTheme = () => { - return "auto"; -}; diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx new file mode 100644 index 0000000000..620f7a9ff6 --- /dev/null +++ b/apps/storybook/.storybook/preview.tsx @@ -0,0 +1,73 @@ +// adds tooltip context to all stories +import { TooltipProvider } from "@radix-ui/react-tooltip"; +import type { Preview } from "@storybook/react"; +import React from "react"; +import { I18nextProvider } from "react-i18next"; + +import type { EmbedThemeConfig } from "@calcom/embed-core/src/types"; +// adds trpc context to all stories (esp. booker) +import { StorybookTrpcProvider } from "@calcom/ui"; + +import "../styles/globals.css"; +import "../styles/storybook-styles.css"; +import i18n from "./i18next"; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + + globals: { + locale: "en", + locales: { + en: "English", + fr: "Français", + }, + }, + + i18n, + + nextjs: { + appDirectory: true, + }, + }, + + decorators: [ + (Story) => ( + + + +
+ +
+
+
+
+ ), + ], +}; + +export default preview; + +declare global { + interface Window { + getEmbedNamespace: () => string | null; + getEmbedTheme: () => EmbedThemeConfig | null; + } +} + +window.getEmbedNamespace = () => { + const url = new URL(document.URL); + const namespace = url.searchParams.get("embed"); + return namespace; +}; + +window.getEmbedTheme = () => { + return "auto"; +}; diff --git a/apps/storybook/components/CustomArgsTable.tsx b/apps/storybook/components/CustomArgsTable.tsx index e8feabf577..1bb51bbdd9 100644 --- a/apps/storybook/components/CustomArgsTable.tsx +++ b/apps/storybook/components/CustomArgsTable.tsx @@ -1,6 +1,6 @@ import { ArgsTable } from "@storybook/addon-docs"; -import { SortType } from "@storybook/components"; -import { PropDescriptor } from "@storybook/store"; +import type { SortType } from "@storybook/blocks"; +import type { PropDescriptor } from "@storybook/preview-api"; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore storybook addon types component as any so we have to do type Component = any; diff --git a/apps/storybook/intro.stories.mdx b/apps/storybook/intro.stories.mdx index d8ca68bf7c..f6858dc892 100644 --- a/apps/storybook/intro.stories.mdx +++ b/apps/storybook/intro.stories.mdx @@ -9,10 +9,7 @@ import { Meta } from "@storybook/addon-docs"; library, we will be adding more components as we go along.

- Our{" "} - - Figma - {" "} + Our Figma library is available for anyone to view and use. If you have any questions or concerns, please reach out to the design team.

diff --git a/apps/storybook/package.json b/apps/storybook/package.json index 3aae79cb0a..0cc7d6cb30 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -3,8 +3,8 @@ "private": true, "version": "0.0.0", "scripts": { - "dev": "start-storybook -p 6006", - "build": "build-storybook" + "dev": "storybook dev -p 6006", + "build": "storybook build" }, "dependencies": { "@calcom/config": "*", @@ -20,23 +20,25 @@ "@radix-ui/react-slider": "^1.0.0", "@radix-ui/react-switch": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0", + "@storybook/addon-docs": "^7.6.3", + "@storybook/blocks": "^7.6.3", + "@storybook/nextjs": "^7.6.3", + "@storybook/preview-api": "^7.6.3", "next": "^13.4.6", "react": "^18.2.0", "react-dom": "^18.2.0", - "storybook-addon-next-router": "^4.0.2", "storybook-addon-rtl-direction": "^0.0.19" }, "devDependencies": { "@babel/core": "^7.19.6", - "@storybook/addon-actions": "^6.5.13", - "@storybook/addon-essentials": "^6.5.13", - "@storybook/addon-interactions": "^6.5.13", - "@storybook/addon-links": "^6.5.13", - "@storybook/builder-vite": "^0.2.4", - "@storybook/builder-webpack5": "^6.5.13", - "@storybook/manager-webpack5": "^6.5.13", - "@storybook/react": "^6.5.13", - "@storybook/testing-library": "^0.0.13", + "@storybook/addon-actions": "^7.6.3", + "@storybook/addon-designs": "^7.0.7", + "@storybook/addon-essentials": "^7.6.3", + "@storybook/addon-interactions": "^7.6.3", + "@storybook/addon-links": "^7.6.3", + "@storybook/nextjs": "^7.6.3", + "@storybook/react": "^7.6.3", + "@storybook/testing-library": "^0.2.2", "@types/react": "18.0.26", "@types/react-dom": "^18.0.9", "@vitejs/plugin-react": "^2.2.0", @@ -46,9 +48,8 @@ "postcss": "^8.4.18", "postcss-pseudo-companion-classes": "^0.1.1", "rollup-plugin-polyfill-node": "^0.10.2", - "storybook-addon-designs": "^6.3.1", - "storybook-addon-next": "^1.6.9", - "storybook-react-i18next": "^1.1.2", + "storybook": "^7.6.3", + "storybook-react-i18next": "^2.0.9", "tailwindcss": "^3.3.3", "typescript": "^4.9.4", "vite": "^4.1.2" diff --git a/apps/storybook/styles/storybook-styles.css b/apps/storybook/styles/storybook-styles.css index 8893aebc15..78d8ce13b7 100644 --- a/apps/storybook/styles/storybook-styles.css +++ b/apps/storybook/styles/storybook-styles.css @@ -197,88 +197,91 @@ @layer { :root { /* background */ - - --cal-bg-emphasis: #e5e7eb; - --cal-bg: white; - --cal-bg-subtle: #f3f4f6; - --cal-bg-muted: #f9fafb; - --cal-bg-inverted: #111827; - + + --cal-bg-emphasis: hsla(220,13%,91%,1); + --cal-bg: hsla(0,0%,100%,1); + --cal-bg-subtle: hsla(220, 14%, 96%,1); + --cal-bg-muted: hsla(210,20%,98%,1); + --cal-bg-inverted: hsla(0,0%,6%,1); + /* background -> components*/ - --cal-bg-info: #dee9fc; - --cal-bg-success: #e2fbe8; - --cal-bg-attention: #fceed8; - --cal-bg-error: #f9e3e2; - --cal-bg-dark-error: #752522; - + --cal-bg-info: hsla(218,83%,98%,1); + --cal-bg-success: hsla(134,76%,94%,1); + --cal-bg-attention: hsla(37, 86%, 92%, 1); + --cal-bg-error: hsla(3,66,93,1); + --cal-bg-dark-error: hsla(2, 55%, 30%, 1); + /* Borders */ - --cal-border-emphasis: #9ca3af; - --cal-border: #d1d5db; - --cal-border-subtle: #e5e7eb; - --cal-border-muted: #f3f4f6; - --cal-border-error: #aa2e26; - + --cal-border-emphasis: hsla(218, 11%, 65%, 1); + --cal-border: hsla(216, 12%, 84%, 1); + --cal-border-subtle: hsla(220, 13%, 91%, 1); + --cal-border-booker: #e5e7eb; + --cal-border-muted: hsla(220, 14%, 96%, 1); + --cal-border-error: hsla(4, 63%, 41%, 1); + /* Content/Text */ - --cal-text-emphasis: #111827; - --cal-text: #374151; - --cal-text-subtle: #6b7280; - --cal-text-muted: #9ca3af; - --cal-text-inverted: white; - + --cal-text-emphasis: hsla(217, 19%, 27%, 1); + --cal-text: hsla(217, 19%, 27%, 1); + --cal-text-subtle: hsla(220, 9%, 46%, 1); + --cal-text-muted: hsla(218, 11%, 65%, 1); + --cal-text-inverted: hsla(0, 0%, 100%, 1); + /* Content/Text -> components */ - --cal-text-info: #253985; - --cal-text-success: #285231; - --cal-text-attention: #73321b; - --cal-text-error: #752522; - + --cal-text-info: hsla(228, 56%, 33%, 1); + --cal-text-success: hsla(133, 34%, 24%, 1); + --cal-text-attention: hsla(16, 62%, 28%, 1); + --cal-text-error: hsla(2, 55%, 30%, 1); + /* Brand shinanigans - -> These will be computed for the users theme at runtime. - */ - --cal-brand: #111827; - --cal-brand-emphasis: #101010; - --cal-brand-text: white; + -> These will be computed for the users theme at runtime. + */ + --cal-brand: hsla(221, 39%, 11%, 1); + --cal-brand-emphasis: hsla(0, 0%, 6%, 1); + --cal-brand-text: hsla(0, 0%, 100%, 1); } .dark { /* background */ - - --cal-bg-emphasis: #2b2b2b; - --cal-bg: #101010; - --cal-bg-subtle: #2b2b2b; - --cal-bg-muted: #1c1c1c; - --cal-bg-inverted: #f3f4f6; - + + --cal-bg-emphasis: hsla(0, 0%, 32%, 1); + --cal-bg: hsla(0, 0%, 10%, 1); + --cal-bg-subtle: hsla(0, 0%, 18%, 1); + --cal-bg-muted: hsla(0, 0%, 12%, 1); + --cal-bg-inverted: hsla(220, 14%, 96%, 1); + /* background -> components*/ - --cal-bg-info: #263fa9; - --cal-bg-success: #306339; - --cal-bg-attention: #8e3b1f; - --cal-bg-error: #8c2822; - --cal-bg-dark-error: #752522; - + --cal-bg-info: hsla(228, 56%, 33%, 1); + --cal-bg-success: hsla(133, 34%, 24%, 1); + --cal-bg-attention: hsla(16, 62%, 28%, 1); + --cal-bg-error: hsla(2, 55%, 30%, 1); + --cal-bg-dark-error: hsla(2, 55%, 30%, 1); + /* Borders */ - --cal-border-emphasis: #575757; - --cal-border: #444444; - --cal-border-subtle: #2b2b2b; - --cal-border-muted: #1c1c1c; - --cal-border-error: #aa2e26; - + --cal-border-emphasis: hsla(0, 0%, 46%, 1); + --cal-border: hsla(0, 0%, 34%, 1); + --cal-border-subtle: hsla(0, 0%, 22%, 1); + --cal-border-booker: hsla(0, 0%, 22%, 1); + --cal-border-muted: hsla(0, 0%, 18%, 1); + --cal-border-error: hsla(4, 63%, 41%, 1); + /* Content/Text */ - --cal-text-emphasis: #f3f4f6; - --cal-text: #d6d6d6; - --cal-text-subtle: #757575; - --cal-text-muted: #575757; - --cal-text-inverted: #101010; - + --cal-text-emphasis: hsla(240, 20%, 99%, 1); + --cal-text: hsla(0, 0%, 84%, 1); + --cal-text-subtle: hsla(0, 0%, 65%, 1); + --cal-text-muted: hsla(0, 0%, 34%, 1); + --cal-text-inverted: hsla(0, 0%, 10%, 1); + /* Content/Text -> components */ - --cal-text-info: #dee9fc; - --cal-text-success: #e2fbe8; - --cal-text-attention: #fceed8; - --cal-text-error: #f9e3e2; - + --cal-text-info: hsla(218, 83%, 93%, 1); + --cal-text-success: hsla(134, 76%, 94%, 1); + --cal-text-attention: hsla(37, 86%, 92%, 1); + --cal-text-error: hsla(3, 66%, 93%, 1); + /* Brand shenanigans - -> These will be computed for the users theme at runtime. - */ - --cal-brand: white; - --cal-brand-emphasis: #e1e1e1; - --cal-brand-text: black; + -> These will be computed for the users theme at runtime. + */ + --cal-brand: hsla(0, 0%, 100%, 1); + --cal-brand-emphasis: hsla(218, 11%, 65%, 1); + --cal-brand-text: hsla(0, 0%, 0%,1); } + } diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts index 5e300cf0d6..ef5c93ebc7 100644 --- a/apps/web/abTest/middlewareFactory.ts +++ b/apps/web/abTest/middlewareFactory.ts @@ -5,6 +5,19 @@ import z from "zod"; const ROUTES: [URLPattern, boolean][] = [ ["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const, + ["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const, + ["/apps/installed/:category", process.env.APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED === "1"] as const, + ["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_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/: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]) => [ new URLPattern({ pathname, @@ -27,7 +40,6 @@ export const abTestMiddlewareFactory = const override = req.cookies.has(FUTURE_ROUTES_OVERRIDE_COOKIE_NAME); const route = ROUTES.find(([regExp]) => regExp.test(req.url)) ?? null; - const enabled = route !== null ? route[1] || override : false; if (pathname.includes("future") || !enabled) { diff --git a/apps/web/app/_trpc/serverClient.ts b/apps/web/app/_trpc/serverClient.ts new file mode 100644 index 0000000000..e4b7d577f0 --- /dev/null +++ b/apps/web/app/_trpc/serverClient.ts @@ -0,0 +1,4 @@ +import type { TRPCContext } from "@calcom/trpc/server/createContext"; +import { appRouter } from "@calcom/trpc/server/routers/_app"; + +export const getServerCaller = (ctx: TRPCContext) => appRouter.createCaller(ctx); diff --git a/apps/web/app/_trpc/trpc-provider.tsx b/apps/web/app/_trpc/trpc-provider.tsx index f6d2ed2817..6e81d2996a 100644 --- a/apps/web/app/_trpc/trpc-provider.tsx +++ b/apps/web/app/_trpc/trpc-provider.tsx @@ -8,33 +8,8 @@ import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink"; import { httpLink } from "@calcom/trpc/client/links/httpLink"; import { loggerLink } from "@calcom/trpc/client/links/loggerLink"; import { splitLink } from "@calcom/trpc/client/links/splitLink"; +import { ENDPOINTS } from "@calcom/trpc/react/shared"; -const ENDPOINTS = [ - "admin", - "apiKeys", - "appRoutingForms", - "apps", - "auth", - "availability", - "appBasecamp3", - "bookings", - "deploymentSetup", - "eventTypes", - "features", - "insights", - "payments", - "public", - "saml", - "slots", - "teams", - "organizations", - "users", - "viewer", - "webhook", - "workflows", - "appsRouter", - "googleWorkspace", -] as const; export type Endpoint = (typeof ENDPOINTS)[number]; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/web/app/_types.ts b/apps/web/app/_types.ts new file mode 100644 index 0000000000..0e6ce75a13 --- /dev/null +++ b/apps/web/app/_types.ts @@ -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 }; diff --git a/apps/web/app/future/(shared-page-wrapper)/(layout)/layout.tsx b/apps/web/app/future/(shared-page-wrapper)/(layout)/layout.tsx deleted file mode 100644 index 7d78c3b422..0000000000 --- a/apps/web/app/future/(shared-page-wrapper)/(layout)/layout.tsx +++ /dev/null @@ -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 ( - - {children} - - ); -} diff --git a/apps/web/app/future/apps/[slug]/layout.tsx b/apps/web/app/future/apps/[slug]/layout.tsx new file mode 100644 index 0000000000..dc2fb3468f --- /dev/null +++ b/apps/web/app/future/apps/[slug]/layout.tsx @@ -0,0 +1,3 @@ +import { WithLayout } from "app/layoutHOC"; + +export default WithLayout({ getLayout: null })<"L">; diff --git a/apps/web/app/future/apps/[slug]/page.tsx b/apps/web/app/future/apps/[slug]/page.tsx new file mode 100644 index 0000000000..ec1121c949 --- /dev/null +++ b/apps/web/app/future/apps/[slug]/page.tsx @@ -0,0 +1,126 @@ +import AppPage from "@pages/apps/[slug]/index"; +import { Prisma } from "@prisma/client"; +import { _generateMetadata } from "app/_utils"; +import fs from "fs"; +import matter from "gray-matter"; +import { notFound } from "next/navigation"; +import path from "path"; +import { z } from "zod"; + +import { getAppWithMetadata } from "@calcom/app-store/_appRegistry"; +import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath"; +import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; + +const sourceSchema = z.object({ + content: z.string(), + data: z.object({ + description: z.string().optional(), + items: z + .array( + z.union([ + z.string(), + z.object({ + iframe: z.object({ src: z.string() }), + }), + ]) + ) + .optional(), + }), +}); + +export const generateMetadata = async ({ params }: { params: Record }) => { + const { data } = await getPageProps({ params }); + + return await _generateMetadata( + () => `${data.name} | ${APP_NAME}`, + () => data.description + ); +}; + +export const generateStaticParams = async () => { + try { + const appStore = await prisma.app.findMany({ select: { slug: true } }); + return appStore.map(({ slug }) => ({ slug })); + } catch (e: unknown) { + if (e instanceof Prisma.PrismaClientInitializationError) { + // Database is not available at build time, but that's ok – we fall back to resolving paths on demand + } else { + throw e; + } + } + + return []; +}; + +const getPageProps = async ({ params }: { params: Record }) => { + if (typeof params?.slug !== "string") { + notFound(); + } + + const appMeta = await getAppWithMetadata({ + slug: params?.slug, + }); + + const appFromDb = await prisma.app.findUnique({ + where: { slug: params.slug.toLowerCase() }, + }); + + const isAppAvailableInFileSystem = appMeta; + const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled); + + if (!IS_PRODUCTION && isAppDisabled) { + return { + isAppDisabled: true as const, + data: { + ...appMeta, + }, + }; + } + + if (!appFromDb || !appMeta || isAppDisabled) { + notFound(); + } + + const isTemplate = appMeta.isTemplate; + const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName); + const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`); + const postFilePath = path.join(README_PATH); + let source = ""; + + try { + source = fs.readFileSync(postFilePath).toString(); + source = source.replace(/{DESCRIPTION}/g, appMeta.description); + } catch (error) { + /* If the app doesn't have a README we fallback to the package description */ + console.log(`No DESCRIPTION.md provided for: ${appDirname}`); + source = appMeta.description; + } + + const result = matter(source); + const { content, data } = sourceSchema.parse({ content: result.content, data: result.data }); + if (data.items) { + data.items = data.items.map((item) => { + if (typeof item === "string") { + return getAppAssetFullPath(item, { + dirName: appMeta.dirName, + isTemplate: appMeta.isTemplate, + }); + } + return item; + }); + } + return { + isAppDisabled: false as const, + source: { content, data }, + data: appMeta, + }; +}; + +export default async function Page({ params }: { params: Record }) { + const pageProps = await getPageProps({ params }); + + return ; +} + +export const dynamic = "force-static"; diff --git a/apps/web/app/future/apps/[slug]/setup/page.tsx b/apps/web/app/future/apps/[slug]/setup/page.tsx new file mode 100644 index 0000000000..ce6abc75ba --- /dev/null +++ b/apps/web/app/future/apps/[slug]/setup/page.tsx @@ -0,0 +1,36 @@ +import SetupPage from "@pages/apps/[slug]/setup"; +import { _generateMetadata } from "app/_utils"; +import type { GetServerSidePropsContext } from "next"; +import { cookies, headers } from "next/headers"; +import { notFound, redirect } from "next/navigation"; + +import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; +import { APP_NAME } from "@calcom/lib/constants"; + +export const generateMetadata = async ({ params }: { params: Record }) => { + return await _generateMetadata( + () => `${params.slug} | ${APP_NAME}`, + () => "" + ); +}; + +const getPageProps = async ({ params }: { params: Record }) => { + const req = { headers: headers(), cookies: cookies() }; + + const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext); + + if (!result || "notFound" in result) { + notFound(); + } + + if ("redirect" in result) { + redirect(result.redirect.destination); + } + + return result.props; +}; + +export default async function Page({ params }: { params: Record }) { + const pageProps = await getPageProps({ params }); + return ; +} diff --git a/apps/web/app/future/apps/categories/[category]/page.tsx b/apps/web/app/future/apps/categories/[category]/page.tsx new file mode 100644 index 0000000000..6f3d54e434 --- /dev/null +++ b/apps/web/app/future/apps/categories/[category]/page.tsx @@ -0,0 +1,71 @@ +import CategoryPage from "@pages/apps/categories/[category]"; +import { Prisma } from "@prisma/client"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { notFound } from "next/navigation"; +import z from "zod"; + +import { getAppRegistry } from "@calcom/app-store/_appRegistry"; +import { APP_NAME } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; +import { AppCategories } from "@calcom/prisma/enums"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => `${APP_NAME} | ${APP_NAME}`, + () => "" + ); +}; + +export const generateStaticParams = async () => { + const paths = Object.keys(AppCategories); + + try { + await prisma.$queryRaw`SELECT 1`; + } catch (e: unknown) { + if (e instanceof Prisma.PrismaClientInitializationError) { + // Database is not available at build time. Make sure we fall back to building these pages on demand + return []; + } else { + throw e; + } + } + + return paths.map((category) => ({ category })); +}; + +const querySchema = z.object({ + category: z.nativeEnum(AppCategories), +}); + +const getPageProps = async ({ params }: { params: Record }) => { + const p = querySchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + + const appQuery = await prisma.app.findMany({ + where: { + categories: { + has: p.data.category, + }, + }, + select: { + slug: true, + }, + }); + + const dbAppsSlugs = appQuery.map((category) => category.slug); + + const appStore = await getAppRegistry(); + + const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug)); + return { + apps, + }; +}; + +// @ts-expect-error getData arg +export default WithLayout({ getData: getPageProps, Page: CategoryPage })<"P">; +export const dynamic = "force-static"; diff --git a/apps/web/app/future/apps/categories/page.tsx b/apps/web/app/future/apps/categories/page.tsx new file mode 100644 index 0000000000..d3b36bb356 --- /dev/null +++ b/apps/web/app/future/apps/categories/page.tsx @@ -0,0 +1,47 @@ +import LegacyPage from "@pages/apps/categories/index"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { APP_NAME } from "@calcom/lib/constants"; + +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => `Categories | ${APP_NAME}`, + () => "" + ); +}; + +const getData = async (ctx: ReturnType) => { + // @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 | IncomingMessage + const session = await getServerSession({ req: ctx.req }); + + let appStore; + if (session?.user?.id) { + appStore = await getAppRegistryWithCredentials(session.user.id); + } else { + appStore = await getAppRegistry(); + } + + const categories = appStore.reduce((c, app) => { + for (const category of app.categories) { + c[category] = c[category] ? c[category] + 1 : 1; + } + return c; + }, {} as Record); + + return { + categories: Object.entries(categories).map(([name, count]) => ({ name, count })), + dehydratedState: ssr.dehydrate(), + }; +}; + +export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">; diff --git a/apps/web/app/future/apps/installed/[category]/layout.tsx b/apps/web/app/future/apps/installed/[category]/layout.tsx new file mode 100644 index 0000000000..dc2fb3468f --- /dev/null +++ b/apps/web/app/future/apps/installed/[category]/layout.tsx @@ -0,0 +1,3 @@ +import { WithLayout } from "app/layoutHOC"; + +export default WithLayout({ getLayout: null })<"L">; diff --git a/apps/web/app/future/apps/installed/[category]/page.tsx b/apps/web/app/future/apps/installed/[category]/page.tsx new file mode 100644 index 0000000000..44b511f0f8 --- /dev/null +++ b/apps/web/app/future/apps/installed/[category]/page.tsx @@ -0,0 +1,36 @@ +import LegacyPage from "@pages/apps/installed/[category]"; +import { _generateMetadata } from "app/_utils"; +import { notFound } from "next/navigation"; +import { z } from "zod"; + +import { APP_NAME } from "@calcom/lib/constants"; +import { AppCategories } from "@calcom/prisma/enums"; + +const querySchema = z.object({ + category: z.nativeEnum(AppCategories), +}); + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${t("installed_apps")} | ${APP_NAME}`, + (t) => t("manage_your_connected_apps") + ); +}; + +const getPageProps = async ({ params }: { params: Record }) => { + const p = querySchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + + return { + category: p.data.category, + }; +}; + +export default async function Page({ params }: { params: Record }) { + await getPageProps({ params }); + + return ; +} diff --git a/apps/web/app/future/apps/page.tsx b/apps/web/app/future/apps/page.tsx new file mode 100644 index 0000000000..0a24ec567e --- /dev/null +++ b/apps/web/app/future/apps/page.tsx @@ -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) => { + // @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); + + 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 }); diff --git a/apps/web/app/future/bookings/[status]/layout.tsx b/apps/web/app/future/bookings/[status]/layout.tsx new file mode 100644 index 0000000000..3eff385303 --- /dev/null +++ b/apps/web/app/future/bookings/[status]/layout.tsx @@ -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) => { + 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"; diff --git a/apps/web/app/future/bookings/[status]/page.tsx b/apps/web/app/future/bookings/[status]/page.tsx new file mode 100644 index 0000000000..bd24129e55 --- /dev/null +++ b/apps/web/app/future/bookings/[status]/page.tsx @@ -0,0 +1 @@ +export { default } from "@pages/bookings/[status]"; diff --git a/apps/web/app/future/event-types/layout.tsx b/apps/web/app/future/event-types/layout.tsx new file mode 100644 index 0000000000..9bf51a70f5 --- /dev/null +++ b/apps/web/app/future/event-types/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/MainLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/(shared-page-wrapper)/(layout)/event-types/page.tsx b/apps/web/app/future/event-types/page.tsx similarity index 100% rename from apps/web/app/future/(shared-page-wrapper)/(layout)/event-types/page.tsx rename to apps/web/app/future/event-types/page.tsx diff --git a/apps/web/app/future/getting-started/[[...step]]/page.tsx b/apps/web/app/future/getting-started/[[...step]]/page.tsx new file mode 100644 index 0000000000..2751566cd5 --- /dev/null +++ b/apps/web/app/future/getting-started/[[...step]]/page.tsx @@ -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) => { + 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 }); diff --git a/apps/web/app/future/settings/admin/apps/[category]/page.tsx b/apps/web/app/future/settings/admin/apps/[category]/page.tsx new file mode 100644 index 0000000000..0805169a9e --- /dev/null +++ b/apps/web/app/future/settings/admin/apps/[category]/page.tsx @@ -0,0 +1,10 @@ +import Page from "@pages/settings/admin/apps/[category]"; +import { _generateMetadata } from "app/_utils"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("apps"), + (t) => t("admin_apps_description") + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/apps/layout.tsx b/apps/web/app/future/settings/admin/apps/layout.tsx new file mode 100644 index 0000000000..f415bae764 --- /dev/null +++ b/apps/web/app/future/settings/admin/apps/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/admin/apps/page.tsx b/apps/web/app/future/settings/admin/apps/page.tsx new file mode 100644 index 0000000000..c53f6eb9f9 --- /dev/null +++ b/apps/web/app/future/settings/admin/apps/page.tsx @@ -0,0 +1,10 @@ +import Page from "@pages/settings/admin/apps/index"; +import { _generateMetadata } from "app/_utils"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("apps"), + (t) => t("admin_apps_description") + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/flags/layout.tsx b/apps/web/app/future/settings/admin/flags/layout.tsx new file mode 100644 index 0000000000..f415bae764 --- /dev/null +++ b/apps/web/app/future/settings/admin/flags/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/admin/flags/page.tsx b/apps/web/app/future/settings/admin/flags/page.tsx new file mode 100644 index 0000000000..adf10ecbc3 --- /dev/null +++ b/apps/web/app/future/settings/admin/flags/page.tsx @@ -0,0 +1,10 @@ +import Page from "@pages/settings/admin/flags"; +import { _generateMetadata } from "app/_utils"; + +export const generateMetadata = async () => + await _generateMetadata( + () => "Feature Flags", + () => "Here you can toggle your Cal.com instance features." + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/impersonation/layout.tsx b/apps/web/app/future/settings/admin/impersonation/layout.tsx new file mode 100644 index 0000000000..f415bae764 --- /dev/null +++ b/apps/web/app/future/settings/admin/impersonation/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/admin/impersonation/page.tsx b/apps/web/app/future/settings/admin/impersonation/page.tsx new file mode 100644 index 0000000000..f18a6df858 --- /dev/null +++ b/apps/web/app/future/settings/admin/impersonation/page.tsx @@ -0,0 +1,10 @@ +import Page from "@pages/settings/admin/impersonation"; +import { _generateMetadata } from "app/_utils"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("admin"), + (t) => t("impersonation") + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/oAuth/oAuthView/layout.tsx b/apps/web/app/future/settings/admin/oAuth/oAuthView/layout.tsx new file mode 100644 index 0000000000..dc2fb3468f --- /dev/null +++ b/apps/web/app/future/settings/admin/oAuth/oAuthView/layout.tsx @@ -0,0 +1,3 @@ +import { WithLayout } from "app/layoutHOC"; + +export default WithLayout({ getLayout: null })<"L">; diff --git a/apps/web/app/future/settings/admin/oAuth/oAuthView/page.tsx b/apps/web/app/future/settings/admin/oAuth/oAuthView/page.tsx new file mode 100644 index 0000000000..165622e62a --- /dev/null +++ b/apps/web/app/future/settings/admin/oAuth/oAuthView/page.tsx @@ -0,0 +1,10 @@ +import Page from "@pages/settings/admin/oAuth/oAuthView"; +import { _generateMetadata } from "app/_utils"; + +export const generateMetadata = async () => + await _generateMetadata( + () => "OAuth", + () => "Add new OAuth Clients" + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/oAuth/page.tsx b/apps/web/app/future/settings/admin/oAuth/page.tsx new file mode 100644 index 0000000000..58a8a41dd6 --- /dev/null +++ b/apps/web/app/future/settings/admin/oAuth/page.tsx @@ -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">; diff --git a/apps/web/app/future/settings/admin/organizations/layout.tsx b/apps/web/app/future/settings/admin/organizations/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/admin/organizations/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/admin/organizations/page.tsx b/apps/web/app/future/settings/admin/organizations/page.tsx new file mode 100644 index 0000000000..7ec2b7fa6b --- /dev/null +++ b/apps/web/app/future/settings/admin/organizations/page.tsx @@ -0,0 +1,11 @@ +import { _generateMetadata } from "app/_utils"; + +import Page from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("organizations"), + (t) => t("orgs_page_description") + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/page.tsx b/apps/web/app/future/settings/admin/page.tsx new file mode 100644 index 0000000000..cfc6e0aeec --- /dev/null +++ b/apps/web/app/future/settings/admin/page.tsx @@ -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">; diff --git a/apps/web/app/future/settings/admin/users/[id]/edit/page.tsx b/apps/web/app/future/settings/admin/users/[id]/edit/page.tsx new file mode 100644 index 0000000000..ae354725f6 --- /dev/null +++ b/apps/web/app/future/settings/admin/users/[id]/edit/page.tsx @@ -0,0 +1,36 @@ +import { getServerCaller } from "app/_trpc/serverClient"; +import { type Params } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { cookies, headers } from "next/headers"; +import { z } from "zod"; + +import Page from "@calcom/features/ee/users/pages/users-edit-view"; +import prisma from "@calcom/prisma"; + +const userIdSchema = z.object({ id: z.coerce.number() }); + +export const generateMetadata = async ({ params }: { params: Params }) => { + const input = userIdSchema.safeParse(params); + + let title = ""; + if (!input.success) { + title = "Editing user"; + } else { + const req = { + headers: headers(), + cookies: cookies(), + }; + + // @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest' + const data = await getServerCaller({ req, prisma }).viewer.users.get({ userId: input.data.id }); + const { user } = data; + title = `Editing user: ${user.username}`; + } + + return await _generateMetadata( + () => title, + () => "Here you can edit a current user." + ); +}; + +export default Page; diff --git a/apps/web/app/future/settings/admin/users/add/page.tsx b/apps/web/app/future/settings/admin/users/add/page.tsx new file mode 100644 index 0000000000..511ad02a7d --- /dev/null +++ b/apps/web/app/future/settings/admin/users/add/page.tsx @@ -0,0 +1,11 @@ +import { _generateMetadata } from "app/_utils"; + +import Page from "@calcom/features/ee/users/pages/users-add-view"; + +export const generateMetadata = async () => + await _generateMetadata( + () => "Add new user", + () => "Here you can add a new user." + ); + +export default Page; diff --git a/apps/web/app/future/settings/admin/users/layout.tsx b/apps/web/app/future/settings/admin/users/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/admin/users/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/admin/users/page.tsx b/apps/web/app/future/settings/admin/users/page.tsx new file mode 100644 index 0000000000..0bd414f599 --- /dev/null +++ b/apps/web/app/future/settings/admin/users/page.tsx @@ -0,0 +1,11 @@ +import { _generateMetadata } from "app/_utils"; + +import Page from "@calcom/features/ee/users/pages/users-listing-view"; + +export const generateMetadata = async () => + await _generateMetadata( + () => "Users", + () => "A list of all the users in your account including their name, title, email and role." + ); + +export default Page; diff --git a/apps/web/app/future/settings/teams/[id]/appearance/layout.tsx b/apps/web/app/future/settings/teams/[id]/appearance/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/appearance/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/[id]/appearance/page.tsx b/apps/web/app/future/settings/teams/[id]/appearance/page.tsx new file mode 100644 index 0000000000..ac76104d07 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/appearance/page.tsx @@ -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; diff --git a/apps/web/app/future/settings/teams/[id]/billing/layout.tsx b/apps/web/app/future/settings/teams/[id]/billing/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/billing/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/[id]/billing/page.tsx b/apps/web/app/future/settings/teams/[id]/billing/page.tsx new file mode 100644 index 0000000000..96b7f2f3b3 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/billing/page.tsx @@ -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; diff --git a/apps/web/app/future/settings/teams/[id]/members/layout.tsx b/apps/web/app/future/settings/teams/[id]/members/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/members/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/[id]/members/page.tsx b/apps/web/app/future/settings/teams/[id]/members/page.tsx new file mode 100644 index 0000000000..0f38c54f59 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/members/page.tsx @@ -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; diff --git a/apps/web/app/future/settings/teams/[id]/onboard-members/page.tsx b/apps/web/app/future/settings/teams/[id]/onboard-members/page.tsx new file mode 100644 index 0000000000..adb33f8b63 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/onboard-members/page.tsx @@ -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">; diff --git a/apps/web/app/future/settings/teams/[id]/profile/layout.tsx b/apps/web/app/future/settings/teams/[id]/profile/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/profile/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/[id]/profile/page.tsx b/apps/web/app/future/settings/teams/[id]/profile/page.tsx new file mode 100644 index 0000000000..b2e02352ef --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/profile/page.tsx @@ -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; diff --git a/apps/web/app/future/settings/teams/[id]/sso/layout.tsx b/apps/web/app/future/settings/teams/[id]/sso/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/sso/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/[id]/sso/page.tsx b/apps/web/app/future/settings/teams/[id]/sso/page.tsx new file mode 100644 index 0000000000..8fba20bd29 --- /dev/null +++ b/apps/web/app/future/settings/teams/[id]/sso/page.tsx @@ -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; diff --git a/apps/web/app/future/settings/teams/layout.tsx b/apps/web/app/future/settings/teams/layout.tsx new file mode 100644 index 0000000000..1359b26601 --- /dev/null +++ b/apps/web/app/future/settings/teams/layout.tsx @@ -0,0 +1,5 @@ +import { WithLayout } from "app/layoutHOC"; + +import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir"; + +export default WithLayout({ getLayout })<"L">; diff --git a/apps/web/app/future/settings/teams/new/page.tsx b/apps/web/app/future/settings/teams/new/page.tsx new file mode 100644 index 0000000000..592517ab48 --- /dev/null +++ b/apps/web/app/future/settings/teams/new/page.tsx @@ -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">; diff --git a/apps/web/app/future/settings/teams/page.ts b/apps/web/app/future/settings/teams/page.ts new file mode 100644 index 0000000000..6175f853ec --- /dev/null +++ b/apps/web/app/future/settings/teams/page.ts @@ -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; diff --git a/apps/web/app/future/teams/page.tsx b/apps/web/app/future/teams/page.tsx new file mode 100644 index 0000000000..fe17ec9fb4 --- /dev/null +++ b/apps/web/app/future/teams/page.tsx @@ -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) { + // @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">; diff --git a/apps/web/app/future/video/[uid]/page.tsx b/apps/web/app/future/video/[uid]/page.tsx new file mode 100644 index 0000000000..1e4d7ee00a --- /dev/null +++ b/apps/web/app/future/video/[uid]/page.tsx @@ -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) { + // @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">; diff --git a/apps/web/app/future/video/meeting-ended/[uid]/page.tsx b/apps/web/app/future/video/meeting-ended/[uid]/page.tsx new file mode 100644 index 0000000000..0674d39566 --- /dev/null +++ b/apps/web/app/future/video/meeting-ended/[uid]/page.tsx @@ -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) { + 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">; diff --git a/apps/web/app/future/video/meeting-not-started/[uid]/page.tsx b/apps/web/app/future/video/meeting-not-started/[uid]/page.tsx new file mode 100644 index 0000000000..bde0c18328 --- /dev/null +++ b/apps/web/app/future/video/meeting-not-started/[uid]/page.tsx @@ -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) { + 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">; diff --git a/apps/web/app/future/video/no-meeting-found/page.tsx b/apps/web/app/future/video/no-meeting-found/page.tsx new file mode 100644 index 0000000000..9c69388ad3 --- /dev/null +++ b/apps/web/app/future/video/no-meeting-found/page.tsx @@ -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) => { + // @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">; diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index f17543074f..ecdfb2cd8d 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,4 +1,6 @@ import { dir } from "i18next"; +import { Inter } from "next/font/google"; +import localFont from "next/font/local"; import { headers, cookies } from "next/headers"; import Script from "next/script"; import React from "react"; @@ -10,6 +12,14 @@ import { prepareRootMetadata } from "@lib/metadata"; import "../styles/globals.css"; +const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" }); +const calFont = localFont({ + src: "../fonts/CalSans-SemiBold.woff2", + variable: "--font-cal", + preload: true, + display: "block", +}); + export const generateMetadata = () => prepareRootMetadata({ twitterCreator: "@calcom", @@ -66,9 +76,15 @@ export default async function RootLayout({ children }: { children: React.ReactNo src="https://snippet.meticulous.ai/v1/stagingMeticulousSnippet.js" /> )} + > = { + getLayout: ((page: React.ReactElement) => React.ReactNode) | null; + Page?: (props: T) => React.ReactElement; + getData?: (arg: ReturnType) => Promise; +}; + +export function WithLayout>({ getLayout, getData, Page }: WithLayoutParams) { + return async

(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 ( + + {Page ? : children} + + ); + }; +} diff --git a/apps/web/components/PageWrapperAppDir.tsx b/apps/web/components/PageWrapperAppDir.tsx index 414e4009bc..ae36417c97 100644 --- a/apps/web/components/PageWrapperAppDir.tsx +++ b/apps/web/components/PageWrapperAppDir.tsx @@ -2,8 +2,6 @@ import { type DehydratedState } from "@tanstack/react-query"; import type { SSRConfig } from "next-i18next"; -import { Inter } from "next/font/google"; -import localFont from "next/font/local"; // import I18nLanguageHandler from "@components/I18nLanguageHandler"; import { usePathname } from "next/navigation"; import Script from "next/script"; @@ -20,17 +18,9 @@ export interface CalPageWrapper { PageWrapper?: AppProps["Component"]["PageWrapper"]; } -const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" }); -const calFont = localFont({ - src: "../fonts/CalSans-SemiBold.woff2", - variable: "--font-cal", - preload: true, - display: "swap", -}); - export type PageWrapperProps = Readonly<{ getLayout: ((page: React.ReactElement) => ReactNode) | null; - children: React.ReactElement; + children: React.ReactNode; requiresLicense: boolean; nonce: string | undefined; themeBasis: string | null; @@ -71,15 +61,8 @@ function PageWrapper(props: PageWrapperProps) { id="page-status" dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }} /> - - {getLayout( - props.requiresLicense ? {props.children} : props.children + props.requiresLicense ? {props.children} : <>{props.children} )} diff --git a/apps/web/components/auth/layouts/AdminLayout.tsx b/apps/web/components/auth/layouts/AdminLayout.tsx index 7289c08f0c..5c51ef47ce 100644 --- a/apps/web/components/auth/layouts/AdminLayout.tsx +++ b/apps/web/components/auth/layouts/AdminLayout.tsx @@ -26,7 +26,7 @@ export default function AdminLayout({ const isAppsPage = pathname?.startsWith("/settings/admin/apps"); return ( -

+
*]:flex-1"}> {children}
diff --git a/apps/web/components/auth/layouts/AdminLayoutAppDir.tsx b/apps/web/components/auth/layouts/AdminLayoutAppDir.tsx new file mode 100644 index 0000000000..b3d76190c4 --- /dev/null +++ b/apps/web/components/auth/layouts/AdminLayoutAppDir.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useSession } from "next-auth/react"; +import { usePathname, useRouter } from "next/navigation"; +import type { ComponentProps } from "react"; +import React, { useEffect } from "react"; + +import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout"; +import type Shell from "@calcom/features/shell/Shell"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import { ErrorBoundary } from "@calcom/ui"; + +export default function AdminLayout({ + children, + ...rest +}: { children: React.ReactNode } & ComponentProps) { + const pathname = usePathname(); + const session = useSession(); + const router = useRouter(); + + // Force redirect on component level + useEffect(() => { + if (session.data && session.data.user.role !== UserPermissionRole.ADMIN) { + router.replace("/settings/my-account/profile"); + } + }, [session, router]); + + const isAppsPage = pathname?.startsWith("/settings/admin/apps"); + return ( + +
+
*]:flex-1"}> + {children} +
+
+
+ ); +} + +export const getLayout = (page: React.ReactElement) => {page}; diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index f2281abd8a..5abfb821cc 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -1,8 +1,12 @@ import Link from "next/link"; import { useState } from "react"; -import type { EventLocationType } from "@calcom/app-store/locations"; -import { getEventLocationType } from "@calcom/app-store/locations"; +import type { EventLocationType, getEventLocationValue } from "@calcom/app-store/locations"; +import { + getEventLocationType, + getSuccessPageLocationMessage, + guessEventLocationType, +} from "@calcom/app-store/locations"; import dayjs from "@calcom/dayjs"; // TODO: Use browser locale, implement Intl in Dayjs maybe? import "@calcom/dayjs/locales"; @@ -14,6 +18,7 @@ import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { getEveryFreqFor } from "@calcom/lib/recurringStrings"; import { BookingStatus } from "@calcom/prisma/enums"; +import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import type { ActionType } from "@calcom/ui"; @@ -93,6 +98,16 @@ function BookingListItem(booking: BookingItemProps) { const paymentAppData = getPaymentAppData(booking.eventType); + const location = booking.location as ReturnType; + const locationVideoCallUrl = bookingMetadataSchema.parse(booking?.metadata || {})?.videoCallUrl; + + const locationToDisplay = getSuccessPageLocationMessage( + locationVideoCallUrl ? locationVideoCallUrl : location, + t, + booking.status + ); + const provider = guessEventLocationType(location); + const bookingConfirm = async (confirm: boolean) => { let body = { bookingId: booking.id, @@ -359,6 +374,33 @@ function BookingListItem(booking: BookingItemProps) { attendees={booking.attendees} />
+ {!isPending && ( + + )} {isPending && ( {t("unconfirmed")} @@ -525,8 +567,8 @@ const RecurringBookingsTooltip = ({ return ( recurringDate >= now && !booking.recurringInfo?.bookings[BookingStatus.CANCELLED] - .map((date) => date.toDateString()) - .includes(recurringDate.toDateString()) + .map((date) => date.toString()) + .includes(recurringDate.toString()) ); }).length; @@ -543,8 +585,8 @@ const RecurringBookingsTooltip = ({ const pastOrCancelled = aDate < now || booking.recurringInfo?.bookings[BookingStatus.CANCELLED] - .map((date) => date.toDateString()) - .includes(aDate.toDateString()); + .map((date) => date.toString()) + .includes(aDate.toString()); return (

{formatTime(aDate, userTimeFormat, userTimeZone)} diff --git a/apps/web/components/dialog/EditLocationDialog.tsx b/apps/web/components/dialog/EditLocationDialog.tsx index 252d5fcf92..4aee471929 100644 --- a/apps/web/components/dialog/EditLocationDialog.tsx +++ b/apps/web/components/dialog/EditLocationDialog.tsx @@ -382,7 +382,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { }} /> {selectedLocation && LocationOptions} - +

diff --git a/apps/web/components/eventtype/EventInstantTab.tsx b/apps/web/components/eventtype/EventInstantTab.tsx new file mode 100644 index 0000000000..8182a61b2b --- /dev/null +++ b/apps/web/components/eventtype/EventInstantTab.tsx @@ -0,0 +1,18 @@ +import type { EventTypeSetupProps } from "pages/event-types/[type]"; + +import getPaymentAppData from "@calcom/lib/getPaymentAppData"; + +import InstantEventController from "./InstantEventController"; + +export const EventInstantTab = ({ + eventType, + isTeamEvent, +}: Pick & { isTeamEvent: boolean }) => { + const paymentAppData = getPaymentAppData(eventType); + + const requirePayment = paymentAppData.price > 0; + + return ( + + ); +}; diff --git a/apps/web/components/eventtype/EventLimitsTab.tsx b/apps/web/components/eventtype/EventLimitsTab.tsx index 24502bb2b8..da68c260a9 100644 --- a/apps/web/components/eventtype/EventLimitsTab.tsx +++ b/apps/web/components/eventtype/EventLimitsTab.tsx @@ -298,6 +298,29 @@ export const EventLimitsTab = ({ eventType }: Pick + { + const isChecked = value; + return ( + { + formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false); + }} + switchContainerClassName={classNames( + "border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6", + isChecked && "rounded-b-none" + )} + /> + ); + }} + /> (eventType?.isInstantEvent ?? false); + const formMethods = useFormContext(); + + const { shouldLockDisableProps } = useLockedFieldsManager( + eventType, + t("locked_fields_admin_description"), + t("locked_fields_member_description") + ); + + const instantLocked = shouldLockDisableProps("isInstantEvent"); + + const isOrg = !!session.data?.user?.org?.id; + + if (session.status === "loading") return <>; + + return ( + +
+ {!isOrg || !isTeamEvent ? ( + {t("upgrade")}} + /> + ) : ( +
+ {paymentEnabled ? ( + + ) : ( + <> + + { + if (!e) { + formMethods.setValue("isInstantEvent", false); + setInstantEventState(false); + } else { + formMethods.setValue("isInstantEvent", true); + setInstantEventState(true); + } + }}> +
+ {instantEventState && ( +
+

{t("warning_payment_instant_meeting_event")}

+
+ )} +
+
+ + )} +
+ )} +
+
+ ); +} diff --git a/apps/web/components/getting-started/steps-views/ConnectCalendars.tsx b/apps/web/components/getting-started/steps-views/ConnectCalendars.tsx index 6c606178b5..abcf37ef6e 100644 --- a/apps/web/components/getting-started/steps-views/ConnectCalendars.tsx +++ b/apps/web/components/getting-started/steps-views/ConnectCalendars.tsx @@ -82,7 +82,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => { type="button" data-testid="save-calendar-button" className={classNames( - "text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm", + "text-inverted bg-inverted border-inverted mt-8 flex w-full flex-row justify-center rounded-md border p-2 text-center text-sm", disabledNextButton ? "cursor-not-allowed opacity-20" : "" )} onClick={() => nextStep()} diff --git a/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx b/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx index 1179460825..ad5072dcb4 100644 --- a/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx +++ b/apps/web/components/getting-started/steps-views/ConnectedVideoStep.tsx @@ -53,7 +53,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => { type="button" data-testid="save-video-button" className={classNames( - "text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm", + "text-inverted border-inverted bg-inverted mt-8 flex w-full flex-row justify-center rounded-md border p-2 text-center text-sm", !hasAnyInstalledVideoApps ? "cursor-not-allowed opacity-20" : "" )} disabled={!hasAnyInstalledVideoApps} diff --git a/apps/web/components/getting-started/steps-views/UserProfile.tsx b/apps/web/components/getting-started/steps-views/UserProfile.tsx index 79f4fd9076..54ff76fc1f 100644 --- a/apps/web/components/getting-started/steps-views/UserProfile.tsx +++ b/apps/web/components/getting-started/steps-views/UserProfile.tsx @@ -3,7 +3,6 @@ import type { FormEvent } from "react"; import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; -import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; @@ -11,6 +10,7 @@ import turndown from "@calcom/lib/turndownService"; import { trpc } from "@calcom/trpc/react"; import type { Ensure } from "@calcom/types/utils"; import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui"; +import { UserAvatar } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; type FormData = { @@ -108,9 +108,7 @@ const UserProfile = () => { return (
- {user && ( - - )} + {user && } { firstRender={firstRender} setFirstRender={setFirstRender} /> -

- {t("few_sentences_about_yourself")} -

+

{t("few_sentences_about_yourself")}

- ); diff --git a/apps/web/components/getting-started/steps-views/UserSettings.tsx b/apps/web/components/getting-started/steps-views/UserSettings.tsx index 7ab1d29f7f..cfb49a89f8 100644 --- a/apps/web/components/getting-started/steps-views/UserSettings.tsx +++ b/apps/web/components/getting-started/steps-views/UserSettings.tsx @@ -9,7 +9,7 @@ import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { trpc } from "@calcom/trpc/react"; -import { Button, TimezoneSelect } from "@calcom/ui"; +import { Button, TimezoneSelect, Input } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; @@ -76,7 +76,7 @@ const UserSettings = (props: IUserSettingsProps) => { - { type="text" autoComplete="off" autoCorrect="off" - className="border-default w-full rounded-md border text-sm" /> {errors.name && (

@@ -106,7 +105,7 @@ const UserSettings = (props: IUserSettingsProps) => { className="mt-2 w-full rounded-md text-sm" /> -

+

{t("current_time")} {dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()}

diff --git a/apps/web/components/team/screens/Team.tsx b/apps/web/components/team/screens/Team.tsx index aa3673cd42..c47bcae5e9 100644 --- a/apps/web/components/team/screens/Team.tsx +++ b/apps/web/components/team/screens/Team.tsx @@ -5,14 +5,13 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import { md } from "@calcom/lib/markdownIt"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import type { TeamWithMembers } from "@calcom/lib/server/queries/teams"; - -import { UserAvatar } from "@components/ui/avatar/UserAvatar"; +import { UserAvatar } from "@calcom/ui"; type TeamType = Omit, "inviteToken">; type MembersType = TeamType["members"]; type MemberType = Pick & { safeBio: string | null; - orgOrigin: string; + bookerUrl: string; }; const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => { @@ -26,7 +25,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n return ( + href={{ pathname: `${member.bookerUrl}/${member.username}`, query: queryParamsToForward }}>
diff --git a/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx b/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx index fb33a0f899..8f746a1726 100644 --- a/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx +++ b/apps/web/components/ui/UsernameAvailability/PremiumTextfield.tsx @@ -74,7 +74,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => { const debouncedApiCall = useMemo( () => debounce(async (username: string) => { - const { data } = await fetchUsername(username); + // TODO: Support orgSlug + const { data } = await fetchUsername(username, null); setMarkAsError(!data.available && !!currentUsername && username !== currentUsername); setIsInputUsernamePremium(data.premium); setUsernameIsAvailable(data.available); diff --git a/apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx b/apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx index 44421edbb9..1fe59a916b 100644 --- a/apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx +++ b/apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx @@ -44,7 +44,8 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial debounce(async (username) => { - const { data } = await fetchUsername(username); + // TODO: Support orgSlug + const { data } = await fetchUsername(username, null); setMarkAsError(!data.available); setUsernameIsAvailable(data.available); }, 150), @@ -80,7 +81,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial { return usernameIsAvailable && currentUsername !== inputUsernameValue ? ( -
+
)} diff --git a/apps/web/components/ui/avatar/UserAvatar.tsx b/apps/web/components/ui/avatar/UserAvatar.tsx deleted file mode 100644 index a542fc3d9f..0000000000 --- a/apps/web/components/ui/avatar/UserAvatar.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; -import type { User } from "@calcom/prisma/client"; -import { Avatar } from "@calcom/ui"; - -type UserAvatarProps = Omit, "alt" | "imageSrc"> & { - user: Pick; - /** - * Useful when allowing the user to upload their own avatar and showing the avatar before it's uploaded - */ - previewSrc?: string | null; -}; - -/** - * It is aware of the user's organization to correctly show the avatar from the correct URL - */ -export function UserAvatar(props: UserAvatarProps) { - const { user, previewSrc = getUserAvatarUrl(user), ...rest } = props; - return ; -} diff --git a/apps/web/components/ui/avatar/UserAvatarGroupWithOrg.tsx b/apps/web/components/ui/avatar/UserAvatarGroupWithOrg.tsx index 6b265395d8..7fcef0b6a8 100644 --- a/apps/web/components/ui/avatar/UserAvatarGroupWithOrg.tsx +++ b/apps/web/components/ui/avatar/UserAvatarGroupWithOrg.tsx @@ -1,21 +1,19 @@ -import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; -import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL } from "@calcom/lib/constants"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import type { Team, User } from "@calcom/prisma/client"; import { AvatarGroup } from "@calcom/ui"; type UserAvatarProps = Omit, "items"> & { - users: Pick[]; + users: (Pick & { bookerUrl: string })[]; organization: Pick; }; export function UserAvatarGroupWithOrg(props: UserAvatarProps) { const { users, organization, ...rest } = props; - const orgBranding = useOrgBranding(); - const baseUrl = `${orgBranding?.fullDomain ?? CAL_URL}`; const items = [ { - href: baseUrl, + href: getBookerBaseUrlSync(organization.slug), image: `${WEBAPP_URL}/team/${organization.slug}/avatar.png`, alt: organization.name || undefined, title: organization.name, @@ -23,13 +21,12 @@ export function UserAvatarGroupWithOrg(props: UserAvatarProps) { ].concat( users.map((user) => { return { - href: `${baseUrl}/${user.username}/?redirect=false`, + href: `${user.bookerUrl}/${user.username}?redirect=false`, image: getUserAvatarUrl(user), alt: user.name || undefined, title: user.name || user.username || "", }; }) ); - users.unshift(); return ; } diff --git a/apps/web/components/ui/form/CheckboxField.tsx b/apps/web/components/ui/form/CheckboxField.tsx index 8298fbb5b5..a189b6d413 100644 --- a/apps/web/components/ui/form/CheckboxField.tsx +++ b/apps/web/components/ui/form/CheckboxField.tsx @@ -49,7 +49,7 @@ const CheckboxField = forwardRef( {...rest} ref={ref} type="checkbox" - className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded" + className="text-emphasis focus:ring-emphasis dark:text-muted border-default bg-default h-4 w-4 rounded" />
{description} diff --git a/apps/web/lib/app-providers-app-dir.tsx b/apps/web/lib/app-providers-app-dir.tsx index 2b2d57d2ea..0b6c8f009c 100644 --- a/apps/web/lib/app-providers-app-dir.tsx +++ b/apps/web/lib/app-providers-app-dir.tsx @@ -97,13 +97,9 @@ const CustomI18nextProvider = (props: { children: React.ReactElement; i18n?: SSR const clientViewerI18n = useViewerI18n(locale); const i18n = clientViewerI18n.data?.i18n ?? props.i18n; - if (!i18n || !i18n._nextI18Next) { - return null; - } - return ( // @ts-expect-error AppWithTranslationHoc expects AppProps - + {props.children} ); diff --git a/apps/web/lib/buildLegacyCtx.tsx b/apps/web/lib/buildLegacyCtx.tsx new file mode 100644 index 0000000000..2dfbf3ff2f --- /dev/null +++ b/apps/web/lib/buildLegacyCtx.tsx @@ -0,0 +1,23 @@ +import { type Params } from "app/_types"; +import { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; +import { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; + +// returns query object same as ctx.query but for app dir +export const getQuery = (url: string, params: Params) => { + if (!url.length) { + return params; + } + + const { searchParams } = new URL(url); + const searchParamsObj = Object.fromEntries(searchParams.entries()); + + return { ...searchParamsObj, ...params }; +}; + +export const buildLegacyCtx = (headers: ReadonlyHeaders, cookies: ReadonlyRequestCookies, params: Params) => { + return { + query: getQuery(headers.get("x-url") ?? "", params), + params, + req: { headers, cookies }, + }; +}; diff --git a/apps/web/lib/orgMigration.test.ts b/apps/web/lib/orgMigration.test.ts new file mode 100644 index 0000000000..18b924dbc2 --- /dev/null +++ b/apps/web/lib/orgMigration.test.ts @@ -0,0 +1,1512 @@ +import prismock from "../../../tests/libs/__mocks__/prisma"; + +import { describe, expect, it } from "vitest"; +import type { z } from "zod"; + +import { WEBSITE_URL } from "@calcom/lib/constants"; +import type { MembershipRole, Prisma } from "@calcom/prisma/client"; +import { RedirectType } from "@calcom/prisma/enums"; +import type { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { moveTeamToOrg, moveUserToOrg, removeTeamFromOrg, removeUserFromOrg } from "./orgMigration"; + +const WEBSITE_PROTOCOL = new URL(WEBSITE_URL).protocol; +describe("orgMigration", () => { + describe("moveUserToOrg", () => { + describe("when user email does not match orgAutoAcceptEmail", () => { + it(`should migrate a user to become a part of an organization with ADMIN role + - username in the organization should be automatically derived from email when it isn't passed`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "ADMIN", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + const team1 = await createTeamOutsideOrg({ + name: "Team-1", + slug: "team-1", + }); + + // Make userToMigrate part of team-1 + await addMemberShipOfUserWithTeam({ + teamId: team1.id, + userId: dbUserToMigrate.id, + role: "MEMBER", + accepted: true, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + await expectTeamToBeNotPartOfAnyOrganization({ + teamId: team1.id, + }); + + expectUserRedirectToBeEnabled({ + from: { + username: data.userToMigrate.username, + }, + to: data.userToMigrate.expectedUsernameInOrg, + orgSlug: data.targetOrg.slug, + }); + }); + + it("should migrate a user to become a part of an organization(which has slug set) with MEMBER role", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + id: 1, + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + slug: data.targetOrg.slug, + name: data.targetOrg.name, + }); + + await moveUserToOrg({ + user: { + id: dbOrg.id, + }, + targetOrg: { + id: data.targetOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: data.targetOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + }); + + it(`should migrate a user to become a part of an organization(which has no slug but requestedSlug) with MEMBER role + 1. Should set the slug as requestedSlug for the organization(so that the redirect doesnt take to an unpublished organization page)`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + requestedSlug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: "user-1@example.com", + username: "user-1", + }); + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + metadata: { + requestedSlug: data.targetOrg.requestedSlug, + }, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + const organization = await prismock.team.findUnique({ + where: { + id: dbOrg.id, + }, + }); + + expect(organization?.slug).toBe(data.targetOrg.requestedSlug); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + }); + + it("should migrate a user along with its teams(without other members)", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + teams: [ + { + name: "Team 100", + slug: "team-100", + }, + { + name: "Team 101", + slug: "team-101", + }, + ], + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + userPartOfTeam1: { + username: "user-2", + email: "user-2@example.com", + }, + userPartOfTeam2: { + username: "user-3", + email: "user-3@example.com", + }, + }; + + // Create user to migrate + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + // Create another user that would be part of team-1 along with userToMigrate + const userPartOfTeam1 = await createUserOutsideOrg({ + email: data.userPartOfTeam1.email, + username: data.userPartOfTeam1.username, + }); + + // Create another user that would be part of team-2 along with userToMigrate + const userPartOfTeam2 = await createUserOutsideOrg({ + email: data.userPartOfTeam2.email, + username: data.userPartOfTeam2.username, + }); + + const team1 = await createTeamOutsideOrg({ + name: data.userToMigrate.teams[0].name, + slug: data.userToMigrate.teams[0].slug, + }); + + const team2 = await createTeamOutsideOrg({ + name: data.userToMigrate.teams[1].name, + slug: data.userToMigrate.teams[1].slug, + }); + + // Make userToMigrate part of team-1 + await addMemberShipOfUserWithTeam({ + teamId: team1.id, + userId: dbUserToMigrate.id, + role: "MEMBER", + accepted: true, + }); + + // Make userToMigrate part of team-2 + await addMemberShipOfUserWithTeam({ + teamId: team2.id, + userId: dbUserToMigrate.id, + role: "MEMBER", + accepted: true, + }); + + // Make userPartofTeam1 part of team-1 + await addMemberShipOfUserWithTeam({ + teamId: team1.id, + userId: userPartOfTeam1.id, + role: "MEMBER", + accepted: true, + }); + + // Make userPartofTeam2 part of team2 + await addMemberShipOfUserWithTeam({ + teamId: team2.id, + userId: userPartOfTeam2.id, + role: "MEMBER", + accepted: true, + }); + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: true, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + await expectTeamToBeAPartOfOrg({ + teamId: team1.id, + orgId: dbOrg.id, + teamSlugInOrg: team1.slug, + }); + + await expectTeamToBeAPartOfOrg({ + teamId: team2.id, + orgId: dbOrg.id, + teamSlugInOrg: team2.slug, + }); + + await expectUserToBeNotAPartOfTheOrg({ + userId: userPartOfTeam1.id, + orgId: dbOrg.id, + username: data.userPartOfTeam1.username, + }); + + await expectUserToBeNotAPartOfTheOrg({ + userId: userPartOfTeam2.id, + orgId: dbOrg.id, + username: data.userPartOfTeam2.username, + }); + }); + + it(`should migrate a user to become a part of an organization + - username in the organization should same as provided to moveUserToOrg`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + usernameInOrgThatWeWant: "user-1-in-org", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "ADMIN", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + username: data.userToMigrate.usernameInOrgThatWeWant, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.usernameInOrgThatWeWant, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + expectUserRedirectToBeEnabled({ + from: { + username: data.userToMigrate.username, + }, + to: data.userToMigrate.usernameInOrgThatWeWant, + orgSlug: data.targetOrg.slug, + }); + }); + + it(`should be able to re-migrate an already migrated user fixing things as we do it + - Redirect should correctly determine the nonOrgUsername so that on repeat migrations, the original user link doesn't break`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + usernameWeWantInOrg: "user-1-in-org", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + const dbUserToMigrate = await createUserInsideTheOrg( + { + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }, + dbOrg.id + ); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + username: data.userToMigrate.usernameWeWantInOrg, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.usernameWeWantInOrg, + // membership role should be updated + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + username: data.userToMigrate.usernameWeWantInOrg, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.usernameWeWantInOrg, + // membership role should be updated + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + expectUserRedirectToBeEnabled({ + from: { + username: data.userToMigrate.username, + }, + to: data.userToMigrate.usernameWeWantInOrg, + orgSlug: data.targetOrg.slug, + }); + + console.log(await prismock.tempOrgRedirect.findMany({})); + }); + + describe("Failures handling", () => { + it("migration should fail if the username already exists in the organization", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + await createUserInsideTheOrg( + { + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }, + dbOrg.id + ); + + // User with same username exists outside the org as well as inside the org + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + expect(() => { + return moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + username: data.userToMigrate.username, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + }).rejects.toThrowError("already exists"); + }); + + it("should fail the migration if the target org doesn't exist", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + await createUserInsideTheOrg( + { + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }, + dbOrg.id + ); + + // User with same username exists outside the org as well as inside the org + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + expect(() => { + return moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + // Non existent teamId + id: 1001, + username: data.userToMigrate.username, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + }).rejects.toThrowError(/Org .* not found/); + }); + + it("should fail the migration if the target teamId is not an organization", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + // Create a team instead of an organization + const dbOrgWhichIsActuallyATeam = await createTeamOutsideOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + expect(() => { + return moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + // Non existent teamId + id: dbOrgWhichIsActuallyATeam.id, + username: data.userToMigrate.username, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + }).rejects.toThrowError(/is not an Org/); + }); + + it("should fail the migration if the user is part of any other organization", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@example.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1-example", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + const dbOrgOther = await createOrg({ + name: data.targetOrg.name, + slug: data.targetOrg.slug, + }); + + const dbUserToMigrate = await createUserInsideTheOrg( + { + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }, + dbOrgOther.id + ); + + expect(() => { + return moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + // Non existent teamId + id: dbOrg.id, + username: data.userToMigrate.username, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + }).rejects.toThrowError(/already a part of different organization/); + }); + }); + }); + + describe("when user email matches orgAutoAcceptEmail", () => { + const orgMetadata = { + orgAutoAcceptEmail: "org1.com", + } as const; + + it(`should migrate a user to become a part of an organization with ADMIN role`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@org1.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "ADMIN", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + slug: data.targetOrg.slug, + name: data.targetOrg.name, + metadata: { + ...orgMetadata, + }, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + + expectUserRedirectToBeEnabled({ + from: { + username: data.userToMigrate.username, + }, + to: data.userToMigrate.expectedUsernameInOrg, + orgSlug: data.targetOrg.slug, + }); + }); + + it("should migrate a user to become a part of an organization(which has slug set) with MEMBER role", async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@org1.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + slug: data.targetOrg.slug, + name: data.targetOrg.name, + metadata: { + ...orgMetadata, + }, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + }); + + it(`should migrate a user to become a part of an organization(which has no slug but requestedSlug) with MEMBER role + 1. Should set the slug as requestedSlug for the organization(so that the redirect doesnt take to an unpublished organization page)`, async () => { + const data = { + userToMigrate: { + username: "user-1", + email: "user-1@org1.com", + // Because example.com isn't the orgAutoAcceptEmail + expectedUsernameInOrg: "user-1", + }, + targetOrg: { + name: "Org 1", + requestedSlug: "org1", + }, + membershipWeWant: { + role: "MEMBER", + } as const, + }; + + const dbUserToMigrate = await createUserOutsideOrg({ + email: data.userToMigrate.email, + username: data.userToMigrate.username, + }); + + const dbOrg = await createOrg({ + name: data.targetOrg.name, + metadata: { + requestedSlug: data.targetOrg.requestedSlug, + ...orgMetadata, + }, + }); + + await moveUserToOrg({ + user: { + id: dbUserToMigrate.id, + }, + targetOrg: { + id: dbOrg.id, + membership: { + role: data.membershipWeWant.role, + }, + }, + shouldMoveTeams: false, + }); + + const organization = await prismock.team.findUnique({ + where: { + id: dbOrg.id, + }, + }); + + expect(organization?.slug).toBe(data.targetOrg.requestedSlug); + + await expectUserToBeAPartOfOrg({ + userId: dbUserToMigrate.id, + orgId: dbOrg.id, + usernameInOrg: data.userToMigrate.expectedUsernameInOrg, + expectedMembership: { role: data.membershipWeWant.role, accepted: true }, + }); + }); + }); + }); + + describe("moveTeamToOrg", () => { + it(`should migrate a team to become a part of an organization`, async () => { + const data = { + teamToMigrate: { + id: 1, + name: "Team 1", + slug: "team1", + newSlug: "team1-new-slug", + }, + targetOrg: { + id: 2, + name: "Org 1", + slug: "org1", + }, + }; + + await prismock.team.create({ + data: { + id: data.teamToMigrate.id, + slug: data.teamToMigrate.slug, + name: data.teamToMigrate.name, + }, + }); + + await prismock.team.create({ + data: { + id: data.targetOrg.id, + slug: data.targetOrg.slug, + name: data.targetOrg.name, + metadata: { + isOrganization: true, + }, + }, + }); + + await moveTeamToOrg({ + teamId: data.teamToMigrate.id, + targetOrg: { + id: data.targetOrg.id, + teamSlug: data.teamToMigrate.newSlug, + }, + }); + + await expectTeamToBeAPartOfOrg({ + teamId: data.teamToMigrate.id, + orgId: data.targetOrg.id, + teamSlugInOrg: data.teamToMigrate.newSlug, + }); + + expectTeamRedirectToBeEnabled({ + from: { + teamSlug: data.teamToMigrate.slug, + }, + to: data.teamToMigrate.newSlug, + orgSlug: data.targetOrg.slug, + }); + }); + it.todo("should migrate a team with members"); + it.todo("Try migrating an already migrated team"); + }); + + describe("removeUserFromOrg", () => { + it(`should remove a user from an organization but he should remain in team`, async () => { + const data = { + userToUnmigrate: { + username: "user1-in-org1", + email: "user-1@example.com", + usernameBeforeMovingToOrg: "user1", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + membershipWeWant: { + role: "ADMIN", + } as const, + }; + + const dbOrg = await createOrg({ + slug: data.targetOrg.slug, + name: data.targetOrg.name, + }); + + const dbTeamOutsideOrg = await createTeamOutsideOrg({ + slug: "team-1", + name: "Team 1", + }); + + const dbUser = await createUserInsideTheOrg( + { + email: data.userToUnmigrate.email, + username: data.userToUnmigrate.username, + metadata: { + migratedToOrgFrom: { + username: data.userToUnmigrate.usernameBeforeMovingToOrg, + }, + }, + }, + dbOrg.id + ); + + await addMemberShipOfUserWithOrg({ + userId: dbUser.id, + teamId: dbTeamOutsideOrg.id, + role: "MEMBER", + accepted: true, + }); + + await addMemberShipOfUserWithTeam({ + userId: dbUser.id, + teamId: dbOrg.id, + role: data.membershipWeWant.role, + accepted: true, + }); + + const userToUnmigrate = await prismock.user.findUnique({ + where: { + id: dbUser.id, + }, + include: { + organization: true, + }, + }); + + if (!userToUnmigrate?.organizationId || !userToUnmigrate.organization) { + throw new Error( + `Couldn't setup user to unmigrate properly userToUnMigrate: ${{ + organizationId: userToUnmigrate?.organizationId, + organization: !!userToUnmigrate?.organization, + }}` + ); + } + + await removeUserFromOrg({ + userId: dbUser.id, + targetOrgId: dbOrg.id, + }); + + await expectUserToBeNotAPartOfTheOrg({ + userId: dbUser.id, + orgId: dbOrg.id, + username: data.userToUnmigrate.usernameBeforeMovingToOrg, + }); + + await expectUserToBeAPartOfTeam({ + userId: dbUser.id, + teamId: dbTeamOutsideOrg.id, + expectedMembership: { + role: "MEMBER", + accepted: true, + }, + }); + + expectUserRedirectToBeNotEnabled({ + from: { + username: data.userToUnmigrate.username, + }, + }); + }); + }); + + describe("removeTeamFromOrg", () => { + it(`should remove a team from an organization`, async () => { + const data = { + teamToUnmigrate: { + name: "Team 1", + slug: "team1", + }, + targetOrg: { + name: "Org 1", + slug: "org1", + }, + }; + + const targetOrg = await prismock.team.create({ + data: { + slug: data.targetOrg.slug, + name: data.targetOrg.name, + metadata: { + isOrganization: true, + }, + }, + }); + + const { id: teamToUnMigrateId } = await prismock.team.create({ + data: { + slug: data.teamToUnmigrate.slug, + name: data.teamToUnmigrate.name, + parent: { + connect: { + id: targetOrg.id, + }, + }, + }, + }); + + const teamToUnmigrate = await prismock.team.findUnique({ + where: { + id: teamToUnMigrateId, + }, + include: { + parent: true, + }, + }); + + if (!teamToUnmigrate?.parent || !teamToUnmigrate.parentId) { + throw new Error(`Couldn't setup team to unmigrate properly ID:${teamToUnMigrateId}`); + } + + await removeTeamFromOrg({ + teamId: teamToUnMigrateId, + targetOrgId: targetOrg.id, + }); + + await expectTeamToBeNotPartOfAnyOrganization({ + teamId: teamToUnMigrateId, + }); + + expectTeamRedirectToBeNotEnabled({ + from: { + teamSlug: data.teamToUnmigrate.slug, + }, + to: data.teamToUnmigrate.slug, + orgSlug: data.targetOrg.slug, + }); + }); + }); +}); + +async function expectUserToBeAPartOfOrg({ + userId, + orgId, + expectedMembership, + usernameInOrg, +}: { + userId: number; + orgId: number; + usernameInOrg: string; + expectedMembership: { + role: MembershipRole; + accepted: boolean; + }; +}) { + const migratedUser = await prismock.user.findUnique({ + where: { + id: userId, + }, + include: { + teams: true, + }, + }); + if (!migratedUser) { + throw new Error(`User with id ${userId} does not exist`); + } + + expect(migratedUser.username).toBe(usernameInOrg); + expect(migratedUser.organizationId).toBe(orgId); + + const membership = migratedUser.teams.find( + (membership) => membership.teamId === orgId && membership.userId === userId + ); + + expect(membership).not.toBeNull(); + if (!membership) { + throw new Error(`User with id ${userId} is not a part of org with id ${orgId}`); + } + expect(membership.role).toBe(expectedMembership.role); + expect(membership.accepted).toBe(expectedMembership.accepted); +} + +async function expectUserToBeAPartOfTeam({ + userId, + teamId, + expectedMembership, +}: { + userId: number; + teamId: number; + expectedMembership: { + role: MembershipRole; + accepted: boolean; + }; +}) { + const user = await prismock.user.findUnique({ + where: { + id: userId, + }, + include: { + teams: true, + }, + }); + if (!user) { + throw new Error(`User with id ${userId} does not exist`); + } + + const membership = user.teams.find( + (membership) => membership.teamId === teamId && membership.userId === userId + ); + + expect(membership).not.toBeNull(); + if (!membership) { + throw new Error(`User with id ${userId} is not a part of team with id ${teamId}`); + } + expect(membership.role).toBe(expectedMembership.role); + expect(membership.accepted).toBe(expectedMembership.accepted); +} + +async function expectUserToBeNotAPartOfTheOrg({ + userId, + orgId, + username, +}: { + userId: number; + orgId: number; + username: string; +}) { + const user = await prismock.user.findUnique({ + where: { + id: userId, + }, + include: { + teams: true, + }, + }); + if (!user) { + throw new Error(`User with id ${userId} does not exist`); + } + + expect(user.username).toBe(username); + expect(user.organizationId).toBe(null); + + const membership = user.teams.find( + (membership) => membership.teamId === orgId && membership.userId === userId + ); + + expect(membership).toBeUndefined(); +} + +async function expectTeamToBeAPartOfOrg({ + teamId, + orgId, + teamSlugInOrg, +}: { + teamId: number; + orgId: number; + teamSlugInOrg: string | null; +}) { + const migratedTeam = await prismock.team.findUnique({ + where: { + id: teamId, + }, + }); + if (!migratedTeam) { + throw new Error(`Team with id ${teamId} does not exist`); + } + + if (!teamSlugInOrg) { + throw new Error(`teamSlugInOrg should be defined`); + } + expect(migratedTeam.parentId).toBe(orgId); + expect(migratedTeam.slug).toBe(teamSlugInOrg); +} + +async function expectTeamToBeNotPartOfAnyOrganization({ teamId }: { teamId: number }) { + const team = await prismock.team.findUnique({ + where: { + id: teamId, + }, + }); + if (!team) { + throw new Error(`Team with id ${teamId} does not exist`); + } + + expect(team.parentId).toBe(null); +} + +async function expectUserRedirectToBeEnabled({ + from, + to, + orgSlug, +}: { + from: { username: string } | { teamSlug: string }; + to: string; + orgSlug: string; +}) { + expectRedirectToBeEnabled({ + from, + to, + orgSlug, + redirectType: RedirectType.User, + }); +} + +async function expectTeamRedirectToBeEnabled({ + from, + to, + orgSlug, +}: { + from: { username: string } | { teamSlug: string }; + to: string; + orgSlug: string; +}) { + expectRedirectToBeEnabled({ + from, + to, + orgSlug, + redirectType: RedirectType.Team, + }); +} + +async function expectUserRedirectToBeNotEnabled({ + from, +}: { + from: { username: string } | { teamSlug: string }; +}) { + expectRedirectToBeNotEnabled({ + from, + }); +} + +async function expectTeamRedirectToBeNotEnabled({ + from, +}: { + from: { username: string } | { teamSlug: string }; + to: string; + orgSlug: string; +}) { + expectRedirectToBeNotEnabled({ + from, + }); +} + +async function expectRedirectToBeEnabled({ + from, + to, + orgSlug, + redirectType, +}: { + from: { username: string } | { teamSlug: string }; + to: string; + orgSlug: string; + redirectType: RedirectType; +}) { + let tempOrgRedirectWhere = null; + let tempOrgRedirectThatShouldNotExistWhere = null; + if ("username" in from) { + tempOrgRedirectWhere = { + from_type_fromOrgId: { + from: from.username, + type: RedirectType.User, + fromOrgId: 0, + }, + }; + + // Normally with user migration `from.username != to` + if (from.username !== to) { + // There must not be a redirect setup from=To to something else as that would cause double redirection + tempOrgRedirectThatShouldNotExistWhere = { + from_type_fromOrgId: { + from: to, + type: RedirectType.User, + fromOrgId: 0, + }, + }; + } + } else if ("teamSlug" in from) { + tempOrgRedirectWhere = { + from_type_fromOrgId: { + from: from.teamSlug, + type: RedirectType.Team, + fromOrgId: 0, + }, + }; + + if (from.teamSlug !== to) { + // There must not be a redirect setup from=To to something else as that would cause double redirection + tempOrgRedirectThatShouldNotExistWhere = { + from_type_fromOrgId: { + from: to, + type: RedirectType.Team, + fromOrgId: 0, + }, + }; + } + } else { + throw new Error("Atleast one of userId or teamId should be present in from"); + } + const redirect = await prismock.tempOrgRedirect.findUnique({ + where: tempOrgRedirectWhere, + }); + + if (tempOrgRedirectThatShouldNotExistWhere) { + const redirectThatShouldntBeThere = await prismock.tempOrgRedirect.findUnique({ + where: tempOrgRedirectThatShouldNotExistWhere, + }); + expect(redirectThatShouldntBeThere).toBeNull(); + } + + expect(redirect).not.toBeNull(); + expect(redirect?.toUrl).toBe(`${WEBSITE_PROTOCOL}//${orgSlug}.cal.local:3000/${to}`); + if (!redirect) { + throw new Error(`Redirect doesn't exist for ${JSON.stringify(tempOrgRedirectWhere)}`); + } + expect(redirect.type).toBe(redirectType); +} + +async function expectRedirectToBeNotEnabled({ from }: { from: { username: string } | { teamSlug: string } }) { + let tempOrgRedirectWhere = null; + if ("username" in from) { + tempOrgRedirectWhere = { + from_type_fromOrgId: { + from: from.username, + type: RedirectType.User, + fromOrgId: 0, + }, + }; + } else if ("teamSlug" in from) { + tempOrgRedirectWhere = { + from_type_fromOrgId: { + from: from.teamSlug, + type: RedirectType.Team, + fromOrgId: 0, + }, + }; + } else { + throw new Error("Atleast one of userId or teamId should be present in from"); + } + const redirect = await prismock.tempOrgRedirect.findUnique({ + where: tempOrgRedirectWhere, + }); + expect(redirect).toBeNull(); +} + +async function createOrg( + data: Omit & { + metadata?: z.infer; + } +) { + return await prismock.team.create({ + data: { + ...data, + metadata: { + ...(data.metadata || {}), + isOrganization: true, + }, + }, + }); +} + +async function createTeamOutsideOrg( + data: Omit & { + metadata?: z.infer; + } +) { + return await prismock.team.create({ + data: { + ...data, + parentId: null, + metadata: { + ...(data.metadata || {}), + isOrganization: false, + }, + }, + }); +} + +async function createUserOutsideOrg(data: Omit) { + return await prismock.user.create({ + data: { + ...data, + organizationId: null, + }, + }); +} + +async function createUserInsideTheOrg( + data: Omit, + orgId: number +) { + const org = await prismock.team.findUnique({ + where: { + id: orgId, + }, + }); + if (!org) { + throw new Error(`Org with id ${orgId} does not exist`); + } + return await prismock.user.create({ + data: { + ...data, + organization: { + connect: { + id: orgId, + }, + }, + }, + }); +} + +async function addMemberShipOfUserWithTeam({ + teamId, + userId, + role, + accepted, +}: { + teamId: number; + userId: number; + role: MembershipRole; + accepted: boolean; +}) { + await prismock.membership.create({ + data: { + role, + accepted, + team: { + connect: { + id: teamId, + }, + }, + user: { + connect: { + id: userId, + }, + }, + }, + }); + + const membership = await prismock.membership.findUnique({ + where: { + userId_teamId: { + teamId, + userId, + }, + }, + }); + if (!membership) { + throw new Error(`Membership between teamId ${teamId} and userId ${userId} couldn't be created`); + } +} + +const addMemberShipOfUserWithOrg = addMemberShipOfUserWithTeam; diff --git a/apps/web/lib/orgMigration.ts b/apps/web/lib/orgMigration.ts new file mode 100644 index 0000000000..5000a6d202 --- /dev/null +++ b/apps/web/lib/orgMigration.ts @@ -0,0 +1,855 @@ +import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail"; +import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import prisma from "@calcom/prisma"; +import type { Team, User } from "@calcom/prisma/client"; +import { RedirectType } from "@calcom/prisma/client"; +import { Prisma } from "@calcom/prisma/client"; +import type { MembershipRole } from "@calcom/prisma/enums"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +const log = logger.getSubLogger({ prefix: ["orgMigration"] }); + +type UserMetadata = { + migratedToOrgFrom?: { + username: string; + reverted: boolean; + revertTime: string; + lastMigrationTime: string; + }; +}; + +/** + * Make sure that the migration is idempotent + */ +export async function moveUserToOrg({ + user: { id: userId, userName: userName }, + targetOrg: { + id: targetOrgId, + username: targetOrgUsername, + membership: { role: targetOrgRole, accepted: targetOrgMembershipAccepted = true }, + }, + shouldMoveTeams, +}: { + user: { id?: number; userName?: string }; + targetOrg: { + id: number; + username?: string; + membership: { role: MembershipRole; accepted?: boolean }; + }; + shouldMoveTeams: boolean; +}) { + assertUserIdOrUserName(userId, userName); + const team = await getTeamOrThrowError(targetOrgId); + + const teamMetadata = teamMetadataSchema.parse(team?.metadata); + + if (!teamMetadata?.isOrganization) { + throw new Error(`Team with ID:${targetOrgId} is not an Org`); + } + + const targetOrganization = { + ...team, + metadata: teamMetadata, + }; + const userToMoveToOrg = await getUniqueUserThatDoesntBelongToOrg(userName, userId, targetOrgId); + assertUserPartOfOtherOrg(userToMoveToOrg, userName, userId, targetOrgId); + + if (!targetOrgUsername) { + targetOrgUsername = getOrgUsernameFromEmail( + userToMoveToOrg.email, + targetOrganization.metadata.orgAutoAcceptEmail || "" + ); + } + + const userWithSameUsernameInOrg = await prisma.user.findFirst({ + where: { + username: targetOrgUsername, + organizationId: targetOrgId, + }, + }); + + log.debug({ + userWithSameUsernameInOrg, + targetOrgUsername, + targetOrgId, + userId, + }); + + if (userWithSameUsernameInOrg && userWithSameUsernameInOrg.id !== userId) { + throw new HttpError({ + statusCode: 400, + message: `Username ${targetOrgUsername} already exists for orgId: ${targetOrgId} for some other user`, + }); + } + + assertUserPartOfOrgAndRemigrationAllowed(userToMoveToOrg, targetOrgId, targetOrgUsername, userId); + + const orgMetadata = teamMetadata; + + const userToMoveToOrgMetadata = (userToMoveToOrg.metadata || {}) as UserMetadata; + + const nonOrgUserName = + (userToMoveToOrgMetadata.migratedToOrgFrom?.username as string) || userToMoveToOrg.username; + if (!nonOrgUserName) { + throw new HttpError({ + statusCode: 400, + message: `User with id: ${userId} doesn't have a non-org username`, + }); + } + + await dbMoveUserToOrg({ userToMoveToOrg, targetOrgId, targetOrgUsername, nonOrgUserName }); + + let teamsToBeMovedToOrg; + if (shouldMoveTeams) { + teamsToBeMovedToOrg = await moveTeamsWithoutMembersToOrg({ targetOrgId, userToMoveToOrg }); + } + + await updateMembership({ targetOrgId, userToMoveToOrg, targetOrgRole, targetOrgMembershipAccepted }); + + await addRedirect({ + nonOrgUserName, + teamsToBeMovedToOrg: teamsToBeMovedToOrg || [], + organization: targetOrganization, + targetOrgUsername, + }); + + await setOrgSlugIfNotSet(targetOrganization, orgMetadata, targetOrgId); + + log.debug(`orgId:${targetOrgId} attached to userId:${userId}`); +} + +/** + * Make sure that the migration is idempotent + */ +export async function removeUserFromOrg({ targetOrgId, userId }: { targetOrgId: number; userId: number }) { + const userToRemoveFromOrg = await prisma.user.findUnique({ + where: { + id: userId, + }, + }); + + if (!userToRemoveFromOrg) { + throw new HttpError({ + statusCode: 400, + message: `User with id: ${userId} not found`, + }); + } + + if (userToRemoveFromOrg.organizationId !== targetOrgId) { + throw new HttpError({ + statusCode: 400, + message: `User with id: ${userId} is not part of orgId: ${targetOrgId}`, + }); + } + + const userToRemoveFromOrgMetadata = (userToRemoveFromOrg.metadata || {}) as { + migratedToOrgFrom?: { + username: string; + reverted: boolean; + revertTime: string; + lastMigrationTime: string; + }; + }; + + if (!userToRemoveFromOrgMetadata.migratedToOrgFrom) { + throw new HttpError({ + statusCode: 400, + message: `User with id: ${userId} wasn't migrated. So, there is nothing to revert`, + }); + } + + const nonOrgUserName = userToRemoveFromOrgMetadata.migratedToOrgFrom.username as string; + if (!nonOrgUserName) { + throw new HttpError({ + statusCode: 500, + message: `User with id: ${userId} doesn't have a non-org username`, + }); + } + + const teamsToBeRemovedFromOrg = await removeTeamsWithoutItsMemberFromOrg({ userToRemoveFromOrg }); + await dbRemoveUserFromOrg({ userToRemoveFromOrg, nonOrgUserName }); + + await removeUserAlongWithItsTeamsRedirects({ nonOrgUserName, teamsToBeRemovedFromOrg }); + await removeMembership({ targetOrgId, userToRemoveFromOrg }); + + log.debug(`orgId:${targetOrgId} attached to userId:${userId}`); +} + +/** + * Make sure that the migration is idempotent + */ +export async function moveTeamToOrg({ + targetOrg, + teamId, + moveMembers, +}: { + targetOrg: { id: number; teamSlug: string }; + teamId: number; + moveMembers?: boolean; +}) { + const possibleOrg = await getTeamOrThrowError(targetOrg.id); + const { oldTeamSlug, updatedTeam } = await dbMoveTeamToOrg({ teamId, targetOrg }); + + const teamMetadata = teamMetadataSchema.parse(possibleOrg?.metadata); + + if (!teamMetadata?.isOrganization) { + throw new Error(`${targetOrg.id} is not an Org`); + } + + const targetOrganization = possibleOrg; + const orgMetadata = teamMetadata; + await addTeamRedirect({ + oldTeamSlug, + teamSlug: updatedTeam.slug, + orgSlug: targetOrganization.slug || orgMetadata.requestedSlug || null, + }); + await setOrgSlugIfNotSet({ slug: targetOrganization.slug }, orgMetadata, targetOrg.id); + if (moveMembers) { + for (const membership of updatedTeam.members) { + await moveUserToOrg({ + user: { + id: membership.userId, + }, + targetOrg: { + id: targetOrg.id, + membership: { + role: membership.role, + accepted: membership.accepted, + }, + }, + shouldMoveTeams: false, + }); + } + } + log.debug(`Successfully moved team ${teamId} to org ${targetOrg.id}`); +} + +/** + * Make sure that the migration is idempotent + */ +export async function removeTeamFromOrg({ targetOrgId, teamId }: { targetOrgId: number; teamId: number }) { + const removedTeam = await dbRemoveTeamFromOrg({ teamId }); + + await removeTeamRedirect(removedTeam.slug); + + log.debug(`Successfully removed team ${teamId} from org ${targetOrgId}`); +} + +async function dbMoveTeamToOrg({ + teamId, + targetOrg, +}: { + teamId: number; + targetOrg: { + id: number; + teamSlug: string; + }; +}) { + const team = await prisma.team.findUnique({ + where: { + id: teamId, + }, + include: { + members: true, + }, + }); + + if (!team) { + throw new HttpError({ + statusCode: 400, + message: `Team with id: ${teamId} not found`, + }); + } + + const teamMetadata = teamMetadataSchema.parse(team?.metadata); + const oldTeamSlug = teamMetadata?.migratedToOrgFrom?.teamSlug || team.slug; + + const updatedTeam = await prisma.team.update({ + where: { + id: teamId, + }, + data: { + slug: targetOrg.teamSlug, + parentId: targetOrg.id, + metadata: { + ...teamMetadata, + migratedToOrgFrom: { + teamSlug: team.slug, + lastMigrationTime: new Date().toISOString(), + }, + }, + }, + include: { + members: true, + }, + }); + + return { oldTeamSlug, updatedTeam }; +} + +async function getUniqueUserThatDoesntBelongToOrg( + userName: string | undefined, + userId: number | undefined, + excludeOrgId: number +) { + log.debug("getUniqueUserThatDoesntBelongToOrg", { userName, userId, excludeOrgId }); + if (userName) { + const matchingUsers = await prisma.user.findMany({ + where: { + username: userName, + }, + }); + const foundUsers = matchingUsers.filter( + (user) => user.organizationId === excludeOrgId || user.organizationId === null + ); + if (foundUsers.length > 1) { + throw new Error(`More than one user found with username: ${userName}`); + } + return foundUsers[0]; + } else { + return await prisma.user.findUnique({ + where: { + id: userId, + }, + }); + } +} + +async function setOrgSlugIfNotSet( + targetOrganization: { + slug: string | null; + }, + orgMetadata: { + requestedSlug?: string | undefined; + }, + targetOrgId: number +) { + if (targetOrganization.slug) { + return; + } + if (!orgMetadata.requestedSlug) { + throw new HttpError({ + statusCode: 400, + message: `Org with id: ${targetOrgId} doesn't have a slug. Tried using requestedSlug but that's also not present. So, all migration done but failed to set the Organization slug. Please set it manually`, + }); + } + await setOrgSlug({ + targetOrgId, + targetSlug: orgMetadata.requestedSlug, + }); +} + +function assertUserPartOfOrgAndRemigrationAllowed( + userToMoveToOrg: { + organizationId: User["organizationId"]; + }, + targetOrgId: number, + targetOrgUsername: string, + userId: number | undefined +) { + if (userToMoveToOrg.organizationId) { + if (userToMoveToOrg.organizationId !== targetOrgId) { + throw new HttpError({ + statusCode: 400, + message: `User ${targetOrgUsername} already exists for different Org with orgId: ${targetOrgId}`, + }); + } else { + log.debug(`Redoing migration for userId: ${userId} to orgId:${targetOrgId}`); + } + } +} + +async function getTeamOrThrowError(targetOrgId: number) { + const team = await prisma.team.findUnique({ + where: { + id: targetOrgId, + }, + }); + + if (!team) { + throw new HttpError({ + statusCode: 400, + message: `Org with id: ${targetOrgId} not found`, + }); + } + return team; +} + +function assertUserPartOfOtherOrg( + userToMoveToOrg: { + organizationId: User["organizationId"]; + } | null, + userName: string | undefined, + userId: number | undefined, + targetOrgId: number +): asserts userToMoveToOrg { + if (!userToMoveToOrg) { + throw new HttpError({ + message: `User ${userName ? userName : `ID:${userId}`} is part of an org already`, + statusCode: 400, + }); + } + + if (userToMoveToOrg.organizationId && userToMoveToOrg.organizationId !== targetOrgId) { + throw new HttpError({ + message: `User is already a part of different organization ID: ${userToMoveToOrg.organizationId}`, + statusCode: 400, + }); + } +} + +function assertUserIdOrUserName(userId: number | undefined, userName: string | undefined) { + if (!userId && !userName) { + throw new HttpError({ statusCode: 400, message: "userId or userName is required" }); + } + if (userId && userName) { + throw new HttpError({ statusCode: 400, message: "Provide either userId or userName" }); + } +} + +async function addRedirect({ + nonOrgUserName, + organization, + targetOrgUsername, + teamsToBeMovedToOrg, +}: { + nonOrgUserName: string | null; + organization: Team; + targetOrgUsername: string; + teamsToBeMovedToOrg: { slug: string | null }[]; +}) { + if (!nonOrgUserName) { + return; + } + const orgSlug = organization.slug || (organization.metadata as { requestedSlug?: string })?.requestedSlug; + if (!orgSlug) { + log.debug("No slug for org. Not adding the redirect", safeStringify({ organization, nonOrgUserName })); + return; + } + // If the user had a username earlier, we need to redirect it to the new org username + const orgUrlPrefix = getOrgFullOrigin(orgSlug); + log.debug({ + orgUrlPrefix, + nonOrgUserName, + targetOrgUsername, + }); + + await prisma.tempOrgRedirect.upsert({ + where: { + from_type_fromOrgId: { + type: RedirectType.User, + from: nonOrgUserName, + fromOrgId: 0, + }, + }, + create: { + type: RedirectType.User, + from: nonOrgUserName, + fromOrgId: 0, + toUrl: `${orgUrlPrefix}/${targetOrgUsername}`, + }, + update: { + toUrl: `${orgUrlPrefix}/${targetOrgUsername}`, + }, + }); + + for (const [, team] of Object.entries(teamsToBeMovedToOrg)) { + if (!team.slug) { + log.debug("No slug for team. Not adding the redirect", safeStringify({ team })); + continue; + } + await prisma.tempOrgRedirect.upsert({ + where: { + from_type_fromOrgId: { + type: RedirectType.Team, + from: team.slug, + fromOrgId: 0, + }, + }, + create: { + type: RedirectType.Team, + from: team.slug, + fromOrgId: 0, + toUrl: `${orgUrlPrefix}/team/${team.slug}`, + }, + update: { + toUrl: `${orgUrlPrefix}/team/${team.slug}`, + }, + }); + } +} + +async function addTeamRedirect({ + oldTeamSlug, + teamSlug, + orgSlug, +}: { + oldTeamSlug: string | null; + teamSlug: string | null; + orgSlug: string | null; +}) { + if (!oldTeamSlug) { + throw new HttpError({ + statusCode: 400, + message: "No oldSlug for team. Not adding the redirect", + }); + } + if (!teamSlug) { + throw new HttpError({ + statusCode: 400, + message: "No slug for team. Not adding the redirect", + }); + } + if (!orgSlug) { + log.warn(`No slug for org. Not adding the redirect`); + return; + } + const orgUrlPrefix = getOrgFullOrigin(orgSlug); + + await prisma.tempOrgRedirect.upsert({ + where: { + from_type_fromOrgId: { + type: RedirectType.Team, + from: oldTeamSlug, + fromOrgId: 0, + }, + }, + create: { + type: RedirectType.Team, + from: oldTeamSlug, + fromOrgId: 0, + toUrl: `${orgUrlPrefix}/${teamSlug}`, + }, + update: { + toUrl: `${orgUrlPrefix}/${teamSlug}`, + }, + }); +} + +async function updateMembership({ + targetOrgId, + userToMoveToOrg, + targetOrgRole, + targetOrgMembershipAccepted, +}: { + targetOrgId: number; + userToMoveToOrg: User; + targetOrgRole: MembershipRole; + targetOrgMembershipAccepted: boolean; +}) { + log.debug("updateMembership", { targetOrgId, userToMoveToOrg, targetOrgRole, targetOrgMembershipAccepted }); + await prisma.membership.upsert({ + where: { + userId_teamId: { + teamId: targetOrgId, + userId: userToMoveToOrg.id, + }, + }, + create: { + teamId: targetOrgId, + userId: userToMoveToOrg.id, + role: targetOrgRole, + accepted: targetOrgMembershipAccepted, + }, + update: { + role: targetOrgRole, + accepted: targetOrgMembershipAccepted, + }, + }); +} + +async function dbMoveUserToOrg({ + userToMoveToOrg, + targetOrgId, + targetOrgUsername, + nonOrgUserName, +}: { + userToMoveToOrg: User; + targetOrgId: number; + targetOrgUsername: string; + nonOrgUserName: string | null; +}) { + await prisma.user.update({ + where: { + id: userToMoveToOrg.id, + }, + data: { + organizationId: targetOrgId, + username: targetOrgUsername, + metadata: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...(userToMoveToOrg.metadata || {}), + migratedToOrgFrom: { + username: nonOrgUserName, + lastMigrationTime: new Date().toISOString(), + }, + }, + }, + }); +} + +async function moveTeamsWithoutMembersToOrg({ + targetOrgId, + userToMoveToOrg, +}: { + targetOrgId: number; + userToMoveToOrg: User; +}) { + const memberships = await prisma.membership.findMany({ + where: { + userId: userToMoveToOrg.id, + }, + }); + + const membershipTeamIds = memberships.map((m) => m.teamId); + const teams = await prisma.team.findMany({ + where: { + id: { + in: membershipTeamIds, + }, + }, + select: { + id: true, + slug: true, + metadata: true, + }, + }); + + const teamsToBeMovedToOrg = teams + .map((team) => { + return { + ...team, + metadata: teamMetadataSchema.parse(team.metadata), + }; + }) + // Remove Orgs from the list + .filter((team) => !team.metadata?.isOrganization); + + const teamIdsToBeMovedToOrg = teamsToBeMovedToOrg.map((t) => t.id); + + if (memberships.length) { + // Add the user's teams to the org + await prisma.team.updateMany({ + where: { + id: { + in: teamIdsToBeMovedToOrg, + }, + }, + data: { + parentId: targetOrgId, + }, + }); + } + return teamsToBeMovedToOrg; +} + +/** + * Make sure you pass it an organization ID only and not a team ID. + */ +async function setOrgSlug({ targetOrgId, targetSlug }: { targetOrgId: number; targetSlug: string }) { + await prisma.team.update({ + where: { + id: targetOrgId, + }, + data: { + slug: targetSlug, + }, + }); +} + +async function removeTeamRedirect(teamSlug: string | null) { + if (!teamSlug) { + throw new HttpError({ + statusCode: 400, + message: "No slug for team. Not removing the redirect", + }); + return; + } + + await prisma.tempOrgRedirect.deleteMany({ + where: { + type: RedirectType.Team, + from: teamSlug, + fromOrgId: 0, + }, + }); +} + +async function removeUserAlongWithItsTeamsRedirects({ + nonOrgUserName, + teamsToBeRemovedFromOrg, +}: { + nonOrgUserName: string | null; + teamsToBeRemovedFromOrg: { slug: string | null }[]; +}) { + if (!nonOrgUserName) { + return; + } + + await prisma.tempOrgRedirect.deleteMany({ + // This where clause is unique, so we will get only one result but using deleteMany because it doesn't throw an error if there are no rows to delete + where: { + type: RedirectType.User, + from: nonOrgUserName, + fromOrgId: 0, + }, + }); + + for (const [, team] of Object.entries(teamsToBeRemovedFromOrg)) { + if (!team.slug) { + log.debug("No slug for team. Not removing the redirect", safeStringify({ team })); + continue; + } + await prisma.tempOrgRedirect.deleteMany({ + where: { + type: RedirectType.Team, + from: team.slug, + fromOrgId: 0, + }, + }); + } +} + +async function dbRemoveTeamFromOrg({ teamId }: { teamId: number }) { + const team = await prisma.team.findUnique({ + where: { + id: teamId, + }, + }); + + if (!team) { + throw new HttpError({ + statusCode: 400, + message: `Team with id: ${teamId} not found`, + }); + } + + const teamMetadata = teamMetadataSchema.parse(team?.metadata); + try { + return await prisma.team.update({ + where: { + id: teamId, + }, + data: { + parentId: null, + slug: teamMetadata?.migratedToOrgFrom?.teamSlug || team.slug, + metadata: { + ...teamMetadata, + migratedToOrgFrom: { + reverted: true, + lastRevertTime: new Date().toISOString(), + }, + }, + }, + select: { + slug: true, + }, + }); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + if (e.code === "P2002") { + throw new HttpError({ + message: `Looks like the team's name is already taken by some other team outside the org or an org itself. Please change this team's name or the other team/org's name. If you rename the team that you are trying to remove from the org, you will have to manually remove the redirect from the database for that team as the slug would have changed.`, + statusCode: 400, + }); + } + } + throw e; + } +} + +async function removeTeamsWithoutItsMemberFromOrg({ userToRemoveFromOrg }: { userToRemoveFromOrg: User }) { + const memberships = await prisma.membership.findMany({ + where: { + userId: userToRemoveFromOrg.id, + }, + }); + + const membershipTeamIds = memberships.map((m) => m.teamId); + const teams = await prisma.team.findMany({ + where: { + id: { + in: membershipTeamIds, + }, + }, + select: { + id: true, + slug: true, + metadata: true, + }, + }); + + const teamsToBeRemovedFromOrg = teams + .map((team) => { + return { + ...team, + metadata: teamMetadataSchema.parse(team.metadata), + }; + }) + // Remove Orgs from the list + .filter((team) => !team.metadata?.isOrganization); + + const teamIdsToBeRemovedFromOrg = teamsToBeRemovedFromOrg.map((t) => t.id); + + if (memberships.length) { + // Remove the user's teams from the org + await prisma.team.updateMany({ + where: { + id: { + in: teamIdsToBeRemovedFromOrg, + }, + }, + data: { + parentId: null, + }, + }); + } + return teamsToBeRemovedFromOrg; +} + +async function dbRemoveUserFromOrg({ + userToRemoveFromOrg, + nonOrgUserName, +}: { + userToRemoveFromOrg: User; + nonOrgUserName: string; +}) { + await prisma.user.update({ + where: { + id: userToRemoveFromOrg.id, + }, + data: { + organizationId: null, + username: nonOrgUserName, + metadata: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...(userToRemoveFromOrg.metadata || {}), + migratedToOrgFrom: { + username: null, + reverted: true, + revertTime: new Date().toISOString(), + }, + }, + }, + }); +} + +async function removeMembership({ + targetOrgId, + userToRemoveFromOrg, +}: { + targetOrgId: number; + userToRemoveFromOrg: User; +}) { + await prisma.membership.deleteMany({ + where: { + teamId: targetOrgId, + userId: userToRemoveFromOrg.id, + }, + }); +} diff --git a/apps/web/lib/withNonce.tsx b/apps/web/lib/withNonce.tsx index 1649d6fab9..08e24bde0e 100644 --- a/apps/web/lib/withNonce.tsx +++ b/apps/web/lib/withNonce.tsx @@ -2,7 +2,7 @@ import type { GetServerSideProps } from "next"; import { csp } from "@lib/csp"; -export type WithNonceProps> = T & { +export type WithNonceProps> = T & { nonce?: string; }; @@ -11,7 +11,7 @@ export type WithNonceProps> = T & { * Note that if the Components are not adding any script tag then this is not needed. Even in absence of this, Document.getInitialProps would be able to generate nonce itself which it needs to add script tags common to all pages * There is no harm in wrapping a `getServerSideProps` fn with this even if it doesn't add any script tag. */ -export default function withNonce>( +export default function withNonce>( getServerSideProps: GetServerSideProps ): GetServerSideProps> { return async (context) => { diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index d14b59cb45..213869dbfa 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -64,6 +64,23 @@ const middleware = async (req: NextRequest): Promise> => { requestHeaders.set("x-csp-enforce", "true"); } + if (url.pathname.startsWith("/future/apps/installed")) { + const returnTo = req.cookies.get("return-to")?.value; + if (returnTo !== undefined) { + requestHeaders.set("Set-Cookie", "return-to=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"); + + let validPathname = returnTo; + + try { + validPathname = new URL(returnTo).pathname; + } catch (e) {} + + const nextUrl = url.clone(); + nextUrl.pathname = validPathname; + return NextResponse.redirect(nextUrl, { headers: requestHeaders }); + } + } + requestHeaders.set("x-pathname", url.pathname); const locale = await getLocale(req); @@ -101,6 +118,32 @@ export const config = { "/apps/routing_forms/:path*", "/event-types", "/future/event-types/", + "/settings/admin/:path*", + "/future/settings/admin/:path*", + "/apps/installed/:category/", + "/future/apps/installed/:category/", + "/apps/:slug/", + "/future/apps/:slug/", + "/apps/:slug/setup/", + "/future/apps/:slug/setup/", + "/apps/categories/", + "/future/apps/categories/", + "/apps/categories/:category/", + "/future/apps/categories/:category/", + "/workflows/:path*", + "/future/workflows/:path*", + "/settings/teams/:path*", + "/future/settings/teams/:path*", + "/getting-started/:step/", + "/future/getting-started/:step/", + "/apps", + "/future/apps", + "/bookings/:status/", + "/future/bookings/:status/", + "/video/:path*", + "/future/video/:path*", + "/teams", + "/future/teams/", ], }; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 24d6ceb2fc..58c928083f 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -154,11 +154,14 @@ const matcherConfigUserTypeEmbedRoute = { /** @type {import("next").NextConfig} */ const nextConfig = { + experimental: { + serverComponentsExternalPackages: ["next-i18next"], + }, i18n: { ...i18n, localeDetection: false, }, - productionBrowserSourceMaps: true, + productionBrowserSourceMaps: false, /* We already do type check on GH actions */ typescript: { ignoreBuildErrors: !!process.env.CI, @@ -231,6 +234,9 @@ const nextConfig = { ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified // by next.js will be dropped. Doesn't make much sense, but how it is fs: false, + // ignore module resolve errors caused by the server component bundler + "pg-native": false, + "superagent-proxy": false, }; /** @@ -510,6 +516,11 @@ const nextConfig = { destination: "/apps/installed/conferencing", permanent: true, }, + { + source: "/apps/installed", + destination: "/apps/installed/calendar", + permanent: true, + }, // OAuth callbacks when sent to localhost:3000(w would be expected) should be redirected to corresponding to WEBAPP_URL ...(process.env.NODE_ENV === "development" && // Safer to enable the redirect only when the user is opting to test out organizations @@ -561,6 +572,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_DSN) { nextConfig["sentry"] = { autoInstrumentServerFunctions: true, hideSourceMaps: true, + // disable source map generation for the server code + disableServerWebpackPlugin: !!process.env.SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN, }; plugins.push(withSentryConfig); diff --git a/apps/web/package.json b/apps/web/package.json index 1a21c8c524..90d4f66f7d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@calcom/web", - "version": "3.5.3", + "version": "3.6.1", "private": true, "scripts": { "analyze": "ANALYZE=true next build", @@ -39,7 +39,7 @@ "@calcom/tsconfig": "*", "@calcom/ui": "*", "@daily-co/daily-js": "^0.37.0", - "@formkit/auto-animate": "^1.0.0-beta.5", + "@formkit/auto-animate": "^0.8.1", "@glidejs/glide": "^3.5.2", "@hookform/error-message": "^2.0.0", "@hookform/resolvers": "^2.9.7", @@ -83,7 +83,7 @@ "ics": "^2.37.0", "jose": "^4.13.1", "kbar": "^0.1.0-beta.36", - "libphonenumber-js": "^1.10.12", + "libphonenumber-js": "^1.10.51", "lodash": "^4.17.21", "lottie-react": "^2.3.1", "markdown-it": "^13.0.1", diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx index 12cb57ac7d..2410f6c4d4 100644 --- a/apps/web/pages/404.tsx +++ b/apps/web/pages/404.tsx @@ -85,7 +85,7 @@ export default function Custom404() { const isSuccessPage = pathname?.startsWith("/booking"); const isSubpage = pathname?.includes("/", 2) || isSuccessPage; - const isSignup = pathname?.startsWith("/signup"); + /** * If we're on 404 and the route is insights it means it is disabled * TODO: Abstract this for all disabled features @@ -112,7 +112,7 @@ export default function Custom404() {
- + {t("or_go_back_home")} @@ -129,7 +129,7 @@ export default function Custom404() { return ( <>
- {isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? ( -
-
-

- {t("missing_license")} -

-

- {t("signup_requires")} -

-

{t("signup_requires_description", { companyName: "Cal.com" })}

-
-
-

- {t("next_steps")} -

- - -
-
- {((!isSubpage && IS_CALCOM) || - currentPageType === pageType.ORG || - currentPageType === pageType.TEAM) && ( - - )} -

- {t("popular_pages")} -

- + )} +

+ {t("popular_pages")} +

+ -
- - {t("or_go_back_home")} - - -
-
- - )} + ))} +
  • + +
    + + + +
    +
    +

    + + +

    +

    {t("join_our_community")}

    +
    +
    +
    +
    +
  • + +
    + + {t("or_go_back_home")} + + +
    +
    diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 493b8f3953..349c8be4b6 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -11,7 +11,6 @@ import { useEmbedStyles, useIsEmbed, } from "@calcom/embed-core/embed-iframe"; -import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar"; import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components"; @@ -20,6 +19,7 @@ import { getUsernameList } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; +import logger from "@calcom/lib/logger"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { stripMarkdown } from "@calcom/lib/stripMarkdown"; import prisma from "@calcom/prisma"; @@ -27,6 +27,7 @@ import { RedirectType, type EventType, type User } from "@calcom/prisma/client"; import { baseEventTypeSelect } from "@calcom/prisma/selects"; import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { HeadSeo, UnpublishedEntity } from "@calcom/ui"; +import { UserAvatar } from "@calcom/ui"; import { Verified, ArrowRight } from "@calcom/ui/components/icon"; import type { EmbedProps } from "@lib/withEmbedSsr"; @@ -100,7 +101,7 @@ export function UserPage(props: InferGetServerSidePropsType
    - { - return ( - await prisma.eventType.findMany({ - where: { - AND: [ - { - teamId: null, - }, - { - OR: [ - { - userId, - }, - { - users: { - some: { - id: userId, - }, + const eventTypes = await prisma.eventType.findMany({ + where: { + AND: [ + { + teamId: null, + }, + { + OR: [ + { + userId, + }, + { + users: { + some: { + id: userId, }, }, - ], - }, - ], - }, - orderBy: [ - { - position: "desc", - }, - { - id: "asc", + }, + ], }, ], - select: { - ...baseEventTypeSelect, - metadata: true, + }, + orderBy: [ + { + position: "desc", }, - }) - ).map((eventType) => ({ - ...eventType, - metadata: EventTypeMetaDataSchema.parse(eventType.metadata), - })); + { + id: "asc", + }, + ], + select: { + ...baseEventTypeSelect, + metadata: true, + }, + }); + // map and filter metadata, exclude eventType entirely when faulty metadata is found. + // report error to exception so we don't lose the error. + return eventTypes.reduce((eventTypes, eventType) => { + const parsedMetadata = EventTypeMetaDataSchema.safeParse(eventType.metadata); + if (!parsedMetadata.success) { + logger.error(parsedMetadata.error); + return eventTypes; + } + eventTypes.push({ + ...eventType, + metadata: parsedMetadata.data, + }); + return eventTypes; + }, []); }; export type UserPageProps = { diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index b9b7293353..b328800c45 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -1,15 +1,12 @@ import type { GetServerSidePropsContext } from "next"; +import { useSearchParams } from "next/navigation"; import { z } from "zod"; import { Booker } from "@calcom/atoms"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; -import { - getBookingForReschedule, - getBookingForSeatedEvent, - getMultipleDurationValue, -} from "@calcom/features/bookings/lib/get-booking"; +import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking"; import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains"; import { getUsernameList } from "@calcom/lib/defaultEvents"; @@ -26,6 +23,16 @@ import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect"; export type PageProps = inferSSRProps & EmbedProps; +export const getMultipleDurationValue = ( + multipleDurationConfig: number[] | undefined, + queryDuration: string | string[] | null | undefined, + defaultValue: number +) => { + if (!multipleDurationConfig) return null; + if (multipleDurationConfig.includes(Number(queryDuration))) return Number(queryDuration); + return defaultValue; +}; + export default function Type({ slug, user, @@ -35,9 +42,10 @@ export default function Type({ isBrandingHidden, isSEOIndexable, rescheduleUid, - entity, - duration, + eventData, }: PageProps) { + const searchParams = useSearchParams(); + return (
    ); @@ -68,7 +84,7 @@ Type.PageWrapper = PageWrapper; async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { const session = await getServerSession(context); const { user: usernames, type: slug } = paramsSchema.parse(context.params); - const { rescheduleUid, bookingUid, duration: queryDuration } = context.query; + const { rescheduleUid, bookingUid } = context.query; const { ssrInit } = await import("@server/lib/ssr"); const ssr = await ssrInit(context); @@ -120,12 +136,14 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { return { props: { - entity: eventData.entity, - duration: getMultipleDurationValue( - eventData.metadata?.multipleDuration, - queryDuration, - eventData.length - ), + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: { + ...eventData.metadata, + multipleDuration: [15, 30, 60], + }, + }, booking, user: usernames.join("+"), slug, @@ -144,7 +162,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { const session = await getServerSession(context); const { user: usernames, type: slug } = paramsSchema.parse(context.params); const username = usernames[0]; - const { rescheduleUid, bookingUid, duration: queryDuration } = context.query; + const { rescheduleUid, bookingUid } = context.query; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); const isOrgContext = currentOrgDomain && isValidOrgDomain; @@ -167,7 +185,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { const user = await prisma.user.findFirst({ where: { username, - organization: userOrgQuery(context.req.headers.host ?? "", context.params?.orgSlug), + organization: userOrgQuery(context.req, context.params?.orgSlug), }, select: { away: true, @@ -207,15 +225,14 @@ async function getUserPageProps(context: GetServerSidePropsContext) { return { props: { booking, - duration: getMultipleDurationValue( - eventData.metadata?.multipleDuration, - queryDuration, - eventData.length - ), + eventData: { + entity: eventData.entity, + length: eventData.length, + metadata: eventData.metadata, + }, away: user?.away, user: username, slug, - entity: eventData.entity, trpcState: ssr.dehydrate(), isBrandingHidden: user?.hideBranding, isSEOIndexable: user?.allowSEOIndexing, diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index 89d53107ec..511bd9eb95 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -28,6 +28,7 @@ MyApp.getInitialProps = async (ctx: AppContextType) => { if (req) { const { getLocale } = await import("@calcom/features/auth/lib/getLocale"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any newLocale = await getLocale(req as IncomingMessage & { cookies: Record }); } else if (typeof window !== "undefined" && window.calNewLocale) { newLocale = window.calNewLocale; diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 6ffa6f293d..2296b50224 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -32,7 +32,8 @@ class MyDocument extends Document { const newLocale = ctx.req && getLocaleModule - ? await getLocaleModule.getLocale(ctx.req as IncomingMessage & { cookies: Record }) + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + await getLocaleModule.getLocale(ctx.req as IncomingMessage & { cookies: Record }) : "en"; const asPath = ctx.asPath || ""; @@ -87,7 +88,7 @@ class MyDocument extends Document { { res.setHeader("Content-Type", "text/html"); res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate"); res.write( - renderEmail("MonthlyDigestEmail", { + await renderEmail("MonthlyDigestEmail", { language: t, Created: 12, Completed: 13, diff --git a/apps/web/pages/api/orgMigration/moveTeamToOrg.ts b/apps/web/pages/api/orgMigration/moveTeamToOrg.ts new file mode 100644 index 0000000000..f6a3e2bec4 --- /dev/null +++ b/apps/web/pages/api/orgMigration/moveTeamToOrg.ts @@ -0,0 +1,76 @@ +import { getFormSchema } from "@pages/settings/admin/orgMigrations/moveTeamToOrg"; +import type { NextApiRequest, NextApiResponse } from "next/types"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import { getTranslation } from "@calcom/lib/server"; +import { UserPermissionRole } from "@calcom/prisma/enums"; + +import { moveTeamToOrg } from "../../../lib/orgMigration"; + +const log = logger.getSubLogger({ prefix: ["moveTeamToOrg"] }); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const rawBody = req.body; + + log.debug( + "Moving team to org:", + safeStringify({ + body: rawBody, + }) + ); + + const translate = await getTranslation("en", "common"); + const moveTeamToOrgSchema = getFormSchema(translate); + + const parsedBody = moveTeamToOrgSchema.safeParse(rawBody); + + const session = await getServerSession({ req, res }); + + if (!session) { + return res.status(403).json({ message: "No session found" }); + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + + if (!parsedBody.success) { + log.error("moveTeamToOrg failed:", safeStringify(parsedBody.error)); + return res.status(400).json({ message: JSON.stringify(parsedBody.error) }); + } + + const { teamId, targetOrgId, moveMembers, teamSlugInOrganization } = parsedBody.data; + const isAllowed = isAdmin; + if (!isAllowed) { + return res.status(403).json({ message: "Not Authorized" }); + } + + try { + await moveTeamToOrg({ + targetOrg: { + id: targetOrgId, + teamSlug: teamSlugInOrganization, + }, + teamId, + moveMembers, + }); + } catch (error) { + if (error instanceof HttpError) { + if (error.statusCode > 300) { + log.error("moveTeamToOrg failed:", safeStringify(error.message)); + } + return res.status(error.statusCode).json({ message: error.message }); + } + log.error("moveTeamToOrg failed:", safeStringify(error)); + const errorMessage = error instanceof Error ? error.message : "Something went wrong"; + + return res.status(500).json({ message: errorMessage }); + } + + return res.status(200).json({ + message: `Added team ${teamId} to Org: ${targetOrgId} ${ + moveMembers ? " along with the members" : " without the members" + }`, + }); +} diff --git a/apps/web/pages/api/orgMigration/moveUserToOrg.ts b/apps/web/pages/api/orgMigration/moveUserToOrg.ts new file mode 100644 index 0000000000..82b299c5b8 --- /dev/null +++ b/apps/web/pages/api/orgMigration/moveUserToOrg.ts @@ -0,0 +1,75 @@ +import { getFormSchema } from "@pages/settings/admin/orgMigrations/moveUserToOrg"; +import type { NextApiRequest, NextApiResponse } from "next/types"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import { getTranslation } from "@calcom/lib/server"; +import { UserPermissionRole } from "@calcom/prisma/enums"; + +import { moveUserToOrg } from "../../../lib/orgMigration"; + +const log = logger.getSubLogger({ prefix: ["moveUserToOrg"] }); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const rawBody = req.body; + const translate = await getTranslation("en", "common"); + const migrateBodySchema = getFormSchema(translate); + log.debug( + "Starting migration:", + safeStringify({ + body: rawBody, + }) + ); + const parsedBody = migrateBodySchema.safeParse(rawBody); + + const session = await getServerSession({ req }); + + if (!session) { + res.status(403).json({ message: "No session found" }); + return; + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + + if (parsedBody.success) { + const { userId, userName, shouldMoveTeams, targetOrgId, targetOrgUsername, targetOrgRole } = + parsedBody.data; + const isAllowed = isAdmin; + if (isAllowed) { + try { + await moveUserToOrg({ + targetOrg: { + id: targetOrgId, + username: targetOrgUsername, + membership: { + role: targetOrgRole, + }, + }, + user: { + id: userId, + userName, + }, + shouldMoveTeams, + }); + } catch (error) { + if (error instanceof HttpError) { + if (error.statusCode > 300) { + log.error("Migration failed:", safeStringify(error)); + } + return res.status(error.statusCode).json({ message: error.message }); + } + log.error("Migration failed:", safeStringify(error)); + const errorMessage = error instanceof Error ? error.message : "Something went wrong"; + + return res.status(400).json({ message: errorMessage }); + } + } else { + return res.status(403).json({ message: "Not Authorized" }); + } + return res.status(200).json({ message: "Migrated" }); + } + log.error("Migration failed:", safeStringify(parsedBody.error)); + return res.status(400).json({ message: JSON.stringify(parsedBody.error) }); +} diff --git a/apps/web/pages/api/orgMigration/removeTeamFromOrg.ts b/apps/web/pages/api/orgMigration/removeTeamFromOrg.ts new file mode 100644 index 0000000000..fc4a88e4bb --- /dev/null +++ b/apps/web/pages/api/orgMigration/removeTeamFromOrg.ts @@ -0,0 +1,63 @@ +import { getFormSchema } from "@pages/settings/admin/orgMigrations/removeTeamFromOrg"; +import type { NextApiRequest, NextApiResponse } from "next/types"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import { getTranslation } from "@calcom/lib/server"; +import { UserPermissionRole } from "@calcom/prisma/enums"; + +import { removeTeamFromOrg } from "../../../lib/orgMigration"; + +const log = logger.getSubLogger({ prefix: ["removeTeamFromOrg"] }); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const rawBody = req.body; + const translate = await getTranslation("en", "common"); + const removeTeamFromOrgSchema = getFormSchema(translate); + log.debug( + "Removing team from org:", + safeStringify({ + body: rawBody, + }) + ); + const parsedBody = removeTeamFromOrgSchema.safeParse(rawBody); + + const session = await getServerSession({ req }); + + if (!session) { + return res.status(403).json({ message: "No session found" }); + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + + if (!parsedBody.success) { + log.error("RemoveTeamFromOrg failed:", safeStringify(parsedBody.error)); + return res.status(400).json({ message: JSON.stringify(parsedBody.error) }); + } + const { teamId, targetOrgId } = parsedBody.data; + const isAllowed = isAdmin; + if (!isAllowed) { + return res.status(403).json({ message: "Not Authorized" }); + } + + try { + await removeTeamFromOrg({ + targetOrgId, + teamId, + }); + } catch (error) { + if (error instanceof HttpError) { + if (error.statusCode > 300) { + log.error("RemoveTeamFromOrg failed:", safeStringify(error)); + } + return res.status(error.statusCode).json({ message: error.message }); + } + log.error("RemoveTeamFromOrg failed:", safeStringify(error)); + const errorMessage = error instanceof Error ? error.message : "Something went wrong"; + return res.status(500).json({ message: errorMessage }); + } + + return res.status(200).json({ message: `Removed team ${teamId} from ${targetOrgId}` }); +} diff --git a/apps/web/pages/api/orgMigration/removeUserFromOrg.ts b/apps/web/pages/api/orgMigration/removeUserFromOrg.ts new file mode 100644 index 0000000000..ce48fc07e9 --- /dev/null +++ b/apps/web/pages/api/orgMigration/removeUserFromOrg.ts @@ -0,0 +1,59 @@ +import { getFormSchema } from "@pages/settings/admin/orgMigrations/removeUserFromOrg"; +import type { NextApiRequest, NextApiResponse } from "next/types"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; +import { safeStringify } from "@calcom/lib/safeStringify"; +import { getTranslation } from "@calcom/lib/server"; +import { UserPermissionRole } from "@calcom/prisma/enums"; + +import { removeUserFromOrg } from "../../../lib/orgMigration"; + +const log = logger.getSubLogger({ prefix: ["removeUserFromOrg"] }); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const body = req.body; + + log.debug( + "Starting reverse migration:", + safeStringify({ + body, + }) + ); + + const translate = await getTranslation("en", "common"); + const migrateRevertBodySchema = getFormSchema(translate); + const parsedBody = migrateRevertBodySchema.safeParse(body); + const session = await getServerSession({ req }); + + if (!session) { + return res.status(403).json({ message: "No session found" }); + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + if (!isAdmin) { + return res.status(403).json({ message: "Only admin can take this action" }); + } + + if (parsedBody.success) { + const { userId, targetOrgId } = parsedBody.data; + try { + await removeUserFromOrg({ targetOrgId, userId }); + } catch (error) { + if (error instanceof HttpError) { + if (error.statusCode > 300) { + log.error("Reverse migration failed:", safeStringify(error)); + } + return res.status(error.statusCode).json({ message: error.message }); + } + log.error("Reverse migration failed:", safeStringify(error)); + const errorMessage = error instanceof Error ? error.message : "Something went wrong"; + + return res.status(500).json({ message: errorMessage }); + } + return res.status(200).json({ message: "Reverted" }); + } + log.error("Reverse Migration failed:", safeStringify(parsedBody.error)); + return res.status(400).json({ message: JSON.stringify(parsedBody.error) }); +} diff --git a/apps/web/pages/api/recorded-daily-video.ts b/apps/web/pages/api/recorded-daily-video.ts index c35a9d5c7f..412d73bbfc 100644 --- a/apps/web/pages/api/recorded-daily-video.ts +++ b/apps/web/pages/api/recorded-daily-video.ts @@ -1,24 +1,38 @@ import type { WebhookTriggerEvents } from "@prisma/client"; +import { createHmac } from "crypto"; import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; import { DailyLocationType } from "@calcom/app-store/locations"; import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient"; import { sendDailyVideoRecordingEmails } from "@calcom/emails"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; -import { IS_SELF_HOSTED } from "@calcom/lib/constants"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { defaultHandler } from "@calcom/lib/server"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import type { CalendarEvent } from "@calcom/types/Calendar"; -const schema = z.object({ - recordingId: z.string(), - bookingUID: z.string(), -}); +const schema = z + .object({ + version: z.string(), + type: z.string(), + id: z.string(), + payload: z.object({ + recording_id: z.string(), + end_ts: z.number().optional(), + room_name: z.string(), + start_ts: z.number().optional(), + status: z.string(), + + max_participants: z.number().optional(), + duration: z.number().optional(), + s3_key: z.string().optional(), + }), + event_ts: z.number().optional(), + }) + .passthrough(); const downloadLinkSchema = z.object({ download_link: z.string(), @@ -39,8 +53,8 @@ const triggerWebhook = async ({ }; }) => { const eventTrigger: WebhookTriggerEvents = "RECORDING_READY"; - // Send Webhook call if hooked to BOOKING.RECORDING_READY + // Send Webhook call if hooked to BOOKING.RECORDING_READY const triggerForUser = !booking.teamId || (booking.teamId && booking.eventTypeParentId); const subscriberOptions = { @@ -62,71 +76,62 @@ const triggerWebhook = async ({ await Promise.all(promises); }; -const checkIfUserIsPartOfTheSameTeam = async ( - teamId: number | undefined | null, - userId: number, - userEmail: string | undefined | null -) => { - if (!teamId) return false; - - const getUserQuery = () => { - if (!!userEmail) { - return { - OR: [ - { - id: userId, - }, - { - email: userEmail, - }, - ], - }; - } else { - return { - id: userId, - }; - } - }; - - const team = await prisma.team.findFirst({ - where: { - id: teamId, - members: { - some: { - user: getUserQuery(), - }, - }, - }, - }); - - return !!team; -}; +const testRequestSchema = z.object({ + test: z.enum(["test"]), +}); async function handler(req: NextApiRequest, res: NextApiResponse) { if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) { return res.status(405).json({ message: "No SendGrid API key or email" }); } - const response = schema.safeParse(JSON.parse(req.body)); - if (!response.success) { + if (testRequestSchema.safeParse(req.body).success) { + return res.status(200).json({ message: "Test request successful" }); + } + + const hmacSecret = process.env.DAILY_WEBHOOK_SECRET; + if (!hmacSecret) { + return res.status(405).json({ message: "No Daily Webhook Secret" }); + } + + const signature = `${req.headers["x-webhook-timestamp"]}.${JSON.stringify(req.body)}`; + const base64DecodedSecret = Buffer.from(hmacSecret, "base64"); + const hmac = createHmac("sha256", base64DecodedSecret); + const computed_signature = hmac.update(signature).digest("base64"); + + if (req.headers["x-webhook-signature"] !== computed_signature) { + return res.status(403).json({ message: "Signature does not match" }); + } + + const response = schema.safeParse(req.body); + + if (!response.success || response.data.type !== "recording.ready-to-download") { return res.status(400).send({ message: "Invalid Payload", }); } - const { recordingId, bookingUID } = response.data; - const session = await getServerSession({ req, res }); + const { room_name, recording_id, status } = response.data.payload; - if (!session?.user) { - return res.status(401).send({ - message: "User not logged in", + if (status !== "finished") { + return res.status(400).send({ + message: "Recording not finished", }); } try { - const booking = await prisma.booking.findFirst({ + const bookingReference = await prisma.bookingReference.findFirst({ + where: { type: "daily_video", uid: room_name, meetingId: room_name }, + select: { bookingId: true }, + }); + + if (!bookingReference || !bookingReference.bookingId) { + return res.status(404).send({ message: "Booking reference not found" }); + } + + const booking = await prisma.booking.findUniqueOrThrow({ where: { - uid: bookingUID, + id: bookingReference.bookingId, }, select: { ...bookingMinimalSelect, @@ -153,9 +158,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }, }); - if (!booking || booking.location !== DailyLocationType) { + if (!booking || !(booking.location === DailyLocationType || booking?.location?.trim() === "")) { return res.status(404).send({ - message: `Booking of uid ${bookingUID} does not exist or does not contain daily video as location`, + message: `Booking of room_name ${room_name} does not exist or does not contain daily video as location`, }); } @@ -175,26 +180,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const attendeesList = await Promise.all(attendeesListPromises); - const isUserAttendeeOrOrganiser = - booking?.user?.id === session.user.id || - attendeesList.find( - (attendee) => attendee.id === session.user.id || attendee.email === session.user.email - ); - - if (!isUserAttendeeOrOrganiser) { - const isUserMemberOfTheTeam = checkIfUserIsPartOfTheSameTeam( - booking?.eventType?.teamId, - session.user.id, - session.user.email - ); - - if (!isUserMemberOfTheTeam) { - return res.status(403).send({ - message: "Unauthorised", - }); - } - } - await prisma.booking.update({ where: { uid: booking.uid, @@ -204,7 +189,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }, }); - const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId); + const response = await getDownloadLinkOfCalVideoByRecordingId(recording_id); const downloadLinkResponse = downloadLinkSchema.parse(response); const downloadLink = downloadLinkResponse.download_link; @@ -242,17 +227,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }, }); - const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam; - // send emails to all attendees only when user has team plan - if (isSendingEmailsAllowed) { - await sendDailyVideoRecordingEmails(evt, downloadLink); - return res.status(200).json({ message: "Success" }); - } - - return res.status(403).json({ message: "User does not have team plan to send out emails" }); + await sendDailyVideoRecordingEmails(evt, downloadLink); + return res.status(200).json({ message: "Success" }); } catch (err) { - console.warn("Error in /recorded-daily-video", err); + console.error("Error in /recorded-daily-video", err); return res.status(500).json({ message: "something went wrong" }); } } diff --git a/apps/web/pages/api/username.ts b/apps/web/pages/api/username.ts index ea34666f7f..19315208a0 100644 --- a/apps/web/pages/api/username.ts +++ b/apps/web/pages/api/username.ts @@ -1,4 +1,5 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { z } from "zod"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { checkUsername } from "@calcom/lib/server/checkUsername"; @@ -8,8 +9,14 @@ type Response = { premium: boolean; }; +const bodySchema = z.object({ + username: z.string(), + orgSlug: z.string().optional(), +}); + export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { const { currentOrgDomain } = orgDomainConfig(req); - const result = await checkUsername(req.body.username, currentOrgDomain); + const { username, orgSlug } = bodySchema.parse(req.body); + const result = await checkUsername(username, currentOrgDomain || orgSlug); return res.status(200).json(result); } diff --git a/apps/web/pages/apps/[slug]/index.tsx b/apps/web/pages/apps/[slug]/index.tsx index da41a33402..27bd8dbdfb 100644 --- a/apps/web/pages/apps/[slug]/index.tsx +++ b/apps/web/pages/apps/[slug]/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Prisma } from "@prisma/client"; import fs from "fs"; import matter from "gray-matter"; diff --git a/apps/web/pages/apps/[slug]/setup.tsx b/apps/web/pages/apps/[slug]/setup.tsx index 31d8c78a11..9dd65d9faf 100644 --- a/apps/web/pages/apps/[slug]/setup.tsx +++ b/apps/web/pages/apps/[slug]/setup.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { InferGetServerSidePropsType } from "next"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; diff --git a/apps/web/pages/apps/categories/[category].tsx b/apps/web/pages/apps/categories/[category].tsx index 81de5e70c8..0cad97bfa1 100644 --- a/apps/web/pages/apps/categories/[category].tsx +++ b/apps/web/pages/apps/categories/[category].tsx @@ -1,3 +1,5 @@ +"use client"; + import { Prisma } from "@prisma/client"; import type { GetStaticPropsContext, InferGetStaticPropsType } from "next"; import Link from "next/link"; diff --git a/apps/web/pages/apps/categories/index.tsx b/apps/web/pages/apps/categories/index.tsx index 7b40ade47b..1012640fc8 100644 --- a/apps/web/pages/apps/categories/index.tsx +++ b/apps/web/pages/apps/categories/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetServerSidePropsContext } from "next"; import Link from "next/link"; @@ -13,7 +15,7 @@ import PageWrapper from "@components/PageWrapper"; import { ssrInit } from "@server/lib/ssr"; -export default function Apps({ categories }: inferSSRProps) { +export default function Apps({ categories }: Omit, "trpcState">) { const { t, isLocaleReady } = useLocale(); return ( diff --git a/apps/web/pages/apps/index.tsx b/apps/web/pages/apps/index.tsx index 679957c923..8315bdc4e3 100644 --- a/apps/web/pages/apps/index.tsx +++ b/apps/web/pages/apps/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetServerSidePropsContext } from "next"; import type { ChangeEventHandler } from "react"; import { useState } from "react"; @@ -64,7 +66,7 @@ export default function Apps({ categories, appStore, userAdminTeams, -}: inferSSRProps) { +}: Omit, "trpcState">) { const { t } = useLocale(); const [searchText, setSearchText] = useState(undefined); diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx index 2cffde2cdf..5a207ecc13 100644 --- a/apps/web/pages/apps/installed/[category].tsx +++ b/apps/web/pages/apps/installed/[category].tsx @@ -1,3 +1,5 @@ +"use client"; + import { useReducer } from "react"; import { z } from "zod"; diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx index c6c32275d6..856e6a7153 100644 --- a/apps/web/pages/auth/login.tsx +++ b/apps/web/pages/auth/login.tsx @@ -5,7 +5,6 @@ import type { GetServerSidePropsContext } from "next"; import { getCsrfToken, signIn } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import type { CSSProperties } from "react"; import { useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { FaGoogle } from "react-icons/fa"; @@ -174,15 +173,7 @@ inferSSRProps & WithNonceProps<{}>) { : isSAMLLoginEnabled && !isLoading && data?.connectionExists; return ( -
    +
    & WithNonceProps<{}>) { type="submit" color="primary" disabled={formState.isSubmitting} - className="w-full justify-center dark:bg-white dark:text-black"> + className="w-full justify-center"> {twoFactorRequired ? t("submit") : t("sign_in")}
    @@ -251,6 +242,7 @@ inferSSRProps & WithNonceProps<{}>) { -
    +
    +

    Don’t seen an email?

    + +
    ); } +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const EMAIL_FROM = process.env.EMAIL_FROM; + + return { + props: { + EMAIL_FROM, + }, + }; +}; Verify.PageWrapper = PageWrapper; diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index 029652211a..afada8d7a1 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -486,48 +486,24 @@ export default function Success(props: SuccessProps) {
    {t("where")}
    {!rescheduleLocation || locationToDisplay === rescheduleLocationToDisplay ? ( - locationToDisplay.startsWith("http") ? ( - - {providerName || "Link"} - - - ) : ( - locationToDisplay - ) + ) : ( <> - {!!formerTime && - (locationToDisplay.startsWith("http") ? ( - - {providerName || "Link"} - - - ) : ( -

    {locationToDisplay}

    - ))} - {rescheduleLocationToDisplay.startsWith("http") ? ( - - {rescheduleProviderName || "Link"} - - - ) : ( - rescheduleLocationToDisplay + {!!formerTime && ( + )} + + )}
    @@ -575,7 +551,7 @@ export default function Success(props: SuccessProps) { className="text-default break-words" data-testid="field-response" data-fob-field={field.name}> - {response.toString()} + {field.type === "boolean" ? (response ? t("yes") : t("no")) : response.toString()}

    ); @@ -830,6 +806,29 @@ export default function Success(props: SuccessProps) { ); } +const DisplayLocation = ({ + locationToDisplay, + providerName, + className, +}: { + locationToDisplay: string; + providerName?: string; + className?: string; +}) => + locationToDisplay.startsWith("http") ? ( + + {providerName || "Link"} + + + ) : ( +

    {locationToDisplay}

    + ); + Success.isBookingPage = true; Success.PageWrapper = PageWrapper; @@ -1244,6 +1243,7 @@ async function getRecurringBookings(recurringEventId: string | null) { const recurringBookings = await prisma.booking.findMany({ where: { recurringEventId, + status: BookingStatus.ACCEPTED, }, select: { startTime: true, diff --git a/apps/web/pages/bookings/[status].tsx b/apps/web/pages/bookings/[status].tsx index 24b6cbc948..878d344620 100644 --- a/apps/web/pages/bookings/[status].tsx +++ b/apps/web/pages/bookings/[status].tsx @@ -1,12 +1,15 @@ +"use client"; + import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { GetStaticPaths, GetStaticProps } from "next"; -import { Fragment } from "react"; +import { Fragment, useState } from "react"; import React from "react"; import { z } from "zod"; import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; import dayjs from "@calcom/dayjs"; import { getLayout } from "@calcom/features/MainLayout"; +import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle"; import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer"; import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery"; import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; @@ -81,6 +84,7 @@ export default function Bookings() { const { status } = params ? querySchema.parse(params) : { status: "upcoming" as const }; const { t } = useLocale(); const user = useMeQuery().data; + const [isFiltersVisible, setIsFiltersVisible] = useState(false); const query = trpc.viewer.bookings.get.useInfiniteQuery( { @@ -151,12 +155,11 @@ export default function Bookings() { return (
    -
    +
    -
    - -
    +
    +
    {query.status === "error" && ( diff --git a/apps/web/pages/connect-and-join.tsx b/apps/web/pages/connect-and-join.tsx new file mode 100644 index 0000000000..8d85bb62fe --- /dev/null +++ b/apps/web/pages/connect-and-join.tsx @@ -0,0 +1,92 @@ +import { useSession } from "next-auth/react"; +import { Trans } from "next-i18next"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; +import { TRPCClientError } from "@calcom/trpc/react"; +import { Button, EmptyScreen, Alert } from "@calcom/ui"; +import { PhoneCall } from "@calcom/ui/components/icon"; + +import PageWrapper from "@components/PageWrapper"; + +function ConnectAndJoin() { + const { t } = useLocale(); + const router = useRouter(); + const token = getQueryParam("token"); + const [meetingUrl, setMeetingUrl] = useState(null); + const [errorMessage, setErrorMessage] = useState(); + + const session = useSession(); + const isUserPartOfOrg = session.status === "authenticated" && !!session.data.user?.org; + + const mutation = trpc.viewer.connectAndJoin.useMutation({ + onSuccess: (res) => { + if (res.meetingUrl && !res.isBookingAlreadyAcceptedBySomeoneElse) { + router.push(res.meetingUrl); + } else if (res.isBookingAlreadyAcceptedBySomeoneElse && res.meetingUrl) { + setMeetingUrl(res.meetingUrl); + } + }, + onError: (err) => { + console.log("err", err, err instanceof TRPCClientError); + if (err instanceof TRPCClientError) { + setErrorMessage(t(err.message)); + } else { + setErrorMessage(t("something_went_wrong")); + } + }, + }); + + if (session.status === "loading") return

    {t("loading")}

    ; + + if (!token) return

    {t("token_not_found")}

    ; + + return ( +
    + {session ? ( + + {meetingUrl ? ( +
    + + Some other host already accepted the meeting. Do you still want to join? + + Continue to Meeting + + +
    + ) : ( + + )} + {errorMessage && } +
    + } + /> + ) : ( +
    {t("you_must_be_logged_in_to", { url: WEBAPP_URL })}
    + )} +
    + ); +} + +ConnectAndJoin.requiresLicense = true; +ConnectAndJoin.PageWrapper = PageWrapper; + +export default ConnectAndJoin; diff --git a/apps/web/pages/event-types/[type]/index.tsx b/apps/web/pages/event-types/[type]/index.tsx index 5c48faa773..cdad0b5c1c 100644 --- a/apps/web/pages/event-types/[type]/index.tsx +++ b/apps/web/pages/event-types/[type]/index.tsx @@ -8,6 +8,7 @@ import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import checkForMultiplePaymentApps from "@calcom/app-store/_utils/payments/checkForMultiplePaymentApps"; import { getEventLocationType } from "@calcom/app-store/locations"; import { validateCustomEventName } from "@calcom/core/event"; import type { EventLocationType } from "@calcom/core/location"; @@ -63,6 +64,10 @@ const EventAdvancedTab = dynamic(() => import("@components/eventtype/EventAdvancedTab").then((mod) => mod.EventAdvancedTab) ); +const EventInstantTab = dynamic(() => + import("@components/eventtype/EventInstantTab").then((mod) => mod.EventInstantTab) +); + const EventRecurringTab = dynamic(() => import("@components/eventtype/EventRecurringTab").then((mod) => mod.EventRecurringTab) ); @@ -84,6 +89,7 @@ export type FormValues = { eventTitle: string; eventName: string; slug: string; + isInstantEvent: boolean; length: number; offsetStart: number; description: string; @@ -131,6 +137,7 @@ export type FormValues = { successRedirectUrl: string; durationLimits?: IntervalLimit; bookingLimits?: IntervalLimit; + onlyShowFirstAvailableSlot: boolean; children: ChildrenEventType[]; hosts: { userId: number; isFixed: boolean }[]; bookingFields: z.infer; @@ -148,6 +155,7 @@ const querySchema = z.object({ "availability", "apps", "limits", + "instant", "recurring", "team", "advanced", @@ -247,9 +255,11 @@ const EventTypePage = (props: EventTypeSetupProps) => { title: eventType.title, locations: eventType.locations || [], recurringEvent: eventType.recurringEvent || null, + isInstantEvent: eventType.isInstantEvent, description: eventType.description ?? undefined, schedule: eventType.schedule || undefined, bookingLimits: eventType.bookingLimits || undefined, + onlyShowFirstAvailableSlot: eventType.onlyShowFirstAvailableSlot || undefined, durationLimits: eventType.durationLimits || undefined, length: eventType.length, hidden: eventType.hidden, @@ -408,6 +418,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { team: , limits: , advanced: , + instant: , recurring: , apps: , workflows: ( @@ -429,6 +440,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { seatsShowAttendees, seatsShowAvailabilityCount, bookingLimits, + onlyShowFirstAvailableSlot, durationLimits, recurringEvent, locations, @@ -473,6 +485,11 @@ const EventTypePage = (props: EventTypeSetupProps) => { } } + // Prevent two payment apps to be enabled + // Ok to cast type here because this metadata will be updated as the event type metadata + if (checkForMultiplePaymentApps(metadata as z.infer)) + throw new Error(t("event_setup_multiple_payment_apps_error")); + if (metadata?.apps?.stripe?.paymentOption === "HOLD" && seatsPerTimeSlot) { throw new Error(t("seats_and_no_show_fee_error")); } @@ -491,6 +508,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { beforeEventBuffer: beforeBufferTime, afterEventBuffer: afterBufferTime, bookingLimits, + onlyShowFirstAvailableSlot, durationLimits, seatsPerTimeSlot, seatsShowAttendees, @@ -504,6 +522,32 @@ const EventTypePage = (props: EventTypeSetupProps) => { const [slugExistsChildrenDialogOpen, setSlugExistsChildrenDialogOpen] = useState([]); const slug = formMethods.watch("slug") ?? eventType.slug; + // Optional prerender all tabs after 300 ms on mount + useEffect(() => { + const timeout = setTimeout(() => { + const Components = [ + EventSetupTab, + EventAvailabilityTab, + EventTeamTab, + EventLimitsTab, + EventAdvancedTab, + EventInstantTab, + EventRecurringTab, + EventAppsTab, + EventWorkflowsTab, + EventWebhooksTab, + ]; + + Components.forEach((C) => { + // @ts-expect-error Property 'render' does not exist on type 'ComponentClass + C.render.preload(); + }); + }, 300); + + return () => { + clearTimeout(timeout); + }; + }, []); return ( <> { // disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"} disableBorder={true} currentUserMembership={currentUserMembership} + bookerUrl={eventType.bookerUrl} isUserOrganizationAdmin={props.isUserOrganizationAdmin}>
    { seatsShowAttendees, seatsShowAvailabilityCount, bookingLimits, + onlyShowFirstAvailableSlot, durationLimits, recurringEvent, locations, @@ -570,6 +616,12 @@ const EventTypePage = (props: EventTypeSetupProps) => { } } } + + // Prevent two payment apps to be enabled + // Ok to cast type here because this metadata will be updated as the event type metadata + if (checkForMultiplePaymentApps(metadata as z.infer)) + throw new Error(t("event_setup_multiple_payment_apps_error")); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { availability, ...rest } = input; updateMutation.mutate({ @@ -584,6 +636,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { beforeEventBuffer: beforeBufferTime, afterEventBuffer: afterBufferTime, bookingLimits, + onlyShowFirstAvailableSlot, durationLimits, seatsPerTimeSlot, seatsShowAttendees, diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index d9f32aeeb5..ce15cea75f 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -1,7 +1,6 @@ "use client"; import { useAutoAnimate } from "@formkit/auto-animate/react"; -import type { User } from "@prisma/client"; import { Trans } from "next-i18next"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; @@ -19,8 +18,8 @@ import { DuplicateDialog } from "@calcom/features/eventtypes/components/Duplicat import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter"; import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery"; import { ShellMain } from "@calcom/features/shell/Shell"; -import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; -import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; +import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; +import { CAL_URL } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; @@ -33,7 +32,6 @@ import { trpc, TRPCClientError } from "@calcom/trpc/react"; import { Alert, Avatar, - AvatarGroup, Badge, Button, ButtonGroup, @@ -69,6 +67,7 @@ import { Trash, Upload, Users, + VenetianMask, } from "@calcom/ui/components/icon"; import useMeQuery from "@lib/hooks/useMeQuery"; @@ -85,7 +84,7 @@ interface EventTypeListHeadingProps { profile: EventTypeGroupProfile; membershipCount: number; teamId?: number | null; - orgSlug?: string; + bookerUrl: string; } type EventTypeGroup = EventTypeGroups[number]; @@ -95,6 +94,7 @@ interface EventTypeListProps { group: EventTypeGroup; groupIndex: number; readOnly: boolean; + bookerUrl: string | null; types: EventType[]; } @@ -127,6 +127,7 @@ const MobileTeamsTab: FC = (props) => { types={events[0].eventTypes} group={events[0]} groupIndex={0} + bookerUrl={events[0].bookerUrl} readOnly={events[0].metadata.readOnly} /> ) : ( @@ -207,12 +208,17 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou const MemoizedItem = memo(Item); -export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeListProps): JSX.Element => { +export const EventTypeList = ({ + group, + groupIndex, + readOnly, + types, + bookerUrl, +}: EventTypeListProps): JSX.Element => { const { t } = useLocale(); const router = useRouter(); const pathname = usePathname(); const searchParams = useCompatSearchParams(); - const orgBranding = useOrgBranding(); const [parent] = useAutoAnimate(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogTypeId, setDeleteDialogTypeId] = useState(0); @@ -383,7 +389,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
      {types.map((type, index) => { const embedLink = `${group.profile.slug}/${type.slug}`; - const calLink = `${orgBranding?.fullDomain ?? CAL_URL}/${embedLink}`; + const calLink = `${bookerUrl}/${embedLink}`; + const isPrivateURLEnabled = type.hashedLink?.link; + const placeholderHashedLink = `${CAL_URL}/d/${type.hashedLink?.link}/${type.slug}`; const isManagedEventType = type.schedulingType === SchedulingType.MANAGED; const isChildrenManagedEventType = type.metadata?.managedEventConfig !== undefined && type.schedulingType !== SchedulingType.MANAGED; @@ -403,24 +411,18 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
      {type.team && !isManagedEventType && ( )} {isManagedEventType && type?.children && type.children?.length > 0 && ( - ch.users) - .map((user: Pick) => ({ - alt: user.name || "", - image: `${orgBranding?.fullDomain ?? WEBAPP_URL}/${user.username}/avatar.png`, - title: user.name || "", - }))} + users={type?.children.flatMap((ch) => ch.users) ?? []} /> )}
      @@ -467,6 +469,20 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL }} /> + + {isPrivateURLEnabled && ( + + + + + ); +} + +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const session = await getSession(ctx); + if (!session || !session.user) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + if (!isAdmin) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + return { + props: { + error: null, + migrated: null, + userId: session.user.id, + ...(await serverSideTranslations(ctx.locale || "en", ["common"])), + username: session.user.username, + }, + }; +} + +MoveTeamToOrg.PageWrapper = PageWrapper; +MoveTeamToOrg.getLayout = getLayout; diff --git a/apps/web/pages/settings/admin/orgMigrations/moveUserToOrg.tsx b/apps/web/pages/settings/admin/orgMigrations/moveUserToOrg.tsx new file mode 100644 index 0000000000..784152caa7 --- /dev/null +++ b/apps/web/pages/settings/admin/orgMigrations/moveUserToOrg.tsx @@ -0,0 +1,213 @@ +/** + * It could be an admin feature to move a user to an organization but because it's a temporary thing before mono-user orgs are implemented, it's not right to spend time on it. + * Plus, we need to do it only for cal.com and not provide as a feature to our self hosters. + */ +import { zodResolver } from "@hookform/resolvers/zod"; +import type { GetServerSidePropsContext } from "next"; +import { getSession } from "next-auth/react"; +import type { TFunction } from "next-i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import z from "zod"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { MembershipRole } from "@calcom/prisma/client"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import { getStringAsNumberRequiredSchema } from "@calcom/prisma/zod-utils"; +import { Button, Form, Meta, SelectField, TextField, showToast } from "@calcom/ui"; + +import PageWrapper from "@components/PageWrapper"; + +import { getLayout } from "./_OrgMigrationLayout"; + +function Wrapper({ children }: { children: React.ReactNode }) { + return ( +
      + + {children} +
      + ); +} + +export const getFormSchema = (t: TFunction) => + z.object({ + userId: z.union([z.string().pipe(z.coerce.number()), z.number()]).optional(), + userName: z.string().optional(), + targetOrgId: z.union([getStringAsNumberRequiredSchema(t), z.number()]), + targetOrgUsername: z.string().min(1, t("error_required_field")), + shouldMoveTeams: z.boolean(), + targetOrgRole: z.union([ + z.literal(MembershipRole.ADMIN), + z.literal(MembershipRole.MEMBER), + z.literal(MembershipRole.OWNER), + ]), + }); + +const enum State { + IDLE, + LOADING, + SUCCESS, + ERROR, +} +export default function MoveUserToOrg() { + const [state, setState] = useState(State.IDLE); + + const roles = Object.values(MembershipRole).map((role) => ({ + label: role, + value: role, + })); + + const moveTeamsOptions = [ + { + label: "Yes", + value: "true", + }, + { + label: "No", + value: "false", + }, + ]; + const { t } = useLocale(); + const formSchema = getFormSchema(t); + const form = useForm({ + mode: "onSubmit", + resolver: zodResolver(formSchema), + }); + + const shouldMoveTeams = form.watch("shouldMoveTeams"); + const register = form.register; + return ( + + {/* Due to some reason auth from website doesn't work if /api endpoint is used. Spent a lot of time and in the end went with submitting data to the same page, because you can't do POST request to a page in Next.js, doing a GET request */} +
      { + setState(State.LOADING); + const res = await fetch(`/api/orgMigration/moveUserToOrg`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(values), + }); + let response = null; + try { + response = await res.json(); + } catch (e) { + if (e instanceof Error) { + showToast(e.message, "error", 10000); + } else { + showToast(t("something_went_wrong"), "error", 10000); + } + setState(State.ERROR); + return; + } + if (res.status === 200) { + setState(State.SUCCESS); + showToast(response.message, "success", 10000); + } else { + setState(State.ERROR); + showToast(response.message, "error", 10000); + } + }}> +
      + + ( + { + if (!option) return; + onChange(option.value); + }} + value={roles.find((role) => role.value === value)} + required + placeholder="Enter userId" + /> + )} + /> + + + ( + { + if (!option) return; + onChange(option.value === "true"); + }} + value={moveTeamsOptions.find((opt) => opt.value === value)} + required + options={moveTeamsOptions} + /> + )} + /> +
      + + +
      +
      + ); +} + +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const session = await getSession(ctx); + if (!session || !session.user) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + if (!isAdmin) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + return { + props: { + ...(await serverSideTranslations(ctx.locale || "en", ["common"])), + }, + }; +} + +MoveUserToOrg.PageWrapper = PageWrapper; +MoveUserToOrg.getLayout = getLayout; diff --git a/apps/web/pages/settings/admin/orgMigrations/removeTeamFromOrg.tsx b/apps/web/pages/settings/admin/orgMigrations/removeTeamFromOrg.tsx new file mode 100644 index 0000000000..17a90f115a --- /dev/null +++ b/apps/web/pages/settings/admin/orgMigrations/removeTeamFromOrg.tsx @@ -0,0 +1,142 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import type { GetServerSidePropsContext } from "next"; +import { getSession } from "next-auth/react"; +import type { TFunction } from "next-i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import z from "zod"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import { getStringAsNumberRequiredSchema } from "@calcom/prisma/zod-utils"; +import { Button, Form, Meta, TextField, showToast } from "@calcom/ui"; + +import PageWrapper from "@components/PageWrapper"; + +import { getLayout } from "./_OrgMigrationLayout"; + +function Wrapper({ children }: { children: React.ReactNode }) { + return ( +
      + + {children} +
      + ); +} + +const enum State { + IDLE, + LOADING, + SUCCESS, + ERROR, +} + +export const getFormSchema = (t: TFunction) => + z.object({ + targetOrgId: z.union([getStringAsNumberRequiredSchema(t), z.number()]), + teamId: z.union([getStringAsNumberRequiredSchema(t), z.number()]), + }); + +export default function RemoveTeamFromOrg() { + const [state, setState] = useState(State.IDLE); + const { t } = useLocale(); + const formSchema = getFormSchema(t); + const form = useForm({ + mode: "onSubmit", + resolver: zodResolver(formSchema), + }); + + const register = form.register; + + return ( + + {/* Due to some reason auth from website doesn't work if /api endpoint is used. Spent a lot of time and in the end went with submitting data to the same page, because you can't do POST request to a page in Next.js, doing a GET request */} +
      { + setState(State.LOADING); + const res = await fetch(`/api/orgMigration/removeTeamFromOrg`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(values), + }); + let response = null; + try { + response = await res.json(); + } catch (e) { + if (e instanceof Error) { + showToast(e.message, "error", 10000); + } else { + showToast(t("something_went_wrong"), "error", 10000); + } + setState(State.ERROR); + return; + } + if (res.status === 200) { + setState(State.SUCCESS); + showToast(response.message, "success", 10000); + } else { + setState(State.ERROR); + showToast(response.message, "error", 10000); + } + }}> +
      + + +
      + +
      +
      + ); +} + +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const session = await getSession(ctx); + if (!session || !session.user) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + if (!isAdmin) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + return { + props: { + ...(await serverSideTranslations(ctx.locale || "en", ["common"])), + }, + }; +} + +RemoveTeamFromOrg.PageWrapper = PageWrapper; +RemoveTeamFromOrg.getLayout = getLayout; diff --git a/apps/web/pages/settings/admin/orgMigrations/removeUserFromOrg.tsx b/apps/web/pages/settings/admin/orgMigrations/removeUserFromOrg.tsx new file mode 100644 index 0000000000..ef7710ed37 --- /dev/null +++ b/apps/web/pages/settings/admin/orgMigrations/removeUserFromOrg.tsx @@ -0,0 +1,137 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import type { GetServerSidePropsContext } from "next"; +import { getSession } from "next-auth/react"; +import type { TFunction } from "next-i18next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import z from "zod"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import { getStringAsNumberRequiredSchema } from "@calcom/prisma/zod-utils"; +import { Button, TextField, Meta, showToast, Form } from "@calcom/ui"; + +import PageWrapper from "@components/PageWrapper"; + +import { getLayout } from "./_OrgMigrationLayout"; + +function Wrapper({ children }: { children: React.ReactNode }) { + return ( +
      + + {children} +
      + ); +} + +const enum State { + IDLE, + LOADING, + SUCCESS, + ERROR, +} + +export const getFormSchema = (t: TFunction) => + z.object({ + userId: z.union([getStringAsNumberRequiredSchema(t), z.number()]), + targetOrgId: z.union([getStringAsNumberRequiredSchema(t), z.number()]), + }); + +export default function RemoveUserFromOrg() { + const [state, setState] = useState(State.IDLE); + const { t } = useLocale(); + const formSchema = getFormSchema(t); + const form = useForm({ + mode: "onSubmit", + resolver: zodResolver(formSchema), + }); + const register = form.register; + return ( + + {/* Due to some reason auth from website doesn't work if /api endpoint is used. Spent a lot of time and in the end went with submitting data to the same page, because you can't do POST request to a page in Next.js, doing a GET request */} +
      { + setState(State.LOADING); + const res = await fetch(`/api/orgMigration/removeUserFromOrg`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(values), + }); + let response = null; + try { + response = await res.json(); + } catch (e) { + if (e instanceof Error) { + showToast(e.message, "error", 10000); + } else { + showToast(t("something_went_wrong"), "error", 10000); + } + setState(State.ERROR); + return; + } + if (res.status === 200) { + setState(State.SUCCESS); + showToast(response.message, "success", 10000); + } else { + setState(State.ERROR); + showToast(response.message, "error", 10000); + } + }}> +
      + + +
      + +
      +
      + ); +} + +export async function getServerSideProps(ctx: GetServerSidePropsContext) { + const session = await getSession(ctx); + if (!session || !session.user) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + if (!isAdmin) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + return { + props: { + ...(await serverSideTranslations(ctx.locale || "en", ["common"])), + }, + }; +} + +RemoveUserFromOrg.PageWrapper = PageWrapper; +RemoveUserFromOrg.getLayout = getLayout; diff --git a/apps/web/pages/settings/admin/organizations/[id]/edit.tsx b/apps/web/pages/settings/admin/organizations/[id]/edit.tsx new file mode 100644 index 0000000000..2236d8f850 --- /dev/null +++ b/apps/web/pages/settings/admin/organizations/[id]/edit.tsx @@ -0,0 +1,9 @@ +import OrgEditView from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgEditPage"; + +import type { CalPageWrapper } from "@components/PageWrapper"; +import PageWrapper from "@components/PageWrapper"; + +const Page = OrgEditView as CalPageWrapper; +Page.PageWrapper = PageWrapper; + +export default Page; diff --git a/apps/web/pages/settings/billing/index.tsx b/apps/web/pages/settings/billing/index.tsx index 501c405a25..91ad564e4e 100644 --- a/apps/web/pages/settings/billing/index.tsx +++ b/apps/web/pages/settings/billing/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { usePathname } from "next/navigation"; import { useIntercom } from "@calcom/features/ee/support/lib/intercom/useIntercom"; diff --git a/apps/web/pages/settings/my-account/profile.tsx b/apps/web/pages/settings/my-account/profile.tsx index 6bd871bf7c..f224d3adf6 100644 --- a/apps/web/pages/settings/my-account/profile.tsx +++ b/apps/web/pages/settings/my-account/profile.tsx @@ -6,7 +6,6 @@ import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; -import OrganizationMemberAvatar from "@calcom/features/ee/organizations/components/OrganizationMemberAvatar"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout"; import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; @@ -41,6 +40,7 @@ import { SkeletonText, TextField, } from "@calcom/ui"; +import { UserAvatar } from "@calcom/ui"; import { AlertTriangle, Trash2 } from "@calcom/ui/components/icon"; import PageWrapper from "@components/PageWrapper"; @@ -106,8 +106,17 @@ const ProfileView = () => { setConfirmAuthEmailChangeWarningDialogOpen(false); setTempFormValues(null); }, - onError: () => { - showToast(t("error_updating_settings"), "error"); + onError: (e) => { + switch (e.message) { + // TODO: Add error codes. + case "email_already_used": + { + showToast(t(e.message), "error"); + } + return; + default: + showToast(t("error_updating_settings"), "error"); + } }, }); @@ -439,7 +448,8 @@ const ProfileForm = ({ : null; return ( <> - { ); }; -OnboardTeamMembersPage.getLayout = (page: React.ReactElement) => ( +export const GetLayout = (page: React.ReactElement) => ( {page} ); +OnboardTeamMembersPage.getLayout = GetLayout; OnboardTeamMembersPage.PageWrapper = PageWrapper; export default OnboardTeamMembersPage; diff --git a/apps/web/pages/settings/teams/new/index.tsx b/apps/web/pages/settings/teams/new/index.tsx index d3442f1696..9a1ba88808 100644 --- a/apps/web/pages/settings/teams/new/index.tsx +++ b/apps/web/pages/settings/teams/new/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import Head from "next/head"; import { CreateANewTeamForm } from "@calcom/features/ee/teams/components"; @@ -18,7 +20,7 @@ const CreateNewTeamPage = () => { ); }; -const LayoutWrapper = (page: React.ReactElement) => { +export const LayoutWrapper = (page: React.ReactElement) => { return ( {page} diff --git a/apps/web/pages/signup.tsx b/apps/web/pages/signup.tsx index af52ed9894..7eae7d4463 100644 --- a/apps/web/pages/signup.tsx +++ b/apps/web/pages/signup.tsx @@ -4,21 +4,29 @@ import type { GetServerSidePropsContext } from "next"; import { signIn } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import type { CSSProperties } from "react"; import { useState, useEffect } from "react"; import type { SubmitHandler } from "react-hook-form"; import { useForm, useFormContext } from "react-hook-form"; +import { Toaster } from "react-hot-toast"; import { z } from "zod"; import getStripe from "@calcom/app-store/stripepayment/lib/client"; import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils"; +import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail"; import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; import { useFlagMap } from "@calcom/features/flags/context/provider"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { classNames } from "@calcom/lib"; -import { APP_NAME, IS_CALCOM, IS_SELF_HOSTED, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants"; +import { + APP_NAME, + URL_PROTOCOL_REGEX, + IS_CALCOM, + IS_SELF_HOSTED, + WEBAPP_URL, + WEBSITE_URL, +} from "@calcom/lib/constants"; import { fetchUsername } from "@calcom/lib/fetchUsername"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useDebounce } from "@calcom/lib/hooks/useDebounce"; @@ -28,7 +36,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils"; import type { inferSSRProps } from "@calcom/types/inferSSRProps"; -import { Button, HeadSeo, PasswordField, TextField, Form, Alert } from "@calcom/ui"; +import { Button, HeadSeo, PasswordField, TextField, Form, Alert, showToast } from "@calcom/ui"; import PageWrapper from "@components/PageWrapper"; @@ -67,28 +75,21 @@ const FEATURES = [ }, ]; -const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) => { - const [emailUser, emailDomain = ""] = email.split("@"); - const username = - emailDomain === autoAcceptEmailDomain - ? slugify(emailUser) - : slugify(`${emailUser}-${emailDomain.split(".")[0]}`); - - return username; -}; - function UsernameField({ username, setPremium, premium, setUsernameTaken, + orgSlug, usernameTaken, + disabled, ...props }: React.ComponentProps & { username: string; setPremium: (value: boolean) => void; premium: boolean; usernameTaken: boolean; + orgSlug?: string; setUsernameTaken: (value: boolean) => void; }) { const { t } = useLocale(); @@ -99,22 +100,33 @@ function UsernameField({ if (formState.isSubmitting || formState.isSubmitSuccessful) return; async function checkUsername() { + // If the username can't be changed, there is no point in doing the username availability check + if (disabled) return; if (!debouncedUsername) { setPremium(false); setUsernameTaken(false); return; } - fetchUsername(debouncedUsername).then(({ data }) => { + fetchUsername(debouncedUsername, orgSlug ?? null).then(({ data }) => { setPremium(data.premium); setUsernameTaken(!data.available); }); } checkUsername(); - }, [debouncedUsername, setPremium, setUsernameTaken, formState.isSubmitting, formState.isSubmitSuccessful]); + }, [ + debouncedUsername, + setPremium, + disabled, + orgSlug, + setUsernameTaken, + formState.isSubmitting, + formState.isSubmitSuccessful, + ]); return (
      {(!formState.isSubmitting || !formState.isSubmitted) && (
      -

      +

      {usernameTaken ? ( -
      +
      - {t("already_in_use_error")} +

      {t("already_in_use_error")}

      ) : premium ? ( -
      +
      - {t("premium_username", { - price: getPremiumPlanPriceValue(), - })} +

      + {t("premium_username", { + price: getPremiumPlanPriceValue(), + })} +

      ) : null} -

      +
      )}
      @@ -161,6 +175,7 @@ export default function Signup({ }: SignupProps) { const [premiumUsername, setPremiumUsername] = useState(false); const [usernameTaken, setUsernameTaken] = useState(false); + const [isGoogleLoading, setIsGoogleLoading] = useState(false); const searchParams = useCompatSearchParams(); const telemetry = useTelemetry(); @@ -236,30 +251,28 @@ export default function Signup({ return (
      -
      + className={classNames( + "light bg-muted 2xl:bg-default flex min-h-screen w-full flex-col items-center justify-center [--cal-brand:#111827] dark:[--cal-brand:#FFFFFF]", + "[--cal-brand-subtle:#9CA3AF]", + "[--cal-brand-text:#FFFFFF] dark:[--cal-brand-text:#000000]", + "[--cal-brand-emphasis:#101010] dark:[--cal-brand-emphasis:#e1e1e1] " + )}> +
      -
      + {/* Left side */} +
      {/* Header */} {errors.apiError && ( )} -
      -

      +
      +

      {IS_CALCOM ? t("create_your_calcom_account") : t("create_your_account")}

      {IS_CALCOM ? ( -

      {t("cal_signup_description")}

      +

      {t("cal_signup_description")}

      ) : ( -

      +

      {t("calcom_explained", { appName: APP_NAME, })} @@ -267,7 +280,7 @@ export default function Signup({ )}

      {/* Form Container */} -
      +
      {/* Username */} - setUsernameTaken(value)} - data-testid="signup-usernamefield" - setPremium={(value) => setPremiumUsername(value)} - addOnLeading={ - orgSlug - ? `${getOrgFullOrigin(orgSlug, { protocol: true })}/` - : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/` - } - /> + {!isOrgInviteByLink ? ( + setUsernameTaken(value)} + data-testid="signup-usernamefield" + setPremium={(value) => setPremiumUsername(value)} + addOnLeading={ + orgSlug + ? `${getOrgFullOrigin(orgSlug, { protocol: true }).replace(URL_PROTOCOL_REGEX, "")}/` + : `${process.env.NEXT_PUBLIC_WEBSITE_URL.replace(URL_PROTOCOL_REGEX, "")}/` + } + /> + ) : null} {/* Email */} @@ -318,6 +336,9 @@ export default function Signup({ disabled={ !!formMethods.formState.errors.username || !!formMethods.formState.errors.email || + !formMethods.getValues("email") || + !formMethods.getValues("password") || + isSubmitting || usernameTaken }> {premiumUsername && !usernameTaken @@ -325,7 +346,7 @@ export default function Signup({ : t("create_account")} - {/* Continue with Social Logins */} + {/* Continue with Social Logins - Only for non-invite links */} {token || (!isGoogleLoginEnabled && !isSAMLLoginEnabled) ? null : (
      @@ -337,36 +358,45 @@ export default function Signup({
      )} - {/* Social Logins */} + {/* Social Logins - Only for non-invite links*/} {!token && (
      {isGoogleLoginEnabled ? ( ) : null} @@ -376,7 +406,9 @@ export default function Signup({ disabled={ !!formMethods.formState.errors.username || !!formMethods.formState.errors.email || - premiumUsername + premiumUsername || + isSubmitting || + isGoogleLoading } className={classNames( "w-full justify-center rounded-md text-center", @@ -390,13 +422,18 @@ export default function Signup({ } if (!formMethods.getValues("email")) { formMethods.trigger("email"); + return; } const username = formMethods.getValues("username"); + if (!username) { + showToast("error", t("username_required")); + return; + } localStorage.setItem("username", username); const sp = new URLSearchParams(); // @NOTE: don't remove username query param as it's required right now for stripe payment page - sp.set("username", formMethods.getValues("username")); + sp.set("username", username); sp.set("email", formMethods.getValues("email")); router.push( `${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/sso/saml` + `?${sp.toString()}` @@ -410,17 +447,20 @@ export default function Signup({ )}
      {/* Already have an account & T&C */} -
      +
      - - {t("already_have_account")} - +
      +

      {t("already_have_account")}

      + + {t("sign_in")} + +
      By signing up, you agree to our{" "} - Terms of Service{" "} + Terms{" "} - and{" "} + &{" "} Privacy Policy. @@ -428,52 +468,90 @@ export default function Signup({
      -
      +
      {IS_CALCOM && ( -
      -
      - # + <> +
      +
      + Cal.com was Product of the Day at ProductHunt +
      +
      + Cal.com was Product of the Week at ProductHunt +
      +
      + Cal.com was Product of the Month at ProductHunt +
      -
      - # +
      +
      + ProductHunt Rating of 5 Stars +
      +
      + Trustpilot Rating of 4.7 Stars + Trustpilot Rating of 4.7 Stars +
      +
      + G2 Rating of 4.7 Stars +
      -
      - # -
      -
      + )}
      - # + Cal.com Booking Page
      -
      - {!IS_CALCOM && - FEATURES.map((feature) => ( - <> -
      -
      - - {t(feature.title)} -
      -
      -

      - {t( - feature.description, - feature.i18nOptions && { - ...feature.i18nOptions, - } - )} -

      -
      +
      + {FEATURES.map((feature) => ( + <> +
      +
      + + {t(feature.title)}
      - - ))} +
      +

      + {t( + feature.description, + feature.i18nOptions && { + ...feature.i18nOptions, + } + )} +

      +
      +
      + + ))}
      +
      ); } @@ -502,7 +580,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { // username + email prepopulated from query params const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query); - if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) { + if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || flags["disable-signup"]) { return { notFound: true, }; @@ -586,15 +664,17 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata), }; + const isATeamInOrganization = tokenTeam?.parentId !== null; + const isOrganization = tokenTeam.metadata?.isOrganization; // Detect if the team is an org by either the metadata flag or if it has a parent team - const isOrganization = tokenTeam.metadata?.isOrganization || tokenTeam?.parentId !== null; + const isOrganizationOrATeamInOrganization = isOrganization || isATeamInOrganization; // If we are dealing with an org, the slug may come from the team itself or its parent - const orgSlug = isOrganization + const orgSlug = isOrganizationOrATeamInOrganization ? tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug || tokenTeam.slug : null; // Org context shouldn't check if a username is premium - if (!IS_SELF_HOSTED && !isOrganization) { + if (!IS_SELF_HOSTED && !isOrganizationOrATeamInOrganization) { // Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :) const { available, suggestion } = await checkPremiumUsername(username); @@ -602,7 +682,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { } const isValidEmail = checkValidEmail(verificationToken.identifier); - const isOrgInviteByLink = isOrganization && !isValidEmail; + const isOrgInviteByLink = isOrganizationOrATeamInOrganization && !isValidEmail; const parentMetaDataForSubteam = tokenTeam?.parent?.metadata ? teamMetadataSchema.parse(tokenTeam.parent.metadata) : null; @@ -614,7 +694,14 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { prepopulateFormValues: !isOrgInviteByLink ? { email: verificationToken.identifier, - username: slugify(username), + username: isOrganizationOrATeamInOrganization + ? getOrgUsernameFromEmail( + verificationToken.identifier, + (isOrganization + ? tokenTeam.metadata?.orgAutoAcceptEmail + : parentMetaDataForSubteam?.orgAutoAcceptEmail) || "" + ) + : slugify(username), } : null, orgSlug, @@ -625,5 +712,4 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { }; }; -Signup.isThemeSupported = false; Signup.PageWrapper = PageWrapper; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 88fca5f179..a5cdaca7fa 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -11,10 +11,11 @@ import { usePathname } from "next/navigation"; import { useEffect } from "react"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; -import { orgDomainConfig, getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { WEBAPP_URL } from "@calcom/lib/constants"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; @@ -364,7 +365,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => accepted: member.accepted, organizationId: member.organizationId, safeBio: markdownToSafeHTML(member.bio || ""), - orgOrigin: getOrgFullOrigin(member.organization?.slug || ""), + bookerUrl: getBookerBaseUrlSync(member.organization?.slug || ""), }; }) : []; diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 55bbabb7c6..781f17d4b8 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -31,6 +31,7 @@ export default function Type({ isBrandingHidden, entity, duration, + isInstantMeeting, }: PageProps) { return (
      @@ -48,6 +49,7 @@ export default function Type({ eventSlug={slug} bookingData={booking} isAway={away} + isInstantMeeting={isInstantMeeting} hideBranding={isBrandingHidden} isTeamEvent entity={entity} @@ -71,7 +73,7 @@ const paramsSchema = z.object({ export const getServerSideProps = async (context: GetServerSidePropsContext) => { const session = await getServerSession(context); const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params); - const { rescheduleUid, duration: queryDuration } = context.query; + const { rescheduleUid, duration: queryDuration, isInstantMeeting: queryIsInstantMeeting } = context.query; const { ssrInit } = await import("@server/lib/ssr"); const ssr = await ssrInit(context); const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); @@ -143,6 +145,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => slug: meetingSlug, trpcState: ssr.dehydrate(), isBrandingHidden: team?.hideBranding, + isInstantMeeting: eventData.isInstantEvent && queryIsInstantMeeting ? true : false, themeBasis: null, }, }; diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx index 909973f4f2..87a49c83ac 100644 --- a/apps/web/pages/teams/index.tsx +++ b/apps/web/pages/teams/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetServerSidePropsContext } from "next"; import { getLayout } from "@calcom/features/MainLayout"; diff --git a/apps/web/pages/video/[uid].tsx b/apps/web/pages/video/[uid].tsx index 0f8340501c..49fd7e26eb 100644 --- a/apps/web/pages/video/[uid].tsx +++ b/apps/web/pages/video/[uid].tsx @@ -1,10 +1,10 @@ -import type { DailyEventObjectRecordingStarted } from "@daily-co/daily-js"; +"use client"; + import DailyIframe from "@daily-co/daily-js"; import MarkdownIt from "markdown-it"; import type { GetServerSidePropsContext } from "next"; import Head from "next/head"; import { useState, useEffect, useRef } from "react"; -import z from "zod"; import dayjs from "@calcom/dayjs"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; @@ -21,19 +21,12 @@ import PageWrapper from "@components/PageWrapper"; import { ssrInit } from "@server/lib/ssr"; -const recordingStartedEventResponse = z - .object({ - recordingId: z.string(), - }) - .passthrough(); - -export type JoinCallPageProps = inferSSRProps; +export type JoinCallPageProps = Omit, "trpcState">; const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); export default function JoinCall(props: JoinCallPageProps) { const { t } = useLocale(); const { meetingUrl, meetingPassword, booking } = props; - const recordingId = useRef(null); useEffect(() => { const callFrame = DailyIframe.createFrame({ @@ -61,31 +54,12 @@ export default function JoinCall(props: JoinCallPageProps) { ...(typeof meetingPassword === "string" && { token: meetingPassword }), }); callFrame.join(); - callFrame.on("recording-started", onRecordingStarted).on("recording-stopped", onRecordingStopped); return () => { callFrame.destroy(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const onRecordingStopped = () => { - const data = { recordingId: recordingId.current, bookingUID: booking.uid }; - - fetch("/api/recorded-daily-video", { - method: "POST", - body: JSON.stringify(data), - }).catch((err) => { - console.log(err); - }); - - recordingId.current = null; - }; - - const onRecordingStarted = (event?: DailyEventObjectRecordingStarted | undefined) => { - const response = recordingStartedEventResponse.parse(event); - recordingId.current = response.recordingId; - }; - const title = `${APP_NAME} Video`; return ( <> @@ -104,15 +78,27 @@ export default function JoinCall(props: JoinCallPageProps) {
      - Cal.com Logo + {booking?.user?.organization?.calVideoLogo ? ( + My Org Logo + ) : ( + Logo + )}
      @@ -288,6 +274,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { timeZone: true, name: true, email: true, + organization: { + select: { + calVideoLogo: true, + }, + }, }, }, references: { @@ -342,11 +333,14 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }); } + const videoReferences = bookingObj.references.filter((reference) => reference.type.includes("_video")); + const latestVideoReference = videoReferences[videoReferences.length - 1]; + return { props: { - meetingUrl: bookingObj.references[0].meetingUrl ?? "", - ...(typeof bookingObj.references[0].meetingPassword === "string" && { - meetingPassword: bookingObj.references[0].meetingPassword, + meetingUrl: latestVideoReference.meetingUrl ?? "", + ...(typeof latestVideoReference.meetingPassword === "string" && { + meetingPassword: latestVideoReference.meetingPassword, }), booking: { ...bookingObj, diff --git a/apps/web/pages/video/meeting-ended/[uid].tsx b/apps/web/pages/video/meeting-ended/[uid].tsx index b511de96bb..ce13db6233 100644 --- a/apps/web/pages/video/meeting-ended/[uid].tsx +++ b/apps/web/pages/video/meeting-ended/[uid].tsx @@ -1,3 +1,5 @@ +"use client"; + import type { NextPageContext } from "next"; import dayjs from "@calcom/dayjs"; diff --git a/apps/web/pages/video/meeting-not-started/[uid].tsx b/apps/web/pages/video/meeting-not-started/[uid].tsx index 144312ac38..26a2c50070 100644 --- a/apps/web/pages/video/meeting-not-started/[uid].tsx +++ b/apps/web/pages/video/meeting-not-started/[uid].tsx @@ -1,3 +1,5 @@ +"use client"; + import type { NextPageContext } from "next"; import dayjs from "@calcom/dayjs"; diff --git a/apps/web/pages/video/no-meeting-found.tsx b/apps/web/pages/video/no-meeting-found.tsx index 7de824c944..e3d30fe413 100644 --- a/apps/web/pages/video/no-meeting-found.tsx +++ b/apps/web/pages/video/no-meeting-found.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, EmptyScreen, HeadSeo } from "@calcom/ui"; import { X, ArrowRight } from "@calcom/ui/components/icon"; diff --git a/apps/web/playwright/ab-tests-redirect.e2e.ts b/apps/web/playwright/ab-tests-redirect.e2e.ts new file mode 100644 index 0000000000..781c2d9dad --- /dev/null +++ b/apps/web/playwright/ab-tests-redirect.e2e.ts @@ -0,0 +1,196 @@ +import { expect } from "@playwright/test"; + +import { test } from "./lib/fixtures"; + +test.describe.configure({ mode: "parallel" }); + +test.describe("apps/ A/B tests", () => { + test("should point to the /future/apps/installed/[category]", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/installed/messaging"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "Messaging" }); + + await expect(locator).toBeVisible(); + }); + + test("should point to the /future/apps/[slug]", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/telegram"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "Telegram" }); + + await expect(locator).toBeVisible(); + }); + + test("should point to the /future/apps/[slug]/setup", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/apple-calendar/setup"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "Connect to Apple Server" }); + + await expect(locator).toBeVisible(); + }); + + test("should point to the /future/apps/categories", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/categories"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByTestId("app-store-category-messaging"); + + await expect(locator).toBeVisible(); + }); + + test("should point to the /future/apps/categories/[category]", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/categories/messaging"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByText(/messaging apps/i); + + await expect(locator).toBeVisible(); + }); + + test("should point to the /future/bookings/[status]", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/bookings/upcoming/"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByTestId("horizontal-tab-upcoming"); + + await expect(locator).toHaveClass(/bg-emphasis/); + }); + + test("should point to the /future/getting-started", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create({ completedOnboarding: false, name: null }); + + await user.apiLogin(); + + await page.goto("/getting-started/connected-calendar"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByText("Apple Calendar"); + + await expect(locator).toBeVisible(); + }); +}); diff --git a/apps/web/playwright/app-store.e2e.ts b/apps/web/playwright/app-store.e2e.ts index bf6ada8561..ba1ea470b6 100644 --- a/apps/web/playwright/app-store.e2e.ts +++ b/apps/web/playwright/app-store.e2e.ts @@ -8,6 +8,33 @@ test.describe.configure({ mode: "parallel" }); test.afterEach(({ users }) => users.deleteAll()); test.describe("App Store - Authed", () => { + test("should point to the /future/apps/", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "App Store" }); + + await expect(locator).toBeVisible(); + }); + test("Browse apple-calendar and try to install", async ({ page, users }) => { const pro = await users.create(); await pro.apiLogin(); diff --git a/apps/web/playwright/booking-limits.e2e.ts b/apps/web/playwright/booking-limits.e2e.ts new file mode 100644 index 0000000000..6e728998bc --- /dev/null +++ b/apps/web/playwright/booking-limits.e2e.ts @@ -0,0 +1,420 @@ +/** + * These e2e tests only aim to cover standard cases + * Edge cases are currently handled in integration tests only + */ +import { expect } from "@playwright/test"; + +import type { Dayjs } from "@calcom/dayjs"; +import dayjs from "@calcom/dayjs"; +import { intervalLimitKeyToUnit } from "@calcom/lib/intervalLimit"; +import { entries } from "@calcom/prisma/zod-utils"; +import type { IntervalLimit } from "@calcom/types/Calendar"; + +import { test } from "./lib/fixtures"; +import { bookTimeSlot, createUserWithLimits } from "./lib/testUtils"; + +test.describe.configure({ mode: "parallel" }); +test.afterEach(async ({ users }) => { + await users.deleteAll(); +}); + +// used as a multiplier for duration limits +const EVENT_LENGTH = 30; + +// limits used when testing each limit seperately +const BOOKING_LIMITS_SINGLE = { + PER_DAY: 2, + PER_WEEK: 2, + PER_MONTH: 2, + PER_YEAR: 2, +}; + +// limits used when testing multiple limits together +const BOOKING_LIMITS_MULTIPLE = { + PER_DAY: 1, + PER_WEEK: 2, + PER_MONTH: 3, + PER_YEAR: 4, +}; + +// prevent tests from crossing year boundaries - if currently in Oct or later, start booking in Jan instead of Nov +// (we increment months twice when checking multiple limits) +const firstDayInBookingMonth = + dayjs().month() >= 9 ? dayjs().add(1, "year").month(0).date(1) : dayjs().add(1, "month").date(1); + +// avoid weekly edge cases +const firstMondayInBookingMonth = firstDayInBookingMonth.day( + firstDayInBookingMonth.date() === firstDayInBookingMonth.startOf("week").date() ? 1 : 8 +); + +// ensure we land on the same weekday when incrementing month +const incrementDate = (date: Dayjs, unit: dayjs.ManipulateType) => { + if (unit !== "month") return date.add(1, unit); + return date.add(1, "month").day(date.day()); +}; + +const getLastEventUrlWithMonth = (user: Awaited>, date: Dayjs) => { + return `/${user.username}/${user.eventTypes.at(-1)?.slug}?month=${date.format("YYYY-MM")}`; +}; + +test.describe("Booking limits", () => { + entries(BOOKING_LIMITS_SINGLE).forEach(([limitKey, bookingLimit]) => { + const limitUnit = intervalLimitKeyToUnit(limitKey); + + // test one limit at a time + test(limitUnit, async ({ page, users }) => { + const slug = `booking-limit-${limitUnit}`; + const singleLimit = { [limitKey]: bookingLimit }; + + const user = await createUserWithLimits({ + users, + slug, + length: EVENT_LENGTH, + bookingLimits: singleLimit, + }); + + let slotUrl = ""; + + const monthUrl = getLastEventUrlWithMonth(user, firstMondayInBookingMonth); + await page.goto(monthUrl); + + const availableDays = page.locator('[data-testid="day"][data-disabled="false"]'); + const bookingDay = availableDays.getByText(firstMondayInBookingMonth.date().toString(), { + exact: true, + }); + + // finish rendering days before counting + await expect(bookingDay).toBeVisible({ timeout: 10_000 }); + const availableDaysBefore = await availableDays.count(); + + await test.step("can book up to limit", async () => { + for (let i = 0; i < bookingLimit; i++) { + await bookingDay.click(); + + await page.getByTestId("time").nth(0).click(); + await bookTimeSlot(page); + + slotUrl = page.url(); + + await expect(page.getByTestId("success-page")).toBeVisible(); + + await page.goto(monthUrl); + } + }); + + const expectedAvailableDays = { + day: -1, + week: -5, + month: 0, + year: 0, + }; + + await test.step("but not over", async () => { + // should already have navigated to monthUrl - just ensure days are rendered + await expect(page.getByTestId("day").nth(0)).toBeVisible(); + + // ensure the day we just booked is now blocked + await expect(bookingDay).toBeHidden({ timeout: 10_000 }); + + const availableDaysAfter = await availableDays.count(); + + // equals 0 if no available days, otherwise signed difference + expect(availableDaysAfter && availableDaysAfter - availableDaysBefore).toBe( + expectedAvailableDays[limitUnit] + ); + + // try to book directly via form page + await page.goto(slotUrl); + await bookTimeSlot(page); + + await expect(page.getByTestId("booking-fail")).toBeVisible({ timeout: 1000 }); + }); + + await test.step(`month after booking`, async () => { + await page.goto(getLastEventUrlWithMonth(user, firstMondayInBookingMonth.add(1, "month"))); + + // finish rendering days before counting + await expect(page.getByTestId("day").nth(0)).toBeVisible({ timeout: 10_000 }); + + // the month after we made bookings should have availability unless we hit a yearly limit + await expect((await availableDays.count()) === 0).toBe(limitUnit === "year"); + }); + }); + }); + + test("multiple", async ({ page, users }) => { + const slug = "booking-limit-multiple"; + + const user = await createUserWithLimits({ + users, + slug, + length: EVENT_LENGTH, + bookingLimits: BOOKING_LIMITS_MULTIPLE, + }); + + let slotUrl = ""; + + let bookingDate = firstMondayInBookingMonth; + + // keep track of total bookings across multiple limits + let bookingCount = 0; + + for (const [limitKey, limitValue] of entries(BOOKING_LIMITS_MULTIPLE)) { + const limitUnit = intervalLimitKeyToUnit(limitKey); + + const monthUrl = getLastEventUrlWithMonth(user, bookingDate); + await page.goto(monthUrl); + + const availableDays = page.locator('[data-testid="day"][data-disabled="false"]'); + const bookingDay = availableDays.getByText(bookingDate.date().toString(), { exact: true }); + + // finish rendering days before counting + await expect(bookingDay).toBeVisible({ timeout: 10_000 }); + + const availableDaysBefore = await availableDays.count(); + + await test.step(`can book up ${limitUnit} to limit`, async () => { + for (let i = 0; i + bookingCount < limitValue; i++) { + await bookingDay.click(); + + await page.getByTestId("time").nth(0).click(); + await bookTimeSlot(page); + bookingCount++; + + slotUrl = page.url(); + + await expect(page.getByTestId("success-page")).toBeVisible(); + + await page.goto(monthUrl); + } + }); + + const expectedAvailableDays = { + day: -1, + week: -4, // one day will already be blocked by daily limit + month: 0, + year: 0, + }; + + await test.step("but not over", async () => { + // should already have navigated to monthUrl - just ensure days are rendered + await expect(page.getByTestId("day").nth(0)).toBeVisible(); + + // ensure the day we just booked is now blocked + await expect(bookingDay).toBeHidden({ timeout: 10_000 }); + + const availableDaysAfter = await availableDays.count(); + + // equals 0 if no available days, otherwise signed difference + expect(availableDaysAfter && availableDaysAfter - availableDaysBefore).toBe( + expectedAvailableDays[limitUnit] + ); + + // try to book directly via form page + await page.goto(slotUrl); + await bookTimeSlot(page); + + await expect(page.getByTestId("booking-fail")).toBeVisible({ timeout: 5000 }); + }); + + await test.step(`month after booking`, async () => { + await page.goto(getLastEventUrlWithMonth(user, bookingDate.add(1, "month"))); + + // finish rendering days before counting + await expect(page.getByTestId("day").nth(0)).toBeVisible({ timeout: 10_000 }); + + // the month after we made bookings should have availability unless we hit a yearly limit + // TODO: Temporary fix for failing test. It passes locally but fails on CI. + // See #13097 + // await expect((await availableDays.count()) === 0).toBe(limitUnit === "year"); + }); + + // increment date by unit after hitting each limit + bookingDate = incrementDate(bookingDate, limitUnit); + } + }); +}); + +test.describe("Duration limits", () => { + entries(BOOKING_LIMITS_SINGLE).forEach(([limitKey, bookingLimit]) => { + const limitUnit = intervalLimitKeyToUnit(limitKey); + + // test one limit at a time + test(limitUnit, async ({ page, users }) => { + const slug = `duration-limit-${limitUnit}`; + const singleLimit = { [limitKey]: bookingLimit * EVENT_LENGTH }; + + const user = await createUserWithLimits({ + users, + slug, + length: EVENT_LENGTH, + durationLimits: singleLimit, + }); + + let slotUrl = ""; + + const monthUrl = getLastEventUrlWithMonth(user, firstMondayInBookingMonth); + await page.goto(monthUrl); + + const availableDays = page.locator('[data-testid="day"][data-disabled="false"]'); + const bookingDay = availableDays.getByText(firstMondayInBookingMonth.date().toString(), { + exact: true, + }); + + // finish rendering days before counting + await expect(bookingDay).toBeVisible({ timeout: 10_000 }); + const availableDaysBefore = await availableDays.count(); + + await test.step("can book up to limit", async () => { + for (let i = 0; i < bookingLimit; i++) { + await bookingDay.click(); + + await page.getByTestId("time").nth(0).click(); + await bookTimeSlot(page); + + slotUrl = page.url(); + + await expect(page.getByTestId("success-page")).toBeVisible(); + + await page.goto(monthUrl); + } + }); + + const expectedAvailableDays = { + day: -1, + week: -5, + month: 0, + year: 0, + }; + + await test.step("but not over", async () => { + // should already have navigated to monthUrl - just ensure days are rendered + await expect(page.getByTestId("day").nth(0)).toBeVisible(); + + // ensure the day we just booked is now blocked + await expect(bookingDay).toBeHidden({ timeout: 10_000 }); + + const availableDaysAfter = await availableDays.count(); + + // equals 0 if no available days, otherwise signed difference + expect(availableDaysAfter && availableDaysAfter - availableDaysBefore).toBe( + expectedAvailableDays[limitUnit] + ); + + // try to book directly via form page + await page.goto(slotUrl); + await bookTimeSlot(page); + + await expect(page.getByTestId("booking-fail")).toBeVisible({ timeout: 1000 }); + }); + + await test.step(`month after booking`, async () => { + await page.goto(getLastEventUrlWithMonth(user, firstMondayInBookingMonth.add(1, "month"))); + + // finish rendering days before counting + await expect(page.getByTestId("day").nth(0)).toBeVisible({ timeout: 10_000 }); + + // the month after we made bookings should have availability unless we hit a yearly limit + await expect((await availableDays.count()) === 0).toBe(limitUnit === "year"); + }); + }); + }); + + test("multiple", async ({ page, users }) => { + const slug = "duration-limit-multiple"; + + // multiply all booking limits by EVENT_LENGTH + const durationLimits = entries(BOOKING_LIMITS_MULTIPLE).reduce((limits, [limitKey, bookingLimit]) => { + return { + ...limits, + [limitKey]: bookingLimit * EVENT_LENGTH, + }; + }, {} as Record); + + const user = await createUserWithLimits({ + users, + slug, + length: EVENT_LENGTH, + durationLimits, + }); + + let slotUrl = ""; + + let bookingDate = firstMondayInBookingMonth; + + // keep track of total bookings across multiple limits + let bookingCount = 0; + + for (const [limitKey, limitValue] of entries(BOOKING_LIMITS_MULTIPLE)) { + const limitUnit = intervalLimitKeyToUnit(limitKey); + + const monthUrl = getLastEventUrlWithMonth(user, bookingDate); + await page.goto(monthUrl); + + const availableDays = page.locator('[data-testid="day"][data-disabled="false"]'); + const bookingDay = availableDays.getByText(bookingDate.date().toString(), { exact: true }); + + // finish rendering days before counting + await expect(bookingDay).toBeVisible({ timeout: 10_000 }); + + const availableDaysBefore = await availableDays.count(); + + await test.step(`can book up ${limitUnit} to limit`, async () => { + for (let i = 0; i + bookingCount < limitValue; i++) { + await bookingDay.click(); + + await page.getByTestId("time").nth(0).click(); + await bookTimeSlot(page); + bookingCount++; + + slotUrl = page.url(); + + await expect(page.getByTestId("success-page")).toBeVisible(); + + await page.goto(monthUrl); + } + }); + + const expectedAvailableDays = { + day: -1, + week: -4, // one day will already be blocked by daily limit + month: 0, + year: 0, + }; + + await test.step("but not over", async () => { + // should already have navigated to monthUrl - just ensure days are rendered + await expect(page.getByTestId("day").nth(0)).toBeVisible(); + + // ensure the day we just booked is now blocked + await expect(bookingDay).toBeHidden({ timeout: 10_000 }); + + const availableDaysAfter = await availableDays.count(); + + // equals 0 if no available days, otherwise signed difference + expect(availableDaysAfter && availableDaysAfter - availableDaysBefore).toBe( + expectedAvailableDays[limitUnit] + ); + + // try to book directly via form page + await page.goto(slotUrl); + await bookTimeSlot(page); + + await expect(page.getByTestId("booking-fail")).toBeVisible({ timeout: 1000 }); + }); + + await test.step(`month after booking`, async () => { + await page.goto(getLastEventUrlWithMonth(user, bookingDate.add(1, "month"))); + + // finish rendering days before counting + await expect(page.getByTestId("day").nth(0)).toBeVisible({ timeout: 10_000 }); + + // the month after we made bookings should have availability unless we hit a yearly limit + await expect((await availableDays.count()) === 0).toBe(limitUnit === "year"); + }); + + // increment date by unit after hitting each limit + bookingDate = incrementDate(bookingDate, limitUnit); + } + }); +}); diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index 462d1938e6..a4733ab2a4 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -29,7 +29,10 @@ test.describe("free user", () => { test("cannot book same slot multiple times", async ({ page, users, emails }) => { const [user] = users.get(); - const bookerObj = { email: `testEmail-${randomString(4)}@example.com`, name: "testBooker" }; + const bookerObj = { + email: users.trackEmail({ username: "testEmail", domain: "example.com" }), + name: "testBooker", + }; // Click first event type await page.click('[data-testid="event-type-link"]'); @@ -126,6 +129,34 @@ test.describe("pro user", () => { await bookFirstEvent(page); }); + test("Can cancel the recently created booking and shouldn't be allowed to reschedule it", async ({ + page, + users, + }, testInfo) => { + // Because it tests the entire booking flow + the cancellation + rebooking + test.setTimeout(testInfo.timeout * 3); + await bookFirstEvent(page); + await expect(page.locator(`[data-testid="attendee-email-${testEmail}"]`)).toHaveText(testEmail); + await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); + + const [pro] = users.get(); + await pro.apiLogin(); + + await page.goto("/bookings/upcoming"); + await page.locator('[data-testid="cancel"]').click(); + await page.waitForURL((url) => { + return url.pathname.startsWith("/booking/"); + }); + await page.locator('[data-testid="confirm_cancel"]').click(); + + const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]'); + await expect(cancelledHeadline).toBeVisible(); + const bookingCancelledId = new URL(page.url()).pathname.split("/booking/")[1]; + await page.goto(`/reschedule/${bookingCancelledId}`); + // Should be redirected to the booking details page which shows the cancelled headline + await expect(page.locator('[data-testid="cancelled-headline"]')).toBeVisible(); + }); + test("can book an event that requires confirmation and then that booking can be accepted by organizer", async ({ page, users, @@ -365,7 +396,7 @@ test.describe("Booking round robin event", () => { teammates: teamMatesObj, } ); - const team = await testUser.getFirstTeam(); + const team = await testUser.getFirstTeamMembership(); await page.goto(`/team/${team.team.slug}`); }); @@ -373,7 +404,7 @@ test.describe("Booking round robin event", () => { const [testUser] = users.get(); testUser.apiLogin(); - const team = await testUser.getFirstTeam(); + const team = await testUser.getFirstTeamMembership(); // Click first event type (round robin) await page.click('[data-testid="event-type-link"]'); diff --git a/apps/web/playwright/booking/recurringBooking.e2e.ts b/apps/web/playwright/booking/recurringBooking.e2e.ts new file mode 100644 index 0000000000..5f89ef9849 --- /dev/null +++ b/apps/web/playwright/booking/recurringBooking.e2e.ts @@ -0,0 +1,28 @@ +/* eslint-disable playwright/no-conditional-in-test */ +import { loginUser } from "../fixtures/regularBookings"; +import { test } from "../lib/fixtures"; + +test.describe.configure({ mode: "serial" }); + +test.describe("Booking with recurring checked", () => { + test.beforeEach(async ({ page, users, bookingPage }) => { + await loginUser(users); + await page.goto("/event-types"); + await bookingPage.goToEventType("30 min"); + await bookingPage.goToTab("recurring"); + }); + + test("Updates event type with recurring events", async ({ page, bookingPage }) => { + await bookingPage.updateRecurringTab("2", "3"); + await bookingPage.updateEventType(); + await page.getByRole("link", { name: "Event Types" }).click(); + await bookingPage.assertRepeatEventType(); + }); + + test("Updates and shows recurring schedule correctly in booking page", async ({ bookingPage }) => { + await bookingPage.updateRecurringTab("2", "3"); + await bookingPage.updateEventType(); + const eventTypePage = await bookingPage.previewEventType(); + await bookingPage.fillRecurringFieldAndConfirm(eventTypePage); + }); +}); diff --git a/apps/web/playwright/dynamic-booking-pages.e2e.ts b/apps/web/playwright/dynamic-booking-pages.e2e.ts index f41fe4c91b..bf30c2bd01 100644 --- a/apps/web/playwright/dynamic-booking-pages.e2e.ts +++ b/apps/web/playwright/dynamic-booking-pages.e2e.ts @@ -1,8 +1,11 @@ import { expect } from "@playwright/test"; +import { MembershipRole } from "@calcom/prisma/client"; + import { test } from "./lib/fixtures"; import { bookTimeSlot, + doOnOrgDomain, selectFirstAvailableTimeSlotNextMonth, selectSecondAvailableTimeSlotNextMonth, } from "./lib/testUtils"; @@ -58,3 +61,46 @@ test("dynamic booking", async ({ page, users }) => { await expect(cancelledHeadline).toBeVisible(); }); }); + +test.describe("Organization:", () => { + test.afterEach(({ orgs, users }) => { + orgs.deleteAll(); + users.deleteAll(); + }); + test("Can book a time slot for an organization", async ({ page, users, orgs }) => { + const org = await orgs.create({ + name: "TestOrg", + }); + + const user1 = await users.create({ + organizationId: org.id, + name: "User 1", + roleInOrganization: MembershipRole.ADMIN, + }); + + const user2 = await users.create({ + organizationId: org.id, + name: "User 2", + roleInOrganization: MembershipRole.ADMIN, + }); + await doOnOrgDomain( + { + orgSlug: org.slug, + page, + }, + async () => { + await page.goto(`/${user1.username}+${user2.username}`); + await selectFirstAvailableTimeSlotNextMonth(page); + await bookTimeSlot(page, { + title: "Test meeting", + }); + await expect(page.getByTestId("success-page")).toBeVisible(); + // All the teammates should be in the booking + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await expect(page.getByText(user1.name!, { exact: true })).toBeVisible(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await expect(page.getByText(user2.name!, { exact: true })).toBeVisible(); + } + ); + }); +}); diff --git a/apps/web/playwright/fixtures/bookings.ts b/apps/web/playwright/fixtures/bookings.ts index 56cbd1002f..ad2e6f072c 100644 --- a/apps/web/playwright/fixtures/bookings.ts +++ b/apps/web/playwright/fixtures/bookings.ts @@ -62,6 +62,7 @@ export const createBookingsFixture = (page: Page) => { rescheduled, paid, status, + iCalUID: `${uid}@cal.com`, }, }); const bookingFixture = createBookingFixture(booking, store.page); diff --git a/apps/web/playwright/fixtures/emails.ts b/apps/web/playwright/fixtures/emails.ts new file mode 100644 index 0000000000..8d02dacb2f --- /dev/null +++ b/apps/web/playwright/fixtures/emails.ts @@ -0,0 +1,34 @@ +import mailhog from "mailhog"; + +import { IS_MAILHOG_ENABLED } from "@calcom/lib/constants"; + +const unimplemented = () => { + throw new Error("Mailhog is not enabled"); +}; + +const hasUUID = (query: string) => { + return /[a-zA-Z0-9]{22}/.test(query) || /[0-9a-f]{8}/.test(query); +}; +export const createEmailsFixture = () => { + if (IS_MAILHOG_ENABLED) { + const mailhogAPI = mailhog(); + return { + search: (query: string, kind?: string, start?: number, limit?: number) => { + if (kind === "from" || kind === "to") { + if (!hasUUID(query)) { + throw new Error( + `You should not use "from" or "to" queries without UUID in emails. Because mailhog maintains all the emails sent through tests, you should be able to uniquely identify the email among those. Found query: ${query}` + ); + } + } + return mailhogAPI.search.bind(mailhogAPI)(query, kind, start, limit); + }, + deleteMessage: mailhogAPI.deleteMessage.bind(mailhogAPI), + }; + } else { + return { + search: unimplemented, + deleteMessage: unimplemented, + }; + } +}; diff --git a/apps/web/playwright/fixtures/regularBookings.ts b/apps/web/playwright/fixtures/regularBookings.ts index eb31599cfd..51f344a87a 100644 --- a/apps/web/playwright/fixtures/regularBookings.ts +++ b/apps/web/playwright/fixtures/regularBookings.ts @@ -237,6 +237,23 @@ export function createBookingPageFixture(page: Page) { } await page.getByTestId("field-add-save").click(); }, + updateRecurringTab: async (repeatWeek: string, maxEvents: string) => { + const repeatText = (await localize("en"))("repeats_every"); + const maximumOf = (await localize("en"))("for_a_maximum_of"); + await page.getByTestId("recurring-event-check").click(); + await page + .getByTestId("recurring-event-collapsible") + .locator("div") + .filter({ hasText: repeatText }) + .getByRole("spinbutton") + .fill(repeatWeek); + await page + .getByTestId("recurring-event-collapsible") + .locator("div") + .filter({ hasText: maximumOf }) + .getByRole("spinbutton") + .fill(maxEvents); + }, updateEventType: async (options?: { shouldCheck: boolean; name: string }) => { await page.getByTestId("update-eventtype").click(); options?.shouldCheck && @@ -267,6 +284,14 @@ export function createBookingPageFixture(page: Page) { await page.getByTestId("confirm-reschedule-button").click(); }, + fillRecurringFieldAndConfirm: async (eventTypePage: Page) => { + await eventTypePage.getByTestId("occurrence-input").click(); + await eventTypePage.getByTestId("occurrence-input").fill("2"); + await goToNextMonthIfNoAvailabilities(eventTypePage); + await eventTypePage.getByTestId("time").first().click(); + await expect(eventTypePage.getByTestId("recurring-dates")).toBeVisible(); + }, + cancelBookingWithReason: async (page: Page) => { await page.getByTestId("cancel").click(); await page.getByTestId("cancel_reason").fill("Test cancel"); @@ -292,6 +317,10 @@ export function createBookingPageFixture(page: Page) { await expect(page.getByText(scheduleSuccessfullyText)).toBeVisible(); }, + assertRepeatEventType: async () => { + await expect(page.getByTestId("repeat-eventtype")).toBeVisible(); + }, + cancelBooking: async (eventTypePage: Page) => { await eventTypePage.getByTestId("cancel").click(); await eventTypePage.getByTestId("cancel_reason").fill("Test cancel"); diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts index 16b362c2fa..f6b0992ffe 100644 --- a/apps/web/playwright/fixtures/users.ts +++ b/apps/web/playwright/fixtures/users.ts @@ -1,8 +1,9 @@ import type { Page, WorkerInfo } from "@playwright/test"; import type Prisma from "@prisma/client"; +import type { Team } from "@prisma/client"; import { Prisma as PrismaType } from "@prisma/client"; import { hashSync as hash } from "bcryptjs"; -import type { API } from "mailhog"; +import { uuid } from "short-uuid"; import stripe from "@calcom/features/ee/payments/server/stripe"; import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; @@ -13,6 +14,7 @@ import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import type { Schedule } from "@calcom/types/schedule"; import { selectFirstAvailableTimeSlotNextMonth, teamEventSlug, teamEventTitle } from "../lib/testUtils"; +import type { createEmailsFixture } from "./emails"; import { TimeZoneEnum } from "./types"; // Don't import hashPassword from app as that ends up importing next-auth and initializing it before NEXTAUTH_URL can be updated during tests. @@ -101,7 +103,7 @@ const createTeamAndAddUser = async ( ) => { const slug = `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}`; const data: PrismaType.TeamCreateInput = { - name: `user-id-${user.id}'s Team ${isOrg ? "Org" : "Team"}`, + name: `user-id-${user.id}'s ${isOrg ? "Org" : "Team"}`, }; data.metadata = { ...(isUnpublished ? { requestedSlug: slug } : {}), @@ -140,8 +142,17 @@ const createTeamAndAddUser = async ( }; // creates a user fixture instance and stores the collection -export const createUsersFixture = (page: Page, emails: API | undefined, workerInfo: WorkerInfo) => { - const store = { users: [], page } as { users: UserFixture[]; page: typeof page }; +export const createUsersFixture = ( + page: Page, + emails: ReturnType, + workerInfo: WorkerInfo +) => { + const store = { users: [], trackedEmails: [], page, teams: [] } as { + users: UserFixture[]; + trackedEmails: { email: string }[]; + page: typeof page; + teams: Team[]; + }; return { buildForSignup: (opts?: Pick) => { const uname = @@ -322,6 +333,7 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn }, workerInfo ); + store.teams.push(team); const teamEvent = await createTeamEventType(user, team, scenario); if (scenario.teammates) { // Create Teammate users @@ -379,6 +391,16 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn store.users.push(userFixture); return userFixture; }, + /** + * Use this method to get an email that can be automatically cleaned up from all the places in DB + */ + trackEmail: ({ username, domain }: { username: string; domain: string }) => { + const email = `${username}-${uuid().substring(0, 8)}@${domain}`; + store.trackedEmails.push({ + email, + }); + return email; + }, get: () => store.users, logout: async () => { await page.goto("/auth/logout"); @@ -387,7 +409,7 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn const ids = store.users.map((u) => u.id); if (emails) { const emailMessageIds: string[] = []; - for (const user of store.users) { + for (const user of store.trackedEmails.concat(store.users.map((u) => ({ email: u.email })))) { const emailMessages = await emails.search(user.email); if (emailMessages && emailMessages.count > 0) { emailMessages.items.forEach((item) => { @@ -401,7 +423,12 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn } await prisma.user.deleteMany({ where: { id: { in: ids } } }); + // Delete all users that were tracked by email(if they were created) + await prisma.user.deleteMany({ where: { email: { in: store.trackedEmails.map((e) => e.email) } } }); + await prisma.team.deleteMany({ where: { id: { in: store.teams.map((org) => org.id) } } }); store.users = []; + store.teams = []; + store.trackedEmails = []; }, delete: async (id: number) => { await prisma.user.delete({ where: { id } }); @@ -458,7 +485,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => { logout: async () => { await page.goto("/auth/logout"); }, - getFirstTeam: async () => { + getFirstTeamMembership: async () => { const memberships = await prisma.membership.findMany({ where: { userId: user.id }, include: { team: true }, @@ -491,13 +518,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => { }, }, }, - include: { - team: { - include: { - children: true, - }, - }, - }, + include: { team: { include: { children: true } } }, }); }, getFirstEventAsOwner: async () => @@ -514,8 +535,8 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => { }); }, getPaymentCredential: async () => getPaymentCredential(store.page), - setupEventWithPrice: async (eventType: Pick) => - setupEventWithPrice(eventType, store.page), + setupEventWithPrice: async (eventType: Pick, slug: string) => + setupEventWithPrice(eventType, slug, store.page), bookAndPayEvent: async (eventType: Pick) => bookAndPayEvent(user, eventType, store.page), makePaymentUsingStripe: async () => makePaymentUsingStripe(store.page), @@ -541,7 +562,8 @@ type CustomUserOptsKeys = | "locale" | "name" | "email" - | "organizationId"; + | "organizationId" + | "role"; type CustomUserOpts = Partial> & { timeZone?: TimeZoneEnum; eventTypes?: SupportedTestEventTypes[]; @@ -571,6 +593,7 @@ const createUser = ( completedOnboarding: opts?.completedOnboarding ?? true, timeZone: opts?.timeZone ?? TimeZoneEnum.UK, locale: opts?.locale ?? "en", + role: opts?.role ?? "USER", ...getOrganizationRelatedProps({ organizationId: opts?.organizationId, role: opts?.roleInOrganization }), schedules: opts?.completedOnboarding ?? true @@ -673,8 +696,8 @@ export async function login( await passwordLocator.fill(user.password ?? user.username!); await signInLocator.click(); - // Moving away from waiting 2 seconds, as it is not a reliable way to expect session to be started - await page.waitForLoadState("networkidle"); + // waiting for specific login request to resolve + await page.waitForResponse(/\/api\/auth\/callback\/credentials/); } export async function apiLogin( @@ -699,9 +722,9 @@ export async function apiLogin( }); } -export async function setupEventWithPrice(eventType: Pick, page: Page) { +export async function setupEventWithPrice(eventType: Pick, slug: string, page: Page) { await page.goto(`/event-types/${eventType?.id}?tabName=apps`); - await page.locator("[data-testid='app-switch']").first().click(); + await page.locator(`[data-testid='${slug}-app-switch']`).first().click(); await page.getByPlaceholder("Price").fill("100"); await page.getByTestId("update-eventtype").click(); } @@ -728,7 +751,7 @@ export async function makePaymentUsingStripe(page: Page) { const stripeFrame = stripeElement.frameLocator("iframe").first(); await stripeFrame.locator('[name="number"]').fill("4242 4242 4242 4242"); const now = new Date(); - await stripeFrame.locator('[name="expiry"]').fill(`${now.getMonth()} / ${now.getFullYear() + 1}`); + await stripeFrame.locator('[name="expiry"]').fill(`${now.getMonth() + 1} / ${now.getFullYear() + 1}`); await stripeFrame.locator('[name="cvc"]').fill("111"); const postcalCodeIsVisible = await stripeFrame.locator('[name="postalCode"]').isVisible(); if (postcalCodeIsVisible) { diff --git a/apps/web/playwright/integrations-stripe.e2e.ts b/apps/web/playwright/integrations-stripe.e2e.ts index c9d86ccf0e..1d76db2699 100644 --- a/apps/web/playwright/integrations-stripe.e2e.ts +++ b/apps/web/playwright/integrations-stripe.e2e.ts @@ -46,7 +46,7 @@ test.describe("Stripe integration", () => { await user.getPaymentCredential(); const eventType = user.eventTypes.find((e) => e.slug === "paid") as Prisma.EventType; - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); // Need to wait for the DB to be updated with the metadata await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); @@ -84,7 +84,7 @@ test.describe("Stripe integration", () => { schedulingType: SchedulingType.COLLECTIVE, }); await owner.apiLogin(); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); const teamEvent = await owner.getFirstTeamEvent(team.id); @@ -104,7 +104,7 @@ test.describe("Stripe integration", () => { page.click('[id="skip-account-app"]'), ]); - await owner.setupEventWithPrice(teamEvent); + await owner.setupEventWithPrice(teamEvent, "stripe"); // Need to wait for the DB to be updated with the metadata await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); @@ -134,7 +134,7 @@ test.describe("Stripe integration", () => { await page.goto("/apps/installed"); await user.getPaymentCredential(); - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); await user.bookAndPayEvent(eventType); // success await expect(page.locator("[data-testid=success-page]")).toBeVisible(); @@ -147,7 +147,7 @@ test.describe("Stripe integration", () => { await page.goto("/apps/installed"); await user.getPaymentCredential(); - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); // booking process without payment await page.goto(`${user.username}/${eventType?.slug}`); @@ -171,7 +171,7 @@ test.describe("Stripe integration", () => { await page.goto("/apps/installed"); await user.getPaymentCredential(); - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); await user.bookAndPayEvent(eventType); // Rescheduling the event @@ -194,7 +194,7 @@ test.describe("Stripe integration", () => { await page.goto("/apps/installed"); await user.getPaymentCredential(); - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); await user.bookAndPayEvent(eventType); await page.click('[data-testid="cancel"]'); @@ -214,7 +214,7 @@ test.describe("Stripe integration", () => { await page.goto("/apps/installed"); await user.getPaymentCredential(); - await user.setupEventWithPrice(eventType); + await user.setupEventWithPrice(eventType, "stripe"); await user.bookAndPayEvent(eventType); await user.confirmPendingPayment(); }); @@ -264,7 +264,7 @@ test.describe("Stripe integration", () => { await page.locator("#event-type-form").getByRole("switch").click(); // Set price - await page.getByTestId("price-input-stripe").fill("200"); + await page.getByTestId("stripe-price-input").fill("200"); // Select currency in dropdown await page.getByTestId("stripe-currency-select").click(); diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts index 0d7f34879a..1b7cb5b3c0 100644 --- a/apps/web/playwright/lib/fixtures.ts +++ b/apps/web/playwright/lib/fixtures.ts @@ -1,14 +1,11 @@ import type { Page } from "@playwright/test"; import { test as base } from "@playwright/test"; -import type { API } from "mailhog"; -import mailhog from "mailhog"; -import { IS_MAILHOG_ENABLED } from "@calcom/lib/constants"; -import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; import type { ExpectedUrlDetails } from "../../../../playwright.config"; import { createBookingsFixture } from "../fixtures/bookings"; +import { createEmailsFixture } from "../fixtures/emails"; import { createEmbedsFixture } from "../fixtures/embeds"; import { createFeatureFixture } from "../fixtures/features"; import { createOrgsFixture } from "../fixtures/orgs"; @@ -27,7 +24,7 @@ export interface Fixtures { embeds: ReturnType; servers: ReturnType; prisma: typeof prisma; - emails?: API; + emails: ReturnType; routingForms: ReturnType; bookingPage: ReturnType; features: ReturnType; @@ -84,14 +81,7 @@ export const test = base.extend({ await use(createRoutingFormsFixture()); }, emails: async ({}, use) => { - if (IS_MAILHOG_ENABLED) { - const mailhogAPI = mailhog(); - await use(mailhogAPI); - } else { - //FIXME: Ideally we should error out here. If someone is running tests with mailhog disabled, they should be aware of it - logger.warn("Mailhog is not enabled - Skipping Emails verification"); - await use(undefined); - } + await use(createEmailsFixture()); }, bookingPage: async ({ page }, use) => { const bookingPage = createBookingPageFixture(page); diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 5bb711d71a..2de5bca29f 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -6,12 +6,14 @@ import type { IncomingMessage, ServerResponse } from "http"; import { createServer } from "http"; // eslint-disable-next-line no-restricted-imports import { noop } from "lodash"; -import type { API, Messages } from "mailhog"; +import type { Messages } from "mailhog"; import { totp } from "otplib"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; +import type { IntervalLimit } from "@calcom/types/Calendar"; +import type { createEmailsFixture } from "../fixtures/emails"; import type { Fixtures } from "./fixtures"; import { test } from "./fixtures"; @@ -133,12 +135,16 @@ export async function bookFirstEvent(page: Page) { await bookEventOnThisPage(page); } -export const bookTimeSlot = async (page: Page, opts?: { name?: string; email?: string }) => { +export const bookTimeSlot = async (page: Page, opts?: { name?: string; email?: string; title?: string }) => { // --- fill form await page.fill('[name="name"]', opts?.name ?? testName); await page.fill('[name="email"]', opts?.email ?? testEmail); + if (opts?.title) { + await page.fill('[name="title"]', opts.title); + } await page.press('[name="email"]', "Enter"); }; + // Provide an standalone localize utility not managed by next-i18n export async function localize(locale: string) { const localeModule = `../../public/static/locales/${locale}/common.json`; @@ -213,11 +219,15 @@ export async function getEmailsReceivedByUser({ emails, userEmail, }: { - emails?: API; + emails?: ReturnType; userEmail: string; }): Promise { if (!emails) return null; - return emails.search(userEmail, "to"); + const matchingEmails = await emails.search(userEmail, "to"); + if (!matchingEmails?.total) { + console.log(`No emails received by ${userEmail}`); + } + return matchingEmails; } export async function expectEmailsToHaveSubject({ @@ -226,7 +236,7 @@ export async function expectEmailsToHaveSubject({ booker, eventTitle, }: { - emails?: API; + emails?: ReturnType; organizer: { name?: string | null; email: string }; booker: { name: string; email: string }; eventTitle: string; @@ -246,6 +256,38 @@ export async function expectEmailsToHaveSubject({ expect(bookerFirstEmail.subject).toBe(emailSubject); } +export const createUserWithLimits = ({ + users, + slug, + title, + length, + bookingLimits, + durationLimits, +}: { + users: Fixtures["users"]; + slug: string; + title?: string; + length?: number; + bookingLimits?: IntervalLimit; + durationLimits?: IntervalLimit; +}) => { + if (!bookingLimits && !durationLimits) { + throw new Error("Need to supply at least one of bookingLimits or durationLimits"); + } + + return users.create({ + eventTypes: [ + { + slug, + title: title ?? slug, + length: length ?? 30, + bookingLimits, + durationLimits, + }, + ], + }); +}; + // this method is not used anywhere else // but I'm keeping it here in case we need in the future async function createUserWithSeatedEvent(users: Fixtures["users"]) { @@ -303,3 +345,20 @@ export async function fillStripeTestCheckout(page: Page) { await page.fill("[name=billingName]", "Stripe Stripeson"); await page.click(".SubmitButton--complete-Shimmer"); } + +export async function doOnOrgDomain( + { orgSlug, page }: { orgSlug: string | null; page: Page }, + callback: ({ page }: { page: Page }) => Promise +) { + if (!orgSlug) { + throw new Error("orgSlug is not available"); + } + page.setExtraHTTPHeaders({ + "x-cal-force-slug": orgSlug, + }); + await callback({ page }); +} + +// When App directory is there, this is the 404 page text. We should work on fixing the 404 page as it changed due to app directory. +export const NotFoundPageText = "This page could not be found"; +// export const NotFoundPageText = "ERROR 404"; diff --git a/apps/web/playwright/locale.e2e.ts b/apps/web/playwright/locale.e2e.ts index f84a8ab85e..067f8a3d46 100644 --- a/apps/web/playwright/locale.e2e.ts +++ b/apps/web/playwright/locale.e2e.ts @@ -11,7 +11,8 @@ test.describe("unauthorized user sees correct translations (de)", async () => { test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + // we dont need to wait for styles and images, only for dom + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -35,7 +36,7 @@ test.describe("unauthorized user sees correct translations (ar)", async () => { test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); @@ -59,7 +60,7 @@ test.describe("unauthorized user sees correct translations (zh)", async () => { test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=zh]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -83,7 +84,7 @@ test.describe("unauthorized user sees correct translations (zh-CN)", async () => test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=zh-CN]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -107,7 +108,7 @@ test.describe("unauthorized user sees correct translations (zh-TW)", async () => test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=zh-TW]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -131,7 +132,7 @@ test.describe("unauthorized user sees correct translations (pt)", async () => { test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -155,7 +156,7 @@ test.describe("unauthorized user sees correct translations (pt-br)", async () => test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); @@ -179,7 +180,7 @@ test.describe("unauthorized user sees correct translations (es-419)", async () = test("should use correct translations and html attributes", async ({ page }) => { await page.goto("/"); - await page.waitForLoadState("load"); + await page.waitForLoadState("domcontentloaded"); // es-419 is disabled in i18n config, so es should be used as fallback await page.locator("html[lang=es]").waitFor({ state: "attached" }); @@ -213,57 +214,61 @@ test.describe("authorized user sees correct translations (de)", async () => { await test.step("should navigate to /event-types and show German translations", async () => { await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Ereignistypen", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Ereignistypen", exact: true }); + // locator.count() does not wait for elements + // but event-types page is client side, so it takes some time to render html + // thats why we need to use method that awaits for the element + // https://github.com/microsoft/playwright/issues/14278#issuecomment-1131754679 + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Event Types", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should navigate to /bookings and show German translations", async () => { await page.goto("/bookings"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Buchungen", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Buchungen", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should reload the /bookings and show German translations", async () => { await page.reload(); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Buchungen", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Buchungen", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); }); @@ -285,57 +290,57 @@ test.describe("authorized user sees correct translations (pt-br)", async () => { await test.step("should navigate to /event-types and show Brazil-Portuguese translations", async () => { await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Tipos de Eventos", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Tipos de Eventos", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Event Types", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should navigate to /bookings and show Brazil-Portuguese translations", async () => { await page.goto("/bookings"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Reservas", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Reservas", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should reload the /bookings and show Brazil-Portuguese translations", async () => { await page.reload(); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Reservas", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "Reservas", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); }); @@ -357,57 +362,57 @@ test.describe("authorized user sees correct translations (ar)", async () => { await test.step("should navigate to /event-types and show Arabic translations", async () => { await page.goto("/event-types"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); { - const locator = page.getByText("أنواع الحدث", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "أنواع الحدث", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Event Types", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should navigate to /bookings and show Arabic translations", async () => { await page.goto("/bookings"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); { - const locator = page.getByText("عمليات الحجز", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should reload the /bookings and show Arabic translations", async () => { await page.reload(); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); { - const locator = page.getByText("عمليات الحجز", { exact: true }); - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true }); + await expect(locator).toHaveCount(1); } { const locator = page.getByText("Bookings", { exact: true }); - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); }); @@ -429,7 +434,7 @@ test.describe("authorized user sees changed translations (de->ar)", async () => await test.step("should change the language and show Arabic translations", async () => { await page.goto("/settings/my-account/general"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator(".bg-default > div > div:nth-child(2)").first().click(); await page.locator("#react-select-2-option-0").click(); @@ -444,32 +449,33 @@ test.describe("authorized user sees changed translations (de->ar)", async () => await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); { - const locator = page.getByText("عام", { exact: true }); // "general" - expect(await locator.count()).toBeGreaterThanOrEqual(1); + // at least one is visible + const locator = page.getByText("عام", { exact: true }).last(); // "general" + await expect(locator).toBeVisible(); } { const locator = page.getByText("Allgemein", { exact: true }); // "general" - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should reload and show Arabic translations", async () => { await page.reload(); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); { - const locator = page.getByText("عام", { exact: true }); // "general" - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByText("عام", { exact: true }).last(); // "general" + await expect(locator).toBeVisible(); } { const locator = page.getByText("Allgemein", { exact: true }); // "general" - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); }); @@ -491,7 +497,7 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]", await test.step("should change the language and show Brazil-Portuguese translations", async () => { await page.goto("/settings/my-account/general"); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator(".bg-default > div > div:nth-child(2)").first().click(); await page.locator("#react-select-2-option-14").click(); @@ -506,32 +512,32 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]", await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Geral", { exact: true }); // "general" - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByText("Geral", { exact: true }).last(); // "general" + await expect(locator).toBeVisible(); } { const locator = page.getByText("Allgemein", { exact: true }); // "general" - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); await test.step("should reload and show Brazil-Portuguese translations", async () => { await page.reload(); - await page.waitForLoadState("networkidle"); + await page.waitForLoadState("domcontentloaded"); await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); { - const locator = page.getByText("Geral", { exact: true }); // "general" - expect(await locator.count()).toBeGreaterThanOrEqual(1); + const locator = page.getByText("Geral", { exact: true }).last(); // "general" + await expect(locator).toBeVisible(); } { const locator = page.getByText("Allgemein", { exact: true }); // "general" - expect(await locator.count()).toEqual(0); + await expect(locator).toHaveCount(0); } }); }); diff --git a/apps/web/playwright/oauth-provider.e2e.ts b/apps/web/playwright/oauth-provider.e2e.ts index 701cabae92..fae75e5cb7 100644 --- a/apps/web/playwright/oauth-provider.e2e.ts +++ b/apps/web/playwright/oauth-provider.e2e.ts @@ -104,7 +104,7 @@ test.describe("OAuth Provider", () => { expect(meData.username.startsWith("test user")).toBe(true); }); - test("should create valid access toke & refresh token for team", async ({ page, users }) => { + test("should create valid access token & refresh token for team", async ({ page, users }) => { const user = await users.create({ username: "test user", name: "test user" }, { hasTeam: true }); await user.apiLogin(); @@ -157,8 +157,8 @@ test.describe("OAuth Provider", () => { const meData = await meResponse.json(); - // check if team access token is valid - expect(meData.username.endsWith("Team Team")).toBe(true); + // Check if team access token is valid + expect(meData.username).toEqual(`user-id-${user.id}'s Team`); // request new token with refresh token const refreshTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/refreshToken`, { @@ -186,7 +186,7 @@ test.describe("OAuth Provider", () => { }, }); - expect(meData.username.endsWith("Team Team")).toBe(true); + expect(meData.username).toEqual(`user-id-${user.id}'s Team`); }); test("redirect not logged-in users to login page and after forward to authorization page", async ({ diff --git a/apps/web/playwright/organization/across-org/across-org.e2e.ts b/apps/web/playwright/organization/across-org/across-org.e2e.ts new file mode 100644 index 0000000000..ccd1c7309e --- /dev/null +++ b/apps/web/playwright/organization/across-org/across-org.e2e.ts @@ -0,0 +1,100 @@ +import { expect } from "@playwright/test"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; + +import { test } from "../../lib/fixtures"; + +test.afterAll(({ users }) => { + users.deleteAll(); +}); + +test.describe("user1NotMemberOfOrg1 is part of team1MemberOfOrg1", () => { + test("Team1 profile should show correct domain if logged in as User1", async ({ page, users, orgs }) => { + const org = await orgs.create({ + name: "TestOrg", + }); + + const user1NotMemberOfOrg1 = await users.create(undefined, { + hasTeam: true, + }); + + const { team: team1MemberOfOrg1 } = await user1NotMemberOfOrg1.getFirstTeamMembership(); + await moveTeamToOrg({ team: team1MemberOfOrg1, org }); + + await user1NotMemberOfOrg1.apiLogin(); + + await page.goto(`/settings/teams/${team1MemberOfOrg1.id}/profile`); + const domain = await page.locator(".testid-leading-text-team-url").textContent(); + expect(domain).toContain(org.slug); + }); + + test("EventTypes listing should show correct link for user events and team1MemberOfOrg1's events", async ({ + page, + users, + orgs, + }) => { + const org = await orgs.create({ + name: "TestOrg", + }); + + const user1NotMemberOfOrg1 = await users.create(undefined, { + hasTeam: true, + }); + + const { team: team1MemberOfOrg1 } = await user1NotMemberOfOrg1.getFirstTeamMembership(); + await moveTeamToOrg({ team: team1MemberOfOrg1, org }); + + await user1NotMemberOfOrg1.apiLogin(); + await page.goto("/event-types"); + await page.waitForLoadState("networkidle"); + + const userEventLinksLocators = await page + .locator(`[data-testid=slug-${user1NotMemberOfOrg1.username}] [data-testid="preview-link-button"]`) + .all(); + + expect(userEventLinksLocators.length).toBeGreaterThan(0); + + for (const userEventLinkLocator of userEventLinksLocators) { + const href = await userEventLinkLocator.getAttribute("href"); + expect(href).toContain(WEBAPP_URL); + } + + const teamEventLinksLocators = await page + .locator(`[data-testid=slug-${team1MemberOfOrg1.slug}] [data-testid="preview-link-button"]`) + .all(); + + expect(teamEventLinksLocators.length).toBeGreaterThan(0); + + for (const teamEventLinksLocator of teamEventLinksLocators) { + const href = await teamEventLinksLocator.getAttribute("href"); + expect(href).not.toContain(WEBAPP_URL); + expect(href).toContain(org.slug); + } + }); +}); + +async function moveTeamToOrg({ + team, + org, +}: { + team: { + id: number; + }; + org: { + id: number; + }; +}) { + await prisma.team.update({ + where: { + id: team.id, + }, + data: { + parent: { + connect: { + id: org.id, + }, + }, + }, + }); +} diff --git a/apps/web/playwright/organization/expects.ts b/apps/web/playwright/organization/expects.ts index e5ba1a0e83..901e306440 100644 --- a/apps/web/playwright/organization/expects.ts +++ b/apps/web/playwright/organization/expects.ts @@ -2,13 +2,14 @@ import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import { JSDOM } from "jsdom"; // eslint-disable-next-line no-restricted-imports -import type { API, Messages } from "mailhog"; +import type { Messages } from "mailhog"; +import type { createEmailsFixture } from "playwright/fixtures/emails"; import { getEmailsReceivedByUser } from "../lib/testUtils"; export async function expectInvitationEmailToBeReceived( page: Page, - emails: API | undefined, + emails: ReturnType, userEmail: string, subject: string, returnLink?: string @@ -16,7 +17,7 @@ export async function expectInvitationEmailToBeReceived( if (!emails) return null; // We need to wait for the email to go through, otherwise it will fail // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(5000); + await page.waitForTimeout(2000); const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail }); expect(receivedEmails?.total).toBe(1); const [firstReceivedEmail] = (receivedEmails as Messages).items; diff --git a/apps/web/playwright/organization/organization-creation.e2e.ts b/apps/web/playwright/organization/organization-creation.e2e.ts index 19b3477026..65c3b735a2 100644 --- a/apps/web/playwright/organization/organization-creation.e2e.ts +++ b/apps/web/playwright/organization/organization-creation.e2e.ts @@ -1,13 +1,14 @@ import { expect } from "@playwright/test"; import path from "path"; +import { uuid } from "short-uuid"; import { test } from "../lib/fixtures"; import { generateTotpCode } from "../lib/testUtils"; import { expectInvitationEmailToBeReceived } from "./expects"; -test.afterAll(({ users, emails }) => { +test.afterAll(({ users, orgs }) => { users.deleteAll(); - emails?.deleteAll(); + orgs.deleteAll(); }); function capitalize(text: string) { @@ -18,12 +19,19 @@ function capitalize(text: string) { } test.describe("Organization", () => { - test("should be able to create an organization and complete onboarding", async ({ + test("Admin should be able to create an organization and complete onboarding", async ({ page, users, emails, }) => { - const orgOwner = await users.create(); + const orgOwner = await users.create({ + role: "ADMIN", + }); + const instanceAdmin = await users.create({ + username: `admin-${uuid()}`, + email: users.trackEmail({ username: "admin", domain: "example.com" }), + role: "ADMIN", + }); const orgDomain = `${orgOwner.username}-org`; const orgName = capitalize(`${orgOwner.username}-org`); await orgOwner.apiLogin(); @@ -36,7 +44,8 @@ test.describe("Organization", () => { await expect(page.locator(".text-red-700")).toHaveCount(3); // Happy path - await page.locator("input[name=adminEmail]").fill(`john@${orgDomain}.com`); + const adminEmail = users.trackEmail({ username: "john", domain: `${orgDomain}.com` }); + await page.locator("input[name=adminEmail]").fill(adminEmail); expect(await page.locator("input[name=name]").inputValue()).toEqual(orgName); expect(await page.locator("input[name=slug]").inputValue()).toEqual(orgDomain); await page.locator("button[type=submit]").click(); @@ -46,7 +55,7 @@ test.describe("Organization", () => { await expectInvitationEmailToBeReceived( page, emails, - `john@${orgOwner.username}-org.com`, + adminEmail, "Verify your email to create an organization" ); @@ -54,13 +63,11 @@ test.describe("Organization", () => { // Code verification await expect(page.locator("#modal-title")).toBeVisible(); await page.locator("input[name='2fa1']").fill(generateTotpCode(`john@${orgDomain}.com`)); - await page.locator("button:text('Verify')").click(); - // Check admin email about DNS pending action await expectInvitationEmailToBeReceived( page, emails, - "admin@example.com", + instanceAdmin.email, "New organization created: pending action" ); @@ -104,14 +111,15 @@ test.describe("Organization", () => { await page.locator("button[type=submit]").click(); // Happy path - await page.locator('textarea[name="emails"]').fill(`rick@${orgDomain}.com`); + const adminEmail = users.trackEmail({ username: "rick", domain: `${orgDomain}.com` }); + await page.locator('textarea[name="emails"]').fill(adminEmail); await page.locator("button[type=submit]").click(); // Check if invited admin received the invitation email await expectInvitationEmailToBeReceived( page, emails, - `rick@${orgDomain}.com`, + adminEmail, `${orgName}'s admin invited you to join the organization ${orgName} on Cal.com` ); diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts index f84c639665..48e9721409 100644 --- a/apps/web/playwright/organization/organization-invitation.e2e.ts +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -1,118 +1,486 @@ +import type { Browser, Page } from "@playwright/test"; import { expect } from "@playwright/test"; +import prisma from "@calcom/prisma"; + import { test } from "../lib/fixtures"; import { getInviteLink } from "../lib/testUtils"; import { expectInvitationEmailToBeReceived } from "./expects"; test.describe.configure({ mode: "parallel" }); -test.afterEach(async ({ users, emails }) => { +test.afterEach(async ({ users, orgs }) => { await users.deleteAll(); - emails?.deleteAll(); + await orgs.deleteAll(); }); test.describe("Organization", () => { - test("Invitation (non verified)", async ({ browser, page, users, emails }) => { - const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true }); - const { team: org } = await orgOwner.getOrgMembership(); - await orgOwner.apiLogin(); - await page.goto("/settings/organizations/members"); - await page.waitForLoadState("networkidle"); - - await test.step("To the organization by email (external user)", async () => { - const invitedUserEmail = `rick@domain-${Date.now()}.com`; - await page.locator('button:text("Add")').click(); - await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); - await page.locator('button:text("Send invite")').click(); - await page.waitForLoadState("networkidle"); - const inviteLink = await expectInvitationEmailToBeReceived( - page, - emails, - invitedUserEmail, - `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`, - "signup?token" - ); - - // Check newly invited member exists and is pending - await expect( - page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) - ).toHaveCount(1); - - // eslint-disable-next-line playwright/no-conditional-in-test - if (!inviteLink) return null; - - // Follow invite link in new window - const context = await browser.newContext(); - const newPage = await context.newPage(); - newPage.goto(inviteLink); - await newPage.waitForLoadState("networkidle"); - - // Check required fields - await newPage.locator("button[type=submit]").click(); - await expect(newPage.locator(".text-red-700")).toHaveCount(3); // 3 password hints - await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); - await newPage.locator("button[type=submit]").click(); - await newPage.waitForURL("/getting-started?from=signup"); - await context.close(); - await newPage.close(); - - // Check newly invited member is not pending anymore - await page.bringToFront(); + test.describe("Email not matching orgAutoAcceptEmail", () => { + test("Org Invitation", async ({ browser, page, users, emails }) => { + const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true }); + const { team: org } = await orgOwner.getOrgMembership(); + await orgOwner.apiLogin(); await page.goto("/settings/organizations/members"); - page.locator(`[data-testid="login-form"]`); - await expect( - page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) - ).toHaveCount(0); + await page.waitForLoadState("networkidle"); + + await test.step("By email", async () => { + const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" }); + // '-domain' because the email doesn't match orgAutoAcceptEmail + const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`; + + await inviteAnEmail(page, invitedUserEmail); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`, + "signup?token" + ); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: false, + email: invitedUserEmail, + }); + + assertInviteLink(inviteLink); + await signupFromEmailInviteLink({ + browser, + inviteLink, + expectedEmail: invitedUserEmail, + expectedUsername: usernameDerivedFromEmail, + }); + + const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + }); + + await test.step("By invite link", async () => { + const inviteLink = await copyInviteLink(page); + const email = users.trackEmail({ username: "rick", domain: "domain.com" }); + // '-domain' because the email doesn't match orgAutoAcceptEmail + const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`; + await signupFromInviteLink({ browser, inviteLink, email }); + const dbUser = await prisma.user.findUnique({ where: { email } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email, + }); + }); }); - await test.step("To the organization by invite link", async () => { - // Get the invite link - await page.locator('button:text("Add")').click(); - await page.locator(`[data-testid="copy-invite-link-button"]`).click(); + test("Team invitation", async ({ browser, page, users, emails }) => { + const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, hasSubteam: true }); + await orgOwner.apiLogin(); + const { team } = await orgOwner.getFirstTeamMembership(); + const { team: org } = await orgOwner.getOrgMembership(); - const inviteLink = await getInviteLink(page); - // Follow invite link in new window - const context = await browser.newContext(); - const inviteLinkPage = await context.newPage(); - await inviteLinkPage.goto(inviteLink); - await inviteLinkPage.waitForLoadState("networkidle"); + await test.step("By email", async () => { + await page.goto(`/settings/teams/${team.id}/members`); + await page.waitForLoadState("networkidle"); + const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" }); + // '-domain' because the email doesn't match orgAutoAcceptEmail + const usernameDerivedFromEmail = `${invitedUserEmail.split("@")[0]}-domain`; + await inviteAnEmail(page, invitedUserEmail); + await expectUserToBeAMemberOfTeam({ + page, + teamId: team.id, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: false, + email: invitedUserEmail, + }); - // Check required fields - await inviteLinkPage.locator("button[type=submit]").click(); - await expect(inviteLinkPage.locator(".text-red-700")).toHaveCount(4); // email + 3 password hints + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: false, + email: invitedUserEmail, + }); - // Happy path - await inviteLinkPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`); - await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`); - await inviteLinkPage.locator("button[type=submit]").click(); - await inviteLinkPage.waitForURL("/getting-started"); + await page.waitForLoadState("networkidle"); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${team.name}'s admin invited you to join the team ${team.name} of organization ${org.name} on Cal.com`, + "signup?token" + ); + + assertInviteLink(inviteLink); + + await signupFromEmailInviteLink({ + browser, + inviteLink, + expectedEmail: invitedUserEmail, + expectedUsername: usernameDerivedFromEmail, + }); + + const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + + await expectUserToBeAMemberOfTeam({ + page, + teamId: team.id, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + }); + + await test.step("By invite link", async () => { + await page.goto(`/settings/teams/${team.id}/members`); + const inviteLink = await copyInviteLink(page); + const email = users.trackEmail({ username: "rick", domain: "domain.com" }); + // '-domain' because the email doesn't match orgAutoAcceptEmail + const usernameDerivedFromEmail = `${email.split("@")[0]}-domain`; + await signupFromInviteLink({ browser, inviteLink, email }); + + const dbUser = await prisma.user.findUnique({ where: { email } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + await expectUserToBeAMemberOfTeam({ + teamId: team.id, + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: email, + }); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: email, + }); + }); }); }); - test("Invitation (verified)", async ({ browser, page, users, emails }) => { - const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true }); - const { team: org } = await orgOwner.getOrgMembership(); - await orgOwner.apiLogin(); - await page.goto("/settings/organizations/members"); - await page.waitForLoadState("networkidle"); - - await test.step("To the organization by email (internal user)", async () => { - const invitedUserEmail = `rick-${Date.now()}@example.com`; - await page.locator('button:text("Add")').click(); - await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); - await page.locator('button:text("Send invite")').click(); + test.describe("Email matching orgAutoAcceptEmail and a Verified Organization", () => { + test("Org Invitation", async ({ browser, page, users, emails }) => { + const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true }); + const { team: org } = await orgOwner.getOrgMembership(); + await orgOwner.apiLogin(); + await page.goto("/settings/organizations/members"); await page.waitForLoadState("networkidle"); - await expectInvitationEmailToBeReceived( - page, - emails, - invitedUserEmail, - `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com` - ); - // Check newly invited member exists and is not pending - await expect( - page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) - ).toHaveCount(0); + await test.step("By email", async () => { + const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" }); + const usernameDerivedFromEmail = invitedUserEmail.split("@")[0]; + await inviteAnEmail(page, invitedUserEmail); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`, + "signup?token" + ); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + + assertInviteLink(inviteLink); + await signupFromEmailInviteLink({ + browser, + inviteLink, + expectedEmail: invitedUserEmail, + expectedUsername: usernameDerivedFromEmail, + }); + + const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + }); + + await test.step("By invite link", async () => { + const inviteLink = await copyInviteLink(page); + const email = users.trackEmail({ username: "rick", domain: "example.com" }); + const usernameDerivedFromEmail = email.split("@")[0]; + await signupFromInviteLink({ browser, inviteLink, email }); + + const dbUser = await prisma.user.findUnique({ where: { email } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email, + }); + }); + }); + + test("Team Invitation", async ({ browser, page, users, emails }) => { + const orgOwner = await users.create(undefined, { + hasTeam: true, + isOrg: true, + hasSubteam: true, + isOrgVerified: true, + }); + const { team: org } = await orgOwner.getOrgMembership(); + const { team } = await orgOwner.getFirstTeamMembership(); + + await orgOwner.apiLogin(); + + await test.step("By email", async () => { + await page.goto(`/settings/teams/${team.id}/members`); + await page.waitForLoadState("networkidle"); + const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" }); + const usernameDerivedFromEmail = invitedUserEmail.split("@")[0]; + await inviteAnEmail(page, invitedUserEmail); + await expectUserToBeAMemberOfTeam({ + page, + teamId: team.id, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${team.name}'s admin invited you to join the organization ${org.name} on Cal.com`, + "signup?token" + ); + + assertInviteLink(inviteLink); + + await signupFromEmailInviteLink({ + browser, + inviteLink, + expectedEmail: invitedUserEmail, + expectedUsername: usernameDerivedFromEmail, + }); + + const dbUser = await prisma.user.findUnique({ where: { email: invitedUserEmail } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + + await expectUserToBeAMemberOfTeam({ + page, + teamId: team.id, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: invitedUserEmail, + }); + }); + + await test.step("By invite link", async () => { + await page.goto(`/settings/teams/${team.id}/members`); + + const inviteLink = await copyInviteLink(page); + const email = users.trackEmail({ username: "rick", domain: "example.com" }); + // '-domain' because the email doesn't match orgAutoAcceptEmail + const usernameDerivedFromEmail = `${email.split("@")[0]}`; + + await signupFromInviteLink({ browser, inviteLink, email }); + + const dbUser = await prisma.user.findUnique({ where: { email } }); + expect(dbUser?.username).toBe(usernameDerivedFromEmail); + await expectUserToBeAMemberOfTeam({ + teamId: team.id, + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: email, + }); + await expectUserToBeAMemberOfOrganization({ + page, + username: usernameDerivedFromEmail, + role: "member", + isMemberShipAccepted: true, + email: email, + }); + }); }); }); }); + +async function signupFromInviteLink({ + browser, + inviteLink, + email, +}: { + browser: Browser; + inviteLink: string; + email: string; +}) { + const context = await browser.newContext(); + const inviteLinkPage = await context.newPage(); + await inviteLinkPage.goto(inviteLink); + await inviteLinkPage.waitForLoadState("networkidle"); + + // Check required fields + const button = inviteLinkPage.locator("button[type=submit][disabled]"); + await expect(button).toBeVisible(); // email + 3 password hints + + await inviteLinkPage.locator("input[name=email]").fill(email); + await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`); + await inviteLinkPage.locator("button[type=submit]").click(); + await inviteLinkPage.waitForURL("/getting-started"); + return { email }; +} + +async function signupFromEmailInviteLink({ + browser, + inviteLink, + expectedUsername, + expectedEmail, +}: { + browser: Browser; + inviteLink: string; + expectedUsername: string; + expectedEmail: string; +}) { + // Follow invite link in new window + const context = await browser.newContext(); + const signupPage = await context.newPage(); + + signupPage.goto(inviteLink); + await signupPage.locator(`[data-testid="signup-usernamefield"]`).waitFor({ state: "visible" }); + await expect(signupPage.locator(`[data-testid="signup-usernamefield"]`)).toBeDisabled(); + expect(await signupPage.locator(`[data-testid="signup-usernamefield"]`).inputValue()).toBe( + expectedUsername + ); + await expect(signupPage.locator(`[data-testid="signup-emailfield"]`)).toBeDisabled(); + expect(await signupPage.locator(`[data-testid="signup-emailfield"]`).inputValue()).toBe(expectedEmail); + + await signupPage.waitForLoadState("networkidle"); + // Check required fields + await signupPage.locator("input[name=password]").fill(`P4ssw0rd!`); + await signupPage.locator("button[type=submit]").click(); + await signupPage.waitForURL("/getting-started?from=signup"); + await context.close(); + await signupPage.close(); +} + +async function inviteAnEmail(page: Page, invitedUserEmail: string) { + await page.locator('button:text("Add")').click(); + await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); + await page.locator('button:text("Send invite")').click(); + await page.waitForLoadState("networkidle"); +} + +async function expectUserToBeAMemberOfOrganization({ + page, + username, + email, + role, + isMemberShipAccepted, +}: { + page: Page; + username: string; + role: string; + isMemberShipAccepted: boolean; + email: string; +}) { + // Check newly invited member is not pending anymore + await page.goto("/settings/organizations/members"); + expect(await page.locator(`[data-testid="member-${username}-username"]`).textContent()).toBe(username); + expect(await page.locator(`[data-testid="member-${username}-email"]`).textContent()).toBe(email); + expect((await page.locator(`[data-testid="member-${username}-role"]`).textContent())?.toLowerCase()).toBe( + role.toLowerCase() + ); + if (isMemberShipAccepted) { + await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeHidden(); + } else { + await expect(page.locator(`[data-testid2="member-${username}-pending"]`)).toBeVisible(); + } +} + +async function expectUserToBeAMemberOfTeam({ + page, + teamId, + email, + role, + username, + isMemberShipAccepted, +}: { + page: Page; + username: string; + role: string; + teamId: number; + isMemberShipAccepted: boolean; + email: string; +}) { + // Check newly invited member is not pending anymore + await page.goto(`/settings/teams/${teamId}/members`); + await page.reload(); + expect( + ( + await page.locator(`[data-testid="member-${username}"] [data-testid=member-role]`).textContent() + )?.toLowerCase() + ).toBe(role.toLowerCase()); + if (isMemberShipAccepted) { + await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeHidden(); + } else { + await expect(page.locator(`[data-testid="email-${email.replace("@", "")}-pending"]`)).toBeVisible(); + } +} + +function assertInviteLink(inviteLink: string | null | undefined): asserts inviteLink is string { + if (!inviteLink) throw new Error("Invite link not found"); +} + +async function copyInviteLink(page: Page) { + await page.locator('button:text("Add")').click(); + await page.locator(`[data-testid="copy-invite-link-button"]`).click(); + const inviteLink = await getInviteLink(page); + return inviteLink; +} diff --git a/apps/web/playwright/payment-apps.e2e.ts b/apps/web/playwright/payment-apps.e2e.ts index 77bf674d92..4aca2efb22 100644 --- a/apps/web/playwright/payment-apps.e2e.ts +++ b/apps/web/playwright/payment-apps.e2e.ts @@ -13,9 +13,7 @@ test.describe("Payment app", () => { const user = await users.create(); await user.apiLogin(); const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { type: "alby_payment", @@ -30,7 +28,7 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); await page.getByPlaceholder("Price").click(); @@ -38,7 +36,7 @@ test.describe("Payment app", () => { await page.getByText("SatoshissatsCurrencyBTCPayment optionCollect payment on booking").click(); await page.getByTestId("update-eventtype").click(); - await page.goto(`${user.username}/${paymentEvent.slug}`); + await page.goto(`${user.username}/${paymentEvent?.slug}`); // expect 200 sats to be displayed in page expect(await page.locator("text=200 sats").first()).toBeTruthy(); @@ -55,9 +53,7 @@ test.describe("Payment app", () => { const user = await users.create(); await user.apiLogin(); const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { type: "stripe_payment", @@ -75,16 +71,16 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); await page.getByTestId("stripe-currency-select").click(); await page.getByTestId("select-option-usd").click(); - await page.getByTestId("price-input-stripe").click(); - await page.getByTestId("price-input-stripe").fill("350"); + await page.getByTestId("stripe-price-input").click(); + await page.getByTestId("stripe-price-input").fill("350"); await page.getByTestId("update-eventtype").click(); - await page.goto(`${user.username}/${paymentEvent.slug}`); + await page.goto(`${user.username}/${paymentEvent?.slug}`); // expect 200 sats to be displayed in page expect(await page.locator("text=350").first()).toBeTruthy(); @@ -101,9 +97,7 @@ test.describe("Payment app", () => { const user = await users.create(); await user.apiLogin(); const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { type: "paypal_payment", @@ -116,7 +110,7 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); @@ -131,7 +125,7 @@ test.describe("Payment app", () => { await page.getByText("$MXNCurrencyMexican pesoPayment option").click(); await page.getByTestId("update-eventtype").click(); - await page.goto(`${user.username}/${paymentEvent.slug}`); + await page.goto(`${user.username}/${paymentEvent?.slug}`); // expect 150 to be displayed in page expect(await page.locator("text=MX$150.00").first()).toBeTruthy(); @@ -149,9 +143,7 @@ test.describe("Payment app", () => { const user = await users.create(); await user.apiLogin(); const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { type: "alby_payment", @@ -160,7 +152,7 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); @@ -177,9 +169,7 @@ test.describe("Payment app", () => { const user = await users.create(); await user.apiLogin(); const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { type: "paypal_payment", @@ -188,7 +178,7 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); @@ -211,9 +201,7 @@ test.describe("Payment app", () => { await user.apiLogin(); // Any event should work here const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); - if (!paymentEvent) { - throw new Error("No payment event found"); - } + expect(paymentEvent).not.toBeNull(); await prisma.credential.create({ data: { @@ -225,7 +213,7 @@ test.describe("Payment app", () => { }, }); - await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + await page.goto(`event-types/${paymentEvent?.id}?tabName=apps`); await page.locator("#event-type-form").getByRole("switch").click(); // make sure Tracking ID is displayed @@ -234,4 +222,128 @@ test.describe("Payment app", () => { await page.getByLabel("Tracking ID").fill("demo"); await page.getByTestId("update-eventtype").click(); }); + + test("Should only be allowed to enable one payment app", async ({ page, users }) => { + const user = await users.create(); + await user.apiLogin(); + const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); + if (!paymentEvent) { + throw new Error("No payment event found"); + } + await prisma.credential.createMany({ + data: [ + { + type: "paypal_payment", + userId: user.id, + key: { + client_id: "randomString", + secret_key: "randomString", + webhook_id: "randomString", + }, + }, + { + type: "stripe_payment", + userId: user.id, + key: { + scope: "read_write", + livemode: false, + token_type: "bearer", + access_token: "sk_test_randomString", + refresh_token: "rt_randomString", + stripe_user_id: "acct_randomString", + default_currency: "usd", + stripe_publishable_key: "pk_test_randomString", + }, + }, + ], + }); + + await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + + await page.locator("[data-testid='paypal-app-switch']").click(); + await page.locator("[data-testid='stripe-app-switch']").isDisabled(); + }); + + test("when more than one payment app is installed the price should be updated when changing settings", async ({ + page, + users, + }) => { + const user = await users.create(); + await user.apiLogin(); + const paymentEvent = user.eventTypes.find((item) => item.slug === "paid"); + if (!paymentEvent) { + throw new Error("No payment event found"); + } + + await prisma.credential.createMany({ + data: [ + { + type: "paypal_payment", + userId: user.id, + key: { + client_id: "randomString", + secret_key: "randomString", + webhook_id: "randomString", + }, + }, + { + type: "stripe_payment", + userId: user.id, + key: { + scope: "read_write", + livemode: false, + token_type: "bearer", + access_token: "sk_test_randomString", + refresh_token: "rt_randomString", + stripe_user_id: "acct_randomString", + default_currency: "usd", + stripe_publishable_key: "pk_test_randomString", + }, + }, + ], + }); + + await page.goto(`event-types/${paymentEvent.id}?tabName=apps`); + + await page.locator("[data-testid='paypal-app-switch']").click(); + await page.locator("[data-testid='paypal-price-input']").fill("100"); + await page.locator("[data-testid='paypal-currency-select']").first().click(); + await page.locator("#react-select-2-option-13").click(); + // await page.locator(".mb-1 > .bg-default > div > div:nth-child(2)").first().click(); + // await page.getByText("$MXNCurrencyMexican pesoPayment option").click(); + await page.locator("[data-testid='update-eventtype']").click(); + + // Need to wait for the DB to be updated + await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); + + const paypalPrice = await prisma.eventType.findFirst({ + where: { + id: paymentEvent.id, + }, + select: { + price: true, + }, + }); + + expect(paypalPrice?.price).toEqual(10000); + + await page.locator("[data-testid='paypal-app-switch']").click(); + await page.locator("[data-testid='stripe-app-switch']").click(); + await page.locator("[data-testid='stripe-price-input']").fill("200"); + await page.locator("[data-testid='update-eventtype']").click(); + + // Need to wait for the DB to be updated + await page.waitForResponse((res) => res.url().includes("update") && res.status() === 200); + + const stripePrice = await prisma.eventType.findFirst({ + where: { + id: paymentEvent.id, + }, + select: { + price: true, + }, + }); + + expect(stripePrice?.price).toEqual(20000); + }); }); diff --git a/apps/web/playwright/profile.e2e.ts b/apps/web/playwright/profile.e2e.ts index 76692c9bd0..fa98d1deae 100644 --- a/apps/web/playwright/profile.e2e.ts +++ b/apps/web/playwright/profile.e2e.ts @@ -1,5 +1,3 @@ -import { expect } from "@playwright/test"; - import { test } from "./lib/fixtures"; test.describe.configure({ mode: "parallel" }); @@ -19,6 +17,6 @@ test.describe("Teams", () => { await page.goto("/settings/my-account/profile"); // check if user avatar is loaded - await expect(page.locator('[data-testid="organization-avatar"]')).toBeVisible(); + await page.getByTestId("profile-upload-avatar").isVisible(); }); }); diff --git a/apps/web/playwright/reschedule.e2e.ts b/apps/web/playwright/reschedule.e2e.ts index 3c31cb5678..bdb1edda01 100644 --- a/apps/web/playwright/reschedule.e2e.ts +++ b/apps/web/playwright/reschedule.e2e.ts @@ -252,6 +252,7 @@ test.describe("Reschedule Tests", async () => { let firstOfNextMonth = dayjs().add(1, "month").startOf("month"); // find first available slot of next month (available monday-friday) + // eslint-disable-next-line playwright/no-conditional-in-test while (firstOfNextMonth.day() < 1 || firstOfNextMonth.day() > 5) { firstOfNextMonth = firstOfNextMonth.add(1, "day"); } diff --git a/apps/web/playwright/settings-admin.e2e.ts b/apps/web/playwright/settings-admin.e2e.ts new file mode 100644 index 0000000000..58b4bee7e4 --- /dev/null +++ b/apps/web/playwright/settings-admin.e2e.ts @@ -0,0 +1,35 @@ +import { expect } from "@playwright/test"; + +import { test } from "./lib/fixtures"; + +test.describe.configure({ mode: "parallel" }); + +test.describe("Settings/admin A/B tests", () => { + test("should point to the /future/settings/admin page", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create({ + role: "ADMIN", + }); + await user.apiLogin(); + + await page.goto("/settings/admin"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "Feature Flags" }); + + await expect(locator).toBeVisible(); + }); +}); diff --git a/apps/web/playwright/settings/upload-avatar.e2e.ts b/apps/web/playwright/settings/upload-avatar.e2e.ts index 6d2fbc3219..191dfd476a 100644 --- a/apps/web/playwright/settings/upload-avatar.e2e.ts +++ b/apps/web/playwright/settings/upload-avatar.e2e.ts @@ -43,8 +43,11 @@ test.describe("UploadAvatar", async () => { // todo: remove this; ideally the organization-avatar is updated the moment // 'Settings updated succesfully' is saved. await page.waitForLoadState("networkidle"); + const avatar = page.getByTestId("profile-upload-avatar").locator("img"); - await expect(await page.getByTestId("organization-avatar").innerHTML()).toContain(response.objectKey); + const src = await avatar.getAttribute("src"); + + await expect(src).toContain(response.objectKey); const urlResponse = await page.request.get(`/api/avatar/${response.objectKey}.png`, { maxRedirects: 0, diff --git a/apps/web/playwright/signup.e2e.ts b/apps/web/playwright/signup.e2e.ts index 60775a2143..4e4c18fd37 100644 --- a/apps/web/playwright/signup.e2e.ts +++ b/apps/web/playwright/signup.e2e.ts @@ -2,9 +2,11 @@ import { expect } from "@playwright/test"; import { randomBytes } from "crypto"; import { APP_NAME, IS_PREMIUM_USERNAME_ENABLED, IS_MAILHOG_ENABLED } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; import { test } from "./lib/fixtures"; -import { getEmailsReceivedByUser } from "./lib/testUtils"; +import { getEmailsReceivedByUser, localize } from "./lib/testUtils"; +import { expectInvitationEmailToBeReceived } from "./team/expects"; test.describe.configure({ mode: "parallel" }); @@ -101,6 +103,8 @@ test.describe("Signup Flow Test", async () => { const userToCreate = users.buildForSignup({ username: "rick-jones", password: "Password99!", + // Email intentonally kept as different from username + email: `rickjones${Math.random()}-${Date.now()}@example.com`, }); await page.goto("/signup"); @@ -118,6 +122,9 @@ test.describe("Signup Flow Test", async () => { // Check that the URL matches the expected URL expect(page.url()).toContain("/auth/verify-email"); + const dbUser = await prisma.user.findUnique({ where: { email: userToCreate.email } }); + // Verify that the username is the same as the one provided and isn't accidentally changed to email derived username - That happens only for organization member signup + expect(dbUser?.username).toBe(userToCreate.username); }); test("Signup fields prefilled with query params", async ({ page, users }) => { const signupUrlWithParams = "/signup?username=rick-jones&email=rick-jones%40example.com"; @@ -196,6 +203,7 @@ test.describe("Signup Flow Test", async () => { data: { enabled: true }, }); const userToCreate = users.buildForSignup({ + email: users.trackEmail({ username: "email-verify", domain: "example.com" }), username: "email-verify", password: "Password99!", }); @@ -228,4 +236,55 @@ test.describe("Signup Flow Test", async () => { const verifyEmail = receivedEmails?.items[0]; expect(verifyEmail?.subject).toBe(`${APP_NAME}: Verify your account`); }); + test("If signup is disabled allow team invites", async ({ browser, page, users, emails }) => { + // eslint-disable-next-line playwright/no-skipped-test + test.skip(process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true", "Skipping due to signup being enabled"); + + const t = await localize("en"); + const teamOwner = await users.create(undefined, { hasTeam: true }); + const { team } = await teamOwner.getFirstTeamMembership(); + await teamOwner.apiLogin(); + await page.goto(`/settings/teams/${team.id}/members`); + await page.waitForLoadState("networkidle"); + + await test.step("Invite User to team", async () => { + // TODO: This invite logic should live in a fixture - its used in team and orgs invites (Duplicated from team/org invites) + const invitedUserEmail = `rick_${Date.now()}@domain-${Date.now()}.com`; + await page.locator(`button:text("${t("add")}")`).click(); + await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); + await page.locator(`button:text("${t("send_invite")}")`).click(); + await page.waitForLoadState("networkidle"); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${team.name}'s admin invited you to join the team ${team.name} on Cal.com`, + "signup?token" + ); + + //Check newly invited member exists and is pending + await expect( + page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) + ).toHaveCount(1); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!inviteLink) return; + + // Follow invite link to new window + const context = await browser.newContext(); + const newPage = await context.newPage(); + await newPage.goto(inviteLink); + await newPage.waitForLoadState("networkidle"); + + const url = new URL(newPage.url()); + expect(url.pathname).toBe("/signup"); + + // Check required fields + await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); + await newPage.locator("button[type=submit]").click(); + await newPage.waitForURL("/getting-started?from=signup"); + await newPage.close(); + await context.close(); + }); + }); }); diff --git a/apps/web/playwright/team/expects.ts b/apps/web/playwright/team/expects.ts index 43e02063f6..4579a1e613 100644 --- a/apps/web/playwright/team/expects.ts +++ b/apps/web/playwright/team/expects.ts @@ -1,13 +1,14 @@ import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import { JSDOM } from "jsdom"; -import type { API, Messages } from "mailhog"; +import type { Messages } from "mailhog"; +import type { createEmailsFixture } from "playwright/fixtures/emails"; import { getEmailsReceivedByUser } from "../lib/testUtils"; export async function expectInvitationEmailToBeReceived( page: Page, - emails: API | undefined, + emails: ReturnType, userEmail: string, subject: string, returnLink?: string @@ -15,7 +16,7 @@ export async function expectInvitationEmailToBeReceived( if (!emails) return null; // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(10000); + await page.waitForTimeout(2000); const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail }); expect(receivedEmails?.total).toBe(1); diff --git a/apps/web/playwright/team/team-invitation.e2e.ts b/apps/web/playwright/team/team-invitation.e2e.ts index 40308fdd71..d5c5c77a3d 100644 --- a/apps/web/playwright/team/team-invitation.e2e.ts +++ b/apps/web/playwright/team/team-invitation.e2e.ts @@ -8,22 +8,24 @@ import { expectInvitationEmailToBeReceived } from "./expects"; test.describe.configure({ mode: "parallel" }); -test.afterEach(async ({ users, emails }) => { +test.afterEach(async ({ users }) => { await users.deleteAll(); - emails?.deleteAll(); }); test.describe("Team", () => { test("Invitation (non verified)", async ({ browser, page, users, emails }) => { const t = await localize("en"); const teamOwner = await users.create(undefined, { hasTeam: true }); - const { team } = await teamOwner.getFirstTeam(); + const { team } = await teamOwner.getFirstTeamMembership(); await teamOwner.apiLogin(); await page.goto(`/settings/teams/${team.id}/members`); await page.waitForLoadState("networkidle"); await test.step("To the team by email (external user)", async () => { - const invitedUserEmail = `rick_${Date.now()}@domain-${Date.now()}.com`; + const invitedUserEmail = users.trackEmail({ + username: "rick", + domain: `domain-${Date.now()}.com`, + }); await page.locator(`button:text("${t("add")}")`).click(); await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); await page.locator(`button:text("${t("send_invite")}")`).click(); @@ -51,8 +53,10 @@ test.describe("Team", () => { await newPage.waitForLoadState("networkidle"); // Check required fields - await newPage.locator("button[type=submit]").click(); - await expect(newPage.locator('[data-testid="hint-error"]')).toHaveCount(3); + const button = newPage.locator("button[type=submit][disabled]"); + await expect(button).toBeVisible(); // email + 3 password hints + + // Check required fields await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); await newPage.locator("button[type=submit]").click(); await newPage.waitForURL("/getting-started?from=signup"); @@ -96,13 +100,16 @@ test.describe("Team", () => { test("Invitation (verified)", async ({ browser, page, users, emails }) => { const t = await localize("en"); const teamOwner = await users.create({ name: `team-owner-${Date.now()}` }, { hasTeam: true }); - const { team } = await teamOwner.getFirstTeam(); + const { team } = await teamOwner.getFirstTeamMembership(); await teamOwner.apiLogin(); await page.goto(`/settings/teams/${team.id}/members`); await page.waitForLoadState("networkidle"); await test.step("To the organization by email (internal user)", async () => { - const invitedUserEmail = `rick@example.com`; + const invitedUserEmail = users.trackEmail({ + username: "rick", + domain: `example.com`, + }); await page.locator(`button:text("${t("add")}")`).click(); await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); await page.locator(`button:text("${t("send_invite")}")`).click(); diff --git a/apps/web/playwright/teams.e2e.ts b/apps/web/playwright/teams.e2e.ts index 4c8cae0bd5..dce93d42b9 100644 --- a/apps/web/playwright/teams.e2e.ts +++ b/apps/web/playwright/teams.e2e.ts @@ -1,4 +1,3 @@ -import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; @@ -7,7 +6,9 @@ import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { test } from "./lib/fixtures"; import { + NotFoundPageText, bookTimeSlot, + doOnOrgDomain, fillStripeTestCheckout, selectFirstAvailableTimeSlotNextMonth, testName, @@ -16,12 +17,41 @@ import { test.describe.configure({ mode: "parallel" }); +test.describe("Teams A/B tests", () => { + test("should point to the /future/teams page", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/teams"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "teams" }); + + await expect(locator).toBeVisible(); + }); +}); + test.describe("Teams - NonOrg", () => { test.afterEach(({ users }) => users.deleteAll()); test("Team Onboarding Invite Members", async ({ page, users }) => { const user = await users.create(undefined, { hasTeam: true }); - const { team } = await user.getFirstTeam(); + const { team } = await user.getFirstTeamMembership(); const inviteeEmail = `${user.username}+invitee@example.com`; await user.apiLogin(); @@ -79,7 +109,7 @@ test.describe("Teams - NonOrg", () => { schedulingType: SchedulingType.COLLECTIVE, } ); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); await page.goto(`/team/${team.slug}/${teamEventSlug}`); @@ -117,7 +147,7 @@ test.describe("Teams - NonOrg", () => { } ); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); await page.goto(`/team/${team.slug}/${teamEventSlug}`); @@ -234,12 +264,15 @@ test.describe("Teams - NonOrg", () => { ); await owner.apiLogin(); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); // Mark team as private await page.goto(`/settings/teams/${team.id}/members`); await page.click("[data-testid=make-team-private-check]"); await expect(page.locator(`[data-testid=make-team-private-check][data-state="checked"]`)).toBeVisible(); + // according to switch implementation, checked state can be set before mutation is resolved + // so we need to await for req to resolve + await page.waitForResponse((res) => res.url().includes("/api/trpc/teams/update")); // Go to Team's page await page.goto(`/team/${team.slug}`); @@ -295,15 +328,19 @@ test.describe("Teams - Org", () => { await page.locator('[placeholder="email\\@example\\.com"]').fill(inviteeEmail); await page.getByTestId("invite-new-member-button").click(); await expect(page.locator(`li:has-text("${inviteeEmail}")`)).toBeVisible(); - expect(await page.getByTestId("pending-member-item").count()).toBe(2); + + // locator.count() does not await for the expected number of elements + // https://github.com/microsoft/playwright/issues/14278 + // using toHaveCount() is more reliable + await expect(page.getByTestId("pending-member-item")).toHaveCount(2); }); await test.step("Can remove members", async () => { - expect(await page.getByTestId("pending-member-item").count()).toBe(2); + await expect(page.getByTestId("pending-member-item")).toHaveCount(2); const lastRemoveMemberButton = page.getByTestId("remove-member-button").last(); await lastRemoveMemberButton.click(); await page.waitForLoadState("networkidle"); - expect(await page.getByTestId("pending-member-item").count()).toBe(1); + await expect(page.getByTestId("pending-member-item")).toHaveCount(1); // Cleanup here since this user is created without our fixtures. await prisma.user.delete({ where: { email: inviteeEmail } }); @@ -347,12 +384,12 @@ test.describe("Teams - Org", () => { schedulingType: SchedulingType.COLLECTIVE, } ); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); await page.goto(`/team/${team.slug}/${teamEventSlug}`); - await expect(page.locator("text=This page could not be found")).toBeVisible(); + await expect(page.locator(`text=${NotFoundPageText}`)).toBeVisible(); await doOnOrgDomain( { orgSlug: org.slug, @@ -396,7 +433,7 @@ test.describe("Teams - Org", () => { } ); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); await page.goto(`/team/${team.slug}/${teamEventSlug}`); @@ -418,17 +455,43 @@ test.describe("Teams - Org", () => { expect(teamMatesObj.concat([{ name: owner.name! }]).some(({ name }) => name === chosenUser)).toBe(true); // TODO: Assert whether the user received an email }); -}); -async function doOnOrgDomain( - { orgSlug, page }: { orgSlug: string | null; page: Page }, - callback: ({ page }: { page: Page }) => Promise -) { - if (!orgSlug) { - throw new Error("orgSlug is not available"); - } - page.setExtraHTTPHeaders({ - "x-cal-force-slug": orgSlug, + test("Can access booking page with event slug and team page in lowercase/uppercase/mixedcase", async ({ + page, + orgs, + users, + }) => { + const org = await orgs.create({ + name: "TestOrg", + }); + const teamMatesObj = [ + { name: "teammate-1" }, + { name: "teammate-2" }, + { name: "teammate-3" }, + { name: "teammate-4" }, + ]; + + const owner = await users.create( + { + username: "pro-user", + name: "pro-user", + organizationId: org.id, + roleInOrganization: MembershipRole.MEMBER, + }, + { + hasTeam: true, + teammates: teamMatesObj, + schedulingType: SchedulingType.COLLECTIVE, + } + ); + const { team } = await owner.getFirstTeamMembership(); + const { slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); + + const teamSlugUpperCase = team.slug?.toUpperCase(); + const teamEventSlugUpperCase = teamEventSlug.toUpperCase(); + + // This is the most closest to the actual user flow as org1.cal.com maps to /org/orgSlug + await page.goto(`/org/${org.slug}/${teamSlugUpperCase}/${teamEventSlugUpperCase}`); + await page.waitForSelector("[data-testid=day]"); }); - await callback({ page }); -} +}); diff --git a/apps/web/playwright/unpublished.e2e.ts b/apps/web/playwright/unpublished.e2e.ts index 8ae45b8038..fb486c3859 100644 --- a/apps/web/playwright/unpublished.e2e.ts +++ b/apps/web/playwright/unpublished.e2e.ts @@ -18,7 +18,7 @@ test.afterAll(async ({ users }) => { test.describe("Unpublished", () => { test("Regular team profile", async ({ page, users }) => { const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true }); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { requestedSlug } = team.metadata as { requestedSlug: string }; await page.goto(`/team/${requestedSlug}`); expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1); @@ -33,7 +33,7 @@ test.describe("Unpublished", () => { isUnpublished: true, schedulingType: SchedulingType.COLLECTIVE, }); - const { team } = await owner.getFirstTeam(); + const { team } = await owner.getFirstTeamMembership(); const { requestedSlug } = team.metadata as { requestedSlug: string }; const { slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); await page.goto(`/team/${requestedSlug}/${teamEventSlug}`); diff --git a/apps/web/public/product-cards/trustpilot-dark.svg b/apps/web/public/product-cards/trustpilot-dark.svg new file mode 100644 index 0000000000..c03dbf6358 --- /dev/null +++ b/apps/web/public/product-cards/trustpilot-dark.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/static/locales/ar/common.json b/apps/web/public/static/locales/ar/common.json index c23ca4dc2a..6478916b0e 100644 --- a/apps/web/public/static/locales/ar/common.json +++ b/apps/web/public/static/locales/ar/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "لتمكين هذه الميزة، احصل على مفتاح نشر في وحدة التحكم {{consoleUrl}} وأضفه إلى وحدة التحكم الخاصة بك. nv باسم CALCOM_LICENSE_KEY. إذا كان لدى فريقك بالفعل ترخيص، يرجى الاتصال بـ {{supportMail}} للحصول على المساعدة.", "enterprise_license_development": "يمكنك اختبار هذه الميزة في وضع التطوير. لاستخدام الإنتاج، يرجى مطالبة المسؤول بالذهاب إلى <2>/auth/setup لإدخال مفتاح الترخيص.", "missing_license": "الترخيص مفقود", - "signup_requires": "يلزم ترخيص تجاري", - "signup_requires_description": "لا تقدم شركة {{companyName}} حاليًا إصدارًا مجانيًا مفتوح المصدر لصفحة التسجيل. للحصول على حق الوصول الكامل إلى مكونات التسجيل، يجب أن تحصل على ترخيص تجاري. للاستخدام الشخصي، نوصي باستخدام منصة Prisma Data أو أي واجهة أخرى من واجهات Postgres لإنشاء الحسابات.", "next_steps": "الخطوات التالية", "acquire_commercial_license": "الحصول على ترخيص تجاري", "the_infrastructure_plan": "تعتمد خطة البنية التحتية على الاستخدام وتتضمن خصومات مناسبة للأعمال التجارية حديثة الإنشاء.", @@ -1581,6 +1579,7 @@ "enable_apps": "تمكين التطبيقات", "enable_apps_description": "تمكين التطبيقات التي يمكن للمستخدمين دمجها مع {{appName}}", "purchase_license": "شراء ترخيص", + "already_have_account": "هل لديك حساب بالفعل؟", "already_have_key": "لدي مفتاح بالفعل:", "already_have_key_suggestion": "يرجى نسخ متغير البيئة الحالي CALCOM_LICENSE_KEY هنا.", "app_is_enabled": "تم تمكين {{appName}}", @@ -1993,7 +1992,7 @@ "select_time": "اختيار الوقت", "select_date": "اختيار التاريخ", "see_all_available_times": "رؤية كل الأوقات المتاحة", - "org_team_names_example": "مثال، فريق التسويق", + "org_team_names_example_1": "مثال، فريق التسويق", "org_team_names_example_2": "مثال، فريق المبيعات", "org_team_names_example_3": "مثال، فريق التصميم", "org_team_names_example_4": "مثال، الفريق الهندسي", diff --git a/apps/web/public/static/locales/cs/common.json b/apps/web/public/static/locales/cs/common.json index 4ea861ef1d..13f62236b3 100644 --- a/apps/web/public/static/locales/cs/common.json +++ b/apps/web/public/static/locales/cs/common.json @@ -537,8 +537,8 @@ "address": "Adresa", "enter_address": "Zadejte adresu", "in_person_attendee_address": "Osobně (adresa účastníka)", - "yes": "ano", - "no": "ne", + "yes": "Ano", + "no": "Ne", "additional_notes": "Doplňující poznámky", "booking_fail": "Schůzku nelze rezervovat.", "reschedule_fail": "Schůzku nelze přesunout na jindy.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Pokud chcete povolit tuto funkci, získejte klíč pro nasazení v konzoli {{consoleUrl}} a přidejte ho do souboru .env jako CALCOM_LICENSE_KEY. Pokud váš tým již licenci má, kontaktujte prosím {{supportMail}} a požádejte o pomoc.", "enterprise_license_development": "Tuto funkci můžete vyzkoušet v režimu vývoje. Produkční použití vyžaduje od správce zadání licenčního klíče v <2>/auth/setup.", "missing_license": "Chybějící licence", - "signup_requires": "Je vyžadována komerční licence", - "signup_requires_description": "{{companyName}} v současné době nenabízí bezplatnou open source verzi stránky registrace. Chcete-li získat plný přístup ke komponentám registrace, musíte získat komerční licenci. Pro osobní použití doporučujeme Prisma Data Platform nebo jakékoli jiné Postgres rozhraní pro vytváření účtů.", "next_steps": "Další kroky", "acquire_commercial_license": "Získat komerční licenci", "the_infrastructure_plan": "Infrastruktura se účtuje podle reálného využívání a má příznivé slevy pro startupy.", @@ -1581,6 +1579,7 @@ "enable_apps": "Povolit aplikace", "enable_apps_description": "Zapněte aplikace, které mohou uživatelé integrovat s aplikací {{appName}}", "purchase_license": "Kupte si licenci", + "already_have_account": "Už máte účet?", "already_have_key": "Klíč již mám:", "already_have_key_suggestion": "Zkopírujte sem svou stávající proměnnou prostředí CALCOM_LICENSE_KEY.", "app_is_enabled": "Aplikace {{appName}} je povolena", @@ -1993,7 +1992,7 @@ "select_time": "Vyberte čas", "select_date": "Vyberte datum", "see_all_available_times": "Zobrazit všechny dostupné časy", - "org_team_names_example": "Např. marketingový tým", + "org_team_names_example_1": "Např. marketingový tým", "org_team_names_example_2": "Např. obchodní tým", "org_team_names_example_3": "Např. designérský tým", "org_team_names_example_4": "Např. inženýrský tým", diff --git a/apps/web/public/static/locales/da/common.json b/apps/web/public/static/locales/da/common.json index 3e042556e5..0347e68017 100644 --- a/apps/web/public/static/locales/da/common.json +++ b/apps/web/public/static/locales/da/common.json @@ -446,8 +446,8 @@ "address": "Adresse", "enter_address": "Indtast adresse", "in_person_attendee_address": "Personlig (Deltageradresse)", - "yes": "ja", - "no": "nej", + "yes": "Ja", + "no": "Nej", "additional_notes": "Yderligere bemærkninger", "booking_fail": "Kunne ikke booke mødet.", "reschedule_fail": "Kunne ikke omlægge mødet.", @@ -801,8 +801,6 @@ "enterprise_license": "Dette er en virksomhedsfunktion", "enterprise_license_description": "For at aktivere denne funktion, skal du gå til {{setupUrl}} for at indtaste en licensnøgle. Hvis en licensnøgle allerede er på plads, bedes du kontakte {{supportMail}} for hjælp.", "missing_license": "Manglende Licens", - "signup_requires": "Erhvervsmæssig licens kræves", - "signup_requires_description": "{{companyName}} tilbyder i øjeblikket ikke en gratis open source version af tilmeldingssiden. For at modtage fuld adgang til tilmeldingskomponenterne skal du erhverve en kommerciel licens. Til personligt brug anbefaler vi Prisma Dataplatformen eller enhver anden Postgres-grænseflade for at oprette konti.", "next_steps": "Næste Trin", "acquire_commercial_license": "Erhverv en kommerciel licens", "the_infrastructure_plan": "Infrastrukturplanen er brugerbaseret og har startup-venlige rabatter.", @@ -1367,6 +1365,7 @@ "disabled_calendar": "Hvis du har en anden kalender installeret vil nye bookinger blive tilføjet til den. Hvis ikke, så forbind en ny kalender, så du ikke går glip af nye bookinger.", "enable_apps": "Aktivér Apps", "purchase_license": "Køb en licens", + "already_have_account": "Har du allerede en konto?", "already_have_key": "Jeg har allerede en nøgle:", "already_have_key_suggestion": "Kopiér venligst din eksisterende CALCOM_LICENSE_KEY environment variabel her.", "app_is_enabled": "{{appName}} er aktiveret", diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index dd75bfa9df..aafec106a2 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "Um diese Funktion zu aktivieren, holen Sie sich einen Deployment-Schlüssel von der {{consoleUrl}}-Konsole und fügen Sie ihn als CALCOM_LICENSE_KEY zu Ihrer .env hinzu. Wenn Ihr Team bereits eine Lizenz hat, wenden Sie sich bitte an {{supportMail}} für Hilfe.", "enterprise_license_development": "Sie können diese Funktion im Entwicklungsmodus testen. Zur Nutzung im regulären Arbeitsumfeld, besuchen Sie bitte <2>/auth/setup, um einen Lizenzschlüssel einzugeben.", "missing_license": "Lizenz fehlt", - "signup_requires": "Kommerzielle Lizenz erforderlich", - "signup_requires_description": "{{companyName}} bietet zur Zeit keine kostenlose Open-Source-Version der Anmeldeseite an. Um vollen Zugriff auf die Registrierungskomponenten zu erhalten, müssen Sie eine kommerzielle Lizenz erwerben. Für den persönlichen Gebrauch empfehlen wir die Prisma Data Platform oder jede andere Postgres-Schnittstelle zur Erstellung von Accounts.", "next_steps": "Nächste Schritte", "acquire_commercial_license": "Eine kommerzielle Lizenz erwerben", "the_infrastructure_plan": "Der Infrastrukturplan ist nutzungsbasiert und bietet Startup-freundliche Discounts an.", @@ -1581,6 +1579,7 @@ "enable_apps": "Apps aktivieren", "enable_apps_description": "Apps aktivieren, die Benutzer mit {{appName}} integrieren können", "purchase_license": "Lizenz kaufen", + "already_have_account": "Haben Sie bereits ein Konto?", "already_have_key": "Ich habe bereits einen Schlüssel:", "already_have_key_suggestion": "Bitte fügen Sie Ihre vorhandene CALCOM_LICENSE_KEY Umgebungsvariable hier ein.", "app_is_enabled": "{{appName}} ist aktiviert", @@ -1993,7 +1992,7 @@ "select_time": "Zeit auswählen", "select_date": "Datum auswählen", "see_all_available_times": "Alle verfügbaren Zeiten ansehen", - "org_team_names_example": "z.B. Marketing-Team", + "org_team_names_example_1": "z.B. Marketing-Team", "org_team_names_example_2": "z.B. Vertriebsteam", "org_team_names_example_3": "z. B. Design-Team", "org_team_names_example_4": "z.B. Engineering-Team", diff --git a/apps/web/public/static/locales/el/common.json b/apps/web/public/static/locales/el/common.json index 426720f46e..a6dec61fbb 100644 --- a/apps/web/public/static/locales/el/common.json +++ b/apps/web/public/static/locales/el/common.json @@ -212,8 +212,8 @@ "location": "Τοποθεσία", "address": "Διεύθυνση", "enter_address": "Εισάγετε διεύθυνση", - "yes": "ναι", - "no": "όχι", + "yes": "Ναί", + "no": "Όχι", "additional_notes": "Πρόσθετες σημειώσεις", "phone_number": "Αριθμός Τηλεφώνου", "attendee_phone_number": "Αριθμός Τηλεφώνου Συμμετέχοντα", @@ -274,5 +274,6 @@ "event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια", "label": "Ετικέτα", "edit": "Επεξεργασία", - "disable_guests": "Απενεργοποίηση επισκεπτών" + "disable_guests": "Απενεργοποίηση επισκεπτών", + "already_have_account": "Έχετε ήδη λογαριασμό;" } diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index fad3de8546..188d9d21df 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -78,7 +78,7 @@ "cannot_repackage_codebase": "You can not repackage or sell the codebase", "acquire_license": "Acquire a commercial license to remove these terms by emailing", "terms_summary": "Summary of terms", - "signing_up_terms":"By signing up, you agree to our <2>Terms of Service and <3>Privacy Policy.", + "signing_up_terms":"By signing up you agree to our <2>Terms & <3>Privacy Policy.", "open_env": "Open .env and agree to our License", "env_changed": "I've changed my .env", "accept_license": "Accept License", @@ -113,6 +113,7 @@ "requested_to_reschedule_subject_attendee": "Action Required Reschedule: Please book a new time for {{eventType}} with {{name}}", "hi_user_name": "Hi {{name}}", "ics_event_title": "{{eventType}} with {{name}}", + "please_book_a_time_sometime_later":"No one was available at the moment. Please book for sometime later", "new_event_subject": "New event: {{attendeeName}} - {{date}} - {{eventType}}", "join_by_entrypoint": "Join by {{entryPoint}}", "notes": "Notes", @@ -129,15 +130,20 @@ "meeting_id": "Meeting ID", "meeting_password": "Meeting Password", "meeting_url": "Meeting URL", + "meeting_url_not_found":"Meeting URL not found", + "token_not_found":"Token not found", + "some_other_host_already_accepted_the_meeting":"Some other host already accepted the meeting. Do you still want to join? <1>Continue to Meeting", "meeting_request_rejected": "Your meeting request has been rejected", "rejected_event_type_with_organizer": "Rejected: {{eventType}} with {{organizer}} on {{date}}", "hi": "Hi", "join_team": "Join team", "manage_this_team": "Manage this team", "team_info": "Team Info", + "join_meeting":"Join Meeting", "request_another_invitation_email": "If you prefer not to use {{toEmail}} as your {{appName}} email or already have a {{appName}} account, please request another invitation to that email.", "you_have_been_invited": "You have been invited to join the team {{teamName}}", "user_invited_you": "{{user}} invited you to join the {{entity}} {{team}} on {{appName}}", + "user_invited_you_to_subteam": "{{user}} invited you to join the team {{team}} of organization {{parentTeamName}} on {{appName}}", "hidden_team_member_title": "You are hidden in this team", "hidden_team_member_message": "Your seat is not paid for, either Upgrade to PRO or let the team owner know they can pay for your seat.", "hidden_team_owner_message": "You need a pro account to use teams, you are hidden until you upgrade.", @@ -435,6 +441,7 @@ "browse_api_documentation": "Browse our API documentation", "leverage_our_api": "Leverage our API for full control and customizability.", "create_webhook": "Create Webhook", + "instant_meeting_created":"Instant Meeting Created", "booking_cancelled": "Booking Canceled", "booking_rescheduled": "Booking Rescheduled", "recording_ready": "Recording Download Link Ready", @@ -553,8 +560,8 @@ "address": "Address", "enter_address": "Enter address", "in_person_attendee_address": "In Person (Attendee Address)", - "yes": "yes", - "no": "no", + "yes": "Yes", + "no": "No", "additional_notes": "Additional notes", "booking_fail": "Could not book the meeting.", "reschedule_fail": "Could not reschedule the meeting.", @@ -673,6 +680,7 @@ "default_duration": "Default duration", "default_duration_no_options": "Please choose available durations first", "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", + "multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)", "minutes": "Minutes", "round_robin": "Round Robin", "round_robin_description": "Cycle meetings between multiple team members.", @@ -685,6 +693,7 @@ "add_members": "Add members...", "no_assigned_members": "No assigned members", "assigned_to": "Assigned to", + "you_must_be_logged_in_to":"You must be logged in to {{url}}", "start_assigning_members_above": "Start assigning members above", "locked_fields_admin_description": "Members will not be able to edit this", "locked_fields_member_description": "This option was locked by the team admin", @@ -797,6 +806,9 @@ "requires_confirmation_description": "The booking needs to be manually confirmed before it is pushed to the integrations and a confirmation mail is sent.", "recurring_event": "Recurring Event", "recurring_event_description": "People can subscribe for recurring events", + "cannot_be_used_with_paid_event_types":"It cannot be used with paid event types", + "warning_payment_instant_meeting_event":"Instant Meetings are not supported with recurring events and payment apps yet", + "warning_instant_meeting_experimental":"Experimental: Instant Meeting Events are currently experimental.", "starting": "Starting", "disable_guests": "Disable Guests", "disable_guests_description": "Disable adding additional guests while booking.", @@ -879,6 +891,7 @@ "toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.", "connect_additional_calendar": "Connect additional calendar", "calendar_updated_successfully": "Calendar updated successfully", + "check_here":"Check here", "conferencing": "Conferencing", "calendar": "Calendar", "payments": "Payments", @@ -959,8 +972,6 @@ "enterprise_license_description": "To enable this feature, have an administrator go to <2>/auth/setup to enter a license key. If a license key is already in place, please contact <5>{{SUPPORT_MAIL_ADDRESS}} for help.", "enterprise_license_development": "You can test this feature on development mode. For production usage please have an administrator go to <2>/auth/setup to enter a license key.", "missing_license": "Missing License", - "signup_requires": "Commercial license required", - "signup_requires_description": "{{companyName}} currently does not offer a free open source version of the sign up page. To receive full access to the signup components you need to acquire a commercial license. For personal use we recommend the Prisma Data Platform or any other Postgres interface to create accounts.", "next_steps": "Next Steps", "acquire_commercial_license": "Acquire a commercial license", "the_infrastructure_plan": "The infrastructure plan is usage-based and has startup-friendly discounts.", @@ -1319,6 +1330,7 @@ "customize_your_brand_colors": "Customize your own brand colour into your booking page.", "pro": "Pro", "removes_cal_branding": "Removes any {{appName}} related brandings, i.e. 'Powered by {{appName}}.'", + "instant_meeting_with_title":"Instant Meeting with {{name}}", "profile_picture": "Profile Picture", "upload": "Upload", "add_profile_photo": "Add profile photo", @@ -1480,6 +1492,8 @@ "report_app": "Report app", "limit_booking_frequency": "Limit booking frequency", "limit_booking_frequency_description": "Limit how many times this event can be booked", + "limit_booking_only_first_slot": "Limit booking only first slot", + "limit_booking_only_first_slot_description": "Allow only the first slot of every day to be booked", "limit_total_booking_duration": "Limit total booking duration", "limit_total_booking_duration_description": "Limit total amount of time that this event can be booked", "add_limit": "Add Limit", @@ -1552,6 +1566,9 @@ "member_already_invited": "Member has already been invited", "already_in_use_error": "Username already in use", "enter_email_or_username": "Enter an email or username", + "enter_email": "Enter an email", + "enter_emails": "Enter emails", + "too_many_invites": "You are limited to inviting a maximum of {{nbUsers}} users at once.", "team_name_taken": "This name is already taken", "must_enter_team_name": "Must enter a team name", "team_url_required": "Must enter a team URL", @@ -1604,7 +1621,7 @@ "enable_apps": "Enable Apps", "enable_apps_description": "Enable apps that users can integrate with {{appName}}", "purchase_license": "Purchase a License", - "already_have_account":"I already have an account", + "already_have_account":"Already have an account?", "already_have_key": "I already have a key:", "already_have_key_suggestion": "Please copy your existing CALCOM_LICENSE_KEY environment variable here.", "app_is_enabled": "{{appName}} is enabled", @@ -1631,6 +1648,7 @@ "individual": "Individual", "all_bookings_filter_label": "All Bookings", "all_users_filter_label": "All Users", + "all_event_types_filter_label": "All Event Types", "your_bookings_filter_label": "Your Bookings", "meeting_url_variable": "Meeting url", "meeting_url_info": "The event meeting conference url", @@ -1727,10 +1745,12 @@ "organizer_timezone": "Organizer timezone", "email_user_cta": "View Invitation", "email_no_user_invite_heading_team": "You’ve been invited to join a {{appName}} team", + "email_no_user_invite_heading_subteam": "You’ve been invited to join a team of {{parentTeamName}} organization", "email_no_user_invite_heading_org": "You’ve been invited to join a {{appName}} organization", "email_no_user_invite_subheading": "{{invitedBy}} has invited you to join their team on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.", - "email_user_invite_subheading_team": "{{invitedBy}} has invited you to join their team `{{teamName}}` on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.", - "email_user_invite_subheading_org": "{{invitedBy}} has invited you to join their organization `{{teamName}}` on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your organization to schedule meetings without the email tennis.", + "email_user_invite_subheading_team": "{{invitedBy}} has invited you to join their team {{teamName}} on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.", + "email_user_invite_subheading_subteam": "{{invitedBy}} has invited you to join the team {{teamName}} in their organization {{parentTeamName}} on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.", + "email_user_invite_subheading_org": "{{invitedBy}} has invited you to join their organization {{teamName}} on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your organization to schedule meetings without the email tennis.", "email_no_user_invite_steps_intro": "We’ll walk you through a few short steps and you’ll be enjoying stress free scheduling with your {{entity}} in no time.", "email_no_user_step_one": "Choose your username", "email_no_user_step_two": "Connect your calendar account", @@ -1861,7 +1881,9 @@ "review_event_type": "Review Event Type", "looking_for_more_analytics": "Looking for more analytics?", "looking_for_more_insights": "Looking for more Insights?", + "filters": "Filters", "add_filter": "Add filter", + "remove_filters": "Clear all filters", "select_user": "Select User", "select_event_type": "Select Event Type", "select_date_range": "Select Date Range", @@ -2016,8 +2038,10 @@ "add_times_to_your_email": "Select a few available times and embed them in your Email", "select_time": "Select Time", "select_date": "Select Date", + "connecting_you_to_someone":"We are connecting you to someone.", + "please_do_not_close_this_tab":"Please do not close this tab", "see_all_available_times": "See all available times", - "org_team_names_example": "e.g. Marketing Team", + "org_team_names_example_1": "e.g. Marketing Team", "org_team_names_example_2": "e.g. Sales Team", "org_team_names_example_3": "e.g. Design Team", "org_team_names_example_4": "e.g. Engineering Team", @@ -2033,8 +2057,12 @@ "description_requires_booker_email_verification": "To ensure booker's email verification before scheduling events", "requires_confirmation_mandatory": "Text messages can only be sent to attendees when event type requires confirmation.", "organizations": "Organizations", + "upload_cal_video_logo":"Upload Cal Video Logo", + "update_cal_video_logo":"Update Cal Video Logo", + "cal_video_logo_upload_instruction":"To ensure your logo is visible against Cal video's dark background, please upload a light-colored image in PNG or SVG format to maintain transparency.", "org_admin_other_teams": "Other teams", "org_admin_other_teams_description": "Here you can see teams inside your organization that you are not part of. You can add yourself to them if needed.", + "not_part_of_org":"You are not part of any organization", "no_other_teams_found": "No other teams found", "no_other_teams_found_description": "There are no other teams in this organization.", "attendee_first_name_variable": "Attendee first name", @@ -2068,9 +2096,11 @@ "admin_org_notification_email_cta": "Go to Organizations Admin Settings", "org_has_been_processed": "Org has been processed", "org_error_processing": "There has been an error processing this organization", - "orgs_page_description": "A list of all organizations. Accepting an organization will allow all users with that email domain to sign up WITHOUT email verifciation.", + "orgs_page_description": "A list of all organizations. Accepting an organization will allow all users with that email domain to sign up WITHOUT email verification.", "unverified": "Unverified", + "verified": "Verified", "dns_missing": "DNS Missing", + "dns_configured": "DNS Configured", "mark_dns_configured": "Mark as DNS configured", "value": "Value", "your_organization_updated_sucessfully": "Your organization updated successfully", @@ -2080,7 +2110,7 @@ "oAuth": "OAuth", "recently_added":"Recently added", "connect_all_calendars":"Connect all your calendars", - "connect_all_calendars_description":"{{appName}} reads availability from all your existing calendars.", + "connect_all_calendars_description":"{{appName}} reads availability from all your existing calendars.", "workflow_automation":"Workflow automation", "workflow_automation_description":"Personalise your scheduling experience with workflows", "scheduling_for_your_team":"Workflow automation", @@ -2117,6 +2147,8 @@ "overlay_my_calendar":"Overlay my calendar", "overlay_my_calendar_toc":"By connecting to your calendar, you accept our privacy policy and terms of use. You may revoke access at any time.", "view_overlay_calendar_events":"View your calendar events to prevent clashed booking.", + "join_event_location":"Join {{eventLocationType}}", + "join_meeting":"Join Meeting", "troubleshooting":"Troubleshooting", "calendars_were_checking_for_conflicts":"Calendars we’re checking for conflicts", "availabilty_schedules":"Availability schedules", @@ -2124,6 +2156,7 @@ "manage_availability_schedules":"Manage availability schedules", "lock_timezone_toggle_on_booking_page": "Lock timezone on booking page", "description_lock_timezone_toggle_on_booking_page" : "To lock the timezone on booking page, useful for in-person events.", + "event_setup_multiple_payment_apps_error": "You can only have one payment app enabled per event type.", "number_in_international_format": "Please enter number in international format.", "install_calendar":"Install Calendar", "branded_subdomain": "Branded Subdomain", @@ -2141,9 +2174,21 @@ "enterprise_description": "Upgrade to Enterprise to create your Organization", "create_your_org": "Create your Organization", "create_your_org_description": "Upgrade to Enterprise and receive a subdomain, unified billing, Insights, extensive whitelabeling and more", + "other_payment_app_enabled": "You can only enable one payment app per event type", + "admin_delete_organization_description": "
      • Teams that are member of this organization will also be deleted along with their event-types
      • Users that were part of the organization will not be deleted and their event-types will also remain intact.
      • Usernames would be changed to allow them to exist outside the organization
      ", + "admin_delete_organization_title": "Delete organization?", + "published": "Published", + "unpublished": "Unpublished", + "publish": "Publish", + "org_publish_error": "Organization could not be published", "troubleshooter_tooltip": "Open the troubleshooter and figure out what is wrong with your schedule", "need_help": "Need help?", "troubleshooter": "Troubleshooter", "please_install_a_calendar": "Please install a calendar", + "instant_tab_title": "Instant Booking", + "instant_event_tab_description": "Let people book immediately", + "uprade_to_create_instant_bookings": "Upgrade to Enterprise and let guests join an instant call that attendees can jump straight into. This is only for team event types", + "dont_want_to_wait": "Don't want to wait?", + "meeting_started": "Meeting Started", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/apps/web/public/static/locales/es/common.json b/apps/web/public/static/locales/es/common.json index 112b03d865..ff27586a46 100644 --- a/apps/web/public/static/locales/es/common.json +++ b/apps/web/public/static/locales/es/common.json @@ -538,7 +538,6 @@ "enter_address": "Ingrese la dirección", "in_person_attendee_address": "En persona (dirección del asistente)", "yes": "Sí", - "no": "No", "additional_notes": "Notas Adicionales", "booking_fail": "No se ha podido reservar la reunión.", "reschedule_fail": "No se ha podido reprogramar la reunión.", @@ -941,8 +940,6 @@ "enterprise_license_description": "Para habilitar esta función, obtenga una clave de despliegue en la consola {{consoleUrl}} y añádala a su .env como CALCOM_LICENSE_KEY. Si su equipo ya tiene una licencia, póngase en contacto con {{supportMail}} para obtener ayuda.", "enterprise_license_development": "Puede probar esta función en el modo de desarrollo. Para el uso de producción, haga que un administrador vaya a <2>/auth/setup para ingresar una clave de licencia.", "missing_license": "Falta la licencia", - "signup_requires": "Licencia comercial requerida", - "signup_requires_description": "Actualmente, {{companyName}} no ofrece una versión gratuita de código abierto de la página de registro. Para recibir acceso completo a los componentes de registro es necesario adquirir una licencia comercial. Para uso personal, recomendamos la Plataforma de Datos Prisma o cualquier otra interfaz Postgres para crear cuentas.", "next_steps": "Pasos siguientes", "acquire_commercial_license": "Adquirir una licencia comercial", "the_infrastructure_plan": "El plan de infraestructura está basado en el uso y tiene descuentos favorables para empresas emergentes.", @@ -1581,6 +1578,7 @@ "enable_apps": "Activar aplicaciones", "enable_apps_description": "Habilite las aplicaciones que los usuarios pueden integrar con {{appName}}", "purchase_license": "Compre una licencia", + "already_have_account": "¿Ya tienes una cuenta?", "already_have_key": "Ya tengo una clave:", "already_have_key_suggestion": "Copie aquí su variable de entorno CALCOM_LICENSE_KEY existente.", "app_is_enabled": "{{appName}} está activada", @@ -1993,7 +1991,7 @@ "select_time": "Seleccione la hora", "select_date": "Seleccione la fecha", "see_all_available_times": "Ver todas las horas disponibles", - "org_team_names_example": "ej. Equipo de marketing", + "org_team_names_example_1": "ej. Equipo de marketing", "org_team_names_example_2": "ej. Equipo de ventas", "org_team_names_example_3": "ej. Equipo de diseño", "org_team_names_example_4": "ej. Equipo de ingeniería", diff --git a/apps/web/public/static/locales/eu/common.json b/apps/web/public/static/locales/eu/common.json index 210dbbb6a0..c96ebd7f0f 100644 --- a/apps/web/public/static/locales/eu/common.json +++ b/apps/web/public/static/locales/eu/common.json @@ -367,8 +367,8 @@ "address": "Helbidea", "enter_address": "Sartu helbidea", "in_person_attendee_address": "Aurrez aurre (partaidearen helbidean)", - "yes": "bai", - "no": "ez", + "yes": "Bai", + "no": "Ez", "additional_notes": "Ohar gehigarriak", "booking_fail": "Ezin izan da bilera erreserbatu.", "reschedule_fail": "Ezin izan da bilera berrantolatu.", @@ -752,6 +752,7 @@ "enter_email_or_username": "Sartu email edo erabiltzaile izen bat", "team_name_taken": "Izen hau dagoeneko hartua dago", "must_enter_team_name": "Taldearentzat izen bat behar da", + "already_have_account": "Baduzu kontua dagoeneko?", "fill_this_field": "Mesedez, bete ezazu eremu hau", "options": "Aukerak", "add_an_option": "Gehitu aukera bat", diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index 8b6abb135d..e5a5a937f5 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -541,8 +541,8 @@ "address": "Adresse", "enter_address": "Entrer une adresse", "in_person_attendee_address": "En personne (adresse du participant)", - "yes": "oui", - "no": "non", + "yes": "Oui", + "no": "Non", "additional_notes": "Notes supplémentaires", "booking_fail": "Le rendez-vous n'a pas pu être réservé.", "reschedule_fail": "Le rendez-vous n'a pas pu être replanifié.", @@ -660,6 +660,7 @@ "default_duration": "Durée par défaut", "default_duration_no_options": "Veuillez d'abord choisir les durées disponibles", "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", + "multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)", "minutes": "minutes", "round_robin": "Round-robin", "round_robin_description": "Alternez vos rendez-vous entre plusieurs membres d'équipe.", @@ -946,8 +947,6 @@ "enterprise_license_description": "Pour activer cette fonctionnalité, demandez à un administrateur d'accéder à <2>/auth/setup pour saisir une clé de licence. Si une clé de licence est déjà en place, veuillez contacter <5>{{SUPPORT_MAIL_ADDRESS}} pour obtenir de l'aide.", "enterprise_license_development": "Vous pouvez tester cette fonctionnalité en mode développement. Pour une utilisation en production, veuillez demander à un administrateur d'aller à <2>/auth/setup pour entrer une clé de licence.", "missing_license": "Licence manquante", - "signup_requires": "Licence commerciale requise", - "signup_requires_description": "{{companyName}} ne propose actuellement pas de version open source gratuite de la page d'inscription. Pour recevoir un accès complet aux composants d'inscription, vous devez acquérir une licence commerciale. Pour un usage personnel, nous recommandons la plateforme de données Prisma ou toute autre interface Postgres pour créer des comptes.", "next_steps": "Prochaines étapes", "acquire_commercial_license": "Obtenir une licence commerciale", "the_infrastructure_plan": "Le plan d'infrastructure est basé sur l'utilisation et propose des réductions adaptées aux startups.", @@ -1843,6 +1842,7 @@ "looking_for_more_analytics": "Vous cherchez plus d'analyses ?", "looking_for_more_insights": "Vous cherchez plus de statistiques ?", "add_filter": "Ajouter un filtre", + "remove_filters": "Effacer les filtres", "select_user": "Sélectionner un utilisateur", "select_event_type": "Sélectionner un type d'événement", "select_date_range": "Sélectionner une plage de dates", @@ -1998,7 +1998,7 @@ "select_time": "Sélectionner un créneau", "select_date": "Sélectionner une date", "see_all_available_times": "Voir tous les créneaux disponibles", - "org_team_names_example": "p. ex. Équipe marketing", + "org_team_names_example_1": "p. ex. Équipe marketing", "org_team_names_example_2": "p. ex. Équipe de vente", "org_team_names_example_3": "p. ex. Équipe de design", "org_team_names_example_4": "p. ex. Équipe d'ingénierie", @@ -2085,11 +2085,17 @@ "overlay_my_calendar": "Superposer mon calendrier", "overlay_my_calendar_toc": "En vous connectant à votre calendrier, vous acceptez notre politique de confidentialité et nos conditions d'utilisation. Vous pouvez révoquer cet accès à tout moment.", "view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.", + "calendars_were_checking_for_conflicts": "Calendriers dont nous vérifions les conflits", + "availabilty_schedules": "Horaires de disponibilité", + "manage_calendars": "Gérer les calendriers", + "manage_availability_schedules": "Gérer les horaires de disponibilité", "lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation", "description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.", "number_in_international_format": "Veuillez entrer le numéro au format international.", "extensive_whitelabeling": "Marque blanche étendue", "unlimited_teams": "Équipes illimitées", + "troubleshooter_tooltip": "Ouvrez l'outil de dépannage et déterminez ce qui ne va pas avec votre planning", "need_help": "Besoin d'aide ?", + "troubleshooter": "Dépannage", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/apps/web/public/static/locales/he/common.json b/apps/web/public/static/locales/he/common.json index 6f8b7b8d40..1731499962 100644 --- a/apps/web/public/static/locales/he/common.json +++ b/apps/web/public/static/locales/he/common.json @@ -56,6 +56,16 @@ "a_refund_failed": "ההחזר הכספי נכשל", "awaiting_payment_subject": "ממתין לתשלום: {{title}} ב- {{date}}", "meeting_awaiting_payment": "התשלום על הפגישה שלך טרם בוצע", + "dark_theme_contrast_error": "צבע ערכת עיצוב כהה ללא עובר בדיקת ניגודיות. אנו ממליצים לשנות את הצבע הזה כדי שהכפתורים שלך יהיו יותר בולטים.", + "light_theme_contrast_error": "צבע ערכת עיצוב בהירה ללא עובר בדיקת ניגודיות. אנו ממליצים לשנות את הצבע הזה כדי שהכפתורים שלך יהיו יותר בולטים.", + "payment_not_created_error": "אי אפשר ליצור תשלום", + "couldnt_charge_card_error": "לא ניתן לחייב את התשלום בכרטיס", + "no_available_users_found_error": "לא נמצאו משתמשים זמינים. אפשר לנסות ליצור חלון זמן נוסף?", + "request_body_end_time_internal_error": "שגיאה פנימית. גוף הבקשה לא מכיל זמן סיום", + "create_calendar_event_error": "לא ניתן ליצור אירוע לוח שנה בלוח השנה של הגוף המארגן", + "update_calendar_event_error": "לא ניתן לעדכן אירוע לוח שנה.", + "delete_calendar_event_error": "לא ניתן למחוק אירוע לוח שנה.", + "already_signed_up_for_this_booking_error": "כבר נרשמת להזמנה הזאת.", "help": "עזרה", "price": "מחיר", "paid": "שולם", @@ -67,6 +77,7 @@ "cannot_repackage_codebase": "לא ניתן לארוז מחדש או למכור את בסיס הקוד", "acquire_license": "כדי להסיר את התנאים האלה, ניתן לקנות רישיון מסחרי על ידי שליחת דוא\"ל", "terms_summary": "סיכום התנאים", + "signing_up_terms": "הרשמה מהווה את הסכמתך ל<2>תנאים ול<3>מדיניות הפרטיות שלנו.", "open_env": "יש לפתוח את ‎.env ולאשר את הרישיון שלנו", "env_changed": "שיניתי את ה-‎.env שלי", "accept_license": "אישור הרישיון", @@ -101,6 +112,7 @@ "requested_to_reschedule_subject_attendee": "הפעולה חייבה קביעת מועד חדש: יש לקבוע מועד חדש עבור {{eventType}} עם {{name}}", "hi_user_name": "שלום {{name}}", "ics_event_title": "{{eventType}} עם {{name}}", + "please_book_a_time_sometime_later": "אף אחד לא זמין כרגע. נא לקבוע זימון למועד אחר", "new_event_subject": "אירוע חדש: {{attendeeName}} - {{date}} - {{eventType}}", "join_by_entrypoint": "ניתן להצטרף עד {{entryPoint}}", "notes": "הערות", @@ -117,15 +129,20 @@ "meeting_id": "מזהה הפגישה", "meeting_password": "סיסמת הפגישה", "meeting_url": "כתובת ה-URL של הפגישה", + "meeting_url_not_found": "כתובת הפגישה לא נמצאה", + "token_not_found": "האסימון לא נמצא", + "some_other_host_already_accepted_the_meeting": "מארח אחר כבר קיבל את הפגישה. בכל זאת מעניין אותך להצטרף? <1>להמשיך לפגישה", "meeting_request_rejected": "בקשת הפגישה שלך נדחתה", "rejected_event_type_with_organizer": "נדחה: {{eventType}} עם {{organizer}} בתאריך {{date}}", "hi": "שלום", "join_team": "להצטרף לצוות", "manage_this_team": "לנהל את הצוות הנוכחי", "team_info": "מידע על הצוות", + "join_meeting": "הצטרפות לפגישה", "request_another_invitation_email": "אם אתה מעדיף לא להשתמש בכתובת {{toEmail}} ככתובת הדוא\"ל שלך עבור {{appName}} או שכבר יש לך חשבון {{appName}}, יש לבקש לקבל הזמנה נוספת לכתובת הדוא\"ל הרצויה.", "you_have_been_invited": "הוזמנת להצטרף לצוות {{teamName}}", "user_invited_you": "{{user}} הזמין/ה אותך להצטרף ל{{entity}} {{team}} ב-{{appName}}", + "user_invited_you_to_subteam": "הוזמנת על ידי {{user}} להצטרף לצוות {{team}} של הארגון {{parentTeamName}} אצל {{appName}}", "hidden_team_member_title": "אתה מוסתר בצוות זה", "hidden_team_member_message": "לא בוצע תשלום עבור המקום שלך. ניתן לשדרג ל-PRO או ליידע את הבעלים של הצוות שהוא או היא יכולים לשלם עבור המקום שלך.", "hidden_team_owner_message": "נדרש חשבון Pro כדי להשתמש בתכונות הצוותים. תהיה מוסתר עד שתבצע שידרוג.", @@ -229,6 +246,7 @@ "reset_your_password": "הגדר/י את הסיסמה החדשה שלך לפי ההוראות שנשלחו אל כתובת הדוא\"ל שלך.", "email_change": "התחבר/י שוב עם כתובת הדוא\"ל החדשה והסיסמה.", "create_your_account": "צור את החשבון שלך", + "create_your_calcom_account": "יצירת החשבון שלך ב־Cal.com", "sign_up": "הרשמה", "youve_been_logged_out": "יצאת מהמערכת", "hope_to_see_you_soon": "מקווים לראותך שוב בקרוב!", @@ -266,6 +284,9 @@ "nearly_there_instructions": "דבר אחרון: תיאור קצר אודותיך/ייך בתוספת תמונה עוזרים מאוד להשיג הזמנות ומאפשרים לאנשים לדעת עם מי הם עומדים להיפגש.", "set_availability_instructions": "הגדר טווחי זמן שבהם אתה זמין באופן קבוע. ניתן יהיה ליצור טווחי זמן נוספים מאוחר יותר ולהקצות אותם ללוחות שנה אחרים.", "set_availability": "ציין את הזמינות שלך", + "set_availbility_description": "הגדרת תזמונים למועדים שמתאים לך לקבוע בהם זימונים.", + "share_a_link_or_embed": "שיתוף קישור או הטמעה", + "share_a_link_or_embed_description": "שיתוף הקישור שלך אל {{appName}} באתר שלך.", "availability_settings": "הגדרות זמינוּת", "continue_without_calendar": "להמשיך בלי לוח שנה", "continue_with": "להמשיך עם {{appName}}", @@ -419,6 +440,7 @@ "browse_api_documentation": "עיון במסמכי ממשק תכנות היישומים (API) שלנו", "leverage_our_api": "מומלץ להיעזר בממשק תכנות היישומים (API) שלנו לקבלת שליטה מלאה ויכולת התאמה אישית.", "create_webhook": "יצירת Webhook", + "instant_meeting_created": "נוצרה פגישה מיידית", "booking_cancelled": "ההזמנה בוטלה", "booking_rescheduled": "מועד ההזמנה השתנה", "recording_ready": "הקישור להורדת ההקלטה מוכן", @@ -606,6 +628,7 @@ "hide_book_a_team_member_description": "הסתר/י את הלחצן לשריון זמן של חבר/ת צוות מהדפים הציבוריים שלך.", "danger_zone": "אזור מסוכן", "account_deletion_cannot_be_undone": "יש לנקוט זהירות. מחיקת חשבון היא פעולה בלתי הפיכה.", + "team_deletion_cannot_be_undone": "יש לנקוט במשנה זהירות. מחיקת צוות היא פעולה בלתי הפיכה", "back": "הקודם", "cancel": "ביטול", "cancel_all_remaining": "לבטל את כל הנותרים", @@ -656,6 +679,7 @@ "default_duration": "משך הזמן המוגדר כברירת מחדל", "default_duration_no_options": "ראשית, אנא בחר משך זמינות", "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", + "multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)", "minutes": "דקות", "round_robin": "לפי תורות", "round_robin_description": "פגישות מחזוריות בין חברי צוות מרובים.", @@ -668,6 +692,7 @@ "add_members": "הוספת חברים...", "no_assigned_members": "לא הוקצה אף חבר", "assigned_to": "הוקצה ל", + "you_must_be_logged_in_to": "חובה להיכנס אל {{url}}", "start_assigning_members_above": "התחל/י להקצות חברים למעלה", "locked_fields_admin_description": "חברים לא יוכלו לערוך את זה", "locked_fields_member_description": "מנהל הצוות נעל את האפשרות הזו", @@ -780,6 +805,9 @@ "requires_confirmation_description": "יש לאשר את ההזמנה באופן ידני כדי שניתן יהיה להעביר אותה אל השילובים ולשלוח הודעת אישור בדוא״ל.", "recurring_event": "אירוע חוזר", "recurring_event_description": "אנשים יכולים להירשם לאירועים חוזרים", + "cannot_be_used_with_paid_event_types": "אי אפשר להשתמש בזה עם סוגי פגישות בתשלום", + "warning_payment_instant_meeting_event": "אין תמיכה בפגישות מיידיות עם אירועים מחזוריים ויישומוני תשלום", + "warning_instant_meeting_experimental": "ניסיוני: פגישות מיידיות הן ניסיוניות כרגע.", "starting": "מועד התחלה", "disable_guests": "השבתת אורחים", "disable_guests_description": "השבת את האפשרות להוסיף אורחים נוספים בעת ביצוע הזמנה.", @@ -847,6 +875,7 @@ "next_step": "לדלג על שלב זה", "prev_step": "לשלב הקודם", "install": "התקנה", + "start_paid_trial": "התחלת ניסיון בחינם", "installed": "מותקן", "active_install_one": "התקנה פעילה {{count}}", "active_install_other": "{{count}} התקנות פעילות", @@ -941,8 +970,6 @@ "enterprise_license_description": "כדי להפעיל את יכולת זו, בקש ממנהל לגשת אל הקישור {{setupUrl}} והקלדת הרישיון. אם כבר יש רישיון מוגדר, צור קשר עם {{supportMail}} לעזרה.", "enterprise_license_development": "ניתן לבדוק את התכונה הזו במצב פיתוח. לשימוש מצב הייצור, מנהל/ת מערכת צריך/ה לעבור אל <2>/auth/setup ולהזין מפתח רישיון.", "missing_license": "חסר רישיון", - "signup_requires": "נדרש רישיון לשימוש מסחרי", - "signup_requires_description": "בשלב זה, {{companyName}} אינה מציעה גרסת קוד פתוח חינמית של דף ההרשמה. לקבלת גישה מלאה לרכיבי ההרשמה, תצטרך לקנות רישיון לשימוש מסחרי. לצרכים אישיים אנו ממליצים על Prisma Data Platform או כל ממשק Postgres אחר ליצירת חשבונות.", "next_steps": "השלבים הבאים", "acquire_commercial_license": "רכישת רישיון לשימוש מסחרי", "the_infrastructure_plan": "חבילת הבסיס מחויבת לפי שימוש ומוצעת בהנחות למתחילים.", @@ -1034,6 +1061,7 @@ "user_impersonation_heading": "התחזות למשתמשים", "user_impersonation_description": "מצב זה מאפשר לצוות התמיכה שלנו להתחבר באופן זמני לחשבונך כדי שנוכל לפתור במהירות את הבעיות שתדווח/י לנו עליהם.", "team_impersonation_description": "מאפשר לבעלים של הצוות/מנהלים להיכנס זמנית בשמך.", + "cal_signup_description": "בחינם למשתמשים פרטיים. התוכנית לצוותים מאפשרת יכולות לשיתופי פעולה.", "make_team_private": "להגדיר את הצוות כפרטי", "make_team_private_description": "כשההגדרה הזו מופעלת, חברי הצוות שלך לא יוכלו לראות חברי צוות אחרים.", "you_cannot_see_team_members": "אין לך אפשרות לראות את כל חברי הצוות של צוות פרטי.", @@ -1093,6 +1121,7 @@ "developer_documentation": "מסמכי מפתחים", "get_in_touch": "יצירת קשר", "contact_support": "פנייה לתמיכה", + "premium_support": "תמיכת פרימיום", "community_support": "תמיכת קהילה", "feedback": "משוב", "submitted_feedback": "תודה על המשוב!", @@ -1299,6 +1328,7 @@ "customize_your_brand_colors": "בצע התאמה אישית של דף ההזמנות שלך עם צבעי מותג משלך.", "pro": "Pro", "removes_cal_branding": "הסרת מיתוגים הקשורים ל-{{appName}}, כגון 'מופעל על ידי {{appName}}'", + "instant_meeting_with_title": "פגישה מיידית עם {{name}}", "profile_picture": "תמונת פרופיל", "upload": "העלאה", "add_profile_photo": "הוספת תמונת פרופיל", @@ -1354,6 +1384,7 @@ "event_name_info": "שם סוג האירוע", "event_date_info": "תאריך האירוע", "event_time_info": "שעת ההתחלה של האירוע", + "event_type_not_found": "EventType לא נמצא", "location_info": "מיקום האירוע", "additional_notes_info": "הערות נוספות להזמנה", "attendee_name_info": "שם האדם שביצע את ההזמנה", @@ -1394,6 +1425,7 @@ "slot_length": "אורך חלון הזמן", "booking_appearance": "מראה ההזמנה", "appearance_team_description": "ניהול ההגדרות של מראה הזמנות הצוות שלך", + "appearance_org_description": "ניהול ההגדרות למראה ההזמנות של הארגון שלך", "only_owner_change": "רק הבעלים של הצוות יכולים לבצע שינויים בהזמנת הצוות ", "team_disable_cal_branding_description": "הסרת מיתוגים הקשורים ל-{{appName}}, כגון 'מופעל על ידי {{appName}}'", "invited_by_team": "הוזמנת על ידי {{teamName}} להצטרף לצוות בתפקיד {{role}}", @@ -1458,6 +1490,8 @@ "report_app": "דיווח על האפליקציה", "limit_booking_frequency": "הגבלת תדירות ההזמנות", "limit_booking_frequency_description": "הגבלת מספר הפעמים שבהן ניתן להזמין את האירוע הזה", + "limit_booking_only_first_slot": "להגביל את ההזמנה לחלון הפנוי הראשון בלבד", + "limit_booking_only_first_slot_description": "לאפשר להזמין רק את החלון הפנוי הראשון בכל יום", "limit_total_booking_duration": "הגבל משך תזמון כולל", "limit_total_booking_duration_description": "הגבלת משך הזמן הכולל שבו ניתן להזמין את האירוע הזה", "add_limit": "הוספת הגבלה", @@ -1525,10 +1559,14 @@ "your_org_disbanded_successfully": "פירוק הארגון שלך בוצע בהצלחה", "error_creating_team": "אירעה שגיאה במהלך יצירת הצוות", "you": "את/ה", + "or_continue_with": "או להמשיך עם", "resend_email": "לשלוח שוב את הדוא״ל", "member_already_invited": "החבר כבר הוזמן", "already_in_use_error": "שם המשתמש כבר קיים", "enter_email_or_username": "יש להזין כתובת דוא\"ל או שם משתמש", + "enter_email": "נא למלא כתובת דוא״ל", + "enter_emails": "נא למלא כתובות דוא״ל", + "too_many_invites": "חלה מגבלה על הזמנת עד {{nbUsers}} משתמשים בבת אחת.", "team_name_taken": "השם הזה כבר תפוס", "must_enter_team_name": "יש להזין שם צוות", "team_url_required": "יש להזין כתובת URL של הצוות", @@ -1581,6 +1619,7 @@ "enable_apps": "הפעלת אפליקציות", "enable_apps_description": "הפעל/הפעילי אפליקציות שמשתמשים יוכלו לשלב עם {{appName}}", "purchase_license": "רכוש רישיון", + "already_have_account": "כבר יש לך חשבון?", "already_have_key": "כבר יש לי מפתח:", "already_have_key_suggestion": "אנא העתק את משתנה הסביבה CALCOM_LICENSE_KEY הקיים שלך לכאן.", "app_is_enabled": "האפליקציה {{appName}} מופעלת", @@ -1607,6 +1646,7 @@ "individual": "משתמש בודד", "all_bookings_filter_label": "כל ההזמנות", "all_users_filter_label": "כל המשתמשים", + "all_event_types_filter_label": "כל סוגי האירועים", "your_bookings_filter_label": "התזמונים שלך", "meeting_url_variable": "כתובת ה-URL של הפגישה", "meeting_url_info": "כתובת ה-URL של שיחת הוועידה באירוע", @@ -1703,6 +1743,7 @@ "organizer_timezone": "מארגן אזורי זמן", "email_user_cta": "צפה בהזמנה", "email_no_user_invite_heading_team": "הוזמנת להצטרף לצוות ב-{{appName}}", + "email_no_user_invite_heading_subteam": "הוזמנת להצטרף לצוות בארגון {{parentTeamName}}", "email_no_user_invite_heading_org": "הוזמנת להצטרף לארגון ב-{{appName}}", "email_no_user_invite_subheading": "{{invitedBy}} הזמין אותך להצטרף לצוות שלו ב- {{appName}}. {{appName}} הינה מתזמן זימונים שמאפשר לך ולצוות שלך לזמן פגישות בלי כל הפינג פונג במיילים.", "email_user_invite_subheading_team": "{{invitedBy}} הזמין/ה אותך להצטרף לצוות שלו/ה בשם '{{teamName}}' באפליקציה {{appName}}. אפליקציית {{appName}} היא כלי לקביעת מועדים לאירועים שמאפשר לך ולצוות שלך לתזמן פגישות בלי כל הפינג פונג במיילים.", @@ -1837,7 +1878,9 @@ "review_event_type": "בדיקת סוג האירוע", "looking_for_more_analytics": "מחפש עוד מידע אנליטי?", "looking_for_more_insights": "רוצה עוד Insights?", + "filters": "מסננים", "add_filter": "הוסף סנן", + "remove_filters": "ניקוי כל המסננים", "select_user": "בחר משתמש", "select_event_type": "בחר סוג ארוע", "select_date_range": "בחר טווח תאריכים", @@ -1992,8 +2035,10 @@ "add_times_to_your_email": "בחר/י כמה מועדים פנויים והטבע/י אותם בדוא\"ל", "select_time": "בחירת שעה", "select_date": "בחירת תאריך", + "connecting_you_to_someone": "אנו מחברים אותך למישהו או מישהי.", + "please_do_not_close_this_tab": "נא לא לסגור את הלשונית הזאת", "see_all_available_times": "לצפייה בכל המועדים הפנויים", - "org_team_names_example": "לדוגמה, מחלקת שיווק", + "org_team_names_example_1": "לדוגמה, מחלקת שיווק", "org_team_names_example_2": "לדוגמה, מחלקת מכירות", "org_team_names_example_3": "לדוגמה, מחלקת עיצוב", "org_team_names_example_4": "לדוגמה, מחלקת הנדסה", @@ -2009,8 +2054,12 @@ "description_requires_booker_email_verification": "כדי להבטיח אימות של כתובת הדוא\"ל של המזמין לפני תזמון אירועים", "requires_confirmation_mandatory": "ניתן לשלוח הודעות טקסט למשתתפים רק כאשר סוג האירוע מחייב אישור.", "organizations": "ארגונים", + "upload_cal_video_logo": "העלאת סרטון לוגו ל־Cal", + "update_cal_video_logo": "עדכון סרטון לוגו ל־Cal", + "cal_video_logo_upload_instruction": "כדי לוודא שהלוגו שלך גלוי כנגד הרקע הכהה של הסרטון של Cal, נא להעלות תמונה בצבעים בהירים מהסוגים PNG או SVG כדי שהחלקים המתאימים יישארו שקופים.", "org_admin_other_teams": "צוותים אחרים", "org_admin_other_teams_description": "כאן תוכל/י לראות צוותים בארגון שאינך שייך/ת אליהם. יש לך אפשרות להוסיף את עצמך, במקרה הצורך.", + "not_part_of_org": "אינך חלק משום ארגון", "no_other_teams_found": "לא נמצא אף צוות אחר", "no_other_teams_found_description": "אין צוותים אחרים בארגון הזה.", "attendee_first_name_variable": "השם הפרטי של המשתתף", @@ -2046,7 +2095,9 @@ "org_error_processing": "היתה שגיאה בעיבוד של ארגון זה", "orgs_page_description": "רשימה של כל הארגונים. קבלת ארגון תאפשר לכל המשתמשים מאותו דומיין דוא\"ל להירשם בלי להצטרך לבצע אימות של כתובת הדוא\"ל.", "unverified": "לא אומת", + "verified": "מאומת", "dns_missing": "DNS חסר", + "dns_configured": "DNS מוגדר", "mark_dns_configured": "סימון כי DNS הוגדר", "value": "ערך", "your_organization_updated_sucessfully": "עדכון הארגון שלך בוצע בהצלחה", @@ -2056,7 +2107,9 @@ "oAuth": "OAuth", "recently_added": "נוספו לאחרונה", "connect_all_calendars": "חבר את כל לוחות השנה שלך", + "connect_all_calendars_description": "{{appName}} קורא את הזמינות מכל לוחות השנה הקיימים שלך.", "workflow_automation": "אוטומצית תהליך עבודה", + "workflow_automation_description": "אפשר לכוון את חוויית התזמון שלך עם תהליכי עבודה", "scheduling_for_your_team": "אוטומצית תהליך עבודה", "no_members_found": "לא נמצא אף חבר", "event_setup_length_error": "הגדרת אירוע: משך הזמן חייב להיות לפחות דקה אחת.", @@ -2090,9 +2143,39 @@ "overlay_my_calendar": "הצג את לוח השנה שלי בשכבת-על", "overlay_my_calendar_toc": "על ידי חיבור אל לוח השנה שלך, את/ה מקבל/ת את מדיניות הפרטיות ואת תנאי השימוש שלנו. אפשר לשלול את הגישה בכל שלב.", "view_overlay_calendar_events": "ראה/י את האירועים שלך בלוח השנה כדי למנוע התנגשות בהזמנות.", + "join_event_location": "הצטרפות אל {{eventLocationType}}", + "troubleshooting": "פתרון בעיות", + "calendars_were_checking_for_conflicts": "לוחות השנה לא בודקים סתירות", + "manage_calendars": "ניהול לוחות שנה", "lock_timezone_toggle_on_booking_page": "נעילת אזור הזמן בדף ההזמנות", "description_lock_timezone_toggle_on_booking_page": "כדי לנעול את אזור הזמן בדף ההזמנות – שימושי לאירועים אישיים.", + "install_calendar": "התקנת לוח שנה", + "branded_subdomain": "תת־תחום ממותג", + "branded_subdomain_description": "קבלת תת־תחום ממותג משלך, כגון acme.cal.com", + "org_insights": "תובנות כלל־ארגוניות", "extensive_whitelabeling": "תהליך הטמעה והנדסת תמיכה אישי", + "unlimited_teams": "כמות בלתי מוגבלת של צוותים", + "unlimited_teams_description": "אפשר להוסיף כמה תת־צוותים שדרושים לארגון שלך", + "unified_billing": "חיוב מאוחד", + "unified_billing_description": "ניתן להוסיף כרטיס אשראי אחד כדי לשלם על כל המינויים של הצוות שלך", + "advanced_managed_events": "סוגי אירועים מנוהלים מתקדמים", + "advanced_managed_events_description": "אפשר להוסיף כרטיס אשראי יחיד כדי לשלם עבור כל המינויים של הצוות שלך", + "enterprise_description": "יש לשדרג לרישיון תאגידי כדי ליצור את הארגון שלך", + "create_your_org": "יצירת הארגון שלך", + "create_your_org_description": "אפשר לשדרג לרישיון תאגידי ולקבל תת־תחום, חיוב מאוחד, תובנות, שינוי מיתוג נרחב ועוד", + "other_payment_app_enabled": "אפשר להפעיל רק יישומון תשלום אחד לכל סוג אירוע", + "admin_delete_organization_title": "למחוק את הארגון?", + "published": "מפורסם", + "unpublished": "לא מפורסם", + "publish": "פרסום", + "org_publish_error": "אי אפשר לפרסם את הארגון", "need_help": "צריך עזרה?", + "troubleshooter": "פותר בעיות", + "please_install_a_calendar": "נא להתקין לוח שנה", + "instant_tab_title": "הזמנה מיידית", + "instant_event_tab_description": "לאפשר לאנשים ליצור הזמנות מיידית", + "uprade_to_create_instant_bookings": "ניתן לשדג לרישיון התאגידי ולאפשר למשתמשים להצטרף לשיחה מיידית שמשתתפים יכולים לקפוץ ישירות אליה. זה מיועד רק לסוגי אירועים של צוותים", + "dont_want_to_wait": "לא רוצה להמתין?", + "meeting_started": "הפגישה החלה", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/apps/web/public/static/locales/hr/common.json b/apps/web/public/static/locales/hr/common.json index b37febdb6d..cbdfcf4316 100644 --- a/apps/web/public/static/locales/hr/common.json +++ b/apps/web/public/static/locales/hr/common.json @@ -333,5 +333,6 @@ "dark": "Tamna", "automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba", "user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije", - "full_name": "Puno ime" + "full_name": "Puno ime", + "already_have_account": "Već imate račun?" } diff --git a/apps/web/public/static/locales/hu/common.json b/apps/web/public/static/locales/hu/common.json index 519ce8b26d..e8fdf70ff4 100644 --- a/apps/web/public/static/locales/hu/common.json +++ b/apps/web/public/static/locales/hu/common.json @@ -123,8 +123,8 @@ "your_name": "Neved", "email_address": "Email cím", "location": "Helyszín", - "yes": "igen", - "no": "nem", + "yes": "Igen", + "no": "Nem", "additional_notes": "Egyéb jegyzetek", "phone_number": "Telefonszám", "or": "VAGY", diff --git a/apps/web/public/static/locales/it/common.json b/apps/web/public/static/locales/it/common.json index 53e854ec8b..5c6dc04dee 100644 --- a/apps/web/public/static/locales/it/common.json +++ b/apps/web/public/static/locales/it/common.json @@ -537,8 +537,7 @@ "address": "Indirizzo", "enter_address": "Immettere l'indirizzo", "in_person_attendee_address": "Di persona (indirizzo del partecipante)", - "yes": "sì", - "no": "no", + "yes": "Sì", "additional_notes": "Note aggiuntive", "booking_fail": "Impossibile prenotare la riunione.", "reschedule_fail": "Impossibile riprogrammare la riunione.", @@ -941,8 +940,6 @@ "enterprise_license_description": "Per abilitare questa funzione, ottenere una chiave di distribuzione presso la console {{consoleUrl}} e aggiungerla al proprio .env come CALCOM_LICENSE_KEY. Nel caso il team possieda già una licenza, contattare {{supportMail}} per assistenza.", "enterprise_license_development": "Puoi testare questa funzionalità in modalità sviluppo. Per l'utilizzo in produzione, l'amministratore deve accedere a <2>/auth/setup e immettere la chiave di licenza.", "missing_license": "Licenza mancante", - "signup_requires": "È necessaria una licenza commerciale", - "signup_requires_description": "{{companyName}} attualmente non offre una versione open source gratuita della pagina di registrazione. Per avere accesso completo ai componenti di registrazione è necessario acquisire una licenza commerciale. Per un uso personale consigliamo Prisma Data Platform o qualsiasi altra interfaccia Postgres per creare account.", "next_steps": "Prossimi Passi", "acquire_commercial_license": "Acquista una licenza commerciale", "the_infrastructure_plan": "Il piano infrastrutturale è basato sull'utilizzo e ha sconti rivolti alle startup.", @@ -1581,6 +1578,7 @@ "enable_apps": "Abilita app", "enable_apps_description": "Abilita le app che gli utenti possono integrare con {{appName}}", "purchase_license": "Acquista una licenza", + "already_have_account": "Hai già un account?", "already_have_key": "Ho già una chiave:", "already_have_key_suggestion": "Copia qui la variabile di ambiente CALCOM_LICENSE_KEY esistente.", "app_is_enabled": "{{appName}} è abilitato", @@ -1993,7 +1991,7 @@ "select_time": "Seleziona l'ora", "select_date": "Seleziona la data", "see_all_available_times": "Vedi tutti gli orari disponibili", - "org_team_names_example": "ad es., team di marketing", + "org_team_names_example_1": "ad es., team di marketing", "org_team_names_example_2": "ad es., team vendite", "org_team_names_example_3": "ad es., team di progettazione", "org_team_names_example_4": "ad es., team di ingegneria", diff --git a/apps/web/public/static/locales/ja/common.json b/apps/web/public/static/locales/ja/common.json index e09cb4c74b..1145ab4ff3 100644 --- a/apps/web/public/static/locales/ja/common.json +++ b/apps/web/public/static/locales/ja/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "この機能を有効にするには、{{consoleUrl}} コンソールでデプロイメントキーを入手して .env に CALCOM_LICENSE_KEY として追加してください。既にチームがライセンスを持っている場合は {{supportMail}} にお問い合わせください。", "enterprise_license_development": "この機能は開発モードでテストできます。本番環境で使用するには、管理者に <2>/auth/setup にアクセスするよう依頼し、ライセンスキーを入力してもらってください。", "missing_license": "ライセンスが見つかりません", - "signup_requires": "商用ライセンスが必要です", - "signup_requires_description": "{{companyName}} は、現時点ではサインアップページの無料のオープンソース版を提供しておりません。サインアップコンポーネントへのフルアクセスをご利用いただくためには、商用ライセンスを取得する必要があります。個人での利用につきましては、Prisma Data Platform またはその他の Postgres インターフェースを使用してアカウントの作成を行うことをお勧めしております。", "next_steps": "次のステップ", "acquire_commercial_license": "商用ライセンスを取得する", "the_infrastructure_plan": "インフラストラクチャプランは使用量に応じて課金され、スタートアップに優しい割引制度もあります。", @@ -1581,6 +1579,7 @@ "enable_apps": "アプリを有効にする", "enable_apps_description": "ユーザーが {{appName}} と連携できるアプリを有効にします", "purchase_license": "ライセンスを購入", + "already_have_account": "既にアカウントをお持ちですか?", "already_have_key": "すでにキーを持っています:", "already_have_key_suggestion": "ここに既存の CALCOM_LICENSE_KEY 環境変数をコピーしてください。", "app_is_enabled": "{{appName}} は有効です", @@ -1993,7 +1992,7 @@ "select_time": "時間帯を選ぶ", "select_date": "日付を選ぶ", "see_all_available_times": "出席できる時間帯をすべて表示", - "org_team_names_example": "例:マーケティングチーム", + "org_team_names_example_1": "例:マーケティングチーム", "org_team_names_example_2": "例:営業チーム", "org_team_names_example_3": "例:デザインチーム", "org_team_names_example_4": "例:エンジニアリングチーム", diff --git a/apps/web/public/static/locales/ko/common.json b/apps/web/public/static/locales/ko/common.json index 9264c062b9..0887097ac7 100644 --- a/apps/web/public/static/locales/ko/common.json +++ b/apps/web/public/static/locales/ko/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "이 기능을 활성화하려면 {{consoleUrl}} 콘솔에서 배포 키를 가져와 .env에 CALCOM_LICENSE_KEY로 추가하세요. 팀에 이미 라이선스가 있는 경우 {{supportMail}}에 문의하여 도움을 받으세요.", "enterprise_license_development": "개발 모드에서 이 기능을 테스트할 수 있습니다. 프로덕션 용도의 경우 관리자가 <2>/auth/setup으로 이동하여 라이선스 키를 입력하도록 하십시오.", "missing_license": "라이선스 없음", - "signup_requires": "상용 라이선스 필요", - "signup_requires_description": "지금은 {{companyName}}에서 가입 페이지의 무료 오픈 소스 버전을 제공하지 않습니다. 가입 구성 요소에 대한 정식 액세스 권한을 받으려면 상용 라이선스를 취득해야 합니다. 개인 용도의 경우 Prisma Data Platform 또는 기타 Postgres 인터페이스를 사용하여 계정을 생성하는 것이 좋습니다.", "next_steps": "다음 단계", "acquire_commercial_license": "상용 라이선스 취득하기", "the_infrastructure_plan": "인프라 플랜은 사용량 기반이고 스타트업 위주의 할인을 제공합니다.", @@ -1581,6 +1579,7 @@ "enable_apps": "앱 활성화", "enable_apps_description": "사용자가 {{appName}} 앱과 통합할 수 있는 앱 활성화", "purchase_license": "라이선스 구매", + "already_have_account": "이미 계정이 있으신가요?", "already_have_key": "이미 키가 있습니다:", "already_have_key_suggestion": "기존 CALCOM_LICENSE_KEY 환경 변수를 여기에 복사하십시오.", "app_is_enabled": "{{appName}} 앱이 활성화되었습니다", @@ -1993,7 +1992,7 @@ "select_time": "시간 선택", "select_date": "날짜 선택", "see_all_available_times": "모든 사용 가능한 시간 보기", - "org_team_names_example": "예: 마케팅 팀", + "org_team_names_example_1": "예: 마케팅 팀", "org_team_names_example_2": "예: 세일즈 팀", "org_team_names_example_3": "예: 디자인 팀", "org_team_names_example_4": "예: 엔지니어링 팀", diff --git a/apps/web/public/static/locales/lv/common.json b/apps/web/public/static/locales/lv/common.json index 0d030d0a3a..f4c69c2b82 100644 --- a/apps/web/public/static/locales/lv/common.json +++ b/apps/web/public/static/locales/lv/common.json @@ -124,5 +124,6 @@ "already_have_an_account": "Vai jums jau ir konts?", "create_account": "Izveidot Kontu", "confirm_password": "Apstiprināt paroli", - "confirm_auth_change": "Šis mainīs veidu, kā jūs autorizējaties" + "confirm_auth_change": "Šis mainīs veidu, kā jūs autorizējaties", + "already_have_account": "Vai jums jau ir konts?" } diff --git a/apps/web/public/static/locales/nl/common.json b/apps/web/public/static/locales/nl/common.json index 3fdcec1924..71a0609e4c 100644 --- a/apps/web/public/static/locales/nl/common.json +++ b/apps/web/public/static/locales/nl/common.json @@ -537,8 +537,8 @@ "address": "Adres", "enter_address": "Voer adres in", "in_person_attendee_address": "Persoonlijk (adres deelnemer)", - "yes": "ja", - "no": "nee", + "yes": "Ja", + "no": "Nee", "additional_notes": "Aanvullende notities", "booking_fail": "De afspraak kon niet geboekt worden.", "reschedule_fail": "De afspraak kon niet opnieuw gepland worden.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Om deze functie in te schakelen, krijgt u een implementatiesleutel op de {{consoleUrl}}-console en voegt u deze toe aan uw .env als CALCOM_LICENSE_KEY. Als uw team al een licentie heeft, neem dan contact op met {{supportMail}} voor hulp.", "enterprise_license_development": "U kunt deze functie testen in de in ontwikkelingsmodus. Voor productiegebruik moet een beheerder naar <2>/auth/setup gaan om een licentiecode in te voeren.", "missing_license": "Ontbrekende licentie", - "signup_requires": "Commerciële licentie vereist", - "signup_requires_description": "{{companyName}} biedt momenteel geen gratis opensourceversie van de registratiepagina aan. Om volledige toegang te krijgen tot de registratieonderdelen moet u een commerciële licentie verkrijgen. Voor persoonlijk gebruik raden we aan om accounts aan te maken op het Prisma Data Platform of een andere Postgres-interface.", "next_steps": "Volgende stappen", "acquire_commercial_license": "Verkrijg een commerciële licentie", "the_infrastructure_plan": "Het infrastructuurabonnement is gebaseerd op gebruik en heeft speciale kortingen voor start-ups.", @@ -1581,6 +1579,7 @@ "enable_apps": "Apps inschakelen", "enable_apps_description": "Schakel apps in die gebruikers kunnen integreren met {{appName}}", "purchase_license": "Koop een licentie", + "already_have_account": "Heeft u al een account?", "already_have_key": "Ik heb al een code:", "already_have_key_suggestion": "Kopieer hier uw bestaande CALCOM_LICENSE_KEY-omgevingsvariabele.", "app_is_enabled": "{{appName}} is ingeschakeld", @@ -1993,7 +1992,7 @@ "select_time": "Tijd selecteren", "select_date": "Datum selecteren", "see_all_available_times": "Bekijk alle beschikbare tijden", - "org_team_names_example": "bijvoorbeeld Marketingteam", + "org_team_names_example_1": "bijvoorbeeld Marketingteam", "org_team_names_example_2": "bijvoorbeeld Verkoopteam", "org_team_names_example_3": "bijvoorbeeld Ontwerpteam", "org_team_names_example_4": "bijvoorbeeld Engineeringteam", diff --git a/apps/web/public/static/locales/no/common.json b/apps/web/public/static/locales/no/common.json index 3cf8a73e4c..722a7be985 100644 --- a/apps/web/public/static/locales/no/common.json +++ b/apps/web/public/static/locales/no/common.json @@ -440,8 +440,8 @@ "address": "Adresse", "enter_address": "Skriv inn adresse", "in_person_attendee_address": "Personlig Oppmøte (Deltakers Adresse)", - "yes": "ja", - "no": "nei", + "yes": "Ja", + "no": "Nei", "additional_notes": "Tilleggsinformasjon", "booking_fail": "Kunne ikke booke møtet.", "reschedule_fail": "Kunne ikke endre tidspunkt for møtet.", @@ -789,8 +789,6 @@ "enterprise_license": "Dette er en bedriftsfunksjon", "enterprise_license_description": "For å aktivere denne funksjonen, skaff deg en distribusjonsnøkkel på {{consoleUrl}}-konsollen og legg den til i .env som CALCOM_LICENSE_KEY. Hvis teamet ditt allerede har en lisens, vennligst kontakt {{supportMail}} for å få hjelp.", "missing_license": "Mangler Lisens", - "signup_requires": "Kommersiell lisens kreves", - "signup_requires_description": "{{companyName}} tilbyr for øyeblikket ikke en gratis åpen kildekode-versjon av registreringssiden. For å få full tilgang til registreringskomponentene må du anskaffe en kommersiell lisens. For personlig bruk anbefaler vi Prisma Data Platform eller et annet Postgres-grensesnitt for å opprette kontoer.", "next_steps": "Neste Skritt", "acquire_commercial_license": "Skaff en kommersiell lisens", "the_infrastructure_plan": "Infrastruktur-planen er bruksbasert og har oppstartsvennlige rabatter.", @@ -1338,6 +1336,7 @@ "app_disabled_subject": "{{appName}} har blitt deaktivert", "navigate_installed_apps": "Gå til installerte apper", "enable_apps": "Aktiver Apper", + "already_have_account": "Har du allerede en bruker?", "app_is_enabled": "{{appName}} er aktivert", "app_is_disabled": "{{appName}} er deaktivert", "disable_app": "Deaktiver App", diff --git a/apps/web/public/static/locales/pl/common.json b/apps/web/public/static/locales/pl/common.json index 782f7d85b8..f4965cc628 100644 --- a/apps/web/public/static/locales/pl/common.json +++ b/apps/web/public/static/locales/pl/common.json @@ -537,8 +537,8 @@ "address": "Adres", "enter_address": "Wprowadź adres", "in_person_attendee_address": "Osobiście (adres uczestnika)", - "yes": "tak", - "no": "nie", + "yes": "Tak", + "no": "Nie", "additional_notes": "Dotatkowe uwagi", "booking_fail": "Nie można zarezerwować spotkania.", "reschedule_fail": "Nie udało się przełożyć spotkania.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Aby włączyć tę funkcję, uzyskaj klucz wdrożenia na konsoli {{consoleUrl}} i dodaj go do swojego pliku .env jako CALCOM_LICENSE_KEY. Jeśli Twój zespół ma już licencję, napisz na adres {{supportMail}}, aby uzyskać pomoc.", "enterprise_license_development": "Możesz przetestować tę funkcję w trybie deweloperskim. Aby wykorzystać ją w celach produkcyjnych, poproś administratora o wprowadzenie klucza licencyjnego w obszarze <2>/auth/setup.", "missing_license": "Brakująca licencja", - "signup_requires": "Wymagana licencja handlowa", - "signup_requires_description": "{{companyName}} obecnie nie oferuje darmowej otwartej wersji strony rejestracji. Aby uzyskać pełny dostęp do komponentów rejestracji, musisz zdobyć licencję komercyjną. Do użytku osobistego zalecamy platformę danych Prisma lub jakikolwiek inny interfejs Postgres do tworzenia kont.", "next_steps": "Następne kroki", "acquire_commercial_license": "Uzyskaj licencję komercyjną", "the_infrastructure_plan": "Plan infrastrukturalny opiera się na użytkowaniu i zawiera rabaty sprzyjające uruchomieniu.", @@ -1581,6 +1579,7 @@ "enable_apps": "Włącz aplikacje", "enable_apps_description": "Włącz aplikacje, które użytkownicy mogą zintegrować z aplikacją {{appName}}", "purchase_license": "Kup licencję", + "already_have_account": "Masz już konto?", "already_have_key": "Mam już klucz:", "already_have_key_suggestion": "Skopiuj istniejącą zmienną środowiskową CALCOM_LICENSE_KEY tutaj.", "app_is_enabled": "Aplikacja {{appName}} jest włączona", @@ -1993,7 +1992,7 @@ "select_time": "Wybierz godzinę", "select_date": "Wybierz datę", "see_all_available_times": "Zobacz wszystkie dostępne godziny", - "org_team_names_example": "np. zespół marketingowy", + "org_team_names_example_1": "np. zespół marketingowy", "org_team_names_example_2": "np. zespół ds. sprzedaży", "org_team_names_example_3": "np. zespół projektowy", "org_team_names_example_4": "np. zespół ds. inżynierii", diff --git a/apps/web/public/static/locales/pt-BR/common.json b/apps/web/public/static/locales/pt-BR/common.json index 68694326c0..43836e4fd8 100644 --- a/apps/web/public/static/locales/pt-BR/common.json +++ b/apps/web/public/static/locales/pt-BR/common.json @@ -537,8 +537,8 @@ "address": "Local", "enter_address": "Insira o endereço", "in_person_attendee_address": "Pessoalmente (endereço do participante)", - "yes": "sim", - "no": "não", + "yes": "Sim", + "no": "Não", "additional_notes": "Observações adicionais", "booking_fail": "Não foi possível agendar a reunião.", "reschedule_fail": "Não foi possível reagendar a reunião.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Para ativar este recurso, obtenha uma chave de desenvolvimento no console {{consoleUrl}} e adicione ao seu .env como CALCOM_LICENSE_KEY. Caso sua equipe já tenha uma licença, entre em contato com {{supportMail}} para obter ajuda.", "enterprise_license_development": "Você pode testar este recurso no modo de desenvolvimento. Para uso em produção, solicite que um administrador acesse <2>/auth/setup e insira uma chave de licença.", "missing_license": "Falta a licença", - "signup_requires": "Licença comercial obrigatória", - "signup_requires_description": "Atualmente a {{companyName}} não oferece nenhuma versão gratuita de código aberto da página de inscrição. Para obter acesso completo aos componentes da inscrição, você precisa adquirir uma licença comercial. Para uso pessoal, recomendamos o Prisma Data Platform ou outras interfaces em Postgres para criação de contas.", "next_steps": "Próximos passos", "acquire_commercial_license": "Adquira uma licença comercial", "the_infrastructure_plan": "O plano de infraestrutura é baseado no uso e tem descontos acessíveis para novos usuários.", @@ -1581,6 +1579,7 @@ "enable_apps": "Ativar aplicativos", "enable_apps_description": "Ative aplicativos que os usuários podem integrar com o {{appName}}", "purchase_license": "Comprar uma licença", + "already_have_account": "Já tem uma conta?", "already_have_key": "Já tenho uma chave:", "already_have_key_suggestion": "Copie a variável de ambiente CALCOM_LICENSE_KEY existente aqui.", "app_is_enabled": "{{appName}} foi ativado", @@ -1993,7 +1992,7 @@ "select_time": "Selecione o horário", "select_date": "Selecione a data", "see_all_available_times": "Veja todos os horários disponíveis", - "org_team_names_example": "ex.: Time de Marketing", + "org_team_names_example_1": "ex.: Time de Marketing", "org_team_names_example_2": "ex: Time de Vendas", "org_team_names_example_3": "ex: Time de Design", "org_team_names_example_4": "ex: Time de Engenharia", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index c819eb7dd2..cae210c157 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "Para ativar esta funcionalidade, obtenha uma chave de instalação na consola {{consoleUrl}} e adicione-a ao seu .env como CALCOM_LICENSE_KEY. Se a sua equipa já tem uma licença, entre em contacto com {{supportMail}} para obter ajuda.", "enterprise_license_development": "Pode testar esta funcionalidade no modo de desenvolvimento. Para utilização em produção, um administrador deverá aceder a <2>/auth/setup para inserir a chave da licença.", "missing_license": "Licença em falta", - "signup_requires": "Necessita de licença comercial", - "signup_requires_description": "De momento a {{companyName}} não oferece uma versão gratuita de código aberto da página de registo. Para aceder completamente aos componentes de registo tem de adquirir uma licença comercial. Para uso pessoal, recomendamos a Prisma Data Platform ou qualquer outra interface Postgres para criar contas.", "next_steps": "Passos seguintes", "acquire_commercial_license": "Adquira uma licença comercial", "the_infrastructure_plan": "O plano de infraestrutura é baseado em utilização e tem descontos para quem está a começar.", @@ -1581,6 +1579,7 @@ "enable_apps": "Ativar aplicações", "enable_apps_description": "Ativar aplicações que os utilizadores podem integrar com {{appName}}", "purchase_license": "Adquira uma Licença", + "already_have_account": "Já tem uma conta?", "already_have_key": "Eu já tenho uma chave:", "already_have_key_suggestion": "Por favor, copie a sua variável de ambiente CALCOM_LICENSE_KEY existente aqui.", "app_is_enabled": "{{appName}} foi ativada", @@ -1993,7 +1992,7 @@ "select_time": "Selecionar horário", "select_date": "Selecionar data", "see_all_available_times": "Ver todos os horários disponíveis", - "org_team_names_example": "Por exemplo, Equipa de Marketing", + "org_team_names_example_1": "Por exemplo, Equipa de Marketing", "org_team_names_example_2": "Por exemplo, Equipa de Vendas", "org_team_names_example_3": "Por exemplo, Equipa de Design", "org_team_names_example_4": "Por exemplo, Equipa de Engenharia", diff --git a/apps/web/public/static/locales/ro/common.json b/apps/web/public/static/locales/ro/common.json index ffe1b5af80..6ce6c3fd1a 100644 --- a/apps/web/public/static/locales/ro/common.json +++ b/apps/web/public/static/locales/ro/common.json @@ -537,8 +537,8 @@ "address": "Adresă", "enter_address": "Introdu adresa", "in_person_attendee_address": "În persoană (adresă participant)", - "yes": "da", - "no": "nu", + "yes": "Da", + "no": "Nu", "additional_notes": "Date suplimentare", "booking_fail": "Nu s-a putut rezerva întâlnirea.", "reschedule_fail": "Nu s-a putut reprograma întâlnirea.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Pentru a activa această caracteristică, obține o cheie de implementare la consola {{consoleUrl}} și adaug-o la .env ca CALCOM_LICENSE_KEY. Dacă echipa ta are deja o licență, te rugăm să contactezi {{supportMail}} pentru ajutor.", "enterprise_license_development": "Puteți testa această caracteristică în modul de dezvoltare. Pentru utilizarea în mediul de producție, solicitați unui administrator să acceseze <2>/auth/setup pentru a introduce o cheie de licență.", "missing_license": "Licență lipsă", - "signup_requires": "Licență comercială necesară", - "signup_requires_description": "În prezent, {{companyName}} nu oferă o versiune open-source gratuită a paginii de înscriere. Pentru a primi acces deplin la componentele de înscriere, trebuie să obțineți o licență comercială. Pentru uz personal, recomandăm Prisma Data Platform sau orice altă interfață Postgres pentru a crea conturi.", "next_steps": "Pașii următori", "acquire_commercial_license": "Obțineți o licență comercială", "the_infrastructure_plan": "Planul de infrastructură este bazat pe utilizare și oferă reduceri atractive companiilor de tip start-up.", @@ -1581,6 +1579,7 @@ "enable_apps": "Activează aplicații", "enable_apps_description": "Activați aplicațiile pe care utilizatorii le pot integra cu {{appName}}", "purchase_license": "Achiziționați o licență", + "already_have_account": "Aveți deja un cont?", "already_have_key": "Am deja o cheie:", "already_have_key_suggestion": "Copiați aici variabila de mediu CALCOM_LICENSE_KEY existentă.", "app_is_enabled": "{{appName}} este activat", @@ -1993,7 +1992,7 @@ "select_time": "Selectați ora", "select_date": "Selectați data", "see_all_available_times": "Vedeți toate orele disponibile", - "org_team_names_example": "de ex. echipa de marketing", + "org_team_names_example_1": "de ex. echipa de marketing", "org_team_names_example_2": "de ex. echipa de vânzări", "org_team_names_example_3": "de ex. echipa de design", "org_team_names_example_4": "de ex. echipa de inginerie", diff --git a/apps/web/public/static/locales/ru/common.json b/apps/web/public/static/locales/ru/common.json index c09ec35b2c..662df18032 100644 --- a/apps/web/public/static/locales/ru/common.json +++ b/apps/web/public/static/locales/ru/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "Чтобы включить эту функцию, получите ключ развертывания на консоли {{consoleUrl}} и добавьте его в .env как CALCOM_LICENSE_KEY. Если у вашей команды уже есть лицензия, пожалуйста, обратитесь за помощью в {{supportMail}}.", "enterprise_license_development": "Протестируйте эту функцию в режиме разработки. Для использования в рабочем режиме администратор должен перейти по ссылке <2>/auth/setup и ввести лицензионный ключ.", "missing_license": "Отсутствует лицензия", - "signup_requires": "Требуется коммерческая лицензия", - "signup_requires_description": "В настоящее время компания {{companyName}} не предлагает бесплатную версию страницы регистрации с открытым исходным кодом. Чтобы получить полный доступ к компонентам входа в систему, необходимо приобрести коммерческую лицензию. Для личного пользования мы рекомендуем Prisma Data Platform или любой другой интерфейс Postgres для создания учетных записей.", "next_steps": "Следующие шаги", "acquire_commercial_license": "Приобрести коммерческую лицензию", "the_infrastructure_plan": "Стоимость тарифного плана по развертыванию инфраструктуры зависит от вариантов использования; стартапам предоставляются скидки.", @@ -1581,6 +1579,7 @@ "enable_apps": "Включить приложения", "enable_apps_description": "Выберите приложения, которые пользователи смогут интегрировать с {{appName}}", "purchase_license": "Купить лицензию", + "already_have_account": "Уже есть аккаунт?", "already_have_key": "У меня уже есть ключ:", "already_have_key_suggestion": "Скопируйте сюда переменную окружения CALCOM_LICENSE_KEY.", "app_is_enabled": "{{appName}} включено", @@ -1993,7 +1992,7 @@ "select_time": "Выбрать время", "select_date": "Выбрать дату", "see_all_available_times": "Посмотреть все доступные интервалы времени", - "org_team_names_example": "например, команда по маркетингу", + "org_team_names_example_1": "например, команда по маркетингу", "org_team_names_example_2": "например, Отдел продаж", "org_team_names_example_3": "например, отдел дизайна", "org_team_names_example_4": "например, технический отдел", diff --git a/apps/web/public/static/locales/sr/common.json b/apps/web/public/static/locales/sr/common.json index 976866e071..b8d3015312 100644 --- a/apps/web/public/static/locales/sr/common.json +++ b/apps/web/public/static/locales/sr/common.json @@ -537,8 +537,8 @@ "address": "Lokacija", "enter_address": "Unesite adresu", "in_person_attendee_address": "Lično (adresa polaznika)", - "yes": "da", - "no": "ne", + "yes": "Da", + "no": "Ne", "additional_notes": "Dodatne beleške", "booking_fail": "Sastanak nije mogao da se rezerviše.", "reschedule_fail": "Sastanak nije mogao da se odloži.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Da biste omogućili ovu funkciju, nabavite ključ za primenu na {{consoleUrl}} konzoli i dodajte ga u svoj .env kao CALCOM_LICENCE_KEY. Ako vaš tim već ima licencu, obratite se na {{supportMail}} za pomoć.", "enterprise_license_development": "Ovu funkciju možete testirati u razvojnom režimu. Za proizvodnu upotrebu neka administrator ode na <2>/auth/setup da unese ključ za licencu.", "missing_license": "Nedostaje Licenca", - "signup_requires": "Komercijalna dozvola je neophodna", - "signup_requires_description": "{{companyName}} trenutno ne nudi besplatnu verziju otvorenog koda od stranice za prijavu. Da bi dobili potpuni pristup kompenentama za prijavu, neophodno je da ste vlasnik komercijalne licence. Za ličnu upotrebu preporučujemo Prisma Data Platformu ili bilo koji drugi Postgres interfejs za pravljenje naloga.", "next_steps": "Sledeći koraci", "acquire_commercial_license": "Steknite komercijalnu licencu", "the_infrastructure_plan": "Plan infrastrukture je na osnovu upotrebe i ima popuste pogdne za startup.", @@ -1581,6 +1579,7 @@ "enable_apps": "Omogući aplikacije", "enable_apps_description": "Omogući aplikacije koje korisnici mogu da integrišu sa aplikacijom {{appName}}", "purchase_license": "Kupovina licence", + "already_have_account": "Već imate nalog?", "already_have_key": "Već imam ključ:", "already_have_key_suggestion": "Kopirajte ovde postojeću CALCOM_LICENSE_KEY promenljivu okruženja.", "app_is_enabled": "Aplikacija {{appName}} je omogućena", @@ -1993,7 +1992,7 @@ "select_time": "Izaberite vreme", "select_date": "Izaberite datum", "see_all_available_times": "Pogledajte sva dostupna vremena", - "org_team_names_example": "npr. Marketing tim", + "org_team_names_example_1": "npr. Marketing tim", "org_team_names_example_2": "npr. Prodajni tim", "org_team_names_example_3": "npr. Dizajnerski tim", "org_team_names_example_4": "npr. Inženjerski tim", diff --git a/apps/web/public/static/locales/sv/common.json b/apps/web/public/static/locales/sv/common.json index f96a5e35ff..7808b89c27 100644 --- a/apps/web/public/static/locales/sv/common.json +++ b/apps/web/public/static/locales/sv/common.json @@ -537,8 +537,8 @@ "address": "Adress", "enter_address": "Ange adress", "in_person_attendee_address": "Personligen (deltagandeadress)", - "yes": "ja", - "no": "nej", + "yes": "Ja", + "no": "Nej", "additional_notes": "Ytterligare noteringar", "booking_fail": "Det gick inte att boka mötet.", "reschedule_fail": "Kunde inte omboka mötet.", @@ -941,8 +941,6 @@ "enterprise_license_description": "För att aktivera den här funktionen hämtar du en distributionsnyckel i konsolen {{consoleUrl}} och lägger till den i din .env som CALCOM_LICENSE_KEY. Om ditt team redan har en licens kan du kontakta {{supportMail}} för att få hjälp.", "enterprise_license_development": "Du kan testa den här funktionen i utvecklingsläge. För produktionsanvändning ska en administratör gå till <2>/auth/setup för att ange en licensnyckel.", "missing_license": "Licens saknas", - "signup_requires": "Kommersiell licens krävs", - "signup_requires_description": "{{companyName}} erbjuder för närvarande inte en gratis open source-version av registreringssidan. För att få full tillgång till de registreringskomponenter du behöver för att skaffa en kommersiell licens. För personligt bruk rekommenderar vi Prisma Data Platform eller något annat Postgres-gränssnitt för att skapa konton.", "next_steps": "Kommande steg", "acquire_commercial_license": "Skaffa en kommersiell licens", "the_infrastructure_plan": "Infrastrukturplanen är användningsbaserad och har start-vänliga rabatter.", @@ -1581,6 +1579,7 @@ "enable_apps": "Aktivera appar", "enable_apps_description": "Aktivera appar som användare kan integrera med {{appName}}", "purchase_license": "Köp en licens", + "already_have_account": "Har du redan ett konto?", "already_have_key": "Jag har redan en nyckel:", "already_have_key_suggestion": "Kopiera din befintliga CALCOM_LICENSE_KEY-miljövariabel här.", "app_is_enabled": "{{appName}} har aktiverats", @@ -1993,7 +1992,7 @@ "select_time": "Välj tid", "select_date": "Välj datum", "see_all_available_times": "Se alla tillgängliga tider", - "org_team_names_example": "t.ex. marknadsföringsteam", + "org_team_names_example_1": "t.ex. marknadsföringsteam", "org_team_names_example_2": "t.ex. säljteam", "org_team_names_example_3": "t.ex. designteam", "org_team_names_example_4": "t.ex. ingenjörsteam", diff --git a/apps/web/public/static/locales/tr/common.json b/apps/web/public/static/locales/tr/common.json index ef8a1d1cdb..30bb39d76b 100644 --- a/apps/web/public/static/locales/tr/common.json +++ b/apps/web/public/static/locales/tr/common.json @@ -537,8 +537,8 @@ "address": "Adres", "enter_address": "Adres girin", "in_person_attendee_address": "Şahsen (Katılımcı Adresi)", - "yes": "evet", - "no": "hayır", + "yes": "Evet", + "no": "Hayır", "additional_notes": "Ek notlar", "booking_fail": "Toplantı rezervasyonu yapılamadı.", "reschedule_fail": "Toplantı yeniden planlanamadı.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Bu özelliği etkinleştirmek için {{consoleUrl}} konsolundan bir dağıtım anahtarı alın ve bunu .env dosyanıza CALCOM_LICENSE_KEY olarak ekleyin. Ekibinizin zaten bir lisansı varsa yardım için lütfen {{supportMail}} adresinden iletişime geçin.", "enterprise_license_development": "Bu özelliği, geliştirme modunda deneyebilirsiniz. Üretim kullanımı için lütfen bir yöneticinin lisans anahtarını girmesi için <2>/auth/setup adımlarını izlemesini sağlayın.", "missing_license": "Eksik Lisans", - "signup_requires": "Ticari lisans gerekli", - "signup_requires_description": "{{companyName}} şu anda kayıt sayfasının ücretsiz, açık kaynaklı bir sürümünü sunmamaktadır. Kayıt bileşenlerine tam erişim elde etmek için ticari bir lisans almanız gerekiyor. Kişisel kullanım için hesap oluşturmak üzere Prisma Data veya başka bir Postgres arayüzü öneriyoruz.", "next_steps": "Sonraki Adımlar", "acquire_commercial_license": "Ticari bir lisans edinin", "the_infrastructure_plan": "Altyapı planı kullanıma dayalıdır ve yeni başlayanlar için uygun indirimlere sahiptir.", @@ -1581,6 +1579,7 @@ "enable_apps": "Uygulamaları Etkinleştir", "enable_apps_description": "Kullanıcıların {{appName}} ile entegre edebileceği uygulamaları etkinleştirin", "purchase_license": "Lisans satın alın", + "already_have_account": "Zaten bir hesabınız var mı?", "already_have_key": "Zaten bir anahtarım var:", "already_have_key_suggestion": "Lütfen mevcut CALCOM_LICENSE_KEY ortam değişkeninizi buraya kopyalayın.", "app_is_enabled": "{{appName}} etkinleştirildi", @@ -1993,7 +1992,7 @@ "select_time": "Saati Seçin", "select_date": "Tarihi Seçin", "see_all_available_times": "Tüm müsait saatleri görün", - "org_team_names_example": "Örneğin. Pazarlama ekibi", + "org_team_names_example_1": "Örneğin. Pazarlama ekibi", "org_team_names_example_2": "Örn. Satış Ekibi", "org_team_names_example_3": "Örn. Tasarım Ekibi", "org_team_names_example_4": "Örn. Mühendislik Ekibi", diff --git a/apps/web/public/static/locales/uk/common.json b/apps/web/public/static/locales/uk/common.json index 3d4b62e586..40d8c52cf3 100644 --- a/apps/web/public/static/locales/uk/common.json +++ b/apps/web/public/static/locales/uk/common.json @@ -537,8 +537,8 @@ "address": "Адреса", "enter_address": "Введіть адресу", "in_person_attendee_address": "Особисто (адреса відвідувача)", - "yes": "так", - "no": "ні", + "yes": "Так", + "no": "Hі", "additional_notes": "Додаткові примітки", "booking_fail": "Не вдалося забронювати нараду.", "reschedule_fail": "Не вдалося перенести нараду.", @@ -941,8 +941,6 @@ "enterprise_license_description": "Щоб увімкнути цю функцію, отримайте ключ розгортання в консолі {{consoleUrl}} і додайте його у свій файл .env як CALCOM_LICENSE_KEY. Якщо у вашої команди вже є ліцензія, зверніться по допомогу за адресою {{supportMail}}.", "enterprise_license_development": "Ви можете випробувати цю функцію в режимі розробки. Щоб скористатися нею, адміністратору потрібно перейти в розділ <2>«Перевірка»/«Налаштування» і ввести ключ ліцензії.", "missing_license": "Відсутня ліцензія", - "signup_requires": "Потрібна комерційна ліцензія", - "signup_requires_description": "{{companyName}} зараз не надає безкоштовну версію сторінки реєстрації з відкритим кодом. Щоб отримати повний доступ до складових функціоналу реєстрації, потрібно придбати комерційну ліцензію. Для особистого використання та створення облікових записів радимо Prisma Data Platform або будь-який інший інтерфейс Postgres.", "next_steps": "Подальші кроки", "acquire_commercial_license": "Отримати комерційну ліцензію", "the_infrastructure_plan": "Інфраструктурний план базується на використанні та надає знижки для стартапів.", @@ -1581,6 +1579,7 @@ "enable_apps": "Увімкнути додатки", "enable_apps_description": "Активуйте застосунки, які користувачі зможуть інтегрувати із застосунком {{appName}}", "purchase_license": "Придбати ліцензію", + "already_have_account": "Уже маєте обліковий запис?", "already_have_key": "У мене вже є ключ:", "already_have_key_suggestion": "Скопіюйте сюди наявну змінну оточення CALCOM_LICENSE_KEY.", "app_is_enabled": "{{appName}} увімкнено", @@ -1993,7 +1992,7 @@ "select_time": "Виберіть час", "select_date": "Виберіть дату", "see_all_available_times": "Переглянути доступні часові проміжки", - "org_team_names_example": "напр. команда маркетингу", + "org_team_names_example_1": "напр. команда маркетингу", "org_team_names_example_2": "напр. відділ продажів", "org_team_names_example_3": "напр. дизайнерський відділ", "org_team_names_example_4": "e.g. інженерний відділ", diff --git a/apps/web/public/static/locales/vi/common.json b/apps/web/public/static/locales/vi/common.json index edeeff1de6..b072d4bc90 100644 --- a/apps/web/public/static/locales/vi/common.json +++ b/apps/web/public/static/locales/vi/common.json @@ -117,6 +117,7 @@ "meeting_id": "ID cuộc họp", "meeting_password": "Mật khẩu cuộc họp", "meeting_url": "URL cuộc họp", + "meeting_url_not_found": "Không Thấy URL Cuộc Họp", "meeting_request_rejected": "Yêu cầu lên cuộc họp của bạn đã bị từ chối", "rejected_event_type_with_organizer": "Bị từ chối: {{eventType}} với {{organizer}} vào {{date}}", "hi": "Chào", @@ -537,8 +538,8 @@ "address": "Địa chỉ", "enter_address": "Nhập địa chỉ", "in_person_attendee_address": "Đích thân (địa chỉ người tham gia)", - "yes": "có", - "no": "không", + "yes": "Đúng", + "no": "Không", "additional_notes": "Ghi chú bổ sung", "booking_fail": "Không thể đặt cuộc họp.", "reschedule_fail": "Không thể lên lịch lại cuộc họp.", @@ -780,6 +781,8 @@ "requires_confirmation_description": "Lịch hẹn cần được xác nhận thủ công trước khi nó được chuyển sang ứng dụng tích hợp và thư xác nhận sẽ được gửi.", "recurring_event": "Sự kiện định kỳ", "recurring_event_description": "Mọi người có thể ghi danh sự kiện định kỳ", + "cannot_be_used_with_paid_event_types": "Nó không thể được sử dụng với các loại sự kiện phải trả tiền", + "warning_instant_meeting_experimental": "Thử nghiệm: Sự Kiện Cuộc Họp Tức Thì hiện đang thử nghiệm.", "starting": "Bắt đầu", "disable_guests": "Vô hiệu hóa Khách", "disable_guests_description": "Vô hiệu hóa việc thêm khách bổ sung trong khi đặt lịch.", @@ -941,8 +944,6 @@ "enterprise_license_description": "Để bật tính năng này, nhận khoá triển khai tại console {{consoleUrl}} và thêm nó vào .env của bạn ở dạng CALCOM_LICENSE_KEY. Nếu nhóm của bạn đã có giấy phép, vui lòng liên hệ {{supportMail}} để được trợ giúp.", "enterprise_license_development": "Bạn có thể thử nghiệm tính năng này ở chế độ phát triển. Để sử dụng cho sản xuất, hãy nhờ quản trị viên vào phần <2>/auth/setup để nhập một khoá giấy phép.", "missing_license": "Giấy phép bị thiếu", - "signup_requires": "Yêu cầu giấy phép thương mại", - "signup_requires_description": "{{companyName}} hiện không cung cấp phiên bản nguồn mở miễn phí của trang đăng ký. Để nhận toàn quyền truy cập vào các thành phần đăng ký, bạn cần có giấy phép thương mại. Đối với mục đích sử dụng cá nhân, chúng tôi khuyên bạn nên sử dụng Nền tảng dữ liệu Prisma hoặc bất kỳ giao diện Postgres nào khác để tạo tài khoản.", "next_steps": "Bước tiếp theo", "acquire_commercial_license": "Lấy giấy phép thương mại", "the_infrastructure_plan": "Gói cơ sở hạ tầng dựa trên mức sử dụng và có chiết khấu thân thiện với startup.", @@ -1581,6 +1582,7 @@ "enable_apps": "Kích hoạt ứng dụng", "enable_apps_description": "Kích hoạt những ứng dụng mà người dùng có thể tích hợp với {{appName}}", "purchase_license": "Mua một giấy phép", + "already_have_account": "Bạn đã có sẵn tài khoản?", "already_have_key": "Tôi đã có một khoá:", "already_have_key_suggestion": "Vui lòng sao chép biến số môi trường CALCOM_LICENSE_KEY hiện có của bạn tại đây.", "app_is_enabled": "{{appName}} đã được kích hoạt", @@ -1607,6 +1609,7 @@ "individual": "Cá nhân", "all_bookings_filter_label": "Tất cả lịch hẹn", "all_users_filter_label": "Tất cả người dùng", + "all_event_types_filter_label": "Các Loại Sự Kiện", "your_bookings_filter_label": "Lịch hẹn của bạn", "meeting_url_variable": "Url cuộc họp", "meeting_url_info": "URL hội nghị cuộc họp sự kiện", @@ -1837,6 +1840,7 @@ "review_event_type": "Xem lại loại sự kiện", "looking_for_more_analytics": "Bạn muốn có thêm thông tin phân tích?", "looking_for_more_insights": "Bạn muốn có thêm Insights?", + "filters": "Bộ Lọc", "add_filter": "Thêm bộ lọc", "select_user": "Chọn người dùng", "select_event_type": "Chọn loại sự kiện", @@ -1992,8 +1996,9 @@ "add_times_to_your_email": "Chọn một số thời gian trống và nhúng chúng vào trong Email của bạn", "select_time": "Chọn thời gian", "select_date": "Chọn ngày", + "please_do_not_close_this_tab": "Vui lòng không đóng tab này", "see_all_available_times": "Xem tất cả những thời gian trống", - "org_team_names_example": "ví dụ Nhóm Tiếp thị", + "org_team_names_example_1": "ví dụ Nhóm Tiếp thị", "org_team_names_example_2": "ví dụ Nhóm Kinh doanh", "org_team_names_example_3": "ví dụ Nhóm Thiết kế", "org_team_names_example_4": "ví dụ Nhóm Kỹ thuật", @@ -2046,6 +2051,7 @@ "org_error_processing": "Có lỗi khi xử lý tổ chức này", "orgs_page_description": "Một danh sách chứa tất cả các tổ chức. Việc chấp thuận một tổ chức sẽ cho phép tất cả người dùng có tên miền email đó có thể đăng ký mà KHÔNG CẦN xác minh email.", "unverified": "Chưa xác minh", + "verified": "Đã xác minh", "dns_missing": "DNS bị thiếu", "mark_dns_configured": "Đánh dấu khi DNS đã cấu hình", "value": "Giá trị", @@ -2094,5 +2100,6 @@ "description_lock_timezone_toggle_on_booking_page": "Để khoá múi giờ trên trang lịch hẹn, hữu dụng cho những sự kiện đích thân tham dự.", "extensive_whitelabeling": "Hướng dẫn bắt đầu và hỗ trợ kỹ thuật chuyên biệt", "need_help": "Cần hỗ trợ?", + "instant_tab_title": "Đặt Cuộc Họp Ngay", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/apps/web/public/static/locales/zh-CN/common.json b/apps/web/public/static/locales/zh-CN/common.json index 41440025c4..6b61b16df7 100644 --- a/apps/web/public/static/locales/zh-CN/common.json +++ b/apps/web/public/static/locales/zh-CN/common.json @@ -942,8 +942,6 @@ "enterprise_license_description": "要启用此功能,请在 {{consoleUrl}} 控制台获取一个部署密钥,并将其添加到您的 .env 中作为 CALCOM_LICENSE_KEY。如果您的团队已经有许可证,请联系 {{supportMail}} 获取帮助。", "enterprise_license_development": "您可以在开发模式下测试此功能。对于生产用途,请让管理员转到 <2>/auth/setup 输入许可证密钥。", "missing_license": "缺少许可证", - "signup_requires": "需要商业许可证", - "signup_requires_description": "{{companyName}} 免费开源版目前不提供的注册页面。 要获得对注册页面组件的完全访问权,您需要获得商业许可证。 对于个人使用用途,我们推荐使用Prisma 数据平台或任何其他Postgres接口创建账户。", "next_steps": "下一步", "acquire_commercial_license": "获取商业许可证", "the_infrastructure_plan": "基础设施计划是按用量计费的,并对初创公司有优惠。", @@ -1582,6 +1580,7 @@ "enable_apps": "启用应用", "enable_apps_description": "启用用户可以与 {{appName}} 集成的应用", "purchase_license": "购买许可证", + "already_have_account": "已经有帐号?", "already_have_key": "我已经有密钥:", "already_have_key_suggestion": "请将您现有的 CALCOM_LICENSE_KEY 环境变量复制到此处。", "app_is_enabled": "{{appName}} 已启用", @@ -1994,7 +1993,7 @@ "select_time": "选择时间", "select_date": "选择日期", "see_all_available_times": "查看所有可预约时间", - "org_team_names_example": "例如,营销团队", + "org_team_names_example_1": "例如,营销团队", "org_team_names_example_2": "例如,销售团队", "org_team_names_example_3": "例如,设计团队", "org_team_names_example_4": "例如,工程团队", diff --git a/apps/web/public/static/locales/zh-TW/common.json b/apps/web/public/static/locales/zh-TW/common.json index 412c49c29e..958eb14432 100644 --- a/apps/web/public/static/locales/zh-TW/common.json +++ b/apps/web/public/static/locales/zh-TW/common.json @@ -941,8 +941,6 @@ "enterprise_license_description": "若要啟用此功能,請在 {{consoleUrl}} 主控台取得部署金鑰,並在您的 .env 中新增為 CALCOM_LICENSE_KEY。如果您的團隊已經取得授權,請聯絡 {{supportMail}} 取得協助。", "enterprise_license_development": "您可以在開發模式下測試此功能。若要用於生產環境,請由管理員前往 <2>/auth/setup 輸入授權金鑰。", "missing_license": "遺失授權", - "signup_requires": "必須有商業授權", - "signup_requires_description": "{{companyName}} 目前不提供註冊頁面的免費開源版本。如果要完整獲得註冊元件,就得要取得商業授權。個人使用的情況,我們推薦 Prisma Data Platform 或其它 Postgres 介面來建立帳號。", "next_steps": "下一步", "acquire_commercial_license": "取得商業授權", "the_infrastructure_plan": "基礎架構方案是根據使用量,並提供新創公司友善折扣。", @@ -1581,6 +1579,7 @@ "enable_apps": "啟用應用程式", "enable_apps_description": "啟用使用者可與 {{appName}} 整合的應用程式", "purchase_license": "購買授權", + "already_have_account": "已經有帳號嗎?", "already_have_key": "我已經有金鑰:", "already_have_key_suggestion": "請複製您目前的 CALCOM_LICENSE_KEY 環境變數至此處。", "app_is_enabled": "已啟用 {{appName}}", @@ -1993,7 +1992,7 @@ "select_time": "選取時間", "select_date": "選取日期", "see_all_available_times": "查看所有可預約時段", - "org_team_names_example": "例如行銷團隊", + "org_team_names_example_1": "例如行銷團隊", "org_team_names_example_2": "例如銷售團隊", "org_team_names_example_3": "例如設計團隊", "org_team_names_example_4": "例如工程團隊", diff --git a/apps/web/scripts/vercel-staging-deploy.sh b/apps/web/scripts/vercel-staging-deploy.sh new file mode 100755 index 0000000000..994cf899dc --- /dev/null +++ b/apps/web/scripts/vercel-staging-deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ "$SKIP_APP_DIR" == "1" ]; then + echo "Skipping app directory build" + rm -rf \ + app/\ + components/PageWrapperAppDir.tsx\ + lib/app-providers-app-dir.tsx\ + .next/types/app/ +fi +if [ "$VERCEL_ENV" == "preview" ]; then exit 1; else exit 0; fi diff --git a/apps/web/sentry.server.config.ts b/apps/web/sentry.server.config.ts index 842f09fccc..a429088bd9 100644 --- a/apps/web/sentry.server.config.ts +++ b/apps/web/sentry.server.config.ts @@ -2,4 +2,5 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, }); diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index d482254aed..ee8fc6fcc3 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -7,90 +7,90 @@ :root { /* background */ - --cal-bg-emphasis: #e5e7eb; - --cal-bg: white; - --cal-bg-subtle: #f3f4f6; - --cal-bg-muted: #f9fafb; - --cal-bg-inverted: #111827; + --cal-bg-emphasis: hsla(220,13%,91%,1); + --cal-bg: hsla(0,0%,100%,1); + --cal-bg-subtle: hsla(220, 14%, 96%,1); + --cal-bg-muted: hsla(210,20%,98%,1); + --cal-bg-inverted: hsla(0,0%,6%,1); /* background -> components*/ - --cal-bg-info: #dee9fc; - --cal-bg-success: #e2fbe8; - --cal-bg-attention: #fceed8; - --cal-bg-error: #f9e3e2; - --cal-bg-dark-error: #752522; + --cal-bg-info: hsla(218,83%,98%,1); + --cal-bg-success: hsla(134,76%,94%,1); + --cal-bg-attention: hsla(37, 86%, 92%, 1); + --cal-bg-error: hsla(3,66,93,1); + --cal-bg-dark-error: hsla(2, 55%, 30%, 1); /* Borders */ - --cal-border-emphasis: #9ca3af; - --cal-border: #d1d5db; - --cal-border-subtle: #e5e7eb; + --cal-border-emphasis: hsla(218, 11%, 65%, 1); + --cal-border: hsla(216, 12%, 84%, 1); + --cal-border-subtle: hsla(220, 13%, 91%, 1); --cal-border-booker: #e5e7eb; - --cal-border-muted: #f3f4f6; - --cal-border-error: #aa2e26; + --cal-border-muted: hsla(220, 14%, 96%, 1); + --cal-border-error: hsla(4, 63%, 41%, 1); /* Content/Text */ - --cal-text-emphasis: #111827; - --cal-text: #374151; - --cal-text-subtle: #6b7280; - --cal-text-muted: #9ca3af; - --cal-text-inverted: white; + --cal-text-emphasis: hsla(217, 19%, 27%, 1); + --cal-text: hsla(217, 19%, 27%, 1); + --cal-text-subtle: hsla(220, 9%, 46%, 1); + --cal-text-muted: hsla(218, 11%, 65%, 1); + --cal-text-inverted: hsla(0, 0%, 100%, 1); /* Content/Text -> components */ - --cal-text-info: #253985; - --cal-text-success: #285231; - --cal-text-attention: #73321b; - --cal-text-error: #752522; + --cal-text-info: hsla(228, 56%, 33%, 1); + --cal-text-success: hsla(133, 34%, 24%, 1); + --cal-text-attention: hsla(16, 62%, 28%, 1); + --cal-text-error: hsla(2, 55%, 30%, 1); /* Brand shinanigans -> These will be computed for the users theme at runtime. */ - --cal-brand: #111827; - --cal-brand-emphasis: #101010; - --cal-brand-text: white; + --cal-brand: hsla(221, 39%, 11%, 1); + --cal-brand-emphasis: hsla(0, 0%, 6%, 1); + --cal-brand-text: hsla(0, 0%, 100%, 1); } .dark { /* background */ - --cal-bg-emphasis: #2b2b2b; - --cal-bg: #101010; - --cal-bg-subtle: #2b2b2b; - --cal-bg-muted: #1c1c1c; - --cal-bg-inverted: #f3f4f6; + --cal-bg-emphasis: hsla(0, 0%, 25%, 1); + --cal-bg: hsla(0, 0%, 10%, 1); + --cal-bg-subtle: hsla(0, 0%, 18%, 1); + --cal-bg-muted: hsla(0, 0%, 12%, 1); + --cal-bg-inverted: hsla(220, 14%, 96%, 1); /* background -> components*/ - --cal-bg-info: #263fa9; - --cal-bg-success: #306339; - --cal-bg-attention: #8e3b1f; - --cal-bg-error: #8c2822; - --cal-bg-dark-error: #752522; + --cal-bg-info: hsla(228, 56%, 33%, 1); + --cal-bg-success: hsla(133, 34%, 24%, 1); + --cal-bg-attention: hsla(16, 62%, 28%, 1); + --cal-bg-error: hsla(2, 55%, 30%, 1); + --cal-bg-dark-error: hsla(2, 55%, 30%, 1); /* Borders */ - --cal-border-emphasis: #575757; - --cal-border: #444444; - --cal-border-subtle: #2b2b2b; - --cal-border-booker: #2b2b2b; - --cal-border-muted: #1c1c1c; - --cal-border-error: #aa2e26; + --cal-border-emphasis: hsla(0, 0%, 46%, 1); + --cal-border: hsla(0, 0%, 34%, 1); + --cal-border-subtle: hsla(0, 0%, 22%, 1); + --cal-border-booker: hsla(0, 0%, 22%, 1); + --cal-border-muted: hsla(0, 0%, 18%, 1); + --cal-border-error: hsla(4, 63%, 41%, 1); /* Content/Text */ - --cal-text-emphasis: #f3f4f6; - --cal-text: #d6d6d6; - --cal-text-subtle: #a5a5a5; - --cal-text-muted: #575757; - --cal-text-inverted: #101010; + --cal-text-emphasis: hsla(240, 20%, 99%, 1); + --cal-text: hsla(0, 0%, 84%, 1); + --cal-text-subtle: hsla(0, 0%, 65%, 1); + --cal-text-muted: hsla(0, 0%, 34%, 1); + --cal-text-inverted: hsla(0, 0%, 10%, 1); /* Content/Text -> components */ - --cal-text-info: #dee9fc; - --cal-text-success: #e2fbe8; - --cal-text-attention: #fceed8; - --cal-text-error: #f9e3e2; + --cal-text-info: hsla(218, 83%, 93%, 1); + --cal-text-success: hsla(134, 76%, 94%, 1); + --cal-text-attention: hsla(37, 86%, 92%, 1); + --cal-text-error: hsla(3, 66%, 93%, 1); /* Brand shenanigans -> These will be computed for the users theme at runtime. */ - --cal-brand: white; - --cal-brand-emphasis: #e1e1e1; - --cal-brand-text: black; + --cal-brand: hsla(0, 0%, 100%, 1); + --cal-brand-emphasis: hsla(218, 11%, 65%, 1); + --cal-brand-text: hsla(0, 0%, 0%,1); } @layer base { @@ -109,10 +109,6 @@ background: var(--cal-brand); } -html { - scrollbar-gutter: stable; -} - body  { text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; @@ -146,43 +142,11 @@ html.todesktop div { html.todesktop header { -webkit-app-region: drag; } + html.todesktop header button { -webkit-app-region: no-drag; } -html.todesktop .logo { - display: none; -} - -.desktop-only { - display: none; -} - -html.todesktop .desktop-only { - display: block; -} - -html.todesktop .desktop-hidden { - display: none; -} - -html.todesktop header { - margin-top: -14px; -} - -html.todesktop header nav { - margin-top: 8px; -} - -html.todesktop aside { - margin: 0px -6px; -} - -html.todesktop-platform-darwin .desktop-transparent { - background: transparent !important; - border: none !important; -} - html.todesktop-platform-darwin.dark main.bg-default { background: rgba(0, 0, 0, 0.6) !important; } @@ -191,26 +155,14 @@ html.todesktop-platform-darwin.light main.bg-default { background: rgba(255, 255, 255, 0.8) !important; } -/* -html.todesktop aside a { - height: 28px; - padding: 0px 8px; - font-size: 12px; - color: #383438 !important +html.todesktop.light { + --cal-bg-emphasis: hsla(0, 0%, 11%, 0.1); } -html.todesktop nav a:hover{ - background-color: inherit !important +html.todesktop.dark { + --cal-bg-emphasis: hsla(220, 2%, 26%, 0.3); } -html.todesktop nav a[aria-current="page"]{ - background: rgba(0, 0, 0, 0.1) !important; -} - -html.todesktop nav a svg{ - color: #0272F7 !important -} */ - /* Adds Utility to hide scrollbar to tailwind https://github.com/tailwindlabs/tailwindcss/discussions/2394 diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index 84d417733f..d167dfa4ae 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import prismaMock from "../../../../tests/libs/__mocks__/prismaMock"; import type { EventType } from "@prisma/client"; @@ -97,7 +98,6 @@ describe("handleChildrenEventTypes", () => { it("Adds new users", async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - // eslint-disable-next-line const { schedulingType, id, @@ -141,7 +141,6 @@ describe("handleChildrenEventTypes", () => { it("Updates old users", async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - // eslint-disable-next-line const { schedulingType, id, @@ -237,7 +236,6 @@ describe("handleChildrenEventTypes", () => { it("Deletes existent event types for new users added", async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - // eslint-disable-next-line const { schedulingType, id, @@ -282,7 +280,6 @@ describe("handleChildrenEventTypes", () => { it("Deletes existent event types for old users updated", async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - // eslint-disable-next-line const { schedulingType, id, diff --git a/apps/web/test/utils/bookingScenario/bookingScenario.ts b/apps/web/test/utils/bookingScenario/bookingScenario.ts index 65efac03e9..06271d0f8e 100644 --- a/apps/web/test/utils/bookingScenario/bookingScenario.ts +++ b/apps/web/test/utils/bookingScenario/bookingScenario.ts @@ -12,14 +12,16 @@ import "vitest-fetch-mock"; import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; import { handleStripePaymentSuccess } from "@calcom/features/ee/payments/api/webhook"; +import { weekdayToWeekIndex, type WeekDays } from "@calcom/lib/date-fns"; import type { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; +import type { WorkflowActions, WorkflowTemplates, WorkflowTriggerEvents } from "@calcom/prisma/client"; import type { SchedulingType } from "@calcom/prisma/enums"; import type { BookingStatus } from "@calcom/prisma/enums"; import type { AppMeta } from "@calcom/types/App"; import type { NewCalendarEventType } from "@calcom/types/Calendar"; -import type { EventBusyDate } from "@calcom/types/Calendar"; +import type { EventBusyDate, IntervalLimit } from "@calcom/types/Calendar"; import { getMockPaymentService } from "./MockPaymentService"; @@ -35,10 +37,20 @@ type InputWebhook = { eventTriggers: WebhookTriggerEvents[]; subscriberUrl: string; }; + +type InputWorkflow = { + userId?: number | null; + teamId?: number | null; + name?: string; + activeEventTypeId?: number; + trigger: WorkflowTriggerEvents; + action: WorkflowActions; + template: WorkflowTemplates; +}; /** * Data to be mocked */ -type ScenarioData = { +export type ScenarioData = { // hosts: { id: number; eventTypeId?: number; userId?: number; isFixed?: boolean }[]; /** * Prisma would return these eventTypes @@ -54,6 +66,7 @@ type ScenarioData = { apps?: Partial[]; bookings?: InputBooking[]; webhooks?: InputWebhook[]; + workflows?: InputWorkflow[]; }; type InputCredential = typeof TestData.credentials.google & { @@ -89,6 +102,7 @@ type InputUser = Omit & { timeZone: string; }[]; destinationCalendar?: Prisma.DestinationCalendarCreateInput; + weekStart?: string; }; export type InputEventType = { @@ -110,10 +124,9 @@ export type InputEventType = { requiresConfirmation?: boolean; destinationCalendar?: Prisma.DestinationCalendarCreateInput; schedule?: InputUser["schedules"][number]; - bookingLimits?: { - PER_DAY?: number; - }; -} & Partial>; + bookingLimits?: IntervalLimit; + durationLimits?: IntervalLimit; +} & Partial>; type WhiteListedBookingProps = { id?: number; @@ -370,6 +383,43 @@ async function addWebhooks(webhooks: InputWebhook[]) { await addWebhooksToDb(webhooks); } +async function addWorkflowsToDb(workflows: InputWorkflow[]) { + await prismock.$transaction( + workflows.map((workflow) => { + return prismock.workflow.create({ + data: { + userId: workflow.userId, + teamId: workflow.teamId, + trigger: workflow.trigger, + name: workflow.name ? workflow.name : "Test Workflow", + steps: { + create: { + stepNumber: 1, + action: workflow.action, + template: workflow.template, + numberVerificationPending: false, + includeCalendarEvent: false, + }, + }, + activeOn: { + create: workflow.activeEventTypeId ? { eventTypeId: workflow.activeEventTypeId } : undefined, + }, + }, + include: { + activeOn: true, + steps: true, + }, + }); + }) + ); +} + +async function addWorkflows(workflows: InputWorkflow[]) { + log.silly("TestData: Creating Workflows", safeStringify(workflows)); + + await addWorkflowsToDb(workflows); +} + async function addUsersToDb(users: (Prisma.UserCreateInput & { schedules: Prisma.ScheduleCreateInput[] })[]) { log.silly("TestData: Creating Users", JSON.stringify(users)); await prismock.user.createMany({ @@ -509,6 +559,8 @@ export async function createBookingScenario(data: ScenarioData) { // mockBusyCalendarTimes([]); await addWebhooks(data.webhooks || []); // addPaymentMock(); + await addWorkflows(data.workflows || []); + return { eventTypes, }; @@ -536,20 +588,32 @@ export async function createOrganization(orgData: { name: string; slug: string } * - `dateIncrement` adds the increment to current day * - `monthIncrement` adds the increment to current month * - `yearIncrement` adds the increment to current year + * - `fromDate` starts incrementing from this date (default: today) */ export const getDate = ( - param: { dateIncrement?: number; monthIncrement?: number; yearIncrement?: number } = {} + param: { + dateIncrement?: number; + monthIncrement?: number; + yearIncrement?: number; + fromDate?: Date; + } = {} ) => { - let { dateIncrement, monthIncrement, yearIncrement } = param; + let { dateIncrement, monthIncrement, yearIncrement, fromDate } = param; + dateIncrement = dateIncrement || 0; monthIncrement = monthIncrement || 0; yearIncrement = yearIncrement || 0; + fromDate = fromDate || new Date(); - let _date = new Date().getDate() + dateIncrement; - let year = new Date().getFullYear() + yearIncrement; + fromDate.setDate(fromDate.getDate() + dateIncrement); + fromDate.setMonth(fromDate.getMonth() + monthIncrement); + fromDate.setFullYear(fromDate.getFullYear() + yearIncrement); + + let _date = fromDate.getDate(); + let year = fromDate.getFullYear(); // Make it start with 1 to match with DayJS requiremet - let _month = new Date().getMonth() + monthIncrement + 1; + let _month = fromDate.getMonth() + 1; // If last day of the month(As _month is plus 1 already it is going to be the 0th day of next month which is the last day of current month) const lastDayOfMonth = new Date(year, _month, 0).getDate(); @@ -568,13 +632,35 @@ export const getDate = ( const month = _month < 10 ? `0${_month}` : _month; return { - date, - month, - year, + date: String(date), + month: String(month), + year: String(year), dateString: `${year}-${month}-${date}`, }; }; +const isWeekStart = (date: Date, weekStart: WeekDays) => { + return date.getDay() === weekdayToWeekIndex(weekStart); +}; + +export const getNextMonthNotStartingOnWeekStart = (weekStart: WeekDays, from?: Date) => { + const date = from ?? new Date(); + + const incrementMonth = (date: Date) => { + date.setMonth(date.getMonth() + 1); + }; + + // start searching from the 1st day of next month + incrementMonth(date); + date.setDate(1); + + while (isWeekStart(date, weekStart)) { + incrementMonth(date); + } + + return getDate({ fromDate: date }); +}; + export function getMockedCredential({ metadataLookupKey, key, @@ -613,6 +699,16 @@ export function getGoogleCalendarCredential() { }); } +export function getAppleCalendarCredential() { + return getMockedCredential({ + metadataLookupKey: "applecalendar", + key: { + scope: + "https://www.applecalendar.example/auth/calendar.events https://www.applecalendar.example/auth/calendar.readonly", + }, + }); +} + export function getZoomAppCredential() { return getMockedCredential({ metadataLookupKey: "zoomvideo", @@ -788,21 +884,24 @@ export function getOrganizer({ selectedCalendars, destinationCalendar, defaultScheduleId, + weekStart = "Sunday", teams, + organizationId, }: { name: string; email: string; id: number; + organizationId?: number | null; schedules: InputUser["schedules"]; credentials?: InputCredential[]; selectedCalendars?: InputSelectedCalendar[]; defaultScheduleId?: number | null; destinationCalendar?: Prisma.DestinationCalendarCreateInput; + weekStart?: WeekDays; teams?: InputUser["teams"]; }) { return { ...TestData.users.example, - organizationId: null as null | number, name, email, id, @@ -811,7 +910,9 @@ export function getOrganizer({ selectedCalendars, destinationCalendar, defaultScheduleId, + weekStart, teams, + organizationId, }; } @@ -822,6 +923,7 @@ export function getScenarioData( usersApartFromOrganizer = [], apps = [], webhooks, + workflows, bookings, }: // hosts = [], { @@ -830,6 +932,7 @@ export function getScenarioData( apps?: ScenarioData["apps"]; usersApartFromOrganizer?: ScenarioData["users"]; webhooks?: ScenarioData["webhooks"]; + workflows?: ScenarioData["workflows"]; bookings?: ScenarioData["bookings"]; // hosts?: ScenarioData["hosts"]; }, @@ -856,6 +959,7 @@ export function getScenarioData( eventTypes: eventTypes.map((eventType, index) => { return { ...eventType, + teamId: eventType.teamId || null, title: `Test Event Type - ${index + 1}`, description: `It's a test event type - ${index + 1}`, }; @@ -863,6 +967,7 @@ export function getScenarioData( users: users.map((user) => { const newUser = { ...user, + organizationId: user.organizationId ?? null, }; if (user.destinationCalendar) { newUser.destinationCalendar = { @@ -876,7 +981,8 @@ export function getScenarioData( apps: [...apps], webhooks, bookings: bookings || [], - }; + workflows, + } satisfies ScenarioData; } export function enableEmailFeature() { diff --git a/apps/web/test/utils/bookingScenario/expects.ts b/apps/web/test/utils/bookingScenario/expects.ts index 5db6e68c4c..64f04b4b7a 100644 --- a/apps/web/test/utils/bookingScenario/expects.ts +++ b/apps/web/test/utils/bookingScenario/expects.ts @@ -4,11 +4,11 @@ import type { WebhookTriggerEvents, Booking, BookingReference, DestinationCalend import { parse } from "node-html-parser"; import type { VEvent } from "node-ical"; import ical from "node-ical"; -import { expect } from "vitest"; +import { expect, vi } from "vitest"; import "vitest-fetch-mock"; import dayjs from "@calcom/dayjs"; -import { WEBAPP_URL } from "@calcom/lib/constants"; +import { CAL_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -68,6 +68,7 @@ type ExpectedEmail = { filename: string; iCalUID?: string; recurrence?: Recurrence; + method: string; }; /** * Checks that there is no @@ -135,7 +136,7 @@ expect.extend({ return { pass: false, - message: () => `Email content ${isNot ? "is" : "is not"} matching`, + message: () => `Email content ${isNot ? "is" : "is not"} matching. ${JSON.stringify(emailsToLog)}`, actual: actualEmailContent, expected: expectedEmailContent, }; @@ -162,9 +163,14 @@ expect.extend({ } if (!expectedEmail.noIcs && !isIcsUIDExpected) { + const icsObjectKeys = icsObject ? Object.keys(icsObject) : []; + const icsKey = icsObjectKeys.find((key) => key !== "vcalendar"); + if (!icsKey) throw new Error("icsKey not found"); return { pass: false, - actual: JSON.stringify(icsObject), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + actual: icsObject[icsKey].uid!, expected: expectedEmail.ics?.iCalUID, message: () => `Expected ICS UID ${isNot ? "is" : "isn't"} present in actual`, }; @@ -298,8 +304,41 @@ export function expectWebhookToHaveBeenCalledWith( } } -export function expectWorkflowToBeTriggered() { - // TODO: Implement this. +export function expectWorkflowToBeTriggered({ + emails, + organizer, +}: { + emails: Fixtures["emails"]; + organizer: { email: string; name: string; timeZone: string }; +}) { + const subjectPattern = /^Reminder: /i; + expect(emails.get()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + subject: expect.stringMatching(subjectPattern), + to: organizer.email, + }), + ]) + ); +} + +export function expectWorkflowToBeNotTriggered({ + emails, + organizer, +}: { + emails: Fixtures["emails"]; + organizer: { email: string; name: string; timeZone: string }; +}) { + const subjectPattern = /^Reminder: /i; + + expect(emails.get()).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + subject: expect.stringMatching(subjectPattern), + to: organizer.email, + }), + ]) + ); } export async function expectBookingToBeInDatabase( @@ -343,7 +382,7 @@ export function expectSuccessfulBookingCreationEmails({ bookingTimeRange?: { start: Date; end: Date }; booking: { uid: string; urlOrigin?: string }; }) { - const bookingUrlOrigin = booking.urlOrigin || WEBAPP_URL; + const bookingUrlOrigin = booking.urlOrigin || CAL_URL; expect(emails).toHaveEmail( { titleTag: "confirmed_event_type_subject", @@ -375,6 +414,7 @@ export function expectSuccessfulBookingCreationEmails({ filename: "event.ics", iCalUID: `${iCalUID}`, recurrence, + method: "REQUEST", }, }, `${organizer.email}` @@ -397,8 +437,9 @@ export function expectSuccessfulBookingCreationEmails({ to: `${booker.name} <${booker.email}>`, ics: { filename: "event.ics", - iCalUID: iCalUID, + iCalUID: `${iCalUID}`, recurrence, + method: "REQUEST", }, links: recurrence ? [ @@ -436,7 +477,8 @@ export function expectSuccessfulBookingCreationEmails({ to: `${otherTeamMember.email}`, ics: { filename: "event.ics", - iCalUID: iCalUID, + iCalUID: `${iCalUID}`, + method: "REQUEST", }, links: [ { @@ -472,7 +514,8 @@ export function expectSuccessfulBookingCreationEmails({ to: `${guest.email}`, ics: { filename: "event.ics", - iCalUID: iCalUID, + iCalUID: `${iCalUID}`, + method: "REQUEST", }, }, `${guest.name} <${guest.email}` @@ -529,6 +572,7 @@ export function expectCalendarEventCreationFailureEmails({ ics: { filename: "event.ics", iCalUID, + method: "REQUEST", }, }, `${organizer.email}` @@ -541,13 +585,14 @@ export function expectCalendarEventCreationFailureEmails({ ics: { filename: "event.ics", iCalUID, + method: "REQUEST", }, }, `${booker.name} <${booker.email}>` ); } -export function expectSuccessfulRoudRobinReschedulingEmails({ +export function expectSuccessfulRoundRobinReschedulingEmails({ emails, newOrganizer, prevOrganizer, @@ -557,32 +602,38 @@ export function expectSuccessfulRoudRobinReschedulingEmails({ prevOrganizer: { email: string; name: string }; }) { if (newOrganizer !== prevOrganizer) { - // new organizer should recieve scheduling emails - expect(emails).toHaveEmail( - { - heading: "new_event_scheduled", - to: `${newOrganizer.email}`, - }, - `${newOrganizer.email}` - ); + vi.waitFor(() => { + // new organizer should recieve scheduling emails + expect(emails).toHaveEmail( + { + heading: "new_event_scheduled", + to: `${newOrganizer.email}`, + }, + `${newOrganizer.email}` + ); + }); - // old organizer should recieve cancelled emails - expect(emails).toHaveEmail( - { - heading: "event_request_cancelled", - to: `${prevOrganizer.email}`, - }, - `${prevOrganizer.email}` - ); + vi.waitFor(() => { + // old organizer should recieve cancelled emails + expect(emails).toHaveEmail( + { + heading: "event_request_cancelled", + to: `${prevOrganizer.email}`, + }, + `${prevOrganizer.email}` + ); + }); } else { - // organizer should recieve rescheduled emails - expect(emails).toHaveEmail( - { - heading: "event_has_been_rescheduled", - to: `${newOrganizer.email}`, - }, - `${newOrganizer.email}` - ); + vi.waitFor(() => { + // organizer should recieve rescheduled emails + expect(emails).toHaveEmail( + { + heading: "event_has_been_rescheduled", + to: `${newOrganizer.email}`, + }, + `${newOrganizer.email}` + ); + }); } } @@ -606,6 +657,7 @@ export function expectSuccessfulBookingRescheduledEmails({ ics: { filename: "event.ics", iCalUID, + method: "REQUEST", }, appsStatus, }, @@ -619,6 +671,7 @@ export function expectSuccessfulBookingRescheduledEmails({ ics: { filename: "event.ics", iCalUID, + method: "REQUEST", }, }, `${booker.name} <${booker.email}>` @@ -689,7 +742,7 @@ export function expectBookingRequestRescheduledEmails({ booking: { uid: string; urlOrigin?: string }; bookNewTimePath: string; }) { - const bookingUrlOrigin = booking.urlOrigin || WEBAPP_URL; + const bookingUrlOrigin = booking.urlOrigin || CAL_URL; expect(emails).toHaveEmail( { @@ -705,6 +758,7 @@ export function expectBookingRequestRescheduledEmails({ to: `${booker.email}`, ics: { filename: "event.ics", + method: "REQUEST", }, }, `${booker.email}` @@ -718,6 +772,7 @@ export function expectBookingRequestRescheduledEmails({ to: `${loggedInUser.email}`, ics: { filename: "event.ics", + method: "REQUEST", }, }, `${loggedInUser.email}` @@ -1068,3 +1123,11 @@ export async function expectBookingInDBToBeRescheduledFromTo({ from, to }: { fro ...to, }); } + +export function expectICalUIDAsString(iCalUID: string | undefined | null) { + if (typeof iCalUID !== "string") { + throw new Error("iCalUID is not a string"); + } + + return iCalUID; +} diff --git a/apps/web/test/utils/bookingScenario/test.ts b/apps/web/test/utils/bookingScenario/test.ts index 74eb503f86..7a00f894fd 100644 --- a/apps/web/test/utils/bookingScenario/test.ts +++ b/apps/web/test/utils/bookingScenario/test.ts @@ -1,9 +1,11 @@ import type { TestFunction } from "vitest"; +import { WEBSITE_URL } from "@calcom/lib/constants"; import { test } from "@calcom/web/test/fixtures/fixtures"; import type { Fixtures } from "@calcom/web/test/fixtures/fixtures"; import { createOrganization } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; +const WEBSITE_PROTOCOL = new URL(WEBSITE_URL).protocol; const _testWithAndWithoutOrg = ( description: Parameters[0], fn: Parameters[1], @@ -28,7 +30,7 @@ const _testWithAndWithoutOrg = ( skip, org: { organization: org, - urlOrigin: `http://${org.slug}.cal.local:3000`, + urlOrigin: `${WEBSITE_PROTOCOL}//${org.slug}.cal.local:3000`, }, }); }, diff --git a/package.json b/package.json index 586870f28e..b45ec93a00 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "prismock": "^1.21.1", "tsc-absolute": "^1.0.0", "typescript": "^4.9.4", - "vitest": "^0.34.3", + "vitest": "^0.34.6", "vitest-fetch-mock": "^0.2.2", "vitest-mock-extended": "^1.1.3" }, @@ -113,8 +113,9 @@ "@types/node": "16.9.1", "@types/react": "18.0.26", "@types/react-dom": "^18.0.9", - "libphonenumber-js@^1.10.12": "patch:libphonenumber-js@npm%3A1.10.12#./.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch", - "next-i18next@^13.2.2": "patch:next-i18next@npm%3A13.3.0#./.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch" + "next-i18next@^13.2.2": "patch:next-i18next@npm%3A13.3.0#./.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch", + "libphonenumber-js@^1.10.51": "patch:libphonenumber-js@npm%3A1.10.51#./.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch", + "libphonenumber-js@^1.10.12": "patch:libphonenumber-js@npm%3A1.10.51#./.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch" }, "lint-staged": { "(apps|packages)/**/*.{js,ts,jsx,tsx}": [ diff --git a/packages/app-store/_components/AppCard.tsx b/packages/app-store/_components/AppCard.tsx index e946aab684..4445b2fb5d 100644 --- a/packages/app-store/_components/AppCard.tsx +++ b/packages/app-store/_components/AppCard.tsx @@ -19,6 +19,8 @@ export default function AppCard({ children, returnTo, teamId, + disableSwitch, + switchTooltip, }: { app: RouterOutputs["viewer"]["integrations"]["items"][number] & { credentialOwner?: CredentialOwner }; description?: React.ReactNode; @@ -28,10 +30,12 @@ export default function AppCard({ returnTo?: string; teamId?: number; LockedIcon?: React.ReactNode; + disableSwitch?: boolean; + switchTooltip?: string; }) { const { t } = useTranslation(); const [animationRef] = useAutoAnimate(); - const { setAppData, LockedIcon, disabled } = useAppContextWithSchema(); + const { setAppData, LockedIcon, disabled: managedDisabled } = useAppContextWithSchema(); return (
      { if (switchOnClick) { switchOnClick(enabled); @@ -100,6 +103,8 @@ export default function AppCard({ }} checked={switchChecked} LockedIcon={LockedIcon} + data-testid={`${app.slug}-app-switch`} + tooltip={switchTooltip} />
      ) : ( diff --git a/packages/app-store/_components/EventTypeAppCardInterface.tsx b/packages/app-store/_components/EventTypeAppCardInterface.tsx index 8f34ce9e11..c02ec480ba 100644 --- a/packages/app-store/_components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/_components/EventTypeAppCardInterface.tsx @@ -1,6 +1,9 @@ +import type z from "zod"; + import type { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext"; import EventTypeAppContext from "@calcom/app-store/EventTypeAppContext"; import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated"; +import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { RouterOutputs } from "@calcom/trpc/react"; import { ErrorBoundary } from "@calcom/ui"; @@ -14,6 +17,7 @@ export const EventTypeAppCard = (props: { setAppData: SetAppData; // For event type apps, get these props from shouldLockDisableProps LockedIcon?: JSX.Element | false; + eventTypeFormMetadata: z.infer; disabled?: boolean; }) => { const { app, getAppData, setAppData, LockedIcon, disabled } = props; diff --git a/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts b/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts index fc0d7fc21d..958205707a 100644 --- a/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts +++ b/packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts @@ -14,6 +14,7 @@ export type ParseRefreshTokenResponse = | z.infer | z.infer; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const parseRefreshTokenResponse = (response: any, schema: z.ZodTypeAny) => { let refreshTokenResponse; const credentialSyncingEnabled = diff --git a/packages/app-store/_utils/oauth/refreshOAuthTokens.ts b/packages/app-store/_utils/oauth/refreshOAuthTokens.ts index b667154c90..999ada5245 100644 --- a/packages/app-store/_utils/oauth/refreshOAuthTokens.ts +++ b/packages/app-store/_utils/oauth/refreshOAuthTokens.ts @@ -1,5 +1,6 @@ import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const refreshOAuthTokens = async (refreshFunction: () => any, appSlug: string, userId: number | null) => { // Check that app syncing is enabled and that the credential belongs to a user if (APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT && userId) { diff --git a/packages/app-store/_utils/payments/checkForMultiplePaymentApps.ts b/packages/app-store/_utils/payments/checkForMultiplePaymentApps.ts new file mode 100644 index 0000000000..e1186b4a1e --- /dev/null +++ b/packages/app-store/_utils/payments/checkForMultiplePaymentApps.ts @@ -0,0 +1,34 @@ +import type z from "zod"; + +import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; + +import type { appDataSchemas } from "../../apps.schemas.generated"; + +/** + * + * @param metadata The event type metadata + * @param inclusive Determines if multiple includes the case of 1 + * @returns boolean + */ +const checkForMultiplePaymentApps = ( + metadata: z.infer, + inclusive = false +) => { + let enabledPaymentApps = 0; + for (const appKey in metadata?.apps) { + const app = metadata?.apps[appKey as keyof typeof appDataSchemas]; + + if ("appCategories" in app) { + const isPaymentApp = app.appCategories.includes("payment"); + if (isPaymentApp && app.enabled) { + enabledPaymentApps++; + } + } else if ("price" in app && app.enabled) { + enabledPaymentApps++; + } + } + + return inclusive ? enabledPaymentApps >= 1 : enabledPaymentApps > 1; +}; + +export default checkForMultiplePaymentApps; diff --git a/packages/app-store/alby/components/EventTypeAppCardInterface.tsx b/packages/app-store/alby/components/EventTypeAppCardInterface.tsx index 0eb71ba136..f6bf717dd6 100644 --- a/packages/app-store/alby/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/alby/components/EventTypeAppCardInterface.tsx @@ -10,12 +10,17 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Alert, Select, TextField } from "@calcom/ui"; import { SatSymbol } from "@calcom/ui/components/icon/SatSymbol"; +import checkForMultiplePaymentApps from "../../_utils/payments/checkForMultiplePaymentApps"; import type { appDataSchema } from "../zod"; import { PaypalPaymentOptions as paymentOptions } from "../zod"; type Option = { value: string; label: string }; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + eventTypeFormMetadata, +}) { const { asPath } = useRouter(); const { getAppData, setAppData } = useAppContextWithSchema(); const price = getAppData("price"); @@ -32,6 +37,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ const [requirePayment, setRequirePayment] = useState(getAppData("enabled")); const { t } = useLocale(); const recurringEventDefined = eventType.recurringEvent?.count !== undefined; + const otherPaymentAppEnabled = checkForMultiplePaymentApps(eventTypeFormMetadata); + + const shouldDisableSwitch = !requirePayment && otherPaymentAppEnabled; // make sure a currency is selected useEffect(() => { @@ -48,7 +56,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ switchOnClick={(enabled) => { setRequirePayment(enabled); }} - description={<>Add bitcoin lightning payments to your events}> + description={<>Add bitcoin lightning payments to your events} + disableSwitch={shouldDisableSwitch} + switchTooltip={shouldDisableSwitch ? t("other_payment_app_enabled") : undefined}> <> {recurringEventDefined ? ( diff --git a/packages/app-store/applecalendar/pages/setup/index.tsx b/packages/app-store/applecalendar/pages/setup/index.tsx index 0bef9573c6..af700122e1 100644 --- a/packages/app-store/applecalendar/pages/setup/index.tsx +++ b/packages/app-store/applecalendar/pages/setup/index.tsx @@ -5,7 +5,7 @@ import { Toaster } from "react-hot-toast"; import { APP_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Alert, Button, Form, TextField } from "@calcom/ui"; +import { Alert, Button, Form, PasswordField, TextField } from "@calcom/ui"; export default function AppleCalendarSetup() { const { t } = useLocale(); @@ -20,8 +20,8 @@ export default function AppleCalendarSetup() { const [errorMessage, setErrorMessage] = useState(""); return ( -
      -
      +
      +
      {/* eslint-disable @next/next/no-img-element */} @@ -32,12 +32,14 @@ export default function AppleCalendarSetup() { />
      -

      {t("connect_apple_server")}

      +

      + {t("connect_apple_server")} +

      {t("apple_server_generate_password", { appName: APP_NAME })}{" "} @@ -45,7 +47,7 @@ export default function AppleCalendarSetup() { . {t("credentials_stored_encrypted")}
      -
      +
      { @@ -65,7 +67,7 @@ export default function AppleCalendarSetup() { } }}>
      - import("./ga4/components/EventTypeAppCardInterface")), giphy: dynamic(() => import("./giphy/components/EventTypeAppCardInterface")), gtm: dynamic(() => import("./gtm/components/EventTypeAppCardInterface")), + matomo: dynamic(() => import("./matomo/components/EventTypeAppCardInterface")), metapixel: dynamic(() => import("./metapixel/components/EventTypeAppCardInterface")), paypal: dynamic(() => import("./paypal/components/EventTypeAppCardInterface")), plausible: dynamic(() => import("./plausible/components/EventTypeAppCardInterface")), diff --git a/packages/app-store/apps.keys-schemas.generated.ts b/packages/app-store/apps.keys-schemas.generated.ts index 94c0ae6188..541bb55bd3 100644 --- a/packages/app-store/apps.keys-schemas.generated.ts +++ b/packages/app-store/apps.keys-schemas.generated.ts @@ -15,10 +15,12 @@ import { appKeysSchema as intercom_zod_ts } from "./intercom/zod"; import { appKeysSchema as jitsivideo_zod_ts } from "./jitsivideo/zod"; import { appKeysSchema as larkcalendar_zod_ts } from "./larkcalendar/zod"; import { appKeysSchema as make_zod_ts } from "./make/zod"; +import { appKeysSchema as matomo_zod_ts } from "./matomo/zod"; import { appKeysSchema as metapixel_zod_ts } from "./metapixel/zod"; import { appKeysSchema as office365calendar_zod_ts } from "./office365calendar/zod"; import { appKeysSchema as office365video_zod_ts } from "./office365video/zod"; import { appKeysSchema as paypal_zod_ts } from "./paypal/zod"; +import { appKeysSchema as pipedrive_crm_zod_ts } from "./pipedrive-crm/zod"; import { appKeysSchema as plausible_zod_ts } from "./plausible/zod"; import { appKeysSchema as qr_code_zod_ts } from "./qr_code/zod"; import { appKeysSchema as routing_forms_zod_ts } from "./routing-forms/zod"; @@ -51,10 +53,12 @@ export const appKeysSchemas = { jitsivideo: jitsivideo_zod_ts, larkcalendar: larkcalendar_zod_ts, make: make_zod_ts, + matomo: matomo_zod_ts, metapixel: metapixel_zod_ts, office365calendar: office365calendar_zod_ts, office365video: office365video_zod_ts, paypal: paypal_zod_ts, + "pipedrive-crm": pipedrive_crm_zod_ts, plausible: plausible_zod_ts, qr_code: qr_code_zod_ts, "routing-forms": routing_forms_zod_ts, diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts index ed50360009..e2c9f3e978 100644 --- a/packages/app-store/apps.metadata.generated.ts +++ b/packages/app-store/apps.metadata.generated.ts @@ -33,6 +33,7 @@ import intercom_config_json from "./intercom/config.json"; import { metadata as jitsivideo__metadata_ts } from "./jitsivideo/_metadata"; import { metadata as larkcalendar__metadata_ts } from "./larkcalendar/_metadata"; import make_config_json from "./make/config.json"; +import matomo_config_json from "./matomo/config.json"; import metapixel_config_json from "./metapixel/config.json"; import mirotalk_config_json from "./mirotalk/config.json"; import n8n_config_json from "./n8n/config.json"; @@ -41,10 +42,12 @@ import office365video_config_json from "./office365video/config.json"; import paypal_config_json from "./paypal/config.json"; import ping_config_json from "./ping/config.json"; import pipedream_config_json from "./pipedream/config.json"; +import pipedrive_crm_config_json from "./pipedrive-crm/config.json"; import plausible_config_json from "./plausible/config.json"; import qr_code_config_json from "./qr_code/config.json"; import raycast_config_json from "./raycast/config.json"; import riverside_config_json from "./riverside/config.json"; +import roam_config_json from "./roam/config.json"; import routing_forms_config_json from "./routing-forms/config.json"; import salesforce_config_json from "./salesforce/config.json"; import sendgrid_config_json from "./sendgrid/config.json"; @@ -109,6 +112,7 @@ export const appStoreMetadata = { jitsivideo: jitsivideo__metadata_ts, larkcalendar: larkcalendar__metadata_ts, make: make_config_json, + matomo: matomo_config_json, metapixel: metapixel_config_json, mirotalk: mirotalk_config_json, n8n: n8n_config_json, @@ -117,10 +121,12 @@ export const appStoreMetadata = { paypal: paypal_config_json, ping: ping_config_json, pipedream: pipedream_config_json, + "pipedrive-crm": pipedrive_crm_config_json, plausible: plausible_config_json, qr_code: qr_code_config_json, raycast: raycast_config_json, riverside: riverside_config_json, + roam: roam_config_json, "routing-forms": routing_forms_config_json, salesforce: salesforce_config_json, sendgrid: sendgrid_config_json, diff --git a/packages/app-store/apps.schemas.generated.ts b/packages/app-store/apps.schemas.generated.ts index 73acac0dc6..3e1e37634e 100644 --- a/packages/app-store/apps.schemas.generated.ts +++ b/packages/app-store/apps.schemas.generated.ts @@ -15,10 +15,12 @@ import { appDataSchema as intercom_zod_ts } from "./intercom/zod"; import { appDataSchema as jitsivideo_zod_ts } from "./jitsivideo/zod"; import { appDataSchema as larkcalendar_zod_ts } from "./larkcalendar/zod"; import { appDataSchema as make_zod_ts } from "./make/zod"; +import { appDataSchema as matomo_zod_ts } from "./matomo/zod"; import { appDataSchema as metapixel_zod_ts } from "./metapixel/zod"; import { appDataSchema as office365calendar_zod_ts } from "./office365calendar/zod"; import { appDataSchema as office365video_zod_ts } from "./office365video/zod"; import { appDataSchema as paypal_zod_ts } from "./paypal/zod"; +import { appDataSchema as pipedrive_crm_zod_ts } from "./pipedrive-crm/zod"; import { appDataSchema as plausible_zod_ts } from "./plausible/zod"; import { appDataSchema as qr_code_zod_ts } from "./qr_code/zod"; import { appDataSchema as routing_forms_zod_ts } from "./routing-forms/zod"; @@ -51,10 +53,12 @@ export const appDataSchemas = { jitsivideo: jitsivideo_zod_ts, larkcalendar: larkcalendar_zod_ts, make: make_zod_ts, + matomo: matomo_zod_ts, metapixel: metapixel_zod_ts, office365calendar: office365calendar_zod_ts, office365video: office365video_zod_ts, paypal: paypal_zod_ts, + "pipedrive-crm": pipedrive_crm_zod_ts, plausible: plausible_zod_ts, qr_code: qr_code_zod_ts, "routing-forms": routing_forms_zod_ts, diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index 23f3038bab..55d544efdc 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -33,6 +33,7 @@ export const apiHandlers = { jitsivideo: import("./jitsivideo/api"), larkcalendar: import("./larkcalendar/api"), make: import("./make/api"), + matomo: import("./matomo/api"), metapixel: import("./metapixel/api"), mirotalk: import("./mirotalk/api"), n8n: import("./n8n/api"), @@ -41,10 +42,12 @@ export const apiHandlers = { paypal: import("./paypal/api"), ping: import("./ping/api"), pipedream: import("./pipedream/api"), + "pipedrive-crm": import("./pipedrive-crm/api"), plausible: import("./plausible/api"), qr_code: import("./qr_code/api"), raycast: import("./raycast/api"), riverside: import("./riverside/api"), + roam: import("./roam/api"), "routing-forms": import("./routing-forms/api"), salesforce: import("./salesforce/api"), sendgrid: import("./sendgrid/api"), diff --git a/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx b/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx index cc81f0237e..cbab47df35 100644 --- a/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/basecamp3/components/EventTypeAppCardInterface.tsx @@ -18,9 +18,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ useEffect(() => { setSelectedProject({ value: data?.projects.currentProject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any label: data?.projects?.find((project: any) => project.id === data?.currentProject)?.name, }); setProjects( + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?.projects?.map((project: any) => { return { value: project.id, diff --git a/packages/app-store/basecamp3/lib/CalendarService.ts b/packages/app-store/basecamp3/lib/CalendarService.ts index 783373ac96..46531a6dee 100644 --- a/packages/app-store/basecamp3/lib/CalendarService.ts +++ b/packages/app-store/basecamp3/lib/CalendarService.ts @@ -59,6 +59,7 @@ export default class BasecampCalendarService implements Calendar { constructor(credential: CredentialPayload) { this.integrationName = "basecamp3"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any getAppKeysFromSlug("basecamp3").then(({ user_agent }: any) => { this.userAgent = user_agent as string; }); diff --git a/packages/app-store/basecamp3/trpc/_router.ts b/packages/app-store/basecamp3/trpc/_router.ts index ce2122375f..e3f9d71595 100644 --- a/packages/app-store/basecamp3/trpc/_router.ts +++ b/packages/app-store/basecamp3/trpc/_router.ts @@ -3,6 +3,7 @@ import { router } from "@calcom/trpc/server/trpc"; import { ZProjectMutationInputSchema } from "./projectMutation.schema"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const UNSTABLE_HANDLER_CACHE: any = {}; const appBasecamp3 = router({ diff --git a/packages/app-store/basecamp3/trpc/projectMutation.handler.ts b/packages/app-store/basecamp3/trpc/projectMutation.handler.ts index 6a9ffaeb9e..5f7afc2aa2 100644 --- a/packages/app-store/basecamp3/trpc/projectMutation.handler.ts +++ b/packages/app-store/basecamp3/trpc/projectMutation.handler.ts @@ -16,6 +16,10 @@ interface ProjectMutationHandlerOptions { input: TProjectMutationInputSchema; } +interface IDock { + id: number; + name: string; +} export const projectMutationHandler = async ({ ctx, input }: ProjectMutationHandlerOptions) => { const { user_agent } = await getAppKeysFromSlug("basecamp3"); const { user, prisma } = ctx; @@ -48,7 +52,7 @@ export const projectMutationHandler = async ({ ctx, input }: ProjectMutationHand } ); const scheduleJson = await scheduleResponse.json(); - const scheduleId = scheduleJson.dock.find((dock: any) => dock.name === "schedule").id; + const scheduleId = scheduleJson.dock.find((dock: IDock) => dock.name === "schedule").id; await prisma.credential.update({ where: { id: credential.id }, data: { key: { ...credentialKey, projectId: Number(projectId), scheduleId } }, diff --git a/packages/app-store/bookerApps.metadata.generated.ts b/packages/app-store/bookerApps.metadata.generated.ts index 4ecb096206..8989cb1264 100644 --- a/packages/app-store/bookerApps.metadata.generated.ts +++ b/packages/app-store/bookerApps.metadata.generated.ts @@ -15,12 +15,14 @@ import { metadata as googlevideo__metadata_ts } from "./googlevideo/_metadata"; import gtm_config_json from "./gtm/config.json"; import { metadata as huddle01video__metadata_ts } from "./huddle01video/_metadata"; import { metadata as jitsivideo__metadata_ts } from "./jitsivideo/_metadata"; +import matomo_config_json from "./matomo/config.json"; import metapixel_config_json from "./metapixel/config.json"; import mirotalk_config_json from "./mirotalk/config.json"; import office365video_config_json from "./office365video/config.json"; import ping_config_json from "./ping/config.json"; import plausible_config_json from "./plausible/config.json"; import riverside_config_json from "./riverside/config.json"; +import roam_config_json from "./roam/config.json"; import shimmervideo_config_json from "./shimmervideo/config.json"; import signal_config_json from "./signal/config.json"; import sirius_video_config_json from "./sirius_video/config.json"; @@ -48,12 +50,14 @@ export const appStoreMetadata = { gtm: gtm_config_json, huddle01video: huddle01video__metadata_ts, jitsivideo: jitsivideo__metadata_ts, + matomo: matomo_config_json, metapixel: metapixel_config_json, mirotalk: mirotalk_config_json, office365video: office365video_config_json, ping: ping_config_json, plausible: plausible_config_json, riverside: riverside_config_json, + roam: roam_config_json, shimmervideo: shimmervideo_config_json, signal: signal_config_json, sirius_video: sirius_video_config_json, diff --git a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts index 5c070c2ea1..79f3e20529 100644 --- a/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/dailyvideo/lib/VideoApiAdapter.ts @@ -145,6 +145,35 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => { }; }; + async function createInstantMeeting(endTime: string) { + // added a 1 hour buffer for room expiration + const exp = Math.round(new Date(endTime).getTime() / 1000) + 60 * 60; + + const body = { + privacy: "public", + properties: { + enable_prejoin_ui: true, + enable_knocking: true, + enable_screenshare: true, + enable_chat: true, + exp: exp, + enable_recording: "cloud", + }, + }; + + const dailyEvent = await postToDailyAPI("/rooms", body).then(dailyReturnTypeSchema.parse); + const meetingToken = await postToDailyAPI("/meeting-tokens", { + properties: { room_name: dailyEvent.name, exp: dailyEvent.config.exp, is_owner: true }, + }).then(meetingTokenSchema.parse); + + return Promise.resolve({ + type: "daily_video", + id: dailyEvent.name, + password: meetingToken.token, + url: dailyEvent.url, + }); + } + return { /** Daily doesn't need to return busy times, so we return empty */ getAvailability: () => { @@ -168,6 +197,7 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => { throw new Error("Something went wrong! Unable to get recording"); } }, + createInstantCalVideoRoom: (endTime: string) => createInstantMeeting(endTime), getRecordingDownloadLink: async (recordingId: string): Promise => { try { const res = await fetcher(`/recordings/${recordingId}/access-link?valid_for_secs=172800`).then( diff --git a/packages/app-store/eventTypeAppCardZod.ts b/packages/app-store/eventTypeAppCardZod.ts index 28c3878eb1..dd1630bc3a 100644 --- a/packages/app-store/eventTypeAppCardZod.ts +++ b/packages/app-store/eventTypeAppCardZod.ts @@ -4,6 +4,7 @@ import { z } from "zod"; export const eventTypeAppCardZod = z.object({ enabled: z.boolean().optional(), credentialId: z.number().optional(), + appCategories: z.array(z.string()).optional(), }); export const appKeysSchema = z.object({}); diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index 0ffc9a0442..80f77cb2a8 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -202,6 +202,7 @@ export default class GoogleCalendarService implements Calendar { useDefault: true, }, guestsCanSeeOtherGuests: !!calEventRaw.seatsPerTimeSlot ? calEventRaw.seatsShowAttendees : true, + iCalUID: calEventRaw.iCalUID, }; if (calEventRaw.location) { @@ -250,7 +251,6 @@ export default class GoogleCalendarService implements Calendar { type: "google_calendar", password: "", url: "", - iCalUID: event.data.iCalUID, }; } catch (error) { this.log.error( diff --git a/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts b/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts index 226b7a61cd..5c38243161 100644 --- a/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts +++ b/packages/app-store/googlecalendar/tests/google-calendar.e2e.ts @@ -4,7 +4,7 @@ import type { Page } from "@playwright/test"; import dayjs from "@calcom/dayjs"; import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; -import type { Prisma } from "@calcom/prisma/client"; +import type { CredentialPayload } from "@calcom/types/Credential"; import { test } from "@calcom/web/playwright/lib/fixtures"; import { selectSecondAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils"; @@ -13,14 +13,34 @@ import GoogleCalendarService from "../lib/CalendarService"; import { createBookingAndFetchGCalEvent, deleteBookingAndEvent, assertValueExists } from "./testUtils"; test.describe("Google Calendar", async () => { - test.describe("Test using the primary calendar", async () => { + // Skip till the tests are flaky + // eslint-disable-next-line playwright/no-skipped-test + test.describe.skip("Test using the primary calendar", async () => { let qaUsername: string; - let qaGCalCredential: Prisma.CredentialGetPayload<{ select: { id: true } }>; + let qaGCalCredential: CredentialPayload; test.beforeAll(async () => { let runIntegrationTest = false; + const errorMessage = "Could not run test"; test.skip(!!APP_CREDENTIAL_SHARING_ENABLED, "Credential sharing enabled"); + if (process.env.E2E_TEST_CALCOM_GCAL_KEYS) { + const gCalKeys = JSON.parse(process.env.E2E_TEST_CALCOM_GCAL_KEYS); + await prisma.app.update({ + where: { + slug: "google-calendar", + }, + data: { + keys: gCalKeys, + }, + }); + } else { + test.skip(!process.env.E2E_TEST_CALCOM_GCAL_KEYS, "GCal keys not found"); + } + + test.skip(!process.env.E2E_TEST_CALCOM_QA_EMAIL, "QA email not found"); + test.skip(!process.env.E2E_TEST_CALCOM_QA_PASSWORD, "QA password not found"); + if (process.env.E2E_TEST_CALCOM_QA_EMAIL && process.env.E2E_TEST_CALCOM_QA_PASSWORD) { qaGCalCredential = await prisma.credential.findFirstOrThrow({ where: { @@ -29,63 +49,59 @@ test.describe("Google Calendar", async () => { }, type: metadata.type, }, - select: { - id: true, + include: { + user: { + select: { + email: true, + }, + }, }, }); + test.skip(!qaGCalCredential, "Google QA credential not found"); const qaUserQuery = await prisma.user.findFirstOrThrow({ where: { email: process.env.E2E_TEST_CALCOM_QA_EMAIL, }, select: { + id: true, username: true, }, }); + test.skip(!qaUserQuery, "QA user not found"); + assertValueExists(qaUserQuery.username, "qaUsername"); qaUsername = qaUserQuery.username; + test.skip(!qaUsername, "QA username not found"); + + const googleCalendarService = new GoogleCalendarService(qaGCalCredential); + + const calendars = await googleCalendarService.listCalendars(); + + const primaryCalendarName = calendars.find((calendar) => calendar.primary)?.name; + assertValueExists(primaryCalendarName, "primaryCalendarName"); + + await prisma.destinationCalendar.upsert({ + where: { + userId: qaUserQuery.id, + externalId: primaryCalendarName, + eventTypeId: undefined, + }, + update: {}, + create: { + integration: "google_calendar", + userId: qaUserQuery.id, + externalId: primaryCalendarName, + credentialId: qaGCalCredential.id, + }, + }); + if (qaGCalCredential && qaUsername) runIntegrationTest = true; } - test.skip(!runIntegrationTest, "QA user not found"); - }); - - test.beforeEach(async ({ page, users }) => { - assertValueExists(process.env.E2E_TEST_CALCOM_QA_EMAIL, "qaEmail"); - - const qaUserStore = await users.set(process.env.E2E_TEST_CALCOM_QA_EMAIL); - - await qaUserStore.apiLogin(process.env.E2E_TEST_CALCOM_QA_PASSWORD); - - // Need to refresh keys from DB - const refreshedCredential = await prisma.credential.findFirst({ - where: { - id: qaGCalCredential?.id, - }, - include: { - user: { - select: { - email: true, - }, - }, - }, - }); - assertValueExists(refreshedCredential, "refreshedCredential"); - - const googleCalendarService = new GoogleCalendarService(refreshedCredential); - - const calendars = await googleCalendarService.listCalendars(); - - const primaryCalendarName = calendars.find((calendar) => calendar.primary)?.name; - assertValueExists(primaryCalendarName, "primaryCalendarName"); - - await page.goto("/apps/installed/calendar"); - - await page.waitForSelector('[title*="Create events on"]'); - await page.locator('[title*="Create events on"]').locator("svg").click(); - await page.locator("#react-select-2-option-0-0").getByText(primaryCalendarName).click(); + test.skip(!runIntegrationTest, errorMessage); }); test("On new booking, event should be created on GCal", async ({ page }) => { diff --git a/packages/app-store/googlecalendar/tests/testUtils.ts b/packages/app-store/googlecalendar/tests/testUtils.ts index 5d4920d2b1..c7960fd3bd 100644 --- a/packages/app-store/googlecalendar/tests/testUtils.ts +++ b/packages/app-store/googlecalendar/tests/testUtils.ts @@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; -import { bookFirstEvent } from "@calcom/web/playwright/lib/testUtils"; +import { bookTimeSlot, selectSecondAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils"; import metadata from "../_metadata"; import GoogleCalendarService from "../lib/CalendarService"; @@ -20,8 +20,13 @@ export const createBookingAndFetchGCalEvent = async ( qaGCalCredential: Prisma.CredentialGetPayload<{ select: { id: true } }> | null, qaUsername: string ) => { - await page.goto(`/${qaUsername}`); - await bookFirstEvent(page); + await page.goto(`/${qaUsername}/15min`); + await selectSecondAvailableTimeSlotNextMonth(page); + await bookTimeSlot(page); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await page.waitForNavigation({ state: "networkidle" }); + await page.locator("[data-testid=success-page]"); const bookingUrl = await page.url(); const bookingUid = bookingUrl.match(/booking\/([^\/?]+)/); @@ -64,6 +69,7 @@ export const createBookingAndFetchGCalEvent = async ( }), ]); assertValueExists(gCalReference, "gCalReference"); + assertValueExists(booking, "booking"); // Need to refresh keys from DB diff --git a/packages/app-store/index.ts b/packages/app-store/index.ts index 2a90955403..940e644994 100644 --- a/packages/app-store/index.ts +++ b/packages/app-store/index.ts @@ -16,6 +16,7 @@ const appStore = { office365video: () => import("./office365video"), plausible: () => import("./plausible"), paypal: () => import("./paypal"), + "pipedrive-crm": () => import("./pipedrive-crm"), salesforce: () => import("./salesforce"), zohocrm: () => import("./zohocrm"), sendgrid: () => import("./sendgrid"), diff --git a/packages/app-store/matomo/DESCRIPTION.md b/packages/app-store/matomo/DESCRIPTION.md new file mode 100644 index 0000000000..d07280ea4c --- /dev/null +++ b/packages/app-store/matomo/DESCRIPTION.md @@ -0,0 +1,6 @@ +--- +items: + - 1.png +--- + +{DESCRIPTION} diff --git a/packages/app-store/matomo/api/add.ts b/packages/app-store/matomo/api/add.ts new file mode 100644 index 0000000000..6ab3106577 --- /dev/null +++ b/packages/app-store/matomo/api/add.ts @@ -0,0 +1,16 @@ +import { createDefaultInstallation } from "@calcom/app-store/_utils/installation"; +import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; + +import appConfig from "../config.json"; + +const handler: AppDeclarativeHandler = { + appType: appConfig.type, + variant: appConfig.variant, + slug: appConfig.slug, + supportsMultipleInstalls: false, + handlerType: "add", + createCredential: ({ appType, user, slug, teamId }) => + createDefaultInstallation({ appType, userId: user.id, slug, key: {}, teamId }), +}; + +export default handler; diff --git a/packages/app-store/matomo/api/index.ts b/packages/app-store/matomo/api/index.ts new file mode 100644 index 0000000000..4c0d2ead01 --- /dev/null +++ b/packages/app-store/matomo/api/index.ts @@ -0,0 +1 @@ +export { default as add } from "./add"; diff --git a/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx b/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx new file mode 100644 index 0000000000..f71200486f --- /dev/null +++ b/packages/app-store/matomo/components/EventTypeAppCardInterface.tsx @@ -0,0 +1,47 @@ +import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext"; +import AppCard from "@calcom/app-store/_components/AppCard"; +import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled"; +import type { EventTypeAppCardComponent } from "@calcom/app-store/types"; +import { TextField } from "@calcom/ui"; + +import type { appDataSchema } from "../zod"; + +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { + const { getAppData, setAppData, disabled } = useAppContextWithSchema(); + const matomoUrl = getAppData("MATOMO_URL"); + const siteId = getAppData("SITE_ID"); + const { enabled, updateEnabled } = useIsAppEnabled(app); + + return ( + { + updateEnabled(e); + }} + switchChecked={enabled} + teamId={eventType.team?.id || undefined}> +
      + { + setAppData("MATOMO_URL", e.target.value); + }} + /> + { + setAppData("SITE_ID", e.target.value); + }} + /> +
      +
      + ); +}; + +export default EventTypeAppCard; diff --git a/packages/app-store/matomo/config.json b/packages/app-store/matomo/config.json new file mode 100644 index 0000000000..cedb3934e5 --- /dev/null +++ b/packages/app-store/matomo/config.json @@ -0,0 +1,29 @@ +{ + "name": "Matomo", + "slug": "matomo", + "type": "matomo_analytics", + "logo": "icon.svg", + "url": "https://cal.com/", + "variant": "analytics", + "categories": ["analytics"], + "publisher": "Cal.com, Inc.", + "email": "help@cal.com", + "description": "Google Analytics alternative that protects your data and your customers' privacy", + "extendsFeature": "EventType", + "appData": { + "tag": { + "scripts": [ + { + "src": "{MATOMO_URL}/matomo.js", + "attrs": {} + }, + { + "content": "var _paq = window._paq || [];\n _paq.push(['trackPageView']);\n _paq.push(['enableLinkTracking']);\n (function() {\n var u='{MATOMO_URL}/'; \n _paq.push(['setTrackerUrl', u+'matomo.php']);\n _paq.push(['setSiteId', '{SITE_ID}']); \n var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];\n g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);\n })();" + } + ] + } + }, + "isTemplate": false, + "__createdUsingCli": true, + "__template": "booking-pages-tag" +} diff --git a/packages/app-store/matomo/index.ts b/packages/app-store/matomo/index.ts new file mode 100644 index 0000000000..d7f3602204 --- /dev/null +++ b/packages/app-store/matomo/index.ts @@ -0,0 +1 @@ +export * as api from "./api"; diff --git a/packages/app-store/matomo/package.json b/packages/app-store/matomo/package.json new file mode 100644 index 0000000000..9d34ae7ba5 --- /dev/null +++ b/packages/app-store/matomo/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/matomo", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Google Analytics alternative that protects your data and your customers' privacy" +} diff --git a/packages/app-store/matomo/static/1.png b/packages/app-store/matomo/static/1.png new file mode 100644 index 0000000000..2fc9a9d477 Binary files /dev/null and b/packages/app-store/matomo/static/1.png differ diff --git a/packages/app-store/matomo/static/icon.svg b/packages/app-store/matomo/static/icon.svg new file mode 100644 index 0000000000..a91b34b693 --- /dev/null +++ b/packages/app-store/matomo/static/icon.svg @@ -0,0 +1 @@ + diff --git a/packages/app-store/matomo/zod.ts b/packages/app-store/matomo/zod.ts new file mode 100644 index 0000000000..c13304b444 --- /dev/null +++ b/packages/app-store/matomo/zod.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod"; + +export const appDataSchema = eventTypeAppCardZod.merge( + z.object({ + MATOMO_URL: z.string().optional(), + SITE_ID: z.string().optional(), + }) +); + +export const appKeysSchema = z.object({}); diff --git a/packages/app-store/paypal/components/EventTypeAppCardInterface.tsx b/packages/app-store/paypal/components/EventTypeAppCardInterface.tsx index db6ba04755..a51e7a91d5 100644 --- a/packages/app-store/paypal/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/paypal/components/EventTypeAppCardInterface.tsx @@ -13,12 +13,17 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Alert, Select, TextField } from "@calcom/ui"; +import checkForMultiplePaymentApps from "../../_utils/payments/checkForMultiplePaymentApps"; import type { appDataSchema } from "../zod"; import { PaypalPaymentOptions as paymentOptions } from "../zod"; type Option = { value: string; label: string }; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + eventTypeFormMetadata, +}) { const { asPath } = useRouter(); const { getAppData, setAppData } = useAppContextWithSchema(); const price = getAppData("price"); @@ -38,6 +43,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ const [requirePayment, setRequirePayment] = useState(getAppData("enabled")); const { t } = useLocale(); const recurringEventDefined = eventType.recurringEvent?.count !== undefined; + const otherPaymentAppEnabled = checkForMultiplePaymentApps(eventTypeFormMetadata); + + const shouldDisableSwitch = !requirePayment && otherPaymentAppEnabled; useEffect(() => { if (requirePayment) { @@ -48,6 +56,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ setAppData("paymentOption", paymentOptions[0].value); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -58,7 +67,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ switchOnClick={(enabled) => { setRequirePayment(enabled); }} - description={<>Add Paypal payment to your events}> + description={<>Add Paypal payment to your events} + disableSwitch={shouldDisableSwitch} + switchTooltip={shouldDisableSwitch ? t("other_payment_app_enabled") : undefined}> <> {recurringEventDefined ? ( @@ -77,6 +88,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ required className="block w-full rounded-sm pl-2 text-sm" placeholder="Price" + data-testid="paypal-price-input" onChange={(e) => { setAppData("price", Number(e.target.value) * 100); if (selectedCurrency) { diff --git a/packages/app-store/paypal/config.json b/packages/app-store/paypal/config.json index f6a7ce84cd..5624fd3f39 100644 --- a/packages/app-store/paypal/config.json +++ b/packages/app-store/paypal/config.json @@ -3,7 +3,7 @@ "slug": "paypal", "type": "paypal_payment", "logo": "icon.svg", - "url": "https://example.com/link", + "url": "https://paypal.com", "variant": "payment", "categories": ["payment"], "publisher": "Cal.com", diff --git a/packages/app-store/paypal/lib/PaymentService.ts b/packages/app-store/paypal/lib/PaymentService.ts index 9ad68c6846..ce3b1862bb 100644 --- a/packages/app-store/paypal/lib/PaymentService.ts +++ b/packages/app-store/paypal/lib/PaymentService.ts @@ -8,7 +8,6 @@ import { ErrorCode } from "@calcom/lib/errorCodes"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import prisma from "@calcom/prisma"; -import type { CalendarEvent } from "@calcom/types/Calendar"; import type { IAbstractPaymentService } from "@calcom/types/PaymentService"; import { paymentOptionEnum } from "../zod"; @@ -179,10 +178,7 @@ export class PaymentService implements IAbstractPaymentService { throw new Error("Paypal: Payment method could not be collected"); } } - chargeCard( - payment: Pick, - bookingId: number - ): Promise { + chargeCard(): Promise { throw new Error("Method not implemented."); } getPaymentPaidStatus(): Promise { @@ -191,19 +187,10 @@ export class PaymentService implements IAbstractPaymentService { getPaymentDetails(): Promise { throw new Error("Method not implemented."); } - afterPayment( - event: CalendarEvent, - booking: { - user: { email: string | null; name: string | null; timeZone: string } | null; - id: number; - startTime: { toISOString: () => string }; - uid: string; - }, - paymentData: Payment - ): Promise { + afterPayment(): Promise { return Promise.resolve(); } - deletePayment(paymentId: number): Promise { + deletePayment(): Promise { return Promise.resolve(false); } diff --git a/packages/app-store/pipedrive-crm/DESCRIPTION.md b/packages/app-store/pipedrive-crm/DESCRIPTION.md new file mode 100644 index 0000000000..1ace85dd67 --- /dev/null +++ b/packages/app-store/pipedrive-crm/DESCRIPTION.md @@ -0,0 +1,6 @@ +--- +items: +- pipedrive-banner.jpeg +--- + +{DESCRIPTION} diff --git a/packages/app-store/pipedrive-crm/README.md b/packages/app-store/pipedrive-crm/README.md new file mode 100644 index 0000000000..ac053cab04 --- /dev/null +++ b/packages/app-store/pipedrive-crm/README.md @@ -0,0 +1,19 @@ +## Pipedrive Integration via Revert + +#### Obtaining Pipedrive Client ID and Secret + +* Open [Pipedrive Developers Corner](https://developers.pipedrive.com/) and sign in to your account, or create a new one +* Go to Settings > (company name) Developer Hub +* Create a Pipedrive app, using the steps mentioned [here](https://pipedrive.readme.io/docs/marketplace-creating-a-proper-app#create-an-app-in-5-simple-steps) + * You can skip this step and use the default revert Pipedrive app +* Set `https://app.revert.dev/oauth-callback/pipedrive` as a callback url for your app +* **Get your client\_id and client\_secret**: + * Go to the "OAuth & access scopes" tab of your app + * Copy your client\_id and client\_secret + +#### Obtaining Revert API keys + +* Create an account on Revert if you don't already have one. (https://app.revert.dev/sign-up) +* Login to your revert dashboard (https://app.revert.dev/sign-in) and click on `Customize your apps` - `Pipedrive` +* Enter the `client_id` and `client_secret` you copied in the previous step +* Enter the `client_id` and `client_secret` previously copied to `Settings > Admin > Apps > CRM > Pipedrive` by clicking the `Edit` button on the app settings. diff --git a/packages/app-store/pipedrive-crm/api/add.ts b/packages/app-store/pipedrive-crm/api/add.ts new file mode 100644 index 0000000000..a5e06acf01 --- /dev/null +++ b/packages/app-store/pipedrive-crm/api/add.ts @@ -0,0 +1,35 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { createDefaultInstallation } from "@calcom/app-store/_utils/installation"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { HttpError } from "@calcom/lib/http-error"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; +import appConfig from "../config.json"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" }); + const appKeys = await getAppKeysFromSlug(appConfig.slug); + let client_id = ""; + if (typeof appKeys.client_id === "string") client_id = appKeys.client_id; + if (!client_id) return res.status(400).json({ message: "pipedrive client id missing." }); + // Check that user is authenticated + req.session = await getServerSession({ req, res }); + const { teamId } = req.query; + const userId = req.session?.user.id; + if (!userId) { + throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" }); + } + await createDefaultInstallation({ + appType: `${appConfig.slug}_other_calendar`, + userId: userId, + slug: appConfig.slug, + key: {}, + teamId: Number(teamId), + }); + const tenantId = teamId ? teamId : userId; + res.status(200).json({ + url: `https://oauth.pipedrive.com/oauth/authorize?client_id=${appKeys.client_id}&redirect_uri=https://app.revert.dev/oauth-callback/pipedrive&state={%22tenantId%22:%22${tenantId}%22,%22revertPublicToken%22:%22${process.env.REVERT_PUBLIC_TOKEN}%22}`, + newTab: true, + }); +} diff --git a/packages/app-store/pipedrive-crm/api/callback.ts b/packages/app-store/pipedrive-crm/api/callback.ts new file mode 100644 index 0000000000..46e1433328 --- /dev/null +++ b/packages/app-store/pipedrive-crm/api/callback.ts @@ -0,0 +1,19 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; + +import getInstalledAppPath from "../../_utils/getInstalledAppPath"; +import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState"; +import appConfig from "../config.json"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (!req.session?.user?.id) { + return res.status(401).json({ message: "You must be logged in to do this" }); + } + + const state = decodeOAuthState(req); + res.redirect( + getSafeRedirectUrl(state?.returnTo) ?? + getInstalledAppPath({ variant: appConfig.variant, slug: appConfig.slug }) + ); +} diff --git a/packages/app-store/pipedrive-crm/api/index.ts b/packages/app-store/pipedrive-crm/api/index.ts new file mode 100644 index 0000000000..eb12c1b4ed --- /dev/null +++ b/packages/app-store/pipedrive-crm/api/index.ts @@ -0,0 +1,2 @@ +export { default as add } from "./add"; +export { default as callback } from "./callback"; diff --git a/packages/app-store/pipedrive-crm/config.json b/packages/app-store/pipedrive-crm/config.json new file mode 100644 index 0000000000..ff390101ba --- /dev/null +++ b/packages/app-store/pipedrive-crm/config.json @@ -0,0 +1,17 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Pipedrive CRM", + "slug": "pipedrive-crm", + "type": "pipedrive-crm_other_calendar", + "logo": "icon.svg", + "url": "https://revert.dev", + "variant": "crm", + "categories": ["crm"], + "publisher": "Revert.dev ", + "email": "jatin@revert.dev", + "description": "Founded in 2010, Pipedrive is an easy and effective sales CRM that drives small business growth.\r\rToday, Pipedrive is used by revenue teams at more than 100,000 companies worldwide. Pipedrive is headquartered in New York and has offices across Europe and the US.\r\rThe company is backed by majority holder Vista Equity Partners, Bessemer Venture Partners, Insight Partners, Atomico, and DTCP.\r\rLearn more at www.pipedrive.com.", + "isTemplate": false, + "__createdUsingCli": true, + "__template": "basic", + "dirName": "pipedrive-crm" +} diff --git a/packages/app-store/pipedrive-crm/index.ts b/packages/app-store/pipedrive-crm/index.ts new file mode 100644 index 0000000000..e2e9d7b029 --- /dev/null +++ b/packages/app-store/pipedrive-crm/index.ts @@ -0,0 +1,2 @@ +export * as api from "./api"; +export * as lib from "./lib"; diff --git a/packages/app-store/pipedrive-crm/lib/CalendarService.ts b/packages/app-store/pipedrive-crm/lib/CalendarService.ts new file mode 100644 index 0000000000..a3c80cd0a7 --- /dev/null +++ b/packages/app-store/pipedrive-crm/lib/CalendarService.ts @@ -0,0 +1,313 @@ +import { getLocation } from "@calcom/lib/CalEventParser"; +import logger from "@calcom/lib/logger"; +import type { + Calendar, + CalendarEvent, + EventBusyDate, + IntegrationCalendar, + NewCalendarEventType, + Person, +} from "@calcom/types/Calendar"; +import type { CredentialPayload } from "@calcom/types/Credential"; + +import appConfig from "../config.json"; + +type ContactSearchResult = { + status: string; + results: Array<{ + id: string; + email: string; + firstName: string; + lastName: string; + name: string; + }>; +}; + +type ContactCreateResult = { + status: string; + result: { + id: string; + email: string; + firstName: string; + lastName: string; + name: string; + }; +}; + +export default class PipedriveCalendarService implements Calendar { + private log: typeof logger; + private tenantId: string; + private revertApiKey: string; + private revertApiUrl: string; + constructor(credential: CredentialPayload) { + this.revertApiKey = process.env.REVERT_API_KEY || ""; + this.revertApiUrl = process.env.REVERT_API_URL || "https://api.revert.dev/"; + this.tenantId = String(credential.teamId ? credential.teamId : credential.userId); // Question: Is this a reasonable assumption to be made? Get confirmation on the exact field to be used here. + this.log = logger.getSubLogger({ prefix: [`[[lib] ${appConfig.slug}`] }); + } + + private createContacts = async (attendees: Person[]) => { + const result = attendees.map(async (attendee) => { + const headers = new Headers(); + headers.append("x-revert-api-token", this.revertApiKey); + headers.append("x-revert-t-id", this.tenantId); + headers.append("Content-Type", "application/json"); + + const [firstname, lastname] = !!attendee.name ? attendee.name.split(" ") : [attendee.email, "-"]; + const bodyRaw = JSON.stringify({ + firstName: firstname, + lastName: lastname || "-", + email: attendee.email, + }); + + const requestOptions = { + method: "POST", + headers: headers, + body: bodyRaw, + }; + + try { + const response = await fetch(`${this.revertApiUrl}crm/contacts`, requestOptions); + const result = (await response.json()) as ContactCreateResult; + return result; + } catch (error) { + return Promise.reject(error); + } + }); + return await Promise.all(result); + }; + + private contactSearch = async (event: CalendarEvent) => { + const result = event.attendees.map(async (attendee) => { + const headers = new Headers(); + headers.append("x-revert-api-token", this.revertApiKey); + headers.append("x-revert-t-id", this.tenantId); + headers.append("Content-Type", "application/json"); + + const bodyRaw = JSON.stringify({ searchCriteria: attendee.email }); + + const requestOptions = { + method: "POST", + headers: headers, + body: bodyRaw, + }; + + try { + const response = await fetch(`${this.revertApiUrl}crm/contacts/search`, requestOptions); + const result = (await response.json()) as ContactSearchResult; + return result; + } catch (error) { + return { status: "error", results: [] }; + } + }); + return await Promise.all(result); + }; + + private getMeetingBody = (event: CalendarEvent): string => { + return `${event.organizer.language.translate("invitee_timezone")}: ${ + event.attendees[0].timeZone + }

      ${event.organizer.language.translate("share_additional_notes")}
      ${ + event.additionalNotes || "-" + }`; + }; + + private createPipedriveEvent = async (event: CalendarEvent, contacts: CalendarEvent["attendees"]) => { + const eventPayload = { + subject: event.title, + startDateTime: event.startTime, + endDateTime: event.endTime, + description: this.getMeetingBody(event), + location: getLocation(event), + associations: { + contactId: String(contacts[0].id), + }, + }; + const headers = new Headers(); + headers.append("x-revert-api-token", this.revertApiKey); + headers.append("x-revert-t-id", this.tenantId); + headers.append("Content-Type", "application/json"); + + const eventBody = JSON.stringify(eventPayload); + const requestOptions = { + method: "POST", + headers: headers, + body: eventBody, + }; + + return await fetch(`${this.revertApiUrl}crm/events`, requestOptions); + }; + + private updateMeeting = async (uid: string, event: CalendarEvent) => { + const eventPayload = { + subject: event.title, + startDateTime: event.startTime, + endDateTime: event.endTime, + description: this.getMeetingBody(event), + location: getLocation(event), + }; + const headers = new Headers(); + headers.append("x-revert-api-token", this.revertApiKey); + headers.append("x-revert-t-id", this.tenantId); + headers.append("Content-Type", "application/json"); + + const eventBody = JSON.stringify(eventPayload); + const requestOptions = { + method: "PATCH", + headers: headers, + body: eventBody, + }; + + return await fetch(`${this.revertApiUrl}crm/events/${uid}`, requestOptions); + }; + + private deleteMeeting = async (uid: string) => { + const headers = new Headers(); + headers.append("x-revert-api-token", this.revertApiKey); + headers.append("x-revert-t-id", this.tenantId); + + const requestOptions = { + method: "DELETE", + headers: headers, + }; + + return await fetch(`${this.revertApiUrl}crm/events/${uid}`, requestOptions); + }; + + async handleEventCreation(event: CalendarEvent, contacts: CalendarEvent["attendees"]) { + const meetingEvent = await (await this.createPipedriveEvent(event, contacts)).json(); + if (meetingEvent && meetingEvent.status === "ok") { + this.log.debug("event:creation:ok", { meetingEvent }); + return Promise.resolve({ + uid: meetingEvent.result.id, + id: meetingEvent.result.id, + type: appConfig.slug, + password: "", + url: "", + additionalInfo: { contacts, meetingEvent }, + }); + } + this.log.debug("meeting:creation:notOk", { meetingEvent, event, contacts }); + return Promise.reject("Something went wrong when creating a meeting in PipedriveCRM"); + } + + async createEvent(event: CalendarEvent): Promise { + let contacts = await this.contactSearch(event); + contacts = contacts.filter((c) => c.results.length >= 1); + if (contacts && contacts.length) { + if (contacts.length === event.attendees.length) { + // all contacts are in Pipedrive CRM already. + this.log.debug("contact:search:all", { event, contacts: contacts }); + const existingPeople = contacts.map((c) => { + return { + id: Number(c.results[0].id), + name: `${c.results[0].firstName} ${c.results[0].lastName}`, + email: c.results[0].email, + timeZone: event.attendees[0].timeZone, + language: event.attendees[0].language, + }; + }); + return await this.handleEventCreation(event, existingPeople); + } else { + // Some attendees don't exist in PipedriveCRM + // Get the existing contacts' email to filter out + this.log.debug("contact:search:notAll", { event, contacts }); + const existingContacts = contacts.map((contact) => contact.results[0].email); + this.log.debug("contact:filter:existing", { existingContacts }); + // Get non existing contacts filtering out existing from attendees + const nonExistingContacts: Person[] = event.attendees.filter( + (attendee) => !existingContacts.includes(attendee.email) + ); + this.log.debug("contact:filter:nonExisting", { nonExistingContacts }); + // Only create contacts in PipedriveCRM that were not present in the previous contact search + const createdContacts = await this.createContacts(nonExistingContacts); + this.log.debug("contact:created", { createdContacts }); + // Continue with event creation and association only when all contacts are present in Pipedrive + if (createdContacts[0] && createdContacts[0].status === "ok") { + this.log.debug("contact:creation:ok"); + const existingPeople = contacts.map((c) => { + return { + id: Number(c.results[0].id), + name: c.results[0].name, + email: c.results[0].email, + timeZone: nonExistingContacts[0].timeZone, + language: nonExistingContacts[0].language, + }; + }); + const newlyCreatedPeople = createdContacts.map((c) => { + return { + id: Number(c.result.id), + name: c.result.name, + email: c.result.email, + timeZone: nonExistingContacts[0].timeZone, + language: nonExistingContacts[0].language, + }; + }); + const allContacts = existingPeople.concat(newlyCreatedPeople); + // ensure the order of attendees is maintained. + allContacts.sort((a, b) => { + const indexA = event.attendees.findIndex((c) => c.email === a.email); + const indexB = event.attendees.findIndex((c) => c.email === b.email); + return indexA - indexB; + }); + return await this.handleEventCreation(event, allContacts); + } + return Promise.reject({ + calError: "Something went wrong when creating non-existing attendees in PipedriveCRM", + }); + } + } else { + this.log.debug("contact:search:none", { event, contacts }); + const createdContacts = await this.createContacts(event.attendees); + this.log.debug("contact:created", { createdContacts }); + if (createdContacts[0] && createdContacts[0].status === "ok") { + this.log.debug("contact:creation:ok"); + const newContacts = createdContacts.map((c) => { + return { + id: Number(c.result.id), + name: c.result.name, + email: c.result.email, + timeZone: event.attendees[0].timeZone, + language: event.attendees[0].language, + }; + }); + return await this.handleEventCreation(event, newContacts); + } + } + return Promise.reject({ + calError: "Something went wrong when searching/creating the attendees in PipedriveCRM", + }); + } + + async updateEvent(uid: string, event: CalendarEvent): Promise { + const meetingEvent = await (await this.updateMeeting(uid, event)).json(); + if (meetingEvent && meetingEvent.status === "ok") { + this.log.debug("event:updation:ok", { meetingEvent }); + return Promise.resolve({ + uid: meetingEvent.result.id, + id: meetingEvent.result.id, + type: appConfig.slug, + password: "", + url: "", + additionalInfo: { meetingEvent }, + }); + } + this.log.debug("meeting:updation:notOk", { meetingEvent, event }); + return Promise.reject("Something went wrong when updating a meeting in PipedriveCRM"); + } + + async deleteEvent(uid: string): Promise { + await this.deleteMeeting(uid); + } + + async getAvailability( + _dateFrom: string, + _dateTo: string, + _selectedCalendars: IntegrationCalendar[] + ): Promise { + return Promise.resolve([]); + } + + async listCalendars(_event?: CalendarEvent): Promise { + return Promise.resolve([]); + } +} diff --git a/packages/app-store/pipedrive-crm/lib/index.ts b/packages/app-store/pipedrive-crm/lib/index.ts new file mode 100644 index 0000000000..e168c149df --- /dev/null +++ b/packages/app-store/pipedrive-crm/lib/index.ts @@ -0,0 +1 @@ +export { default as CalendarService } from "./CalendarService"; diff --git a/packages/app-store/pipedrive-crm/package.json b/packages/app-store/pipedrive-crm/package.json new file mode 100644 index 0000000000..5d8130acf0 --- /dev/null +++ b/packages/app-store/pipedrive-crm/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/pipedrive-crm", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Founded in 2010, Pipedrive is an easy and effective sales CRM that drives small business growth.\r\rToday, Pipedrive is used by revenue teams at more than 100,000 companies worldwide. Pipedrive is headquartered in New York and has offices across Europe and the US.\r\rThe company is backed by majority holder Vista Equity Partners, Bessemer Venture Partners, Insight Partners, Atomico, and DTCP.\r\rLearn more at www.pipedrive.com." +} diff --git a/packages/app-store/pipedrive-crm/static/icon.svg b/packages/app-store/pipedrive-crm/static/icon.svg new file mode 100644 index 0000000000..689c4cd337 --- /dev/null +++ b/packages/app-store/pipedrive-crm/static/icon.svg @@ -0,0 +1,23 @@ + + + + Pipedrive_letter_logo_light@1,5x + + + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app-store/pipedrive-crm/static/pipedrive-banner.jpeg b/packages/app-store/pipedrive-crm/static/pipedrive-banner.jpeg new file mode 100644 index 0000000000..0d07a67795 Binary files /dev/null and b/packages/app-store/pipedrive-crm/static/pipedrive-banner.jpeg differ diff --git a/packages/app-store/pipedrive-crm/zod.ts b/packages/app-store/pipedrive-crm/zod.ts new file mode 100644 index 0000000000..fe378bc4bc --- /dev/null +++ b/packages/app-store/pipedrive-crm/zod.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const appKeysSchema = z.object({ + client_id: z.string().min(1), + client_secret: z.string().min(1), +}); + +export const appDataSchema = z.object({}); diff --git a/packages/app-store/roam/DESCRIPTION.md b/packages/app-store/roam/DESCRIPTION.md new file mode 100644 index 0000000000..ac49de471a --- /dev/null +++ b/packages/app-store/roam/DESCRIPTION.md @@ -0,0 +1,16 @@ +--- +items: + - 1.jpg + - 2.jpg + - 3.jpg + +--- + +{DESCRIPTION} + +Roam makes companies: +- more productive with shorter meetings +- more connected with a map that gives a feeling of working together without meeting +- & Roam saves companies money with our all-in-one bundle + +When the whole company is in one HQ, productivity is high, people feel connected to the company. Calendars are emptied. Culture comes back. And companies using Roam benefit from our all-in-one approach to cut tools and save money. \ No newline at end of file diff --git a/packages/app-store/roam/api/add.ts b/packages/app-store/roam/api/add.ts new file mode 100644 index 0000000000..6ab3106577 --- /dev/null +++ b/packages/app-store/roam/api/add.ts @@ -0,0 +1,16 @@ +import { createDefaultInstallation } from "@calcom/app-store/_utils/installation"; +import type { AppDeclarativeHandler } from "@calcom/types/AppHandler"; + +import appConfig from "../config.json"; + +const handler: AppDeclarativeHandler = { + appType: appConfig.type, + variant: appConfig.variant, + slug: appConfig.slug, + supportsMultipleInstalls: false, + handlerType: "add", + createCredential: ({ appType, user, slug, teamId }) => + createDefaultInstallation({ appType, userId: user.id, slug, key: {}, teamId }), +}; + +export default handler; diff --git a/packages/app-store/roam/api/index.ts b/packages/app-store/roam/api/index.ts new file mode 100644 index 0000000000..4c0d2ead01 --- /dev/null +++ b/packages/app-store/roam/api/index.ts @@ -0,0 +1 @@ +export { default as add } from "./add"; diff --git a/packages/app-store/roam/config.json b/packages/app-store/roam/config.json new file mode 100644 index 0000000000..b73d81b6b5 --- /dev/null +++ b/packages/app-store/roam/config.json @@ -0,0 +1,25 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Roam", + "slug": "roam", + "type": "roam_conferencing", + "logo": "icon.png", + "url": "https://ro.am", + "variant": "conferencing", + "categories": ["conferencing"], + "publisher": "Roam HQ, Inc.", + "email": "support@ro.am", + "appData": { + "location": { + "type": "integrations:{SLUG}_video", + "label": "{TITLE}", + "linkType": "static", + "organizerInputPlaceholder": "https://ro.am/r/#/p/yHwFBQrRTMuptqKYo_wu8A/huzRiHnR-np4RGYKV-c0pQ", + "urlRegExp": "^http(s)?:\\/\\/(www\\.)?ro.am\\/[a-zA-Z0-9]*" + } + }, + "description": "Roam is Your Whole Company in one HQ\r", + "isTemplate": false, + "__createdUsingCli": true, + "__template": "event-type-location-video-static" +} diff --git a/packages/app-store/roam/index.ts b/packages/app-store/roam/index.ts new file mode 100644 index 0000000000..d7f3602204 --- /dev/null +++ b/packages/app-store/roam/index.ts @@ -0,0 +1 @@ +export * as api from "./api"; diff --git a/packages/app-store/roam/package.json b/packages/app-store/roam/package.json new file mode 100644 index 0000000000..50b2309cec --- /dev/null +++ b/packages/app-store/roam/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/roam", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Roam is Your Whole Company in one HQ" +} diff --git a/packages/app-store/roam/static/1.jpg b/packages/app-store/roam/static/1.jpg new file mode 100644 index 0000000000..434e247032 Binary files /dev/null and b/packages/app-store/roam/static/1.jpg differ diff --git a/packages/app-store/roam/static/2.jpg b/packages/app-store/roam/static/2.jpg new file mode 100644 index 0000000000..4d4479bbff Binary files /dev/null and b/packages/app-store/roam/static/2.jpg differ diff --git a/packages/app-store/roam/static/3.jpg b/packages/app-store/roam/static/3.jpg new file mode 100644 index 0000000000..3577d971c7 Binary files /dev/null and b/packages/app-store/roam/static/3.jpg differ diff --git a/packages/app-store/roam/static/icon-dark.svg b/packages/app-store/roam/static/icon-dark.svg new file mode 100644 index 0000000000..37bc95ca68 --- /dev/null +++ b/packages/app-store/roam/static/icon-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/app-store/roam/static/icon.png b/packages/app-store/roam/static/icon.png new file mode 100644 index 0000000000..ec6b72acd3 Binary files /dev/null and b/packages/app-store/roam/static/icon.png differ diff --git a/packages/app-store/routing-forms/components/FormActions.tsx b/packages/app-store/routing-forms/components/FormActions.tsx index 088d374231..337b8c30d3 100644 --- a/packages/app-store/routing-forms/components/FormActions.tsx +++ b/packages/app-store/routing-forms/components/FormActions.tsx @@ -12,6 +12,7 @@ import { CAL_URL } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; +import slugify from "@calcom/lib/slugify"; import { trpc } from "@calcom/trpc/react"; import type { ButtonProps } from "@calcom/ui"; import { @@ -434,6 +435,8 @@ export const FormAction = forwardRef(function FormAction { + protected async getNodeMailerPayload(): Promise> { const toAddresses = this.toAddresses; const subject = `${this.form.name} has a new response`; return { from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), subject, - html: renderEmail("ResponseEmail", { + html: await renderEmail("ResponseEmail", { form: this.form, orderedResponses: this.orderedResponses, subject, diff --git a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx index 07b43aa06d..fbc2a0cc91 100644 --- a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx @@ -92,7 +92,9 @@ const Route = ({ }) => { const index = routes.indexOf(route); - const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery(); + const { data: eventTypesByGroup } = trpc.viewer.eventTypes.getByViewer.useQuery({ + forRoutingForms: true, + }); const eventOptions: { label: string; value: string }[] = []; eventTypesByGroup?.eventTypeGroups.forEach((group) => { @@ -551,11 +553,18 @@ export default function RouteBuilder({ ( -
      - -
      - )} + Page={({ hookForm, form }) => { + // If hookForm hasn't been initialized, don't render anything + // This is important here because some states get initialized which aren't reset when the hookForm is reset with the form values and they don't get the updated values + if (!hookForm.getValues().id) { + return null; + } + return ( +
      + +
      + ); + }} /> ); } diff --git a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts index f09d5f8345..7ee5e15737 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; import type { Fixtures } from "@calcom/web/playwright/lib/fixtures"; import { test } from "@calcom/web/playwright/lib/fixtures"; -import { gotoRoutingLink } from "@calcom/web/playwright/lib/testUtils"; +import { NotFoundPageText, gotoRoutingLink } from "@calcom/web/playwright/lib/testUtils"; import { addForm, @@ -36,7 +36,7 @@ test.describe("Routing Forms", () => { await page.goto(`apps/routing-forms/route-builder/${formId}`); await disableForm(page); await gotoRoutingLink({ page, formId }); - await expect(page.locator("text=This page could not be found")).toBeVisible(); + await expect(page.locator(`text=${NotFoundPageText}`)).toBeVisible(); }); test("should be able to edit the form", async ({ page }) => { diff --git a/packages/app-store/routing-forms/trpc/forms.schema.ts b/packages/app-store/routing-forms/trpc/forms.schema.ts index 787d6e2be7..343097874f 100644 --- a/packages/app-store/routing-forms/trpc/forms.schema.ts +++ b/packages/app-store/routing-forms/trpc/forms.schema.ts @@ -1,3 +1,5 @@ +"use client"; + import { z } from "zod"; import { filterQuerySchemaStrict } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery"; diff --git a/packages/app-store/stripepayment/components/EventTypeAppCardInterface.tsx b/packages/app-store/stripepayment/components/EventTypeAppCardInterface.tsx index 04498af048..886f626913 100644 --- a/packages/app-store/stripepayment/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/stripepayment/components/EventTypeAppCardInterface.tsx @@ -8,6 +8,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Alert, Select, TextField } from "@calcom/ui"; +import checkForMultiplePaymentApps from "../../_utils/payments/checkForMultiplePaymentApps"; import { paymentOptions } from "../lib/constants"; import { convertToSmallestCurrencyUnit, @@ -18,7 +19,11 @@ import type { appDataSchema } from "../zod"; type Option = { value: string; label: string }; -const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) { +const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ + app, + eventType, + eventTypeFormMetadata, +}) { const pathname = usePathname(); const { getAppData, setAppData, disabled } = useAppContextWithSchema(); const price = getAppData("price"); @@ -32,6 +37,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ const paymentOption = getAppData("paymentOption"); const paymentOptionSelectValue = paymentOptions.find((option) => paymentOption === option.value); const [requirePayment, setRequirePayment] = useState(getAppData("enabled")); + const otherPaymentAppEnabled = checkForMultiplePaymentApps(eventTypeFormMetadata); + + const shouldDisableSwitch = !requirePayment && otherPaymentAppEnabled; const { t } = useLocale(); const recurringEventDefined = eventType.recurringEvent?.count !== undefined; @@ -66,7 +74,9 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ switchOnClick={(enabled) => { setRequirePayment(enabled); }} - teamId={eventType.team?.id || undefined}> + teamId={eventType.team?.id || undefined} + disableSwitch={shouldDisableSwitch} + switchTooltip={shouldDisableSwitch ? t("other_payment_app_enabled") : undefined}> <> {recurringEventDefined && ( @@ -75,7 +85,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ <>
      ; }; export type EventTypeAppCardComponent = React.FC; diff --git a/packages/atoms/booker/Booker.docs.mdx b/packages/atoms/booker/Booker.docs.mdx new file mode 100644 index 0000000000..f92c09c954 --- /dev/null +++ b/packages/atoms/booker/Booker.docs.mdx @@ -0,0 +1,12 @@ +import { Meta, Canvas, ArgsTable } from "@storybook/blocks"; +import { Title } from "@calcom/storybook/components"; +import { Booker } from "./Booker"; +import * as BookerStories from "./Booker.stories"; + + + + + +<ArgsTable of={Booker} /> + +<Canvas of={BookerStories.Default}/> diff --git a/packages/atoms/booker/Booker.stories.tsx b/packages/atoms/booker/Booker.stories.tsx new file mode 100644 index 0000000000..ab485daa3f --- /dev/null +++ b/packages/atoms/booker/Booker.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { Booker } from "./Booker"; + +const meta: Meta<typeof Booker> = { + component: Booker, + title: "Atoms/Booker", +}; + +export default meta; +type Story = StoryObj<typeof Booker>; + +export const Default: Story = { + name: "Booker", + render: () => <Booker username="pro" eventSlug="" entity={{}} />, +}; diff --git a/packages/atoms/booker/booker.stories.mdx b/packages/atoms/booker/booker.stories.mdx deleted file mode 100644 index 1a9021ec8e..0000000000 --- a/packages/atoms/booker/booker.stories.mdx +++ /dev/null @@ -1,16 +0,0 @@ -import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; - -import { Title } from "@calcom/storybook/components"; -import { Icon } from "@calcom/ui"; - -import { Booker } from "./Booker"; - -<Meta title="Atoms/Booker" component={Booker} /> - -<Title title="Booker" /> - -<Canvas> - <Story name="Booker"> - <Booker username="pro" /> - </Story> -</Canvas> diff --git a/packages/config/package.json b/packages/config/package.json index 14d083319c..3001d202e7 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -11,6 +11,7 @@ ], "dependencies": { "@calcom/eslint-plugin-eslint": "*", + "@todesktop/tailwind-variants": "^1.0.0", "eslint-config-next": "^13.2.1", "eslint-config-prettier": "^8.6.0", "eslint-config-turbo": "^0.0.7", diff --git a/packages/config/tailwind-preset.js b/packages/config/tailwind-preset.js index 90fcaae250..b72727f7f9 100644 --- a/packages/config/tailwind-preset.js +++ b/packages/config/tailwind-preset.js @@ -104,6 +104,7 @@ module.exports = { }, animation: { "fade-in-up": "fade-in-up 600ms var(--animation-delay, 0ms) cubic-bezier(.21,1.02,.73,1) forwards", + "fade-in-bottom": "fade-in-bottom cubic-bezier(.21,1.02,.73,1) forwards", spinning: "spinning 0.75s linear infinite", }, boxShadow: { @@ -149,6 +150,7 @@ module.exports = { }, }, plugins: [ + require("@todesktop/tailwind-variants"), require("@tailwindcss/forms"), require("@tailwindcss/typography"), require("tailwind-scrollbar")({ nocompatible: true }), diff --git a/packages/core/EventManager.ts b/packages/core/EventManager.ts index 4aa5e30beb..673087d954 100644 --- a/packages/core/EventManager.ts +++ b/packages/core/EventManager.ts @@ -179,7 +179,7 @@ export default class EventManager { } const isCalendarType = isCalendarResult(result); if (isCalendarType) { - evt.iCalUID = result.iCalUID || undefined; + evt.iCalUID = result.iCalUID || event.iCalUID || undefined; } return { @@ -574,24 +574,25 @@ export default class EventManager { (c) => c.type === destination.integration ); // It might not be the first connected calendar as it seems that the order is not guaranteed to be ascending of credentialId. - const firstCalendarCredential = destinationCalendarCredentials[0]; + const firstCalendarCredential = destinationCalendarCredentials[0] as + | (typeof destinationCalendarCredentials)[number] + | undefined; if (!firstCalendarCredential) { log.warn( "No other credentials found of the same type as the destination calendar. Falling back to first connected calendar" ); await fallbackToFirstConnectedCalendar(); + } else { + log.warn( + "No credentialId found for destination calendar, falling back to first found calendar of same type as destination calendar", + safeStringify({ + destination: getPiiFreeDestinationCalendar(destination), + firstConnectedCalendar: getPiiFreeCredential(firstCalendarCredential), + }) + ); + createdEvents.push(await createEvent(firstCalendarCredential, event)); } - - log.warn( - "No credentialId found for destination calendar, falling back to first found calendar", - safeStringify({ - destination: getPiiFreeDestinationCalendar(destination), - firstConnectedCalendar: getPiiFreeCredential(firstCalendarCredential), - }) - ); - - createdEvents.push(await createEvent(firstCalendarCredential, event)); } } } else { diff --git a/packages/core/builders/CalendarEvent/class.ts b/packages/core/builders/CalendarEvent/class.ts index 8a449dbfd2..5f2e16bad3 100644 --- a/packages/core/builders/CalendarEvent/class.ts +++ b/packages/core/builders/CalendarEvent/class.ts @@ -30,6 +30,7 @@ class CalendarEventClass implements CalendarEvent { hideCalendarNotes?: boolean; additionalNotes?: string | null | undefined; recurrence?: string; + iCalUID?: string | null; constructor(initProps?: CalendarEvent) { // If more parameters are given we update this diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index 80dc92d609..4323419134 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -7,7 +7,7 @@ import logger from "@calcom/lib/logger"; import { getPiiFreeBooking } from "@calcom/lib/piiFreeData"; import { performance } from "@calcom/lib/server/perfObserver"; import prisma from "@calcom/prisma"; -import type { SelectedCalendar } from "@calcom/prisma/client"; +import type { Prisma, SelectedCalendar } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; import type { EventBusyDetails } from "@calcom/types/Calendar"; import type { CredentialPayload } from "@calcom/types/Credential"; @@ -83,9 +83,10 @@ export async function getBusyTimes(params: { const endTimeDate = rescheduleUid && duration ? dayjs(endTime).add(duration, "minute").toDate() : new Date(endTime); + // startTime is less than endTimeDate and endTime grater than startTimeDate const sharedQuery = { - startTime: { gte: startTimeDate }, - endTime: { lte: endTimeDate }, + startTime: { lte: endTimeDate }, + endTime: { gte: startTimeDate }, status: { in: [BookingStatus.ACCEPTED], }, @@ -264,8 +265,9 @@ export async function getBusyTimesForLimitChecks(params: { eventTypeId: number; startDate: Date; endDate: Date; + rescheduleUid?: string | null; }) { - const { userId, eventTypeId, startDate, endDate } = params; + const { userId, eventTypeId, startDate, endDate, rescheduleUid } = params; logger.silly( `Fetch limit checks bookings in range ${startDate} to ${endDate} for input ${JSON.stringify({ userId, @@ -275,19 +277,27 @@ export async function getBusyTimesForLimitChecks(params: { ); performance.mark("getBusyTimesForLimitChecksStart"); - const bookings = await prisma.booking.findMany({ - where: { - userId, - eventTypeId, - status: BookingStatus.ACCEPTED, - // FIXME: bookings that overlap on one side will never be counted - startTime: { - gte: startDate, - }, - endTime: { - lte: endDate, - }, + const where: Prisma.BookingWhereInput = { + userId, + eventTypeId, + status: BookingStatus.ACCEPTED, + // FIXME: bookings that overlap on one side will never be counted + startTime: { + gte: startDate, }, + endTime: { + lte: endDate, + }, + }; + + if (rescheduleUid) { + where.NOT = { + uid: rescheduleUid, + }; + } + + const bookings = await prisma.booking.findMany({ + where, select: { id: true, startTime: true, diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 3c4f77497b..4f33347792 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -25,6 +25,7 @@ import type { } from "@calcom/types/Calendar"; import { getBusyTimes, getBusyTimesForLimitChecks } from "./getBusyTimes"; +import monitorCallbackAsync, { monitorCallbackSync } from "./sentryWrapper"; const log = logger.getSubLogger({ prefix: ["getUserAvailability"] }); const availabilitySchema = z @@ -41,7 +42,13 @@ const availabilitySchema = z }) .refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in."); -const getEventType = async (id: number) => { +const getEventType = async ( + ...args: Parameters<typeof _getEventType> +): Promise<ReturnType<typeof _getEventType>> => { + return monitorCallbackAsync(_getEventType, ...args); +}; + +const _getEventType = async (id: number) => { const eventType = await prisma.eventType.findUnique({ where: { id }, select: { @@ -86,7 +93,11 @@ const getEventType = async (id: number) => { type EventType = Awaited<ReturnType<typeof getEventType>>; -const getUser = (where: Prisma.UserWhereInput) => +const getUser = (...args: Parameters<typeof _getUser>): ReturnType<typeof _getUser> => { + return monitorCallbackSync(_getUser, ...args); +}; + +const _getUser = (where: Prisma.UserWhereInput) => prisma.user.findFirst({ where, select: { @@ -99,7 +110,13 @@ const getUser = (where: Prisma.UserWhereInput) => type User = Awaited<ReturnType<typeof getUser>>; -export const getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) => +export const getCurrentSeats = ( + ...args: Parameters<typeof _getCurrentSeats> +): ReturnType<typeof _getCurrentSeats> => { + return monitorCallbackSync(_getCurrentSeats, ...args); +}; + +const _getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) => prisma.booking.findMany({ where: { eventTypeId, @@ -122,8 +139,14 @@ export const getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Da export type CurrentSeats = Awaited<ReturnType<typeof getCurrentSeats>>; +export const getUserAvailability = async ( + ...args: Parameters<typeof _getUserAvailability> +): Promise<ReturnType<typeof _getUserAvailability>> => { + return monitorCallbackAsync(_getUserAvailability, ...args); +}; + /** This should be called getUsersWorkingHoursAndBusySlots (...and remaining seats, and final timezone) */ -export const getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseAndEverythingElse( +const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseAndEverythingElse( query: { withSource?: boolean; username?: string; @@ -192,7 +215,8 @@ export const getUserAvailability = async function getUsersWorkingHoursLifeTheUni dateTo, duration, eventType, - user.id + user.id, + initialData?.rescheduleUid ) : []; @@ -305,7 +329,13 @@ export const getUserAvailability = async function getUsersWorkingHoursLifeTheUni }; }; -const getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit) => { +const getPeriodStartDatesBetween = ( + ...args: Parameters<typeof _getPeriodStartDatesBetween> +): ReturnType<typeof _getPeriodStartDatesBetween> => { + return monitorCallbackSync(_getPeriodStartDatesBetween, ...args); +}; + +const _getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit) => { const dates = []; let startDate = dayjs(dateFrom).startOf(period); const endDate = dayjs(dateTo).endOf(period); @@ -378,13 +408,20 @@ class LimitManager { } const getBusyTimesFromLimits = async ( + ...args: Parameters<typeof _getBusyTimesFromLimits> +): Promise<ReturnType<typeof _getBusyTimesFromLimits>> => { + return monitorCallbackAsync(_getBusyTimesFromLimits, ...args); +}; + +const _getBusyTimesFromLimits = async ( bookingLimits: IntervalLimit | null, durationLimits: IntervalLimit | null, dateFrom: Dayjs, dateTo: Dayjs, duration: number | undefined, eventType: NonNullable<EventType>, - userId: number + userId: number, + rescheduleUid?: string | null ) => { performance.mark("limitsStart"); @@ -410,6 +447,7 @@ const getBusyTimesFromLimits = async ( eventTypeId: eventType.id, startDate: limitDateFrom.toDate(), endDate: limitDateTo.toDate(), + rescheduleUid: rescheduleUid, }); // run this first, as counting bookings should always run faster.. @@ -450,6 +488,12 @@ const getBusyTimesFromLimits = async ( }; const getBusyTimesFromBookingLimits = async ( + ...args: Parameters<typeof _getBusyTimesFromBookingLimits> +): Promise<ReturnType<typeof _getBusyTimesFromBookingLimits>> => { + return monitorCallbackAsync(_getBusyTimesFromBookingLimits, ...args); +}; + +const _getBusyTimesFromBookingLimits = async ( bookings: EventBusyDetails[], bookingLimits: IntervalLimit, dateFrom: Dayjs, @@ -504,6 +548,12 @@ const getBusyTimesFromBookingLimits = async ( }; const getBusyTimesFromDurationLimits = async ( + ...args: Parameters<typeof _getBusyTimesFromDurationLimits> +): Promise<ReturnType<typeof _getBusyTimesFromDurationLimits>> => { + return monitorCallbackAsync(_getBusyTimesFromDurationLimits, ...args); +}; + +const _getBusyTimesFromDurationLimits = async ( bookings: EventBusyDetails[], durationLimits: IntervalLimit, dateFrom: Dayjs, diff --git a/packages/core/sentryWrapper.ts b/packages/core/sentryWrapper.ts new file mode 100644 index 0000000000..5d2ae4b384 --- /dev/null +++ b/packages/core/sentryWrapper.ts @@ -0,0 +1,86 @@ +import * as Sentry from "@sentry/nextjs"; +import type { Span, Transaction } from "@sentry/types"; + +/* +WHEN TO USE +We ran a script that performs a simple mathematical calculation within a loop of 1000000 iterations. +Our results were: Plain execution time: 441, Monitored execution time: 8094. +This suggests that using these wrappers within large loops can incur significant overhead and is thus not recommended. + +For smaller loops, the cost incurred may not be very significant on an absolute scale +considering that a million monitored iterations only took roughly 8 seconds when monitored. +*/ + +const setUpMonitoring = (name: string) => { + // Attempt to retrieve the current transaction from Sentry's scope + let transaction = Sentry.getCurrentHub().getScope()?.getTransaction(); + + // Check if there's an existing transaction, if not, start a new one + if (!transaction) { + transaction = Sentry.startTransaction({ + op: name, + name: name, + }); + } + + // Start a new span in the current transaction + const span = transaction.startChild({ + op: name, + description: `Executing ${name}`, + }); + return [transaction, span]; +}; + +// transaction will always be Transaction, since returned in a list with Span type must be listed as either or here +const finishMonitoring = (transaction: Transaction | Span, span: Span) => { + // Attempt to retrieve the current transaction from Sentry's scope + span.finish(); + + // If this was a new transaction, finish it + if (!Sentry.getCurrentHub().getScope()?.getTransaction()) { + transaction.finish(); + } +}; + +const monitorCallbackAsync = async <T extends (...args: any[]) => any>( + cb: T, + ...args: Parameters<T> +): Promise<ReturnType<T>> => { + // Check if Sentry set + if (!process.env.NEXT_PUBLIC_SENTRY_DSN) return (await cb(...args)) as ReturnType<T>; + + const [transaction, span] = setUpMonitoring(cb.name); + + try { + const result = await cb(...args); + return result as ReturnType<T>; + } catch (error) { + Sentry.captureException(error); + throw error; + } finally { + finishMonitoring(transaction, span); + } +}; + +const monitorCallbackSync = <T extends (...args: any[]) => any>( + cb: T, + ...args: Parameters<T> +): ReturnType<T> => { + // Check if Sentry set + if (!process.env.NEXT_PUBLIC_SENTRY_DSN) return cb(...args) as ReturnType<T>; + + const [transaction, span] = setUpMonitoring(cb.name); + + try { + const result = cb(...args); + return result as ReturnType<T>; + } catch (error) { + Sentry.captureException(error); + throw error; + } finally { + finishMonitoring(transaction, span); + } +}; + +export default monitorCallbackAsync; +export { monitorCallbackSync }; diff --git a/packages/core/videoClient.ts b/packages/core/videoClient.ts index 7bb3118949..854bf4a996 100644 --- a/packages/core/videoClient.ts +++ b/packages/core/videoClient.ts @@ -204,6 +204,28 @@ const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => { return videoAdapter?.createMeeting(calEvent); }; +export const createInstantMeetingWithCalVideo = async (endTime: string) => { + let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>; + try { + dailyAppKeys = await getDailyAppKeys(); + } catch (e) { + return; + } + const [videoAdapter] = await getVideoAdapters([ + { + id: 0, + appId: "daily-video", + type: "daily_video", + userId: null, + user: { email: "" }, + teamId: null, + key: dailyAppKeys, + invalid: false, + }, + ]); + return videoAdapter?.createInstantCalVideoRoom?.(endTime); +}; + const getRecordingsOfCalVideoByRoomName = async ( roomName: string ): Promise<GetRecordingsResponseSchema | undefined> => { diff --git a/packages/emails/README.md b/packages/emails/README.md index 520b7e1ffd..bd5439926a 100644 --- a/packages/emails/README.md +++ b/packages/emails/README.md @@ -8,7 +8,7 @@ ```ts import { renderEmail } from "@calcom/emails"; -renderEmail("TeamInviteEmail", */{ +await renderEmail("TeamInviteEmail", { language: t, from: "teampro@example.com", to: "pro@example.com", diff --git a/packages/emails/lib/generateIcsString.ts b/packages/emails/lib/generateIcsString.ts new file mode 100644 index 0000000000..c70b8ba818 --- /dev/null +++ b/packages/emails/lib/generateIcsString.ts @@ -0,0 +1,105 @@ +import type { DateArray, ParticipationStatus, ParticipationRole, EventStatus } from "ics"; +import { createEvent } from "ics"; +import type { TFunction } from "next-i18next"; +import { RRule } from "rrule"; + +import dayjs from "@calcom/dayjs"; +import { getRichDescription } from "@calcom/lib/CalEventParser"; +import { getWhen } from "@calcom/lib/CalEventParser"; +import type { CalendarEvent, Person } from "@calcom/types/Calendar"; + +export enum BookingAction { + Create = "create", + Cancel = "cancel", + Reschedule = "reschedule", + RequestReschedule = "request_reschedule", + LocationChange = "location_change", +} + +const generateIcsString = ({ + event, + title, + subtitle, + status, + role, + isRequestReschedule, + t, +}: { + event: CalendarEvent; + title: string; + subtitle: string; + status: EventStatus; + role: "attendee" | "organizer"; + isRequestReschedule?: boolean; + t?: TFunction; +}) => { + // Taking care of recurrence rule + let recurrenceRule: string | undefined = undefined; + const partstat: ParticipationStatus = "ACCEPTED"; + const icsRole: ParticipationRole = "REQ-PARTICIPANT"; + if (event.recurringEvent?.count) { + // ics appends "RRULE:" already, so removing it from RRule generated string + recurrenceRule = new RRule(event.recurringEvent).toString().replace("RRULE:", ""); + } + + const getTextBody = (title: string, subtitle: string): string => { + let body: string; + if (isRequestReschedule && role === "attendee" && t) { + body = ` + ${title} + ${getWhen(event, t)} + ${subtitle}`; + } + body = ` + ${title} + ${subtitle} + + ${getRichDescription(event, t)} + `.trim(); + + return body; + }; + + const icsEvent = createEvent({ + uid: event.iCalUID || event.uid!, + sequence: event.iCalSequence || 0, + start: dayjs(event.startTime) + .utc() + .toArray() + .slice(0, 6) + .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, + startInputType: "utc", + productId: "calcom/ics", + title: event.title, + description: getTextBody(title, subtitle), + duration: { minutes: dayjs(event.endTime).diff(dayjs(event.startTime), "minute") }, + organizer: { name: event.organizer.name, email: event.organizer.email }, + ...{ recurrenceRule }, + attendees: [ + ...event.attendees.map((attendee: Person) => ({ + name: attendee.name, + email: attendee.email, + partstat, + role: icsRole, + rsvp: true, + })), + ...(event.team?.members + ? event.team?.members.map((member: Person) => ({ + name: member.name, + email: member.email, + partstat, + role: icsRole, + rsvp: true, + })) + : []), + ], + method: "REQUEST", + status, + }); + if (icsEvent.error) { + throw icsEvent.error; + } + return icsEvent.value; +}; + +export default generateIcsString; diff --git a/packages/emails/lib/getICalUID.ts b/packages/emails/lib/getICalUID.ts new file mode 100644 index 0000000000..1dc7e7216d --- /dev/null +++ b/packages/emails/lib/getICalUID.ts @@ -0,0 +1,36 @@ +import short from "short-uuid"; +import { v5 as uuidv5 } from "uuid"; + +import { APP_NAME } from "@calcom/lib/constants"; + +/** + * This function returns the iCalUID if a uid is passed or if it is present in the event that is passed + * @param uid - the uid of the event + * @param event - an event that already has an iCalUID or one that has a uid + * @param defaultToEventUid - if true, will default to the event.uid if present + * + * @returns the iCalUID whether already present or generated + */ +const getICalUID = ({ + uid, + event, + defaultToEventUid, +}: { + uid?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + event?: { iCalUID?: string | null; uid?: string | null; [key: string]: any }; + defaultToEventUid?: boolean; +}) => { + if (event?.iCalUID) return event.iCalUID; + + if (defaultToEventUid && event?.uid) return `${event.uid}@${APP_NAME}`; + + if (uid) return `${uid}@${APP_NAME}`; + + const translator = short(); + + uid = translator.fromUUID(uuidv5(APP_NAME, uuidv5.URL)); + return `${uid}@${APP_NAME}`; +}; + +export default getICalUID; diff --git a/packages/emails/lib/test/generateIcsString.test.ts b/packages/emails/lib/test/generateIcsString.test.ts new file mode 100644 index 0000000000..12f18956d1 --- /dev/null +++ b/packages/emails/lib/test/generateIcsString.test.ts @@ -0,0 +1,137 @@ +import { describe, expect } from "vitest"; + +import dayjs from "@calcom/dayjs"; +import type { CalendarEvent } from "@calcom/types/Calendar"; +import { test } from "@calcom/web/test/fixtures/fixtures"; + +import { buildCalendarEvent, buildPerson } from "../../../lib/test/builder"; +import generateIcsString from "../generateIcsString"; + +const assertHasIcsString = (icsString: string | undefined) => { + if (!icsString) throw new Error("icsString is undefined"); + + expect(icsString).toBeDefined(); + + return icsString; +}; + +const testIcsStringContains = ({ + icsString, + event, + status, +}: { + icsString: string; + event: CalendarEvent; + status: string; +}) => { + const DTSTART = event.startTime.split(".")[0].replace(/[-:]/g, ""); + const startTime = dayjs(event.startTime); + const endTime = dayjs(event.endTime); + const duration = endTime.diff(startTime, "minute"); + + expect(icsString).toEqual(expect.stringContaining(`UID:${event.iCalUID}`)); + // Sometimes the deeply equal stringMatching error appears. Don't want to add flakey tests + // expect(icsString).toEqual(expect.stringContaining(`SUMMARY:${event.title}`)); + expect(icsString).toEqual(expect.stringContaining(`DTSTART:${DTSTART}`)); + expect(icsString).toEqual( + expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) + ); + expect(icsString).toEqual(expect.stringContaining(`DURATION:PT${duration}M`)); + expect(icsString).toEqual(expect.stringContaining(`STATUS:${status}`)); + // Getting an error expected icsString to deeply equal stringMatching + // for (const attendee of event.attendees) { + // expect(icsString).toEqual( + // expect.stringMatching( + // `RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=${attendee.name}:mailto:${attendee.email}` + // ) + // ); + // } +}; + +describe("generateIcsString", () => { + test("when bookingAction is Create", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + }); + + const title = "new_event_scheduled_recurring"; + const subtitle = "emailed_you_and_any_other_attendees"; + const status = "CONFIRMED"; + + const icsString = generateIcsString({ + event: event, + title, + subtitle, + role: "organizer", + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + testIcsStringContains({ icsString: assertedIcsString, event, status }); + }); + test("when bookingAction is Cancel", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + }); + const title = "event_request_cancelled"; + const subtitle = "emailed_you_and_any_other_attendees"; + const status = "CANCELLED"; + + const icsString = generateIcsString({ + event: event, + title, + subtitle, + role: "organizer", + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + testIcsStringContains({ icsString: assertedIcsString, event, status }); + }); + test("when bookingAction is Reschedule", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + }); + const title = "event_type_has_been_rescheduled"; + const subtitle = "emailed_you_and_any_other_attendees"; + const status = "CONFIRMED"; + + const icsString = generateIcsString({ + event: event, + title, + subtitle, + role: "organizer", + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + testIcsStringContains({ icsString: assertedIcsString, event, status }); + }); + test("when bookingAction is RequestReschedule", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + }); + const title = "request_reschedule_title_organizer"; + const subtitle = "request_reschedule_subtitle_organizer"; + const status = "CANCELLED"; + + const icsString = generateIcsString({ + event: event, + title, + subtitle, + role: "organizer", + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + testIcsStringContains({ icsString: assertedIcsString, event, status }); + }); +}); diff --git a/packages/emails/lib/test/getICalUID.test.ts b/packages/emails/lib/test/getICalUID.test.ts new file mode 100644 index 0000000000..fe66988728 --- /dev/null +++ b/packages/emails/lib/test/getICalUID.test.ts @@ -0,0 +1,29 @@ +import { describe, expect } from "vitest"; + +import { APP_NAME } from "@calcom/lib/constants"; +import { buildCalendarEvent } from "@calcom/lib/test/builder"; +import { test } from "@calcom/web/test/fixtures/fixtures"; + +import getICalUID from "../getICalUID"; + +describe("getICalUid", () => { + test("returns iCalUID when passing a uid", () => { + const iCalUID = getICalUID({ uid: "123" }); + expect(iCalUID).toEqual(`123@${APP_NAME}`); + }); + test("returns iCalUID when passing an event", () => { + const event = buildCalendarEvent({ iCalUID: `123@${APP_NAME}` }); + const iCalUID = getICalUID({ event }); + expect(iCalUID).toEqual(`123@${APP_NAME}`); + }); + test("returns new iCalUID when passing in an event with no iCalUID but has an uid", () => { + const event = buildCalendarEvent({ iCalUID: "" }); + const iCalUID = getICalUID({ event, defaultToEventUid: true }); + expect(iCalUID).toEqual(`${event.uid}@${APP_NAME}`); + }); + test("returns new iCalUID when passing in an event with no iCalUID and uses uid passed", () => { + const event = buildCalendarEvent({ iCalUID: "" }); + const iCalUID = getICalUID({ event, uid: "123" }); + expect(iCalUID).toEqual(`123@${APP_NAME}`); + }); +}); diff --git a/packages/emails/src/components/UserFieldsResponses.tsx b/packages/emails/src/components/UserFieldsResponses.tsx index 4b05b0759a..6e89c87453 100644 --- a/packages/emails/src/components/UserFieldsResponses.tsx +++ b/packages/emails/src/components/UserFieldsResponses.tsx @@ -1,9 +1,12 @@ +import type { TFunction } from "next-i18next"; + import getLabelValueMapFromResponses from "@calcom/lib/getLabelValueMapFromResponses"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { Info } from "./Info"; -export function UserFieldsResponses(props: { calEvent: CalendarEvent }) { +export function UserFieldsResponses(props: { calEvent: CalendarEvent; t: TFunction }) { + const { t } = props; const labelValueMap = getLabelValueMapFromResponses(props.calEvent); if (!labelValueMap) return null; @@ -14,7 +17,13 @@ export function UserFieldsResponses(props: { calEvent: CalendarEvent }) { <Info key={key} label={key} - description={`${labelValueMap[key] ? labelValueMap[key] : ""}`} + description={ + typeof labelValueMap[key] === "boolean" + ? labelValueMap[key] + ? t("yes") + : t("no") + : `${labelValueMap[key] ? labelValueMap[key] : ""}` + } withSpacer /> ) : null diff --git a/packages/emails/src/renderEmail.ts b/packages/emails/src/renderEmail.ts index 4404c94a47..4ada467b55 100644 --- a/packages/emails/src/renderEmail.ts +++ b/packages/emails/src/renderEmail.ts @@ -1,12 +1,11 @@ -import * as ReactDOMServer from "react-dom/server"; - import * as templates from "./templates"; -function renderEmail<K extends keyof typeof templates>( +async function renderEmail<K extends keyof typeof templates>( template: K, props: React.ComponentProps<(typeof templates)[K]> ) { const Component = templates[template]; + const ReactDOMServer = (await import("react-dom/server")).default; return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error diff --git a/packages/emails/src/templates/BaseScheduledEmail.tsx b/packages/emails/src/templates/BaseScheduledEmail.tsx index 7367325ae0..96e589fa14 100644 --- a/packages/emails/src/templates/BaseScheduledEmail.tsx +++ b/packages/emails/src/templates/BaseScheduledEmail.tsx @@ -85,7 +85,7 @@ export const BaseScheduledEmail = ( <Info label={t("description")} description={props.calEvent.description} withSpacer formatted /> <Info label={t("additional_notes")} description={props.calEvent.additionalNotes} withSpacer /> {props.includeAppsStatus && <AppsStatus calEvent={props.calEvent} t={t} />} - <UserFieldsResponses calEvent={props.calEvent} /> + <UserFieldsResponses t={t} calEvent={props.calEvent} /> {props.calEvent.paymentInfo?.amount && ( <Info label={props.calEvent.paymentInfo.paymentOption === "HOLD" ? t("no_show_fee") : t("price")} diff --git a/packages/emails/src/templates/TeamInviteEmail.tsx b/packages/emails/src/templates/TeamInviteEmail.tsx index 4edc91c4e6..6be38be840 100644 --- a/packages/emails/src/templates/TeamInviteEmail.tsx +++ b/packages/emails/src/templates/TeamInviteEmail.tsx @@ -12,6 +12,7 @@ type TeamInvite = { joinLink: string; isCalcomMember: boolean; isOrg: boolean; + parentTeamName: string | undefined; }; export const TeamInviteEmail = ( @@ -19,17 +20,22 @@ export const TeamInviteEmail = ( ) => { return ( <V2BaseEmailHtml - subject={props.language("user_invited_you", { + subject={props.language(`user_invited_you${props.parentTeamName ? "_to_subteam" : ""}`, { user: props.from, team: props.teamName, appName: APP_NAME, + parentTeamName: props.parentTeamName, entity: props.language(props.isOrg ? "organization" : "team").toLowerCase(), })}> <p style={{ fontSize: "24px", marginBottom: "16px", textAlign: "center" }}> <> - {props.language(`email_no_user_invite_heading_${props.isOrg ? "org" : "team"}`, { - appName: APP_NAME, - })} + {props.language( + `email_no_user_invite_heading_${props.isOrg ? "org" : props.parentTeamName ? "subteam" : "team"}`, + { + appName: APP_NAME, + parentTeamName: props.parentTeamName, + } + )} </> </p> <img @@ -56,11 +62,15 @@ export const TeamInviteEmail = ( lineHeightStep: "24px", }}> <> - {props.language(`email_user_invite_subheading_${props.isOrg ? "org" : "team"}`, { - invitedBy: props.from, - appName: APP_NAME, - teamName: props.teamName, - })} + {props.language( + `email_user_invite_subheading_${props.isOrg ? "org" : props.parentTeamName ? "subteam" : "team"}`, + { + invitedBy: props.from.toString(), + appName: APP_NAME, + teamName: props.teamName, + parentTeamName: props.parentTeamName, + } + )} </> </p> <div style={{ display: "flex", justifyContent: "center" }}> diff --git a/packages/emails/templates/_base-email.ts b/packages/emails/templates/_base-email.ts index 51159a198d..0fd65be9d7 100644 --- a/packages/emails/templates/_base-email.ts +++ b/packages/emails/templates/_base-email.ts @@ -24,7 +24,7 @@ export default class BaseEmail { return dayjs(time).tz(this.getTimezone()).locale(this.getLocale()).format(format); } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return {}; } public async sendEmail() { @@ -38,21 +38,20 @@ export default class BaseEmail { if (process.env.INTEGRATION_TEST_MODE === "true") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-expect-error - setTestEmail(this.getNodeMailerPayload()); + setTestEmail(await this.getNodeMailerPayload()); console.log( "Skipped Sending Email as process.env.NEXT_PUBLIC_UNIT_TESTS is set. Emails are available in globalThis.testEmails" ); return new Promise((r) => r("Skipped sendEmail for Unit Tests")); } - const payload = this.getNodeMailerPayload(); + const payload = await this.getNodeMailerPayload(); const parseSubject = z.string().safeParse(payload?.subject); const payloadWithUnEscapedSubject = { headers: this.getMailerOptions().headers, ...payload, ...(parseSubject.success && { subject: decodeHTML(parseSubject.data) }), }; - await new Promise((resolve, reject) => createTransport(this.getMailerOptions().transport).sendMail( payloadWithUnEscapedSubject, @@ -69,7 +68,6 @@ export default class BaseEmail { ).catch((e) => console.error("sendEmail", e)); return new Promise((resolve) => resolve("send mail async")); } - protected getMailerOptions() { return { transport: serverConfig.transport, @@ -77,7 +75,6 @@ export default class BaseEmail { headers: serverConfig.headers, }; } - protected printNodeMailerError(error: Error): void { /** Don't clog the logs with unsent emails in E2E */ if (process.env.NEXT_PUBLIC_IS_E2E) return; diff --git a/packages/emails/templates/account-verify-email.ts b/packages/emails/templates/account-verify-email.ts index 74a651ac00..6f692898aa 100644 --- a/packages/emails/templates/account-verify-email.ts +++ b/packages/emails/templates/account-verify-email.ts @@ -23,14 +23,14 @@ export default class AccountVerifyEmail extends BaseEmail { this.verifyAccountInput = passwordEvent; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.verifyAccountInput.user.name} <${this.verifyAccountInput.user.email}>`, from: `${APP_NAME} <${this.getMailerOptions().from}>`, subject: this.verifyAccountInput.language("verify_email_subject", { appName: APP_NAME, }), - html: renderEmail("VerifyAccountEmail", this.verifyAccountInput), + html: await renderEmail("VerifyAccountEmail", this.verifyAccountInput), text: this.getTextBody(), }; } diff --git a/packages/emails/templates/admin-organization-notification.ts b/packages/emails/templates/admin-organization-notification.ts index 680add21d0..01b6f469ab 100644 --- a/packages/emails/templates/admin-organization-notification.ts +++ b/packages/emails/templates/admin-organization-notification.ts @@ -22,12 +22,12 @@ export default class AdminOrganizationNotification extends BaseEmail { this.input = input; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: this.input.instanceAdmins.map((admin) => admin.email).join(","), subject: `${this.input.t("admin_org_notification_email_subject")}`, - html: renderEmail("AdminOrganizationNotificationEmail", { + html: await renderEmail("AdminOrganizationNotificationEmail", { orgSlug: this.input.orgSlug, webappIPAddress: this.input.webappIPAddress, language: this.input.t, diff --git a/packages/emails/templates/attendee-awaiting-payment-email.ts b/packages/emails/templates/attendee-awaiting-payment-email.ts index 772b55c10b..24eef1262c 100644 --- a/packages/emails/templates/attendee-awaiting-payment-email.ts +++ b/packages/emails/templates/attendee-awaiting-payment-email.ts @@ -2,7 +2,7 @@ import { renderEmail } from "../"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -11,7 +11,7 @@ export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeAwaitingPaymentEmail", { + html: await renderEmail("AttendeeAwaitingPaymentEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-cancelled-email.ts b/packages/emails/templates/attendee-cancelled-email.ts index ecc128cadc..5a18b99dcd 100644 --- a/packages/emails/templates/attendee-cancelled-email.ts +++ b/packages/emails/templates/attendee-cancelled-email.ts @@ -1,9 +1,21 @@ import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeCancelledEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { + icalEvent: { + filename: "event.ics", + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_request_cancelled"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + status: "CANCELLED", + role: "attendee", + }), + method: "REQUEST", + }, to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, @@ -11,7 +23,7 @@ export default class AttendeeCancelledEmail extends AttendeeScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeCancelledEmail", { + html: await renderEmail("AttendeeCancelledEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-cancelled-seat-email.ts b/packages/emails/templates/attendee-cancelled-seat-email.ts index 396891800c..610274732e 100644 --- a/packages/emails/templates/attendee-cancelled-seat-email.ts +++ b/packages/emails/templates/attendee-cancelled-seat-email.ts @@ -2,7 +2,7 @@ import { renderEmail } from "../"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeCancelledSeatEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -11,7 +11,7 @@ export default class AttendeeCancelledSeatEmail extends AttendeeScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeCancelledSeatEmail", { + html: await renderEmail("AttendeeCancelledSeatEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-daily-video-download-recording-email.ts b/packages/emails/templates/attendee-daily-video-download-recording-email.ts index 7fc0a74d52..76cfaff690 100644 --- a/packages/emails/templates/attendee-daily-video-download-recording-email.ts +++ b/packages/emails/templates/attendee-daily-video-download-recording-email.ts @@ -21,7 +21,7 @@ export default class AttendeeDailyVideoDownloadRecordingEmail extends BaseEmail this.downloadLink = downloadLink; this.t = attendee.language.translate; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -30,7 +30,7 @@ export default class AttendeeDailyVideoDownloadRecordingEmail extends BaseEmail title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("DailyVideoDownloadRecordingEmail", { + html: await renderEmail("DailyVideoDownloadRecordingEmail", { title: this.calEvent.title, date: this.getFormattedDate(), downloadLink: this.downloadLink, diff --git a/packages/emails/templates/attendee-declined-email.ts b/packages/emails/templates/attendee-declined-email.ts index 2d7fe6d33b..8ff713332c 100644 --- a/packages/emails/templates/attendee-declined-email.ts +++ b/packages/emails/templates/attendee-declined-email.ts @@ -2,7 +2,7 @@ import { renderEmail } from "../"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -11,7 +11,7 @@ export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeDeclinedEmail", { + html: await renderEmail("AttendeeDeclinedEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-location-change-email.ts b/packages/emails/templates/attendee-location-change-email.ts index 925ba0806d..ad16431dc3 100644 --- a/packages/emails/templates/attendee-location-change-email.ts +++ b/packages/emails/templates/attendee-location-change-email.ts @@ -1,12 +1,20 @@ import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_location_changed"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + role: "attendee", + status: "CONFIRMED", + }), + method: "REQUEST", }, to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -16,7 +24,7 @@ export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail name: this.calEvent.team?.name || this.calEvent.organizer.name, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeLocationChangeEmail", { + html: await renderEmail("AttendeeLocationChangeEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-request-email.ts b/packages/emails/templates/attendee-request-email.ts index 22367aaa50..3f5d555b7c 100644 --- a/packages/emails/templates/attendee-request-email.ts +++ b/packages/emails/templates/attendee-request-email.ts @@ -4,7 +4,7 @@ import { renderEmail } from "../"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeRequestEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = this.calEvent.attendees.map((attendee) => attendee.email); return { @@ -15,7 +15,7 @@ export default class AttendeeRequestEmail extends AttendeeScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeRequestEmail", { + html: await renderEmail("AttendeeRequestEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-rescheduled-email.ts b/packages/emails/templates/attendee-rescheduled-email.ts index 0c7e183335..0ebceb096f 100644 --- a/packages/emails/templates/attendee-rescheduled-email.ts +++ b/packages/emails/templates/attendee-rescheduled-email.ts @@ -1,12 +1,20 @@ import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_type_has_been_rescheduled"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + role: "attendee", + status: "CONFIRMED", + }), + method: "REQUEST", }, to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, @@ -15,7 +23,7 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("AttendeeRescheduledEmail", { + html: await renderEmail("AttendeeRescheduledEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-scheduled-email.ts b/packages/emails/templates/attendee-scheduled-email.ts index 0d22891118..7cf48a77e3 100644 --- a/packages/emails/templates/attendee-scheduled-email.ts +++ b/packages/emails/templates/attendee-scheduled-email.ts @@ -1,16 +1,13 @@ -import type { DateArray, ParticipationStatus, ParticipationRole } from "ics"; -import { createEvent } from "ics"; // eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import type { TFunction } from "next-i18next"; -import { RRule } from "rrule"; -import dayjs from "@calcom/dayjs"; import { getRichDescription } from "@calcom/lib/CalEventParser"; import { TimeFormat } from "@calcom/lib/timeFormat"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import BaseEmail from "./_base-email"; export default class AttendeeScheduledEmail extends BaseEmail { @@ -32,72 +29,28 @@ export default class AttendeeScheduledEmail extends BaseEmail { this.t = attendee.language.translate; } - protected getiCalEventAsString(): string | undefined { - // Taking care of recurrence rule - let recurrenceRule: string | undefined = undefined; - if (this.calEvent.recurringEvent?.count) { - // ics appends "RRULE:" already, so removing it from RRule generated string - recurrenceRule = new RRule(this.calEvent.recurringEvent).toString().replace("RRULE:", ""); - } - const partstat: ParticipationStatus = "ACCEPTED"; - const role: ParticipationRole = "REQ-PARTICIPANT"; - const icsEvent = createEvent({ - uid: this.calEvent.iCalUID || this.calEvent.uid!, - start: dayjs(this.calEvent.startTime) - .utc() - .toArray() - .slice(0, 6) - .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, - startInputType: "utc", - productId: "calcom/ics", - title: this.calEvent.title, - description: this.getTextBody(), - duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, - organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email }, - attendees: [ - ...this.calEvent.attendees.map((attendee: Person) => ({ - name: attendee.name, - email: attendee.email, - partstat, - role, - rsvp: true, - })), - ...(this.calEvent.team?.members - ? this.calEvent.team?.members.map((member: Person) => ({ - name: member.name, - email: member.email, - partstat, - role, - rsvp: true, - })) - : []), - ], - method: "REQUEST", - ...{ recurrenceRule }, - status: "CONFIRMED", - }); - if (icsEvent.error) { - throw icsEvent.error; - } - return icsEvent.value; - } - - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const clonedCalEvent = cloneDeep(this.calEvent); - this.getiCalEventAsString(); - return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.calEvent.recurringEvent?.count + ? this.t("your_event_has_been_scheduled_recurring") + : this.t("your_event_has_been_scheduled"), + role: "attendee", + subtitle: "emailed_you_and_any_other_attendees", + status: "CONFIRMED", + }), method: "REQUEST", }, to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: [...this.calEvent.attendees.map(({ email }) => email), this.calEvent.organizer.email], subject: `${this.calEvent.title}`, - html: renderEmail("AttendeeScheduledEmail", { + html: await renderEmail("AttendeeScheduledEmail", { calEvent: clonedCalEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/attendee-verify-email.ts b/packages/emails/templates/attendee-verify-email.ts index 99e9e3e31f..7919b8aa34 100644 --- a/packages/emails/templates/attendee-verify-email.ts +++ b/packages/emails/templates/attendee-verify-email.ts @@ -23,14 +23,14 @@ export default class AttendeeVerifyEmail extends BaseEmail { this.verifyAccountInput = passwordEvent; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.verifyAccountInput.user.name} <${this.verifyAccountInput.user.email}>`, from: `${APP_NAME} <${this.getMailerOptions().from}>`, subject: this.verifyAccountInput.language("verify_email_subject", { appName: APP_NAME, }), - html: renderEmail("VerifyEmailByCode", this.verifyAccountInput), + html: await renderEmail("VerifyEmailByCode", this.verifyAccountInput), text: this.getTextBody(), }; } diff --git a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts index 184cbf4065..09310c816c 100644 --- a/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts +++ b/packages/emails/templates/attendee-was-requested-to-reschedule-email.ts @@ -1,12 +1,9 @@ -import type { DateArray } from "ics"; -import { createEvent } from "ics"; - -import dayjs from "@calcom/dayjs"; import { getManageLink } from "@calcom/lib/CalEventParser"; import { APP_NAME } from "@calcom/lib/constants"; -import type { CalendarEvent, Person } from "@calcom/types/Calendar"; +import type { CalendarEvent } from "@calcom/types/Calendar"; import { renderEmail } from ".."; +import generateIcsString from "../lib/generateIcsString"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerScheduledEmail { @@ -16,13 +13,22 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche this.metadata = metadata; this.t = this.calEvent.attendees[0].language.translate; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.calEvent.attendees[0].email]; return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("request_reschedule_booking"), + subtitle: this.t("request_reschedule_subtitle", { + organizer: this.calEvent.organizer.name, + }), + role: "attendee", + status: "CANCELLED", + }), + method: "REQUEST", }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), @@ -30,7 +36,7 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, })}`, - html: renderEmail("AttendeeWasRequestedToRescheduleEmail", { + html: await renderEmail("AttendeeWasRequestedToRescheduleEmail", { calEvent: this.calEvent, attendee: this.calEvent.attendees[0], metadata: this.metadata, @@ -39,35 +45,6 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche }; } - // @OVERRIDE - protected getiCalEventAsString(): string | undefined { - const icsEvent = createEvent({ - start: dayjs(this.calEvent.startTime) - .utc() - .toArray() - .slice(0, 6) - .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, - startInputType: "utc", - productId: "calcom/ics", - title: this.t("ics_event_title", { - eventType: this.calEvent.type, - name: this.calEvent.attendees[0].name, - }), - description: this.getTextBody(), - duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, - organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email }, - attendees: this.calEvent.attendees.map((attendee: Person) => ({ - name: attendee.name, - email: attendee.email, - })), - status: "CANCELLED", - method: "CANCEL", - }); - if (icsEvent.error) { - throw icsEvent.error; - } - return icsEvent.value; - } // @OVERRIDE protected getWhen(): string { return ` diff --git a/packages/emails/templates/broken-integration-email.ts b/packages/emails/templates/broken-integration-email.ts index 12d7800a89..b1aaddb175 100644 --- a/packages/emails/templates/broken-integration-email.ts +++ b/packages/emails/templates/broken-integration-email.ts @@ -21,7 +21,7 @@ export default class BrokenIntegrationEmail extends BaseEmail { this.type = type; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.calEvent.organizer.email]; return { @@ -32,7 +32,7 @@ export default class BrokenIntegrationEmail extends BaseEmail { name: this.calEvent.attendees[0].name, date: this.getFormattedDate(), })}`, - html: renderEmail("BrokenIntegrationEmail", { + html: await renderEmail("BrokenIntegrationEmail", { calEvent: this.calEvent, attendee: this.calEvent.organizer, type: this.type, @@ -53,7 +53,7 @@ ${this.t( )} ${this.t(subtitle)} ${extraInfo} -${getRichDescription(this.calEvent)} +${getRichDescription(this.calEvent, this.t, true)} ${callToAction} `.trim(); } diff --git a/packages/emails/templates/disabled-app-email.ts b/packages/emails/templates/disabled-app-email.ts index 0a927a179c..724bed66e1 100644 --- a/packages/emails/templates/disabled-app-email.ts +++ b/packages/emails/templates/disabled-app-email.ts @@ -1,4 +1,4 @@ -import { TFunction } from "next-i18next"; +import type { TFunction } from "next-i18next"; import { renderEmail } from ".."; import BaseEmail from "./_base-email"; @@ -28,7 +28,7 @@ export default class DisabledAppEmail extends BaseEmail { this.eventTypeId = eventTypeId; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `Cal.com <${this.getMailerOptions().from}>`, to: this.email, @@ -36,7 +36,7 @@ export default class DisabledAppEmail extends BaseEmail { this.title && this.eventTypeId ? this.t("disabled_app_affects_event_type", { appName: this.appName, eventType: this.title }) : this.t("admin_has_disabled", { appName: this.appName }), - html: renderEmail("DisabledAppEmail", { + html: await renderEmail("DisabledAppEmail", { title: this.title, appName: this.appName, eventTypeId: this.eventTypeId, diff --git a/packages/emails/templates/feedback-email.ts b/packages/emails/templates/feedback-email.ts index e0dd9e5d0f..4eef2a9f34 100644 --- a/packages/emails/templates/feedback-email.ts +++ b/packages/emails/templates/feedback-email.ts @@ -18,12 +18,12 @@ export default class FeedbackEmail extends BaseEmail { this.feedback = feedback; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: process.env.SEND_FEEDBACK_EMAIL, subject: `User Feedback`, - html: renderEmail("FeedbackEmail", this.feedback), + html: await renderEmail("FeedbackEmail", this.feedback), text: this.getTextBody(), }; } diff --git a/packages/emails/templates/forgot-password-email.ts b/packages/emails/templates/forgot-password-email.ts index 6c21606cc6..7f041693b7 100644 --- a/packages/emails/templates/forgot-password-email.ts +++ b/packages/emails/templates/forgot-password-email.ts @@ -23,14 +23,14 @@ export default class ForgotPasswordEmail extends BaseEmail { this.passwordEvent = passwordEvent; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.passwordEvent.user.name} <${this.passwordEvent.user.email}>`, from: `${APP_NAME} <${this.getMailerOptions().from}>`, subject: this.passwordEvent.language("reset_password_subject", { appName: APP_NAME, }), - html: renderEmail("ForgotPasswordEmail", this.passwordEvent), + html: await renderEmail("ForgotPasswordEmail", this.passwordEvent), text: this.getTextBody(), }; } diff --git a/packages/emails/templates/monthly-digest-email.ts b/packages/emails/templates/monthly-digest-email.ts index 5230732f3a..629b389329 100644 --- a/packages/emails/templates/monthly-digest-email.ts +++ b/packages/emails/templates/monthly-digest-email.ts @@ -12,12 +12,12 @@ export default class MonthlyDigestEmail extends BaseEmail { this.eventData = eventData; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: this.eventData.admin.email, subject: `${APP_NAME}: Your monthly digest`, - html: renderEmail("MonthlyDigestEmail", this.eventData), + html: await renderEmail("MonthlyDigestEmail", this.eventData), text: "", }; } diff --git a/packages/emails/templates/no-show-fee-charged-email.ts b/packages/emails/templates/no-show-fee-charged-email.ts index 500e4393eb..3ede12d719 100644 --- a/packages/emails/templates/no-show-fee-charged-email.ts +++ b/packages/emails/templates/no-show-fee-charged-email.ts @@ -2,7 +2,7 @@ import { renderEmail } from "../"; import AttendeeScheduledEmail from "./attendee-scheduled-email"; export default class NoShowFeeChargedEmail extends AttendeeScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { if (!this.calEvent.paymentInfo?.amount) throw new Error("No payment into"); return { to: `${this.attendee.name} <${this.attendee.email}>`, @@ -14,7 +14,7 @@ export default class NoShowFeeChargedEmail extends AttendeeScheduledEmail { amount: this.calEvent.paymentInfo.amount / 100, formatParams: { amount: { currency: this.calEvent.paymentInfo?.currency } }, })}`, - html: renderEmail("NoShowFeeChargedEmail", { + html: await renderEmail("NoShowFeeChargedEmail", { calEvent: this.calEvent, attendee: this.attendee, }), diff --git a/packages/emails/templates/org-auto-join-invite.ts b/packages/emails/templates/org-auto-join-invite.ts index 002ebf7482..64a2811ec6 100644 --- a/packages/emails/templates/org-auto-join-invite.ts +++ b/packages/emails/templates/org-auto-join-invite.ts @@ -22,7 +22,7 @@ export default class OrgAutoJoinEmail extends BaseEmail { this.orgAutoInviteEvent = orgAutoInviteEvent; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: this.orgAutoInviteEvent.to, from: `${APP_NAME} <${this.getMailerOptions().from}>`, @@ -32,7 +32,7 @@ export default class OrgAutoJoinEmail extends BaseEmail { appName: APP_NAME, entity: this.orgAutoInviteEvent.language("organization").toLowerCase(), }), - html: renderEmail("OrgAutoInviteEmail", this.orgAutoInviteEvent), + html: await renderEmail("OrgAutoInviteEmail", this.orgAutoInviteEvent), text: "", }; } diff --git a/packages/emails/templates/organization-email-verification.ts b/packages/emails/templates/organization-email-verification.ts index cfbc591df6..89cfec56f4 100644 --- a/packages/emails/templates/organization-email-verification.ts +++ b/packages/emails/templates/organization-email-verification.ts @@ -22,12 +22,12 @@ export default class OrganizationEmailVerification extends BaseEmail { this.orgVerifyInput = orgVerifyInput; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: this.orgVerifyInput.user.email, subject: this.orgVerifyInput.language("verify_email_organization"), - html: renderEmail("OrganisationAccountVerifyEmail", this.orgVerifyInput), + html: await renderEmail("OrganisationAccountVerifyEmail", this.orgVerifyInput), text: this.getTextBody(), }; } diff --git a/packages/emails/templates/organizer-attendee-cancelled-seat-email.ts b/packages/emails/templates/organizer-attendee-cancelled-seat-email.ts index fe6ce75726..5fa17765b1 100644 --- a/packages/emails/templates/organizer-attendee-cancelled-seat-email.ts +++ b/packages/emails/templates/organizer-attendee-cancelled-seat-email.ts @@ -4,7 +4,7 @@ import { renderEmail } from "../"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerCancelledEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.calEvent.organizer.email]; if (this.calEvent.team) { this.calEvent.team.members.forEach((member) => { @@ -22,7 +22,7 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerAttendeeCancelledSeatEmail", { + html: await renderEmail("OrganizerAttendeeCancelledSeatEmail", { attendee: this.calEvent.organizer, calEvent: this.calEvent, }), diff --git a/packages/emails/templates/organizer-cancelled-email.ts b/packages/emails/templates/organizer-cancelled-email.ts index d0e2fb315c..3466cab5c2 100644 --- a/packages/emails/templates/organizer-cancelled-email.ts +++ b/packages/emails/templates/organizer-cancelled-email.ts @@ -1,20 +1,32 @@ import { APP_NAME } from "@calcom/lib/constants"; import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerCancelledEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { + icalEvent: { + filename: "event.ics", + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_request_cancelled"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + status: "CANCELLED", + role: "organizer", + }), + method: "REQUEST", + }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), subject: `${this.t("event_cancelled_subject", { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerCancelledEmail", { + html: await renderEmail("OrganizerCancelledEmail", { attendee: this.calEvent.organizer, calEvent: this.calEvent, }), diff --git a/packages/emails/templates/organizer-daily-video-download-recording-email.ts b/packages/emails/templates/organizer-daily-video-download-recording-email.ts index 714ba4f7e4..34e1549132 100644 --- a/packages/emails/templates/organizer-daily-video-download-recording-email.ts +++ b/packages/emails/templates/organizer-daily-video-download-recording-email.ts @@ -19,7 +19,7 @@ export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail this.downloadLink = downloadLink; this.t = this.calEvent.organizer.language.translate; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: `${this.calEvent.organizer.email}>`, from: `${APP_NAME} <${this.getMailerOptions().from}>`, @@ -28,7 +28,7 @@ export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("DailyVideoDownloadRecordingEmail", { + html: await renderEmail("DailyVideoDownloadRecordingEmail", { title: this.calEvent.title, date: this.getFormattedDate(), downloadLink: this.downloadLink, diff --git a/packages/emails/templates/organizer-location-change-email.ts b/packages/emails/templates/organizer-location-change-email.ts index a0ed9e7993..c5c8e893c0 100644 --- a/packages/emails/templates/organizer-location-change-email.ts +++ b/packages/emails/templates/organizer-location-change-email.ts @@ -1,16 +1,24 @@ import { APP_NAME } from "@calcom/lib/constants"; import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_location_changed"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + role: "organizer", + status: "CONFIRMED", + }), + method: "REQUEST", }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), @@ -20,7 +28,7 @@ export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmai name: this.calEvent.attendees[0].name, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerLocationChangeEmail", { + html: await renderEmail("OrganizerLocationChangeEmail", { attendee: this.calEvent.organizer, calEvent: this.calEvent, }), diff --git a/packages/emails/templates/organizer-payment-refund-failed-email.ts b/packages/emails/templates/organizer-payment-refund-failed-email.ts index 26818b1fd7..fd907ba1b5 100644 --- a/packages/emails/templates/organizer-payment-refund-failed-email.ts +++ b/packages/emails/templates/organizer-payment-refund-failed-email.ts @@ -4,7 +4,7 @@ import { renderEmail } from "../"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { @@ -15,7 +15,7 @@ export default class OrganizerPaymentRefundFailedEmail extends OrganizerSchedule name: this.calEvent.attendees[0].name, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerPaymentRefundFailedEmail", { + html: await renderEmail("OrganizerPaymentRefundFailedEmail", { calEvent: this.calEvent, attendee: this.calEvent.organizer, }), diff --git a/packages/emails/templates/organizer-request-email.ts b/packages/emails/templates/organizer-request-email.ts index 0267df2261..47b82627f5 100644 --- a/packages/emails/templates/organizer-request-email.ts +++ b/packages/emails/templates/organizer-request-email.ts @@ -4,7 +4,7 @@ import { renderEmail } from "../"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerRequestEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { @@ -12,7 +12,7 @@ export default class OrganizerRequestEmail extends OrganizerScheduledEmail { to: toAddresses.join(","), replyTo: [this.calEvent.organizer.email, ...this.calEvent.attendees.map(({ email }) => email)], subject: `${this.t("awaiting_approval")}: ${this.calEvent.title}`, - html: renderEmail("OrganizerRequestEmail", { + html: await renderEmail("OrganizerRequestEmail", { calEvent: this.calEvent, attendee: this.calEvent.organizer, }), diff --git a/packages/emails/templates/organizer-request-reminder-email.ts b/packages/emails/templates/organizer-request-reminder-email.ts index b9f2c1b53c..b8d5130a7a 100644 --- a/packages/emails/templates/organizer-request-reminder-email.ts +++ b/packages/emails/templates/organizer-request-reminder-email.ts @@ -4,7 +4,7 @@ import { renderEmail } from "../"; import OrganizerRequestEmail from "./organizer-request-email"; export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { @@ -15,7 +15,7 @@ export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerRequestReminderEmail", { + html: await renderEmail("OrganizerRequestReminderEmail", { calEvent: this.calEvent, attendee: this.calEvent.organizer, }), diff --git a/packages/emails/templates/organizer-requested-to-reschedule-email.ts b/packages/emails/templates/organizer-requested-to-reschedule-email.ts index 7a601af696..8a5e5247b7 100644 --- a/packages/emails/templates/organizer-requested-to-reschedule-email.ts +++ b/packages/emails/templates/organizer-requested-to-reschedule-email.ts @@ -7,6 +7,7 @@ import { APP_NAME } from "@calcom/lib/constants"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { renderEmail } from ".."; +import generateIcsString from "../lib/generateIcsString"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerRequestedToRescheduleEmail extends OrganizerScheduledEmail { @@ -15,13 +16,24 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu super({ calEvent }); this.metadata = metadata; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.calEvent.organizer.email]; return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("request_reschedule_title_organizer", { + attendee: this.calEvent.attendees[0].name, + }), + subtitle: this.t("request_reschedule_subtitle_organizer", { + attendee: this.calEvent.attendees[0].name, + }), + role: "organizer", + status: "CANCELLED", + }), + method: "REQUEST", }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), @@ -30,7 +42,7 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu name: this.calEvent.attendees[0].name, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerRequestedToRescheduleEmail", { + html: await renderEmail("OrganizerRequestedToRescheduleEmail", { calEvent: this.calEvent, attendee: this.calEvent.organizer, }), @@ -83,13 +95,11 @@ export default class OrganizerRequestedToRescheduleEmail extends OrganizerSchedu } // @OVERRIDE - protected getTextBody(title = "", subtitle = "", extraInfo = "", callToAction = ""): string { + protected getTextBody(title = "", subtitle = ""): string { return ` ${this.t(title)} ${this.t(subtitle)} -${extraInfo} -${getRichDescription(this.calEvent)} -${callToAction} +${getRichDescription(this.calEvent, this.t, true)} `.trim(); } } diff --git a/packages/emails/templates/organizer-rescheduled-email.ts b/packages/emails/templates/organizer-rescheduled-email.ts index 26da823a1e..ed63aa8fcc 100644 --- a/packages/emails/templates/organizer-rescheduled-email.ts +++ b/packages/emails/templates/organizer-rescheduled-email.ts @@ -1,16 +1,24 @@ import { APP_NAME } from "@calcom/lib/constants"; import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import OrganizerScheduledEmail from "./organizer-scheduled-email"; export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail { - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.t("event_type_has_been_rescheduled"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + role: "organizer", + status: "CONFIRMED", + }), + method: "REQUEST", }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), @@ -19,7 +27,7 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail { title: this.calEvent.title, date: this.getFormattedDate(), })}`, - html: renderEmail("OrganizerRescheduledEmail", { + html: await renderEmail("OrganizerRescheduledEmail", { calEvent: { ...this.calEvent, attendeeSeatId: undefined }, attendee: this.calEvent.organizer, }), diff --git a/packages/emails/templates/organizer-scheduled-email.ts b/packages/emails/templates/organizer-scheduled-email.ts index 76b036252e..5e8e27f283 100644 --- a/packages/emails/templates/organizer-scheduled-email.ts +++ b/packages/emails/templates/organizer-scheduled-email.ts @@ -1,17 +1,14 @@ -import type { DateArray } from "ics"; -import { createEvent } from "ics"; // eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import type { TFunction } from "next-i18next"; -import { RRule } from "rrule"; -import dayjs from "@calcom/dayjs"; import { getRichDescription } from "@calcom/lib/CalEventParser"; import { APP_NAME } from "@calcom/lib/constants"; import { TimeFormat } from "@calcom/lib/timeFormat"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; import { renderEmail } from "../"; +import generateIcsString from "../lib/generateIcsString"; import BaseEmail from "./_base-email"; export default class OrganizerScheduledEmail extends BaseEmail { @@ -29,61 +26,29 @@ export default class OrganizerScheduledEmail extends BaseEmail { this.teamMember = input.teamMember; } - protected getiCalEventAsString(): string | undefined { - // Taking care of recurrence rule - let recurrenceRule: string | undefined = undefined; - if (this.calEvent.recurringEvent?.count) { - // ics appends "RRULE:" already, so removing it from RRule generated string - recurrenceRule = new RRule(this.calEvent.recurringEvent).toString().replace("RRULE:", ""); - } - const icsEvent = createEvent({ - uid: this.calEvent.iCalUID || this.calEvent.uid!, - start: dayjs(this.calEvent.startTime) - .utc() - .toArray() - .slice(0, 6) - .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, - startInputType: "utc", - productId: "calcom/ics", - title: this.calEvent.title, - description: this.getTextBody(), - duration: { minutes: dayjs(this.calEvent.endTime).diff(dayjs(this.calEvent.startTime), "minute") }, - organizer: { name: this.calEvent.organizer.name, email: this.calEvent.organizer.email }, - ...{ recurrenceRule }, - attendees: [ - ...this.calEvent.attendees.map((attendee: Person) => ({ - name: attendee.name, - email: attendee.email, - })), - ...(this.calEvent.team?.members - ? this.calEvent.team?.members.map((member: Person) => ({ - name: member.name, - email: member.email, - })) - : []), - ], - status: "CONFIRMED", - }); - if (icsEvent.error) { - throw icsEvent.error; - } - return icsEvent.value; - } - - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { const clonedCalEvent = cloneDeep(this.calEvent); const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email]; return { icalEvent: { filename: "event.ics", - content: this.getiCalEventAsString(), + content: generateIcsString({ + event: this.calEvent, + title: this.calEvent.recurringEvent?.count + ? this.t("new_event_scheduled_recurring") + : this.t("new_event_scheduled"), + subtitle: this.t("emailed_you_and_any_other_attendees"), + role: "organizer", + status: "CONFIRMED", + }), + method: "REQUEST", }, from: `${APP_NAME} <${this.getMailerOptions().from}>`, to: toAddresses.join(","), replyTo: [this.calEvent.organizer.email, ...this.calEvent.attendees.map(({ email }) => email)], subject: `${this.newSeat ? `${this.t("new_attendee")}: ` : ""}${this.calEvent.title}`, - html: renderEmail("OrganizerScheduledEmail", { + html: await renderEmail("OrganizerScheduledEmail", { calEvent: clonedCalEvent, attendee: this.calEvent.organizer, teamMember: this.teamMember, @@ -105,7 +70,7 @@ ${this.t( )} ${this.t(subtitle)} ${extraInfo} -${getRichDescription(this.calEvent)} +${getRichDescription(this.calEvent, this.t, true)} ${callToAction} `.trim(); } diff --git a/packages/emails/templates/slug-replacement-email.ts b/packages/emails/templates/slug-replacement-email.ts index c9316cb28d..2a2655718d 100644 --- a/packages/emails/templates/slug-replacement-email.ts +++ b/packages/emails/templates/slug-replacement-email.ts @@ -19,12 +19,12 @@ export default class SlugReplacementEmail extends BaseEmail { this.t = t; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { from: `Cal.com <${this.getMailerOptions().from}>`, to: this.email, subject: this.t("email_subject_slug_replacement", { slug: this.slug }), - html: renderEmail("SlugReplacementEmail", { + html: await renderEmail("SlugReplacementEmail", { slug: this.slug, name: this.name, teamName: this.teamName || "", diff --git a/packages/emails/templates/team-invite-email.ts b/packages/emails/templates/team-invite-email.ts index d583f7dc96..1c589ceec1 100644 --- a/packages/emails/templates/team-invite-email.ts +++ b/packages/emails/templates/team-invite-email.ts @@ -13,6 +13,7 @@ export type TeamInvite = { joinLink: string; isCalcomMember: boolean; isOrg: boolean; + parentTeamName: string | undefined; }; export default class TeamInviteEmail extends BaseEmail { @@ -24,19 +25,23 @@ export default class TeamInviteEmail extends BaseEmail { this.teamInviteEvent = teamInviteEvent; } - protected getNodeMailerPayload(): Record<string, unknown> { + protected async getNodeMailerPayload(): Promise<Record<string, unknown>> { return { to: this.teamInviteEvent.to, from: `${APP_NAME} <${this.getMailerOptions().from}>`, - subject: this.teamInviteEvent.language("user_invited_you", { - user: this.teamInviteEvent.from, - team: this.teamInviteEvent.teamName, - appName: APP_NAME, - entity: this.teamInviteEvent - .language(this.teamInviteEvent.isOrg ? "organization" : "team") - .toLowerCase(), - }), - html: renderEmail("TeamInviteEmail", this.teamInviteEvent), + subject: this.teamInviteEvent.language( + `user_invited_you${this.teamInviteEvent.parentTeamName ? "_to_subteam" : ""}`, + { + user: this.teamInviteEvent.from, + team: this.teamInviteEvent.teamName, + appName: APP_NAME, + parentTeamName: this.teamInviteEvent.parentTeamName, + entity: this.teamInviteEvent + .language(this.teamInviteEvent.isOrg ? "organization" : "team") + .toLowerCase(), + } + ), + html: await renderEmail("TeamInviteEmail", this.teamInviteEvent), text: "", }; } diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index 0cb67b3a05..4be73fc9ea 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -439,6 +439,10 @@ class CalApi { elementOrSelector: string | HTMLElement; config?: PrefillAndIframeAttrsConfig; }) { + if (this.cal.inlineEl) { + console.warn("Inline embed already exists. Ignoring this call"); + return; + } // eslint-disable-next-line prefer-rest-params validate(arguments[0], { required: true, diff --git a/packages/embeds/embed-react/src/Cal.tsx b/packages/embeds/embed-react/src/Cal.tsx index bd409ece41..3c8e06d6cb 100644 --- a/packages/embeds/embed-react/src/Cal.tsx +++ b/packages/embeds/embed-react/src/Cal.tsx @@ -13,12 +13,13 @@ type CalProps = { debug?: boolean; uiDebug?: boolean; }; + namespace?: string; config?: PrefillAndIframeAttrsConfig; embedJsUrl?: string; } & React.HTMLAttributes<HTMLDivElement>; const Cal = function Cal(props: CalProps) { - const { calLink, calOrigin, config, initConfig = {}, embedJsUrl, ...restProps } = props; + const { calLink, calOrigin, namespace = "", config, initConfig = {}, embedJsUrl, ...restProps } = props; if (!calLink) { throw new Error("calLink is required"); } @@ -31,16 +32,28 @@ const Cal = function Cal(props: CalProps) { } initializedRef.current = true; const element = ref.current; - Cal("init", { - ...initConfig, - origin: calOrigin, - }); - Cal("inline", { - elementOrSelector: element, - calLink, - config, - }); - }, [Cal, calLink, config, calOrigin, initConfig]); + if (namespace) { + Cal("init", namespace, { + ...initConfig, + origin: calOrigin, + }); + Cal.ns[namespace]("inline", { + elementOrSelector: element, + calLink, + config, + }); + } else { + Cal("init", { + ...initConfig, + origin: calOrigin, + }); + Cal("inline", { + elementOrSelector: element, + calLink, + config, + }); + } + }, [Cal, calLink, config, namespace, calOrigin, initConfig]); if (!Cal) { return null; diff --git a/packages/embeds/embed-react/tsconfig.json b/packages/embeds/embed-react/tsconfig.json index 4a82423a1e..ccbd96a5d8 100644 --- a/packages/embeds/embed-react/tsconfig.json +++ b/packages/embeds/embed-react/tsconfig.json @@ -13,7 +13,7 @@ "@calcom/embed-snippet": ["../embed-snippet/src"] } }, - "include": ["**/*.ts", "**/*.tsx", "env.d.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx", "env.d.ts"], // Exclude "test" because that has `api.test.ts` which imports @calcom/embed-react which needs it to be built using this tsconfig.json first. Excluding it here prevents type-check from validating test folder "exclude": ["node_modules", "test"] } diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index ebe299571f..277e2aa050 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -232,6 +232,12 @@ const providers: Provider[] = [ if (role !== "ADMIN") return role; // User's identity provider is not "CAL" if (user.identityProvider !== IdentityProvider.CAL) return role; + + if (process.env.NEXT_PUBLIC_IS_E2E) { + console.warn("E2E testing is enabled, skipping password and 2FA requirements for Admin"); + return role; + } + // User's password is valid and two-factor authentication is enabled if (isPasswordValid(credentials.password, false, true) && user.twoFactorEnabled) return role; // Code is running in a development environment diff --git a/packages/features/auth/signup/handlers/calcomHandler.ts b/packages/features/auth/signup/handlers/calcomHandler.ts index ffa7baee73..47e86e779a 100644 --- a/packages/features/auth/signup/handlers/calcomHandler.ts +++ b/packages/features/auth/signup/handlers/calcomHandler.ts @@ -4,19 +4,24 @@ import stripe from "@calcom/app-store/stripepayment/lib/server"; import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail"; +import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { getLocaleFromRequest } from "@calcom/lib/getLocaleFromRequest"; import { HttpError } from "@calcom/lib/http-error"; import { usernameHandler, type RequestWithUsernameStatus } from "@calcom/lib/server/username"; import { createWebUser as syncServicesCreateWebUser } from "@calcom/lib/sync/SyncServiceManager"; import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager"; -import { validateUsername } from "@calcom/lib/validateUsername"; +import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername"; import { prisma } from "@calcom/prisma"; -import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums"; +import { IdentityProvider } from "@calcom/prisma/enums"; import { signupSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { joinAnyChildTeamOnOrgInvite } from "../utils/organization"; -import { findTokenByToken, throwIfTokenExpired, validateUsernameForTeam } from "../utils/token"; +import { + findTokenByToken, + throwIfTokenExpired, + validateAndGetCorrectedUsernameForTeam, +} from "../utils/token"; async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) { const { @@ -52,15 +57,33 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) { if (token) { foundToken = await findTokenByToken({ token }); throwIfTokenExpired(foundToken?.expires); - await validateUsernameForTeam({ username, email, teamId: foundToken?.teamId ?? null }); + username = await validateAndGetCorrectedUsernameForTeam({ + username, + email, + teamId: foundToken?.teamId ?? null, + isSignup: true, + }); } else { - const usernameAndEmailValidation = await validateUsername(username, email); + const usernameAndEmailValidation = await validateAndGetCorrectedUsernameAndEmail({ + username, + email, + isSignup: true, + }); if (!usernameAndEmailValidation.isValid) { throw new HttpError({ statusCode: 409, message: "Username or email is already taken", }); } + + if (!usernameAndEmailValidation.username) { + throw new HttpError({ + statusCode: 422, + message: "Invalid username", + }); + } + + username = usernameAndEmailValidation.username; } // Create the customer in Stripe @@ -125,32 +148,10 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) { }); // Wrapping in a transaction as if one fails we want to rollback the whole thing to preventa any data inconsistencies - const membership = await prisma.$transaction(async (tx) => { - if (teamMetadata?.isOrganization) { - await tx.user.update({ - where: { - id: user.id, - }, - data: { - organizationId: team.id, - }, - }); - } - const membership = await tx.membership.upsert({ - where: { - userId_teamId: { userId: user.id, teamId: team.id }, - }, - update: { - accepted: true, - }, - create: { - userId: user.id, - teamId: team.id, - role: MembershipRole.MEMBER, - accepted: true, - }, - }); - return membership; + const { membership } = await createOrUpdateMemberships({ + teamMetadata, + user, + team, }); closeComUpsertTeamUser(team, user, membership.role); diff --git a/packages/features/auth/signup/handlers/selfHostedHandler.ts b/packages/features/auth/signup/handlers/selfHostedHandler.ts index 4b54669385..60e1111432 100644 --- a/packages/features/auth/signup/handlers/selfHostedHandler.ts +++ b/packages/features/auth/signup/handlers/selfHostedHandler.ts @@ -3,17 +3,23 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { checkPremiumUsername } from "@calcom/ee/common/lib/checkPremiumUsername"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail"; +import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships"; import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants"; +import logger from "@calcom/lib/logger"; import slugify from "@calcom/lib/slugify"; import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager"; -import { validateUsername } from "@calcom/lib/validateUsername"; +import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername"; import prisma from "@calcom/prisma"; -import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums"; +import { IdentityProvider } from "@calcom/prisma/enums"; import { signupSchema } from "@calcom/prisma/zod-utils"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { joinAnyChildTeamOnOrgInvite } from "../utils/organization"; -import { findTokenByToken, throwIfTokenExpired, validateUsernameForTeam } from "../utils/token"; +import { + findTokenByToken, + throwIfTokenExpired, + validateAndGetCorrectedUsernameForTeam, +} from "../utils/token"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const data = req.body; @@ -28,15 +34,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } let foundToken: { id: number; teamId: number | null; expires: Date } | null = null; + let correctedUsername = username; if (token) { foundToken = await findTokenByToken({ token }); throwIfTokenExpired(foundToken?.expires); - await validateUsernameForTeam({ username, email: userEmail, teamId: foundToken?.teamId }); + correctedUsername = await validateAndGetCorrectedUsernameForTeam({ + username, + email: userEmail, + teamId: foundToken?.teamId, + isSignup: true, + }); } else { - const userValidation = await validateUsername(username, userEmail); + const userValidation = await validateAndGetCorrectedUsernameAndEmail({ + username, + email: userEmail, + isSignup: true, + }); if (!userValidation.isValid) { + logger.error("User validation failed", { userValidation }); return res.status(409).json({ message: "Username or email is already taken" }); } + if (!userValidation.username) { + return res.status(422).json({ message: "Invalid username" }); + } + correctedUsername = userValidation.username; } const hashedPassword = await hashPassword(password); @@ -53,45 +74,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const user = await prisma.user.upsert({ where: { email: userEmail }, update: { - username, + username: correctedUsername, password: hashedPassword, emailVerified: new Date(Date.now()), identityProvider: IdentityProvider.CAL, }, create: { - username, + username: correctedUsername, email: userEmail, password: hashedPassword, identityProvider: IdentityProvider.CAL, }, }); - const membership = await prisma.$transaction(async (tx) => { - if (teamMetadata?.isOrganization) { - await tx.user.update({ - where: { - id: user.id, - }, - data: { - organizationId: team.id, - }, - }); - } - const membership = await tx.membership.upsert({ - where: { - userId_teamId: { userId: user.id, teamId: team.id }, - }, - update: { - accepted: true, - }, - create: { - userId: user.id, - teamId: team.id, - role: MembershipRole.MEMBER, - accepted: true, - }, - }); - return membership; + const { membership } = await createOrUpdateMemberships({ + teamMetadata, + user, + team, }); closeComUpsertTeamUser(team, user, membership.role); @@ -113,7 +112,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); } else { if (IS_PREMIUM_USERNAME_ENABLED) { - const checkUsername = await checkPremiumUsername(username); + const checkUsername = await checkPremiumUsername(correctedUsername); if (checkUsername.premium) { res.status(422).json({ message: "Sign up from https://cal.com/signup to claim your premium username", @@ -124,13 +123,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await prisma.user.upsert({ where: { email: userEmail }, update: { - username, + username: correctedUsername, password: hashedPassword, emailVerified: new Date(Date.now()), identityProvider: IdentityProvider.CAL, }, create: { - username, + username: correctedUsername, email: userEmail, password: hashedPassword, identityProvider: IdentityProvider.CAL, @@ -138,7 +137,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); await sendEmailVerification({ email: userEmail, - username, + username: correctedUsername, language, }); } diff --git a/packages/features/auth/signup/utils/createOrUpdateMemberships.ts b/packages/features/auth/signup/utils/createOrUpdateMemberships.ts new file mode 100644 index 0000000000..593fb50ebd --- /dev/null +++ b/packages/features/auth/signup/utils/createOrUpdateMemberships.ts @@ -0,0 +1,61 @@ +import type z from "zod"; + +import { prisma } from "@calcom/prisma"; +import type { Team, User } from "@calcom/prisma/client"; +import { MembershipRole } from "@calcom/prisma/enums"; +import type { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +export const createOrUpdateMemberships = async ({ + teamMetadata, + user, + team, +}: { + user: Pick<User, "id">; + team: Pick<Team, "id" | "parentId">; + teamMetadata: z.infer<typeof teamMetadataSchema>; +}) => { + return await prisma.$transaction(async (tx) => { + if (teamMetadata?.isOrganization) { + await tx.user.update({ + where: { + id: user.id, + }, + data: { + organizationId: team.id, + }, + }); + } + const membership = await tx.membership.upsert({ + where: { + userId_teamId: { userId: user.id, teamId: team.id }, + }, + update: { + accepted: true, + }, + create: { + userId: user.id, + teamId: team.id, + role: MembershipRole.MEMBER, + accepted: true, + }, + }); + const orgMembership = null; + if (team.parentId) { + await tx.membership.upsert({ + where: { + userId_teamId: { userId: user.id, teamId: team.parentId }, + }, + update: { + accepted: true, + }, + create: { + userId: user.id, + teamId: team.parentId, + role: MembershipRole.MEMBER, + accepted: true, + }, + }); + } + return { membership, orgMembership }; + }); +}; diff --git a/packages/features/auth/signup/utils/getOrgUsernameFromEmail.ts b/packages/features/auth/signup/utils/getOrgUsernameFromEmail.ts new file mode 100644 index 0000000000..ee48f78747 --- /dev/null +++ b/packages/features/auth/signup/utils/getOrgUsernameFromEmail.ts @@ -0,0 +1,11 @@ +import slugify from "@calcom/lib/slugify"; + +export const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) => { + const [emailUser, emailDomain = ""] = email.split("@"); + const username = + emailDomain === autoAcceptEmailDomain + ? slugify(emailUser) + : slugify(`${emailUser}-${emailDomain.split(".")[0]}`); + + return username; +}; diff --git a/packages/features/auth/signup/utils/token.ts b/packages/features/auth/signup/utils/token.ts index da1b08967d..41e9e26128 100644 --- a/packages/features/auth/signup/utils/token.ts +++ b/packages/features/auth/signup/utils/token.ts @@ -1,6 +1,6 @@ import dayjs from "@calcom/dayjs"; import { HttpError } from "@calcom/lib/http-error"; -import { validateUsernameInTeam } from "@calcom/lib/validateUsername"; +import { validateAndGetCorrectedUsernameInTeam } from "@calcom/lib/validateUsername"; import { prisma } from "@calcom/prisma"; export async function findTokenByToken({ token }: { token: string }) { @@ -35,21 +35,31 @@ export function throwIfTokenExpired(expires?: Date) { } } -export async function validateUsernameForTeam({ +export async function validateAndGetCorrectedUsernameForTeam({ username, email, teamId, + isSignup, }: { username: string; email: string; teamId: number | null; + isSignup: boolean; }) { - if (!teamId) return; - const teamUserValidation = await validateUsernameInTeam(username, email, teamId); + if (!teamId) return username; + + const teamUserValidation = await validateAndGetCorrectedUsernameInTeam(username, email, teamId, isSignup); if (!teamUserValidation.isValid) { throw new HttpError({ statusCode: 409, message: "Username or email is already taken", }); } + if (!teamUserValidation.username) { + throw new HttpError({ + statusCode: 422, + message: "Invalid username", + }); + } + return teamUserValidation.username; } diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx index 38acbb194b..15749a95a6 100644 --- a/packages/features/bookings/Booker/Booker.tsx +++ b/packages/features/bookings/Booker/Booker.tsx @@ -1,5 +1,6 @@ import { LazyMotion, m, AnimatePresence } from "framer-motion"; import dynamic from "next/dynamic"; +import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef } from "react"; import StickyBox from "react-sticky-box"; import { shallow } from "zustand/shallow"; @@ -9,8 +10,10 @@ import dayjs from "@calcom/dayjs"; import { useEmbedType, useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import { useNonEmptyScheduleDays } from "@calcom/features/schedules"; import classNames from "@calcom/lib/classNames"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils"; +import { Button } from "@calcom/ui"; import { AvailableTimeSlots } from "./components/AvailableTimeSlots"; import { BookEventForm } from "./components/BookEventForm"; @@ -30,7 +33,9 @@ import { useBrandColors } from "./utils/use-brand-colors"; const loadFramerFeatures = () => import("./framer-features").then((res) => res.default); const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy")); -const UnpublishedEntity = dynamic(() => import("@calcom/ui").then((mod) => mod.UnpublishedEntity)); +const UnpublishedEntity = dynamic(() => + import("@calcom/ui/components/unpublished-entity/UnpublishedEntity").then((mod) => mod.UnpublishedEntity) +); const DatePicker = dynamic(() => import("./components/DatePicker").then((mod) => mod.DatePicker), { ssr: false, }); @@ -43,8 +48,10 @@ const BookerComponent = ({ hideBranding = false, isTeamEvent, entity, + durationConfig, duration, hashedLink, + isInstantMeeting = false, }: BookerProps) => { /** * Prioritize dateSchedule load @@ -147,7 +154,8 @@ const BookerComponent = ({ layout: defaultLayout, isTeamEvent, org: entity.orgSlug, - durationConfig: event?.data?.metadata?.multipleDuration, + durationConfig, + isInstantMeeting, }); useEffect(() => { @@ -180,12 +188,6 @@ const BookerComponent = ({ return setBookerState("booking"); }, [event, selectedDate, selectedTimeslot, setBookerState]); - useEffect(() => { - if (layout === "mobile") { - timeslotsRef.current?.scrollIntoView({ behavior: "smooth" }); - } - }, [layout]); - const hideEventTypeDetails = isEmbed ? embedUiConfig.hideEventTypeDetails : false; if (entity.isUnpublished) { @@ -219,6 +221,13 @@ const BookerComponent = ({ return ( <> {event.data ? <BookingPageTagManager eventType={event.data} /> : null} + {bookerState !== "booking" && event.data?.isInstantEvent && ( + <div + className="animate-fade-in-up fixed bottom-2 z-40 my-2 opacity-0" + style={{ animationDelay: "2s" }}> + <InstantBooking /> + </div> + )} <div className={classNames( // In a popup embed, if someone clicks outside the main(having main class or main tag), it closes the embed @@ -239,22 +248,24 @@ const BookerComponent = ({ !isEmbed && layout === BookerLayouts.MONTH_VIEW && "border-subtle" )}> <AnimatePresence> - <BookerSection - area="header" - className={classNames( - layout === BookerLayouts.MONTH_VIEW && "fixed top-4 z-10 ltr:right-4 rtl:left-4", - (layout === BookerLayouts.COLUMN_VIEW || layout === BookerLayouts.WEEK_VIEW) && - "bg-default dark:bg-muted sticky top-0 z-10" - )}> - <Header - username={username} - eventSlug={eventSlug} - enabledLayouts={bookerLayouts.enabledLayouts} - extraDays={layout === BookerLayouts.COLUMN_VIEW ? columnViewExtraDays.current : extraDays} - isMobile={isMobile} - nextSlots={nextSlots} - /> - </BookerSection> + {!isInstantMeeting && ( + <BookerSection + area="header" + className={classNames( + layout === BookerLayouts.MONTH_VIEW && "fixed top-4 z-10 ltr:right-4 rtl:left-4", + (layout === BookerLayouts.COLUMN_VIEW || layout === BookerLayouts.WEEK_VIEW) && + "bg-default dark:bg-muted sticky top-0 z-10" + )}> + <Header + username={username} + eventSlug={eventSlug} + enabledLayouts={bookerLayouts.enabledLayouts} + extraDays={layout === BookerLayouts.COLUMN_VIEW ? columnViewExtraDays.current : extraDays} + isMobile={isMobile} + nextSlots={nextSlots} + /> + </BookerSection> + )} <StickyOnDesktop key="meta" className={classNames( @@ -366,3 +377,54 @@ export const Booker = (props: BookerProps) => { </LazyMotion> ); }; + +export const InstantBooking = () => { + const { t } = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + + return ( + <div className=" bg-default border-subtle mx-2 block items-center gap-3 rounded-xl border p-[6px] text-sm shadow-sm delay-1000 sm:flex"> + <div className="flex items-center gap-3 ps-1"> + {/* TODO: max. show 4 people here */} + <div className="relative"> + {/* <AvatarGroup + size="sm" + className="relative" + items={[ + { + image: "https://cal.com/stakeholder/peer.jpg", + alt: "Peer", + title: "Peer Richelsen", + }, + { + image: "https://cal.com/stakeholder/bailey.jpg", + alt: "Bailey", + title: "Bailey Pumfleet", + }, + { + image: "https://cal.com/stakeholder/alex-van-andel.jpg", + alt: "Alex", + title: "Alex Van Andel", + }, + ]} + /> */} + <div className="border-muted absolute -bottom-0.5 -right-1 h-2 w-2 rounded-full border bg-green-500" /> + </div> + <div>{t("dont_want_to_wait")}</div> + </div> + <div className="mt-2 sm:mt-0"> + <Button + color="primary" + onClick={() => { + const newPath = `${pathname}?isInstantMeeting=true`; + router.push(newPath); + }} + size="sm" + className="w-full justify-center rounded-lg sm:w-auto"> + {t("connect_now")} + </Button> + </div> + </div> + ); +}; diff --git a/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx index f2d40e3654..e01b40ef1e 100644 --- a/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx +++ b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from "react"; +import { useRef } from "react"; import dayjs from "@calcom/dayjs"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; @@ -93,13 +93,6 @@ export const AvailableTimeSlots = ({ const slotsPerDay = useSlotsForAvailableDates(dates, schedule?.data?.slots); - useEffect(() => { - if (isEmbed) return; - if (containerRef.current && !schedule.isLoading && isMobile) { - containerRef.current.scrollIntoView({ behavior: "smooth", block: "center" }); - } - }, [containerRef, schedule.isLoading, isEmbed, isMobile]); - return ( <> <div className="flex"> diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx index ebbbdeeb45..18371c74ed 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx @@ -3,7 +3,7 @@ import type { UseMutationResult } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query"; import { useSession } from "next-auth/react"; import type { TFunction } from "next-i18next"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import type { FieldError } from "react-hook-form"; import { useForm } from "react-hook-form"; @@ -12,6 +12,7 @@ import { z } from "zod"; import type { EventLocationType } from "@calcom/app-store/locations"; import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client"; import dayjs from "@calcom/dayjs"; +import { updateQueryParam, getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param"; import { VerifyCodeDialog } from "@calcom/features/bookings/components/VerifyCodeDialog"; import { createBooking, @@ -19,16 +20,20 @@ import { mapBookingToMutationInput, mapRecurringBookingToMutationInput, useTimePreferences, + createInstantBooking, } from "@calcom/features/bookings/lib"; import getBookingResponsesSchema, { getBookingResponsesPartialSchema, } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; +import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner"; import { getFullName } from "@calcom/features/form-builder/utils"; import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; import { MINUTES_TO_BOOK } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; +import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { trpc } from "@calcom/trpc"; +import { Dialog, DialogContent } from "@calcom/ui"; import { Alert, Button, EmptyScreen, Form, showToast } from "@calcom/ui"; import { Calendar } from "@calcom/ui/components/icon"; @@ -65,6 +70,7 @@ export const BookEventForm = ({ onCancel, hashedLink }: BookEventFormProps) => { const bookingData = useBookerStore((state) => state.bookingData); const duration = useBookerStore((state) => state.selectedDuration); const timeslot = useBookerStore((state) => state.selectedTimeslot); + const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting); const isRescheduling = !!rescheduleUid && !!bookingData; const eventQuery = useEvent(); const eventType = eventQuery.data; @@ -115,6 +121,7 @@ export const BookEventForm = ({ onCancel, hashedLink }: BookEventFormProps) => { eventQuery={eventQuery} rescheduleUid={rescheduleUid} hashedLink={hashedLink} + isInstantMeeting={isInstantMeeting} /> ); }; @@ -126,12 +133,14 @@ export const BookEventFormChild = ({ eventQuery, rescheduleUid, hashedLink, + isInstantMeeting, }: BookEventFormProps & { initialValues: DefaultValues; isRescheduling: boolean; eventQuery: ReturnType<typeof useEvent>; rescheduleUid: string | null; hashedLink?: string | null; + isInstantMeeting?: boolean; }) => { const eventType = eventQuery.data; const bookingFormSchema = z @@ -164,6 +173,8 @@ export const BookEventFormChild = ({ const timeslot = useBookerStore((state) => state.selectedTimeslot); const recurringEventCount = useBookerStore((state) => state.recurringEventCount); const username = useBookerStore((state) => state.username); + const [expiryTime, setExpiryTime] = useState<Date | undefined>(); + type BookingFormValues = { locationType?: EventLocationType["type"]; responses: z.infer<typeof bookingFormSchema>["responses"] | null; @@ -189,7 +200,7 @@ export const BookEventFormChild = ({ const { uid, paymentUid } = responseData; const fullName = getFullName(bookingForm.getValues("responses.name")); if (paymentUid) { - return router.push( + router.push( createPaymentLink({ paymentUid, date: timeslot, @@ -214,7 +225,7 @@ export const BookEventFormChild = ({ isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined, }; - return bookingSuccessRedirect({ + bookingSuccessRedirect({ successRedirectUrl: eventType?.successRedirectUrl || "", query, booking: responseData, @@ -230,6 +241,18 @@ export const BookEventFormChild = ({ }, }); + const createInstantBookingMutation = useMutation(createInstantBooking, { + onSuccess: (responseData) => { + updateQueryParam("bookingId", responseData.bookingId); + setExpiryTime(responseData.expires); + }, + onError: (err, _, ctx) => { + console.error("Error creating instant booking", err); + + errorRef && errorRef.current?.scrollIntoView({ behavior: "smooth" }); + }, + }); + const createRecurringBookingMutation = useMutation(createRecurringBooking, { onSuccess: async (responseData) => { const booking = responseData[0] || {}; @@ -249,7 +272,7 @@ export const BookEventFormChild = ({ isRescheduling && bookingData?.startTime ? dayjs(bookingData.startTime).toString() : undefined, }; - return bookingSuccessRedirect({ + bookingSuccessRedirect({ successRedirectUrl: eventType?.successRedirectUrl || "", query, booking, @@ -302,10 +325,6 @@ export const BookEventFormChild = ({ ); const bookEvent = (values: BookingFormValues) => { - // Clears form values stored in store, so old values won't stick around. - setFormValues({}); - bookingForm.clearErrors(); - // It shouldn't be possible that this method is fired without having eventQuery data, // but since in theory (looking at the types) it is possible, we still handle that case. if (!eventQuery?.data) { @@ -315,12 +334,11 @@ export const BookEventFormChild = ({ // Ensures that duration is an allowed value, if not it defaults to the // default eventQuery duration. - const validDuration = - duration && - eventQuery.data.metadata?.multipleDuration && - eventQuery.data.metadata?.multipleDuration.includes(duration) - ? duration - : eventQuery.data.length; + const validDuration = eventQuery.data.isDynamic + ? duration || eventQuery.data.length + : duration && eventQuery.data.metadata?.multipleDuration?.includes(duration) + ? duration + : eventQuery.data.length; const bookingInput = { values, @@ -344,13 +362,18 @@ export const BookEventFormChild = ({ hashedLink, }; - if (eventQuery.data?.recurringEvent?.freq && recurringEventCount) { + if (isInstantMeeting) { + createInstantBookingMutation.mutate(mapBookingToMutationInput(bookingInput)); + } else if (eventQuery.data?.recurringEvent?.freq && recurringEventCount && !rescheduleUid) { createRecurringBookingMutation.mutate( mapRecurringBookingToMutationInput(bookingInput, recurringEventCount) ); } else { createBookingMutation.mutate(mapBookingToMutationInput(bookingInput)); } + // Clears form values stored in store, so old values won't stick around. + setFormValues({}); + bookingForm.clearErrors(); }; if (!eventType) { @@ -384,6 +407,7 @@ export const BookEventFormChild = ({ /> {(createBookingMutation.isError || createRecurringBookingMutation.isError || + createInstantBookingMutation.isError || bookingForm.formState.errors["globalError"]) && ( <div data-testid="booking-fail"> <Alert @@ -395,6 +419,7 @@ export const BookEventFormChild = ({ bookingForm.formState.errors["globalError"], createBookingMutation, createRecurringBookingMutation, + createInstantBookingMutation, t, responseVercelIdHeader )} @@ -402,22 +427,39 @@ export const BookEventFormChild = ({ </div> )} <div className="modalsticky mt-auto flex justify-end space-x-2 rtl:space-x-reverse"> - {!!onCancel && ( - <Button color="minimal" type="button" onClick={onCancel} data-testid="back"> - {t("back")} + {isInstantMeeting ? ( + <Button type="submit" color="primary" loading={createInstantBookingMutation.isLoading}> + {t("confirm")} </Button> + ) : ( + <> + {!!onCancel && ( + <Button color="minimal" type="button" onClick={onCancel} data-testid="back"> + {t("back")} + </Button> + )} + <Button + type="submit" + color="primary" + loading={ + bookingForm.formState.isSubmitting || + createBookingMutation.isLoading || + createRecurringBookingMutation.isLoading || + // A redirect is triggered on mutation success, so keep the button disabled as this is happening. + createBookingMutation.isSuccess || + createRecurringBookingMutation.isSuccess + } + data-testid={ + rescheduleUid && bookingData ? "confirm-reschedule-button" : "confirm-book-button" + }> + {rescheduleUid && bookingData + ? t("reschedule") + : renderConfirmNotVerifyEmailButtonCond + ? t("confirm") + : t("verify_email_email_button")} + </Button> + </> )} - <Button - type="submit" - color="primary" - loading={createBookingMutation.isLoading || createRecurringBookingMutation.isLoading} - data-testid={rescheduleUid && bookingData ? "confirm-reschedule-button" : "confirm-book-button"}> - {rescheduleUid && bookingData - ? t("reschedule") - : renderConfirmNotVerifyEmailButtonCond - ? t("confirm") - : t("verify_email_email_button")} - </Button> </div> </Form> <VerifyCodeDialog @@ -431,10 +473,75 @@ export const BookEventFormChild = ({ }} isUserSessionRequiredToVerify={false} /> + <RedirectToInstantMeetingModal expiryTime={expiryTime} /> </div> ); }; +const RedirectToInstantMeetingModal = ({ expiryTime }: { expiryTime?: Date }) => { + const { t } = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + const bookingId = parseInt(getQueryParam("bookingId") || "0"); + const hasInstantMeetingTokenExpired = expiryTime && new Date(expiryTime) < new Date(); + + const instantBooking = trpc.viewer.bookings.getInstantBookingLocation.useQuery( + { + bookingId: bookingId, + }, + { + enabled: !!bookingId && !hasInstantMeetingTokenExpired, + refetchInterval: 2000, + onSuccess: (data) => { + try { + showToast(t("something_went_wrong_on_our_end"), "error"); + + const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse( + data.booking?.metadata || {} + )?.videoCallUrl; + + if (locationVideoCallUrl) { + router.push(locationVideoCallUrl); + } else { + showToast(t("something_went_wrong_on_our_end"), "error"); + } + } catch (err) { + showToast(t("something_went_wrong_on_our_end"), "error"); + } + }, + } + ); + + return ( + <Dialog open={!!bookingId}> + <DialogContent enableOverflow className="py-8"> + <div> + {hasInstantMeetingTokenExpired ? ( + <div> + <p className="font-medium">{t("please_book_a_time_sometime_later")}</p> + <Button + className="mt-4" + onClick={() => { + // Prevent null on app directory + if (pathname) window.location.href = pathname; + }} + color="primary"> + {t("go_back")} + </Button> + </div> + ) : ( + <div> + <p className="font-medium">{t("connecting_you_to_someone")}</p> + <p className="font-medium">{t("please_do_not_close_this_tab")}</p> + <Spinner className="relative mt-8" /> + </div> + )} + </div> + </DialogContent> + </Dialog> + ); +}; + const getError = ( globalError: FieldError | undefined, // It feels like an implementation detail to reimplement the types of useMutation here. @@ -444,12 +551,13 @@ const getError = ( bookingMutation: UseMutationResult<any, any, any, any>, // eslint-disable-next-line @typescript-eslint/no-explicit-any recurringBookingMutation: UseMutationResult<any, any, any, any>, + createInstantBookingMutation: UseMutationResult<any, any, any, any>, t: TFunction, responseVercelIdHeader: string | null ) => { if (globalError) return globalError.message; - const error = bookingMutation.error || recurringBookingMutation.error; + const error = bookingMutation.error || recurringBookingMutation.error || createInstantBookingMutation.error; return error.message ? ( <> diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx index 42803e7ae2..1a754d3e6f 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx @@ -2,6 +2,7 @@ import { useFormContext } from "react-hook-form"; import type { LocationObject } from "@calcom/app-store/locations"; import { getOrganizerInputLocationTypes } from "@calcom/app-store/locations"; +import { useBookerStore } from "@calcom/features/bookings/Booker/store"; import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect"; import { FormBuilderField } from "@calcom/features/form-builder/FormBuilderField"; @@ -27,12 +28,16 @@ export const BookingFields = ({ const { watch, setValue } = useFormContext(); const locationResponse = watch("responses.location"); const currentView = rescheduleUid ? "reschedule" : ""; + const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting); return ( // TODO: It might make sense to extract this logic into BookingFields config, that would allow to quickly configure system fields and their editability in fresh booking and reschedule booking view // The logic here intends to make modifications to booking fields based on the way we want to specifically show Booking Form <div> {fields.map((field, index) => { + // Don't Display Location field in case of instant meeting as only Cal Video is supported + if (isInstantMeeting && field.name === "location") return null; + // During reschedule by default all system fields are readOnly. Make them editable on case by case basis. // Allowing a system field to be edited might require sending emails to attendees, so we need to be careful let readOnly = diff --git a/packages/features/bookings/Booker/components/EventMeta.tsx b/packages/features/bookings/Booker/components/EventMeta.tsx index f282e48d5e..3cb6dab61b 100644 --- a/packages/features/bookings/Booker/components/EventMeta.tsx +++ b/packages/features/bookings/Booker/components/EventMeta.tsx @@ -15,9 +15,12 @@ import { useBookerStore } from "../store"; import { FromToTime } from "../utils/dates"; import { useEvent } from "../utils/event"; -const TimezoneSelect = dynamic(() => import("@calcom/ui").then((mod) => mod.TimezoneSelect), { - ssr: false, -}); +const TimezoneSelect = dynamic( + () => import("@calcom/ui/components/form/timezone-select/TimezoneSelect").then((mod) => mod.TimezoneSelect), + { + ssr: false, + } +); export const EventMeta = () => { const { setTimezone, timeFormat, timezone } = useTimePreferences(); @@ -57,7 +60,7 @@ export const EventMeta = () => { : "text-bookinghighlight"; return ( - <div className="relative z-10 p-6"> + <div className="relative z-10 p-6" data-testid="event-meta"> {isLoading && ( <m.div {...fadeInUp} initial="visible" layout> <EventMetaSkeleton /> diff --git a/packages/features/bookings/Booker/store.ts b/packages/features/bookings/Booker/store.ts index 04a995edca..da950c23fa 100644 --- a/packages/features/bookings/Booker/store.ts +++ b/packages/features/bookings/Booker/store.ts @@ -27,6 +27,7 @@ type StoreInitializeType = { seatReferenceUid?: string; durationConfig?: number[] | null; org?: string | null; + isInstantMeeting?: boolean; }; type SeatedEventData = { @@ -131,6 +132,8 @@ export type BookerStore = { org?: string | null; seatedEventData: SeatedEventData; setSeatedEventData: (seatedEventData: SeatedEventData) => void; + + isInstantMeeting?: boolean; }; /** @@ -225,6 +228,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({ isTeamEvent, durationConfig, org, + isInstantMeeting, }: StoreInitializeType) => { const selectedDateInStore = get().selectedDate; @@ -256,14 +260,12 @@ export const useBookerStore = create<BookerStore>((set, get) => ({ (["week_view", "column_view"].includes(layout) ? dayjs().format("YYYY-MM-DD") : null), }); - if (eventId) { - if (durationConfig?.includes(Number(getQueryParam("duration")))) { - set({ - selectedDuration: Number(getQueryParam("duration")), - }); - } else { - removeQueryParam("duration"); - } + if (durationConfig?.includes(Number(getQueryParam("duration")))) { + set({ + selectedDuration: Number(getQueryParam("duration")), + }); + } else { + removeQueryParam("duration"); } // Unset selected timeslot if user is rescheduling. This could happen @@ -272,6 +274,21 @@ export const useBookerStore = create<BookerStore>((set, get) => ({ // force clear this. if (rescheduleUid && bookingData) set({ selectedTimeslot: null }); if (month) set({ month }); + + if (isInstantMeeting) { + const month = dayjs().format("YYYY-MM"); + const selectedDate = dayjs().format("YYYY-MM-DD"); + const selectedTimeslot = new Date().toISOString(); + set({ + month, + selectedDate, + selectedTimeslot, + isInstantMeeting, + }); + updateQueryParam("month", month); + updateQueryParam("date", selectedDate ?? ""); + updateQueryParam("slot", selectedTimeslot ?? ""); + } //removeQueryParam("layout"); }, durationConfig: null, @@ -310,6 +327,7 @@ export const useInitializeBookerStore = ({ isTeamEvent, durationConfig, org, + isInstantMeeting, }: StoreInitializeType) => { const initializeStore = useBookerStore((state) => state.initialize); useEffect(() => { @@ -325,6 +343,7 @@ export const useInitializeBookerStore = ({ org, verifiedEmail, durationConfig, + isInstantMeeting, }); }, [ initializeStore, @@ -339,5 +358,6 @@ export const useInitializeBookerStore = ({ isTeamEvent, verifiedEmail, durationConfig, + isInstantMeeting, ]); }; diff --git a/packages/features/bookings/Booker/types.ts b/packages/features/bookings/Booker/types.ts index badfe667c8..cde630fb1c 100644 --- a/packages/features/bookings/Booker/types.ts +++ b/packages/features/bookings/Booker/types.ts @@ -64,10 +64,15 @@ export interface BookerProps { * otherwise, the default value is selected */ duration?: number | null; + /** + * Configures the selectable options for a multiDuration event type. + */ + durationConfig?: number[]; /** * Refers to the private link from event types page. */ hashedLink?: string | null; + isInstantMeeting?: boolean; } export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking"; diff --git a/packages/features/bookings/BookingMultiFiltersStore.ts b/packages/features/bookings/BookingMultiFiltersStore.ts deleted file mode 100644 index bfbb9ae646..0000000000 --- a/packages/features/bookings/BookingMultiFiltersStore.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { create } from "zustand"; - -import type { SVGComponent } from "@calcom/types/SVGComponent"; -import { User, Link } from "@calcom/ui/components/icon"; - -interface addFilterOption { - StartIcon?: SVGComponent; - label: "people" | "event_type" | "date_range" | "location"; -} - -export type BookingMultiFilterStore = { - addFilterOptions: addFilterOption[]; - toggleOption: (option: addFilterOption) => void; - isFilterActive: (label: addFilterOption["label"]) => boolean; -}; - -export const useBookingMultiFilterStore = create<BookingMultiFilterStore>((set, get) => ({ - addFilterOptions: [ - { - label: "people", - StartIcon: User, - }, - { - label: "event_type", - StartIcon: Link, - }, - // { - // label: "date_range", - // StartIcon: Calendar, - // }, - // { - // label: "location", - // StartIcon: MapPin, - // }, - ], - isFilterActive: (label: addFilterOption["label"]) => { - return !get().addFilterOptions.some((option) => option.label === label); - }, - setState: (state: BookingMultiFilterStore) => set(state), - toggleOption: (option: addFilterOption) => { - const availableOptions = get().addFilterOptions; - const foundOption = availableOptions.find((activeOption) => activeOption.label === option.label); - - if (foundOption) { - set({ - addFilterOptions: availableOptions.filter((activeOption) => activeOption.label !== foundOption.label), - }); - } else { - set({ addFilterOptions: [...availableOptions, option] }); - } - }, -})); diff --git a/packages/features/bookings/components/EventTypeFilter.tsx b/packages/features/bookings/components/EventTypeFilter.tsx index 9b1781573c..6d44a8944e 100644 --- a/packages/features/bookings/components/EventTypeFilter.tsx +++ b/packages/features/bookings/components/EventTypeFilter.tsx @@ -1,11 +1,16 @@ import { useSession } from "next-auth/react"; import { Fragment, useState } from "react"; +import { + FilterCheckboxFieldsContainer, + FilterCheckboxField, +} from "@calcom/features/filters/components/TeamsFilter"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { AnimatedPopover } from "@calcom/ui"; -import { CheckboxField } from "@calcom/ui"; +import { Divider } from "@calcom/ui"; +import { Link } from "@calcom/ui/components/icon"; import { groupBy } from "../groupBy"; import { useFilterQuery } from "../lib/useFilterQuery"; @@ -29,7 +34,7 @@ type GroupedEventTypeState = Record< export const EventTypeFilter = () => { const { t } = useLocale(); const { data: user } = useSession(); - const { data: query, pushItemToKey, removeItemByKeyAndValue } = useFilterQuery(); + const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery(); const [groupedEventTypes, setGroupedEventTypes] = useState<GroupedEventTypeState>(); @@ -56,24 +61,32 @@ export const EventTypeFilter = () => { const getTextForPopover = () => { const eventTypeIds = query.eventTypeIds; if (eventTypeIds) { - return `${t("event_type")}: ${t("number_selected", { count: eventTypeIds.length })}`; + return `${t("number_selected", { count: eventTypeIds.length })}`; } - return `${t("event_type")}: ${t("all")}`; + return `${t("all")}`; }; return ( - <AnimatedPopover text={getTextForPopover()}> - <div> - {groupedEventTypes && - !isEmpty && - Object.keys(groupedEventTypes).map((teamName) => ( - <Fragment key={teamName}> - <div className="text-subtle px-4 py-2 text-xs font-medium uppercase leading-none"> - {teamName === "user_own_event_types" ? t("individual") : teamName} - </div> - {groupedEventTypes[teamName].map((eventType) => ( - <div key={eventType.id} className="flex items-center px-4 py-1.5"> - <CheckboxField + <AnimatedPopover text={getTextForPopover()} prefix={`${t("event_type")}: `}> + {!isEmpty ? ( + <FilterCheckboxFieldsContainer> + <FilterCheckboxField + id="all" + icon={<Link className="h-4 w-4" />} + checked={!query.eventTypeIds?.length} + onChange={removeAllQueryParams} + label={t("all_event_types_filter_label")} + /> + <Divider /> + {groupedEventTypes && + Object.keys(groupedEventTypes).map((teamName) => ( + <Fragment key={teamName}> + <div className="text-subtle px-4 py-2 text-xs font-medium uppercase leading-none"> + {teamName === "user_own_event_types" ? t("individual") : teamName} + </div> + {groupedEventTypes[teamName].map((eventType) => ( + <FilterCheckboxField + key={eventType.id} checked={query.eventTypeIds?.includes(eventType.id)} onChange={(e) => { if (e.target.checked) { @@ -82,16 +95,15 @@ export const EventTypeFilter = () => { removeItemByKeyAndValue("eventTypeIds", eventType.id); } }} - description={eventType.title} + label={eventType.title} /> - </div> - ))} - </Fragment> - ))} - {isEmpty && ( - <h2 className="text-default px-4 py-2 text-sm font-medium">{t("no_options_available")}</h2> - )} - </div> + ))} + </Fragment> + ))} + </FilterCheckboxFieldsContainer> + ) : ( + <h2 className="text-default px-4 py-2 text-sm font-medium">{t("no_options_available")}</h2> + )} </AnimatedPopover> ); }; diff --git a/packages/features/bookings/components/FilterToggle.tsx b/packages/features/bookings/components/FilterToggle.tsx new file mode 100644 index 0000000000..32ef451a4f --- /dev/null +++ b/packages/features/bookings/components/FilterToggle.tsx @@ -0,0 +1,35 @@ +import type { Dispatch, SetStateAction } from "react"; + +import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Tooltip, Badge, Button } from "@calcom/ui"; +import { Filter } from "@calcom/ui/components/icon"; + +export interface FilterToggleProps { + setIsFiltersVisible: Dispatch<SetStateAction<boolean>>; +} + +export function FilterToggle({ setIsFiltersVisible }: FilterToggleProps) { + const { + data: { teamIds, userIds, eventTypeIds }, + } = useFilterQuery(); + const { t } = useLocale(); + + function toggleFiltersVisibility() { + setIsFiltersVisible((prev) => !prev); + } + + return ( + <Button color="secondary" onClick={toggleFiltersVisibility} className="mb-4"> + <Filter className="h-4 w-4" /> + <Tooltip content={t("filters")}> + <div className="mx-2">{t("filters")}</div> + </Tooltip> + {(teamIds || userIds || eventTypeIds) && ( + <Badge variant="gray" rounded> + {(teamIds ? 1 : 0) + (userIds ? 1 : 0) + (eventTypeIds ? 1 : 0)} + </Badge> + )} + </Button> + ); +} diff --git a/packages/features/bookings/components/FiltersContainer.tsx b/packages/features/bookings/components/FiltersContainer.tsx index ee25281eaa..1c3c19fe4f 100644 --- a/packages/features/bookings/components/FiltersContainer.tsx +++ b/packages/features/bookings/components/FiltersContainer.tsx @@ -1,142 +1,41 @@ -import { useState } from "react"; -import { shallow } from "zustand/shallow"; +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { PeopleFilter } from "@calcom/features/bookings/components/PeopleFilter"; import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; -import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; -import { - TeamsFilter, - FilterCheckboxFieldsContainer, - FilterCheckboxField, -} from "@calcom/features/filters/components/TeamsFilter"; -import { WEBAPP_URL } from "@calcom/lib/constants"; +import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { trpc } from "@calcom/trpc/react"; -import { - Dropdown, - DropdownMenuContent, - DropdownMenuItem, - DropdownItem, - DropdownMenuTrigger, - AnimatedPopover, - Avatar, - FilterSearchField, - Tooltip, -} from "@calcom/ui"; -import { Plus } from "@calcom/ui/components/icon"; +import { Tooltip, Button } from "@calcom/ui"; -import { useBookingMultiFilterStore } from "../BookingMultiFiltersStore"; import { EventTypeFilter } from "./EventTypeFilter"; -const PeopleFilter = () => { - const { t } = useLocale(); - const orgBranding = useOrgBranding(); +export interface FiltersContainerProps { + isFiltersVisible: boolean; +} - const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey } = useFilterQuery(); - const [searchText, setSearchText] = useState(""); - - const members = trpc.viewer.teams.listMembers.useQuery({}); - - const filteredMembers = members?.data - ?.filter((member) => member.accepted) - ?.filter((member) => - searchText.trim() !== "" - ? member?.name?.toLowerCase()?.includes(searchText.toLowerCase()) || - member?.username?.toLowerCase()?.includes(searchText.toLowerCase()) - : true - ); - - const getTextForPopover = () => { - const userIds = query.userIds; - if (userIds) { - return `${t("people")}: ${t("number_selected", { count: userIds.length })}`; - } - return `${t("people")}: ${t("all")}`; - }; - - return ( - <AnimatedPopover text={getTextForPopover()}> - <FilterSearchField onChange={(e) => setSearchText(e.target.value)} placeholder={t("search")} /> - <FilterCheckboxFieldsContainer> - {filteredMembers?.map((member) => ( - <FilterCheckboxField - key={member.id} - id={member.id.toString()} - label={member?.name ?? member.username ?? t("no_name")} - checked={!!query.userIds?.includes(member.id)} - onChange={(e) => { - if (e.target.checked) { - pushItemToKey("userIds", member.id); - } else if (!e.target.checked) { - removeItemByKeyAndValue("userIds", member.id); - } - }} - icon={ - <Avatar - alt={`${member?.id} avatar`} - imageSrc={ - member.username - ? `${orgBranding?.fullDomain ?? WEBAPP_URL}/${member.username}/avatar.png` - : undefined - } - size="xs" - /> - } - /> - ))} - {filteredMembers?.length === 0 && ( - <h2 className="text-default px-4 py-2 text-sm font-medium">{t("no_options_available")}</h2> - )} - </FilterCheckboxFieldsContainer> - </AnimatedPopover> - ); -}; - -export function FiltersContainer() { +export function FiltersContainer({ isFiltersVisible }: FiltersContainerProps) { + const [animationParentRef] = useAutoAnimate<HTMLDivElement>(); + const { removeAllQueryParams } = useFilterQuery(); const { t } = useLocale(); - const [addFilterOptions, toggleOption, isFilterActive] = useBookingMultiFilterStore((state) => [ - state.addFilterOptions, - state.toggleOption, - state.isFilterActive, - shallow, - ]); - - const isPeopleFilterActive = isFilterActive("people"); - const isEventTypeFilterActive = isFilterActive("event_type"); - return ( - <div className="flex w-full space-x-2 rtl:space-x-reverse"> - {addFilterOptions.length > 0 && ( - <Dropdown> - <DropdownMenuTrigger asChild> - <div className="hover:border-emphasis border-default text-default hover:text-emphasis mb-4 flex h-9 max-h-72 items-center justify-between whitespace-nowrap rounded-md border px-3 py-2 text-sm hover:cursor-pointer focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1"> - <Plus className="mr-2 h-4 w-4" /> - <Tooltip content={t("add_filter")}> - <div>{t("add_filter")}</div> - </Tooltip> - </div> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-56"> - {addFilterOptions?.map((option) => ( - <DropdownMenuItem key={option.label}> - <DropdownItem - type="button" - StartIcon={option.StartIcon} - onClick={() => { - toggleOption(option); - }}> - {t(option.label)} - </DropdownItem> - </DropdownMenuItem> - ))} - </DropdownMenuContent> - </Dropdown> - )} - - {isPeopleFilterActive && <PeopleFilter />} - {isEventTypeFilterActive && <EventTypeFilter />} - - <TeamsFilter /> + <div ref={animationParentRef}> + {isFiltersVisible ? ( + <div className="no-scrollbar flex w-full space-x-2 overflow-x-scroll rtl:space-x-reverse"> + <PeopleFilter /> + <EventTypeFilter /> + <TeamsFilter /> + <Tooltip content={t("remove_filters")}> + <Button + color="secondary" + type="button" + onClick={() => { + removeAllQueryParams(); + }}> + {t("remove_filters")} + </Button> + </Tooltip> + </div> + ) : null} </div> ); } diff --git a/packages/features/bookings/components/PeopleFilter.tsx b/packages/features/bookings/components/PeopleFilter.tsx new file mode 100644 index 0000000000..f5a1666a5a --- /dev/null +++ b/packages/features/bookings/components/PeopleFilter.tsx @@ -0,0 +1,85 @@ +import { useState } from "react"; + +import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; +import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; +import { + FilterCheckboxFieldsContainer, + FilterCheckboxField, +} from "@calcom/features/filters/components/TeamsFilter"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import { AnimatedPopover, Avatar, Divider, FilterSearchField } from "@calcom/ui"; +import { User } from "@calcom/ui/components/icon"; + +export const PeopleFilter = () => { + const { t } = useLocale(); + const orgBranding = useOrgBranding(); + + const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery(); + const [searchText, setSearchText] = useState(""); + + const members = trpc.viewer.teams.listMembers.useQuery({}); + + const filteredMembers = members?.data + ?.filter((member) => member.accepted) + ?.filter((member) => + searchText.trim() !== "" + ? member?.name?.toLowerCase()?.includes(searchText.toLowerCase()) || + member?.username?.toLowerCase()?.includes(searchText.toLowerCase()) + : true + ); + + const getTextForPopover = () => { + const userIds = query.userIds; + if (userIds) { + return `${t("number_selected", { count: userIds.length })}`; + } + return `${t("all")}`; + }; + + return ( + <AnimatedPopover text={getTextForPopover()} prefix={`${t("people")}: `}> + <FilterCheckboxFieldsContainer> + <FilterCheckboxField + id="all" + icon={<User className="h-4 w-4" />} + checked={!query.userIds?.length} + onChange={removeAllQueryParams} + label={t("all_users_filter_label")} + /> + <Divider /> + <FilterSearchField onChange={(e) => setSearchText(e.target.value)} placeholder={t("search")} /> + {filteredMembers?.map((member) => ( + <FilterCheckboxField + key={member.id} + id={member.id.toString()} + label={member?.name ?? member.username ?? t("no_name")} + checked={!!query.userIds?.includes(member.id)} + onChange={(e) => { + if (e.target.checked) { + pushItemToKey("userIds", member.id); + } else if (!e.target.checked) { + removeItemByKeyAndValue("userIds", member.id); + } + }} + icon={ + <Avatar + alt={`${member?.id} avatar`} + imageSrc={ + member.username + ? `${orgBranding?.fullDomain ?? WEBAPP_URL}/${member.username}/avatar.png` + : undefined + } + size="xs" + /> + } + /> + ))} + {filteredMembers?.length === 0 && ( + <h2 className="text-default px-4 py-2 text-sm font-medium">{t("no_options_available")}</h2> + )} + </FilterCheckboxFieldsContainer> + </AnimatedPopover> + ); +}; diff --git a/packages/features/bookings/components/VerifyCodeDialog.docs.mdx b/packages/features/bookings/components/VerifyCodeDialog.docs.mdx new file mode 100644 index 0000000000..ab7d72009d --- /dev/null +++ b/packages/features/bookings/components/VerifyCodeDialog.docs.mdx @@ -0,0 +1,24 @@ +import { Canvas, Meta } from "@storybook/blocks"; + +import { Title, CustomArgsTable } from "@calcom/storybook/components"; + +import { VerifyCodeDialog } from "./VerifyCodeDialog"; +import * as VerifyCodeDialogStories from "./VerifyCodeDialog.stories"; + +<Meta of={VerifyCodeDialogStories} /> + +<Title title="VerifyCodeDialog" suffix="Brief" subtitle="Version 1.0 — Last Update: 11 Sep 2023" /> + +## Definition + +The `VerifyCodeDialog` component allows users to enter a verification code sent to their email. The component provides feedback in case of an error and can handle different verification processes depending on whether the user session is required. + +## Structure + +This component contains an input form to capture a 6-digit verification code, error handling UI, and action buttons. + +<CustomArgsTable of={VerifyCodeDialog} /> + +{/* ## VerifyCodeDialog Story + +<Canvas of={VerifyCodeDialogStories.Default} /> */} diff --git a/packages/features/bookings/components/VerifyCodeDialog.stories.tsx b/packages/features/bookings/components/VerifyCodeDialog.stories.tsx new file mode 100644 index 0000000000..d58e7604ae --- /dev/null +++ b/packages/features/bookings/components/VerifyCodeDialog.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import type { ComponentProps } from "react"; + +import { StorybookTrpcProvider } from "@calcom/ui"; + +import { VerifyCodeDialog } from "./VerifyCodeDialog"; + +type StoryArgs = ComponentProps<typeof VerifyCodeDialog>; + +const meta: Meta<StoryArgs> = { + component: VerifyCodeDialog, + title: "Features/VerifyCodeDialog", + argTypes: { + isOpenDialog: { control: "boolean", description: "Indicates whether the dialog is open or not." }, + setIsOpenDialog: { action: "setIsOpenDialog", description: "Function to set the dialog state." }, + email: { control: "text", description: "Email to which the verification code was sent." }, + onSuccess: { action: "onSuccess", description: "Callback function when verification succeeds." }, + // onError: { action: "onError", description: "Callback function when verification fails." }, + isUserSessionRequiredToVerify: { + control: "boolean", + description: "Indicates if user session is required for verification.", + }, + }, + decorators: [ + (Story) => ( + <StorybookTrpcProvider> + <Story /> + </StorybookTrpcProvider> + ), + ], + render: (args) => <VerifyCodeDialog {...args} />, +}; + +export default meta; +type Story = StoryObj<StoryArgs>; + +export const Default: Story = { + name: "Dialog", + args: { + isOpenDialog: true, + email: "example@email.com", + // onError: (err) => { + // if (err.message === "invalid_code") { + // alert("Code provided is invalid"); + // } + // }, + }, +}; diff --git a/packages/features/bookings/components/VerifyCodeDialog.tsx b/packages/features/bookings/components/VerifyCodeDialog.tsx index ffa7077aaf..8c8eece806 100644 --- a/packages/features/bookings/components/VerifyCodeDialog.tsx +++ b/packages/features/bookings/components/VerifyCodeDialog.tsx @@ -1,5 +1,5 @@ import type { Dispatch, SetStateAction } from "react"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import useDigitInput from "react-digit-input"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -33,13 +33,18 @@ export const VerifyCodeDialog = ({ // Not using the mutation isLoading flag because after verifying we submit the underlying org creation form const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(""); - const [value, onChange] = useState(""); + const [value, setValue] = useState(""); + const [hasVerified, setHasVerified] = useState(false); const digits = useDigitInput({ acceptedCharacters: /^[0-9]$/, length: 6, value, - onChange, + onChange: useCallback((value: string) => { + // whenever there's a change in the input, we reset the error value. + setError(""); + setValue(value); + }, []), }); const verifyCodeMutationUserSessionRequired = trpc.viewer.organizations.verifyCode.useMutation({ @@ -49,6 +54,7 @@ export const VerifyCodeDialog = ({ }, onError: (err) => { setIsLoading(false); + setHasVerified(false); if (err.message === "invalid_code") { setError(t("code_provided_invalid")); } @@ -62,13 +68,46 @@ export const VerifyCodeDialog = ({ }, onError: (err) => { setIsLoading(false); + setHasVerified(false); if (err.message === "invalid_code") { setError(t("code_provided_invalid")); } }, }); - useEffect(() => onChange(""), [isOpenDialog]); + const verifyCode = useCallback(() => { + setError(""); + setIsLoading(true); + if (isUserSessionRequiredToVerify) { + verifyCodeMutationUserSessionRequired.mutate({ + code: value, + email, + }); + } else { + verifyCodeMutationUserSessionNotRequired.mutate({ + code: value, + email, + }); + } + setHasVerified(true); + }, [ + email, + isUserSessionRequiredToVerify, + value, + verifyCodeMutationUserSessionNotRequired, + verifyCodeMutationUserSessionRequired, + ]); + + useEffect(() => { + // trim the input value because "react-digit-input" creates a string of the given length, + // even when some digits are missing. And finally we use regex to check if the value consists + // of 6 non-empty digits. + if (hasVerified || error || isLoading || !/^\d{6}$/.test(value.trim())) return; + + verifyCode(); + }, [error, isLoading, value, verifyCode, hasVerified]); + + useEffect(() => setValue(""), [isOpenDialog]); const digitClassName = "h-12 w-12 !text-xl text-center"; @@ -76,7 +115,7 @@ export const VerifyCodeDialog = ({ <Dialog open={isOpenDialog} onOpenChange={(open) => { - onChange(""); + setValue(""); setError(""); setIsOpenDialog(open); }}> @@ -110,29 +149,8 @@ export const VerifyCodeDialog = ({ )} <DialogFooter> <DialogClose /> - <Button - loading={isLoading} - disabled={isLoading} - onClick={() => { - setError(""); - if (value === "") { - setError("The code is a required field"); - } else { - setIsLoading(true); - if (isUserSessionRequiredToVerify) { - verifyCodeMutationUserSessionRequired.mutate({ - code: value, - email, - }); - } else { - verifyCodeMutationUserSessionNotRequired.mutate({ - code: value, - email, - }); - } - } - }}> - {t("verify")} + <Button type="submit" onClick={verifyCode} loading={isLoading}> + {t("submit")} </Button> </DialogFooter> </div> diff --git a/packages/features/bookings/components/event-meta/Details.tsx b/packages/features/bookings/components/event-meta/Details.tsx index ee7b9f3017..ead5e44f32 100644 --- a/packages/features/bookings/components/event-meta/Details.tsx +++ b/packages/features/bookings/components/event-meta/Details.tsx @@ -1,6 +1,7 @@ import { Fragment } from "react"; import React from "react"; +import { useBookerStore } from "@calcom/features/bookings/Booker/store"; import classNames from "@calcom/lib/classNames"; import getPaymentAppData from "@calcom/lib/getPaymentAppData"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -110,6 +111,8 @@ export const EventMetaBlock = ({ */ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: EventDetailsProps) => { const { t } = useLocale(); + const rescheduleUid = useBookerStore((state) => state.rescheduleUid); + const isInstantMeeting = useBookerStore((store) => store.isInstantMeeting); return ( <> @@ -127,7 +130,7 @@ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: Even ); case EventDetailBlocks.LOCATION: - if (!event?.locations?.length) return null; + if (!event?.locations?.length || isInstantMeeting) return null; return ( <EventMetaBlock key={block}> <AvailableEventLocations locations={event.locations} /> @@ -144,7 +147,7 @@ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: Even ); case EventDetailBlocks.OCCURENCES: - if (!event.recurringEvent) return null; + if (!event.recurringEvent || rescheduleUid) return null; return ( <EventMetaBlock key={block} icon={RefreshCcw}> diff --git a/packages/features/bookings/components/event-meta/Duration.tsx b/packages/features/bookings/components/event-meta/Duration.tsx index eda63e6e95..8d22ccaa1d 100644 --- a/packages/features/bookings/components/event-meta/Duration.tsx +++ b/packages/features/bookings/components/event-meta/Duration.tsx @@ -1,3 +1,4 @@ +import type { TFunction } from "next-i18next"; import { useEffect } from "react"; import { useBookerStore } from "@calcom/features/bookings/Booker/store"; @@ -7,6 +8,31 @@ import { Badge } from "@calcom/ui"; import type { PublicEvent } from "../../types"; +/** Render X mins as X hours or X hours Y mins instead of in minutes once >= 60 minutes */ +const getDurationFormatted = (mins: number, t: TFunction) => { + const hours = Math.floor(mins / 60); + mins %= 60; + // format minutes string + let minStr = ""; + if (mins > 0) { + minStr = + mins === 1 + ? t("minute_one", { count: 1 }) + : t("multiple_duration_timeUnit", { count: mins, unit: "minute" }); + } + // format hours string + let hourStr = ""; + if (hours > 0) { + hourStr = + hours === 1 + ? t("hour_one", { count: 1 }) + : t("multiple_duration_timeUnit", { count: hours, unit: "hour" }); + } + + if (hourStr && minStr) return `${hourStr} ${minStr}`; + return hourStr || minStr; +}; + export const EventDuration = ({ event }: { event: PublicEvent }) => { const { t } = useLocale(); const [selectedDuration, setSelectedDuration, state] = useBookerStore((state) => [ @@ -25,7 +51,7 @@ export const EventDuration = ({ event }: { event: PublicEvent }) => { }, [selectedDuration, setSelectedDuration, event.metadata?.multipleDuration, event.length, isDynamicEvent]); if (!event?.metadata?.multipleDuration && !isDynamicEvent) - return <>{t("multiple_duration_mins", { count: event.length })}</>; + return <>{getDurationFormatted(event.length, t)}</>; const durations = event?.metadata?.multipleDuration || [15, 30, 60]; @@ -39,7 +65,9 @@ export const EventDuration = ({ event }: { event: PublicEvent }) => { className={classNames(selectedDuration === duration && "bg-brand-default text-brand")} size="md" key={duration} - onClick={() => setSelectedDuration(duration)}>{`${duration} ${t("minute_timeUnit")}`}</Badge> + onClick={() => setSelectedDuration(duration)}> + {getDurationFormatted(duration, t)} + </Badge> ))} </div> ); diff --git a/packages/features/bookings/components/event-meta/EventMeta.docs.mdx b/packages/features/bookings/components/event-meta/EventMeta.docs.mdx new file mode 100644 index 0000000000..7740319317 --- /dev/null +++ b/packages/features/bookings/components/event-meta/EventMeta.docs.mdx @@ -0,0 +1,13 @@ +import { Canvas, Meta } from "@storybook/blocks"; + +import { Title } from "@calcom/storybook/components"; + +import * as EventMetaStories from "./EventMeta.stories"; + +<Meta of={EventMetaStories} /> + +<Title title="Event Meta" suffix="Brief" subtitle="Version 2.0 — Last Update: 12 Dec 2022" /> + +<Canvas of={EventMetaStories.ExampleStory} /> + +<Canvas of={EventMetaStories.AllVariants} /> diff --git a/packages/features/bookings/components/event-meta/EventMeta.stories.mdx b/packages/features/bookings/components/event-meta/EventMeta.stories.mdx deleted file mode 100644 index faa385184c..0000000000 --- a/packages/features/bookings/components/event-meta/EventMeta.stories.mdx +++ /dev/null @@ -1,52 +0,0 @@ -import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; - -import { - Examples, - Example, - Note, - Title, - VariantsTable, - VariantColumn, - RowTitles, - CustomArgsTable, -} from "@calcom/storybook/components"; -import { Icon } from "@calcom/ui"; - -import { EventDetails } from "./Details"; -import { EventMembers } from "./Members"; -import { EventTitle } from "./Title"; -import { mockEvent } from "./event.mock.ts"; - -<Meta title="Features/Events/Meta" component={EventDetails} /> - -<Title title="Event Meta" suffix="Brief" subtitle="Version 2.0 — Last Update: 12 Dec 2022" /> - -<Examples title="Combined event meta block"> - <div style={{ maxWidth: 300 }}> - <Example title="Event Title"> - <EventTitle event={mockEvent} /> - </Example> - <Example title="Event Details"> - <EventDetails event={mockEvent} /> - </Example> - </div> -</Examples> - -<Canvas> - <Story name="All variants"> - <VariantsTable titles={["Event Meta Components"]} columnMinWidth={150}> - <VariantRow variant=""> - <div style={{ maxWidth: 300 }}> - <EventMembers - users={[ - { name: "Pro example", username: "pro" }, - { name: "Team example", username: "team" }, - ]} - /> - <EventTitle>Quick catch-up</EventTitle> - <EventDetails event={mockEvent} /> - </div> - </VariantRow> - </VariantsTable> - </Story> -</Canvas> diff --git a/packages/features/bookings/components/event-meta/EventMeta.stories.tsx b/packages/features/bookings/components/event-meta/EventMeta.stories.tsx new file mode 100644 index 0000000000..9bb5d2c284 --- /dev/null +++ b/packages/features/bookings/components/event-meta/EventMeta.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import type { PublicEvent } from "bookings/types"; +import type { ComponentProps } from "react"; + +import { Examples, Example, VariantsTable, VariantRow } from "@calcom/storybook/components"; + +import { EventDetails } from "./Details"; +import { EventMembers } from "./Members"; +import { EventTitle } from "./Title"; +import { mockEvent } from "./event.mock"; + +type StoryArgs = ComponentProps<typeof EventDetails>; + +const meta: Meta<StoryArgs> = { + component: EventDetails, + parameters: { + nextjs: { + appDirectory: true, + }, + }, + title: "Features/Events/Meta", +}; + +export default meta; +type Story = StoryObj<StoryArgs>; + +export const ExampleStory: Story = { + name: "Examples", + render: () => ( + <Examples title="Combined event meta block"> + <div style={{ maxWidth: 300 }}> + <Example title="Event Title"> + <EventTitle>{mockEvent.title}</EventTitle> + </Example> + <Example title="Event Details"> + <EventDetails event={mockEvent} /> + </Example> + </div> + </Examples> + ), +}; + +export const AllVariants: Story = { + name: "All variants", + render: () => ( + <VariantsTable titles={["Event Meta Components"]} columnMinWidth={150}> + <VariantRow variant=""> + <div style={{ maxWidth: 300 }}> + <EventMembers + users={mockEvent.users} + schedulingType="COLLECTIVE" + entity={{ isUnpublished: false, name: "Example", orgSlug: null }} + // TODO remove type assertion + profile={{ weekStart: "Sunday" } as PublicEvent["profile"]} + /> + <EventTitle>Quick catch-up</EventTitle> + <EventDetails event={mockEvent} /> + </div> + </VariantRow> + </VariantsTable> + ), +}; diff --git a/packages/features/bookings/components/event-meta/Occurences.tsx b/packages/features/bookings/components/event-meta/Occurences.tsx index fbed0add18..38cbdadcdd 100644 --- a/packages/features/bookings/components/event-meta/Occurences.tsx +++ b/packages/features/bookings/components/event-meta/Occurences.tsx @@ -47,7 +47,7 @@ export const EventOccurences = ({ event }: { event: PublicEvent }) => { i18n.language ); return ( - <> + <div data-testid="recurring-dates"> {recurringStrings.slice(0, 5).map((timeFormatted, key) => ( <p key={key}>{timeFormatted}</p> ))} @@ -59,7 +59,7 @@ export const EventOccurences = ({ event }: { event: PublicEvent }) => { <p className=" text-sm">+ {t("plus_more", { count: recurringStrings.length - 5 })}</p> </Tooltip> )} - </> + </div> ); } @@ -73,6 +73,7 @@ export const EventOccurences = ({ event }: { event: PublicEvent }) => { min="1" max={event.recurringEvent.count} defaultValue={occurenceCount || event.recurringEvent.count} + data-testid="occurrence-input" onChange={(event) => { const pattern = /^(?=.*[0-9])\S+$/; const inputValue = parseInt(event.target.value); diff --git a/packages/features/bookings/components/event-meta/Title.tsx b/packages/features/bookings/components/event-meta/Title.tsx index a147b8cfc2..7046a0185a 100644 --- a/packages/features/bookings/components/event-meta/Title.tsx +++ b/packages/features/bookings/components/event-meta/Title.tsx @@ -11,5 +11,9 @@ interface EventTitleProps { export const EventTitle = ({ children, as, className }: EventTitleProps) => { const El = as || "h1"; - return <El className={classNames("text-text text-xl font-semibold", className)}>{children}</El>; + return ( + <El data-testid="event-title" className={classNames("text-text text-xl font-semibold", className)}> + {children} + </El> + ); }; diff --git a/packages/features/bookings/components/event-meta/event.mock.ts b/packages/features/bookings/components/event-meta/event.mock.ts index 1bbff1ff57..100e1b0fe0 100644 --- a/packages/features/bookings/components/event-meta/event.mock.ts +++ b/packages/features/bookings/components/event-meta/event.mock.ts @@ -1,14 +1,17 @@ -import type { RouterOutputs } from "@calcom/trpc/react"; +import type { PublicEvent } from "bookings/types"; -export const mockEvent: RouterOutputs["viewer"]["public"]["event"] = { +export const mockEvent: PublicEvent = { id: 1, title: "Quick check-in", slug: "quick-check-in", eventName: "Quick check-in", description: "Use this event for a quick 15 minute catchup. Visit this long url to test the component https://cal.com/averylongurlwithoutspacesthatshouldntbreaklayout", - users: [{ name: "Pro Example", username: "pro" }], + users: [ + { name: "Pro example", username: "pro", weekStart: "Sunday", organizationId: null }, + { name: "Team example", username: "team", weekStart: "Sunday", organizationId: 1 }, + ], schedulingType: null, length: 30, locations: [{ type: "integrations:google:meet" }, { type: "integrations:zoom" }], -}; +} as PublicEvent; // TODO: complete mock and remove type assertion diff --git a/packages/features/bookings/components/verifyCodeDialog.stories.mdx b/packages/features/bookings/components/verifyCodeDialog.stories.mdx deleted file mode 100644 index 8679477369..0000000000 --- a/packages/features/bookings/components/verifyCodeDialog.stories.mdx +++ /dev/null @@ -1,55 +0,0 @@ -import { Canvas, Meta, Story } from "@storybook/addon-docs"; - -import { Title, CustomArgsTable } from "@calcom/storybook/components"; - -import { StorybookTrpcProvider } from "../../../ui/components/mocks/trpc.tsx"; -import { VerifyCodeDialog } from "./VerifyCodeDialog"; - -<Meta title="Features/VerifyCodeDialog" component={VerifyCodeDialog} /> - -<Title title="VerifyCodeDialog" suffix="Brief" subtitle="Version 1.0 — Last Update: 11 Sep 2023" /> - -## Definition - -The `VerifyCodeDialog` component allows users to enter a verification code sent to their email. The component provides feedback in case of an error and can handle different verification processes depending on whether the user session is required. - -## Structure - -This component contains an input form to capture a 6-digit verification code, error handling UI, and action buttons. - -<CustomArgsTable of={VerifyCodeDialog} /> - -## VerifyCodeDialog Story - -<Canvas> - <Story - name="VerifyCodeDialog" - args={{ - isOpenDialog: true, - setIsOpenDialog: () => {}, - email: "example@email.com", - onSuccess: () => {}, - onError: (err) => { - if (err.message === "invalid_code") { - alert("Code provided is invalid"); - } - }, - }} - argTypes={{ - isOpenDialog: { control: "boolean", description: "Indicates whether the dialog is open or not." }, - setIsOpenDialog: { action: "setIsOpenDialog", description: "Function to set the dialog state." }, - email: { control: "text", description: "Email to which the verification code was sent." }, - onSuccess: { action: "onSuccess", description: "Callback function when verification succeeds." }, - onError: { action: "onError", description: "Callback function when verification fails." }, - isUserSessionRequiredToVerify: { - control: "boolean", - description: "Indicates if user session is required for verification.", - }, - }}> - {(args) => ( - <StorybookTrpcProvider> - <VerifyCodeDialog {...args} /> - </StorybookTrpcProvider> - )} - </Story> -</Canvas> diff --git a/packages/features/bookings/lib/create-instant-booking.ts b/packages/features/bookings/lib/create-instant-booking.ts new file mode 100644 index 0000000000..1431e2ef5e --- /dev/null +++ b/packages/features/bookings/lib/create-instant-booking.ts @@ -0,0 +1,8 @@ +import { post } from "@calcom/lib/fetch-wrapper"; + +import type { BookingCreateBody, InstatBookingResponse } from "../types"; + +export const createInstantBooking = async (data: BookingCreateBody) => { + const response = await post<BookingCreateBody, InstatBookingResponse>("/api/book/instant-event", data); + return response; +}; diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index b71c9aa06d..e81805ca59 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -115,6 +115,8 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine scheduledJobs: true, seatsReferences: true, responses: true, + iCalUID: true, + iCalSequence: true, }, }); } @@ -264,6 +266,8 @@ async function handler(req: CustomRequest) { }), seatsPerTimeSlot: bookingToDelete.eventType?.seatsPerTimeSlot, seatsShowAttendees: bookingToDelete.eventType?.seatsShowAttendees, + iCalUID: bookingToDelete.iCalUID, + iCalSequence: bookingToDelete.iCalSequence + 1, }; const dataForWebhooks = { evt, webhooks, eventTypeInfo }; @@ -390,6 +394,8 @@ async function handler(req: CustomRequest) { data: { status: BookingStatus.CANCELLED, cancellationReason: cancellationReason, + // Assume that canceling the booking is the last action + iCalSequence: evt.iCalSequence || 100, }, select: { startTime: true, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 289e6f7148..14bc68817d 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -2,12 +2,14 @@ import type { Prisma, Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@pri import type { EventManagerUser } from "@calcom/core/EventManager"; import EventManager from "@calcom/core/EventManager"; +import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder"; import { sendScheduledEmails } from "@calcom/emails"; import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; +import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; @@ -137,6 +139,7 @@ export async function handleConfirmation(args: { }[] = []; const videoCallUrl = metadata.hangoutLink ? metadata.hangoutLink : evt.videoCallData?.url || ""; + const meetingUrl = getVideoCallUrlFromCalEvent(evt) || videoCallUrl; if (recurringEventId) { // The booking to confirm is a recurring event and comes from /booking/recurring, proceeding to mark all related @@ -161,7 +164,7 @@ export async function handleConfirmation(args: { paid, metadata: { ...(typeof recurringBooking.metadata === "object" ? recurringBooking.metadata : {}), - videoCallUrl, + videoCallUrl: meetingUrl, }, }, select: { @@ -214,7 +217,10 @@ export async function handleConfirmation(args: { references: { create: scheduleResult.referencesToCreate, }, - metadata: { ...(typeof booking.metadata === "object" ? booking.metadata : {}), videoCallUrl }, + metadata: { + ...(typeof booking.metadata === "object" ? booking.metadata : {}), + videoCallUrl: meetingUrl, + }, }, select: { eventType: { @@ -256,27 +262,31 @@ export async function handleConfirmation(args: { //Workflows - set reminders for confirmed events try { for (let index = 0; index < updatedBookings.length; index++) { - if (updatedBookings[index].eventType?.workflows) { - const evtOfBooking = evt; - evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); - evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); - evtOfBooking.uid = updatedBookings[index].uid; - const eventTypeSlug = updatedBookings[index].eventType?.slug || ""; - - const isFirstBooking = index === 0; - - await scheduleWorkflowReminders({ - workflows: updatedBookings[index]?.eventType?.workflows || [], - smsReminderNumber: updatedBookings[index].smsReminderNumber, - calendarEvent: { - ...evtOfBooking, - ...{ metadata: { videoCallUrl }, eventType: { slug: eventTypeSlug } }, - }, - isFirstRecurringEvent: isFirstBooking, - hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, - eventTypeRequiresConfirmation: true, - }); - } + const eventTypeSlug = updatedBookings[index].eventType?.slug || ""; + const evtOfBooking = { + ...evt, + metadata: { videoCallUrl: meetingUrl }, + eventType: { slug: eventTypeSlug }, + }; + evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); + evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); + evtOfBooking.uid = updatedBookings[index].uid; + const isFirstBooking = index === 0; + await scheduleMandatoryReminder( + evtOfBooking, + updatedBookings[index]?.eventType?.workflows || [], + false, + !!updatedBookings[index].eventType?.owner?.hideBranding, + evt.attendeeSeatId + ); + await scheduleWorkflowReminders({ + workflows: updatedBookings[index]?.eventType?.workflows || [], + smsReminderNumber: updatedBookings[index].smsReminderNumber, + calendarEvent: evtOfBooking, + isFirstRecurringEvent: isFirstBooking, + hideBranding: !!updatedBookings[index].eventType?.owner?.hideBranding, + eventTypeRequiresConfirmation: true, + }); } } catch (error) { // Silently fail @@ -299,6 +309,12 @@ export async function handleConfirmation(args: { triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, teamId, }); + const subscribersMeetingStarted = await getWebhooks({ + userId: triggerForUser ? booking.userId : null, + eventTypeId: booking.eventTypeId, + triggerEvent: WebhookTriggerEvents.MEETING_STARTED, + teamId: booking.eventType?.teamId, + }); const subscribersMeetingEnded = await getWebhooks({ userId: triggerForUser ? booking.userId : null, eventTypeId: booking.eventTypeId, @@ -306,9 +322,14 @@ export async function handleConfirmation(args: { teamId: booking.eventType?.teamId, }); + subscribersMeetingStarted.forEach((subscriber) => { + updatedBookings.forEach((booking) => { + scheduleTrigger(booking, subscriber.subscriberUrl, subscriber, WebhookTriggerEvents.MEETING_STARTED); + }); + }); subscribersMeetingEnded.forEach((subscriber) => { updatedBookings.forEach((booking) => { - scheduleTrigger(booking, subscriber.subscriberUrl, subscriber); + scheduleTrigger(booking, subscriber.subscriberUrl, subscriber, WebhookTriggerEvents.MEETING_ENDED); }); }); @@ -329,6 +350,7 @@ export async function handleConfirmation(args: { eventTypeId: booking.eventType?.id, status: "ACCEPTED", smsReminderNumber: booking.smsReminderNumber || undefined, + metadata: meetingUrl ? { videoCallUrl: meetingUrl } : undefined, }).catch((e) => { console.error( `Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_CREATED}, URL: ${sub.subscriberUrl}`, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index b3a4f5643c..2ec81f8a5c 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,6 +1,7 @@ import type { App, Attendee, DestinationCalendar, EventTypeCustomInput } from "@prisma/client"; import { Prisma } from "@prisma/client"; import async from "async"; +import type { IncomingMessage } from "http"; import { isValidPhoneNumber } from "libphonenumber-js"; // eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; @@ -25,6 +26,7 @@ import { getEventName } from "@calcom/core/event"; import { getUserAvailability } from "@calcom/core/getUserAvailability"; import { deleteMeeting } from "@calcom/core/videoClient"; import dayjs from "@calcom/dayjs"; +import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder"; import { sendAttendeeRequestEmail, sendOrganizerRequestEmail, @@ -36,6 +38,7 @@ import { sendScheduledEmails, sendScheduledSeatsEmails, } from "@calcom/emails"; +import getICalUID from "@calcom/emails/lib/getICalUID"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger"; @@ -58,6 +61,7 @@ import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getPaymentAppData from "@calcom/lib/getPaymentAppData"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { HttpError } from "@calcom/lib/http-error"; @@ -67,7 +71,6 @@ import { handlePayment } from "@calcom/lib/payment/handlePayment"; import { getPiiFreeCalendarEvent, getPiiFreeEventType, getPiiFreeUser } from "@calcom/lib/piiFreeData"; import { safeStringify } from "@calcom/lib/safeStringify"; import { checkBookingLimits, checkDurationLimits, getLuckyUser } from "@calcom/lib/server"; -import { getBookerUrl } from "@calcom/lib/server/getBookerUrl"; import { getTranslation } from "@calcom/lib/server/i18n"; import { slugify } from "@calcom/lib/slugify"; import { updateWebUser as syncServicesUpdateWebUser } from "@calcom/lib/sync/SyncServiceManager"; @@ -272,6 +275,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => { select: { id: true, name: true, + parentId: true, }, }, bookingFields: true, @@ -369,21 +373,16 @@ type IsFixedAwareUser = User & { organization: { slug: string }; }; -const loadUsers = async ( - eventType: NewBookingEventType, - dynamicUserList: string[], - reqHeadersHost: string | undefined -) => { +const loadUsers = async (eventType: NewBookingEventType, dynamicUserList: string[], req: IncomingMessage) => { try { if (!eventType.id) { if (!Array.isArray(dynamicUserList) || dynamicUserList.length === 0) { throw new Error("dynamicUserList is not properly defined or empty."); } - const users = await prisma.user.findMany({ where: { username: { in: dynamicUserList }, - organization: userOrgQuery(reqHeadersHost ? reqHeadersHost.replace(/^https?:\/\//, "") : ""), + organization: userOrgQuery(req), }, select: { ...userSelect.select, @@ -416,7 +415,7 @@ const loadUsers = async ( } }; -async function ensureAvailableUsers( +export async function ensureAvailableUsers( eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> & { users: IsFixedAwareUser[]; }, @@ -550,7 +549,7 @@ async function getOriginalRescheduledBooking(uid: string, seatsEventType?: boole }); } -async function getBookingData({ +export async function getBookingData({ req, isNotAnApiCall, eventType, @@ -716,6 +715,7 @@ async function createBooking({ }, dynamicEventSlugRef, dynamicGroupSlugRef, + iCalUID: evt.iCalUID ?? "", user: { connect: { id: organizerUser.id, @@ -791,7 +791,7 @@ async function createBooking({ return prisma.booking.create(createBookingObj); } -function getCustomInputsResponses( +export function getCustomInputsResponses( reqBody: { responses?: Record<string, object>; customInputs?: z.infer<typeof bookingCreateSchemaLegacyPropsForApi>["customInputs"]; @@ -822,6 +822,21 @@ function getCustomInputsResponses( return customInputsResponses; } +function getICalSequence(originalRescheduledBooking: BookingType | null) { + // If new booking set the sequence to 0 + if (!originalRescheduledBooking) { + return 0; + } + + // If rescheduling and there is no sequence set, assume sequence should be 1 + if (!originalRescheduledBooking.iCalSequence) { + return 1; + } + + // If rescheduling then increment sequence by 1 + return originalRescheduledBooking.iCalSequence + 1; +} + async function handler( req: NextApiRequest & { userId?: number | undefined }, { @@ -950,7 +965,7 @@ async function handler( let users: (Awaited<ReturnType<typeof loadUsers>>[number] & { isFixed?: boolean; metadata?: Prisma.JsonValue; - })[] = await loadUsers(eventType, dynamicUserList, req.headers.host); + })[] = await loadUsers(eventType, dynamicUserList, req); const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking); if (!isDynamicAllowed && !eventTypeId) { @@ -1246,8 +1261,17 @@ async function handler( const calEventUserFieldsResponses = "calEventUserFieldsResponses" in reqBody ? reqBody.calEventUserFieldsResponses : null; + const iCalUID = getICalUID({ + event: { iCalUID: originalRescheduledBooking?.iCalUID, uid: originalRescheduledBooking?.uid }, + uid, + }); + // For bookings made before introducing iCalSequence, assume that the sequence should start at 1. For new bookings start at 0. + const iCalSequence = getICalSequence(originalRescheduledBooking); + let evt: CalendarEvent = { - bookerUrl: await getBookerUrl(organizerUser), + bookerUrl: eventType.team + ? await getBookerBaseUrl({ organizationId: eventType.team.parentId }) + : await getBookerBaseUrl(organizerUser), type: eventType.slug, title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately description: eventType.description, @@ -1282,6 +1306,8 @@ async function handler( seatsPerTimeSlot: eventType.seatsPerTimeSlot, seatsShowAvailabilityCount: eventType.seatsPerTimeSlot ? eventType.seatsShowAvailabilityCount : true, schedulingType: eventType.schedulingType, + iCalUID, + iCalSequence, }; if (isTeamEventType && eventType.schedulingType === "COLLECTIVE") { @@ -1386,6 +1412,12 @@ async function handler( teamId, }; + const subscriberOptionsMeetingStarted = { + userId: triggerForUser ? organizerUser.id : null, + eventTypeId, + triggerEvent: WebhookTriggerEvents.MEETING_STARTED, + teamId, + }; const handleSeats = async () => { let resultBooking: | (Partial<Booking> & { @@ -2487,6 +2519,18 @@ async function handler( evt.appsStatus = handleAppsStatus(results, booking); videoCallUrl = metadata.hangoutLink || organizerOrFirstDynamicGroupMemberDefaultLocationUrl || videoCallUrl; + + if (evt.iCalUID !== booking.iCalUID) { + // The eventManager could change the iCalUID. At this point we can update the DB record + await prisma.booking.update({ + where: { + id: booking.id, + }, + data: { + iCalUID: evt.iCalUID || booking.iCalUID, + }, + }); + } } if (noEmail !== true) { let isHostConfirmationEmailsDisabled = false; @@ -2651,13 +2695,28 @@ async function handler( if (isConfirmedByDefault) { try { const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded); + const subscribersMeetingStarted = await getWebhooks(subscriberOptionsMeetingStarted); subscribersMeetingEnded.forEach((subscriber) => { if (rescheduleUid && originalRescheduledBooking) { cancelScheduledJobs(originalRescheduledBooking, undefined, true); } if (booking && booking.status === BookingStatus.ACCEPTED) { - scheduleTrigger(booking, subscriber.subscriberUrl, subscriber); + scheduleTrigger(booking, subscriber.subscriberUrl, subscriber, WebhookTriggerEvents.MEETING_ENDED); + } + }); + + subscribersMeetingStarted.forEach((subscriber) => { + if (rescheduleUid && originalRescheduledBooking) { + cancelScheduledJobs(originalRescheduledBooking, undefined, true); + } + if (booking && booking.status === BookingStatus.ACCEPTED) { + scheduleTrigger( + booking, + subscriber.subscriberUrl, + subscriber, + WebhookTriggerEvents.MEETING_STARTED + ); } }); } catch (error) { @@ -2718,15 +2777,21 @@ async function handler( } const metadataFromEvent = videoCallUrl ? { videoCallUrl } : undefined; + const evtWithMetadata = { ...evt, metadata: metadataFromEvent, eventType: { slug: eventType.slug } }; + + await scheduleMandatoryReminder( + evtWithMetadata, + eventType.workflows || [], + !isConfirmedByDefault, + !!eventType.owner?.hideBranding, + evt.attendeeSeatId + ); try { await scheduleWorkflowReminders({ workflows: eventType.workflows, smsReminderNumber: smsReminderNumber || null, - calendarEvent: { - ...evt, - ...{ metadata: metadataFromEvent, eventType: { slug: eventType.slug } }, - }, + calendarEvent: evtWithMetadata, isNotConfirmed: !isConfirmedByDefault, isRescheduleEvent: !!rescheduleUid, isFirstRecurringEvent: true, diff --git a/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts b/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts index fcfcef7975..f79531e8cf 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/booking-limits.test.ts @@ -1,7 +1,465 @@ -import { describe } from "vitest"; +/** + * These integration tests aim to cover difficult-to-test edge cases + * Standard cases are currently handled in e2e tests only + * + * see: https://github.com/calcom/cal.com/pull/10480 + * https://github.com/calcom/cal.com/pull/10968 + */ +import prismock from "../../../../../../tests/libs/__mocks__/prisma"; +import { describe, expect, vi } from "vitest"; + +import { BookingStatus } from "@calcom/prisma/enums"; import { test } from "@calcom/web/test/fixtures/fixtures"; +import { + TestData, + createBookingScenario, + getBooker, + getDate, + getNextMonthNotStartingOnWeekStart, + getOrganizer, + getScenarioData, +} from "@calcom/web/test/utils/bookingScenario/bookingScenario"; +import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest"; +import { expectBookingToBeInDatabase } from "@calcom/web/test/utils/bookingScenario/expects"; +import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking"; +import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; -describe("Booking Limits", () => { - test.todo("Test these cases that were failing earlier https://github.com/calcom/cal.com/pull/10480"); +// Local test runs sometime gets too slow +const timeout = process.env.CI ? 5000 : 20000; + +const eventLength = 30; + +describe("handleNewBooking", () => { + setupAndTeardown(); + + describe( + "Booking Limits", + () => { + // This test fails on CI as handleNewBooking throws no_available_users_found_error error + // eslint-disable-next-line playwright/no-skipped-test + test.skip( + `should fail a booking if yearly booking limits are already reached + 1. year with limits reached: should fail to book + 2. following year without bookings: should create a booking in the database + `, + async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const { dateString: nextYearDateString } = getDate({ yearIncrement: 1 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: eventLength, + length: eventLength, + users: [ + { + id: 101, + }, + ], + bookingLimits: { + PER_YEAR: 2, + }, + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextYearDateString}T04:00:00.000Z`, + endTime: `${nextYearDateString}T04:30:00.000Z`, + }, + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextYearDateString}T04:30:00.000Z`, + endTime: `${nextYearDateString}T05:00:00.000Z`, + }, + ], + organizer, + }) + ); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${nextYearDateString}T05:00:00.000Z`, + end: `${nextYearDateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached"); + + const { dateString: yearWithoutBookingsDateString } = getDate({ yearIncrement: 2 }); + + const mockBookingDataFollowingYear = getMockRequestDataForBooking({ + data: { + start: `${yearWithoutBookingsDateString}T05:00:00.000Z`, + end: `${yearWithoutBookingsDateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req: reqFollowingYear } = createMockNextJsRequest({ + method: "POST", + body: mockBookingDataFollowingYear, + }); + + const createdBooking = await handleNewBooking(reqFollowingYear); + + expect(createdBooking.responses).toContain({ + email: booker.email, + name: booker.name, + }); + + expect(createdBooking).toContain({ + location: "New York", + }); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingDataFollowingYear.eventTypeId, + status: BookingStatus.ACCEPTED, + }); + }, + timeout + ); + + test( + `should fail a booking if yearly duration limits are already reached + 1. year with limits reached: should fail to book + 2. following year without bookings: should create a booking in the database + `, + async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const yearlyDurationLimit = 2 * eventLength; + + const { dateString: nextYearDateString } = getDate({ yearIncrement: 1 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: eventLength, + length: eventLength, + users: [ + { + id: 101, + }, + ], + durationLimits: { + PER_YEAR: yearlyDurationLimit, + }, + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextYearDateString}T04:00:00.000Z`, + endTime: `${nextYearDateString}T04:30:00.000Z`, + }, + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${nextYearDateString}T04:30:00.000Z`, + endTime: `${nextYearDateString}T05:00:00.000Z`, + }, + ], + organizer, + }) + ); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${nextYearDateString}T05:00:00.000Z`, + end: `${nextYearDateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + vi.spyOn(prismock, "$queryRaw").mockResolvedValue([{ totalMinutes: yearlyDurationLimit }]); + + await expect(async () => await handleNewBooking(req)).rejects.toThrowError( + "duration_limit_reached" + ); + + const { dateString: yearWithoutBookingsDateString } = getDate({ yearIncrement: 2 }); + + const mockBookingDataFollowingYear = getMockRequestDataForBooking({ + data: { + start: `${yearWithoutBookingsDateString}T05:00:00.000Z`, + end: `${yearWithoutBookingsDateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req: reqFollowingYear } = createMockNextJsRequest({ + method: "POST", + body: mockBookingDataFollowingYear, + }); + + vi.spyOn(prismock, "$queryRaw").mockResolvedValue([{ totalMinutes: 0 }]); + + const createdBooking = await handleNewBooking(reqFollowingYear); + + expect(createdBooking.responses).toContain({ + email: booker.email, + name: booker.name, + }); + + expect(createdBooking).toContain({ + location: "New York", + }); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingDataFollowingYear.eventTypeId, + status: BookingStatus.ACCEPTED, + }); + }, + timeout + ); + + const { date: todayDate } = getDate(); + const { date: tomorrowDate } = getDate({ dateIncrement: 1 }); + + // today or tomorrow can't be the 1st day of month for this test to succeed + test.skipIf([todayDate, tomorrowDate].includes("01"))( + `should fail a booking if exceeds booking limits with bookings in the past`, + async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const { dateString: yesterdayDateString } = getDate({ dateIncrement: -1 }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: eventLength, + length: eventLength, + users: [ + { + id: 101, + }, + ], + bookingLimits: { + PER_MONTH: 2, + }, + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${yesterdayDateString}T04:00:00.000Z`, + endTime: `${yesterdayDateString}T04:30:00.000Z`, + }, + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${yesterdayDateString}T04:30:00.000Z`, + endTime: `${yesterdayDateString}T05:00:00.000Z`, + }, + ], + organizer, + }) + ); + + const { dateString: tomorrowDateString } = getDate({ dateIncrement: 1 }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${tomorrowDateString}T05:00:00.000Z`, + end: `${tomorrowDateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached"); + }, + timeout + ); + + test( + `should fail a booking if exceeds booking limits with bookings in week across two months`, + async ({}) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + }); + + const firstDayOfMonthStartingWithPartialWeek = getNextMonthNotStartingOnWeekStart( + organizer.weekStart, + new Date(getDate({ monthIncrement: 1 }).dateString) + ); + + const lastDayOfMonthEndingWithPartialWeek = getDate({ + fromDate: new Date(firstDayOfMonthStartingWithPartialWeek.dateString), + dateIncrement: -1, + }); + + await createBookingScenario( + getScenarioData({ + eventTypes: [ + { + id: 1, + slotInterval: eventLength, + length: eventLength, + users: [ + { + id: 101, + }, + ], + bookingLimits: { + PER_WEEK: 2, + }, + }, + ], + bookings: [ + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${firstDayOfMonthStartingWithPartialWeek.dateString}T04:00:00.000Z`, + endTime: `${firstDayOfMonthStartingWithPartialWeek.dateString}T04:30:00.000Z`, + }, + { + eventTypeId: 1, + userId: 101, + status: BookingStatus.ACCEPTED, + startTime: `${firstDayOfMonthStartingWithPartialWeek.dateString}T04:30:00.000Z`, + endTime: `${firstDayOfMonthStartingWithPartialWeek.dateString}T05:00:00.000Z`, + }, + ], + organizer, + }) + ); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + start: `${lastDayOfMonthEndingWithPartialWeek.dateString}T05:00:00.000Z`, + end: `${lastDayOfMonthEndingWithPartialWeek.dateString}T05:30:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: "New York" }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached"); + }, + timeout + ); + }, + timeout + ); }); diff --git a/packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts b/packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts index c893929b9a..a487e503df 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts @@ -12,7 +12,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { describe, expect } from "vitest"; import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; -import { WEBAPP_URL } from "@calcom/lib/constants"; +import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { resetTestEmails } from "@calcom/lib/testEmails"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -21,6 +21,7 @@ import { createBookingScenario, getDate, getGoogleCalendarCredential, + getAppleCalendarCredential, TestData, getOrganizer, getBooker, @@ -50,6 +51,8 @@ import { expectBookingPaymentIntiatedWebhookToHaveBeenFired, expectBrokenIntegrationEmails, expectSuccessfulCalendarEventCreationInCalendar, + expectWorkflowToBeNotTriggered, + expectICalUIDAsString, } from "@calcom/web/test/utils/bookingScenario/expects"; import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking"; import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; @@ -70,7 +73,7 @@ describe("handleNewBooking", () => { 1. Should create a booking in the database 2. Should send emails to the booker as well as organizer 3. Should create a booking in the event's destination calendar - 3. Should trigger BOOKING_CREATED webhook + 4. Should trigger BOOKING_CREATED webhook `, async ({ emails, org }) => { const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; @@ -105,6 +108,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -140,7 +152,6 @@ describe("handleNewBooking", () => { const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", { create: { id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", }, }); @@ -161,6 +172,7 @@ describe("handleNewBooking", () => { }); const createdBooking = await handleNewBooking(req); + expect(createdBooking.responses).toContain({ email: booker.email, name: booker.name, @@ -192,23 +204,26 @@ describe("handleNewBooking", () => { meetingUrl: "https://UNUSED_URL", }, ], + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { calendarId: "event-type-1@google-calendar.com", videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", }); + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + expectSuccessfulBookingCreationEmails({ booking: { uid: createdBooking.uid!, - urlOrigin: org ? org.urlOrigin : WEBAPP_URL, + urlOrigin: org ? org.urlOrigin : CAL_URL, }, booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ @@ -258,6 +273,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -289,7 +313,6 @@ describe("handleNewBooking", () => { create: { id: "GOOGLE_CALENDAR_EVENT_ID", uid: "MOCK_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", }, }); @@ -341,9 +364,10 @@ describe("handleNewBooking", () => { meetingUrl: "https://UNUSED_URL", }, ], + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", // We won't be sending evt.destinationCalendar in this case. @@ -352,6 +376,8 @@ describe("handleNewBooking", () => { calendarId: null, }); + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + expectSuccessfulBookingCreationEmails({ booking: { uid: createdBooking.uid!, @@ -359,7 +385,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ booker, @@ -410,6 +436,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -440,7 +475,6 @@ describe("handleNewBooking", () => { create: { uid: "MOCK_ID", id: "GOOGLE_CALENDAR_EVENT_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", }, }); @@ -492,14 +526,17 @@ describe("handleNewBooking", () => { meetingUrl: "https://UNUSED_URL", }, ], + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { calendarId: "organizer@google-calendar.com", videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", }); + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + expectSuccessfulBookingCreationEmails({ booking: { uid: createdBooking.uid!, @@ -507,7 +544,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ @@ -523,7 +560,7 @@ describe("handleNewBooking", () => { test( `an error in creating a calendar event should not stop the booking creation - Current behaviour is wrong as the booking is created but no-one is notified of it`, - async ({}) => { + async ({ emails }) => { const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; const booker = getBooker({ email: "booker@example.com", @@ -554,6 +591,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -617,7 +663,7 @@ describe("handleNewBooking", () => { ], }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); // FIXME: We should send Broken Integration emails on calendar event creation failure // expectCalendarEventCreationFailureEmails({ booker, organizer, emails }); @@ -666,6 +712,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -704,7 +759,6 @@ describe("handleNewBooking", () => { create: { uid: "MOCK_ID", id: "GOOGLE_CALENDAR_EVENT_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", }, }); @@ -756,9 +810,167 @@ describe("handleNewBooking", () => { meetingUrl: "https://UNUSED_URL", }, ], + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); + + expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { + calendarId: "organizer@google-calendar.com", + videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", + }); + + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + + expectSuccessfulBookingCreationEmails({ + booking: { + uid: createdBooking.uid!, + }, + booker, + organizer, + emails, + iCalUID, + }); + + expectBookingCreatedWebhookToHaveBeenFired({ + booker, + organizer, + location: BookingLocations.CalVideo, + subscriberUrl: "http://my-webhook.example.com", + videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`, + }); + }, + timeout + ); + + test( + "If destination calendar is there for Google Calendar but there are no Google Calendar credentials but there is an Apple Calendar credential connected, it should create the event in Apple Calendar", + async ({ emails }) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + schedules: [TestData.schedules.IstWorkHours], + credentials: [getAppleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + destinationCalendar: { + integration: "google_calendar", + externalId: "organizer@google-calendar.com", + }, + }); + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: organizer.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 1, + appId: null, + }, + ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], + eventTypes: [ + { + id: 1, + slotInterval: 45, + length: 45, + users: [ + { + id: 101, + }, + ], + }, + ], + organizer, + apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]], + }) + ); + + mockSuccessfulVideoMeetingCreation({ + metadataLookupKey: "dailyvideo", + videoMeetingData: { + id: "MOCK_ID", + password: "MOCK_PASS", + url: `http://mock-dailyvideo.example.com/meeting-1`, + }, + }); + + const calendarMock = mockCalendarToHaveNoBusySlots("applecalendar", { + create: { + uid: "MOCK_ID", + id: "MOCKED_APPLE_CALENDAR_EVENT_ID", + iCalUID: "MOCKED_APPLE_CALENDAR_ICS_ID", + }, + }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: BookingLocations.CalVideo }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + expect(createdBooking.responses).toContain({ + email: booker.email, + name: booker.name, + }); + + expect(createdBooking).toContain({ + location: BookingLocations.CalVideo, + }); + + await expectBookingToBeInDatabase({ + description: "", + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingData.eventTypeId, + status: BookingStatus.ACCEPTED, + references: [ + { + type: appStoreMetadata.dailyvideo.type, + uid: "MOCK_ID", + meetingId: "MOCK_ID", + meetingPassword: "MOCK_PASS", + meetingUrl: "http://mock-dailyvideo.example.com/meeting-1", + }, + { + type: appStoreMetadata.applecalendar.type, + uid: "MOCKED_APPLE_CALENDAR_EVENT_ID", + meetingId: "MOCKED_APPLE_CALENDAR_EVENT_ID", + meetingPassword: "MOCK_PASSWORD", + meetingUrl: "https://UNUSED_URL", + }, + ], + }); + + expectWorkflowToBeTriggered({ organizer, emails }); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { calendarId: "organizer@google-calendar.com", videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", @@ -771,7 +983,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID: "MOCKED_APPLE_CALENDAR_ICS_ID", }); expectBookingCreatedWebhookToHaveBeenFired({ @@ -852,6 +1064,8 @@ describe("handleNewBooking", () => { }); const createdBooking = await handleNewBooking(req); + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); + expectSuccessfulBookingCreationEmails({ booking: { uid: createdBooking.uid!, @@ -859,8 +1073,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - // Because no calendar was involved, we don't have an ics UID - iCalUID: createdBooking.uid!, + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ @@ -1155,6 +1368,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1217,7 +1439,7 @@ describe("handleNewBooking", () => { status: BookingStatus.PENDING, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeNotTriggered({ organizer, emails }); expectBookingRequestedEmails({ booker, @@ -1272,6 +1494,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1333,7 +1564,7 @@ describe("handleNewBooking", () => { }), }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeNotTriggered({ organizer, emails }); expectBookingRequestedEmails({ booker, @@ -1387,6 +1618,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1415,11 +1655,7 @@ describe("handleNewBooking", () => { metadataLookupKey: "dailyvideo", }); - mockCalendarToHaveNoBusySlots("googlecalendar", { - create: { - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", - }, - }); + mockCalendarToHaveNoBusySlots("googlecalendar", {}); const mockBookingData = getMockRequestDataForBooking({ data: { @@ -1453,9 +1689,12 @@ describe("handleNewBooking", () => { uid: createdBooking.uid!, eventTypeId: mockBookingData.eventTypeId, status: BookingStatus.ACCEPTED, + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); + + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); expectSuccessfulBookingCreationEmails({ booking: { @@ -1464,7 +1703,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ @@ -1511,6 +1750,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1540,11 +1788,7 @@ describe("handleNewBooking", () => { metadataLookupKey: "dailyvideo", }); - mockCalendarToHaveNoBusySlots("googlecalendar", { - create: { - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", - }, - }); + mockCalendarToHaveNoBusySlots("googlecalendar", {}); const mockBookingData = getMockRequestDataForBooking({ data: { @@ -1578,9 +1822,10 @@ describe("handleNewBooking", () => { uid: createdBooking.uid!, eventTypeId: mockBookingData.eventTypeId, status: BookingStatus.PENDING, + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeNotTriggered({ organizer, emails }); expectBookingRequestedEmails({ booker, organizer, emails }); @@ -1711,6 +1956,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1727,11 +1981,7 @@ describe("handleNewBooking", () => { apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]], }); - mockCalendarToHaveNoBusySlots("googlecalendar", { - create: { - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", - }, - }); + mockCalendarToHaveNoBusySlots("googlecalendar", {}); await createBookingScenario(scenarioData); const createdBooking = await handleNewBooking(req); @@ -1750,9 +2000,12 @@ describe("handleNewBooking", () => { uid: createdBooking.uid!, eventTypeId: mockBookingData.eventTypeId, status: BookingStatus.ACCEPTED, + iCalUID: createdBooking.iCalUID, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ organizer, emails }); + + const iCalUID = expectICalUIDAsString(createdBooking.iCalUID); expectSuccessfulBookingCreationEmails({ booking: { @@ -1761,7 +2014,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingCreatedWebhookToHaveBeenFired({ booker, @@ -1780,6 +2033,8 @@ describe("handleNewBooking", () => { 2. Should send email to the booker for Payment request 3. Should trigger BOOKING_PAYMENT_INITIATED webhook 4. Once payment is successful, should trigger BOOKING_CREATED webhook + 5. Workflow should not trigger before payment is made + 6. Workflow triggers once payment is successful `, async ({ emails }) => { const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; @@ -1807,6 +2062,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1883,7 +2147,8 @@ describe("handleNewBooking", () => { }), }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeNotTriggered({ organizer, emails }); + expectAwaitingPaymentEmails({ organizer, booker, emails }); expectBookingPaymentIntiatedWebhookToHaveBeenFired({ @@ -1906,6 +2171,8 @@ describe("handleNewBooking", () => { status: BookingStatus.ACCEPTED, }); + expectWorkflowToBeTriggered({ organizer, emails }); + expectBookingCreatedWebhookToHaveBeenFired({ booker, organizer, @@ -1954,6 +2221,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "NEW_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -2024,7 +2300,9 @@ describe("handleNewBooking", () => { eventTypeId: mockBookingData.eventTypeId, status: BookingStatus.PENDING, }); - expectWorkflowToBeTriggered(); + + expectWorkflowToBeNotTriggered({ organizer, emails }); + expectAwaitingPaymentEmails({ organizer, booker, emails }); expectBookingPaymentIntiatedWebhookToHaveBeenFired({ booker, diff --git a/packages/features/bookings/lib/handleNewBooking/test/recurring-event.test.ts b/packages/features/bookings/lib/handleNewBooking/test/recurring-event.test.ts index 04f7e72266..a0aadf87bf 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/recurring-event.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/recurring-event.test.ts @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from "uuid"; import { describe, expect } from "vitest"; -import { WEBAPP_URL } from "@calcom/lib/constants"; +import { WEBAPP_URL, CAL_URL } from "@calcom/lib/constants"; import { ErrorCode } from "@calcom/lib/errorCodes"; import logger from "@calcom/lib/logger"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -19,7 +19,7 @@ import { } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest"; import { - expectWorkflowToBeTriggered, + // expectWorkflowToBeTriggered, expectSuccessfulBookingCreationEmails, expectBookingToBeInDatabase, expectBookingCreatedWebhookToHaveBeenFired, @@ -202,14 +202,14 @@ describe("handleNewBooking", () => { }); } - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulBookingCreationEmails({ booker, booking: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion uid: createdBookings[0].uid!, - urlOrigin: WEBAPP_URL, + urlOrigin: CAL_URL, }, organizer, emails, @@ -548,14 +548,14 @@ describe("handleNewBooking", () => { }); } - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulBookingCreationEmails({ booker, booking: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion uid: createdBookings[0].uid!, - urlOrigin: WEBAPP_URL, + urlOrigin: CAL_URL, }, organizer, emails, @@ -763,13 +763,13 @@ describe("handleNewBooking", () => { }); } - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulBookingCreationEmails({ booking: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion uid: createdBookings[0].uid!, - urlOrigin: WEBAPP_URL, + urlOrigin: CAL_URL, }, booker, organizer, diff --git a/packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts b/packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts index 622b88e950..b5ce57afbb 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts @@ -37,7 +37,7 @@ import { expectBookingRequestedWebhookToHaveBeenFired, expectSuccessfulCalendarEventDeletionInCalendar, expectSuccessfulVideoMeetingDeletionInCalendar, - expectSuccessfulRoudRobinReschedulingEmails, + expectSuccessfulRoundRobinReschedulingEmails, } from "@calcom/web/test/utils/bookingScenario/expects"; import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking"; import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; @@ -75,6 +75,7 @@ describe("handleNewBooking", () => { const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP"; + const iCalUID = `${uidOfBookingToBeRescheduled}@Cal.com`; await createBookingScenario( getScenarioData({ webhooks: [ @@ -87,6 +88,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -128,6 +138,7 @@ describe("handleNewBooking", () => { credentialId: undefined, }, ], + iCalUID, }, ], organizer, @@ -145,7 +156,7 @@ describe("handleNewBooking", () => { }, update: { uid: "UPDATED_MOCK_ID", - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }, }); @@ -229,8 +240,7 @@ describe("handleNewBooking", () => { ], }, }); - - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ emails, organizer }); expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, { calEvent: { @@ -259,7 +269,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, appsStatus: [ getMockPassingAppStatus({ slug: appStoreMetadata.dailyvideo.slug }), getMockPassingAppStatus({ slug: appStoreMetadata.googlecalendar.slug }), @@ -278,7 +288,7 @@ describe("handleNewBooking", () => { ); test( - `should rechedule a booking successfully and update the event in the same externalCalendarId as was used in the booking earlier. + `should reschedule a booking successfully and update the event in the same externalCalendarId as was used in the booking earlier. 1. Should cancel the existing booking 2. Should create a new booking in the database 3. Should send emails to the booker as well as organizer @@ -302,6 +312,7 @@ describe("handleNewBooking", () => { const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP"; + const iCalUID = `${uidOfBookingToBeRescheduled}@Cal.com`; await createBookingScenario( getScenarioData({ webhooks: [ @@ -314,6 +325,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -355,6 +375,7 @@ describe("handleNewBooking", () => { credentialId: undefined, }, ], + iCalUID, }, ], organizer, @@ -371,7 +392,7 @@ describe("handleNewBooking", () => { uid: "MOCK_ID", }, update: { - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, uid: "UPDATED_MOCK_ID", }, }); @@ -438,7 +459,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ emails, organizer }); expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, { calEvent: { @@ -467,7 +488,7 @@ describe("handleNewBooking", () => { booker, organizer, emails, - iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + iCalUID, }); expectBookingRescheduledWebhookToHaveBeenFired({ booker, @@ -482,7 +503,7 @@ describe("handleNewBooking", () => { test( `an error in updating a calendar event should not stop the rescheduling - Current behaviour is wrong as the booking is resheduled but no-one is notified of it`, - async ({}) => { + async ({ emails }) => { const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; const booker = getBooker({ email: "booker@example.com", @@ -516,6 +537,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -626,7 +656,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ emails, organizer }); // FIXME: We should send Broken Integration emails on calendar event updation failure // expectBrokenIntegrationEmails({ booker, organizer, emails }); @@ -690,6 +720,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -811,7 +850,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered({emails, organizer}); expectBookingRequestedEmails({ booker, @@ -888,6 +927,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1037,7 +1085,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ emails, organizer }); expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, { calEvent: { @@ -1117,6 +1165,7 @@ describe("handleNewBooking", () => { }); const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP"; + const iCalUID = `${uidOfBookingToBeRescheduled}@Cal.com`; const scenarioData = getScenarioData({ webhooks: [ @@ -1129,6 +1178,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1168,6 +1226,7 @@ describe("handleNewBooking", () => { credentialId: 1, }), ], + iCalUID, }, ], organizer, @@ -1253,7 +1312,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + //expectWorkflowToBeTriggered({emails, organizer}); expectBookingRequestedEmails({ booker, @@ -1331,6 +1390,15 @@ describe("handleNewBooking", () => { appId: null, }, ], + workflows: [ + { + userId: organizer.id, + trigger: "RESCHEDULE_EVENT", + action: "EMAIL_HOST", + template: "REMINDER", + activeEventTypeId: 1, + }, + ], eventTypes: [ { id: 1, @@ -1491,7 +1559,7 @@ describe("handleNewBooking", () => { }, }); - expectWorkflowToBeTriggered(); + expectWorkflowToBeTriggered({ emails, organizer }); expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, { calEvent: { @@ -1692,7 +1760,7 @@ describe("handleNewBooking", () => { }, }); - expectSuccessfulRoudRobinReschedulingEmails({ + expectSuccessfulRoundRobinReschedulingEmails({ prevOrganizer: roundRobinHost1, newOrganizer: roundRobinHost2, emails, @@ -1842,7 +1910,7 @@ describe("handleNewBooking", () => { }, }); - expectSuccessfulRoudRobinReschedulingEmails({ + expectSuccessfulRoundRobinReschedulingEmails({ prevOrganizer: roundRobinHost1, newOrganizer: roundRobinHost1, // Round robin host 2 is not available and it will be rescheduled to same user emails, diff --git a/packages/features/bookings/lib/handleNewBooking/test/team-bookings/collective-scheduling.test.ts b/packages/features/bookings/lib/handleNewBooking/test/team-bookings/collective-scheduling.test.ts index eb59eed52e..c556b04bbe 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/team-bookings/collective-scheduling.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/team-bookings/collective-scheduling.test.ts @@ -3,11 +3,12 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { describe, expect } from "vitest"; import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData"; -import { WEBAPP_URL } from "@calcom/lib/constants"; +import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { SchedulingType } from "@calcom/prisma/enums"; import { BookingStatus } from "@calcom/prisma/enums"; import { test } from "@calcom/web/test/fixtures/fixtures"; +import { createOrganization } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; import { createBookingScenario, getGoogleCalendarCredential, @@ -25,7 +26,7 @@ import { } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest"; import { - expectWorkflowToBeTriggered, + // expectWorkflowToBeTriggered, expectSuccessfulBookingCreationEmails, expectBookingToBeInDatabase, expectBookingCreatedWebhookToHaveBeenFired, @@ -197,7 +198,7 @@ describe("handleNewBooking", () => { ], }); - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { destinationCalendars: [ { @@ -512,7 +513,7 @@ describe("handleNewBooking", () => { ], }); - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { destinationCalendars: [ { @@ -817,7 +818,7 @@ describe("handleNewBooking", () => { ], }); - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { destinationCalendars: [ { @@ -1032,7 +1033,7 @@ describe("handleNewBooking", () => { ], }); - expectWorkflowToBeTriggered(); + // expectWorkflowToBeTriggered(); expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { destinationCalendars: [ { @@ -1085,6 +1086,219 @@ describe("handleNewBooking", () => { }, timeout ); + + describe("Team(T1) not part of any org but the organizer is part of an organization(O1)", () => { + test( + `succesfully creates a booking when all the hosts are free as per their schedules + - Destination calendars for event-type and non-first hosts are used to create calendar events + - Reschedule and Cancel link in email are not of the org domain because the team is not part of any org + `, + async ({ emails }) => { + const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default; + const org = await createOrganization({ + name: "Test Org", + slug: "testorg", + }); + + const booker = getBooker({ + email: "booker@example.com", + name: "Booker", + }); + + const otherTeamMembers = [ + { + name: "Other Team Member 1", + username: "other-team-member-1", + timeZone: Timezones["+5:30"], + // So, that it picks the first schedule from the list + defaultScheduleId: null, + email: "other-team-member-1@example.com", + id: 102, + // Has Evening shift + schedules: [TestData.schedules.IstEveningShift], + credentials: [getGoogleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + destinationCalendar: { + integration: TestData.apps["google-calendar"].type, + externalId: "other-team-member-1@google-calendar.com", + }, + }, + ]; + + const organizer = getOrganizer({ + name: "Organizer", + email: "organizer@example.com", + id: 101, + // So, that it picks the first schedule from the list + defaultScheduleId: null, + organizationId: org.id, + teams: [ + { + membership: { + accepted: true, + }, + team: { + id: 1, + name: "Team 1", + slug: "team-1", + }, + }, + ], + // Has morning shift with some overlap with morning shift + schedules: [TestData.schedules.IstMorningShift], + credentials: [getGoogleCalendarCredential()], + selectedCalendars: [TestData.selectedCalendars.google], + destinationCalendar: { + integration: TestData.apps["google-calendar"].type, + externalId: "organizer@google-calendar.com", + }, + }); + + await createBookingScenario( + getScenarioData({ + webhooks: [ + { + userId: organizer.id, + eventTriggers: ["BOOKING_CREATED"], + subscriberUrl: "http://my-webhook.example.com", + active: true, + eventTypeId: 1, + appId: null, + }, + ], + eventTypes: [ + { + id: 1, + slotInterval: 45, + schedulingType: SchedulingType.COLLECTIVE, + length: 45, + users: [ + { + id: 101, + }, + { + id: 102, + }, + ], + // It is a team event but that team isn't part of any org + teamId: 1, + destinationCalendar: { + integration: TestData.apps["google-calendar"].type, + externalId: "event-type-1@google-calendar.com", + }, + }, + ], + organizer, + usersApartFromOrganizer: otherTeamMembers, + apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]], + }) + ); + + mockSuccessfulVideoMeetingCreation({ + metadataLookupKey: appStoreMetadata.dailyvideo.dirName, + videoMeetingData: { + id: "MOCK_ID", + password: "MOCK_PASS", + url: `http://mock-dailyvideo.example.com/meeting-1`, + }, + }); + + const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", { + create: { + id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", + iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + }, + }); + + const mockBookingData = getMockRequestDataForBooking({ + data: { + // Try booking the first available free timeslot in both the users' schedules + start: `${getDate({ dateIncrement: 1 }).dateString}T11:30:00.000Z`, + end: `${getDate({ dateIncrement: 1 }).dateString}T11:45:00.000Z`, + eventTypeId: 1, + responses: { + email: booker.email, + name: booker.name, + location: { optionValue: "", value: BookingLocations.CalVideo }, + }, + }, + }); + + const { req } = createMockNextJsRequest({ + method: "POST", + body: mockBookingData, + }); + + const createdBooking = await handleNewBooking(req); + + await expectBookingToBeInDatabase({ + description: "", + location: BookingLocations.CalVideo, + responses: expect.objectContaining({ + email: booker.email, + name: booker.name, + }), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + uid: createdBooking.uid!, + eventTypeId: mockBookingData.eventTypeId, + status: BookingStatus.ACCEPTED, + references: [ + { + type: appStoreMetadata.dailyvideo.type, + uid: "MOCK_ID", + meetingId: "MOCK_ID", + meetingPassword: "MOCK_PASS", + meetingUrl: "http://mock-dailyvideo.example.com/meeting-1", + }, + { + type: TestData.apps["google-calendar"].type, + uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", + meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID", + meetingPassword: "MOCK_PASSWORD", + meetingUrl: "https://UNUSED_URL", + }, + ], + }); + + // expectWorkflowToBeTriggered(); + expectSuccessfulCalendarEventCreationInCalendar(calendarMock, { + destinationCalendars: [ + { + integration: TestData.apps["google-calendar"].type, + externalId: "event-type-1@google-calendar.com", + }, + { + integration: TestData.apps["google-calendar"].type, + externalId: "other-team-member-1@google-calendar.com", + }, + ], + videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1", + }); + + expectSuccessfulBookingCreationEmails({ + booking: { + uid: createdBooking.uid!, + // All booking links are of WEBAPP_URL and not of the org because the team isn't part of the org + urlOrigin: CAL_URL, + }, + booker, + organizer, + otherTeamMembers, + emails, + iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID", + }); + + expectBookingCreatedWebhookToHaveBeenFired({ + booker, + organizer, + location: BookingLocations.CalVideo, + subscriberUrl: "http://my-webhook.example.com", + videoCallUrl: `${WEBAPP_URL}/video/${createdBooking.uid}`, + }); + }, + timeout + ); + }); }); test.todo("Round Robin booking"); diff --git a/packages/features/bookings/lib/index.ts b/packages/features/bookings/lib/index.ts index a9a36c1264..c0602ec11d 100644 --- a/packages/features/bookings/lib/index.ts +++ b/packages/features/bookings/lib/index.ts @@ -5,3 +5,4 @@ export { } from "./book-event-form/booking-to-mutation-input-mapper"; export { createBooking } from "./create-booking"; export { createRecurringBooking } from "./create-recurring-booking"; +export { createInstantBooking } from "./create-instant-booking"; diff --git a/packages/features/bookings/types.ts b/packages/features/bookings/types.ts index 7c2b070133..ef60006518 100644 --- a/packages/features/bookings/types.ts +++ b/packages/features/bookings/types.ts @@ -32,3 +32,7 @@ export type RecurringBookingCreateBody = BookingCreateBody & { export type BookingResponse = Awaited< ReturnType<typeof import("@calcom/features/bookings/lib/handleNewBooking").default> >; + +export type InstatBookingResponse = Awaited< + ReturnType<typeof import("@calcom/features/instant-meeting/handleInstantMeeting").default> +>; diff --git a/packages/features/calendars/weeklyview/components/calendar.stories.mdx b/packages/features/calendars/weeklyview/components/calendar.stories.mdx index 1d0f2227d0..fb8943ec13 100644 --- a/packages/features/calendars/weeklyview/components/calendar.stories.mdx +++ b/packages/features/calendars/weeklyview/components/calendar.stories.mdx @@ -1,18 +1,14 @@ -import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; +import { Canvas, Meta, Story } from "@storybook/addon-docs"; +import { TooltipProvider } from "@radix-ui/react-tooltip"; import { - Examples, - Example, - Note, + Title, CustomArgsTable, - VariantsTable, - VariantRow, } from "@calcom/storybook/components"; import { events, blockingDates } from "../_storybookData"; import "../styles/styles.css"; -import { CalendarEvent } from "../types/events"; import { Calendar } from "./Calendar"; <Meta title="UI/Calendar" component={Calendar} /> @@ -29,7 +25,7 @@ The Args Table below shows you a breakdown of what props can be passed into the There will be a few examples of how to use the Calendar component to show different usecases. -export const Template = (args) => <Calendar {...args} />; +export const Template = (args) => <TooltipProvider><Calendar {...args} /></TooltipProvider>; <Canvas> <Story @@ -74,6 +70,7 @@ export const Template = (args) => <Calendar {...args} />; }, }}> {({ ...args }) => ( + <TooltipProvider> <Calendar {...args} events={events} @@ -81,6 +78,7 @@ export const Template = (args) => <Calendar {...args} />; onEmptyCellClick={(date) => alert(date.toString())} sortEvents /> + </TooltipProvider> )} </Story> </Canvas> diff --git a/packages/features/calendars/weeklyview/components/event/Event.tsx b/packages/features/calendars/weeklyview/components/event/Event.tsx index 8cb854ae09..1e508b733a 100644 --- a/packages/features/calendars/weeklyview/components/event/Event.tsx +++ b/packages/features/calendars/weeklyview/components/event/Event.tsx @@ -23,6 +23,7 @@ const eventClasses = cva( PENDING: "bg-default text-emphasis border-[1px] border-dashed border-gray-900", REJECTED: "", CANCELLED: "", + AWAITING_HOST: "", }, disabled: { true: "hover:cursor-default", @@ -37,6 +38,7 @@ const eventClasses = cva( PENDING: "border-gray-900", REJECTED: "border-gray-900", CANCELLED: "border-gray-900", + AWAITING_HOST: "", custom: "", }, }, diff --git a/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx b/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx index b8826b9937..2d5dac0a57 100644 --- a/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx +++ b/packages/features/calendars/weeklyview/components/spinner/Spinner.tsx @@ -1,5 +1,11 @@ -export const Spinner = () => ( - <div className="fixed left-[calc(50%+calc(var(--booker-meta-width,0px)/2))] top-1/2 z-[80] h-10 w-10 -translate-x-1/2 -translate-y-1/2"> +import { classNames } from "@calcom/lib"; + +export const Spinner = ({ className }: { className?: string }) => ( + <div + className={classNames( + "fixed left-[calc(50%+calc(var(--booker-meta-width,0px)/2))] top-1/2 z-[80] h-10 w-10 -translate-x-1/2 -translate-y-1/2", + className + )}> <svg className="h-10 w-10" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <path className="fill-default" diff --git a/packages/features/ee/api-keys/components/ApiKeyDialogForm.tsx b/packages/features/ee/api-keys/components/ApiKeyDialogForm.tsx index 8de908195d..a99b94cebd 100644 --- a/packages/features/ee/api-keys/components/ApiKeyDialogForm.tsx +++ b/packages/features/ee/api-keys/components/ApiKeyDialogForm.tsx @@ -202,10 +202,12 @@ export default function ApiKeyDialogForm({ ); }} /> - <span className="text-subtle mt-2 text-xs"> - {t("api_key_expires_on")} - <span className="font-bold"> {dayjs(expiryDate).format("DD-MM-YYYY")}</span> - </span> + {!watchNeverExpires && ( + <span className="text-subtle mt-2 text-xs"> + {t("api_key_expires_on")} + <span className="font-bold"> {dayjs(expiryDate).format("DD-MM-YYYY")}</span> + </span> + )} </div> )} diff --git a/packages/features/ee/impersonation/components/ImpersonatingBanner.tsx b/packages/features/ee/impersonation/components/ImpersonatingBanner.tsx index 7a032900ff..f4ea44eeac 100644 --- a/packages/features/ee/impersonation/components/ImpersonatingBanner.tsx +++ b/packages/features/ee/impersonation/components/ImpersonatingBanner.tsx @@ -1,11 +1,12 @@ -import { useSession } from "next-auth/react"; +import type { SessionContextValue } from "next-auth/react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { TopBanner } from "@calcom/ui"; -function ImpersonatingBanner() { +export type ImpersonatingBannerProps = { data: SessionContextValue["data"] }; + +function ImpersonatingBanner({ data }: ImpersonatingBannerProps) { const { t } = useLocale(); - const { data } = useSession(); if (!data?.user.impersonatedByUID) return null; diff --git a/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts b/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts index 585981796e..043f717d82 100644 --- a/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts +++ b/packages/features/ee/managed-event-types/lib/handleChildrenEventTypes.ts @@ -186,6 +186,7 @@ export default async function handleChildrenEventTypes({ metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined, bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined, durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined, + onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false, userId, users: { connect: [{ id: userId }], @@ -235,6 +236,7 @@ export default async function handleChildrenEventTypes({ hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false, bookingLimits: (managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined, + onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false, recurringEvent: (managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined, metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined, diff --git a/packages/features/ee/organizations/README.md b/packages/features/ee/organizations/README.md index 53b0a3872b..4723fb73a9 100644 --- a/packages/features/ee/organizations/README.md +++ b/packages/features/ee/organizations/README.md @@ -37,7 +37,35 @@ Browsers do not allow camera/mic access on any non-HTTPS hosts except for localh For eg:- Use `http://localhost:3000/video/nAjnkjejuzis99NhN72rGt` instead of `http://app.cal.local:3000/video/nAjnkjejuzis99NhN72rGt`. -You can also use `ngrok` or you can generate SSL certificate for your local domain using `mkcert`. +To get an HTTPS URL for localhost, you can use a tunneling tool such as `ngrok` or [Tunnelmole](https://github.com/robbie-cahill/tunnelmole-client) . Alternatively, you can generate an SSL certificate for your local domain using `mkcert`. Turn off any SSL certificate validation in your HTTPS client (be sure to do this for local only, otherwise its a security risk). + +#### Tunnelmole - Open Source Tunnelling Tool: + +To install Tunnelmole, execute the command: + +``` +curl -O https://install.tunnelmole.com/8dPBw/install && sudo bash install +``` + +After a successful installation, you can run Tunnelmole using the following command, replacing `8000` with your actual port number if it is different: + +``` +tmole 8000 +``` + +In the output, you'll see two URLs, one HTTP and an HTTPS URL. For privacy and security reasons, it is recommended to use the HTTPS URL. + +View the Tunnelmole [README](https://github.com/robbie-cahill/tunnelmole-client) for additional information and other installation methods such as `npm` or building your own binaries from source. + +#### ngrok - Closed Source Tunnelling Tool: + +ngrok is a popular closed source tunneling tool. You can run ngrok using the same port, using the format `ngrok http <port>` replacing `<port>` with your actual port number. For example: + +``` +ngrok http 8000 +``` + +This will generate a public URL that you can use to access your localhost server. ## DNS setup diff --git a/packages/features/ee/organizations/components/AddNewTeamsForm.tsx b/packages/features/ee/organizations/components/AddNewTeamsForm.tsx index 0a12b701c8..4f9a565236 100644 --- a/packages/features/ee/organizations/components/AddNewTeamsForm.tsx +++ b/packages/features/ee/organizations/components/AddNewTeamsForm.tsx @@ -110,7 +110,7 @@ export const AddNewTeamsForm = () => { onChange={(e) => handleInputChange(index, e)} addOnClassname="bg-transparent p-0 border-l-0" className={index > 0 ? "mb-2" : ""} - placeholder={t(`org_team_names_example_${index + 1}`) || t("org_team_names_example")} + placeholder={t(`org_team_names_example_${index + 1}`)} addOnSuffix={ index > 0 && ( <Button diff --git a/packages/features/ee/organizations/components/OrgUpgradeBanner.tsx b/packages/features/ee/organizations/components/OrgUpgradeBanner.tsx index b3c2dd2dc6..073ffe9c35 100644 --- a/packages/features/ee/organizations/components/OrgUpgradeBanner.tsx +++ b/packages/features/ee/organizations/components/OrgUpgradeBanner.tsx @@ -1,13 +1,17 @@ import { useRouter } from "next/navigation"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { showToast, TopBanner } from "@calcom/ui"; -export function OrgUpgradeBanner() { +export type OrgUpgradeBannerProps = { + data: RouterOutputs["viewer"]["getUserTopBanners"]["orgUpgradeBanner"]; +}; + +export function OrgUpgradeBanner({ data }: OrgUpgradeBannerProps) { const { t } = useLocale(); const router = useRouter(); - const { data } = trpc.viewer.organizations.checkIfOrgNeedsUpgrade.useQuery(); const publishOrgMutation = trpc.viewer.organizations.publish.useMutation({ onSuccess(data) { router.push(data.url); diff --git a/packages/features/ee/organizations/components/OrganizationMemberAvatar.tsx b/packages/features/ee/organizations/components/OrganizationMemberAvatar.tsx deleted file mode 100644 index 7c898776c7..0000000000 --- a/packages/features/ee/organizations/components/OrganizationMemberAvatar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import classNames from "@calcom/lib/classNames"; -import { getOrgAvatarUrl } from "@calcom/lib/getAvatarUrl"; -// import { Avatar } from "@calcom/ui"; -import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar"; - -type OrganizationMemberAvatarProps = React.ComponentProps<typeof UserAvatar> & { - organization: { - id: number; - slug: string | null; - requestedSlug: string | null; - } | null; -}; - -/** - * Shows the user's avatar along with a small organization's avatar - */ -const OrganizationMemberAvatar = ({ - size, - user, - organization, - previewSrc, - ...rest -}: OrganizationMemberAvatarProps) => { - return ( - <UserAvatar - data-testid="organization-avatar" - size={size} - user={user} - previewSrc={previewSrc} - indicator={ - organization ? ( - <div - className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}> - <img - src={getOrgAvatarUrl(organization)} - alt={user.username || ""} - className="flex h-full items-center justify-center rounded-full" - /> - </div> - ) : null - } - {...rest} - /> - ); -}; - -export default OrganizationMemberAvatar; diff --git a/packages/features/ee/organizations/components/TeamInviteFromOrg.tsx b/packages/features/ee/organizations/components/TeamInviteFromOrg.tsx index dcdd0e8809..3864361e98 100644 --- a/packages/features/ee/organizations/components/TeamInviteFromOrg.tsx +++ b/packages/features/ee/organizations/components/TeamInviteFromOrg.tsx @@ -92,7 +92,7 @@ function UserToInviteItem({ id={`${member.user.id}`} checked={isSelected} type="checkbox" - className="text-primary-600 focus:ring-primary-500 border-default hover:bg-subtle inline-flex h-4 w-4 place-self-center justify-self-end rounded checked:bg-gray-800" + className="text-emphasis focus:ring-emphasis dark:text-muted border-default hover:bg-subtle inline-flex h-4 w-4 place-self-center justify-self-end rounded checked:bg-gray-800" onChange={() => { onChange(); }} diff --git a/packages/features/ee/organizations/lib/orgDomains.ts b/packages/features/ee/organizations/lib/orgDomains.ts index d21bb8d8c1..ebb0c9d73e 100644 --- a/packages/features/ee/organizations/lib/orgDomains.ts +++ b/packages/features/ee/organizations/lib/orgDomains.ts @@ -1,6 +1,7 @@ import type { Prisma } from "@prisma/client"; import type { IncomingMessage } from "http"; +import { IS_PRODUCTION, WEBSITE_URL } from "@calcom/lib/constants"; import { ALLOWED_HOSTNAMES, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import slugify from "@calcom/lib/slugify"; @@ -90,14 +91,19 @@ export function getOrgDomainConfigFromHostname({ } export function subdomainSuffix() { + if (!IS_PRODUCTION && process.env.LOCAL_TESTING_DOMAIN_VERCEL) { + // Allow testing with a valid domain so that we can test with deployment services like Vercel and Cloudflare locally. + return process.env.LOCAL_TESTING_DOMAIN_VERCEL; + } const urlSplit = WEBAPP_URL.replace("https://", "")?.replace("http://", "").split("."); return urlSplit.length === 3 ? urlSplit.slice(1).join(".") : urlSplit.join("."); } export function getOrgFullOrigin(slug: string, options: { protocol: boolean } = { protocol: true }) { - if (!slug) return WEBAPP_URL; + if (!slug) + return options.protocol ? WEBSITE_URL : WEBSITE_URL.replace("https://", "").replace("http://", ""); const orgFullOrigin = `${ - options.protocol ? `${new URL(WEBAPP_URL).protocol}//` : "" + options.protocol ? `${new URL(WEBSITE_URL).protocol}//` : "" }${slug}.${subdomainSuffix()}`; return orgFullOrigin; } @@ -140,7 +146,7 @@ export function whereClauseForOrgWithSlugOrRequestedSlug(slug: string) { } satisfies Prisma.TeamWhereInput; } -export function userOrgQuery(hostname: string, fallback?: string | string[]) { - const { currentOrgDomain, isValidOrgDomain } = getOrgDomainConfigFromHostname({ hostname, fallback }); +export function userOrgQuery(req: IncomingMessage | undefined, fallback?: string | string[]) { + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req, fallback); return isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null; } diff --git a/packages/features/ee/organizations/lib/utils.ts b/packages/features/ee/organizations/lib/utils.ts index 4cdbdd32ec..72180e2852 100644 --- a/packages/features/ee/organizations/lib/utils.ts +++ b/packages/features/ee/organizations/lib/utils.ts @@ -6,8 +6,3 @@ export function extractDomainFromEmail(email: string) { } catch (ignore) {} return out.split(".")[0]; } - -export const extractDomainFromWebsiteUrl = process.env.NEXT_PUBLIC_WEBSITE_URL?.replace( - "https://", - "" -)?.replace("http://", "") as string; diff --git a/packages/features/ee/organizations/pages/components/MemberListItem.tsx b/packages/features/ee/organizations/pages/components/MemberListItem.tsx index 5a4437ec79..d36baaac9f 100644 --- a/packages/features/ee/organizations/pages/components/MemberListItem.tsx +++ b/packages/features/ee/organizations/pages/components/MemberListItem.tsx @@ -1,11 +1,9 @@ import classNames from "classnames"; import TeamPill, { TeamRole } from "@calcom/ee/teams/components/TeamPill"; -import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { - Avatar, Button, ButtonGroup, Dropdown, @@ -14,11 +12,12 @@ import { DropdownMenuItem, DropdownMenuTrigger, Tooltip, + UserAvatar, } from "@calcom/ui"; import { ExternalLink, MoreHorizontal } from "@calcom/ui/components/icon"; interface Props { - member: RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"][number]; + member: RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"][number]; } export default function MemberListItem(props: Props) { @@ -26,22 +25,17 @@ export default function MemberListItem(props: Props) { const { member } = props; const { user } = member; - const bookerUrl = useBookerUrl(); + const name = user.name || user.username || user.email; + const bookerUrl = props.member.bookerUrl; const bookerUrlWithoutProtocol = bookerUrl.replace(/^https?:\/\//, ""); const bookingLink = user.username && `${bookerUrlWithoutProtocol}/${user.username}`; - const name = user.name || user.username || user.email; return ( <li className="divide-subtle divide-y px-5"> <div className="my-4 flex justify-between"> <div className="flex w-full flex-col justify-between overflow-hidden sm:flex-row"> <div className="flex"> - <Avatar - size="sm" - imageSrc={`${bookerUrl}/${user.username}/avatar.png`} - alt={name || ""} - className="h-10 w-10 rounded-full" - /> + <UserAvatar size="sm" user={user} className="h-10 w-10 rounded-full" /> <div className="ms-3 inline-block overflow-hidden"> <div className="mb-1 flex"> diff --git a/packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx b/packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx new file mode 100644 index 0000000000..620210633e --- /dev/null +++ b/packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx @@ -0,0 +1,110 @@ +import type { Team } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import NoSSR from "@calcom/core/components/NoSSR"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; +import type { teamMetadataSchema } from "@calcom/prisma/zod-utils"; +import { trpc } from "@calcom/trpc/react"; +import { Button, Form, Meta, TextField, showToast } from "@calcom/ui"; + +import { getLayout } from "../../../../../settings/layouts/SettingsLayout"; +import LicenseRequired from "../../../../common/components/LicenseRequired"; + +const paramsSchema = z.object({ id: z.coerce.number() }); + +const OrgEditPage = () => { + const params = useParamsWithFallback(); + const parsedParams = paramsSchema.safeParse(params); + + if (!parsedParams.success) return <div>Invalid id</div>; + + return <OrgEditView orgId={parsedParams.data.id} />; +}; + +const OrgEditView = ({ orgId }: { orgId: number }) => { + const [org] = trpc.viewer.organizations.adminGet.useSuspenseQuery({ id: orgId }); + + return ( + <LicenseRequired> + <Meta + title={`Editing organization: ${org.name}`} + description="Here you can edit a current organization." + /> + <NoSSR> + <OrgForm org={org} /> + </NoSSR> + </LicenseRequired> + ); +}; + +type FormValues = { + name: Team["name"]; + slug: Team["slug"]; + metadata: z.infer<typeof teamMetadataSchema>; +}; + +const OrgForm = ({ + org, +}: { + org: FormValues & { + id: Team["id"]; + }; +}) => { + const { t } = useLocale(); + const router = useRouter(); + const utils = trpc.useContext(); + const mutation = trpc.viewer.organizations.adminUpdate.useMutation({ + onSuccess: async () => { + await Promise.all([ + utils.viewer.organizations.adminGetAll.invalidate(), + utils.viewer.organizations.adminGet.invalidate({ + id: org.id, + }), + ]); + showToast(t("org_has_been_processed"), "success"); + router.replace(`/settings/admin/organizations`); + }, + onError: (err) => { + console.error(err.message); + showToast(t("org_error_processing"), "error"); + }, + }); + + const form = useForm<FormValues>({ + defaultValues: org, + }); + + const onSubmit = (values: FormValues) => { + mutation.mutate({ + id: org.id, + ...values, + }); + }; + + return ( + <Form form={form} className="space-y-4" handleSubmit={onSubmit}> + <TextField label="Name" placeholder="example" required {...form.register("name")} /> + <TextField label="Slug" placeholder="example" required {...form.register("slug")} /> + <p className="text-default mt-2 text-sm"> + Changing the slug would delete the previous organization domain and DNS and setup new domain and DNS + for the organization. + </p> + <TextField + label="Domain for which invitations are auto-accepted" + placeholder="abc.com" + required + {...form.register("metadata.orgAutoAcceptEmail")} + /> + <Button type="submit" color="primary" loading={mutation.isLoading}> + {t("save")} + </Button> + </Form> + ); +}; + +OrgEditPage.getLayout = getLayout; + +export default OrgEditPage; diff --git a/packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx b/packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx index c111ba3040..4bd6fef59c 100644 --- a/packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx +++ b/packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx @@ -1,12 +1,25 @@ +"use client"; + +import { Trans } from "next-i18next"; +import { useState } from "react"; + import NoSSR from "@calcom/core/components/NoSSR"; import LicenseRequired from "@calcom/ee/common/components/LicenseRequired"; -import { extractDomainFromWebsiteUrl } from "@calcom/ee/organizations/lib/utils"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; -import { Meta, DropdownActions, showToast, Table, Badge } from "@calcom/ui"; -import { X, Check, CheckCheck } from "@calcom/ui/components/icon"; +import { + Meta, + DropdownActions, + showToast, + Table, + Badge, + ConfirmationDialogContent, + Dialog, +} from "@calcom/ui"; +import { Check, CheckCheck, Trash, Edit, BookOpenCheck } from "@calcom/ui/components/icon"; import { getLayout } from "../../../../../settings/layouts/SettingsLayout"; +import { subdomainSuffix } from "../../../../organizations/lib/orgDomains"; const { Body, Cell, ColumnTitle, Header, Row } = Table; @@ -15,19 +28,21 @@ function AdminOrgTable() { const utils = trpc.useContext(); const [data] = trpc.viewer.organizations.adminGetAll.useSuspenseQuery(); const verifyMutation = trpc.viewer.organizations.adminVerify.useMutation({ - onSuccess: async () => { + onSuccess: async (_data, variables) => { showToast(t("org_has_been_processed"), "success"); - await utils.viewer.organizations.adminGetAll.invalidate(); + await invalidateQueries(utils, variables); }, onError: (err) => { console.error(err.message); showToast(t("org_error_processing"), "error"); }, }); - const updateMutation = trpc.viewer.organizations.update.useMutation({ - onSuccess: async () => { + const updateMutation = trpc.viewer.organizations.adminUpdate.useMutation({ + onSuccess: async (_data, variables) => { showToast(t("org_has_been_processed"), "success"); - await utils.viewer.organizations.adminGetAll.invalidate(); + await invalidateQueries(utils, { + orgId: variables.id, + }); }, onError: (err) => { console.error(err.message); @@ -35,12 +50,39 @@ function AdminOrgTable() { }, }); + const deleteMutation = trpc.viewer.organizations.adminDelete.useMutation({ + onSuccess: async (res, variables) => { + showToast(res.message, "success"); + await invalidateQueries(utils, variables); + }, + onError: (err) => { + console.error(err.message); + showToast(t("org_error_processing"), "error"); + }, + }); + + const publishOrg = async (org: (typeof data)[number]) => { + if (!org.metadata?.requestedSlug) { + showToast(t("org_publish_error"), "error"); + console.error("metadata.requestedSlug isn't set", org.metadata?.requestedSlug); + return; + } + updateMutation.mutate({ + id: org.id, + slug: org.metadata.requestedSlug, + }); + }; + + const [orgToDelete, setOrgToDelete] = useState<number | null>(null); return ( <div> <Table> <Header> <ColumnTitle widthClassNames="w-auto">{t("organization")}</ColumnTitle> <ColumnTitle widthClassNames="w-auto">{t("owner")}</ColumnTitle> + <ColumnTitle widthClassNames="w-auto">{t("verified")}</ColumnTitle> + <ColumnTitle widthClassNames="w-auto">{t("dns_configured")}</ColumnTitle> + <ColumnTitle widthClassNames="w-auto">{t("published")}</ColumnTitle> <ColumnTitle widthClassNames="w-auto"> <span className="sr-only">{t("edit")}</span> </ColumnTitle> @@ -54,7 +96,7 @@ function AdminOrgTable() { <span className="text-default">{org.name}</span> <br /> <span className="text-muted"> - {org.slug}.{extractDomainFromWebsiteUrl} + {org.slug}.{subdomainSuffix()} </span> </div> </Cell> @@ -65,67 +107,110 @@ function AdminOrgTable() { </Cell> <Cell> <div className="space-x-2"> - {!org.metadata?.isOrganizationVerified && <Badge variant="blue">{t("unverified")}</Badge>} - {!org.metadata?.isOrganizationConfigured && <Badge variant="red">{t("dns_missing")}</Badge>} + {!org.metadata?.isOrganizationVerified ? ( + <Badge variant="red">{t("unverified")}</Badge> + ) : ( + <Badge variant="blue">{t("verified")}</Badge> + )} + </div> + </Cell> + <Cell> + <div className="space-x-2"> + {org.metadata?.isOrganizationConfigured ? ( + <Badge variant="blue">{t("dns_configured")}</Badge> + ) : ( + <Badge variant="red">{t("dns_missing")}</Badge> + )} + </div> + </Cell> + <Cell> + <div className="space-x-2"> + {!org.slug ? ( + <Badge variant="red">{t("unpublished")}</Badge> + ) : ( + <Badge variant="green">{t("published")}</Badge> + )} </div> </Cell> <Cell widthClassNames="w-auto"> <div className="flex w-full justify-end"> - {(!org.metadata?.isOrganizationVerified || !org.metadata?.isOrganizationConfigured) && ( - <DropdownActions - actions={[ - ...(!org.metadata?.isOrganizationVerified - ? [ - { - id: "accept", - label: t("accept"), - onClick: () => { - verifyMutation.mutate({ - orgId: org.id, - status: "ACCEPT", - }); - }, - icon: Check, + <DropdownActions + actions={[ + ...(!org.metadata?.isOrganizationVerified + ? [ + { + id: "verify", + label: t("verify"), + onClick: () => { + verifyMutation.mutate({ + orgId: org.id, + }); }, - { - id: "reject", - label: t("reject"), - onClick: () => { - verifyMutation.mutate({ - orgId: org.id, - status: "DENY", - }); - }, - icon: X, + icon: Check, + }, + ] + : []), + ...(!org.metadata?.isOrganizationConfigured + ? [ + { + id: "dns", + label: t("mark_dns_configured"), + onClick: () => { + updateMutation.mutate({ + id: org.id, + metadata: { + isOrganizationConfigured: true, + }, + }); }, - ] - : []), - ...(!org.metadata?.isOrganizationConfigured - ? [ - { - id: "dns", - label: t("mark_dns_configured"), - onClick: () => { - updateMutation.mutate({ - orgId: org.id, - metadata: { - isOrganizationConfigured: true, - }, - }); - }, - icon: CheckCheck, + icon: CheckCheck, + }, + ] + : []), + { + id: "edit", + label: t("edit"), + href: `/settings/admin/organizations/${org.id}/edit`, + icon: Edit, + }, + ...(!org.slug + ? [ + { + id: "publish", + label: t("publish"), + onClick: () => { + publishOrg(org); }, - ] - : []), - ]} - /> - )} + icon: BookOpenCheck, + }, + ] + : []), + { + id: "delete", + label: t("delete"), + onClick: () => { + setOrgToDelete(org.id); + }, + icon: Trash, + }, + ]} + /> </div> </Cell> </Row> ))} </Body> </Table> + <DeleteOrgDialog + orgId={orgToDelete} + onClose={() => setOrgToDelete(null)} + onConfirm={() => { + if (!orgToDelete) return; + deleteMutation.mutate({ + orgId: orgToDelete, + }); + }} + /> </div> ); } @@ -145,3 +230,53 @@ const AdminOrgList = () => { AdminOrgList.getLayout = getLayout; export default AdminOrgList; + +const DeleteOrgDialog = ({ + orgId, + onConfirm, + onClose, +}: { + orgId: number | null; + onConfirm: () => void; + onClose: () => void; +}) => { + const { t } = useLocale(); + + return ( + // eslint-disable-next-line @typescript-eslint/no-empty-function -- noop + <Dialog name="delete-user" open={!!orgId} onOpenChange={(open) => (open ? () => {} : onClose())}> + <ConfirmationDialogContent + title={t("admin_delete_organization_title")} + confirmBtnText={t("delete")} + cancelBtnText={t("cancel")} + variety="danger" + onConfirm={onConfirm}> + <Trans + i18nKey="admin_delete_organization_description" + components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}> + <ul> + <li> + Teams that are member of this organization will also be deleted along with their event-types + </li> + <li> + Users that were part of the organization will not be deleted and their event-types will also + remain intact. + </li> + <li>Usernames would be changed to allow them to exist outside the organization</li> + </ul> + </Trans> + </ConfirmationDialogContent> + </Dialog> + ); +}; + +async function invalidateQueries(utils: ReturnType<typeof trpc.useContext>, data: { orgId: number }) { + await utils.viewer.organizations.adminGetAll.invalidate(); + await utils.viewer.organizations.adminGet.invalidate({ + id: data.orgId, + }); + // Due to some super weird reason, just invalidate doesn't work, so do refetch as well. + await utils.viewer.organizations.adminGet.refetch({ + id: data.orgId, + }); +} diff --git a/packages/features/ee/organizations/pages/settings/appearance.tsx b/packages/features/ee/organizations/pages/settings/appearance.tsx index 507b38dab1..8760787465 100644 --- a/packages/features/ee/organizations/pages/settings/appearance.tsx +++ b/packages/features/ee/organizations/pages/settings/appearance.tsx @@ -14,7 +14,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import type { RouterOutputs } from "@calcom/trpc/react"; -import { Button, Form, Meta, showToast, SettingsToggle } from "@calcom/ui"; +import { Button, Form, Meta, showToast, SettingsToggle, Avatar, ImageUploader } from "@calcom/ui"; +import { Plus } from "@calcom/ui/components/icon"; type BrandColorsFormValues = { brandColor: string; @@ -83,6 +84,48 @@ const OrgAppearanceView = ({ /> {isAdminOrOwner ? ( <div> + <div className="my-6"> + <div className="flex items-center text-sm"> + <Avatar + alt="calVideoLogo" + imageSrc={currentOrg?.calVideoLogo} + fallback={<Plus className="text-subtle h-6 w-6" />} + size="lg" + /> + <div className="ms-4"> + <div className="flex gap-2"> + <ImageUploader + target="avatar" + id="cal-video-logo-upload" + buttonMsg={ + currentOrg?.calVideoLogo ? t("update_cal_video_logo") : t("upload_cal_video_logo") + } + handleAvatarChange={(newLogo) => { + mutation.mutate({ + calVideoLogo: newLogo, + }); + }} + disabled={mutation.isLoading} + imageSrc={currentOrg?.calVideoLogo ?? undefined} + uploadInstruction={t("cal_video_logo_upload_instruction")} + triggerButtonColor={currentOrg?.calVideoLogo ? "secondary" : "primary"} + /> + {currentOrg?.calVideoLogo && ( + <Button + color="destructive" + disabled={mutation.isLoading} + onClick={() => { + mutation.mutate({ + calVideoLogo: null, + }); + }}> + {t("remove")} + </Button> + )} + </div> + </div> + </div> + </div> <Form form={themeForm} handleSubmit={(value) => { diff --git a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx index 1e37ceaba3..3d49c61265 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx @@ -1,7 +1,7 @@ // import { debounce } from "lodash"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -16,20 +16,21 @@ import { getLayout } from "../../../../settings/layouts/SettingsLayout"; import MakeTeamPrivateSwitch from "../../../teams/components/MakeTeamPrivateSwitch"; import MemberListItem from "../components/MemberListItem"; -type Members = RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]; +type Members = RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"]; type Team = RouterOutputs["viewer"]["organizations"]["getOtherTeam"]; interface MembersListProps { members: Members | undefined; team: Team | undefined; - offset: number; - setOffset: (offset: number) => void; - displayLoadMore: boolean; + fetchNextPage: () => void; + hasNextPage: boolean | undefined; + isFetchingNextPage: boolean | undefined; } function MembersList(props: MembersListProps) { const { t } = useLocale(); - const { displayLoadMore, members, team } = props; + const { hasNextPage, members = [], team, fetchNextPage, isFetchingNextPage } = props; + return ( <div className="flex flex-col gap-y-3"> {members?.length && team ? ( @@ -44,13 +45,15 @@ function MembersList(props: MembersListProps) { <p className="text-default text-sm font-bold">{t("no_members_found")}</p> </div> )} - {displayLoadMore && ( - <button - className="text-primary-500 hover:text-primary-600" - onClick={() => props.setOffset(props.offset + 1)}> - {t("load_more")} - </button> - )} + <div className="text-default p-4 text-center"> + <Button + color="minimal" + loading={isFetchingNextPage} + disabled={!hasNextPage} + onClick={() => fetchNextPage()}> + {hasNextPage ? t("load_more_results") : t("no_more_results")} + </Button> + </div> </div> ); } @@ -62,11 +65,9 @@ const MembersView = () => { const teamId = Number(params.id); const session = useSession(); const utils = trpc.useContext(); - const [offset, setOffset] = useState<number>(1); // const [query, setQuery] = useState<string | undefined>(""); // const [queryToFetch, setQueryToFetch] = useState<string | undefined>(""); - const [loadMore, setLoadMore] = useState<boolean>(true); - const limit = 100; + const limit = 20; const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false); const [members, setMembers] = useState<Members>([]); const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { @@ -91,28 +92,28 @@ const MembersView = () => { enabled: !Number.isNaN(teamId), } ); - const { data: membersFetch, isLoading: isLoadingMembers } = - trpc.viewer.organizations.listOtherTeamMembers.useQuery( - { teamId, limit, offset: (offset - 1) * limit }, + + const { fetchNextPage, isFetchingNextPage, hasNextPage } = + trpc.viewer.organizations.listOtherTeamMembers.useInfiniteQuery( + { teamId, limit }, { + onSuccess: (data) => { + const flatData = data?.pages?.flatMap((page) => page.rows) as Members; + setMembers(flatData); + }, enabled: !Number.isNaN(teamId), onError: () => { router.push("/settings"); }, + getNextPageParam: (lastPage) => lastPage.nextCursor, + keepPreviousData: true, } ); - useEffect(() => { - if (membersFetch) { - setLoadMore(membersFetch.length >= limit); - setMembers((m) => m.concat(membersFetch)); - } - }, [membersFetch]); - - const isLoading = isTeamLoading || isLoadingMembers || isOrgListLoading; - + const isLoading = isTeamLoading || isOrgListLoading; const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation({ onSuccess: () => { + utils.viewer.organizations.getMembers.invalidate(); utils.viewer.organizations.listOtherTeams.invalidate(); utils.viewer.teams.list.invalidate(); utils.viewer.organizations.listOtherTeamMembers.invalidate(); @@ -162,9 +163,9 @@ const MembersView = () => { <MembersList members={members} team={team} - setOffset={setOffset} - offset={offset} - displayLoadMore={loadMore} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + isFetchingNextPage={isFetchingNextPage} /> </> diff --git a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx index dac0d4aabf..cb9960a03f 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx @@ -37,7 +37,7 @@ import { import { ExternalLink, Link as LinkIcon, Trash2 } from "@calcom/ui/components/icon"; import { getLayout } from "../../../../settings/layouts/SettingsLayout"; -import { extractDomainFromWebsiteUrl } from "../../../organizations/lib/utils"; +import { subdomainSuffix } from "../../../organizations/lib/orgDomains"; const regex = new RegExp("^[a-zA-Z0-9-]*$"); @@ -225,9 +225,7 @@ const OtherTeamProfileView = () => { label={t("team_url")} value={value} addOnLeading={ - team?.parent - ? `${team.parent.slug}.${extractDomainFromWebsiteUrl}/` - : `${WEBAPP_URL}/team/` + team?.parent ? `${team.parent.slug}.${subdomainSuffix()}/` : `${WEBAPP_URL}/team/` } onChange={(e) => { form.clearErrors("slug"); diff --git a/packages/features/ee/organizations/pages/settings/profile.tsx b/packages/features/ee/organizations/pages/settings/profile.tsx index 1e690870e7..b2bb7063dd 100644 --- a/packages/features/ee/organizations/pages/settings/profile.tsx +++ b/packages/features/ee/organizations/pages/settings/profile.tsx @@ -205,6 +205,7 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => { return ( <> <Avatar + data-testid="profile-upload-avatar" alt={defaultValues.name || ""} imageSrc={getPlaceholderAvatar(value, defaultValues.name as string)} size="lg" diff --git a/packages/features/ee/package.json b/packages/features/ee/package.json index 33aae3a963..47d1f92b89 100644 --- a/packages/features/ee/package.json +++ b/packages/features/ee/package.json @@ -12,7 +12,7 @@ "@hookform/resolvers": "^2.9.7", "@sendgrid/client": "^7.7.0", "@sendgrid/mail": "^7.6.2", - "libphonenumber-js": "^1.10.12", + "libphonenumber-js": "^1.10.51", "twilio": "^3.80.1", "zod": "^3.22.2" }, diff --git a/packages/features/ee/sso/page/teams-sso-view.tsx b/packages/features/ee/sso/page/teams-sso-view.tsx index 26ab7d0a36..73829f1acd 100644 --- a/packages/features/ee/sso/page/teams-sso-view.tsx +++ b/packages/features/ee/sso/page/teams-sso-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useRouter } from "next/navigation"; import { useEffect } from "react"; diff --git a/packages/features/ee/teams/components/AddNewTeamMembers.tsx b/packages/features/ee/teams/components/AddNewTeamMembers.tsx index da444655da..b54916dbf5 100644 --- a/packages/features/ee/teams/components/AddNewTeamMembers.tsx +++ b/packages/features/ee/teams/components/AddNewTeamMembers.tsx @@ -6,8 +6,7 @@ import { useOrgBranding } from "@calcom/features/ee/organizations/context/provid import InviteLinkSettingsModal from "@calcom/features/ee/teams/components/InviteLinkSettingsModal"; import MemberInvitationModal from "@calcom/features/ee/teams/components/MemberInvitationModal"; import { classNames } from "@calcom/lib"; -import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; -import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; +import { APP_NAME } from "@calcom/lib/constants"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useTelemetry, telemetryEventTypes } from "@calcom/lib/telemetry"; @@ -15,13 +14,13 @@ import { MembershipRole } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { - Avatar, Badge, Button, showToast, SkeletonButton, SkeletonContainer, SkeletonText, + UserAvatar, } from "@calcom/ui"; import { ArrowRight, Plus, Trash2 } from "@calcom/ui/components/icon"; @@ -219,7 +218,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n const { t } = useLocale(); const utils = trpc.useContext(); const session = useSession(); - const bookerUrl = useBookerUrl(); + const bookerUrl = member.bookerUrl; const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { enabled: !!session.data?.user?.org, }); @@ -247,7 +246,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n )} data-testid="pending-member-item"> <div className="mr-4 flex max-w-full space-x-2 overflow-hidden rtl:space-x-reverse"> - <Avatar size="mdLg" imageSrc={`${bookerUrl}/${member.username}/avatar.png`} alt="owner-avatar" /> + <UserAvatar size="mdLg" user={member} /> <div className="max-w-full overflow-hidden"> <div className="flex space-x-1"> <p>{member.name || member.email || t("team_member")}</p> @@ -258,7 +257,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n {member.role === "ADMIN" && <Badge variant="default">{t("admin")}</Badge>} </div> {member.username ? ( - <p className="text-default truncate">{`${WEBAPP_URL}/${member.username}`}</p> + <p className="text-default truncate">{`${bookerUrl}/${member.username}`}</p> ) : ( <p className="text-default truncate">{t("not_on_cal", { appName: APP_NAME })}</p> )} diff --git a/packages/features/ee/teams/components/CreateANewTeamForm.tsx b/packages/features/ee/teams/components/CreateANewTeamForm.tsx index 14de3a1588..4c3d62f723 100644 --- a/packages/features/ee/teams/components/CreateANewTeamForm.tsx +++ b/packages/features/ee/teams/components/CreateANewTeamForm.tsx @@ -3,7 +3,6 @@ import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; -import { extractDomainFromWebsiteUrl } from "@calcom/ee/organizations/lib/utils"; import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -15,6 +14,7 @@ import { Alert, Button, Form, TextField } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; import { useOrgBranding } from "../../organizations/context/provider"; +import { subdomainSuffix } from "../../organizations/lib/orgDomains"; import type { NewTeamFormValues } from "../lib/types"; const querySchema = z.object({ @@ -129,7 +129,7 @@ export const CreateANewTeamForm = () => { addOnLeading={`${ orgBranding ? `${orgBranding.fullDomain.replace("https://", "").replace("http://", "")}/` - : `${extractDomainFromWebsiteUrl}/team/` + : `${subdomainSuffix()}/team/` }`} value={value} defaultValue={value} diff --git a/packages/features/ee/teams/components/MemberInvitationModal.tsx b/packages/features/ee/teams/components/MemberInvitationModal.tsx index bde9365f72..82dcbfbc03 100644 --- a/packages/features/ee/teams/components/MemberInvitationModal.tsx +++ b/packages/features/ee/teams/components/MemberInvitationModal.tsx @@ -1,4 +1,5 @@ import { BuildingIcon, PaperclipIcon, UserIcon, Users } from "lucide-react"; +import { useSession } from "next-auth/react"; import { Trans } from "next-i18next"; import { useMemo, useState, useRef } from "react"; import type { FormEvent } from "react"; @@ -6,11 +7,12 @@ import { Controller, useForm } from "react-hook-form"; import TeamInviteFromOrg from "@calcom/ee/organizations/components/TeamInviteFromOrg"; import { classNames } from "@calcom/lib"; -import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { IS_TEAM_BILLING_ENABLED, MAX_NB_INVITES } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import type { RouterOutputs } from "@calcom/trpc"; import { trpc } from "@calcom/trpc"; +import { isEmail } from "@calcom/trpc/server/routers/viewer/teams/util"; import { Button, Dialog, @@ -69,6 +71,11 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) const { t } = useLocale(); const { disableCopyLink = false, isOrg = false } = props; const trpcContext = trpc.useContext(); + const session = useSession(); + const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { + enabled: !!session.data?.user?.org, + }); + const isOrgOwner = currentOrg && currentOrg.user.role === MembershipRole.OWNER; const [modalImportMode, setModalInputMode] = useState<ModalMode>( props?.orgMembers && props.orgMembers?.length > 0 ? "ORGANIZATION" : "INDIVIDUAL" @@ -76,7 +83,6 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) const createInviteMutation = trpc.viewer.teams.createInvite.useMutation({ async onSuccess({ inviteLink }) { - await copyInviteLinkToClipboard(inviteLink); trpcContext.viewer.teams.get.invalidate(); trpcContext.viewer.teams.list.invalidate(); }, @@ -85,22 +91,20 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) }, }); - const copyInviteLinkToClipboard = async (inviteLink: string) => { - try { - await navigator.clipboard.writeText(inviteLink); - showToast(t("invite_link_copied"), "success"); - } catch (e) { - console.error(e); - } - }; - const options: MembershipRoleOption[] = useMemo(() => { - return [ + const options: MembershipRoleOption[] = [ { value: MembershipRole.MEMBER, label: t("member") }, { value: MembershipRole.ADMIN, label: t("admin") }, { value: MembershipRole.OWNER, label: t("owner") }, ]; - }, [t]); + + // Adjust options for organizations where the user isn't the owner + if (isOrg && !isOrgOwner) { + return options.filter((option) => option.value !== MembershipRole.OWNER); + } + + return options; + }, [t, isOrgOwner, isOrg]); const toggleGroupOptions = useMemo(() => { const array = [ @@ -199,7 +203,10 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) </Label> <ToggleGroup isFullWidth={true} - onValueChange={(val) => setModalInputMode(val as ModalMode)} + onValueChange={(val) => { + setModalInputMode(val as ModalMode); + newMemberFormMethods.clearErrors(); + }} defaultValue={modalImportMode} options={toggleGroupOptions} /> @@ -213,8 +220,10 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) name="emailOrUsername" control={newMemberFormMethods.control} rules={{ - required: t("enter_email_or_username"), + required: isOrg ? t("enter_email") : t("enter_email_or_username"), validate: (value) => { + // orgs can only invite members by email + if (typeof value === "string" && isOrg && !isEmail(value)) return t("enter_email"); if (typeof value === "string") return validateUniqueInvite(value) || t("member_already_invited"); }, @@ -241,7 +250,14 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) name="emailOrUsername" control={newMemberFormMethods.control} rules={{ - required: t("enter_email_or_username"), + required: t("enter_email"), + validate: (value) => { + if (Array.isArray(value) && value.some((email) => !isEmail(email))) + return t("enter_emails"); + if (Array.isArray(value) && value.length > MAX_NB_INVITES) + return t("too_many_invites", { nbUsers: MAX_NB_INVITES }); + if (typeof value === "string" && !isEmail(value)) return t("enter_email"); + }, }} render={({ field: { onChange, value }, fieldState: { error } }) => ( <> @@ -255,7 +271,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) required value={value} onChange={(e) => { - const targetValues = e.target.value.split(","); + const targetValues = e.target.value.split(/[\n,]/); const emails = targetValues.length === 1 ? targetValues[0].trim().toLocaleLowerCase() @@ -363,8 +379,36 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) type="button" color="minimal" variant="icon" - onClick={() => { - createInviteMutation.mutate({ teamId: props.teamId, token: props.token }); + onClick={async function () { + try { + // Required for Safari but also works on Chrome + // Credits to https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/ + if (typeof ClipboardItem !== "undefined") { + const inviteLinkClipbardItem = new ClipboardItem({ + "text/plain": new Promise(async (resolve) => { + // Instead of doing async work and then writing to clipboard, do async work in clipboard API itself + const { inviteLink } = await createInviteMutation.mutateAsync({ + teamId: props.teamId, + token: props.token, + }); + showToast(t("invite_link_copied"), "success"); + resolve(new Blob([inviteLink], { type: "text/plain" })); + }), + }); + await navigator.clipboard.write([inviteLinkClipbardItem]); + } else { + // Fallback for browsers that don't support ClipboardItem e.g. Firefox + const { inviteLink } = await createInviteMutation.mutateAsync({ + teamId: props.teamId, + token: props.token, + }); + await navigator.clipboard.writeText(inviteLink); + showToast(t("invite_link_copied"), "success"); + } + } catch (e) { + showToast(t("something_went_wrong_on_our_end"), "error"); + console.error(e); + } }} className={classNames("gap-2", props.token && "opacity-50")} data-testid="copy-invite-link-button"> diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx index 2f2bacfa32..714cfe9115 100644 --- a/packages/features/ee/teams/components/MemberListItem.tsx +++ b/packages/features/ee/teams/components/MemberListItem.tsx @@ -3,7 +3,6 @@ import { SendIcon } from "lucide-react"; import { signIn } from "next-auth/react"; import { useState } from "react"; -import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; @@ -27,8 +26,8 @@ import { showToast, Tooltip, } from "@calcom/ui"; +import { UserAvatar } from "@calcom/ui"; import { ExternalLink, MoreHorizontal, Edit2, Lock, UserX } from "@calcom/ui/components/icon"; -import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar"; import MemberChangeRoleModal from "./MemberChangeRoleModal"; import TeamAvailabilityModal from "./TeamAvailabilityModal"; @@ -67,6 +66,7 @@ export default function MemberListItem(props: Props) { await utils.viewer.teams.get.invalidate(); await utils.viewer.eventTypes.invalidate(); await utils.viewer.organizations.listMembers.invalidate(); + await utils.viewer.organizations.getMembers.invalidate(); showToast(t("success"), "success"); }, async onError(err) { @@ -119,7 +119,7 @@ export default function MemberListItem(props: Props) { process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "true"; const resendInvitation = editMode && !props.member.accepted; - const bookerUrl = useBookerUrl(); + const bookerUrl = props.member.bookerUrl; const bookerUrlWithoutProtocol = bookerUrl.replace(/^https?:\/\//, ""); const bookingLink = !!props.member.username && `${bookerUrlWithoutProtocol}/${props.member.username}`; const isAdmin = props.team && ["ADMIN", "OWNER"].includes(props.team.membership?.role); @@ -145,11 +145,15 @@ export default function MemberListItem(props: Props) { <div className="flex"> <UserAvatar size="sm" user={props.member} className="h-10 w-10 rounded-full" /> <div className="ms-3 inline-block"> - <div className="mb-1 flex"> - <span className="text-default mr-2 text-sm font-bold leading-4">{name}</span> - {!props.member.accepted && <TeamPill color="orange" text={t("pending")} />} + <div className="mb-1 flex" data-testid={`member-${props.member.username}`}> + <span data-testid="member-name" className="text-default mr-2 text-sm font-bold leading-4"> + {name} + </span> + {!props.member.accepted && ( + <TeamPill data-testid="member-pending" color="orange" text={t("pending")} /> + )} {isAdmin && props.member.accepted && appList} - {props.member.role && <TeamRole role={props.member.role} />} + {props.member.role && <TeamRole data-testid="member-role" role={props.member.role} />} </div> <div className="text-default flex items-center"> <span @@ -195,17 +199,19 @@ export default function MemberListItem(props: Props) { StartIcon={Clock} /> </Tooltip> */} - <Tooltip content={t("view_public_page")}> - <Button - target="_blank" - href={`${bookerUrl}/${props.member.username}`} - color="secondary" - className={classNames(!editMode ? "rounded-r-md" : "")} - variant="icon" - StartIcon={ExternalLink} - disabled={!props.member.accepted} - /> - </Tooltip> + {!!props.member.accepted && ( + <Tooltip content={t("view_public_page")}> + <Button + target="_blank" + href={`${bookerUrl}/${props.member.username}`} + color="secondary" + className={classNames(!editMode ? "rounded-r-md" : "")} + variant="icon" + StartIcon={ExternalLink} + disabled={!props.member.accepted} + /> + </Tooltip> + )} {editMode && ( <Dropdown> <DropdownMenuTrigger asChild> diff --git a/packages/features/ee/teams/components/TeamList.tsx b/packages/features/ee/teams/components/TeamList.tsx index 9cd893b95d..ee2918e80d 100644 --- a/packages/features/ee/teams/components/TeamList.tsx +++ b/packages/features/ee/teams/components/TeamList.tsx @@ -75,22 +75,6 @@ export default function TeamList(props: Props) { child: t("invite"), }} /> - {/* @TODO: uncomment once managed event types is live - <Card - icon={<Unlock className="h-5 w-5 text-blue-700" />} - variant="basic" - title={t("create_a_managed_event")} - description={t("create_a_one_one_template")} - actionButton={{ - href: - "/event-types?dialog=new-eventtype&eventPage=team%2F" + - team.slug + - "&teamId=" + - team.id + - "&managed=true", - child: t("create"), - }} - /> */} <Card icon={<Users className="h-5 w-5 text-orange-700" />} variant="basic" diff --git a/packages/features/ee/teams/components/TeamListItem.tsx b/packages/features/ee/teams/components/TeamListItem.tsx index 6be6a2344d..0eb04d4140 100644 --- a/packages/features/ee/teams/components/TeamListItem.tsx +++ b/packages/features/ee/teams/components/TeamListItem.tsx @@ -6,6 +6,7 @@ import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSetti import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal"; import classNames from "@calcom/lib/classNames"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -13,6 +14,7 @@ import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { Avatar, + Badge, Button, ButtonGroup, ConfirmationDialogContent, @@ -104,11 +106,11 @@ export default function TeamListItem(props: Props) { <div className="ms-3 inline-block truncate"> <span className="text-default text-sm font-bold">{team.name}</span> <span className="text-muted block text-xs"> - {team.slug - ? orgBranding - ? `${orgBranding.fullDomain}/${team.slug}` - : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/${team.slug}` - : "Unpublished team"} + {team.slug ? ( + `${getTeamUrlSync({ orgSlug: team.parent ? team.parent.slug : null, teamSlug: team.slug })}` + ) : ( + <Badge>{t("upgrade")}</Badge> + )} </span> </div> </div> @@ -180,13 +182,17 @@ export default function TeamListItem(props: Props) { )} <div className={classNames("flex items-center justify-between", !isInvitee && "hover:bg-muted group")}> {!isInvitee ? ( - <Link - data-testid="team-list-item-link" - href={`/settings/teams/${team.id}/profile`} - className="flex-grow cursor-pointer truncate text-sm" - title={`${team.name}`}> - {teamInfo} - </Link> + team.slug ? ( + <Link + data-testid="team-list-item-link" + href={`/settings/teams/${team.id}/profile`} + className="flex-grow cursor-pointer truncate text-sm" + title={`${team.name}`}> + {teamInfo} + </Link> + ) : ( + <TeamPublishSection teamId={team.id}>{teamInfo}</TeamPublishSection> + ) ) : ( teamInfo )} @@ -237,11 +243,10 @@ export default function TeamListItem(props: Props) { color="secondary" onClick={() => { navigator.clipboard.writeText( - `${ - orgBranding - ? `${orgBranding.fullDomain}` - : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team` - }/${team.slug}` + `${getTeamUrlSync({ + orgSlug: team.parent ? team.parent.slug : null, + teamSlug: team.slug, + })}` ); showToast(t("link_copied"), "success"); }} @@ -277,11 +282,10 @@ export default function TeamListItem(props: Props) { <DropdownItem type="button" target="_blank" - href={`${ - orgBranding - ? `${orgBranding.fullDomain}` - : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team` - }/${team.slug}`} + href={`${getTeamUrlSync({ + orgSlug: team.parent ? team.parent.slug : null, + teamSlug: team.slug, + })}`} StartIcon={ExternalLink}> {t("preview_team") as string} </DropdownItem> @@ -388,3 +392,26 @@ const TeamPublishButton = ({ teamId }: { teamId: number }) => { </DropdownMenuItem> ); }; + +const TeamPublishSection = ({ children, teamId }: { children: React.ReactNode; teamId: number }) => { + const router = useRouter(); + const publishTeamMutation = trpc.viewer.teams.publish.useMutation({ + onSuccess(data) { + router.push(data.url); + }, + onError: (error) => { + showToast(error.message, "error"); + }, + }); + + return ( + <button + className="block flex-grow cursor-pointer truncate text-left text-sm" + type="button" + onClick={() => { + publishTeamMutation.mutate({ teamId }); + }}> + {children} + </button> + ); +}; diff --git a/packages/features/ee/teams/components/TeamPill.tsx b/packages/features/ee/teams/components/TeamPill.tsx index 66c56edc89..af4919896f 100644 --- a/packages/features/ee/teams/components/TeamPill.tsx +++ b/packages/features/ee/teams/components/TeamPill.tsx @@ -5,31 +5,38 @@ import { MembershipRole } from "@calcom/prisma/enums"; type PillColor = "blue" | "green" | "red" | "orange"; -interface Props { +interface Props extends React.HTMLAttributes<HTMLDivElement> { text: string; color?: PillColor; } export default function TeamPill(props: Props) { + const { color, text, ...rest } = props; return ( <div className={classNames("text-medium self-center rounded-md px-1 py-0.5 text-xs ltr:mr-1 rtl:ml-1", { - " bg-subtle text-emphasis": !props.color, - " bg-info text-info": props.color === "blue", - " bg-error text-error ": props.color === "red", - " bg-attention text-attention": props.color === "orange", - })}> - {props.text} + " bg-subtle text-emphasis": !color, + " bg-info text-info": color === "blue", + " bg-error text-error ": color === "red", + " bg-attention text-attention": color === "orange", + })} + {...rest}> + {text} </div> ); } -export function TeamRole(props: { role: MembershipRole }) { +interface TeamRoleProps extends Omit<React.ComponentProps<typeof TeamPill>, "text"> { + role: MembershipRole; +} + +export function TeamRole(props: TeamRoleProps) { const { t } = useLocale(); + const { role, ...rest } = props; const keys: Record<MembershipRole, PillColor | undefined> = { [MembershipRole.OWNER]: "blue", [MembershipRole.ADMIN]: "red", [MembershipRole.MEMBER]: undefined, }; - return <TeamPill text={t(props.role.toLowerCase())} color={keys[props.role]} />; + return <TeamPill text={t(role.toLowerCase())} color={keys[role]} {...rest} />; } diff --git a/packages/features/ee/teams/components/TeamsListing.tsx b/packages/features/ee/teams/components/TeamsListing.tsx index 5b909484c7..53fa8b4b94 100644 --- a/packages/features/ee/teams/components/TeamsListing.tsx +++ b/packages/features/ee/teams/components/TeamsListing.tsx @@ -102,7 +102,6 @@ export function TeamsListing() { <TeamList teams={invites} pending /> </div> )} - <UpgradeTip plan="team" title={t("calcom_is_better_with_team", { appName: APP_NAME })} diff --git a/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx b/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx index 58441a6337..2804d59a2f 100644 --- a/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx +++ b/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx @@ -2,12 +2,17 @@ import { useRouter } from "next/navigation"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { showToast, TopBanner } from "@calcom/ui"; -export function TeamsUpgradeBanner() { +export type TeamsUpgradeBannerProps = { + data: RouterOutputs["viewer"]["getUserTopBanners"]["teamUpgradeBanner"]; +}; + +export function TeamsUpgradeBanner({ data }: TeamsUpgradeBannerProps) { const { t } = useLocale(); const router = useRouter(); - const { data } = trpc.viewer.teams.getUpgradeable.useQuery(); + const publishTeamMutation = trpc.viewer.teams.publish.useMutation({ onSuccess(data) { router.push(data.url); diff --git a/packages/features/ee/teams/components/index.ts b/packages/features/ee/teams/components/index.ts index 9a2c857f44..4ba43769b0 100644 --- a/packages/features/ee/teams/components/index.ts +++ b/packages/features/ee/teams/components/index.ts @@ -1,3 +1,3 @@ export { CreateANewTeamForm } from "./CreateANewTeamForm"; export { TeamsListing } from "./TeamsListing"; -export { TeamsUpgradeBanner } from "./TeamsUpgradeBanner"; +export { TeamsUpgradeBanner, type TeamsUpgradeBannerProps } from "./TeamsUpgradeBanner"; diff --git a/packages/features/ee/teams/pages/team-appearance-view.tsx b/packages/features/ee/teams/pages/team-appearance-view.tsx index a8cc2ac269..02dfea5357 100644 --- a/packages/features/ee/teams/pages/team-appearance-view.tsx +++ b/packages/features/ee/teams/pages/team-appearance-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; diff --git a/packages/features/ee/teams/pages/team-listing-view.tsx b/packages/features/ee/teams/pages/team-listing-view.tsx index 9040e35cd3..2047a2d004 100644 --- a/packages/features/ee/teams/pages/team-listing-view.tsx +++ b/packages/features/ee/teams/pages/team-listing-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Meta } from "@calcom/ui"; diff --git a/packages/features/ee/teams/pages/team-members-view.tsx b/packages/features/ee/teams/pages/team-members-view.tsx index 553a8a449c..3c142c36ab 100644 --- a/packages/features/ee/teams/pages/team-members-view.tsx +++ b/packages/features/ee/teams/pages/team-members-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -206,6 +208,7 @@ const MembersView = () => { { onSuccess: async (data) => { await utils.viewer.teams.get.invalidate(); + await utils.viewer.organizations.getMembers.invalidate(); setShowMemberInvitationModal(false); if (Array.isArray(data.usernameOrEmail)) { diff --git a/packages/features/ee/teams/pages/team-profile-view.tsx b/packages/features/ee/teams/pages/team-profile-view.tsx index 88c53e991f..d0dff783d4 100644 --- a/packages/features/ee/teams/pages/team-profile-view.tsx +++ b/packages/features/ee/teams/pages/team-profile-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { zodResolver } from "@hookform/resolvers/zod"; import type { Prisma } from "@prisma/client"; import { useSession } from "next-auth/react"; @@ -7,11 +9,10 @@ import { useLayoutEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; -import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; -import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import { md } from "@calcom/lib/markdownIt"; @@ -152,7 +153,7 @@ const ProfileView = () => { {isAdmin ? ( <TeamProfileForm team={team} /> ) : ( - <div className="flex"> + <div className="border-subtle flex rounded-b-xl border border-t-0 px-4 py-8 sm:px-6"> <div className="flex-grow"> <div> <Label className="text-emphasis">{t("team_name")}</Label> @@ -168,7 +169,7 @@ const ProfileView = () => { </> )} </div> - <div className=""> + <div> <Link href={permalink} passHref={true} target="_blank"> <LinkIconButton Icon={ExternalLink}>{t("preview")}</LinkIconButton> </Link> @@ -269,7 +270,6 @@ const TeamProfileForm = ({ team }: TeamProfileFormProps) => { }); const [firstRender, setFirstRender] = useState(true); - const orgBranding = useOrgBranding(); const { formState: { isSubmitting, isDirty }, @@ -375,11 +375,14 @@ const TeamProfileForm = ({ team }: TeamProfileFormProps) => { name="slug" label={t("team_url")} value={value} - addOnLeading={ - team.parent && orgBranding - ? `${getOrgFullOrigin(orgBranding?.slug, { protocol: false })}/` - : `${WEBAPP_URL}/team/` - } + data-testid="team-url" + addOnClassname="testid-leading-text-team-url" + addOnLeading={`${getTeamUrlSync( + { orgSlug: team.parent ? team.parent.slug : null, teamSlug: null }, + { + protocol: false, + } + )}`} onChange={(e) => { form.clearErrors("slug"); form.setValue("slug", slugify(e?.target.value, true), { shouldDirty: true }); diff --git a/packages/features/ee/users/pages/users-add-view.tsx b/packages/features/ee/users/pages/users-add-view.tsx index 30ffc93ae0..44f3d8d1d0 100644 --- a/packages/features/ee/users/pages/users-add-view.tsx +++ b/packages/features/ee/users/pages/users-add-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { usePathname, useRouter } from "next/navigation"; import { getParserWithGeneric } from "@calcom/prisma/zod-utils"; diff --git a/packages/features/ee/users/pages/users-edit-view.tsx b/packages/features/ee/users/pages/users-edit-view.tsx index 2da6d2126e..c4d6169f53 100644 --- a/packages/features/ee/users/pages/users-edit-view.tsx +++ b/packages/features/ee/users/pages/users-edit-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import { usePathname, useRouter } from "next/navigation"; import { z } from "zod"; diff --git a/packages/features/ee/users/pages/users-listing-view.tsx b/packages/features/ee/users/pages/users-listing-view.tsx index 5ddbc9d8e9..915530b4bf 100644 --- a/packages/features/ee/users/pages/users-listing-view.tsx +++ b/packages/features/ee/users/pages/users-listing-view.tsx @@ -1,3 +1,5 @@ +"use client"; + import NoSSR from "@calcom/core/components/NoSSR"; import { Button, Meta } from "@calcom/ui"; diff --git a/packages/features/ee/workflows/api/scheduleEmailReminders.ts b/packages/features/ee/workflows/api/scheduleEmailReminders.ts index fa03762ad1..8dec83f79e 100644 --- a/packages/features/ee/workflows/api/scheduleEmailReminders.ts +++ b/packages/features/ee/workflows/api/scheduleEmailReminders.ts @@ -1,6 +1,5 @@ /* Schedule any workflow reminder that falls within 72 hours for email */ import client from "@sendgrid/client"; -import sgMail from "@sendgrid/mail"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 as uuidv4 } from "uuid"; @@ -20,16 +19,11 @@ import { getAllUnscheduledReminders, } from "../lib/getWorkflowReminders"; import { getiCalEventAsString } from "../lib/getiCalEventAsString"; +import { sendSendgridMail } from "../lib/reminders/providers/sendgridProvider"; import type { VariablesType } from "../lib/reminders/templates/customTemplate"; import customTemplate from "../lib/reminders/templates/customTemplate"; import emailReminderTemplate from "../lib/reminders/templates/emailReminderTemplate"; -const sendgridAPIKey = process.env.SENDGRID_API_KEY as string; -const senderEmail = process.env.SENDGRID_EMAIL as string; - -sgMail.setApiKey(sendgridAPIKey); -client.setApiKey(sendgridAPIKey); - async function handler(req: NextApiRequest, res: NextApiResponse) { const apiKey = req.headers.authorization || req.query.apiKey; if (process.env.CRON_API_KEY !== apiKey) { @@ -42,8 +36,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { return; } - const sandboxMode = process.env.NEXT_PUBLIC_IS_E2E ? true : false; - // delete batch_ids with already past scheduled date from scheduled_sends const remindersToDelete: { referenceId: string | null }[] = await getAllRemindersToDelete(); @@ -121,100 +113,181 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } for (const reminder of unscheduledReminders) { - if (!reminder.workflowStep || !reminder.booking) { + if (!reminder.booking) { continue; } - try { - let sendTo; + if (!reminder.isMandatoryReminder && reminder.workflowStep) { + try { + let sendTo; - switch (reminder.workflowStep.action) { - case WorkflowActions.EMAIL_HOST: - sendTo = reminder.booking.user?.email; - break; - case WorkflowActions.EMAIL_ATTENDEE: - sendTo = reminder.booking.attendees[0].email; - break; - case WorkflowActions.EMAIL_ADDRESS: - sendTo = reminder.workflowStep.sendTo; - } + switch (reminder.workflowStep.action) { + case WorkflowActions.EMAIL_HOST: + sendTo = reminder.booking.user?.email; + break; + case WorkflowActions.EMAIL_ATTENDEE: + sendTo = reminder.booking.attendees[0].email; + break; + case WorkflowActions.EMAIL_ADDRESS: + sendTo = reminder.workflowStep.sendTo; + } - const name = - reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE - ? reminder.booking.attendees[0].name - : reminder.booking.user?.name; + const name = + reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE + ? reminder.booking.attendees[0].name + : reminder.booking.user?.name; - const attendeeName = - reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE - ? reminder.booking.user?.name - : reminder.booking.attendees[0].name; + const attendeeName = + reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE + ? reminder.booking.user?.name + : reminder.booking.attendees[0].name; - const timeZone = - reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE - ? reminder.booking.attendees[0].timeZone - : reminder.booking.user?.timeZone; + const timeZone = + reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE + ? reminder.booking.attendees[0].timeZone + : reminder.booking.user?.timeZone; - const locale = - reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE || - reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE - ? reminder.booking.attendees[0].locale - : reminder.booking.user?.locale; + const locale = + reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE || + reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE + ? reminder.booking.attendees[0].locale + : reminder.booking.user?.locale; - let emailContent = { - emailSubject: reminder.workflowStep.emailSubject || "", - emailBody: `<body style="white-space: pre-wrap;">${reminder.workflowStep.reminderBody || ""}</body>`, - }; - - let emailBodyEmpty = false; - - if (reminder.workflowStep.reminderBody) { - const { responses } = getCalEventResponses({ - bookingFields: reminder.booking.eventType?.bookingFields ?? null, - booking: reminder.booking, - }); - - const variables: VariablesType = { - eventName: reminder.booking.eventType?.title || "", - organizerName: reminder.booking.user?.name || "", - attendeeName: reminder.booking.attendees[0].name, - attendeeEmail: reminder.booking.attendees[0].email, - eventDate: dayjs(reminder.booking.startTime).tz(timeZone), - eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone), - timeZone: timeZone, - location: reminder.booking.location || "", - additionalNotes: reminder.booking.description, - responses: responses, - meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl, - cancelLink: `/booking/${reminder.booking.uid}?cancel=true`, - rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`, + let emailContent = { + emailSubject: reminder.workflowStep.emailSubject || "", + emailBody: `<body style="white-space: pre-wrap;">${ + reminder.workflowStep.reminderBody || "" + }</body>`, }; - const emailLocale = locale || "en"; - const emailSubject = customTemplate( - reminder.workflowStep.emailSubject || "", - variables, - emailLocale, - getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), - !!reminder.booking.user?.hideBranding - ).text; - emailContent.emailSubject = emailSubject; - emailContent.emailBody = customTemplate( - reminder.workflowStep.reminderBody || "", - variables, - emailLocale, - getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), - !!reminder.booking.user?.hideBranding - ).html; - emailBodyEmpty = - customTemplate( + let emailBodyEmpty = false; + + if (reminder.workflowStep.reminderBody) { + const { responses } = getCalEventResponses({ + bookingFields: reminder.booking.eventType?.bookingFields ?? null, + booking: reminder.booking, + }); + + const variables: VariablesType = { + eventName: reminder.booking.eventType?.title || "", + organizerName: reminder.booking.user?.name || "", + attendeeName: reminder.booking.attendees[0].name, + attendeeEmail: reminder.booking.attendees[0].email, + eventDate: dayjs(reminder.booking.startTime).tz(timeZone), + eventEndTime: dayjs(reminder.booking?.endTime).tz(timeZone), + timeZone: timeZone, + location: reminder.booking.location || "", + additionalNotes: reminder.booking.description, + responses: responses, + meetingUrl: bookingMetadataSchema.parse(reminder.booking.metadata || {})?.videoCallUrl, + cancelLink: `/booking/${reminder.booking.uid}?cancel=true`, + rescheduleLink: `/${reminder.booking.user?.username}/${reminder.booking.eventType?.slug}?rescheduleUid=${reminder.booking.uid}`, + }; + const emailLocale = locale || "en"; + const emailSubject = customTemplate( + reminder.workflowStep.emailSubject || "", + variables, + emailLocale, + getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), + !!reminder.booking.user?.hideBranding + ).text; + emailContent.emailSubject = emailSubject; + emailContent.emailBody = customTemplate( reminder.workflowStep.reminderBody || "", variables, emailLocale, - getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat) - ).text.length === 0; - } else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) { + getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), + !!reminder.booking.user?.hideBranding + ).html; + + emailBodyEmpty = + customTemplate( + reminder.workflowStep.reminderBody || "", + variables, + emailLocale, + getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat) + ).text.length === 0; + } else if (reminder.workflowStep.template === WorkflowTemplates.REMINDER) { + emailContent = emailReminderTemplate( + false, + reminder.workflowStep.action, + getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), + reminder.booking.startTime.toISOString() || "", + reminder.booking.endTime.toISOString() || "", + reminder.booking.eventType?.title || "", + timeZone || "", + attendeeName || "", + name || "", + !!reminder.booking.user?.hideBranding + ); + } + + if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) { + const batchIdResponse = await client.request({ + url: "/v3/mail/batch", + method: "POST", + }); + + const batchId = batchIdResponse[1].batch_id; + + if (reminder.workflowStep.action !== WorkflowActions.EMAIL_ADDRESS) { + sendEmailPromises.push( + sendSendgridMail( + { + to: sendTo, + subject: emailContent.emailSubject, + html: emailContent.emailBody, + batchId: batchId, + sendAt: dayjs(reminder.scheduledDate).unix(), + replyTo: reminder.booking.user?.email, + attachments: reminder.workflowStep.includeCalendarEvent + ? [ + { + content: Buffer.from(getiCalEventAsString(reminder.booking) || "").toString( + "base64" + ), + filename: "event.ics", + type: "text/calendar; method=REQUEST", + disposition: "attachment", + contentId: uuidv4(), + }, + ] + : undefined, + }, + { sender: reminder.workflowStep.sender } + ) + ); + } + + await prisma.workflowReminder.update({ + where: { + id: reminder.id, + }, + data: { + scheduled: true, + referenceId: batchId, + }, + }); + } + } catch (error) { + logger.error(`Error scheduling Email with error ${error}`); + } + } else if (reminder.isMandatoryReminder) { + try { + const sendTo = reminder.booking.attendees[0].email; + const name = reminder.booking.attendees[0].name; + const attendeeName = reminder.booking.user?.name; + const timeZone = reminder.booking.attendees[0].timeZone; + + let emailContent = { + emailSubject: "", + emailBody: "", + }; + + const emailBodyEmpty = false; + emailContent = emailReminderTemplate( false, - reminder.workflowStep.action, + WorkflowActions.EMAIL_ATTENDEE, getTimeFormatStringFromUserTimeFormat(reminder.booking.user?.timeFormat), reminder.booking.startTime.toISOString() || "", reminder.booking.endTime.toISOString() || "", @@ -224,61 +297,41 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { name || "", !!reminder.booking.user?.hideBranding ); - } + if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) { + const batchIdResponse = await client.request({ + url: "/v3/mail/batch", + method: "POST", + }); - if (emailContent.emailSubject.length > 0 && !emailBodyEmpty && sendTo) { - const batchIdResponse = await client.request({ - url: "/v3/mail/batch", - method: "POST", - }); + const batchId = batchIdResponse[1].batch_id; - const batchId = batchIdResponse[1].batch_id; - - if (reminder.workflowStep.action !== WorkflowActions.EMAIL_ADDRESS) { sendEmailPromises.push( - sgMail.send({ - to: sendTo, - from: { - email: senderEmail, - name: reminder.workflowStep.sender || "Cal.com", + sendSendgridMail( + { + to: sendTo, + subject: emailContent.emailSubject, + html: emailContent.emailBody, + batchId: batchId, + sendAt: dayjs(reminder.scheduledDate).unix(), + replyTo: reminder.booking.user?.email, }, - subject: emailContent.emailSubject, - html: emailContent.emailBody, - batchId: batchId, - sendAt: dayjs(reminder.scheduledDate).unix(), - replyTo: reminder.booking.user?.email || senderEmail, - mailSettings: { - sandboxMode: { - enable: sandboxMode, - }, - }, - attachments: reminder.workflowStep.includeCalendarEvent - ? [ - { - content: Buffer.from(getiCalEventAsString(reminder.booking) || "").toString("base64"), - filename: "event.ics", - type: "text/calendar; method=REQUEST", - disposition: "attachment", - contentId: uuidv4(), - }, - ] - : undefined, - }) + { sender: reminder.workflowStep?.sender } + ) ); - } - await prisma.workflowReminder.update({ - where: { - id: reminder.id, - }, - data: { - scheduled: true, - referenceId: batchId, - }, - }); + await prisma.workflowReminder.update({ + where: { + id: reminder.id, + }, + data: { + scheduled: true, + referenceId: batchId, + }, + }); + } + } catch (error) { + logger.error(`Error scheduling Email with error ${error}`); } - } catch (error) { - logger.error(`Error scheduling Email with error ${error}`); } } diff --git a/packages/features/ee/workflows/api/scheduleSMSReminders.ts b/packages/features/ee/workflows/api/scheduleSMSReminders.ts index fe88358c64..ef10656b40 100644 --- a/packages/features/ee/workflows/api/scheduleSMSReminders.ts +++ b/packages/features/ee/workflows/api/scheduleSMSReminders.ts @@ -10,7 +10,7 @@ import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/pri import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import { getSenderId } from "../lib/alphanumericSenderIdSupport"; -import * as twilio from "../lib/reminders/smsProviders/twilioProvider"; +import * as twilio from "../lib/reminders/providers/twilioProvider"; import type { VariablesType } from "../lib/reminders/templates/customTemplate"; import customTemplate from "../lib/reminders/templates/customTemplate"; import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate"; diff --git a/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts b/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts index 8b05d276cc..c4021c4712 100644 --- a/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts +++ b/packages/features/ee/workflows/api/scheduleWhatsappReminders.ts @@ -8,7 +8,7 @@ import prisma from "@calcom/prisma"; import { WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums"; import { getWhatsappTemplateFunction } from "../lib/actionHelperFunctions"; -import * as twilio from "../lib/reminders/smsProviders/twilioProvider"; +import * as twilio from "../lib/reminders/providers/twilioProvider"; async function handler(req: NextApiRequest, res: NextApiResponse) { const apiKey = req.headers.authorization || req.query.apiKey; diff --git a/packages/features/ee/workflows/lib/getWorkflowReminders.ts b/packages/features/ee/workflows/lib/getWorkflowReminders.ts index fe16bead7c..e251397aa4 100644 --- a/packages/features/ee/workflows/lib/getWorkflowReminders.ts +++ b/packages/features/ee/workflows/lib/getWorkflowReminders.ts @@ -26,7 +26,10 @@ type PartialBooking = > & { eventType: Partial<EventType> | null } & { user: Partial<User> | null }) | null; -export type PartialWorkflowReminder = Pick<WorkflowReminder, "id" | "scheduledDate"> & { +export type PartialWorkflowReminder = Pick< + WorkflowReminder, + "id" | "isMandatoryReminder" | "scheduledDate" +> & { booking: PartialBooking | null; } & { workflowStep: PartialWorkflowStep }; @@ -113,6 +116,7 @@ export async function getAllUnscheduledReminders(): Promise<PartialWorkflowRemin const select: Prisma.WorkflowReminderSelect = { id: true, scheduledDate: true, + isMandatoryReminder: true, workflowStep: { select: { action: true, diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index f4c36b5d14..e45cdaa920 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -1,6 +1,4 @@ -import client from "@sendgrid/client"; import type { MailData } from "@sendgrid/helpers/classes/mail"; -import sgMail from "@sendgrid/mail"; import { createEvent } from "ics"; import type { ParticipationStatus } from "ics"; import type { DateArray } from "ics"; @@ -20,33 +18,13 @@ import { } from "@calcom/prisma/enums"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; +import { getBatchId, sendSendgridMail } from "./providers/sendgridProvider"; import type { AttendeeInBookingInfo, BookingInfo, timeUnitLowerCase } from "./smsReminderManager"; import type { VariablesType } from "./templates/customTemplate"; import customTemplate from "./templates/customTemplate"; import emailReminderTemplate from "./templates/emailReminderTemplate"; -let sendgridAPIKey, senderEmail: string; - const log = logger.getSubLogger({ prefix: ["[emailReminderManager]"] }); -if (process.env.SENDGRID_API_KEY) { - sendgridAPIKey = process.env.SENDGRID_API_KEY as string; - senderEmail = process.env.SENDGRID_EMAIL as string; - - sgMail.setApiKey(sendgridAPIKey); - client.setApiKey(sendgridAPIKey); -} - -async function getBatchId() { - if (!process.env.SENDGRID_API_KEY) { - console.info("No sendgrid API key provided, returning DUMMY_BATCH_ID"); - return "DUMMY_BATCH_ID"; - } - const batchIdResponse = await client.request({ - url: "/v3/mail/batch", - method: "POST", - }); - return batchIdResponse[1].batch_id as string; -} function getiCalEventAsString(evt: BookingInfo, status?: ParticipationStatus) { const uid = uuidv4(); @@ -94,24 +72,46 @@ type ScheduleEmailReminderAction = Extract< "EMAIL_HOST" | "EMAIL_ATTENDEE" | "EMAIL_ADDRESS" >; -export const scheduleEmailReminder = async ( - evt: BookingInfo, - triggerEvent: WorkflowTriggerEvents, - action: ScheduleEmailReminderAction, +export interface ScheduleReminderArgs { + evt: BookingInfo; + triggerEvent: WorkflowTriggerEvents; timeSpan: { time: number | null; timeUnit: TimeUnit | null; - }, - sendTo: MailData["to"], - emailSubject: string, - emailBody: string, - workflowStepId: number, - template: WorkflowTemplates, - sender: string, - hideBranding?: boolean, - seatReferenceUid?: string, - includeCalendarEvent?: boolean -) => { + }; + template: WorkflowTemplates; + sender?: string | null; + workflowStepId?: number; + seatReferenceUid?: string; +} + +interface scheduleEmailReminderArgs extends ScheduleReminderArgs { + sendTo: MailData["to"]; + action: ScheduleEmailReminderAction; + emailSubject?: string; + emailBody?: string; + hideBranding?: boolean; + includeCalendarEvent?: boolean; + isMandatoryReminder?: boolean; +} + +export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => { + const { + evt, + triggerEvent, + timeSpan, + template, + sender, + workflowStepId, + seatReferenceUid, + sendTo, + emailSubject = "", + emailBody = "", + hideBranding, + includeCalendarEvent, + isMandatoryReminder, + action, + } = args; if (action === WorkflowActions.EMAIL_ADDRESS) return; const { startTime, endTime } = evt; const uid = evt.uid as string; @@ -126,12 +126,6 @@ export const scheduleEmailReminder = async ( scheduledDate = timeSpan.time && timeUnit ? dayjs(endTime).add(timeSpan.time, timeUnit) : null; } - if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) { - console.error("Sendgrid credentials are missing from the .env file"); - } - - const sandboxMode = process.env.NEXT_PUBLIC_IS_E2E ? true : false; - let attendeeEmailToBeUsedInMail: string | null = null; let attendeeToBeUsedInMail: AttendeeInBookingInfo | null = null; let name = ""; @@ -235,11 +229,6 @@ export const scheduleEmailReminder = async ( const batchId = await getBatchId(); function sendEmail(data: Partial<MailData>, triggerEvent?: WorkflowTriggerEvents) { - if (!process.env.SENDGRID_API_KEY) { - console.info("No sendgrid API key provided, skipping email"); - return Promise.resolve(); - } - const status: ParticipationStatus = triggerEvent === WorkflowTriggerEvents.AFTER_EVENT ? "COMPLETED" @@ -247,34 +236,28 @@ export const scheduleEmailReminder = async ( ? "DECLINED" : "ACCEPTED"; - return sgMail.send({ - to: data.to, - from: { - email: senderEmail, - name: sender, + return sendSendgridMail( + { + to: data.to, + subject: emailContent.emailSubject, + html: emailContent.emailBody, + batchId, + replyTo: evt.organizer.email, + attachments: includeCalendarEvent + ? [ + { + content: Buffer.from(getiCalEventAsString(evt, status) || "").toString("base64"), + filename: "event.ics", + type: "text/calendar; method=REQUEST", + disposition: "attachment", + contentId: uuidv4(), + }, + ] + : undefined, + sendAt: data.sendAt, }, - subject: emailContent.emailSubject, - html: emailContent.emailBody, - batchId, - replyTo: evt.organizer.email, - mailSettings: { - sandboxMode: { - enable: sandboxMode, - }, - }, - attachments: includeCalendarEvent - ? [ - { - content: Buffer.from(getiCalEventAsString(evt, status) || "").toString("base64"), - filename: "event.ics", - type: "text/calendar; method=REQUEST", - disposition: "attachment", - contentId: uuidv4(), - }, - ] - : undefined, - sendAt: data.sendAt, - }); + { sender } + ); } if ( @@ -289,7 +272,7 @@ export const scheduleEmailReminder = async ( // TODO: Maybe don't await for this? await Promise.all(promises); } catch (error) { - console.log("Error sending Email"); + log.error("Error sending Email"); } } else if ( (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || @@ -311,32 +294,59 @@ export const scheduleEmailReminder = async ( }, triggerEvent ); + if (!isMandatoryReminder) { + await prisma.workflowReminder.create({ + data: { + bookingUid: uid, + workflowStepId: workflowStepId, + method: WorkflowMethods.EMAIL, + scheduledDate: scheduledDate.toDate(), + scheduled: true, + referenceId: batchId, + seatReferenceId: seatReferenceUid, + }, + }); + } else { + await prisma.workflowReminder.create({ + data: { + bookingUid: uid, + method: WorkflowMethods.EMAIL, + scheduledDate: scheduledDate.toDate(), + scheduled: true, + referenceId: batchId, + seatReferenceId: seatReferenceUid, + isMandatoryReminder: true, + }, + }); + } + } catch (error) { + log.error(`Error scheduling email with error ${error}`); + } + } else if (scheduledDate.isAfter(currentDate.add(72, "hour"))) { + // Write to DB and send to CRON if scheduled reminder date is past 72 hours + if (!isMandatoryReminder) { await prisma.workflowReminder.create({ data: { bookingUid: uid, workflowStepId: workflowStepId, method: WorkflowMethods.EMAIL, scheduledDate: scheduledDate.toDate(), - scheduled: true, - referenceId: batchId, + scheduled: false, seatReferenceId: seatReferenceUid, }, }); - } catch (error) { - console.log(`Error scheduling email with error ${error}`); + } else { + await prisma.workflowReminder.create({ + data: { + bookingUid: uid, + method: WorkflowMethods.EMAIL, + scheduledDate: scheduledDate.toDate(), + scheduled: false, + seatReferenceId: seatReferenceUid, + isMandatoryReminder: true, + }, + }); } - } else if (scheduledDate.isAfter(currentDate.add(72, "hour"))) { - // Write to DB and send to CRON if scheduled reminder date is past 72 hours - await prisma.workflowReminder.create({ - data: { - bookingUid: uid, - workflowStepId: workflowStepId, - method: WorkflowMethods.EMAIL, - scheduledDate: scheduledDate.toDate(), - scheduled: false, - seatReferenceId: seatReferenceUid, - }, - }); } } }; @@ -362,6 +372,6 @@ export const deleteScheduledEmailReminder = async (reminderId: number, reference }, }); } catch (error) { - console.log(`Error canceling reminder with error ${error}`); + log.error(`Error canceling reminder with error ${error}`); } }; diff --git a/packages/features/ee/workflows/lib/reminders/providers/sendgridProvider.ts b/packages/features/ee/workflows/lib/reminders/providers/sendgridProvider.ts new file mode 100644 index 0000000000..f4ba166af8 --- /dev/null +++ b/packages/features/ee/workflows/lib/reminders/providers/sendgridProvider.ts @@ -0,0 +1,79 @@ +import client from "@sendgrid/client"; +import type { MailData } from "@sendgrid/helpers/classes/mail"; +import sgMail from "@sendgrid/mail"; + +import { SENDER_NAME } from "@calcom/lib/constants"; +import { setTestEmail } from "@calcom/lib/testEmails"; + +let sendgridAPIKey: string; +let senderEmail: string; + +function assertSendgrid() { + if (process.env.SENDGRID_API_KEY && process.env.SENDGRID_EMAIL) { + sendgridAPIKey = process.env.SENDGRID_API_KEY as string; + senderEmail = process.env.SENDGRID_EMAIL as string; + sgMail.setApiKey(sendgridAPIKey); + client.setApiKey(sendgridAPIKey); + } else { + console.error("Sendgrid credentials are missing from the .env file"); + } +} + +export async function getBatchId() { + assertSendgrid(); + if (!process.env.SENDGRID_API_KEY) { + console.info("No sendgrid API key provided, returning DUMMY_BATCH_ID"); + return "DUMMY_BATCH_ID"; + } + const batchIdResponse = await client.request({ + url: "/v3/mail/batch", + method: "POST", + }); + return batchIdResponse[1].batch_id as string; +} + +export function sendSendgridMail( + mailData: Partial<MailData>, + addData: { sender?: string | null; includeCalendarEvent?: boolean } +) { + assertSendgrid(); + + const testMode = process.env.NEXT_PUBLIC_IS_E2E || process.env.INTEGRATION_TEST_MODE; + if (testMode) { + if (!mailData.sendAt) { + setTestEmail({ + to: mailData.to?.toString() || "", + from: { + email: senderEmail, + name: addData.sender || SENDER_NAME, + }, + subject: mailData.subject || "", + html: mailData.html || "", + }); + } + console.log( + "Skipped Sending Email as process.env.NEXT_PUBLIC_IS_E2E or process.env.INTEGRATION_TEST_MODE is set. Emails are available in globalThis.testEmails" + ); + + return new Promise((r) => r("Skipped sendEmail for Unit Tests")); + } + + if (!sendgridAPIKey) { + console.info("No sendgrid API key provided, skipping email"); + return Promise.resolve(); + } + + return sgMail.send({ + to: mailData.to, + from: { + email: senderEmail, + name: addData.sender || SENDER_NAME, + }, + subject: mailData.subject, + html: mailData.html || "", + batchId: mailData.batchId, + replyTo: mailData.replyTo || senderEmail, + attachments: mailData.attachments, + sendAt: mailData.sendAt, + }); +} diff --git a/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts b/packages/features/ee/workflows/lib/reminders/providers/twilioProvider.ts similarity index 100% rename from packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts rename to packages/features/ee/workflows/lib/reminders/providers/twilioProvider.ts diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index 7104fa1830..c23291ab94 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -1,14 +1,16 @@ import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; import { + isSMSAction, isTextMessageToAttendeeAction, isWhatsappAction, } from "@calcom/features/ee/workflows/lib/actionHelperFunctions"; -import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; +import { SENDER_NAME } from "@calcom/lib/constants"; import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { deleteScheduledEmailReminder, scheduleEmailReminder } from "./emailReminderManager"; +import type { ScheduleTextReminderAction } from "./smsReminderManager"; import { deleteScheduledSMSReminder, scheduleSMSReminder } from "./smsReminderManager"; import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder } from "./whatsappReminderManager"; @@ -51,26 +53,26 @@ const processWorkflowStep = async ( ) => { if (isTextMessageToAttendeeAction(step.action) && !eventTypeRequiresConfirmation) return; - if (step.action === WorkflowActions.SMS_ATTENDEE || step.action === WorkflowActions.SMS_NUMBER) { + if (isSMSAction(step.action)) { const sendTo = step.action === WorkflowActions.SMS_ATTENDEE ? smsReminderNumber : step.sendTo; - await scheduleSMSReminder( + await scheduleSMSReminder({ evt, - sendTo, - workflow.trigger, - step.action, - { + reminderPhone: sendTo, + triggerEvent: workflow.trigger, + action: step.action as ScheduleTextReminderAction, + timeSpan: { time: workflow.time, timeUnit: workflow.timeUnit, }, - step.reminderBody || "", - step.id, - step.template, - step.sender || SENDER_ID, - workflow.userId, - workflow.teamId, - step.numberVerificationPending, - seatReferenceUid - ); + message: step.reminderBody || "", + workflowStepId: step.id, + template: step.template, + sender: step.sender, + userId: workflow.userId, + teamId: workflow.teamId, + isVerificationPending: step.numberVerificationPending, + seatReferenceUid, + }); } else if (step.action === WorkflowActions.EMAIL_ATTENDEE || step.action === WorkflowActions.EMAIL_HOST) { let sendTo: string[] = []; @@ -88,43 +90,43 @@ const processWorkflowStep = async ( break; } - await scheduleEmailReminder( + await scheduleEmailReminder({ evt, - workflow.trigger, - step.action, - { + triggerEvent: workflow.trigger, + action: step.action, + timeSpan: { time: workflow.time, timeUnit: workflow.timeUnit, }, sendTo, - step.emailSubject || "", - step.reminderBody || "", - step.id, - step.template, - step.sender || SENDER_NAME, + emailSubject: step.emailSubject || "", + emailBody: step.reminderBody || "", + template: step.template, + sender: step.sender || SENDER_NAME, + workflowStepId: step.id, hideBranding, seatReferenceUid, - step.includeCalendarEvent - ); + includeCalendarEvent: step.includeCalendarEvent, + }); } else if (isWhatsappAction(step.action)) { const sendTo = step.action === WorkflowActions.WHATSAPP_ATTENDEE ? smsReminderNumber : step.sendTo; - await scheduleWhatsappReminder( + await scheduleWhatsappReminder({ evt, - sendTo, - workflow.trigger, - step.action, - { + reminderPhone: sendTo, + triggerEvent: workflow.trigger, + action: step.action as ScheduleTextReminderAction, + timeSpan: { time: workflow.time, timeUnit: workflow.timeUnit, }, - step.reminderBody || "", - step.id, - step.template, - workflow.userId, - workflow.teamId, - step.numberVerificationPending, - seatReferenceUid - ); + message: step.reminderBody || "", + workflowStepId: step.id, + template: step.template, + userId: workflow.userId, + teamId: workflow.teamId, + isVerificationPending: step.numberVerificationPending, + seatReferenceUid, + }); } }; @@ -164,7 +166,6 @@ export const scheduleWorkflowReminders = async (args: ScheduleWorkflowRemindersA ) { continue; } - for (const step of workflow.steps) { await processWorkflowStep(workflow, step, { calendarEvent: evt, diff --git a/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts new file mode 100644 index 0000000000..3512e9f9f1 --- /dev/null +++ b/packages/features/ee/workflows/lib/reminders/scheduleMandatoryReminder.ts @@ -0,0 +1,72 @@ +import type { Workflow, WorkflowsOnEventTypes, WorkflowStep } from "@prisma/client"; + +import type { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking"; +import { scheduleEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; +import type { BookingInfo } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; +import type { getDefaultEvent } from "@calcom/lib/defaultEvents"; +import logger from "@calcom/lib/logger"; +import { WorkflowTriggerEvents, TimeUnit, WorkflowActions, WorkflowTemplates } from "@calcom/prisma/enums"; + +const log = logger.getSubLogger({ prefix: ["[scheduleMandatoryReminder]"] }); + +export type NewBookingEventType = + | Awaited<ReturnType<typeof getDefaultEvent>> + | Awaited<ReturnType<typeof getEventTypesFromDB>>; + +export async function scheduleMandatoryReminder( + evt: BookingInfo, + workflows: (WorkflowsOnEventTypes & { + workflow: Workflow & { + steps: WorkflowStep[]; + }; + })[], + requiresConfirmation: boolean, + hideBranding: boolean, + seatReferenceUid: string | undefined +) { + try { + const hasExistingWorkflow = workflows.some((workflow) => { + return ( + workflow.workflow?.trigger === WorkflowTriggerEvents.BEFORE_EVENT && + ((workflow.workflow.time !== null && + workflow.workflow.time <= 12 && + workflow.workflow?.timeUnit === TimeUnit.HOUR) || + (workflow.workflow.time !== null && + workflow.workflow.time <= 720 && + workflow.workflow?.timeUnit === TimeUnit.MINUTE)) && + workflow.workflow?.steps.some((step) => step?.action === WorkflowActions.EMAIL_ATTENDEE) + ); + }); + + if ( + !hasExistingWorkflow && + evt.attendees.some((attendee) => attendee.email.includes("@gmail.com")) && + !requiresConfirmation + ) { + try { + const filteredAttendees = + evt.attendees?.filter((attendee) => attendee.email.includes("@gmail.com")) || []; + + await scheduleEmailReminder({ + evt, + triggerEvent: WorkflowTriggerEvents.BEFORE_EVENT, + action: WorkflowActions.EMAIL_ATTENDEE, + timeSpan: { + time: 1, + timeUnit: TimeUnit.HOUR, + }, + sendTo: filteredAttendees, + template: WorkflowTemplates.REMINDER, + hideBranding, + seatReferenceUid, + includeCalendarEvent: false, + isMandatoryReminder: true, + }); + } catch (error) { + log.error("Error while scheduling mandatory reminders", JSON.stringify({ error })); + } + } + } catch (error) { + log.error("Error while scheduling mandatory reminders", JSON.stringify({ error })); + } +} diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts index 10fb2166d1..176c8ca8fc 100644 --- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts @@ -1,16 +1,17 @@ import dayjs from "@calcom/dayjs"; +import { SENDER_ID } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import type { TimeFormat } from "@calcom/lib/timeFormat"; import prisma from "@calcom/prisma"; import type { Prisma } from "@calcom/prisma/client"; -import type { TimeUnit } from "@calcom/prisma/enums"; import { WorkflowTemplates, WorkflowActions, WorkflowMethods } from "@calcom/prisma/enums"; import { WorkflowTriggerEvents } from "@calcom/prisma/enums"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import type { CalEventResponses, RecurringEvent } from "@calcom/types/Calendar"; import { getSenderId } from "../alphanumericSenderIdSupport"; -import * as twilio from "./smsProviders/twilioProvider"; +import type { ScheduleReminderArgs } from "./emailReminderManager"; +import * as twilio from "./providers/twilioProvider"; import type { VariablesType } from "./templates/customTemplate"; import customTemplate from "./templates/customTemplate"; import smsReminderTemplate from "./templates/smsReminderTemplate"; @@ -55,33 +56,42 @@ export type BookingInfo = { metadata?: Prisma.JsonValue; }; -type ScheduleSMSReminderAction = Extract<WorkflowActions, "SMS_ATTENDEE" | "SMS_NUMBER">; +export type ScheduleTextReminderAction = Extract< + WorkflowActions, + "SMS_ATTENDEE" | "SMS_NUMBER" | "WHATSAPP_ATTENDEE" | "WHATSAPP_NUMBER" +>; +export interface ScheduleTextReminderArgs extends ScheduleReminderArgs { + reminderPhone: string | null; + message: string; + action: ScheduleTextReminderAction; + userId?: number | null; + teamId?: number | null; + isVerificationPending?: boolean; +} -export const scheduleSMSReminder = async ( - evt: BookingInfo, - reminderPhone: string | null, - triggerEvent: WorkflowTriggerEvents, - action: ScheduleSMSReminderAction, - timeSpan: { - time: number | null; - timeUnit: TimeUnit | null; - }, - message: string, - workflowStepId: number, - template: WorkflowTemplates, - sender: string, - userId?: number | null, - teamId?: number | null, - isVerificationPending = false, - seatReferenceUid?: string -) => { +export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => { + const { + evt, + reminderPhone, + triggerEvent, + action, + timeSpan, + message = "", + workflowStepId, + template, + sender, + userId, + teamId, + isVerificationPending = false, + seatReferenceUid, + } = args; const { startTime, endTime } = evt; const uid = evt.uid as string; const currentDate = dayjs(); const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase; let scheduledDate = null; - const senderID = getSenderId(reminderPhone, sender); + const senderID = getSenderId(reminderPhone, sender || SENDER_ID); //SMS_ATTENDEE action does not need to be verified //isVerificationPending is from all already existing workflows (once they edit their workflow, they will also have to verify the number) @@ -126,7 +136,9 @@ export const scheduleSMSReminder = async ( ? attendeeToBeUsedInSMS.language?.locale : evt.organizer.language.locale; - if (message) { + let smsMessage = message; + + if (smsMessage) { const variables: VariablesType = { eventName: evt.title, organizerName: evt.organizer.name, @@ -144,10 +156,10 @@ export const scheduleSMSReminder = async ( cancelLink: `/booking/${evt.uid}?cancel=true`, rescheduleLink: `/${evt.organizer.username}/${evt.eventType.slug}?rescheduleUid=${evt.uid}`, }; - const customMessage = customTemplate(message, variables, locale, evt.organizer.timeFormat); - message = customMessage.text; + const customMessage = customTemplate(smsMessage, variables, locale, evt.organizer.timeFormat); + smsMessage = customMessage.text; } else if (template === WorkflowTemplates.REMINDER) { - message = + smsMessage = smsReminderTemplate( false, action, @@ -161,9 +173,9 @@ export const scheduleSMSReminder = async ( } // Allows debugging generated email content without waiting for sendgrid to send emails - log.debug(`Sending sms for trigger ${triggerEvent}`, message); + log.debug(`Sending sms for trigger ${triggerEvent}`, smsMessage); - if (message.length > 0 && reminderPhone && isNumberVerified) { + if (smsMessage.length > 0 && reminderPhone && isNumberVerified) { //send SMS when event is booked/cancelled/rescheduled if ( triggerEvent === WorkflowTriggerEvents.NEW_EVENT || @@ -171,9 +183,9 @@ export const scheduleSMSReminder = async ( triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT ) { try { - await twilio.sendSMS(reminderPhone, message, senderID); + await twilio.sendSMS(reminderPhone, smsMessage, senderID); } catch (error) { - console.log(`Error sending SMS with error ${error}`); + log.error(`Error sending SMS with error ${error}`); } } else if ( (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT || @@ -188,7 +200,7 @@ export const scheduleSMSReminder = async ( try { const scheduledSMS = await twilio.scheduleSMS( reminderPhone, - message, + smsMessage, scheduledDate.toDate(), senderID ); @@ -205,7 +217,7 @@ export const scheduleSMSReminder = async ( }, }); } catch (error) { - console.log(`Error scheduling SMS with error ${error}`); + log.error(`Error scheduling SMS with error ${error}`); } } else if (scheduledDate.isAfter(currentDate.add(7, "day"))) { // Write to DB and send to CRON if scheduled reminder date is past 7 days @@ -236,6 +248,6 @@ export const deleteScheduledSMSReminder = async (reminderId: number, referenceId }, }); } catch (error) { - console.log(`Error canceling reminder with error ${error}`); + log.error(`Error canceling reminder with error ${error}`); } }; diff --git a/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts b/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts index 6041747e5d..03c079335b 100644 --- a/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts +++ b/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts @@ -1,6 +1,6 @@ import prisma from "@calcom/prisma"; -import * as twilio from "./smsProviders/twilioProvider"; +import * as twilio from "./providers/twilioProvider"; export const sendVerificationCode = async (phoneNumber: string) => { return twilio.sendVerificationCode(phoneNumber); diff --git a/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts b/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts index 2945f0e55e..6652924898 100644 --- a/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/whatsappReminderManager.ts @@ -1,5 +1,3 @@ -import type { TimeUnit } from "@prisma/client"; - import dayjs from "@calcom/dayjs"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; @@ -10,8 +8,8 @@ import { WorkflowMethods, } from "@calcom/prisma/enums"; -import * as twilio from "./smsProviders/twilioProvider"; -import type { BookingInfo, timeUnitLowerCase } from "./smsReminderManager"; +import * as twilio from "./providers/twilioProvider"; +import type { ScheduleTextReminderArgs, timeUnitLowerCase } from "./smsReminderManager"; import { deleteScheduledSMSReminder } from "./smsReminderManager"; import { whatsappEventCancelledTemplate, @@ -22,23 +20,21 @@ import { const log = logger.getSubLogger({ prefix: ["[whatsappReminderManager]"] }); -export const scheduleWhatsappReminder = async ( - evt: BookingInfo, - reminderPhone: string | null, - triggerEvent: WorkflowTriggerEvents, - action: WorkflowActions, - timeSpan: { - time: number | null; - timeUnit: TimeUnit | null; - }, - message: string, - workflowStepId: number, - template: WorkflowTemplates, - userId?: number | null, - teamId?: number | null, - isVerificationPending = false, - seatReferenceUid?: string -) => { +export const scheduleWhatsappReminder = async (args: ScheduleTextReminderArgs) => { + const { + evt, + reminderPhone, + triggerEvent, + action, + timeSpan, + message = "", + workflowStepId, + template, + userId, + teamId, + isVerificationPending = false, + seatReferenceUid, + } = args; const { startTime, endTime } = evt; const uid = evt.uid as string; const currentDate = dayjs(); @@ -72,9 +68,11 @@ export const scheduleWhatsappReminder = async ( const timeZone = action === WorkflowActions.WHATSAPP_ATTENDEE ? evt.attendees[0].timeZone : evt.organizer.timeZone; + let textMessage = message; + switch (template) { case WorkflowTemplates.REMINDER: - message = + textMessage = whatsappReminderTemplate( false, action, @@ -87,7 +85,7 @@ export const scheduleWhatsappReminder = async ( ) || message; break; case WorkflowTemplates.CANCELLED: - message = + textMessage = whatsappEventCancelledTemplate( false, action, @@ -100,7 +98,7 @@ export const scheduleWhatsappReminder = async ( ) || message; break; case WorkflowTemplates.RESCHEDULED: - message = + textMessage = whatsappEventRescheduledTemplate( false, action, @@ -113,7 +111,7 @@ export const scheduleWhatsappReminder = async ( ) || message; break; case WorkflowTemplates.COMPLETED: - message = + textMessage = whatsappEventCompletedTemplate( false, action, @@ -126,7 +124,7 @@ export const scheduleWhatsappReminder = async ( ) || message; break; default: - message = + textMessage = whatsappReminderTemplate( false, action, @@ -140,8 +138,8 @@ export const scheduleWhatsappReminder = async ( } // Allows debugging generated whatsapp content without waiting for twilio to send whatsapp messages - log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, message); - if (message.length > 0 && reminderPhone && isNumberVerified) { + log.debug(`Sending Whatsapp for trigger ${triggerEvent}`, textMessage); + if (textMessage.length > 0 && reminderPhone && isNumberVerified) { //send WHATSAPP when event is booked/cancelled/rescheduled if ( triggerEvent === WorkflowTriggerEvents.NEW_EVENT || @@ -149,7 +147,7 @@ export const scheduleWhatsappReminder = async ( triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT ) { try { - await twilio.sendSMS(reminderPhone, message, "", true); + await twilio.sendSMS(reminderPhone, textMessage, "", true); } catch (error) { console.log(`Error sending WHATSAPP with error ${error}`); } @@ -166,7 +164,7 @@ export const scheduleWhatsappReminder = async ( try { const scheduledWHATSAPP = await twilio.scheduleSMS( reminderPhone, - message, + textMessage, scheduledDate.toDate(), "", true diff --git a/packages/features/ee/workflows/pages/index.tsx b/packages/features/ee/workflows/pages/index.tsx index 41ddc24559..c0714a3898 100644 --- a/packages/features/ee/workflows/pages/index.tsx +++ b/packages/features/ee/workflows/pages/index.tsx @@ -147,7 +147,7 @@ const Filter = (props: { <input id="yourWorkflows" type="checkbox" - className="text-primary-600 focus:ring-primary-500 border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded " + className="text-emphasis focus:ring-emphasis dark:text-muted border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded " checked={!!checked.userId} onChange={(e) => { if (e.target.checked) { @@ -211,7 +211,7 @@ const Filter = (props: { } } }} - className="text-primary-600 focus:ring-primary-500 border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded " + className="text-emphasis focus:ring-emphasis dark:text-muted border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded " /> </div> ))} diff --git a/packages/features/embed/Embed.tsx b/packages/features/embed/Embed.tsx index 50e023ba76..1b4174f1eb 100644 --- a/packages/features/embed/Embed.tsx +++ b/packages/features/embed/Embed.tsx @@ -16,7 +16,6 @@ import { useBookerStore, useInitializeBookerStore } from "@calcom/features/booki import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event"; import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences"; import DatePicker from "@calcom/features/calendars/DatePicker"; -import { useFlagMap } from "@calcom/features/flags/context/provider"; import { useNonEmptyScheduleDays } from "@calcom/features/schedules"; import { useSlotsForDate } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate"; import { APP_NAME, CAL_URL } from "@calcom/lib/constants"; @@ -498,12 +497,14 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ embedType, embedUrl, tabs, + namespace, eventTypeHideOptionDisabled, types, }: { embedType: EmbedType; embedUrl: string; tabs: EmbedTabs; + namespace: string; eventTypeHideOptionDisabled: boolean; types: EmbedTypes; }) => { @@ -513,8 +514,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ const { goto, removeQueryParams } = useRouterHelpers(); const iframeRef = useRef<HTMLIFrameElement>(null); const dialogContentRef = useRef<HTMLDivElement>(null); - const flags = useFlagMap(); - const isBookerLayoutsEnabled = flags["booker-layouts"] === true; const emailContentRef = useRef<HTMLDivElement>(null); const { data } = useSession(); const [month, selectedDatesAndTimes] = useBookerStore( @@ -955,34 +954,32 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ </div> </Label> ))} - {isBookerLayoutsEnabled && ( - <Label className="mb-6"> - <div className="mb-2">{t("layout")}</div> - <Select - className="w-full" - defaultValue={layoutOptions[0]} - onChange={(option) => { - if (!option) { - return; - } - setPreviewState((previewState) => { - const config = { - ...(previewState.floatingPopup.config ?? {}), - layout: option.value, - }; - return { - ...previewState, - floatingPopup: { - config, - }, - layout: option.value, - }; - }); - }} - options={layoutOptions} - /> - </Label> - )} + <Label className="mb-6"> + <div className="mb-2">{t("layout")}</div> + <Select + className="w-full" + defaultValue={layoutOptions[0]} + onChange={(option) => { + if (!option) { + return; + } + setPreviewState((previewState) => { + const config = { + ...(previewState.floatingPopup.config ?? {}), + layout: option.value, + }; + return { + ...previewState, + floatingPopup: { + config, + }, + layout: option.value, + }; + }); + }} + options={layoutOptions} + /> + </Label> </div> </CollapsibleContent> </Collapsible> @@ -1009,6 +1006,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ <div className="flex h-[55vh] flex-grow flex-col"> {tab.type === "code" ? ( <tab.Component + namespace={namespace} embedType={embedType} calLink={calLink} previewState={previewState} @@ -1016,6 +1014,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({ /> ) : ( <tab.Component + namespace={namespace} embedType={embedType} calLink={calLink} previewState={previewState} @@ -1097,7 +1096,8 @@ export const EmbedDialog = ({ eventTypeHideOptionDisabled: boolean; }) => { const searchParams = useCompatSearchParams(); - const embedUrl = searchParams?.get("embedUrl") as string; + const embedUrl = (searchParams?.get("embedUrl") || "") as string; + const namespace = (searchParams?.get("namespace") || "") as string; return ( <Dialog name="embed" clearQueryParamsOnClose={queryParamsForDialog}> {!searchParams?.get("embedType") ? ( @@ -1106,6 +1106,7 @@ export const EmbedDialog = ({ <EmbedTypeCodeAndPreviewDialogContent embedType={searchParams?.get("embedType") as EmbedType} embedUrl={embedUrl} + namespace={namespace} tabs={tabs} types={types} eventTypeHideOptionDisabled={eventTypeHideOptionDisabled} @@ -1117,6 +1118,7 @@ export const EmbedDialog = ({ type EmbedButtonProps<T> = { embedUrl: string; + namespace: string; children?: React.ReactNode; className?: string; as?: T; @@ -1129,6 +1131,7 @@ export const EmbedButton = <T extends React.ElementType>({ className = "", as, eventId, + namespace, ...props }: EmbedButtonProps<T> & React.ComponentPropsWithoutRef<T>) => { const { goto } = useRouterHelpers(); @@ -1137,6 +1140,7 @@ export const EmbedButton = <T extends React.ElementType>({ goto({ dialog: "embed", eventId: eventId ? eventId.toString() : "", + namespace, embedUrl, }); }; diff --git a/packages/features/embed/lib/EmbedCodes.tsx b/packages/features/embed/lib/EmbedCodes.tsx index 53ca519e94..105544ece7 100644 --- a/packages/features/embed/lib/EmbedCodes.tsx +++ b/packages/features/embed/lib/EmbedCodes.tsx @@ -2,6 +2,7 @@ import { CAL_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants"; import type { PreviewState } from "../types"; import { embedLibUrl } from "./constants"; +import { getApiName } from "./getApiName"; import { getDimension } from "./getDimension"; export const doWeNeedCalOriginProp = (embedCalOrigin: string) => { @@ -18,14 +19,17 @@ export const Codes = { uiInstructionCode, previewState, embedCalOrigin, + namespace, }: { calLink: string; uiInstructionCode: string; previewState: PreviewState; embedCalOrigin: string; + namespace: string; }) => { const width = getDimension(previewState.inline.width); const height = getDimension(previewState.inline.height); + const namespaceProp = `${namespace ? `namespace="${namespace}"` : ""}`; return code` import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; @@ -36,21 +40,23 @@ export const Codes = { ${uiInstructionCode} })(); }, []) - return <Cal + return <Cal ${namespaceProp} calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} ${previewState.layout ? `config={{layout: '${previewState.layout}'}}` : ""} ${doWeNeedCalOriginProp(embedCalOrigin) ? ` calOrigin="${embedCalOrigin}"` : ""} - ${IS_SELF_HOSTED ? `calJsUrl="${embedLibUrl}"` : ""} + ${IS_SELF_HOSTED ? `embedJsUrl="${embedLibUrl}"` : ""} />; };`; }, "floating-popup": ({ floatingButtonArg, uiInstructionCode, + namespace, }: { floatingButtonArg: string; uiInstructionCode: string; + namespace: string; }) => { return code` import { getCalApi } from "@calcom/embed-react"; @@ -59,7 +65,7 @@ export const Codes = { useEffect(()=>{ (async function () { const cal = await getCalApi(${IS_SELF_HOSTED ? `"${embedLibUrl}"` : ""}); - cal("floatingButton", ${floatingButtonArg}); + ${getApiName({ namespace, mainApiName: "cal" })}("floatingButton", ${floatingButtonArg}); ${uiInstructionCode} })(); }, []) @@ -70,11 +76,13 @@ export const Codes = { uiInstructionCode, previewState, embedCalOrigin, + namespace, }: { calLink: string; uiInstructionCode: string; previewState: PreviewState; embedCalOrigin: string; + namespace: string; }) => { return code` import { getCalApi } from "@calcom/embed-react"; @@ -86,7 +94,7 @@ export const Codes = { ${uiInstructionCode} })(); }, []) - return <button + return <button data-cal-namespace="${namespace}" data-cal-link="${calLink}" ${doWeNeedCalOriginProp(embedCalOrigin) ? ` data-cal-origin="${embedCalOrigin}"` : ""} ${`data-cal-config='${JSON.stringify({ @@ -101,12 +109,14 @@ export const Codes = { calLink, uiInstructionCode, previewState, + namespace, }: { calLink: string; uiInstructionCode: string; previewState: PreviewState; + namespace: string; }) => { - return code`Cal("inline", { + return code`${getApiName({ namespace })}("inline", { elementOrSelector:"#my-cal-inline", calLink: "${calLink}", layout: "${previewState.layout}" @@ -118,25 +128,30 @@ export const Codes = { "floating-popup": ({ floatingButtonArg, uiInstructionCode, + namespace, }: { floatingButtonArg: string; uiInstructionCode: string; + namespace: string; }) => { - return code`Cal("floatingButton", ${floatingButtonArg}); + return code`${getApiName({ namespace, mainApiName: "Cal" })}("floatingButton", ${floatingButtonArg}); ${uiInstructionCode}`; }, "element-click": ({ calLink, uiInstructionCode, previewState, + namespace, }: { calLink: string; uiInstructionCode: string; previewState: PreviewState; + namespace: string; }) => { return code` - // Important: Please add following attributes to the element you want to open Cal on click + // Important: Please add the following attributes to the element that should trigger the calendar to open upon clicking. // \`data-cal-link="${calLink}"\` + // data-cal-namespace="${namespace}" // \`data-cal-config='${JSON.stringify({ layout: previewState.layout, })}'\` diff --git a/packages/features/embed/lib/EmbedTabs.tsx b/packages/features/embed/lib/EmbedTabs.tsx index 3bcbdfc262..18b8cf28f6 100644 --- a/packages/features/embed/lib/EmbedTabs.tsx +++ b/packages/features/embed/lib/EmbedTabs.tsx @@ -11,6 +11,7 @@ import { Code, Trello } from "@calcom/ui/components/icon"; import type { EmbedType, PreviewState, EmbedFramework } from "../types"; import { Codes, doWeNeedCalOriginProp } from "./EmbedCodes"; import { EMBED_PREVIEW_HTML_URL, embedLibUrl } from "./constants"; +import { getApiName } from "./getApiName"; import { getDimension } from "./getDimension"; import { useEmbedCalOrigin } from "./hooks"; @@ -22,10 +23,10 @@ export const tabs = [ type: "code", Component: forwardRef< HTMLTextAreaElement | HTMLIFrameElement | null, - { embedType: EmbedType; calLink: string; previewState: PreviewState } - >(function EmbedHtml({ embedType, calLink, previewState }, ref) { + { embedType: EmbedType; calLink: string; previewState: PreviewState; namespace: string } + >(function EmbedHtml({ embedType, calLink, previewState, namespace }, ref) { const { t } = useLocale(); - const embedSnippetString = useGetEmbedSnippetString(); + const embedSnippetString = useGetEmbedSnippetString(namespace); const embedCalOrigin = useEmbedCalOrigin(); if (ref instanceof Function || !ref) { return null; @@ -55,7 +56,14 @@ export const tabs = [ : "" }<script type="text/javascript"> ${embedSnippetString} - ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previewState, embedCalOrigin })} + ${getEmbedTypeSpecificString({ + embedFramework: "HTML", + embedType, + calLink, + previewState, + embedCalOrigin, + namespace, + })} </script> <!-- Cal ${embedType} embed code ends -->`} /> @@ -71,8 +79,8 @@ export const tabs = [ type: "code", Component: forwardRef< HTMLTextAreaElement | HTMLIFrameElement | null, - { embedType: EmbedType; calLink: string; previewState: PreviewState } - >(function EmbedReact({ embedType, calLink, previewState }, ref) { + { embedType: EmbedType; calLink: string; previewState: PreviewState; namespace: string } + >(function EmbedReact({ embedType, calLink, previewState, namespace }, ref) { const { t } = useLocale(); const embedCalOrigin = useEmbedCalOrigin(); @@ -99,7 +107,14 @@ export const tabs = [ /* If you are using npm */ // npm install @calcom/embed-react - ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, previewState, embedCalOrigin })} + ${getEmbedTypeSpecificString({ + embedFramework: "react", + embedType, + calLink, + previewState, + embedCalOrigin, + namespace, + })} `} /> </> @@ -113,7 +128,7 @@ export const tabs = [ type: "iframe", Component: forwardRef< HTMLIFrameElement | HTMLTextAreaElement | null, - { calLink: string; embedType: EmbedType; previewState: PreviewState } + { calLink: string; embedType: EmbedType; previewState: PreviewState; namespace: string } >(function Preview({ calLink, embedType }, ref) { const bookerUrl = useBookerUrl(); const iframeSrc = `${EMBED_PREVIEW_HTML_URL}?embedType=${embedType}&calLink=${calLink}&embedLibUrl=${embedLibUrl}&bookerUrl=${bookerUrl}`; @@ -144,12 +159,14 @@ const getEmbedTypeSpecificString = ({ calLink, embedCalOrigin, previewState, + namespace, }: { embedFramework: EmbedFramework; embedType: EmbedType; calLink: string; previewState: PreviewState; embedCalOrigin: string; + namespace: string; }) => { const frameworkCodes = Codes[embedFramework]; if (!frameworkCodes) { @@ -165,7 +182,7 @@ const getEmbedTypeSpecificString = ({ }; if (embedFramework === "react") { uiInstructionStringArg = { - apiName: "cal", + apiName: getApiName({ namespace, mainApiName: "cal" }), theme: previewState.theme, brandColor: previewState.palette.brandColor, hideEventTypeDetails: previewState.hideEventTypeDetails, @@ -173,7 +190,7 @@ const getEmbedTypeSpecificString = ({ }; } else { uiInstructionStringArg = { - apiName: "Cal", + apiName: getApiName({ namespace, mainApiName: "Cal" }), theme: previewState.theme, brandColor: previewState.palette.brandColor, hideEventTypeDetails: previewState.hideEventTypeDetails, @@ -189,6 +206,7 @@ const getEmbedTypeSpecificString = ({ uiInstructionCode: getEmbedUIInstructionString(uiInstructionStringArg), previewState, embedCalOrigin, + namespace, }); } else if (embedType === "floating-popup") { const floatingButtonArg = { @@ -197,11 +215,13 @@ const getEmbedTypeSpecificString = ({ ...previewState.floatingPopup, }; return frameworkCodes[embedType]({ + namespace, floatingButtonArg: JSON.stringify(floatingButtonArg), uiInstructionCode: getEmbedUIInstructionString(uiInstructionStringArg), }); } else if (embedType === "element-click") { return frameworkCodes[embedType]({ + namespace, calLink, uiInstructionCode: getEmbedUIInstructionString(uiInstructionStringArg), previewState, @@ -253,10 +273,10 @@ const getInstructionString = ({ return `${apiName}("${instructionName}", ${JSON.stringify(instructionArg)});`; }; -function useGetEmbedSnippetString() { +function useGetEmbedSnippetString(namespace: string | null) { const bookerUrl = useBookerUrl(); // TODO: Import this string from @calcom/embed-snippet return `(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, "${embedLibUrl}", "init"); -Cal("init", {origin:"${bookerUrl}"}); +Cal("init", ${namespace ? `"${namespace}",` : ""} {origin:"${bookerUrl}"}); `; } diff --git a/packages/features/embed/lib/getApiName.tsx b/packages/features/embed/lib/getApiName.tsx new file mode 100644 index 0000000000..17a0013ab4 --- /dev/null +++ b/packages/features/embed/lib/getApiName.tsx @@ -0,0 +1,14 @@ +export function getApiName({ + namespace, + mainApiName = "Cal", +}: { + namespace: string | null; + mainApiName?: string; +}) { + if (!namespace) { + return mainApiName; + } + const isAValidVariableName = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(namespace); + // Try to use dot notation if possible because it's more readable otherwise use bracket notation + return isAValidVariableName ? `${mainApiName}.ns.${namespace}` : `${mainApiName}.ns["${namespace}"]`; +} diff --git a/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx b/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx index 8a6218d197..e0d346a6e5 100644 --- a/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx +++ b/packages/features/eventtypes/components/ChildrenEventTypeSelect.tsx @@ -1,8 +1,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { Props } from "react-select"; -import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; -import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import { classNames } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -15,6 +13,7 @@ export type ChildrenEventType = { label: string; created: boolean; owner: { + avatar: string; id: number; email: string; name: string; @@ -36,7 +35,6 @@ export const ChildrenEventTypeSelect = ({ onChange: (value: readonly ChildrenEventType[]) => void; }) => { const { t } = useLocale(); - const orgBranding = useOrgBranding(); const [animationRef] = useAutoAnimate<HTMLUListElement>(); return ( @@ -63,9 +61,7 @@ export const ChildrenEventTypeSelect = ({ <Avatar size="mdLg" className="overflow-visible" - imageSrc={`${orgBranding ? getOrgFullOrigin(orgBranding.slug) : CAL_URL}/${ - children.owner.username - }/avatar.png`} + imageSrc={children.owner.avatar} alt={children.owner.name || children.owner.email || ""} /> <div className="flex w-full flex-row justify-between"> diff --git a/packages/features/eventtypes/components/EventTypeDescription.tsx b/packages/features/eventtypes/components/EventTypeDescription.tsx index ec8f15cd4c..437e425f1d 100644 --- a/packages/features/eventtypes/components/EventTypeDescription.tsx +++ b/packages/features/eventtypes/components/EventTypeDescription.tsx @@ -86,7 +86,7 @@ export const EventTypeDescription = ({ </Badge> )} {recurringEvent?.count && recurringEvent.count > 0 && ( - <li className="hidden xl:block"> + <li className="hidden xl:block" data-testid="repeat-eventtype"> <Badge variant="gray" startIcon={RefreshCw}> {t("repeats_up_to", { count: recurringEvent.count, diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index 85bab2e26f..1c2d1d3462 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -8,6 +8,7 @@ import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/ import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; import { isRecurringEvent, parseRecurringEvent } from "@calcom/lib"; import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import type { PrismaClient } from "@calcom/prisma"; import type { BookerLayoutSettings } from "@calcom/prisma/zod-utils"; @@ -27,6 +28,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({ description: true, eventName: true, slug: true, + isInstantEvent: true, schedulingType: true, length: true, locations: true, @@ -81,6 +83,11 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({ darkBrandColor: true, theme: true, organizationId: true, + organization: { + select: { + slug: true, + }, + }, metadata: true, }, }, @@ -171,7 +178,11 @@ export const getPublicEvent = async ( ...defaultEvent, bookingFields: getBookingFieldsWithSystemFields({ ...defaultEvent, disableBookingTitle }), // Clears meta data since we don't want to send this in the public api. - users: users.map((user) => ({ ...user, metadata: undefined })), + users: users.map((user) => ({ + ...user, + metadata: undefined, + bookerUrl: getBookerBaseUrlSync(user.organization?.slug ?? null), + })), locations: privacyFilteredLocations(locations), profile: { username: users[0].username, @@ -190,6 +201,7 @@ export const getPublicEvent = async ( orgSlug: org, name: unPublishedOrgUser?.organization?.name ?? null, }, + isInstantEvent: false, }; } @@ -251,6 +263,7 @@ export const getPublicEvent = async ( name: (event.owner?.organization?.name || event.team?.parent?.name || event.team?.name) ?? null, }, isDynamic: false, + isInstantEvent: event.isInstantEvent, }; }; @@ -296,23 +309,50 @@ function getUsersFromEvent(event: Event) { return null; } const { username, name, weekStart, organizationId } = owner; - return [{ username, name, weekStart, organizationId }]; + return [ + { + username, + name, + weekStart, + organizationId, + bookerUrl: getBookerBaseUrlSync(owner.organization?.slug ?? null), + }, + ]; } async function getOwnerFromUsersArray(prisma: PrismaClient, eventTypeId: number) { const { users } = await prisma.eventType.findUniqueOrThrow({ where: { id: eventTypeId }, - select: { users: { select: { username: true, name: true, weekStart: true, organizationId: true } } }, + select: { + users: { + select: { + username: true, + name: true, + weekStart: true, + organizationId: true, + organization: { + select: { + slug: true, + }, + }, + }, + }, + }, }); if (!users.length) return null; - return [users[0]]; + return [{ ...users[0], bookerUrl: getBookerBaseUrlSync(users[0].organization?.slug ?? null) }]; } -function mapHostsToUsers(host: { user: Pick<User, "username" | "name" | "weekStart" | "organizationId"> }) { +function mapHostsToUsers(host: { + user: Pick<User, "username" | "name" | "weekStart" | "organizationId"> & { + organization: { slug: string | null } | null; + }; +}) { return { username: host.user.username, name: host.user.name, weekStart: host.user.weekStart, organizationId: host.user.organizationId, + bookerUrl: getBookerBaseUrlSync(host.user.organization?.slug ?? null), }; } diff --git a/packages/features/filters/components/TeamsFilter.tsx b/packages/features/filters/components/TeamsFilter.tsx index 8d52f6f317..1c34ce09b1 100644 --- a/packages/features/filters/components/TeamsFilter.tsx +++ b/packages/features/filters/components/TeamsFilter.tsx @@ -60,15 +60,16 @@ export const TeamsFilter = ({ return ( <div className="flex items-center"> - <AnimatedPopover text={getCheckedOptionsNames()} popoverTriggerClassNames={popoverTriggerClassNames}> + <AnimatedPopover + text={getCheckedOptionsNames()} + popoverTriggerClassNames={popoverTriggerClassNames} + prefix={`${t("teams")}: `}> <FilterCheckboxFieldsContainer> <FilterCheckboxField id="all" icon={<Layers className="h-4 w-4" />} checked={!query.teamIds && !query.userIds?.includes(session.data?.user.id || 0)} - onChange={(e) => { - removeAllQueryParams(); - }} + onChange={removeAllQueryParams} label={t("all")} /> @@ -134,7 +135,7 @@ export const FilterCheckboxFieldsContainer = ({ type Props = InputHTMLAttributes<HTMLInputElement> & { label: string; - icon: ReactNode; + icon?: ReactNode; }; export const FilterCheckboxField = forwardRef<HTMLInputElement, Props>(({ label, icon, ...rest }, ref) => { @@ -142,9 +143,11 @@ export const FilterCheckboxField = forwardRef<HTMLInputElement, Props>(({ label, <div className="hover:bg-muted flex items-center py-2 pl-3 pr-2.5 hover:cursor-pointer"> <label className="flex w-full max-w-full items-center justify-between hover:cursor-pointer"> <div className="flex items-center truncate"> - <div className="text-default flex h-4 w-4 items-center justify-center ltr:mr-2 rtl:ml-2"> - {icon} - </div> + {icon && ( + <div className="text-default flex h-4 w-4 items-center justify-center ltr:mr-2 rtl:ml-2"> + {icon} + </div> + )} <Tooltip content={label}> <label htmlFor={rest.id} @@ -158,7 +161,7 @@ export const FilterCheckboxField = forwardRef<HTMLInputElement, Props>(({ label, {...rest} ref={ref} type="checkbox" - className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded hover:cursor-pointer" + className="text-emphasis dark:text-muted focus:ring-emphasis border-default bg-default h-4 w-4 rounded hover:cursor-pointer" /> </div> </label> diff --git a/packages/features/filters/lib/getTeamsFiltersFromQuery.ts b/packages/features/filters/lib/getTeamsFiltersFromQuery.ts index 14d71c9d78..f0c1aad074 100644 --- a/packages/features/filters/lib/getTeamsFiltersFromQuery.ts +++ b/packages/features/filters/lib/getTeamsFiltersFromQuery.ts @@ -1,12 +1,24 @@ +"use client"; + import type { ParsedUrlQuery } from "querystring"; import { z } from "zod"; -import { queryNumberArray } from "@calcom/lib/hooks/useTypedQuery"; import type { RouterOutputs } from "@calcom/trpc/react"; export type IEventTypesFilters = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"]; export type IEventTypeFilter = IEventTypesFilters[0]; +// Take array as a string and return zod array +const queryNumberArray = z + .string() + .or(z.number()) + .or(z.array(z.number())) + .transform((a) => { + if (typeof a === "string") return a.split(",").map((a) => Number(a)); + if (Array.isArray(a)) return a; + return [a]; + }); + // Use filterQuerySchema when parsing filters out of query, so that additional query params(e.g. slug, appPages) aren't passed in filters export const filterQuerySchema = z.object({ teamIds: queryNumberArray.optional(), diff --git a/packages/features/flags/config.ts b/packages/features/flags/config.ts index 50e61214a9..bb03d0f5d4 100644 --- a/packages/features/flags/config.ts +++ b/packages/features/flags/config.ts @@ -12,7 +12,6 @@ export type AppFlags = { "managed-event-types": boolean; organizations: boolean; "email-verification": boolean; - "booker-layouts": boolean; "google-workspace-directory": boolean; "disable-signup": boolean; }; diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts new file mode 100644 index 0000000000..f73b918dc8 --- /dev/null +++ b/packages/features/instant-meeting/handleInstantMeeting.ts @@ -0,0 +1,222 @@ +import { Prisma } from "@prisma/client"; +import { randomBytes } from "crypto"; +import type { NextApiRequest } from "next"; +import short from "short-uuid"; +import { v5 as uuidv5 } from "uuid"; + +import { createInstantMeetingWithCalVideo } from "@calcom/core/videoClient"; +import dayjs from "@calcom/dayjs"; +import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; +import { + getEventTypesFromDB, + getBookingData, + getCustomInputsResponses, +} from "@calcom/features/bookings/lib/handleNewBooking"; +import { getFullName } from "@calcom/features/form-builder/utils"; +import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; +import { isPrismaObjOrUndefined } from "@calcom/lib"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import logger from "@calcom/lib/logger"; +import { getTranslation } from "@calcom/lib/server"; +import prisma from "@calcom/prisma"; +import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums"; + +const handleInstantMeetingWebhookTrigger = async (args: { + eventTypeId: number; + webhookData: Record<string, unknown>; +}) => { + try { + const eventTrigger = WebhookTriggerEvents.INSTANT_MEETING; + + const subscribers = await prisma.webhook.findMany({ + where: { + AND: { + eventTypeId: args.eventTypeId, + eventTriggers: { + has: eventTrigger, + }, + active: true, + }, + }, + select: { + id: true, + subscriberUrl: true, + payloadTemplate: true, + appId: true, + secret: true, + }, + }); + + const { webhookData } = args; + + const promises = subscribers.map((sub) => { + sendGenericWebhookPayload({ + secretKey: sub.secret, + triggerEvent: eventTrigger, + createdAt: new Date().toISOString(), + webhook: sub, + data: webhookData, + }).catch((e) => { + console.error( + `Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, + sub, + e + ); + }); + }); + + await Promise.all(promises); + } catch (error) { + console.error("Error executing webhook", error); + logger.error("Error while sending webhook", error); + } +}; + +async function handler(req: NextApiRequest) { + let eventType = await getEventTypesFromDB(req.body.eventTypeId); + eventType = { + ...eventType, + bookingFields: getBookingFieldsWithSystemFields(eventType), + }; + + if (!eventType.team?.id) { + throw new Error("Only Team Event Types are supported for Instant Meeting"); + } + + const reqBody = await getBookingData({ + req, + isNotAnApiCall: true, + eventType, + }); + const { email: bookerEmail, name: bookerName } = reqBody; + + const translator = short(); + const seed = `${reqBody.email}:${dayjs(reqBody.start).utc().format()}:${new Date().getTime()}`; + const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL)); + + const customInputs = getCustomInputsResponses(reqBody, eventType.customInputs); + const attendeeTimezone = reqBody.timeZone; + const attendeeLanguage = reqBody.language; + const tAttendees = await getTranslation(attendeeLanguage ?? "en", "common"); + + const fullName = getFullName(bookerName); + + const invitee = [ + { + email: bookerEmail, + name: fullName, + timeZone: attendeeTimezone, + locale: attendeeLanguage ?? "en", + }, + ]; + + const guests = (reqBody.guests || []).reduce((guestArray, guest) => { + guestArray.push({ + email: guest, + name: "", + timeZone: attendeeTimezone, + locale: "en", + }); + return guestArray; + }, [] as typeof invitee); + + const attendeesList = [...invitee, ...guests]; + const calVideoMeeting = await createInstantMeetingWithCalVideo(dayjs.utc(reqBody.end).toISOString()); + + if (!calVideoMeeting) { + throw new Error("Cal Video Meeting Creation Failed"); + } + + eventType.team.id; + const bookingReferenceToCreate = [ + { + type: calVideoMeeting.type, + uid: calVideoMeeting.id, + meetingId: calVideoMeeting.id, + meetingPassword: calVideoMeeting.password, + meetingUrl: calVideoMeeting.url, + }, + ]; + + // Create Partial + const newBookingData: Prisma.BookingCreateInput = { + uid, + responses: reqBody.responses === null ? Prisma.JsonNull : reqBody.responses, + title: tAttendees("instant_meeting_with_title", { name: invitee[0].name }), + startTime: dayjs.utc(reqBody.start).toDate(), + endTime: dayjs.utc(reqBody.end).toDate(), + description: reqBody.notes, + customInputs: isPrismaObjOrUndefined(customInputs), + status: BookingStatus.AWAITING_HOST, + references: { + create: bookingReferenceToCreate, + }, + location: "integrations:daily", + eventType: { + connect: { + id: reqBody.eventTypeId, + }, + }, + metadata: { ...reqBody.metadata, videoCallUrl: `${WEBAPP_URL}/video/${uid}` }, + attendees: { + createMany: { + data: attendeesList, + }, + }, + }; + + const createBookingObj = { + include: { + attendees: true, + }, + data: newBookingData, + }; + + const newBooking = await prisma.booking.create(createBookingObj); + + // Create Instant Meeting Token + const token = randomBytes(32).toString("hex"); + const instantMeetingToken = await prisma.instantMeetingToken.create({ + data: { + token, + expires: new Date(new Date().getTime() + 1000 * 60 * 5), + team: { + connect: { + id: eventType.team.id, + }, + }, + booking: { + connect: { + id: newBooking.id, + }, + }, + updatedAt: new Date().toISOString(), + }, + }); + + // Trigger Webhook + + const webhookData = { + triggerEvent: WebhookTriggerEvents.INSTANT_MEETING, + uid: newBooking.uid, + responses: newBooking.responses, + connectAndJoinUrl: `${WEBAPP_URL}/connect-and-join?token=${token}`, + eventTypeId: eventType.id, + eventTypeTitle: eventType.title, + customInputs: newBooking.customInputs, + }; + + await handleInstantMeetingWebhookTrigger({ + eventTypeId: eventType.id, + webhookData, + }); + + return { + message: "Success", + meetingTokenId: instantMeetingToken.id, + bookingId: newBooking.id, + expires: instantMeetingToken.expires, + }; +} + +export default handler; diff --git a/packages/features/kbar/Kbar.tsx b/packages/features/kbar/Kbar.tsx index 0477141a3c..cdc1fa7cc6 100644 --- a/packages/features/kbar/Kbar.tsx +++ b/packages/features/kbar/Kbar.tsx @@ -273,7 +273,7 @@ export const KBarTrigger = () => { <button color="minimal" onClick={query.toggle} - className="text-default hover:bg-subtle lg:hover:bg-emphasis lg:hover:text-emphasis group flex rounded-md px-3 py-2 text-sm font-medium transition lg:px-2"> + className="text-default hover:bg-subtle todesktop:hover:!bg-transparent lg:hover:bg-emphasis lg:hover:text-emphasis group flex rounded-md px-3 py-2 text-sm font-medium transition lg:px-2"> <Search className="h-4 w-4 flex-shrink-0 text-inherit" /> </button> </Tooltip> diff --git a/packages/features/settings/BookerLayoutSelector.tsx b/packages/features/settings/BookerLayoutSelector.tsx index 3ac38433bb..e097298db0 100644 --- a/packages/features/settings/BookerLayoutSelector.tsx +++ b/packages/features/settings/BookerLayoutSelector.tsx @@ -4,7 +4,6 @@ import Link from "next/link"; import { useCallback, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; -import { useFlagMap } from "@calcom/features/flags/context/provider"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils"; @@ -53,9 +52,6 @@ export const BookerLayoutSelector = ({ // Only fallback if event current does not have any settings, and the fallbackToUserSettings boolean is set. const shouldShowUserSettings = (fallbackToUserSettings && !getValues(name || defaultFieldName)) || false; - const flags = useFlagMap(); - if (flags["booker-layouts"] !== true) return null; - return ( <div className={classNames(isOuterBorder && "border-subtle rounded-lg border p-6")}> <div className={classNames(isOuterBorder ? "pb-5" : "border-subtle rounded-t-xl border p-6")}> diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 677c43f5e4..704bdd52d8 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -103,7 +103,7 @@ const tabs: VerticalTabItemProps[] = [ }, { name: "teams", - href: "/settings/teams", + href: "/teams", icon: Users, children: [], }, @@ -175,7 +175,7 @@ const BackButtonInSidebar = ({ name }: { name: string }) => { return ( <Link href="/" - className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-emphasis group my-6 flex h-6 max-h-6 w-full flex-row items-center rounded-md px-3 py-2 text-sm font-medium leading-4" + className="hover:bg-subtle todesktop:mt-10 [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-emphasis group my-6 flex h-6 max-h-6 w-full flex-row items-center rounded-md px-3 py-2 text-sm font-medium leading-4" data-testid={`vertical-tab-${name}`}> <ArrowLeft className="h-4 w-4 stroke-[2px] ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180 md:mt-0" /> <Skeleton title={name} as="p" className="max-w-36 min-h-4 truncate" loadingClassName="ms-3"> @@ -213,7 +213,9 @@ const SettingsSidebarContainer = ({ enabled: !!session.data?.user?.org, }); - const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery(); + const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery(undefined, { + enabled: !!session.data?.user?.org, + }); useEffect(() => { if (teams) { diff --git a/packages/features/settings/layouts/SettingsLayoutAppDir.tsx b/packages/features/settings/layouts/SettingsLayoutAppDir.tsx new file mode 100644 index 0000000000..92108d07f4 --- /dev/null +++ b/packages/features/settings/layouts/SettingsLayoutAppDir.tsx @@ -0,0 +1,723 @@ +"use client"; + +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; +import { useSession } from "next-auth/react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import type { ComponentProps } from "react"; +import React, { Suspense, useEffect, useState } from "react"; + +import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; +import Shell from "@calcom/features/shell/Shell"; +import { classNames } from "@calcom/lib"; +import { HOSTED_CAL_FEATURES, WEBAPP_URL } from "@calcom/lib/constants"; +import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; +import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { IdentityProvider, MembershipRole, UserPermissionRole } from "@calcom/prisma/enums"; +import { trpc } from "@calcom/trpc/react"; +import type { VerticalTabItemProps } from "@calcom/ui"; +import { Badge, Button, ErrorBoundary, Skeleton, useMeta, VerticalTabItem } from "@calcom/ui"; +import { + ArrowLeft, + ChevronDown, + ChevronRight, + CreditCard, + Key, + Loader, + Lock, + Menu, + Plus, + Terminal, + User, + Users, +} from "@calcom/ui/components/icon"; + +const tabs: VerticalTabItemProps[] = [ + { + name: "my_account", + href: "/settings/my-account", + icon: User, + children: [ + { name: "profile", href: "/settings/my-account/profile" }, + { name: "general", href: "/settings/my-account/general" }, + { name: "calendars", href: "/settings/my-account/calendars" }, + { name: "conferencing", href: "/settings/my-account/conferencing" }, + { name: "appearance", href: "/settings/my-account/appearance" }, + // TODO + // { name: "referrals", href: "/settings/my-account/referrals" }, + ], + }, + { + name: "security", + href: "/settings/security", + icon: Key, + children: [ + { name: "password", href: "/settings/security/password" }, + { name: "impersonation", href: "/settings/security/impersonation" }, + { name: "2fa_auth", href: "/settings/security/two-factor-auth" }, + ], + }, + { + name: "billing", + href: "/settings/billing", + icon: CreditCard, + children: [{ name: "manage_billing", href: "/settings/billing" }], + }, + { + name: "developer", + href: "/settings/developer", + icon: Terminal, + children: [ + // + { name: "webhooks", href: "/settings/developer/webhooks" }, + { name: "api_keys", href: "/settings/developer/api-keys" }, + // TODO: Add profile level for embeds + // { name: "embeds", href: "/v2/settings/developer/embeds" }, + ], + }, + { + name: "organization", + href: "/settings/organizations", + children: [ + { + name: "profile", + href: "/settings/organizations/profile", + }, + { + name: "general", + href: "/settings/organizations/general", + }, + { + name: "members", + href: "/settings/organizations/members", + }, + { + name: "appearance", + href: "/settings/organizations/appearance", + }, + { + name: "billing", + href: "/settings/organizations/billing", + }, + ], + }, + { + name: "teams", + href: "/teams", + icon: Users, + children: [], + }, + { + name: "admin", + href: "/settings/admin", + icon: Lock, + children: [ + // + { name: "features", href: "/settings/admin/flags" }, + { name: "license", href: "/auth/setup?step=1" }, + { name: "impersonation", href: "/settings/admin/impersonation" }, + { name: "apps", href: "/settings/admin/apps/calendar" }, + { name: "users", href: "/settings/admin/users" }, + { name: "organizations", href: "/settings/admin/organizations" }, + { name: "oAuth", href: "/settings/admin/oAuth" }, + ], + }, +]; + +tabs.find((tab) => { + // Add "SAML SSO" to the tab + if (tab.name === "security" && !HOSTED_CAL_FEATURES) { + tab.children?.push({ name: "sso_configuration", href: "/settings/security/sso" }); + } +}); + +// The following keys are assigned to admin only +const adminRequiredKeys = ["admin"]; +const organizationRequiredKeys = ["organization"]; + +const useTabs = () => { + const session = useSession(); + const { data: user } = trpc.viewer.me.useQuery(); + const orgBranding = useOrgBranding(); + + const isAdmin = session.data?.user.role === UserPermissionRole.ADMIN; + + tabs.map((tab) => { + if (tab.href === "/settings/my-account") { + tab.name = user?.name || "my_account"; + tab.icon = undefined; + tab.avatar = getUserAvatarUrl(user); + } else if (tab.href === "/settings/organizations") { + tab.name = orgBranding?.name || "organization"; + tab.avatar = `${orgBranding?.fullDomain}/org/${orgBranding?.slug}/avatar.png`; + } else if ( + tab.href === "/settings/security" && + user?.identityProvider === IdentityProvider.GOOGLE && + !user?.twoFactorEnabled + ) { + tab.children = tab?.children?.filter( + (childTab) => childTab.href !== "/settings/security/two-factor-auth" + ); + } + return tab; + }); + + // check if name is in adminRequiredKeys + return tabs.filter((tab) => { + if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.org; + + if (isAdmin) return true; + return !adminRequiredKeys.includes(tab.name); + }); +}; + +const BackButtonInSidebar = ({ name }: { name: string }) => { + return ( + <Link + href="/" + className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-emphasis group my-6 flex h-6 max-h-6 w-full flex-row items-center rounded-md px-3 py-2 text-sm font-medium leading-4" + data-testid={`vertical-tab-${name}`}> + <ArrowLeft className="h-4 w-4 stroke-[2px] ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180 md:mt-0" /> + <Skeleton title={name} as="p" className="max-w-36 min-h-4 truncate" loadingClassName="ms-3"> + {name} + </Skeleton> + </Link> + ); +}; + +interface SettingsSidebarContainerProps { + className?: string; + navigationIsOpenedOnMobile?: boolean; + bannersHeight?: number; +} + +const SettingsSidebarContainer = ({ + className = "", + navigationIsOpenedOnMobile, + bannersHeight, +}: SettingsSidebarContainerProps) => { + const searchParams = useCompatSearchParams(); + const { t } = useLocale(); + const tabsWithPermissions = useTabs(); + const [teamMenuState, setTeamMenuState] = + useState<{ teamId: number | undefined; teamMenuOpen: boolean }[]>(); + const [otherTeamMenuState, setOtherTeamMenuState] = useState< + { + teamId: number | undefined; + teamMenuOpen: boolean; + }[] + >(); + const { data: teams } = trpc.viewer.teams.list.useQuery(); + const session = useSession(); + const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { + enabled: !!session.data?.user?.org, + }); + + const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery(); + + useEffect(() => { + if (teams) { + const teamStates = teams?.map((team) => ({ + teamId: team.id, + teamMenuOpen: String(team.id) === searchParams?.get("id"), + })); + setTeamMenuState(teamStates); + setTimeout(() => { + const tabMembers = Array.from(document.getElementsByTagName("a")).filter( + (bottom) => bottom.dataset.testid === "vertical-tab-Members" + )[1]; + tabMembers?.scrollIntoView({ behavior: "smooth" }); + }, 100); + } + }, [searchParams?.get("id"), teams]); + + // Same as above but for otherTeams + useEffect(() => { + if (otherTeams) { + const otherTeamStates = otherTeams?.map((team) => ({ + teamId: team.id, + teamMenuOpen: String(team.id) === searchParams?.get("id"), + })); + setOtherTeamMenuState(otherTeamStates); + setTimeout(() => { + // @TODO: test if this works for 2 dataset testids + const tabMembers = Array.from(document.getElementsByTagName("a")).filter( + (bottom) => bottom.dataset.testid === "vertical-tab-Members" + )[1]; + tabMembers?.scrollIntoView({ behavior: "smooth" }); + }, 100); + } + }, [searchParams?.get("id"), otherTeams]); + + const isOrgAdminOrOwner = + currentOrg && currentOrg?.user?.role && ["OWNER", "ADMIN"].includes(currentOrg?.user?.role); + + if (isOrgAdminOrOwner) { + const teamsIndex = tabsWithPermissions.findIndex((tab) => tab.name === "teams"); + + tabsWithPermissions.splice(teamsIndex + 1, 0, { + name: "other_teams", + href: "/settings/organizations/teams/other", + icon: Users, + children: [], + }); + } + + return ( + <nav + style={{ maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px` }} + className={classNames( + "no-scrollbar bg-muted fixed bottom-0 left-0 top-0 z-20 flex max-h-screen w-56 flex-col space-y-1 overflow-x-hidden overflow-y-scroll px-2 pb-3 transition-transform max-lg:z-10 lg:sticky lg:flex", + className, + navigationIsOpenedOnMobile + ? "translate-x-0 opacity-100" + : "-translate-x-full opacity-0 lg:translate-x-0 lg:opacity-100" + )} + aria-label="Tabs"> + <> + <BackButtonInSidebar name={t("back")} /> + {tabsWithPermissions.map((tab) => { + return ( + <React.Fragment key={tab.href}> + {!["teams", "other_teams"].includes(tab.name) && ( + <React.Fragment key={tab.href}> + <div className={`${!tab.children?.length ? "!mb-3" : ""}`}> + <div className="[&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis text-default group flex h-9 w-full flex-row items-center rounded-md px-2 text-sm font-medium leading-none"> + {tab && tab.icon && ( + <tab.icon className="h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0" /> + )} + {!tab.icon && tab?.avatar && ( + <img + className="h-4 w-4 rounded-full ltr:mr-3 rtl:ml-3" + src={tab?.avatar} + alt="User Avatar" + /> + )} + <Skeleton + title={tab.name} + as="p" + className="truncate text-sm font-medium leading-5" + loadingClassName="ms-3"> + {t(tab.name)} + </Skeleton> + </div> + </div> + <div className="my-3 space-y-0.5"> + {tab.children?.map((child, index) => ( + <VerticalTabItem + key={child.href} + name={t(child.name)} + isExternalLink={child.isExternalLink} + href={child.href || "/"} + textClassNames="px-3 text-emphasis font-medium text-sm" + className={`my-0.5 me-5 h-7 ${ + tab.children && index === tab.children?.length - 1 && "!mb-3" + }`} + disableChevron + /> + ))} + </div> + </React.Fragment> + )} + + {tab.name === "teams" && ( + <React.Fragment key={tab.href}> + <div className={`${!tab.children?.length ? "mb-3" : ""}`}> + <Link href={tab.href}> + <div className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-default group flex h-9 w-full flex-row items-center rounded-md px-2 py-[10px] text-sm font-medium leading-none"> + {tab && tab.icon && ( + <tab.icon className="h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0" /> + )} + <Skeleton + title={tab.name} + as="p" + className="truncate text-sm font-medium leading-5" + loadingClassName="ms-3"> + {t(isOrgAdminOrOwner ? "my_teams" : tab.name)} + </Skeleton> + </div> + </Link> + {teams && + teamMenuState && + teams.map((team, index: number) => { + if (!teamMenuState[index]) { + return null; + } + if (teamMenuState.some((teamState) => teamState.teamId === team.id)) + return ( + <Collapsible + className="cursor-pointer" + key={team.id} + open={teamMenuState[index].teamMenuOpen} + onOpenChange={() => + setTeamMenuState([ + ...teamMenuState, + (teamMenuState[index] = { + ...teamMenuState[index], + teamMenuOpen: !teamMenuState[index].teamMenuOpen, + }), + ]) + }> + <CollapsibleTrigger asChild> + <div + className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis text-default flex h-9 w-full flex-row items-center rounded-md px-3 py-[10px] text-left text-sm font-medium leading-none" + onClick={() => + setTeamMenuState([ + ...teamMenuState, + (teamMenuState[index] = { + ...teamMenuState[index], + teamMenuOpen: !teamMenuState[index].teamMenuOpen, + }), + ]) + }> + <div className="me-3"> + {teamMenuState[index].teamMenuOpen ? ( + <ChevronDown className="h-4 w-4" /> + ) : ( + <ChevronRight className="h-4 w-4" /> + )} + </div> + {!team.parentId && ( + <img + src={getPlaceholderAvatar(team.logo, team?.name as string)} + className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0" + alt={team.name || "Team logo"} + /> + )} + <p className="w-1/2 truncate leading-normal">{team.name}</p> + {!team.accepted && ( + <Badge className="ms-3" variant="orange"> + Inv. + </Badge> + )} + </div> + </CollapsibleTrigger> + <CollapsibleContent className="space-y-0.5"> + {team.accepted && ( + <VerticalTabItem + name={t("profile")} + href={`/settings/teams/${team.id}/profile`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + )} + <VerticalTabItem + name={t("members")} + href={`/settings/teams/${team.id}/members`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + {(team.role === MembershipRole.OWNER || + team.role === MembershipRole.ADMIN || + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore this exists wtf? + (team.isOrgAdmin && team.isOrgAdmin)) && ( + <> + {/* TODO */} + {/* <VerticalTabItem + name={t("general")} + href={`${WEBAPP_URL}/settings/my-account/appearance`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> */} + <VerticalTabItem + name={t("appearance")} + href={`/settings/teams/${team.id}/appearance`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + {/* Hide if there is a parent ID */} + {!team.parentId ? ( + <> + <VerticalTabItem + name={t("billing")} + href={`/settings/teams/${team.id}/billing`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + {HOSTED_CAL_FEATURES && ( + <VerticalTabItem + name={t("saml_config")} + href={`/settings/teams/${team.id}/sso`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + )} + </> + ) : null} + </> + )} + </CollapsibleContent> + </Collapsible> + ); + })} + {(!currentOrg || (currentOrg && currentOrg?.user?.role !== "MEMBER")) && ( + <VerticalTabItem + name={t("add_a_team")} + href={`${WEBAPP_URL}/settings/teams/new`} + textClassNames="px-3 items-center mt-2 text-emphasis font-medium text-sm" + icon={Plus} + disableChevron + /> + )} + </div> + </React.Fragment> + )} + + {tab.name === "other_teams" && ( + <React.Fragment key={tab.href}> + <div className={`${!tab.children?.length ? "mb-3" : ""}`}> + <Link href={tab.href}> + <div className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis group-hover:text-default text-default group flex h-9 w-full flex-row items-center rounded-md px-2 py-[10px] text-sm font-medium leading-none"> + {tab && tab.icon && ( + <tab.icon className="h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0" /> + )} + <Skeleton + title={t("org_admin_other_teams")} + as="p" + className="truncate text-sm font-medium leading-5" + loadingClassName="ms-3"> + {t("org_admin_other_teams")} + </Skeleton> + </div> + </Link> + {otherTeams && + otherTeamMenuState && + otherTeams.map((otherTeam, index: number) => { + if (!otherTeamMenuState[index]) { + return null; + } + if (otherTeamMenuState.some((teamState) => teamState.teamId === otherTeam.id)) + return ( + <Collapsible + className="cursor-pointer" + key={otherTeam.id} + open={otherTeamMenuState[index].teamMenuOpen} + onOpenChange={() => + setOtherTeamMenuState([ + ...otherTeamMenuState, + (otherTeamMenuState[index] = { + ...otherTeamMenuState[index], + teamMenuOpen: !otherTeamMenuState[index].teamMenuOpen, + }), + ]) + }> + <CollapsibleTrigger asChild> + <div + className="hover:bg-subtle [&[aria-current='page']]:bg-emphasis [&[aria-current='page']]:text-emphasis text-default flex h-9 w-full flex-row items-center rounded-md px-3 py-[10px] text-left text-sm font-medium leading-none" + onClick={() => + setOtherTeamMenuState([ + ...otherTeamMenuState, + (otherTeamMenuState[index] = { + ...otherTeamMenuState[index], + teamMenuOpen: !otherTeamMenuState[index].teamMenuOpen, + }), + ]) + }> + <div className="me-3"> + {otherTeamMenuState[index].teamMenuOpen ? ( + <ChevronDown className="h-4 w-4" /> + ) : ( + <ChevronRight className="h-4 w-4" /> + )} + </div> + {!otherTeam.parentId && ( + <img + src={getPlaceholderAvatar(otherTeam.logo, otherTeam?.name as string)} + className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0" + alt={otherTeam.name || "Team logo"} + /> + )} + <p className="w-1/2 truncate leading-normal">{otherTeam.name}</p> + </div> + </CollapsibleTrigger> + <CollapsibleContent className="space-y-0.5"> + <VerticalTabItem + name={t("profile")} + href={`/settings/organizations/teams/other/${otherTeam.id}/profile`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + <VerticalTabItem + name={t("members")} + href={`/settings/organizations/teams/other/${otherTeam.id}/members`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> + + <> + {/* TODO: enable appearance edit */} + {/* <VerticalTabItem + name={t("appearance")} + href={`/settings/organizations/teams/other/${otherTeam.id}/appearance`} + textClassNames="px-3 text-emphasis font-medium text-sm" + disableChevron + /> */} + </> + </CollapsibleContent> + </Collapsible> + ); + })} + </div> + </React.Fragment> + )} + </React.Fragment> + ); + })} + </> + </nav> + ); +}; + +const MobileSettingsContainer = (props: { onSideContainerOpen?: () => void }) => { + const { t } = useLocale(); + const router = useRouter(); + + return ( + <> + <nav className="bg-muted border-muted sticky top-0 z-20 flex w-full items-center justify-between border-b py-2 sm:relative lg:hidden"> + <div className="flex items-center space-x-3 "> + <Button StartIcon={Menu} color="minimal" variant="icon" onClick={props.onSideContainerOpen}> + <span className="sr-only">{t("show_navigation")}</span> + </Button> + + <button + className="hover:bg-emphasis flex items-center space-x-2 rounded-md px-3 py-1 rtl:space-x-reverse" + onClick={() => router.back()}> + <ArrowLeft className="text-default h-4 w-4" /> + <p className="text-emphasis font-semibold">{t("settings")}</p> + </button> + </div> + </nav> + </> + ); +}; + +export default function SettingsLayout({ + children, + ...rest +}: { children: React.ReactNode } & ComponentProps<typeof Shell>) { + const pathname = usePathname(); + const state = useState(false); + const { t } = useLocale(); + const [sideContainerOpen, setSideContainerOpen] = state; + + useEffect(() => { + const closeSideContainer = () => { + if (window.innerWidth >= 1024) { + setSideContainerOpen(false); + } + }; + + window.addEventListener("resize", closeSideContainer); + return () => { + window.removeEventListener("resize", closeSideContainer); + }; + }, []); + + useEffect(() => { + if (sideContainerOpen) { + setSideContainerOpen(!sideContainerOpen); + } + }, [pathname]); + + return ( + <Shell + withoutSeo={true} + flexChildrenContainer + hideHeadingOnMobile + {...rest} + SidebarContainer={ + <SidebarContainerElement + sideContainerOpen={sideContainerOpen} + setSideContainerOpen={setSideContainerOpen} + /> + } + drawerState={state} + MobileNavigationContainer={null} + TopNavContainer={ + <MobileSettingsContainer onSideContainerOpen={() => setSideContainerOpen(!sideContainerOpen)} /> + }> + <div className="flex flex-1 [&>*]:flex-1"> + <div className="mx-auto max-w-full justify-center lg:max-w-4xl"> + <ShellHeader /> + <ErrorBoundary> + <Suspense fallback={<Loader />}>{children}</Suspense> + </ErrorBoundary> + </div> + </div> + </Shell> + ); +} + +const SidebarContainerElement = ({ + sideContainerOpen, + bannersHeight, + setSideContainerOpen, +}: SidebarContainerElementProps) => { + const { t } = useLocale(); + return ( + <> + {/* Mobile backdrop */} + {sideContainerOpen && ( + <button + onClick={() => setSideContainerOpen(false)} + className="fixed left-0 top-0 z-10 h-full w-full bg-black/50"> + <span className="sr-only">{t("hide_navigation")}</span> + </button> + )} + <SettingsSidebarContainer + navigationIsOpenedOnMobile={sideContainerOpen} + bannersHeight={bannersHeight} + /> + </> + ); +}; + +type SidebarContainerElementProps = { + sideContainerOpen: boolean; + bannersHeight?: number; + setSideContainerOpen: React.Dispatch<React.SetStateAction<boolean>>; +}; + +export const getLayout = (page: React.ReactElement) => <SettingsLayout>{page}</SettingsLayout>; + +export function ShellHeader() { + const { meta } = useMeta(); + const { t, isLocaleReady } = useLocale(); + return ( + <> + <header + className={classNames( + "border-subtle mx-auto block justify-between sm:flex", + meta.borderInShellHeader && "rounded-t-lg border px-4 py-6 sm:px-6", + meta.borderInShellHeader === undefined && "mb-8 border-b pb-8" + )}> + <div className="flex w-full items-center"> + {meta.backButton && ( + <a href="javascript:history.back()"> + <ArrowLeft className="mr-7" /> + </a> + )} + <div> + {meta.title && isLocaleReady ? ( + <h1 className="font-cal text-emphasis mb-1 text-xl font-bold leading-5 tracking-wide"> + {t(meta.title)} + </h1> + ) : ( + <div className="bg-emphasis mb-1 h-5 w-24 animate-pulse rounded-lg" /> + )} + {meta.description && isLocaleReady ? ( + <p className="text-default text-sm ltr:mr-4 rtl:ml-4">{t(meta.description)}</p> + ) : ( + <div className="bg-emphasis h-5 w-32 animate-pulse rounded-lg" /> + )} + </div> + <div className="ms-auto flex-shrink-0">{meta.CTA}</div> + </div> + </header> + </> + ); +} diff --git a/packages/features/shell/Shell.tsx b/packages/features/shell/Shell.tsx index dc49b862ae..5f3d015314 100644 --- a/packages/features/shell/Shell.tsx +++ b/packages/features/shell/Shell.tsx @@ -4,27 +4,39 @@ import dynamic from "next/dynamic"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import type { Dispatch, ReactElement, ReactNode, SetStateAction } from "react"; -import React, { cloneElement, Fragment, useEffect, useMemo, useRef, useState } from "react"; +import React, { cloneElement, Fragment, useEffect, useMemo, useState } from "react"; import { Toaster } from "react-hot-toast"; import dayjs from "@calcom/dayjs"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge"; -import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner"; -import { OrgUpgradeBanner } from "@calcom/features/ee/organizations/components/OrgUpgradeBanner"; +import ImpersonatingBanner, { + type ImpersonatingBannerProps, +} from "@calcom/features/ee/impersonation/components/ImpersonatingBanner"; +import { + OrgUpgradeBanner, + type OrgUpgradeBannerProps, +} from "@calcom/features/ee/organizations/components/OrgUpgradeBanner"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem"; -import { TeamsUpgradeBanner } from "@calcom/features/ee/teams/components"; +import { TeamsUpgradeBanner, type TeamsUpgradeBannerProps } from "@calcom/features/ee/teams/components"; import { useFlagMap } from "@calcom/features/flags/context/provider"; import { KBarContent, KBarRoot, KBarTrigger } from "@calcom/features/kbar/Kbar"; import TimezoneChangeDialog from "@calcom/features/settings/TimezoneChangeDialog"; -import AdminPasswordBanner from "@calcom/features/users/components/AdminPasswordBanner"; -import VerifyEmailBanner from "@calcom/features/users/components/VerifyEmailBanner"; +import AdminPasswordBanner, { + type AdminPasswordBannerProps, +} from "@calcom/features/users/components/AdminPasswordBanner"; +import CalendarCredentialBanner, { + type CalendarCredentialBannerProps, +} from "@calcom/features/users/components/CalendarCredentialBanner"; +import VerifyEmailBanner, { + type VerifyEmailBannerProps, +} from "@calcom/features/users/components/VerifyEmailBanner"; import classNames from "@calcom/lib/classNames"; +import { TOP_BANNER_HEIGHT } from "@calcom/lib/constants"; import { APP_NAME, DESKTOP_APP_LINK, JOIN_DISCORD, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants"; import getBrandColours from "@calcom/lib/getBrandColours"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; -import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { isKeyInObject } from "@calcom/lib/isKeyInObject"; import type { User } from "@calcom/prisma/client"; @@ -131,38 +143,29 @@ function useRedirectToLoginIfUnauthenticated(isPublic = false) { }; } -function AppTop({ setBannersHeight }: { setBannersHeight: Dispatch<SetStateAction<number>> }) { - const bannerRef = useRef<HTMLDivElement | null>(null); +type BannerTypeProps = { + teamUpgradeBanner: TeamsUpgradeBannerProps; + orgUpgradeBanner: OrgUpgradeBannerProps; + verifyEmailBanner: VerifyEmailBannerProps; + adminPasswordBanner: AdminPasswordBannerProps; + impersonationBanner: ImpersonatingBannerProps; + calendarCredentialBanner: CalendarCredentialBannerProps; +}; - useIsomorphicLayoutEffect(() => { - const resizeObserver = new ResizeObserver((entries) => { - const { offsetHeight } = entries[0].target as HTMLElement; - setBannersHeight(offsetHeight); - }); +type BannerType = keyof BannerTypeProps; - const currentBannerRef = bannerRef.current; +type BannerComponent = { + [Key in BannerType]: (props: BannerTypeProps[Key]) => JSX.Element; +}; - if (currentBannerRef) { - resizeObserver.observe(currentBannerRef); - } - - return () => { - if (currentBannerRef) { - resizeObserver.unobserve(currentBannerRef); - } - }; - }, [bannerRef]); - - return ( - <div ref={bannerRef} className="sticky top-0 z-10 w-full divide-y divide-black"> - <TeamsUpgradeBanner /> - <OrgUpgradeBanner /> - <ImpersonatingBanner /> - <AdminPasswordBanner /> - <VerifyEmailBanner /> - </div> - ); -} +const BannerComponent: BannerComponent = { + teamUpgradeBanner: (props: TeamsUpgradeBannerProps) => <TeamsUpgradeBanner {...props} />, + orgUpgradeBanner: (props: OrgUpgradeBannerProps) => <OrgUpgradeBanner {...props} />, + verifyEmailBanner: (props: VerifyEmailBannerProps) => <VerifyEmailBanner {...props} />, + adminPasswordBanner: (props: AdminPasswordBannerProps) => <AdminPasswordBanner {...props} />, + impersonationBanner: (props: ImpersonatingBannerProps) => <ImpersonatingBanner {...props} />, + calendarCredentialBanner: (props: CalendarCredentialBannerProps) => <CalendarCredentialBanner {...props} />, +}; function useRedirectToOnboardingIfNeeded() { const router = useRouter(); @@ -188,10 +191,41 @@ function useRedirectToOnboardingIfNeeded() { }; } +type allBannerProps = { [Key in BannerType]: BannerTypeProps[Key]["data"] }; + +const useBanners = () => { + const { data: getUserTopBanners, isLoading } = trpc.viewer.getUserTopBanners.useQuery(); + const { data: userSession } = useSession(); + + if (isLoading || !userSession) return null; + + const isUserInactiveAdmin = userSession?.user.role === "INACTIVE_ADMIN"; + const userImpersonatedByUID = userSession?.user.impersonatedByUID; + + const userSessionBanners = { + adminPasswordBanner: isUserInactiveAdmin ? userSession : null, + impersonationBanner: userImpersonatedByUID ? userSession : null, + }; + + const allBanners: allBannerProps = Object.assign({}, getUserTopBanners, userSessionBanners); + + return allBanners; +}; + const Layout = (props: LayoutProps) => { - const [bannersHeight, setBannersHeight] = useState<number>(0); + const banners = useBanners(); + const pageTitle = typeof props.heading === "string" && !props.title ? props.heading : props.title; + const bannersHeight = useMemo(() => { + const activeBanners = + banners && + Object.entries(banners).filter(([_, value]) => { + return value && (!Array.isArray(value) || value.length > 0); + }); + return (activeBanners?.length ?? 0) * TOP_BANNER_HEIGHT; + }, [banners]); + return ( <> {!props.withoutSeo && ( @@ -207,7 +241,32 @@ const Layout = (props: LayoutProps) => { {/* todo: only run this if timezone is different */} <TimezoneChangeDialog /> <div className="flex min-h-screen flex-col"> - <AppTop setBannersHeight={setBannersHeight} /> + {banners && ( + <div className="sticky top-0 z-10 w-full divide-y divide-black"> + {Object.keys(banners).map((key) => { + if (key === "teamUpgradeBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } else if (key === "orgUpgradeBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } else if (key === "verifyEmailBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } else if (key === "adminPasswordBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } else if (key === "impersonationBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } else if (key === "calendarCredentialBanner") { + const Banner = BannerComponent[key]; + return <Banner data={banners[key]} key={key} />; + } + })} + </div> + )} + <div className="flex flex-1" data-testid="dashboard-shell"> {props.SidebarContainer ? ( cloneElement(props.SidebarContainer, { bannersHeight }) @@ -362,7 +421,7 @@ function UserDropdown({ small }: UserDropdownProps) { <DropdownMenuTrigger asChild onClick={() => setMenuOpen((menuOpen) => !menuOpen)}> <button className={classNames( - "hover:bg-emphasis group mx-0 flex cursor-pointer appearance-none items-center rounded-full text-left outline-none transition focus:outline-none focus:ring-0 md:rounded-none lg:rounded", + "hover:bg-emphasis todesktop:!bg-transparent group mx-0 flex w-full cursor-pointer appearance-none items-center rounded-full text-left outline-none transition focus:outline-none focus:ring-0 md:rounded-none lg:rounded", small ? "p-2" : "px-2 py-1.5" )}> <span @@ -466,7 +525,7 @@ function UserDropdown({ small }: UserDropdownProps) { {t("help")} </DropdownItem> </DropdownMenuItem> - <DropdownMenuItem className="desktop-hidden hidden lg:flex"> + <DropdownMenuItem className="todesktop:hidden hidden lg:flex"> <DropdownItem StartIcon={Download} target="_blank" rel="noreferrer" href={DESKTOP_APP_LINK}> {t("download_desktop_app")} </DropdownItem> @@ -658,19 +717,21 @@ const NavigationItem: React.FC<{ href={item.href} aria-label={t(item.name)} className={classNames( - "text-default group flex items-center rounded-md px-2 py-1.5 text-sm font-medium transition", - item.child ? `[&[aria-current='page']]:bg-transparent` : `[&[aria-current='page']]:bg-emphasis`, + "todesktop:py-[7px] text-default group flex items-center rounded-md px-2 py-1.5 text-sm font-medium transition", + item.child ? `[&[aria-current='page']]:!bg-transparent` : `[&[aria-current='page']]:bg-emphasis`, isChild ? `[&[aria-current='page']]:text-emphasis [&[aria-current='page']]:bg-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 ${ props.index === 0 ? "mt-0" : "mt-px" }` : "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm", - isLocaleReady ? "hover:bg-emphasis hover:text-emphasis" : "" + isLocaleReady + ? "hover:bg-subtle todesktop:[&[aria-current='page']]:bg-emphasis todesktop:hover:bg-transparent hover:text-emphasis" + : "" )} aria-current={current ? "page" : undefined}> {item.icon && ( <item.icon - className="mr-2 h-4 w-4 flex-shrink-0 rtl:ml-2 md:ltr:mx-auto lg:ltr:mr-2 [&[aria-current='page']]:text-inherit" + className="todesktop:!text-blue-500 mr-2 h-4 w-4 flex-shrink-0 rtl:ml-2 md:ltr:mx-auto lg:ltr:mr-2 [&[aria-current='page']]:text-inherit" aria-hidden="true" aria-current={current ? "page" : undefined} /> @@ -828,9 +889,9 @@ function SideBar({ bannersHeight, user }: SideBarProps) { <div className="relative"> <aside style={{ maxHeight: `calc(100vh - ${bannersHeight}px)`, top: `${bannersHeight}px` }} - className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3"> + className="todesktop:!bg-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r md:sticky md:flex lg:w-56 lg:px-3"> <div className="flex h-full flex-col justify-between py-3 lg:pt-4"> - <header className="items-center justify-between md:hidden lg:flex"> + <header className="todesktop:-mt-3 todesktop:flex-col-reverse todesktop:[-webkit-app-region:drag] items-center justify-between md:hidden lg:flex"> {orgBranding ? ( <Link href="/settings/organizations/profile" className="px-1.5"> <div className="flex items-center gap-2 font-medium"> @@ -845,7 +906,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) { </div> </Link> ) : ( - <div data-testid="user-dropdown-trigger"> + <div data-testid="user-dropdown-trigger" className="todesktop:mt-4 w-full"> <span className="hidden lg:inline"> <UserDropdown /> </span> @@ -854,17 +915,17 @@ function SideBar({ bannersHeight, user }: SideBarProps) { </span> </div> )} - <div className="flex space-x-0.5 rtl:space-x-reverse"> + <div className="flex w-full justify-end space-x-2 rtl:space-x-reverse"> <button color="minimal" onClick={() => window.history.back()} - className="desktop-only hover:text-emphasis text-subtle group flex text-sm font-medium"> + className="todesktop:block hover:text-emphasis text-subtle group hidden text-sm font-medium"> <ArrowLeft className="group-hover:text-emphasis text-subtle h-4 w-4 flex-shrink-0" /> </button> <button color="minimal" onClick={() => window.history.forward()} - className="desktop-only hover:text-emphasis text-subtle group flex text-sm font-medium"> + className="todesktop:block hover:text-emphasis text-subtle group hidden text-sm font-medium"> <ArrowRight className="group-hover:text-emphasis text-subtle h-4 w-4 flex-shrink-0" /> </button> {!!orgBranding && ( @@ -876,8 +937,6 @@ function SideBar({ bannersHeight, user }: SideBarProps) { </div> </header> - <hr className="desktop-only border-subtle absolute -left-3 -right-3 mt-4 block w-full" /> - {/* logo icon for tablet */} <Link href="/event-types" className="text-center md:inline lg:hidden"> <Logo small icon /> @@ -917,7 +976,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) { <div className="flex">{t(item.name)}</div> </span> ) : ( - <SkeletonText style={{ width: `${item.name.length * 10}px` }} className="h-[20px]" /> + <SkeletonText className="h-[20px] w-full" /> )} </ButtonOrLink> </Tooltip> @@ -984,7 +1043,7 @@ export function ShellMain(props: LayoutProps) { props.backPath ? "relative" : "pwa:bottom-[max(7rem,_calc(5rem_+_env(safe-area-inset-bottom)))] fixed bottom-20 z-40 ltr:right-4 rtl:left-4 md:z-auto md:ltr:right-0 md:rtl:left-0", - "flex-shrink-0 md:relative md:bottom-auto md:right-auto" + "flex-shrink-0 [-webkit-app-region:no-drag] md:relative md:bottom-auto md:right-auto" )}> {isLocaleReady && props.CTA} </div> diff --git a/packages/features/timezone-buddy/components/AvailabilitySliderTable.tsx b/packages/features/timezone-buddy/components/AvailabilitySliderTable.tsx index 0dd1a2bb9b..800df9acbb 100644 --- a/packages/features/timezone-buddy/components/AvailabilitySliderTable.tsx +++ b/packages/features/timezone-buddy/components/AvailabilitySliderTable.tsx @@ -9,7 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc"; import { Button, ButtonGroup, DataTable } from "@calcom/ui"; -import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar"; +import { UserAvatar } from "@calcom/ui"; import { UpgradeTip } from "../../tips/UpgradeTip"; import { TBContext, createTimezoneBuddyStore } from "../store"; @@ -130,7 +130,7 @@ export function AvailabilitySliderTable() { id: "slider", header: () => { return ( - <div className="flex items-center"> + <div className="flex items-center space-x-2"> <ButtonGroup containerProps={{ className: "space-x-0" }}> <Button color="minimal" @@ -145,7 +145,7 @@ export function AvailabilitySliderTable() { variant="icon" /> </ButtonGroup> - <span>{browsingDate.format("DD dddd MMM, YYYY")}</span> + <span>{browsingDate.format("LL")}</span> </div> ); }, @@ -192,8 +192,9 @@ export function AvailabilitySliderTable() { browsingDate: browsingDate.toDate(), })}> <> - <div className="relative"> + <div className="relative -mx-2 w-[calc(100%+16px)] overflow-x-scroll px-2 lg:-mx-6 lg:w-[calc(100%+48px)] lg:px-6"> <DataTable + variant="compact" searchKey="member" tableContainerRef={tableContainerRef} columns={memorisedColumns} diff --git a/packages/features/timezone-buddy/components/TimeDial.tsx b/packages/features/timezone-buddy/components/TimeDial.tsx index 2b5878bf85..9c5daf43d0 100644 --- a/packages/features/timezone-buddy/components/TimeDial.tsx +++ b/packages/features/timezone-buddy/components/TimeDial.tsx @@ -106,7 +106,7 @@ export function TimeDial({ timezone, dateRanges }: TimeDialProps) { return ( <> - <div className="flex items-end overflow-auto text-sm"> + <div className="flex items-end justify-center overflow-auto text-sm"> {days.map((day, i) => { if (!day.length) return null; const dateWithDaySet = usersTimezoneDate.add(i - 1, "day"); @@ -165,7 +165,7 @@ export function TimeDial({ timezone, dateRanges }: TimeDialProps) { key={h} className={classNames( "flex h-8 flex-col items-center justify-center", - isInRange ? "text-emphasis dark:text-inverted" : "", + isInRange ? "text-emphasis" : "", isInRange && !rangeOverlap ? "bg-success" : "", hours ? "" : "bg-subtle font-medium" )} diff --git a/packages/features/tips/UpgradeTip.tsx b/packages/features/tips/UpgradeTip.tsx index 9859de8b54..d8c4a177d0 100644 --- a/packages/features/tips/UpgradeTip.tsx +++ b/packages/features/tips/UpgradeTip.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from "react"; import { classNames } from "@calcom/lib"; import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc"; export function UpgradeTip({ dark, @@ -29,10 +30,14 @@ export function UpgradeTip({ }) { const { t } = useLocale(); const { isLoading, hasTeamPlan } = useHasTeamPlan(); + const { data } = trpc.viewer.teams.getUpgradeable.useQuery(); + const hasEnterprisePlan = false; //const { isLoading , hasEnterprisePlan } = useHasEnterprisePlan(); - if (plan === "team" && hasTeamPlan) return children; + const hasUnpublishedTeam = !!data?.[0]; + + if (plan === "team" && (hasTeamPlan || hasUnpublishedTeam)) return children; if (plan === "enterprise" && hasEnterprisePlan) return children; @@ -44,7 +49,7 @@ export function UpgradeTip({ <picture className="absolute min-h-[295px] w-full rounded-lg object-cover"> <source srcSet={`${background}-dark.jpg`} media="(prefers-color-scheme: dark)" /> <img - className="absolute min-h-[295px] w-full rounded-lg object-cover object-left md:object-center select-none" + className="absolute min-h-[295px] w-full select-none rounded-lg object-cover object-left md:object-center" src={`${background}.jpg`} loading="lazy" alt={title} diff --git a/packages/features/troubleshooter/components/TroubleshooterSidebar.tsx b/packages/features/troubleshooter/components/TroubleshooterSidebar.tsx index c5286fa917..3940079ab5 100644 --- a/packages/features/troubleshooter/components/TroubleshooterSidebar.tsx +++ b/packages/features/troubleshooter/components/TroubleshooterSidebar.tsx @@ -29,7 +29,7 @@ export const TroubleshooterSidebar = () => { const { t } = useLocale(); return ( - <div className="relative z-10 hidden w-full flex-col gap-6 py-6 pl-4 pr-6 sm:flex md:pl-0"> + <div className="relative z-10 hidden h-screen w-full flex-col gap-6 overflow-y-scroll py-6 pl-4 pr-6 sm:flex md:pl-0"> <BackButtonInSidebar name={t("troubleshooter")} /> <EventTypeSelect /> <EventScheduleItem /> diff --git a/packages/features/users/components/AdminPasswordBanner.tsx b/packages/features/users/components/AdminPasswordBanner.tsx index 3ac73bd15d..7c01f1d9f4 100644 --- a/packages/features/users/components/AdminPasswordBanner.tsx +++ b/packages/features/users/components/AdminPasswordBanner.tsx @@ -1,12 +1,13 @@ -import { useSession } from "next-auth/react"; +import type { SessionContextValue } from "next-auth/react"; import Link from "next/link"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { TopBanner } from "@calcom/ui"; -function AdminPasswordBanner() { +export type AdminPasswordBannerProps = { data: SessionContextValue["data"] }; + +function AdminPasswordBanner({ data }: AdminPasswordBannerProps) { const { t } = useLocale(); - const { data } = useSession(); if (data?.user.role !== "INACTIVE_ADMIN") return null; diff --git a/packages/features/users/components/CalendarCredentialBanner.tsx b/packages/features/users/components/CalendarCredentialBanner.tsx new file mode 100644 index 0000000000..c20448d78b --- /dev/null +++ b/packages/features/users/components/CalendarCredentialBanner.tsx @@ -0,0 +1,31 @@ +import Link from "next/link"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { type RouterOutputs } from "@calcom/trpc"; +import { TopBanner } from "@calcom/ui"; + +export type CalendarCredentialBannerProps = { + data: RouterOutputs["viewer"]["getUserTopBanners"]["calendarCredentialBanner"]; +}; + +function CalendarCredentialBanner({ data }: CalendarCredentialBannerProps) { + const { t } = useLocale(); + + if (!data) return null; + + return ( + <> + <TopBanner + text={`${t("something_went_wrong")} ${t("calendar_error")}`} + variant="error" + actions={ + <Link href="/apps/installed/calendar" className="border-b border-b-black"> + {t("check_here")} + </Link> + } + /> + </> + ); +} + +export default CalendarCredentialBanner; diff --git a/packages/features/users/components/UserTable/BulkActions/DeleteBulkUsers.tsx b/packages/features/users/components/UserTable/BulkActions/DeleteBulkUsers.tsx index 639618f4c6..3215264b71 100644 --- a/packages/features/users/components/UserTable/BulkActions/DeleteBulkUsers.tsx +++ b/packages/features/users/components/UserTable/BulkActions/DeleteBulkUsers.tsx @@ -8,9 +8,10 @@ import type { User } from "../UserListTable"; interface Props { users: User[]; + onRemove: () => void; } -export function DeleteBulkUsers({ users }: Props) { +export function DeleteBulkUsers({ users, onRemove }: Props) { const { t } = useLocale(); const selectedRows = users; // Get selected rows from table const utils = trpc.useContext(); @@ -37,6 +38,7 @@ export function DeleteBulkUsers({ users }: Props) { deleteMutation.mutateAsync({ userIds: selectedRows.map((user) => user.id), }); + onRemove(); }}> <p className="mt-5"> {t("remove_users_from_org_confirm", { diff --git a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx index 3202758a10..f5b9375635 100644 --- a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx +++ b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx @@ -1,10 +1,12 @@ import { zodResolver } from "@hookform/resolvers/zod"; import type { Dispatch } from "react"; +import { useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { shallow } from "zustand/shallow"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { MembershipRole } from "@calcom/prisma/enums"; import { trpc, type RouterOutputs } from "@calcom/trpc/react"; import { Form, @@ -21,12 +23,17 @@ import { import type { Action } from "../UserListTable"; import { useEditMode } from "./store"; +type MembershipOption = { + value: MembershipRole; + label: string; +}; + const editSchema = z.object({ name: z.string(), email: z.string().email(), avatar: z.string(), bio: z.string(), - role: z.enum(["ADMIN", "MEMBER", "OWNER"]), + role: z.enum([MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER]), timeZone: z.string(), // schedules: z.array(z.string()), // teams: z.array(z.string()), @@ -60,6 +67,31 @@ export function EditForm({ }, }); + const { data: currentMembership } = trpc.viewer.organizations.listCurrent.useQuery(); + const isOwner = currentMembership?.user.role === MembershipRole.OWNER; + + const membershipOptions = useMemo<MembershipOption[]>(() => { + const options: MembershipOption[] = [ + { + value: MembershipRole.MEMBER, + label: t("member"), + }, + { + value: MembershipRole.ADMIN, + label: t("admin"), + }, + ]; + + if (isOwner) { + options.push({ + value: MembershipRole.OWNER, + label: t("owner"), + }); + } + + return options; + }, [t, isOwner]); + const mutation = trpc.viewer.organizations.updateUser.useMutation({ onSuccess: () => { dispatch({ type: "CLOSE_MODAL" }); @@ -88,7 +120,7 @@ export function EditForm({ setMutationLoading(true); mutation.mutate({ userId: selectedUser?.id ?? "", - role: values.role as "ADMIN" | "MEMBER", // Cast needed as we dont provide an option for owner + role: values.role, name: values.name, email: values.email, avatar: values.avatar, @@ -135,16 +167,7 @@ export function EditForm({ isFullWidth defaultValue={selectedUser?.role ?? "MEMBER"} value={form.watch("role")} - options={[ - { - value: "MEMBER", - label: t("member"), - }, - { - value: "ADMIN", - label: t("admin"), - }, - ]} + options={membershipOptions} onValueChange={(value: EditSchema["role"]) => { form.setValue("role", value); }} diff --git a/packages/features/users/components/UserTable/UserListTable.tsx b/packages/features/users/components/UserTable/UserListTable.tsx index a2b9ecb3d6..bd9f6dd411 100644 --- a/packages/features/users/components/UserTable/UserListTable.tsx +++ b/packages/features/users/components/UserTable/UserListTable.tsx @@ -166,10 +166,16 @@ export function UserListTable() { <div className="flex items-center gap-2"> <Avatar size="sm" alt={username || email} imageSrc={`${domain}/${username}/avatar.png`} /> <div className=""> - <div className="text-emphasis text-sm font-medium leading-none"> + <div + data-testid={`member-${username}-username`} + className="text-emphasis text-sm font-medium leading-none"> {username || "No username"} </div> - <div className="text-subtle mt-1 text-sm leading-none">{email}</div> + <div + data-testid={`member-${username}-email`} + className="text-subtle mt-1 text-sm leading-none"> + {email} + </div> </div> </div> ); @@ -185,9 +191,10 @@ export function UserListTable() { accessorFn: (data) => data.role, header: "Role", cell: ({ row, table }) => { - const { role } = row.original; + const { role, username } = row.original; return ( <Badge + data-testid={`member-${username}-role`} variant={role === "MEMBER" ? "gray" : "blue"} onClick={() => { table.getColumn("role")?.setFilterValue([role]); @@ -204,12 +211,13 @@ export function UserListTable() { id: "teams", header: "Teams", cell: ({ row }) => { - const { teams, accepted, email } = row.original; + const { teams, accepted, email, username } = row.original; // TODO: Implement click to filter return ( <div className="flex h-full flex-wrap items-center gap-2"> {accepted ? null : ( <Badge + data-testid2={`member-${username}-pending`} variant="red" className="text-xs" data-testid={`email-${email.replace("@", "")}-pending`}> @@ -290,7 +298,10 @@ export function UserListTable() { { type: "render", render: (table) => ( - <DeleteBulkUsers users={table.getSelectedRowModel().flatRows.map((row) => row.original)} /> + <DeleteBulkUsers + users={table.getSelectedRowModel().flatRows.map((row) => row.original)} + onRemove={() => table.toggleAllPageRowsSelected(false)} + /> ), }, ]} diff --git a/packages/features/users/components/VerifyEmailBanner.tsx b/packages/features/users/components/VerifyEmailBanner.tsx index cc8aebbff1..cdbf9125ff 100644 --- a/packages/features/users/components/VerifyEmailBanner.tsx +++ b/packages/features/users/components/VerifyEmailBanner.tsx @@ -1,23 +1,21 @@ -import { useSession } from "next-auth/react"; - import { APP_NAME } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; -import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck"; import { TopBanner, showToast } from "@calcom/ui"; import { Mail } from "@calcom/ui/components/icon"; import { useFlagMap } from "../../flags/context/provider"; -function VerifyEmailBanner() { +export type VerifyEmailBannerProps = { + data: boolean; +}; + +function VerifyEmailBanner({ data }: VerifyEmailBannerProps) { const flags = useFlagMap(); const { t } = useLocale(); - const { data, isLoading } = useEmailVerifyCheck(); const mutation = trpc.viewer.auth.resendVerifyEmail.useMutation(); - const session = useSession(); - const isLoggedIn = session?.data?.user; - if (!isLoggedIn || isLoading || data?.isVerified || !flags["email-verification"]) return null; + if (!data || !flags["email-verification"]) return null; return ( <> diff --git a/packages/features/webhooks/components/WebhookForm.tsx b/packages/features/webhooks/components/WebhookForm.tsx index d599f77394..fc1ba77b34 100644 --- a/packages/features/webhooks/components/WebhookForm.tsx +++ b/packages/features/webhooks/components/WebhookForm.tsx @@ -39,7 +39,9 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve { value: WebhookTriggerEvents.BOOKING_RESCHEDULED, label: "booking_rescheduled" }, { value: WebhookTriggerEvents.BOOKING_PAID, label: "booking_paid" }, { value: WebhookTriggerEvents.MEETING_ENDED, label: "meeting_ended" }, + { value: WebhookTriggerEvents.MEETING_STARTED, label: "meeting_started" }, { value: WebhookTriggerEvents.RECORDING_READY, label: "recording_ready" }, + { value: WebhookTriggerEvents.INSTANT_MEETING, label: "instant_meeting_created" }, ], "routing-forms": [{ value: WebhookTriggerEvents.FORM_SUBMITTED, label: "form_submitted" }], } as const; @@ -70,7 +72,9 @@ const WebhookForm = (props: { subscriberUrl: props.webhook?.subscriberUrl || "", active: props.webhook ? props.webhook.active : true, eventTriggers: !props.webhook - ? translatedTriggerOptions.map((option) => option.value) + ? translatedTriggerOptions + .filter((option) => option.value !== WebhookTriggerEvents.INSTANT_MEETING) + .map((option) => option.value) : props.webhook.eventTriggers, secret: props?.webhook?.secret || "", payloadTemplate: props?.webhook?.payloadTemplate || undefined, diff --git a/packages/features/webhooks/components/WebhookTestDisclosure.tsx b/packages/features/webhooks/components/WebhookTestDisclosure.tsx index 145c038615..52db5aee4e 100644 --- a/packages/features/webhooks/components/WebhookTestDisclosure.tsx +++ b/packages/features/webhooks/components/WebhookTestDisclosure.tsx @@ -1,7 +1,9 @@ import { useWatch } from "react-hook-form"; +import { ZodError } from "zod"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; +import { ZTestTriggerInputSchema } from "@calcom/trpc/server/routers/viewer/webhook/testTrigger.schema"; import { Badge, Button, showToast } from "@calcom/ui"; import { Activity } from "@calcom/ui/components/icon"; @@ -27,9 +29,25 @@ export default function WebhookTestDisclosure() { color="secondary" disabled={mutation.isLoading || !subscriberUrl} StartIcon={Activity} - onClick={() => - mutation.mutate({ url: subscriberUrl, secret: webhookSecret, type: "PING", payloadTemplate }) - }> + onClick={() => { + try { + ZTestTriggerInputSchema.parse({ + url: subscriberUrl, + secret: webhookSecret, + type: "PING", + payloadTemplate, + }); + mutation.mutate({ url: subscriberUrl, secret: webhookSecret, type: "PING", payloadTemplate }); + } catch (error) { + //this catches invalid subscriberUrl before calling the mutation + if (error instanceof ZodError) { + const errorMessage = error.errors.map((e) => e.message).join(", "); + showToast(errorMessage, "error"); + } else { + showToast(t("unexpected_error_try_again"), "error"); + } + } + }}> {t("ping_test")} </Button> </div> diff --git a/packages/features/webhooks/lib/constants.ts b/packages/features/webhooks/lib/constants.ts index 326a866a6e..317e88e16e 100644 --- a/packages/features/webhooks/lib/constants.ts +++ b/packages/features/webhooks/lib/constants.ts @@ -10,9 +10,11 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = { WebhookTriggerEvents.BOOKING_PAID, WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED, WebhookTriggerEvents.MEETING_ENDED, + WebhookTriggerEvents.MEETING_STARTED, WebhookTriggerEvents.BOOKING_REQUESTED, WebhookTriggerEvents.BOOKING_REJECTED, WebhookTriggerEvents.RECORDING_READY, + WebhookTriggerEvents.INSTANT_MEETING, ] as const, "routing-forms": [WebhookTriggerEvents.FORM_SUBMITTED] as const, }; diff --git a/packages/features/webhooks/lib/cron.ts b/packages/features/webhooks/lib/cron.ts index 089ddbbc5a..913217b274 100644 --- a/packages/features/webhooks/lib/cron.ts +++ b/packages/features/webhooks/lib/cron.ts @@ -5,6 +5,8 @@ import dayjs from "@calcom/dayjs"; import { defaultHandler } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; +import { jsonParse } from "./sendPayload"; + async function handler(req: NextApiRequest, res: NextApiResponse) { const apiKey = req.headers.authorization || req.query.apiKey; if (process.env.CRON_API_KEY !== apiKey) { @@ -27,6 +29,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { await fetch(job.subscriberUrl, { method: "POST", body: job.payload, + headers: { + "Content-Type": + !job.payload || jsonParse(job.payload) ? "application/json" : "application/x-www-form-urlencoded", + }, }); } catch (error) { console.log(`Error running webhook trigger (retry count: ${job.retryCount}): ${error}`); diff --git a/packages/features/webhooks/lib/scheduleTrigger.ts b/packages/features/webhooks/lib/scheduleTrigger.ts index ab44805f21..9048be3c65 100644 --- a/packages/features/webhooks/lib/scheduleTrigger.ts +++ b/packages/features/webhooks/lib/scheduleTrigger.ts @@ -45,7 +45,10 @@ export async function addSubscription({ }, }); - if (triggerEvent === WebhookTriggerEvents.MEETING_ENDED) { + if ( + triggerEvent === WebhookTriggerEvents.MEETING_ENDED || + triggerEvent === WebhookTriggerEvents.MEETING_STARTED + ) { //schedule job for already existing bookings const where: Prisma.BookingWhereInput = {}; if (teamId) { @@ -64,10 +67,15 @@ export async function addSubscription({ }); for (const booking of bookings) { - scheduleTrigger(booking, createSubscription.subscriberUrl, { - id: createSubscription.id, - appId: createSubscription.appId, - }); + scheduleTrigger( + booking, + createSubscription.subscriberUrl, + { + id: createSubscription.id, + appId: createSubscription.appId, + }, + triggerEvent + ); } } @@ -273,12 +281,13 @@ export async function listBookings( } export async function scheduleTrigger( - booking: { id: number; endTime: Date; scheduledJobs: string[] }, + booking: { id: number; endTime: Date; startTime: Date; scheduledJobs: string[] }, subscriberUrl: string, - subscriber: { id: string; appId: string | null } + subscriber: { id: string; appId: string | null }, + triggerEvent: WebhookTriggerEvents ) { try { - const payload = JSON.stringify({ triggerEvent: WebhookTriggerEvents.MEETING_ENDED, ...booking }); + const payload = JSON.stringify({ triggerEvent, ...booking }); const jobName = `${subscriber.appId}_${subscriber.id}`; // add scheduled job to database @@ -286,7 +295,7 @@ export async function scheduleTrigger( data: { jobName, payload, - startAfter: booking.endTime, + startAfter: triggerEvent === WebhookTriggerEvents.MEETING_ENDED ? booking.endTime : booking.startTime, subscriberUrl, }, }); diff --git a/packages/features/webhooks/lib/sendPayload.ts b/packages/features/webhooks/lib/sendPayload.ts index 7ff22d9577..dc6e76f1d2 100644 --- a/packages/features/webhooks/lib/sendPayload.ts +++ b/packages/features/webhooks/lib/sendPayload.ts @@ -87,7 +87,7 @@ function applyTemplate(template: string, data: WebhookDataType, contentType: Con return compiled; } -function jsonParse(jsonString: string) { +export function jsonParse(jsonString: string) { try { return JSON.parse(jsonString); } catch (e) { diff --git a/packages/features/webhooks/pages/webhooks-view.tsx b/packages/features/webhooks/pages/webhooks-view.tsx index 34c1975f0c..2cd9e1946c 100644 --- a/packages/features/webhooks/pages/webhooks-view.tsx +++ b/packages/features/webhooks/pages/webhooks-view.tsx @@ -78,7 +78,7 @@ const WebhooksView = () => { <></> ) } - borderInShellHeader={data && data.profiles.length === 1} + borderInShellHeader={(data && data.profiles.length === 1) || !data?.webhookGroups?.length} /> <div> <WebhooksList webhooksByViewer={data} /> diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts index 29ac3ab171..55c569fcc3 100644 --- a/packages/lib/CalEventParser.ts +++ b/packages/lib/CalEventParser.ts @@ -176,7 +176,11 @@ export const getRescheduleLink = (calEvent: CalendarEvent): string => { return `${calEvent.bookerUrl ?? WEBAPP_URL}/reschedule/${seatUid ? seatUid : Uid}`; }; -export const getRichDescription = (calEvent: CalendarEvent, t_?: TFunction /*, attendee?: Person*/) => { +export const getRichDescription = ( + calEvent: CalendarEvent, + t_?: TFunction /*, attendee?: Person*/, + includeAppStatus = false +) => { const t = t_ ?? calEvent.organizer.language.translate; return ` @@ -189,7 +193,7 @@ ${getLocation(calEvent)} ${getDescription(calEvent, t)} ${getAdditionalNotes(calEvent, t)} ${getUserFieldsResponses(calEvent)} -${getAppsStatus(calEvent, t)} +${includeAppStatus ? getAppsStatus(calEvent, t) : ""} ${ // TODO: Only the original attendee can make changes to the event // Guests cannot diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 6f1a66ab9a..acf6e4ce8c 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -108,6 +108,8 @@ export const APP_CREDENTIAL_SHARING_ENABLED = export const DEFAULT_LIGHT_BRAND_COLOR = "#292929"; export const DEFAULT_DARK_BRAND_COLOR = "#fafafa"; +export const TOP_BANNER_HEIGHT = 40; + const defaultOnNaN = (testedValue: number, defaultValue: number) => !Number.isNaN(testedValue) ? testedValue : defaultValue; @@ -119,3 +121,8 @@ export const AB_TEST_BUCKET_PROBABILITY = defaultOnNaN( export const IS_PREMIUM_USERNAME_ENABLED = (IS_CALCOM || (process.env.NEXT_PUBLIC_IS_E2E && IS_STRIPE_ENABLED)) && process.env.NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE_MONTHLY; + +// Max number of invites to join a team/org that can be sent at once +export const MAX_NB_INVITES = 100; + +export const URL_PROTOCOL_REGEX = /(^\w+:|^)\/\//; diff --git a/packages/lib/date-fns/index.ts b/packages/lib/date-fns/index.ts index c89969bea8..947c731f18 100644 --- a/packages/lib/date-fns/index.ts +++ b/packages/lib/date-fns/index.ts @@ -147,7 +147,7 @@ export const isNextDayInTimezone = (time: string, timezoneA: string, timezoneB: }; const weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] as const; -type WeekDays = (typeof weekDays)[number]; +export type WeekDays = (typeof weekDays)[number]; type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; /** diff --git a/packages/lib/date-ranges.ts b/packages/lib/date-ranges.ts index 1032bba1bd..fabd7cb5c7 100644 --- a/packages/lib/date-ranges.ts +++ b/packages/lib/date-ranges.ts @@ -21,9 +21,10 @@ export function processWorkingHours({ dateFrom: Dayjs; dateTo: Dayjs; }) { + const utcDateTo = dateTo.utc(); const results = []; - for (let date = dateFrom.tz(timeZone).startOf("day"); dateTo.isAfter(date); date = date.add(1, "day")) { - const fromOffset = dateFrom.tz(timeZone).startOf("day").utcOffset(); + for (let date = dateFrom.startOf("day"); utcDateTo.isAfter(date); date = date.add(1, "day")) { + const fromOffset = dateFrom.startOf("day").utcOffset(); const offset = date.tz(timeZone).utcOffset(); // it always has to be start of the day (midnight) even when DST changes @@ -44,7 +45,7 @@ export function processWorkingHours({ start = start.add(offsetDiff, "minute"); end = end.add(offsetDiff, "minute"); - const startResult = dayjs.max(start, dateFrom.tz(timeZone)); + const startResult = dayjs.max(start, dateFrom); const endResult = dayjs.min(end, dateTo.tz(timeZone)); if (endResult.isBefore(startResult)) { @@ -92,10 +93,13 @@ export function buildDateRanges({ dateFrom: Dayjs; dateTo: Dayjs; }): DateRange[] { + const dateFromOrganizerTZ = dateFrom.tz(timeZone); const groupedWorkingHours = groupByDate( availability.reduce((processed: DateRange[], item) => { if ("days" in item) { - processed = processed.concat(processWorkingHours({ item, timeZone, dateFrom, dateTo })); + processed = processed.concat( + processWorkingHours({ item, timeZone, dateFrom: dateFromOrganizerTZ, dateTo }) + ); } return processed; }, []) diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index 5243b63ebd..43ddb0edd7 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -81,6 +81,7 @@ const commons = { seatsPerTimeSlot: null, seatsShowAttendees: null, seatsShowAvailabilityCount: null, + onlyShowFirstAvailableSlot: false, id: 0, hideCalendarNotes: false, recurringEvent: null, @@ -105,9 +106,9 @@ const commons = { const dynamicEvent = { length: 30, slug: "dynamic", - title: "Dynamic", - eventName: "Dynamic Event", - description: "", + title: "Group Meeting", + eventName: "Group Meeting", + description: "Join us for a meeting with multiple people", descriptionAsSafeHTML: "", position: 0, ...commons, diff --git a/packages/lib/domainManager/deploymentServices/cloudflare.ts b/packages/lib/domainManager/deploymentServices/cloudflare.ts new file mode 100644 index 0000000000..c59552cba2 --- /dev/null +++ b/packages/lib/domainManager/deploymentServices/cloudflare.ts @@ -0,0 +1,234 @@ +import z from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { safeStringify } from "@calcom/lib/safeStringify"; + +import logger from "../../logger"; + +const log = logger.getSubLogger({ prefix: ["cloudflare"] }); + +// TODO: This and other settings should really come from DB when admin allows configuring which deployment services to use for the organization +const IS_RECORD_PROXIED = true; +const AUTOMATIC_TTL = 1; + +const ERROR_CODE_CNAME_ALREADY_EXISTS = 81053; +const ERROR_CODE_RECORD_ALREADY_EXISTS = 81057; +const ERROR_CODE_RECORD_DOES_NOT_EXIST = 81044; + +const cloudflareApiForZoneUrl = `https://api.cloudflare.com/client/v4/zones/${process.env.CLOUDFLARE_ZONE_ID}`; +const cloudflareDnsRecordApiResponseSchema = z + .object({ + success: z.boolean().optional(), + errors: z + .array( + z + .object({ + code: z.number(), + }) + .passthrough() + ) + .nullish(), + result: z + .object({ + id: z.string(), + }) + .nullish(), + }) + .passthrough(); + +export const addDnsRecord = async (domain: string) => { + assertCloudflareEnvVars(process.env); + log.info(`Creating dns-record in Cloudflare: ${domain}`); + const data = await api( + `${cloudflareApiForZoneUrl}/dns_records`, + { + method: "POST", + body: JSON.stringify({ + type: "CNAME", + proxied: IS_RECORD_PROXIED, + name: domain, + content: process.env.CLOUDFLARE_VERCEL_CNAME, + ttl: AUTOMATIC_TTL, + }), + }, + cloudflareDnsRecordApiResponseSchema + ); + + if (!data.success) { + if (isRecordAlreadyExistError(data.errors)) { + log.info(`CNAME already exists in Cloudflare: ${domain}`); + return true; + } + const errorMessage = `Failed to create dns-record in Cloudflare: ${domain}`; + log.error( + safeStringify({ + errorMessage, + response: data, + }) + ); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); + } + log.info(`Created dns-record in Cloudflare: ${domain}`); + return true; +}; + +export const deleteDnsRecord = async (domain: string) => { + log.info(`Deleting dns-record in Cloudflare: ${domain}`); + assertCloudflareEnvVars(process.env); + + const dnsRecordToDelete = await getDnsRecordToDelete(); + await deleteDnsRecord(dnsRecordToDelete); + + log.info(`Deleted dns-record in Cloudflare: ${domain}`); + return true; + + async function getDnsRecordToDelete() { + // Get the dns-record id from dns_records list API + const searchResult = await api( + `${cloudflareApiForZoneUrl}/dns_records?name=${domain}`, + { + method: "GET", + }, + z + .object({ + success: z.boolean().optional(), + result: z + .array( + z + .object({ + id: z.string(), + }) + .passthrough() + ) + .nullish(), + }) + .passthrough() + ); + + if (!searchResult.success || !searchResult.result) { + log.error( + safeStringify({ + errorMessage: `Failed to search for dns-record in Cloudflare for ${domain}`, + searchData: searchResult, + }) + ); + throw new HttpError({ + message: `Something went wrong.`, + statusCode: 500, + }); + } + + if (searchResult.result.length > 1) { + log.error( + safeStringify({ + errorMessage: `Found more than one dns-record in Cloudflare for ${domain}`, + searchData: searchResult, + }) + ); + throw new HttpError({ + message: `Something went wrong.`, + statusCode: 400, + }); + } + + return searchResult.result[0]; + } + + async function deleteDnsRecord(dnsRecordToDelete: { id: string }) { + const deletionResult = await api( + `${cloudflareApiForZoneUrl}/dns_records/${dnsRecordToDelete.id}`, + { + method: "DELETE", + }, + cloudflareDnsRecordApiResponseSchema + ); + + if (!deletionResult.success) { + if (isRecordNotExistingError(deletionResult.errors)) { + log.info(`CNAME already deleted: ${domain}`); + return true; + } + log.error( + `Failed to delete dns-record in Cloudflare: ${domain}`, + safeStringify({ + deletionResult, + }) + ); + throw new HttpError({ + message: "Something went wrong.", + statusCode: 400, + }); + } + + log.info(`Deleted dns-record in Cloudflare: ${domain}`); + return true; + } +}; + +async function api<T extends z.ZodType<unknown>>( + url: string, + { + method, + body, + }: { + body?: string; + method: "POST" | "GET" | "DELETE"; + }, + responseSchema: T +): Promise<z.infer<T>> { + const response = await fetch(url, { + method: method, + headers: { + Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_CLOUDFLARE}`, + "Content-Type": "application/json", + }, + body, + }); + + const result = await response.json(); + const dataParsed = responseSchema.safeParse(result); + + if (!dataParsed.success) { + log.error( + "Error parsing", + safeStringify({ + dnsAddResult: result, + }) + ); + throw new HttpError({ + message: "Something went wrong", + statusCode: 500, + }); + } + return dataParsed.data; +} + +function assertCloudflareEnvVars(env: typeof process.env): asserts env is { + CLOUDFLARE_VERCEL_CNAME: string; + CLOUDFLARE_ZONE_ID: string; + AUTH_BEARER_TOKEN_CLOUDFLARE: string; +} & typeof process.env { + if (!env.CLOUDFLARE_VERCEL_CNAME) { + throw new Error("Missing env var: CLOUDFLARE_VERCEL_CNAME"); + } + + if (!env.CLOUDFLARE_ZONE_ID) { + throw new Error("Missing env var: CLOUDFLARE_ZONE_ID"); + } + + if (!env.AUTH_BEARER_TOKEN_CLOUDFLARE) { + throw new Error("Missing env var: AUTH_BEARER_TOKEN_CLOUDFLARE"); + } +} + +const isRecordAlreadyExistError = (errors: { code: number }[] | undefined | null) => + errors?.every( + (error) => + error.code === ERROR_CODE_CNAME_ALREADY_EXISTS || error.code === ERROR_CODE_RECORD_ALREADY_EXISTS + ); + +const isRecordNotExistingError = (errors: { code: number }[] | undefined | null) => + errors?.every((error) => error.code === ERROR_CODE_RECORD_DOES_NOT_EXIST); diff --git a/packages/lib/domainManager/deploymentServices/vercel.ts b/packages/lib/domainManager/deploymentServices/vercel.ts new file mode 100644 index 0000000000..65c1292ee0 --- /dev/null +++ b/packages/lib/domainManager/deploymentServices/vercel.ts @@ -0,0 +1,147 @@ +import z from "zod"; + +import { HttpError } from "@calcom/lib/http-error"; +import { safeStringify } from "@calcom/lib/safeStringify"; + +import logger from "../../logger"; + +const vercelApiForProjectUrl = `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}`; +const vercelDomainApiResponseSchema = z.object({ + error: z + .object({ + code: z.string().nullish(), + domain: z.string().nullish(), + }) + .optional(), +}); + +export const createDomain = async (domain: string) => { + assertVercelEnvVars(process.env); + logger.info(`Creating domain in Vercel: ${domain}`); + const response = await fetch(`${vercelApiForProjectUrl}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, { + body: JSON.stringify({ name: domain }), + headers: { + Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, + "Content-Type": "application/json", + }, + method: "POST", + }); + + const data = vercelDomainApiResponseSchema.parse(await response.json()); + + if (!data.error) { + return true; + } + + return handleDomainCreationError(data.error); +}; + +export const deleteDomain = async (domain: string) => { + logger.info(`Deleting domain in Vercel: ${domain}`); + assertVercelEnvVars(process.env); + + const response = await fetch( + `${vercelApiForProjectUrl}/domains/${domain}?teamId=${process.env.TEAM_ID_VERCEL}`, + { + headers: { + Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, + }, + method: "DELETE", + } + ); + + const data = vercelDomainApiResponseSchema.parse(await response.json()); + if (!data.error) { + return true; + } + + return handleDomainDeletionError(data.error); +}; + +function handleDomainCreationError(error: { code?: string | null; domain?: string | null }) { + // Domain is already owned by another team but you can request delegation to access it + if (error.code === "forbidden") { + const errorMessage = "Domain is already owned by another team"; + logger.error( + safeStringify({ + errorMessage, + vercelError: error, + }) + ); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); + } + + if (error.code === "domain_taken") { + const errorMessage = "Domain is already being used by a different project"; + logger.error( + safeStringify({ + errorMessage, + vercelError: error, + }) + ); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); + } + + if (error.code === "domain_already_in_use") { + // Domain is already configured correctly, this is not an error when it happens during creation as it could be re-attempt to create an existing domain + return true; + } + + const errorMessage = `Failed to create domain on Vercel: ${error.domain}`; + logger.error(safeStringify({ errorMessage, vercelError: error })); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); +} + +function handleDomainDeletionError(error: { code?: string | null; domain?: string | null }) { + if (error.code === "not_found") { + // Domain is already deleted + return true; + } + + // Domain is already owned by another team but you can request delegation to access it + if (error.code === "forbidden") { + const errorMessage = "Domain is owned by another team"; + logger.error( + safeStringify({ + errorMessage, + vercelError: error, + }) + ); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); + } + + const errorMessage = `Failed to take action for domain: ${error.domain}`; + logger.error(safeStringify({ errorMessage, vercelError: error })); + throw new HttpError({ + message: errorMessage, + statusCode: 400, + }); +} + +function assertVercelEnvVars(env: typeof process.env): asserts env is { + PROJECT_ID_VERCEL: string; + TEAM_ID_VERCEL: string; + AUTH_BEARER_TOKEN_VERCEL: string; +} & typeof process.env { + if (!env.PROJECT_ID_VERCEL) { + throw new Error("Missing env var: PROJECT_ID_VERCEL"); + } + + // TEAM_ID_VERCEL is optional + + if (!env.AUTH_BEARER_TOKEN_VERCEL) { + throw new Error("Missing env var: AUTH_BEARER_TOKEN_VERCEL"); + } +} diff --git a/packages/lib/domainManager/organization.ts b/packages/lib/domainManager/organization.ts new file mode 100644 index 0000000000..ab738b09e0 --- /dev/null +++ b/packages/lib/domainManager/organization.ts @@ -0,0 +1,53 @@ +import { subdomainSuffix } from "@calcom/ee/organizations/lib/orgDomains"; + +import { deleteDnsRecord, addDnsRecord } from "./deploymentServices/cloudflare"; +import { + deleteDomain as deleteVercelDomain, + createDomain as createVercelDomain, +} from "./deploymentServices/vercel"; + +export const deleteDomain = async (slug: string) => { + const domain = `${slug}.${subdomainSuffix()}`; + // We must have some domain deleted + let isDomainDeleted = false; + + // TODO: Ideally we should start storing the DNS and domain entries in DB for each organization + // A separate DNS record is optional but if we have it, we must have it deleted + let isDnsRecordDeleted = true; + if (process.env.VERCEL_URL) { + isDomainDeleted = await deleteVercelDomain(domain); + } + + if (process.env.CLOUDFLARE_DNS) { + isDnsRecordDeleted = await deleteDnsRecord(domain); + } + return isDomainDeleted && isDnsRecordDeleted; +}; + +export const createDomain = async (slug: string) => { + const domain = `${slug}.${subdomainSuffix()}`; + + // We must have some domain configured + let domainConfigured = false; + + // A separate DNS record is optional but if we have it, we must have it configured + let dnsConfigured = true; + + if (process.env.VERCEL_URL) { + domainConfigured = await createVercelDomain(domain); + } + + if (process.env.CLOUDFLARE_DNS) { + dnsConfigured = await addDnsRecord(domain); + } + + return domainConfigured && dnsConfigured; +}; + +export const renameDomain = async (oldSlug: string | null, newSlug: string) => { + // First create new domain so that if it fails we still have the old domain + await createDomain(newSlug); + if (oldSlug) { + await deleteDomain(oldSlug); + } +}; diff --git a/packages/lib/fetchUsername.ts b/packages/lib/fetchUsername.ts index e33aedad46..e94ec32d5d 100644 --- a/packages/lib/fetchUsername.ts +++ b/packages/lib/fetchUsername.ts @@ -5,12 +5,13 @@ type ResponseUsernameApi = { suggestion?: string; }; -export async function fetchUsername(username: string) { +export async function fetchUsername(username: string, orgSlug: string | null) { const response = await fetch("/api/username", { credentials: "include", method: "POST", body: JSON.stringify({ username: username.trim(), + orgSlug: orgSlug ?? undefined, }), headers: { "Content-Type": "application/json", diff --git a/packages/lib/formbricks.ts b/packages/lib/formbricks.ts new file mode 100644 index 0000000000..b5ed06a546 --- /dev/null +++ b/packages/lib/formbricks.ts @@ -0,0 +1,42 @@ +import { FormbricksAPI } from "@formbricks/api"; + +import type { Feedback } from "@calcom/emails/templates/feedback-email"; + +enum Rating { + "Extremely unsatisfied" = 1, + "Unsatisfied" = 2, + "Satisfied" = 3, + "Extremely satisfied" = 4, +} + +export const sendFeedbackFormbricks = async (userId: number, feedback: Feedback) => { + if (!process.env.FORMBRICKS_HOST_URL || !process.env.FORMBRICKS_ENVIRONMENT_ID) + throw new Error("Missing FORMBRICKS_HOST_URL or FORMBRICKS_ENVIRONMENT_ID env variable"); + const api = new FormbricksAPI({ + apiHost: process.env.FORMBRICKS_HOST_URL, + environmentId: process.env.FORMBRICKS_ENVIRONMENT_ID, + }); + if (process.env.FORMBRICKS_FEEDBACK_SURVEY_ID) { + const formbricksUserId = userId.toString(); + const ratingValue = Object.keys(Rating).includes(feedback.rating) + ? Rating[feedback.rating as keyof typeof Rating] + : undefined; + if (ratingValue === undefined) throw new Error("Invalid rating value"); + + await api.client.response.create({ + surveyId: process.env.FORMBRICKS_FEEDBACK_SURVEY_ID, + userId: formbricksUserId, + finished: true, + data: { + "formbricks-share-comments-question": feedback.comment, + "formbricks-rating-question": ratingValue, + }, + }); + await api.client.people.update(formbricksUserId, { + attributes: { + email: feedback.email, + username: feedback.username, + }, + }); + } +}; diff --git a/packages/lib/getAvatarUrl.ts b/packages/lib/getAvatarUrl.ts index f1d11aba42..3ef573c63f 100644 --- a/packages/lib/getAvatarUrl.ts +++ b/packages/lib/getAvatarUrl.ts @@ -19,8 +19,22 @@ export const getUserAvatarUrl = ( }`; }; +export function getTeamAvatarUrl( + team: Pick<Team, "slug"> & { + organizationId?: number | null; + logoUrl?: string | null; + requestedSlug: string | null; + } +) { + if (team.logoUrl) { + return team.logoUrl; + } + const slug = team.slug ?? team.requestedSlug; + return `${WEBAPP_URL}/team/${slug}/avatar.png${team.organizationId ? `?orgId=${team.organizationId}` : ""}`; +} + export const getOrgAvatarUrl = ( - org: Pick<Team, "id" | "slug"> & { + org: Pick<Team, "slug"> & { logoUrl?: string | null; requestedSlug: string | null; } diff --git a/packages/lib/getBookerUrl/client.ts b/packages/lib/getBookerUrl/client.ts new file mode 100644 index 0000000000..6eaa692764 --- /dev/null +++ b/packages/lib/getBookerUrl/client.ts @@ -0,0 +1,30 @@ +import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains"; + +export const getBookerBaseUrlSync = ( + orgSlug: string | null, + options?: { + protocol: boolean; + } +) => { + return getOrgFullOrigin(orgSlug ?? "", options); +}; + +export const getTeamUrlSync = ( + { + orgSlug, + teamSlug, + }: { + orgSlug: string | null; + teamSlug: string | null; + }, + options?: { + protocol: boolean; + } +) => { + const bookerUrl = getBookerBaseUrlSync(orgSlug, options); + teamSlug = teamSlug || ""; + if (orgSlug) { + return `${bookerUrl}/${teamSlug}`; + } + return `${bookerUrl}/team/${teamSlug}`; +}; diff --git a/packages/lib/getBookerUrl/server.ts b/packages/lib/getBookerUrl/server.ts new file mode 100644 index 0000000000..0031fa7943 --- /dev/null +++ b/packages/lib/getBookerUrl/server.ts @@ -0,0 +1,12 @@ +import { CAL_URL } from "../constants"; +import { getBrand } from "../server/getBrand"; + +export const getBookerBaseUrl = async (user: { organizationId: number | null }) => { + const orgBrand = await getBrand(user.organizationId); + return orgBrand?.fullDomain ?? CAL_URL; +}; + +export const getTeamBookerUrl = async (team: { organizationId: number | null }) => { + const orgBrand = await getBrand(team.organizationId); + return orgBrand?.fullDomain ?? CAL_URL; +}; diff --git a/packages/lib/getEventTypeById.ts b/packages/lib/getEventTypeById.ts index be26508d9f..0632aed00d 100644 --- a/packages/lib/getEventTypeById.ts +++ b/packages/lib/getEventTypeById.ts @@ -1,7 +1,6 @@ import { Prisma } from "@prisma/client"; import { getLocationGroupedOptions } from "@calcom/app-store/server"; -import type { StripeData } from "@calcom/app-store/stripepayment/lib/server"; import { getEventTypeAppData } from "@calcom/app-store/utils"; import type { LocationObject } from "@calcom/core/location"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; @@ -9,12 +8,14 @@ import { parseBookingLimit, parseDurationLimit, parseRecurringEvent } from "@cal import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { getTranslation } from "@calcom/lib/server/i18n"; import type { PrismaClient } from "@calcom/prisma"; -import type { Credential } from "@calcom/prisma/client"; import { SchedulingType, MembershipRole } from "@calcom/prisma/enums"; import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { TRPCError } from "@trpc/server"; +import { CAL_URL } from "./constants"; +import { getBookerBaseUrl } from "./getBookerUrl/server"; + interface getEventTypeByIdProps { eventTypeId: number; userId: number; @@ -77,6 +78,7 @@ export default async function getEventTypeById({ slug: true, description: true, length: true, + isInstantEvent: true, offsetStart: true, hidden: true, locations: true, @@ -101,10 +103,16 @@ export default async function getEventTypeById({ slotInterval: true, hashedLink: true, bookingLimits: true, + onlyShowFirstAvailableSlot: true, durationLimits: true, successRedirectUrl: true, currency: true, bookingFields: true, + owner: { + select: { + organizationId: true, + }, + }, parent: { select: { teamId: true, @@ -166,6 +174,7 @@ export default async function getEventTypeById({ username: true, email: true, id: true, + organizationId: true, }, }, hidden: true, @@ -256,12 +265,18 @@ export default async function getEventTypeById({ metadata: parsedMetaData, customInputs: parsedCustomInputs, users: rawEventType.users, + bookerUrl: restEventType.team + ? await getBookerBaseUrl({ organizationId: restEventType.team.parentId }) + : restEventType.owner + ? await getBookerBaseUrl(restEventType.owner) + : CAL_URL, children: restEventType.children.flatMap((ch) => ch.owner !== null ? { ...ch, owner: { ...ch.owner, + avatar: getUserAvatarUrl(ch.owner), email: ch.owner.email, name: ch.owner.name ?? "", username: ch.owner.username ?? "", @@ -380,19 +395,3 @@ export default async function getEventTypeById({ }; return finalObj; } - -const getStripeCurrency = (stripeMetadata: { currency: string }, credentials: Credential[]) => { - // Favor the currency from the metadata as EventType.currency was not always set and should be deprecated - if (stripeMetadata.currency) { - return stripeMetadata.currency; - } - - // Legacy support for EventType.currency - const stripeCredential = credentials.find((integration) => integration.type === "stripe_payment"); - if (stripeCredential) { - return (stripeCredential.key as unknown as StripeData)?.default_currency || "usd"; - } - - // Fallback to USD but should not happen - return "usd"; -}; diff --git a/packages/lib/hasEditPermissionForUser.ts b/packages/lib/hasEditPermissionForUser.ts index 87d9e13e2f..c4adf2de40 100644 --- a/packages/lib/hasEditPermissionForUser.ts +++ b/packages/lib/hasEditPermissionForUser.ts @@ -2,8 +2,6 @@ import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; -const ROLES_WITH_EDIT_PERMISSION = [MembershipRole.ADMIN, MembershipRole.OWNER] as MembershipRole[]; - type InputOptions = { ctx: { user: NonNullable<TrpcSessionUser>; diff --git a/packages/lib/hooks/useTypedQuery.ts b/packages/lib/hooks/useTypedQuery.ts index 7fb61414d0..4d51dd2d97 100644 --- a/packages/lib/hooks/useTypedQuery.ts +++ b/packages/lib/hooks/useTypedQuery.ts @@ -1,5 +1,7 @@ +"use client"; + import { usePathname, useRouter } from "next/navigation"; -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, useEffect } from "react"; import { z } from "zod"; import { useRouterQuery } from "./useRouterQuery"; @@ -46,6 +48,17 @@ export function useTypedQuery<T extends z.AnyZodObject>(schema: T) { return {} as Output; }, []); + useEffect(() => { + if (parsedQuerySchema.success && parsedQuerySchema.data) { + Object.entries(parsedQuerySchema.data).forEach(([key, value]) => { + if (key in unparsedQuery || !value) return; + const search = new URLSearchParams(parsedQuery); + search.set(String(key), String(value)); + router.replace(`${pathname}?${search.toString()}`); + }); + } + }, [parsedQuerySchema, schema, router, pathname, unparsedQuery, parsedQuery]); + if (parsedQuerySchema.success) parsedQuery = parsedQuerySchema.data; else if (!parsedQuerySchema.success) console.error(parsedQuerySchema.error); diff --git a/packages/lib/package.json b/packages/lib/package.json index b2ada1df57..fae3a15b8f 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -14,6 +14,7 @@ "dependencies": { "@calcom/config": "*", "@calcom/dayjs": "*", + "@formbricks/api": "^1.1.0", "@sendgrid/client": "^7.7.0", "@vercel/og": "^0.5.0", "bcryptjs": "^2.4.3", diff --git a/packages/lib/payment/getBooking.ts b/packages/lib/payment/getBooking.ts index ec4e97c182..af0ea785ec 100644 --- a/packages/lib/payment/getBooking.ts +++ b/packages/lib/payment/getBooking.ts @@ -97,6 +97,7 @@ export async function getBooking(bookingId: number) { }), organizer: { email: user.email, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion name: user.name!, timeZone: user.timeZone, timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat), diff --git a/packages/lib/redactError.ts b/packages/lib/redactError.ts index dc4d890ac3..6df4355cb1 100644 --- a/packages/lib/redactError.ts +++ b/packages/lib/redactError.ts @@ -1,9 +1,10 @@ import { Prisma } from "@prisma/client"; import logger from "@calcom/lib/logger"; -import { safeStringify } from "@calcom/lib/safeStringify"; -const log = logger.getSubLogger({ prefix: [`[[redactError]`] }); +import { IS_PRODUCTION } from "./constants"; + +const log = logger.getSubLogger({ prefix: [`[redactError]`] }); function shouldRedact<T extends Error>(error: T) { return ( @@ -19,8 +20,8 @@ export const redactError = <T extends Error | unknown>(error: T) => { return error; } log.debug("Type of Error: ", error.constructor); - if (shouldRedact(error)) { - log.error("Error: ", safeStringify(error)); + if (shouldRedact(error) && IS_PRODUCTION) { + log.error("Error: ", JSON.stringify(error)); return new Error("An error occured while querying the database."); } return error; diff --git a/packages/lib/server/defaultResponder.test.ts b/packages/lib/server/defaultResponder.test.ts new file mode 100644 index 0000000000..25276230be --- /dev/null +++ b/packages/lib/server/defaultResponder.test.ts @@ -0,0 +1,22 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { describe, it, expect, vi } from "vitest"; + +import { defaultResponder } from "./defaultResponder"; + +describe("defaultResponder", () => { + it("should call res.json when response is still writable and result is not null", async () => { + const f = vi.fn().mockResolvedValue({}); + const req = {} as NextApiRequest; + const res = { json: vi.fn(), writableEnded: false } as unknown as NextApiResponse; + await defaultResponder(f)(req, res); + expect(res.json).toHaveBeenCalled(); + }); + + it("should not call res.json when response is not writable", async () => { + const f = vi.fn().mockResolvedValue({}); + const req = {} as NextApiRequest; + const res = { json: vi.fn(), writableEnded: true } as unknown as NextApiResponse; + await defaultResponder(f)(req, res); + expect(res.json).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/lib/server/getBookerUrl.ts b/packages/lib/server/getBookerUrl.ts deleted file mode 100644 index 9b42c92a30..0000000000 --- a/packages/lib/server/getBookerUrl.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { WEBAPP_URL } from "../constants"; -import { getBrand } from "./getBrand"; - -export const getBookerUrl = async (user: { organizationId: number | null }) => { - const orgBrand = await getBrand(user.organizationId); - return orgBrand?.fullDomain ?? WEBAPP_URL; -}; diff --git a/packages/lib/server/queries/teams/index.ts b/packages/lib/server/queries/teams/index.ts index a5d8a1b124..c910c8cea1 100644 --- a/packages/lib/server/queries/teams/index.ts +++ b/packages/lib/server/queries/teams/index.ts @@ -1,12 +1,12 @@ import { Prisma } from "@prisma/client"; import { getAppFromSlug } from "@calcom/app-store/utils"; -import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains"; import prisma, { baseEventTypeSelect } from "@calcom/prisma"; import { SchedulingType } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { WEBAPP_URL } from "../../../constants"; +import { getBookerBaseUrlSync } from "../../../getBookerUrl/client"; import { getTeam, getOrg } from "../../repository/team"; export type TeamWithMembers = Awaited<ReturnType<typeof getTeamWithMembers>>; @@ -158,7 +158,7 @@ export async function getTeamWithMembers(args: { .map((membership) => membership.team.slug) : null, avatar: `${WEBAPP_URL}/${m.user.username}/avatar.png`, - orgOrigin: getOrgFullOrigin(m.user.organization?.slug || ""), + bookerUrl: getBookerBaseUrlSync(m.user.organization?.slug || ""), connectedApps: !isTeamView ? credentials?.map((cred) => { const appSlug = cred.app?.slug; diff --git a/packages/lib/serverConfig.ts b/packages/lib/serverConfig.ts index 09feb75661..8a13b31ff2 100644 --- a/packages/lib/serverConfig.ts +++ b/packages/lib/serverConfig.ts @@ -6,6 +6,20 @@ import { isENVDev } from "@calcom/lib/env"; import { getAdditionalEmailHeaders } from "./getAdditionalEmailHeaders"; function detectTransport(): SendmailTransport.Options | SMTPConnection.Options | string { + if (process.env.RESEND_API_KEY) { + const transport = { + host: "smtp.resend.com", + secure: true, + port: 465, + auth: { + user: "resend", + pass: process.env.RESEND_API_KEY, + }, + }; + + return transport; + } + if (process.env.EMAIL_SERVER) { return process.env.EMAIL_SERVER; } diff --git a/packages/lib/test/CalEventParser.test.ts b/packages/lib/test/CalEventParser.test.ts index c90a8daa15..064efaf47e 100644 --- a/packages/lib/test/CalEventParser.test.ts +++ b/packages/lib/test/CalEventParser.test.ts @@ -11,6 +11,7 @@ import { buildCalendarEvent, buildVideoCallData } from "./builder"; vi.mock("@calcom/lib/constants", () => ({ WEBAPP_URL: "http://localhost:3000", + APP_NAME: "Cal.com", })); vi.mock("short-uuid", () => ({ diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index 4a2f79c891..b6238d9349 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -2,6 +2,7 @@ import { faker } from "@faker-js/faker"; import type { Booking, EventType, Prisma, Webhook } from "@prisma/client"; import type { TFunction } from "next-i18next"; +import getICalUID from "@calcom/emails/lib/getICalUID"; import { BookingStatus } from "@calcom/prisma/enums"; import type { CalendarEvent, Person, VideoCallData } from "@calcom/types/Calendar"; @@ -31,9 +32,10 @@ export const buildPerson = (person?: Partial<Person>): Person => { }; export const buildBooking = (booking?: Partial<Booking>): Booking => { + const uid = faker.datatype.uuid(); return { id: faker.datatype.number(), - uid: faker.datatype.uuid(), + uid, userId: null, eventTypeId: null, title: faker.lorem.sentence(), @@ -59,6 +61,8 @@ export const buildBooking = (booking?: Partial<Booking>): Booking => { metadata: null, responses: null, isRecorded: false, + iCalUID: getICalUID({ uid }), + iCalSequence: 0, ...booking, }; }; @@ -70,6 +74,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => { slug: faker.lorem.slug(), description: faker.lorem.paragraph(), position: 1, + isInstantEvent: false, locations: null, length: 15, offsetStart: 0, @@ -92,6 +97,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => { minimumBookingNotice: 120, beforeEventBuffer: 0, afterEventBuffer: 0, + onlyShowFirstAvailableSlot: false, seatsPerTimeSlot: null, seatsShowAttendees: null, seatsShowAvailabilityCount: null, @@ -154,8 +160,10 @@ export const buildSubscriberEvent = (booking?: Partial<Booking>) => { }; export const buildCalendarEvent = (event?: Partial<CalendarEvent>): CalendarEvent => { + const uid = faker.datatype.uuid(); return { - uid: faker.datatype.uuid(), + uid, + iCalUID: getICalUID({ uid }), type: faker.helpers.arrayElement(["event", "meeting"]), title: faker.lorem.sentence(), startTime: faker.date.future().toISOString(), diff --git a/packages/lib/testEmails.ts b/packages/lib/testEmails.ts index f660ec1ca6..a0d6787177 100644 --- a/packages/lib/testEmails.ts +++ b/packages/lib/testEmails.ts @@ -6,7 +6,7 @@ declare global { content: string; }; to: string; - from: string; + from: string | { email: string; name: string }; subject: string; html: string; }[]; diff --git a/packages/lib/validateUsername.ts b/packages/lib/validateUsername.ts index 74687725d6..56d0a10de9 100644 --- a/packages/lib/validateUsername.ts +++ b/packages/lib/validateUsername.ts @@ -1,7 +1,45 @@ +import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail"; import prisma from "@calcom/prisma"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; -export const validateUsername = async (username: string, email: string, organizationId?: number) => { +export const getUsernameForOrgMember = async ({ + email, + orgAutoAcceptEmail, + isSignup, + username, +}: { + username?: string; + email: string; + orgAutoAcceptEmail?: string; + isSignup: boolean; +}) => { + if (isSignup) { + // We ensure that the username is always derived from the email during signup. + return getOrgUsernameFromEmail(email, orgAutoAcceptEmail || ""); + } + if (!username) { + throw new Error("Username is required"); + } + // Right now it's not possible to change username in an org by the user but when that's allowed we would simply accept the provided username + return username; +}; + +export const validateAndGetCorrectedUsernameAndEmail = async ({ + username, + email, + organizationId, + orgAutoAcceptEmail, + isSignup, +}: { + username: string; + email: string; + organizationId?: number; + orgAutoAcceptEmail?: string; + isSignup: boolean; +}) => { + if (username.includes("+")) { + return { isValid: false, username: undefined, email }; + } // There is an existingUser if, within an org context or not, the username matches // OR if the email matches AND either the email is verified // or both username and password are set @@ -31,10 +69,24 @@ export const validateUsername = async (username: string, email: string, organiza email: true, }, }); - return { isValid: !existingUser, email: existingUser?.email }; + let validatedUsername = username; + if (organizationId) { + validatedUsername = await getUsernameForOrgMember({ + email, + orgAutoAcceptEmail, + isSignup, + }); + } + + return { isValid: !existingUser, username: validatedUsername, email: existingUser?.email }; }; -export const validateUsernameInTeam = async (username: string, email: string, teamId: number) => { +export const validateAndGetCorrectedUsernameInTeam = async ( + username: string, + email: string, + teamId: number, + isSignup: boolean +) => { try { const team = await prisma.team.findFirst({ where: { @@ -43,21 +95,37 @@ export const validateUsernameInTeam = async (username: string, email: string, te select: { metadata: true, parentId: true, + parent: { + select: { + metadata: true, + }, + }, }, }); + console.log("validateAndGetCorrectedUsernameInTeam", { + teamId, + team, + }); const teamData = { ...team, metadata: teamMetadataSchema.parse(team?.metadata) }; - - if (teamData.metadata?.isOrganization || teamData.parentId) { + const organization = teamData.metadata?.isOrganization ? teamData : teamData.parent; + if (organization) { + const orgMetadata = teamMetadataSchema.parse(organization.metadata); // Organization context -> org-context username check const orgId = teamData.parentId || teamId; - return validateUsername(username, email, orgId); + return validateAndGetCorrectedUsernameAndEmail({ + username, + email, + organizationId: orgId, + orgAutoAcceptEmail: orgMetadata?.orgAutoAcceptEmail || "", + isSignup, + }); } else { // Regular team context -> regular username check - return validateUsername(username, email); + return validateAndGetCorrectedUsernameAndEmail({ username, email, isSignup }); } } catch (error) { console.error(error); - return { isValid: false, email: undefined }; + return { isValid: false, username: undefined, email: undefined }; } }; diff --git a/packages/prisma/migrations/20231113202947_add_ical_columns_to_booking/migration.sql b/packages/prisma/migrations/20231113202947_add_ical_columns_to_booking/migration.sql new file mode 100644 index 0000000000..9095baefe5 --- /dev/null +++ b/packages/prisma/migrations/20231113202947_add_ical_columns_to_booking/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Booking" ADD COLUMN "iCalSequence" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "iCalUID" TEXT DEFAULT ''; diff --git a/packages/prisma/migrations/20231202181233_adding_show_first_available_timeslot/migration.sql b/packages/prisma/migrations/20231202181233_adding_show_first_available_timeslot/migration.sql new file mode 100644 index 0000000000..9cb3a0d629 --- /dev/null +++ b/packages/prisma/migrations/20231202181233_adding_show_first_available_timeslot/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "onlyShowFirstAvailableSlot" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/migrations/20231203154633_adding_is_mandatory_reminder_field_for_mandatory_reminders/migration.sql b/packages/prisma/migrations/20231203154633_adding_is_mandatory_reminder_field_for_mandatory_reminders/migration.sql new file mode 100644 index 0000000000..c9da8290d3 --- /dev/null +++ b/packages/prisma/migrations/20231203154633_adding_is_mandatory_reminder_field_for_mandatory_reminders/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "WorkflowReminder" ADD COLUMN "isMandatoryReminder" BOOLEAN DEFAULT false; diff --git a/packages/prisma/migrations/20231206191034_add_cal_video_logo/migration.sql b/packages/prisma/migrations/20231206191034_add_cal_video_logo/migration.sql new file mode 100644 index 0000000000..74cab0a73c --- /dev/null +++ b/packages/prisma/migrations/20231206191034_add_cal_video_logo/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Team" ADD COLUMN "calVideoLogo" TEXT; diff --git a/packages/prisma/migrations/20231213083720_add_meeting_started_webhook/migration.sql b/packages/prisma/migrations/20231213083720_add_meeting_started_webhook/migration.sql new file mode 100644 index 0000000000..06a8681821 --- /dev/null +++ b/packages/prisma/migrations/20231213083720_add_meeting_started_webhook/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'MEETING_STARTED'; diff --git a/packages/prisma/migrations/20231213153230_add_instant_meeting/migration.sql b/packages/prisma/migrations/20231213153230_add_instant_meeting/migration.sql new file mode 100644 index 0000000000..7be3c8fa79 --- /dev/null +++ b/packages/prisma/migrations/20231213153230_add_instant_meeting/migration.sql @@ -0,0 +1,36 @@ +-- AlterEnum +ALTER TYPE "BookingStatus" ADD VALUE 'awaiting_host'; + +-- AlterEnum +ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'INSTANT_MEETING'; + +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "isInstantEvent" BOOLEAN NOT NULL DEFAULT false; + +-- CreateTable +CREATE TABLE "InstantMeetingToken" ( + "id" SERIAL NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "teamId" INTEGER NOT NULL, + "bookingId" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "InstantMeetingToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "InstantMeetingToken_token_key" ON "InstantMeetingToken"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "InstantMeetingToken_bookingId_key" ON "InstantMeetingToken"("bookingId"); + +-- CreateIndex +CREATE INDEX "InstantMeetingToken_token_idx" ON "InstantMeetingToken"("token"); + +-- AddForeignKey +ALTER TABLE "InstantMeetingToken" ADD CONSTRAINT "InstantMeetingToken_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InstantMeetingToken" ADD CONSTRAINT "InstantMeetingToken_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/migrations/20240105110500_removed_newbooker_feature_flag/migration.sql b/packages/prisma/migrations/20240105110500_removed_newbooker_feature_flag/migration.sql new file mode 100644 index 0000000000..bdf05ec0ab --- /dev/null +++ b/packages/prisma/migrations/20240105110500_removed_newbooker_feature_flag/migration.sql @@ -0,0 +1,4 @@ +-- Removes the feature flag for the new booker layouts which is no longer needed +DELETE FROM "Feature" +WHERE + slug = 'booker-layouts'; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 8cc61111fa..abb778499e 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -98,6 +98,7 @@ model EventType { beforeEventBuffer Int @default(0) afterEventBuffer Int @default(0) seatsPerTimeSlot Int? + onlyShowFirstAvailableSlot Boolean @default(false) seatsShowAttendees Boolean? @default(false) seatsShowAvailabilityCount Boolean? @default(true) schedulingType SchedulingType? @@ -117,6 +118,7 @@ model EventType { bookingLimits Json? /// @zod.custom(imports.intervalLimitsType) durationLimits Json? + isInstantEvent Boolean @default(false) @@unique([userId, slug]) @@unique([teamId, slug]) @@ -274,42 +276,44 @@ model User { } model Team { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) /// @zod.min(1) - name String + name String /// @zod.min(1) - slug String? - logo String? - logoUrl String? - appLogo String? - appIconLogo String? - bio String? - hideBranding Boolean @default(false) - isPrivate Boolean @default(false) - hideBookATeamMember Boolean @default(false) - members Membership[] - eventTypes EventType[] - workflows Workflow[] - createdAt DateTime @default(now()) + slug String? + logo String? + logoUrl String? + calVideoLogo String? + appLogo String? + appIconLogo String? + bio String? + hideBranding Boolean @default(false) + isPrivate Boolean @default(false) + hideBookATeamMember Boolean @default(false) + members Membership[] + eventTypes EventType[] + workflows Workflow[] + createdAt DateTime @default(now()) /// @zod.custom(imports.teamMetadataSchema) - metadata Json? - theme String? - brandColor String @default("#292929") - darkBrandColor String @default("#fafafa") - verifiedNumbers VerifiedNumber[] - parentId Int? - parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade) - children Team[] @relation("organization") - orgUsers User[] @relation("scope") - inviteTokens VerificationToken[] - webhooks Webhook[] - timeFormat Int? - timeZone String @default("Europe/London") - weekStart String @default("Sunday") - routingForms App_RoutingForms_Form[] - apiKeys ApiKey[] - credentials Credential[] - accessCodes AccessCode[] + metadata Json? + theme String? + brandColor String @default("#292929") + darkBrandColor String @default("#fafafa") + verifiedNumbers VerifiedNumber[] + parentId Int? + parent Team? @relation("organization", fields: [parentId], references: [id], onDelete: Cascade) + children Team[] @relation("organization") + orgUsers User[] @relation("scope") + inviteTokens VerificationToken[] + webhooks Webhook[] + timeFormat Int? + timeZone String @default("Europe/London") + weekStart String @default("Sunday") + routingForms App_RoutingForms_Form[] + apiKeys ApiKey[] + credentials Credential[] + accessCodes AccessCode[] + instantMeetingTokens InstantMeetingToken[] @@unique([slug, parentId]) } @@ -351,6 +355,21 @@ model VerificationToken { @@index([teamId]) } +model InstantMeetingToken { + id Int @id @default(autoincrement()) + token String @unique + expires DateTime + teamId Int + team Team @relation(fields: [teamId], references: [id]) + bookingId Int? @unique + booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([token]) +} + model BookingReference { id Int @id @default(autoincrement()) /// @zod.min(1) @@ -387,10 +406,11 @@ model Attendee { } enum BookingStatus { - CANCELLED @map("cancelled") - ACCEPTED @map("accepted") - REJECTED @map("rejected") - PENDING @map("pending") + CANCELLED @map("cancelled") + ACCEPTED @map("accepted") + REJECTED @map("rejected") + PENDING @map("pending") + AWAITING_HOST @map("awaiting_host") } model Booking { @@ -431,6 +451,9 @@ model Booking { /// @zod.custom(imports.bookingMetadataSchema) metadata Json? isRecorded Boolean @default(false) + iCalUID String? @default("") + iCalSequence Int @default(0) + instantMeetingToken InstantMeetingToken? @@index([eventTypeId]) @@index([userId]) @@ -566,7 +589,9 @@ enum WebhookTriggerEvents { BOOKING_REJECTED FORM_SUBMITTED MEETING_ENDED + MEETING_STARTED RECORDING_READY + INSTANT_MEETING } model Webhook { @@ -821,17 +846,18 @@ enum TimeUnit { } model WorkflowReminder { - id Int @id @default(autoincrement()) - bookingUid String? - booking Booking? @relation(fields: [bookingUid], references: [uid]) - method WorkflowMethods - scheduledDate DateTime - referenceId String? @unique - scheduled Boolean - workflowStepId Int? - workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id]) - cancelled Boolean? - seatReferenceId String? + id Int @id @default(autoincrement()) + bookingUid String? + booking Booking? @relation(fields: [bookingUid], references: [uid]) + method WorkflowMethods + scheduledDate DateTime + referenceId String? @unique + scheduled Boolean + workflowStepId Int? + workflowStep WorkflowStep? @relation(fields: [workflowStepId], references: [id]) + cancelled Boolean? + seatReferenceId String? + isMandatoryReminder Boolean? @default(false) @@index([bookingUid]) @@index([workflowStepId]) diff --git a/packages/prisma/seed-utils.ts b/packages/prisma/seed-utils.ts index 31f5768429..7a69530ac1 100644 --- a/packages/prisma/seed-utils.ts +++ b/packages/prisma/seed-utils.ts @@ -10,6 +10,7 @@ import prisma from "."; export async function createUserAndEventType({ user, eventTypes = [], + credentials, }: { user: { email: string; @@ -27,6 +28,11 @@ export async function createUserAndEventType({ _numBookings?: number; } >; + credentials?: ({ + type: string; + key: Prisma.JsonObject; + appId: string; + } | null)[]; }) { const userData = { ...user, @@ -133,6 +139,7 @@ export async function createUserAndEventType({ }, }, status: bookingInput.status, + iCalUID: "", }, }); console.log( @@ -143,5 +150,20 @@ export async function createUserAndEventType({ } } console.log("👤 User with it's event-types and bookings created", theUser.email); + + if (credentials) { + for (const credential of credentials) { + if (credential) { + await prisma.credential.create({ + data: { + ...credential, + userId: theUser.id, + }, + }); + + console.log(`🔑 ${credential.type} credentials created for ${theUser.email}`); + } + } + } return theUser; } diff --git a/packages/prisma/seed.ts b/packages/prisma/seed.ts index 78c6861372..e311117ed8 100644 --- a/packages/prisma/seed.ts +++ b/packages/prisma/seed.ts @@ -455,21 +455,32 @@ async function main() { }, }); - await createUserAndEventType({ - user: { - email: process.env.E2E_TEST_CALCOM_QA_EMAIL || "qa@example.com", - password: process.env.E2E_TEST_CALCOM_QA_PASSWORD || "qa", - username: "qa", - name: "QA Example", - }, - eventTypes: [ - { - title: "15min", - slug: "15min", - length: 15, + if (!!(process.env.E2E_TEST_CALCOM_QA_EMAIL && process.env.E2E_TEST_CALCOM_QA_PASSWORD)) { + await createUserAndEventType({ + user: { + email: process.env.E2E_TEST_CALCOM_QA_EMAIL || "qa@example.com", + password: process.env.E2E_TEST_CALCOM_QA_PASSWORD || "qa", + username: "qa", + name: "QA Example", }, - ], - }); + eventTypes: [ + { + title: "15min", + slug: "15min", + length: 15, + }, + ], + credentials: [ + !!process.env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS + ? { + type: "google_calendar", + key: JSON.parse(process.env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS) as Prisma.JsonObject, + appId: "google-calendar", + } + : null, + ], + }); + } await createTeamAndAddUsers( { diff --git a/packages/prisma/selects/booking.ts b/packages/prisma/selects/booking.ts index d71d9562d8..7054f7dbf0 100644 --- a/packages/prisma/selects/booking.ts +++ b/packages/prisma/selects/booking.ts @@ -8,4 +8,5 @@ export const bookingMinimalSelect = Prisma.validator<Prisma.BookingSelect>()({ startTime: true, endTime: true, attendees: true, + metadata: true, }); diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index dba23604b7..48647c8bbf 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -1,5 +1,6 @@ import type { Prisma } from "@prisma/client"; import type { UnitTypeLongPlural } from "dayjs"; +import type { TFunction } from "next-i18next"; import z, { ZodNullable, ZodObject, ZodOptional } from "zod"; /* eslint-disable no-underscore-dangle */ @@ -333,6 +334,14 @@ export const teamMetadataSchema = z isOrganizationVerified: z.boolean().nullable(), isOrganizationConfigured: z.boolean().nullable(), orgAutoAcceptEmail: z.string().nullable(), + migratedToOrgFrom: z + .object({ + teamSlug: z.string().or(z.null()).optional(), + lastMigrationTime: z.string().optional(), + reverted: z.boolean().optional(), + lastRevertTime: z.string().optional(), + }) + .optional(), }) .partial() .nullable(); @@ -552,6 +561,7 @@ export const downloadLinkSchema = z.object({ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect, "id">]: true } = { title: true, description: true, + isInstantEvent: true, currency: true, periodDays: true, position: true, @@ -585,6 +595,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect destinationCalendar: true, periodCountCalendarDays: true, bookingLimits: true, + onlyShowFirstAvailableSlot: true, slotInterval: true, scheduleId: true, workflows: true, @@ -609,9 +620,9 @@ export const emailSchemaRefinement = (value: string) => { }; export const signupSchema = z.object({ - username: z.string().refine((value) => !value.includes("+"), { - message: "String should not contain a plus symbol (+).", - }), + // Username is marked optional here because it's requirement depends on if it's the Organization invite or a team invite which isn't easily done in zod + // It's better handled beyond zod in `validateAndGetCorrectedUsernameAndEmail` + username: z.string().optional(), email: z.string().email(), password: z.string().superRefine((data, ctx) => { const isStrict = false; @@ -638,3 +649,5 @@ export const ZVerifyCodeInputSchema = z.object({ export type ZVerifyCodeInputSchema = z.infer<typeof ZVerifyCodeInputSchema>; export const coerceToDate = z.coerce.date(); +export const getStringAsNumberRequiredSchema = (t: TFunction) => + z.string().min(1, t("error_required_field")).pipe(z.coerce.number()); diff --git a/packages/trpc/react/shared.ts b/packages/trpc/react/shared.ts index 0cc50c7959..14598e7011 100644 --- a/packages/trpc/react/shared.ts +++ b/packages/trpc/react/shared.ts @@ -1 +1,29 @@ export * from "@trpc/react-query/shared"; + +export const ENDPOINTS = [ + "admin", + "apiKeys", + "appRoutingForms", + "apps", + "auth", + "availability", + "appBasecamp3", + "bookings", + "deploymentSetup", + "eventTypes", + "features", + "insights", + "payments", + "public", + "saml", + "slots", + "teams", + "organizations", + "users", + "viewer", + "webhook", + "workflows", + "appsRouter", + "googleWorkspace", + "oAuth", +] as const; diff --git a/packages/trpc/react/trpc.ts b/packages/trpc/react/trpc.ts index 6b34bfb2b3..3d1167b775 100644 --- a/packages/trpc/react/trpc.ts +++ b/packages/trpc/react/trpc.ts @@ -11,38 +11,13 @@ import { createTRPCNext } from "../next"; import type { TRPCClientErrorLike } from "../react"; import type { inferRouterInputs, inferRouterOutputs, Maybe } from "../server"; import type { AppRouter } from "../server/routers/_app"; +import { ENDPOINTS } from "./shared"; /** * We deploy our tRPC router on multiple lambdas to keep number of imports as small as possible * TODO: Make this dynamic based on folders in trpc server? */ -const ENDPOINTS = [ - "admin", - "apiKeys", - "appRoutingForms", - "apps", - "auth", - "availability", - "appBasecamp3", - "bookings", - "deploymentSetup", - "eventTypes", - "features", - "insights", - "payments", - "public", - "saml", - "slots", - "teams", - "organizations", - "users", - "viewer", - "webhook", - "workflows", - "appsRouter", - "googleWorkspace", - "oAuth", -] as const; + export type Endpoint = (typeof ENDPOINTS)[number]; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/trpc/server/createContext.ts b/packages/trpc/server/createContext.ts index 042fc4c738..b1f7a68b21 100644 --- a/packages/trpc/server/createContext.ts +++ b/packages/trpc/server/createContext.ts @@ -4,6 +4,7 @@ import type { Session } from "next-auth"; import type { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import getIP from "@calcom/lib/getIP"; import prisma, { readonlyPrisma } from "@calcom/prisma"; import type { SelectedCalendar, User as PrismaUser } from "@calcom/prisma/client"; @@ -12,6 +13,7 @@ import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; type CreateContextOptions = CreateNextContextOptions | GetServerSidePropsContext; export type CreateInnerContextOptions = { + sourceIp?: string; session?: Session | null; locale: string; user?: @@ -64,8 +66,12 @@ export async function createContextInner(opts: CreateInnerContextOptions) { */ export const createContext = async ({ req, res }: CreateContextOptions, sessionGetter?: GetSessionFn) => { const locale = await getLocale(req); + + // This type may not be accurate if this request is coming from SSG init but they both should satisfy the requirements of getIP. + // TODO: @sean - figure out a way to make getIP be happy with trpc req. params + const sourceIp = getIP(req as NextApiRequest); const session = !!sessionGetter ? await sessionGetter({ req, res }) : null; - const contextInner = await createContextInner({ locale, session }); + const contextInner = await createContextInner({ locale, session, sourceIp }); return { ...contextInner, req, diff --git a/packages/trpc/server/routers/loggedInViewer/_router.tsx b/packages/trpc/server/routers/loggedInViewer/_router.tsx index 1c96204997..1d112aaa42 100644 --- a/packages/trpc/server/routers/loggedInViewer/_router.tsx +++ b/packages/trpc/server/routers/loggedInViewer/_router.tsx @@ -3,6 +3,7 @@ import { router } from "../../trpc"; import { ZAppByIdInputSchema } from "./appById.schema"; import { ZAppCredentialsByTypeInputSchema } from "./appCredentialsByType.schema"; import { ZAwayInputSchema } from "./away.schema"; +import { ZConnectAndJoinInputSchema } from "./connectAndJoin.schema"; import { ZConnectedCalendarsInputSchema } from "./connectedCalendars.schema"; import { ZDeleteCredentialInputSchema } from "./deleteCredential.schema"; import { ZDeleteMeInputSchema } from "./deleteMe.schema"; @@ -44,6 +45,8 @@ type AppsRouterHandlerCache = { getUsersDefaultConferencingApp?: typeof import("./getUsersDefaultConferencingApp.handler").getUsersDefaultConferencingAppHandler; updateUserDefaultConferencingApp?: typeof import("./updateUserDefaultConferencingApp.handler").updateUserDefaultConferencingAppHandler; teamsAndUserProfilesQuery?: typeof import("./teamsAndUserProfilesQuery.handler").teamsAndUserProfilesQuery; + getUserTopBanners?: typeof import("./getUserTopBanners.handler").getUserTopBannersHandler; + connectAndJoin?: typeof import("./connectAndJoin.handler").Handler; }; const UNSTABLE_HANDLER_CACHE: AppsRouterHandlerCache = {}; @@ -340,6 +343,21 @@ export const loggedInViewerRouter = router({ return UNSTABLE_HANDLER_CACHE.getCalVideoRecordings({ ctx, input }); }), + getUserTopBanners: authedProcedure.query(async ({ ctx }) => { + if (!UNSTABLE_HANDLER_CACHE.getUserTopBanners) { + UNSTABLE_HANDLER_CACHE.getUserTopBanners = ( + await import("./getUserTopBanners.handler") + ).getUserTopBannersHandler; + } + + // Unreachable code but required for type safety + if (!UNSTABLE_HANDLER_CACHE.getUserTopBanners) { + throw new Error("Failed to load handler"); + } + + return UNSTABLE_HANDLER_CACHE.getUserTopBanners({ ctx }); + }), + getDownloadLinkOfCalVideoRecordings: authedProcedure .input(ZGetDownloadLinkOfCalVideoRecordingsInputSchema) .query(async ({ ctx, input }) => { @@ -416,4 +434,17 @@ export const loggedInViewerRouter = router({ return UNSTABLE_HANDLER_CACHE.teamsAndUserProfilesQuery({ ctx }); }), + + connectAndJoin: authedProcedure.input(ZConnectAndJoinInputSchema).mutation(async ({ ctx, input }) => { + if (!UNSTABLE_HANDLER_CACHE.connectAndJoin) { + UNSTABLE_HANDLER_CACHE.connectAndJoin = (await import("./connectAndJoin.handler")).Handler; + } + + // Unreachable code but required for type safety + if (!UNSTABLE_HANDLER_CACHE.connectAndJoin) { + throw new Error("Failed to load handler"); + } + + return UNSTABLE_HANDLER_CACHE.connectAndJoin({ ctx, input }); + }), }); diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts new file mode 100644 index 0000000000..32fc2c70f9 --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.handler.ts @@ -0,0 +1,218 @@ +import { sendScheduledEmails } from "@calcom/emails"; +import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; +import { isPrismaObjOrUndefined } from "@calcom/lib"; +import { getTranslation } from "@calcom/lib/server/i18n"; +import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; +import { prisma } from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/enums"; +import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; +import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; +import type { CalendarEvent } from "@calcom/types/Calendar"; + +import { TRPCError } from "@trpc/server"; + +import type { TConnectAndJoinInputSchema } from "./connectAndJoin.schema"; + +type Options = { + ctx: { + user: NonNullable<TrpcSessionUser>; + }; + input: TConnectAndJoinInputSchema; +}; + +export const Handler = async ({ ctx, input }: Options) => { + const { token } = input; + const { user } = ctx; + const isLoggedInUserPartOfOrg = !!user.organization.id; + + if (!isLoggedInUserPartOfOrg) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Logged in user is not member of Organization" }); + } + + const tOrganizer = await getTranslation(user?.locale ?? "en", "common"); + + const instantMeetingToken = await prisma.instantMeetingToken.findUnique({ + select: { + expires: true, + teamId: true, + booking: { + select: { + id: true, + status: true, + user: { + select: { + id: true, + }, + }, + }, + }, + }, + where: { + token, + team: { + members: { + some: { + userId: user.id, + accepted: true, + }, + }, + }, + }, + }); + + // Check if logged in user belong to current team + if (!instantMeetingToken) { + throw new TRPCError({ code: "BAD_REQUEST", message: "token_not_found" }); + } + + if (!instantMeetingToken.booking?.id) { + throw new TRPCError({ code: "FORBIDDEN", message: "token_invalid_expired" }); + } + + // Check if token has not expired + if (instantMeetingToken.expires < new Date()) { + throw new TRPCError({ code: "BAD_REQUEST", message: "token_invalid_expired" }); + } + + // Check if Booking is already accepted by any other user + let isBookingAlreadyAcceptedBySomeoneElse = false; + if ( + instantMeetingToken.booking.status === BookingStatus.ACCEPTED && + instantMeetingToken.booking?.user?.id !== user.id + ) { + isBookingAlreadyAcceptedBySomeoneElse = true; + } + + // Update User in Booking + const updatedBooking = await prisma.booking.update({ + where: { + id: instantMeetingToken.booking.id, + }, + data: { + ...(isBookingAlreadyAcceptedBySomeoneElse + ? { status: BookingStatus.ACCEPTED } + : { + status: BookingStatus.ACCEPTED, + user: { + connect: { + id: user.id, + }, + }, + }), + }, + select: { + title: true, + description: true, + customInputs: true, + startTime: true, + references: true, + endTime: true, + attendees: true, + eventTypeId: true, + responses: true, + metadata: true, + eventType: { + select: { + id: true, + owner: true, + teamId: true, + title: true, + slug: true, + requiresConfirmation: true, + currency: true, + length: true, + description: true, + price: true, + bookingFields: true, + disableGuests: true, + metadata: true, + customInputs: true, + parentId: true, + }, + }, + location: true, + userId: true, + id: true, + uid: true, + status: true, + scheduledJobs: true, + }, + }); + + const locationVideoCallUrl = bookingMetadataSchema.parse(updatedBooking.metadata || {})?.videoCallUrl; + + if (!locationVideoCallUrl) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "meeting_url_not_found" }); + } + + const videoCallReference = updatedBooking.references.find((reference) => reference.type.includes("_video")); + const videoCallData = { + type: videoCallReference?.type, + id: videoCallReference?.meetingId, + password: videoCallReference?.meetingPassword, + url: videoCallReference?.meetingUrl, + }; + + const { eventType } = updatedBooking; + + // Send Scheduled Email to Organizer and Attendees + + const translations = new Map(); + const attendeesListPromises = updatedBooking.attendees.map(async (attendee) => { + const locale = attendee.locale ?? "en"; + let translate = translations.get(locale); + if (!translate) { + translate = await getTranslation(locale, "common"); + translations.set(locale, translate); + } + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { + translate, + locale, + }, + }; + }); + + const attendeesList = await Promise.all(attendeesListPromises); + + const evt: CalendarEvent = { + type: updatedBooking?.eventType?.slug as string, + title: updatedBooking.title, + description: updatedBooking.description, + ...getCalEventResponses({ + bookingFields: eventType?.bookingFields ?? null, + booking: updatedBooking, + }), + customInputs: isPrismaObjOrUndefined(updatedBooking.customInputs), + startTime: updatedBooking.startTime.toISOString(), + endTime: updatedBooking.endTime.toISOString(), + organizer: { + email: user.email, + name: user.name || "Unnamed", + username: user.username || undefined, + timeZone: user.timeZone, + timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat), + language: { translate: tOrganizer, locale: user.locale ?? "en" }, + }, + attendees: attendeesList, + location: updatedBooking.location ?? "", + uid: updatedBooking.uid, + requiresConfirmation: false, + eventTypeId: eventType?.id, + videoCallData, + }; + + await sendScheduledEmails( + { + ...evt, + }, + undefined, + false, + false + ); + + return { isBookingAlreadyAcceptedBySomeoneElse, meetingUrl: locationVideoCallUrl }; +}; diff --git a/packages/trpc/server/routers/loggedInViewer/connectAndJoin.schema.ts b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.schema.ts new file mode 100644 index 0000000000..43ae27a2f5 --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/connectAndJoin.schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const ZConnectAndJoinInputSchema = z.object({ + token: z.string(), +}); + +export type TConnectAndJoinInputSchema = z.infer<typeof ZConnectAndJoinInputSchema>; diff --git a/packages/trpc/server/routers/loggedInViewer/getUserTopBanners.handler.ts b/packages/trpc/server/routers/loggedInViewer/getUserTopBanners.handler.ts new file mode 100644 index 0000000000..c81a00452f --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/getUserTopBanners.handler.ts @@ -0,0 +1,57 @@ +import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; +import { prisma } from "@calcom/prisma"; +import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; +import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; + +import { checkIfOrgNeedsUpgradeHandler } from "../viewer/organizations/checkIfOrgNeedsUpgrade.handler"; +import { getUpgradeableHandler } from "../viewer/teams/getUpgradeable.handler"; +import { shouldVerifyEmailHandler } from "./shouldVerifyEmail.handler"; + +type Props = { + ctx: { + user: NonNullable<TrpcSessionUser>; + }; +}; + +const checkInvalidGoogleCalendarCredentials = async ({ ctx }: Props) => { + const userCredentials = await prisma.credential.findMany({ + where: { + userId: ctx.user.id, + type: "google_calendar", + }, + select: credentialForCalendarServiceSelect, + }); + + const calendarCredentials = getCalendarCredentials(userCredentials); + + const { connectedCalendars } = await getConnectedCalendars( + calendarCredentials, + ctx.user.selectedCalendars, + ctx.user.destinationCalendar?.externalId + ); + + return connectedCalendars.some((calendar) => !!calendar.error); +}; + +export const getUserTopBannersHandler = async ({ ctx }: Props) => { + const upgradeableTeamMememberships = getUpgradeableHandler({ ctx }); + const upgradeableOrgMememberships = checkIfOrgNeedsUpgradeHandler({ ctx }); + const shouldEmailVerify = shouldVerifyEmailHandler({ ctx }); + const isInvalidCalendarCredential = checkInvalidGoogleCalendarCredentials({ ctx }); + + const [teamUpgradeBanner, orgUpgradeBanner, verifyEmailBanner, calendarCredentialBanner] = + await Promise.allSettled([ + upgradeableTeamMememberships, + upgradeableOrgMememberships, + shouldEmailVerify, + isInvalidCalendarCredential, + ]); + + return { + teamUpgradeBanner: teamUpgradeBanner.status === "fulfilled" ? teamUpgradeBanner.value : [], + orgUpgradeBanner: orgUpgradeBanner.status === "fulfilled" ? orgUpgradeBanner.value : [], + verifyEmailBanner: verifyEmailBanner.status === "fulfilled" ? !verifyEmailBanner.value.isVerified : false, + calendarCredentialBanner: + calendarCredentialBanner.status === "fulfilled" ? calendarCredentialBanner.value : false, + }; +}; diff --git a/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts b/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts index 70ea87aed5..7976abee83 100644 --- a/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts @@ -51,7 +51,6 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) = extendsFeature, teamId, sortByMostPopular, - categories, appId, } = input; let credentials = await getUsersCredentials(user.id); diff --git a/packages/trpc/server/routers/loggedInViewer/submitFeedback.handler.ts b/packages/trpc/server/routers/loggedInViewer/submitFeedback.handler.ts index 27db135abb..e72932778b 100644 --- a/packages/trpc/server/routers/loggedInViewer/submitFeedback.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/submitFeedback.handler.ts @@ -1,5 +1,6 @@ import dayjs from "@calcom/dayjs"; import { sendFeedbackEmail } from "@calcom/emails"; +import { sendFeedbackFormbricks } from "@calcom/lib/formbricks"; import { prisma } from "@calcom/prisma"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -30,6 +31,8 @@ export const submitFeedbackHandler = async ({ ctx, input }: SubmitFeedbackOption comment: comment, }, }); + if (process.env.FORMBRICKS_HOST_URL && process.env.FORMBRICKS_ENVIRONMENT_ID) + sendFeedbackFormbricks(ctx.user.id, feedback); if (process.env.SEND_FEEDBACK_EMAIL && comment) sendFeedbackEmail(feedback); }; diff --git a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts index 41f4b33072..4dd6a0ef2f 100644 --- a/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/teamsAndUserProfilesQuery.handler.ts @@ -1,6 +1,7 @@ import { isOrganization, withRoleCanCreateEntity } from "@calcom/lib/entityPermissionUtils"; -import { getBookerUrl } from "@calcom/lib/server/getBookerUrl"; +import { getTeamAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import type { PrismaClient } from "@calcom/prisma"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; import { TRPCError } from "@trpc/server"; @@ -36,6 +37,7 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti name: true, slug: true, metadata: true, + parentId: true, members: { select: { userId: true, @@ -51,24 +53,34 @@ export const teamsAndUserProfilesQuery = async ({ ctx }: TeamsAndUserProfileOpti if (!user) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); } - const bookerUrl = await getBookerUrl(user); - const image = user?.username ? `${bookerUrl}/${user.username}/avatar.png` : undefined; - const nonOrgTeams = user.teams.filter((membership) => !isOrganization({ team: membership.team })); + const nonOrgTeams = user.teams + .filter((membership) => !isOrganization({ team: membership.team })) + .map((membership) => ({ + ...membership, + team: { + ...membership.team, + metadata: teamMetadataSchema.parse(membership.team.metadata), + }, + })); return [ { teamId: null, name: user.name, slug: user.username, - image, + image: getUserAvatarUrl(user), readOnly: false, }, ...nonOrgTeams.map((membership) => ({ teamId: membership.team.id, name: membership.team.name, slug: membership.team.slug ? `team/${membership.team.slug}` : null, - image: `${bookerUrl}${membership.team.slug ? "/team" : ""}/${membership.team.slug}/avatar.png`, + image: getTeamAvatarUrl({ + slug: membership.team.slug, + requestedSlug: membership.team.metadata?.requestedSlug ?? null, + organizationId: membership.team.parentId, + }), role: membership.role, readOnly: !withRoleCanCreateEntity(membership.role), })), diff --git a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts index c4f39f54de..8158dd0c69 100644 --- a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts @@ -1,4 +1,4 @@ -import type { Prisma } from "@prisma/client"; +import { Prisma } from "@prisma/client"; import type { GetServerSidePropsContext, NextApiResponse } from "next"; import { v4 as uuidv4 } from "uuid"; @@ -6,6 +6,7 @@ import stripe from "@calcom/app-store/stripepayment/lib/server"; import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils"; import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetRequest"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; +import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { getTranslation } from "@calcom/lib/server"; import { checkUsername } from "@calcom/lib/server/checkUsername"; @@ -159,11 +160,7 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions) data.avatar = null; } - const updatedUser = await prisma.user.update({ - where: { - id: user.id, - }, - data, + const updatedUserSelect = Prisma.validator<Prisma.UserDefaultArgs>()({ select: { id: true, username: true, @@ -183,6 +180,27 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions) }, }); + let updatedUser: Prisma.UserGetPayload<typeof updatedUserSelect>; + + try { + updatedUser = await prisma.user.update({ + where: { + id: user.id, + }, + data, + ...updatedUserSelect, + }); + } catch (e) { + // Catch unique constraint failure on email field. + if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { + const meta = e.meta as { target: string[] }; + if (meta.target.indexOf("email") !== -1) { + throw new HttpError({ statusCode: 409, message: "email_already_used" }); + } + } + throw e; // make sure other errors are rethrown + } + if (user.timeZone !== data.timeZone && updatedUser.schedules.length > 0) { // on timezone change update timezone of default schedule const defaultScheduleId = await getDefaultScheduleId(user.id, prisma); diff --git a/packages/trpc/server/routers/publicViewer/countryCode.handler.ts b/packages/trpc/server/routers/publicViewer/countryCode.handler.ts index ed67a3d959..181a10a1cb 100644 --- a/packages/trpc/server/routers/publicViewer/countryCode.handler.ts +++ b/packages/trpc/server/routers/publicViewer/countryCode.handler.ts @@ -7,7 +7,9 @@ type CountryCodeOptions = { export const countryCodeHandler = async ({ ctx }: CountryCodeOptions) => { const { req } = ctx; - const countryCode: string | string[] = req?.headers?.["x-vercel-ip-country"] ?? ""; + // const countryCode: string | string[] = + // req?.headers?.["​cf-ipcountry"] ?? req?.headers?.["x-vercel-ip-country"] ?? ""; + const countryCode: string | string[] = req?.headers?.["​cf-ipcountry"] ?? ""; return { countryCode: Array.isArray(countryCode) ? countryCode[0] : countryCode }; }; diff --git a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts index 58c6132ba8..6d6d709ffe 100644 --- a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts @@ -187,7 +187,7 @@ export const listTeamAvailabilityHandler = async ({ ctx, input }: GetOptions) => let nextCursor: typeof cursor | undefined = undefined; if (teamMembers && teamMembers.length > limit) { const nextItem = teamMembers.pop(); - nextCursor = nextItem!.id; + nextCursor = nextItem?.id; } const dateFrom = dayjs(input.startDate).tz(input.loggedInUsersTz).subtract(1, "day"); diff --git a/packages/trpc/server/routers/viewer/bookings/_router.tsx b/packages/trpc/server/routers/viewer/bookings/_router.tsx index 294541b017..bece7c9c2b 100644 --- a/packages/trpc/server/routers/viewer/bookings/_router.tsx +++ b/packages/trpc/server/routers/viewer/bookings/_router.tsx @@ -6,6 +6,7 @@ import { ZEditLocationInputSchema } from "./editLocation.schema"; import { ZFindInputSchema } from "./find.schema"; import { ZGetInputSchema } from "./get.schema"; import { ZGetBookingAttendeesInputSchema } from "./getBookingAttendees.schema"; +import { ZInstantBookingInputSchema } from "./getInstantBookingLocation.schema"; import { ZRequestRescheduleInputSchema } from "./requestReschedule.schema"; import { bookingsProcedure } from "./util"; @@ -16,6 +17,7 @@ type BookingsRouterHandlerCache = { confirm?: typeof import("./confirm.handler").confirmHandler; getBookingAttendees?: typeof import("./getBookingAttendees.handler").getBookingAttendeesHandler; find?: typeof import("./find.handler").getHandler; + getInstantBookingLocation?: typeof import("./getInstantBookingLocation.handler").getHandler; }; const UNSTABLE_HANDLER_CACHE: BookingsRouterHandlerCache = {}; @@ -124,4 +126,24 @@ export const bookingsRouter = router({ input, }); }), + + getInstantBookingLocation: publicProcedure + .input(ZInstantBookingInputSchema) + .query(async ({ input, ctx }) => { + if (!UNSTABLE_HANDLER_CACHE.getInstantBookingLocation) { + UNSTABLE_HANDLER_CACHE.getInstantBookingLocation = await import( + "./getInstantBookingLocation.handler" + ).then((mod) => mod.getHandler); + } + + // Unreachable code but required for type safety + if (!UNSTABLE_HANDLER_CACHE.getInstantBookingLocation) { + throw new Error("Failed to load handler"); + } + + return UNSTABLE_HANDLER_CACHE.getInstantBookingLocation({ + ctx, + input, + }); + }), }); diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index 12259073aa..1a26e0e9d2 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -370,7 +370,7 @@ async function getBookings({ } return prev; }, - { ACCEPTED: [], CANCELLED: [], REJECTED: [], PENDING: [] } as { + { ACCEPTED: [], CANCELLED: [], REJECTED: [], PENDING: [], AWAITING_HOST: [] } as { [key in BookingStatus]: Date[]; } ); diff --git a/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.handler.ts new file mode 100644 index 0000000000..f1e808ef34 --- /dev/null +++ b/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.handler.ts @@ -0,0 +1,39 @@ +import type { PrismaClient } from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/enums"; + +import type { TInstantBookingInputSchema } from "./getInstantBookingLocation.schema"; + +type GetOptions = { + ctx: { + prisma: PrismaClient; + }; + input: TInstantBookingInputSchema; +}; + +export const getHandler = async ({ ctx, input }: GetOptions) => { + const { prisma } = ctx; + const { bookingId } = input; + + const booking = await prisma.booking.findUnique({ + where: { + id: bookingId, + status: BookingStatus.ACCEPTED, + }, + select: { + id: true, + uid: true, + location: true, + metadata: true, + startTime: true, + status: true, + endTime: true, + description: true, + eventTypeId: true, + }, + }); + + // Don't leak anything private from the booking + return { + booking, + }; +}; diff --git a/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.schema.ts b/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.schema.ts new file mode 100644 index 0000000000..fd338c7663 --- /dev/null +++ b/packages/trpc/server/routers/viewer/bookings/getInstantBookingLocation.schema.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +const ZInstantBookingInputSchema = z.object({ + bookingId: z.number(), +}); + +export type TInstantBookingInputSchema = z.infer<typeof ZInstantBookingInputSchema>; + +export { ZInstantBookingInputSchema }; diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index 16f9d3a316..eb1dbb1a13 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -15,11 +15,11 @@ import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import { cancelScheduledJobs } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined } from "@calcom/lib"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import { getTranslation } from "@calcom/lib/server"; -import { getBookerUrl } from "@calcom/lib/server/getBookerUrl"; import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials"; import { prisma } from "@calcom/prisma"; import type { WebhookTriggerEvents } from "@calcom/prisma/enums"; @@ -53,7 +53,15 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule startTime: true, endTime: true, eventTypeId: true, - eventType: true, + eventType: { + include: { + team: { + select: { + parentId: true, + }, + }, + }, + }, location: true, attendees: true, references: true, @@ -65,6 +73,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule scheduledJobs: true, workflowReminders: true, responses: true, + iCalUID: true, }, where: { uid: bookingId, @@ -80,7 +89,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule throw new TRPCError({ code: "FORBIDDEN", message: "Booking to reschedule doesn't have an owner" }); } - if (!bookingToReschedule.eventType) { + if (!bookingToReschedule.eventType && !bookingToReschedule.dynamicEventSlugRef) { throw new TRPCError({ code: "FORBIDDEN", message: "EventType not found for current booking." }); } @@ -174,9 +183,12 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule const [userAsPeopleType] = usersToPeopleType([user], userTranslation); const builder = new CalendarEventBuilder(); + const eventType = bookingToReschedule.eventType; builder.init({ title: bookingToReschedule.title, - bookerUrl: await getBookerUrl(user), + bookerUrl: eventType?.team + ? await getBookerBaseUrl({ organizationId: eventType.team.parentId }) + : await getBookerBaseUrl(user), type: event && event.slug ? event.slug : bookingToReschedule.title, startTime: bookingToReschedule.startTime.toISOString(), endTime: bookingToReschedule.endTime.toISOString(), @@ -186,13 +198,14 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule tAttendees ), organizer: userAsPeopleType, + iCalUID: bookingToReschedule.iCalUID, }); const director = new CalendarEventDirector(); director.setBuilder(builder); director.setExistingBooking(bookingToReschedule); cancellationReason && director.setCancellationReason(cancellationReason); - if (event) { + if (Object.keys(event).length) { await director.buildForRescheduleEmail(); } else { await director.buildWithoutEventTypeForRescheduleEmail(); @@ -257,6 +270,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule ? [bookingToReschedule?.destinationCalendar] : [], cancellationReason: `Please reschedule. ${cancellationReason}`, // TODO::Add i18-next for this + iCalUID: bookingToReschedule?.iCalUID, }; // Send webhook diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts index 83f8089c88..9ce7b0ae0f 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts @@ -4,9 +4,10 @@ import { orderBy } from "lodash"; import { hasFilter } from "@calcom/features/filters/lib/hasFilter"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; -import { CAL_URL } from "@calcom/lib/constants"; +import { getTeamAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; -import { getBookerUrl } from "@calcom/lib/server/getBookerUrl"; import type { PrismaClient } from "@calcom/prisma"; import { baseEventTypeSelect } from "@calcom/prisma"; import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; @@ -116,6 +117,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => slug: true, parentId: true, metadata: true, + parent: true, members: { select: { userId: true, @@ -160,6 +162,14 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); } + const memberships = user.teams.map((membership) => ({ + ...membership, + team: { + ...membership.team, + metadata: teamMetadataSchema.parse(membership.team.metadata), + }, + })); + type UserEventTypes = (typeof user.eventTypes)[number]; type TeamEventTypeChildren = (typeof user.teams)[number]["team"]["eventTypes"][number]; @@ -176,11 +186,12 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => type EventTypeGroup = { teamId?: number | null; parentId?: number | null; + bookerUrl: string; membershipRole?: MembershipRole | null; profile: { slug: (typeof user)["username"]; name: (typeof user)["name"]; - image?: string; + image: string; }; metadata: { membershipCount: number; @@ -195,16 +206,16 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => (evType) => evType.schedulingType !== SchedulingType.MANAGED ); - const image = user?.username ? `${CAL_URL}/${user.username}/avatar.png` : undefined; - if (!input?.filters || !hasFilter(input?.filters) || input?.filters?.userIds?.includes(user.id)) { + const bookerUrl = await getBookerBaseUrl(user); eventTypeGroups.push({ teamId: null, + bookerUrl, membershipRole: null, profile: { slug: user.username, name: user.name, - image, + image: getUserAvatarUrl({ username: user.username, organizationId: user.organizationId }), }, eventTypes: orderBy(unmanagedEventTypes, ["position", "id"], ["desc", "asc"]), metadata: { @@ -227,9 +238,9 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => }; eventTypeGroups = ([] as EventTypeGroup[]).concat( eventTypeGroups, - user.teams + memberships .filter((mmship) => { - const metadata = teamMetadataSchema.parse(mmship.team.metadata); + const metadata = mmship.team.metadata; if (metadata?.isOrganization) { return false; } else { @@ -243,33 +254,50 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => const orgMembership = teamMemberships.find( (teamM) => teamM.teamId === membership.team.parentId )?.membershipRole; + + const team = { + ...membership.team, + metadata: teamMetadataSchema.parse(membership.team.metadata), + }; + + let slug; + + if (input?.forRoutingForms) { + // For Routing form we want to ensure that after migration of team to an org, the URL remains same for the team + // Once we solve this https://github.com/calcom/cal.com/issues/12399, we can remove this conditional change in slug + slug = `team/${team.slug}`; + } else { + // In an Org, a team can be accessed without /team prefix as well as with /team prefix + slug = team.slug ? (!team.parentId ? `team/${team.slug}` : `${team.slug}`) : null; + } return { - teamId: membership.team.id, - parentId: membership.team.parentId, + teamId: team.id, + parentId: team.parentId, + bookerUrl: getBookerBaseUrlSync(team.parent?.slug ?? null), membershipRole: orgMembership && compareMembership(orgMembership, membership.role) ? orgMembership : membership.role, profile: { - name: membership.team.name, - image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`, - slug: membership.team.slug - ? !membership.team.parentId - ? `team/${membership.team.slug}` - : `${membership.team.slug}` - : null, + image: getTeamAvatarUrl({ + slug: team.slug, + requestedSlug: team.metadata?.requestedSlug ?? null, + organizationId: team.parentId, + }), + name: team.name, + slug, }, metadata: { - membershipCount: membership.team.members.length, + membershipCount: team.members.length, readOnly: membership.role === - (membership.team.parentId + (team.parentId ? orgMembership && compareMembership(orgMembership, membership.role) ? orgMembership : MembershipRole.MEMBER : MembershipRole.MEMBER), }, - eventTypes: membership.team.eventTypes + eventTypes: team.eventTypes .map(mapEventType) .filter(filterTeamsEventTypesBasedOnInput) .filter((evType) => evType.userId === null || evType.userId === ctx.user.id) @@ -282,7 +310,6 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => }) ); - const bookerUrl = await getBookerUrl(user); return { eventTypeGroups, // so we can show a dropdown when the user has teams @@ -291,7 +318,6 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => ...group.metadata, teamId: group.teamId, membershipRole: group.membershipRole, - image: `${bookerUrl}/${group.profile.slug}/avatar.png`, })), }; }; diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.schema.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.schema.ts index 1d3dd6f41b..b7a7424552 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.schema.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.schema.ts @@ -9,6 +9,7 @@ export const filterQuerySchemaStrict = z.object({ export const ZEventTypeInputSchema = z .object({ filters: filterQuerySchemaStrict.optional(), + forRoutingForms: z.boolean().optional(), }) .nullish(); diff --git a/packages/trpc/server/routers/viewer/eventTypes/types.ts b/packages/trpc/server/routers/viewer/eventTypes/types.ts index eeec8f8919..26e92e99bd 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/types.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/types.ts @@ -7,6 +7,7 @@ import { eventTypeBookingFields } from "@calcom/prisma/zod-utils"; export const EventTypeUpdateInput = _EventTypeModel /** Optional fields */ .extend({ + isInstantEvent: z.boolean().optional(), customInputs: z.array(customInputSchema).optional(), destinationCalendar: _DestinationCalendarModel.pick({ integration: true, diff --git a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts index 1f5fb9e13b..96bf895607 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/update.handler.ts @@ -1,6 +1,7 @@ import { Prisma } from "@prisma/client"; import type { NextApiResponse, GetServerSidePropsContext } from "next"; +import type { appDataSchemas } from "@calcom/app-store/apps.schemas.generated"; import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes"; import { validateIntervalLimitOrder } from "@calcom/lib"; import logger from "@calcom/lib/logger"; @@ -240,31 +241,14 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { } } - /** - * Since you can have multiple payment apps we will honor the first one to save in eventType - * but the real detail will be inside app metadata, so with this you can have different prices in different apps - * So the price and currency inside eventType will be deprecated soon or just keep as reference. - */ - if ( - input.metadata?.apps?.alby?.price || - input?.metadata?.apps?.paypal?.price || - input?.metadata?.apps?.stripe?.price - ) { - data.price = - input.metadata?.apps?.alby?.price || - input.metadata.apps.paypal?.price || - input.metadata.apps.stripe?.price; - } - - if ( - input.metadata?.apps?.alby?.currency || - input?.metadata?.apps?.paypal?.currency || - input?.metadata?.apps?.stripe?.currency - ) { - data.currency = - input.metadata?.apps?.alby?.currency || - input.metadata.apps.paypal?.currency || - input.metadata.apps.stripe?.currency; + for (const appKey in input.metadata?.apps) { + const app = input.metadata?.apps[appKey as keyof typeof appDataSchemas]; + // There should only be one enabled payment app in the metadata + if (app.enabled && app.price && app.currency) { + data.price = app.price; + data.currency = app.currency; + break; + } } const connectedLink = await ctx.prisma.hashedLink.findFirst({ diff --git a/packages/trpc/server/routers/viewer/oAuth/_router.tsx b/packages/trpc/server/routers/viewer/oAuth/_router.tsx index 24a5fb0be7..b2936ed0a4 100644 --- a/packages/trpc/server/routers/viewer/oAuth/_router.tsx +++ b/packages/trpc/server/routers/viewer/oAuth/_router.tsx @@ -14,7 +14,7 @@ type OAuthRouterHandlerCache = { const UNSTABLE_HANDLER_CACHE: OAuthRouterHandlerCache = {}; export const oAuthRouter = router({ - getClient: authedProcedure.input(ZGetClientInputSchema).query(async ({ ctx, input }) => { + getClient: authedProcedure.input(ZGetClientInputSchema).query(async ({ input }) => { if (!UNSTABLE_HANDLER_CACHE.getClient) { UNSTABLE_HANDLER_CACHE.getClient = await import("./getClient.handler").then( (mod) => mod.getClientHandler diff --git a/packages/trpc/server/routers/viewer/organizations/_router.tsx b/packages/trpc/server/routers/viewer/organizations/_router.tsx index 6022cf06ab..ad53625895 100644 --- a/packages/trpc/server/routers/viewer/organizations/_router.tsx +++ b/packages/trpc/server/routers/viewer/organizations/_router.tsx @@ -6,6 +6,9 @@ import authedProcedure, { } from "../../../procedures/authedProcedure"; import { importHandler, router } from "../../../trpc"; import { ZAddBulkTeams } from "./addBulkTeams.schema"; +import { ZAdminDeleteInput } from "./adminDelete.schema"; +import { ZAdminGet } from "./adminGet.schema"; +import { ZAdminUpdate } from "./adminUpdate.schema"; import { ZAdminVerifyInput } from "./adminVerify.schema"; import { ZBulkUsersDelete } from "./bulkDeleteUsers.schema."; import { ZCreateInputSchema } from "./create.schema"; @@ -25,7 +28,7 @@ const NAMESPACE = "organizations"; const namespaced = (s: string) => `${NAMESPACE}.${s}`; export const viewerOrganizationsRouter = router({ - create: authedProcedure.input(ZCreateInputSchema).mutation(async (opts) => { + create: authedAdminProcedure.input(ZCreateInputSchema).mutation(async (opts) => { const handler = await importHandler(namespaced("create"), () => import("./create.handler")); return handler(opts); }), @@ -64,14 +67,6 @@ export const viewerOrganizationsRouter = router({ const handler = await importHandler(namespaced("getMembers"), () => import("./getMembers.handler")); return handler(opts); }), - adminGetAll: authedAdminProcedure.query(async (opts) => { - const handler = await importHandler(namespaced("adminGetAll"), () => import("./adminGetAll.handler")); - return handler(opts); - }), - adminVerify: authedAdminProcedure.input(ZAdminVerifyInput).mutation(async (opts) => { - const handler = await importHandler(namespaced("adminVerify"), () => import("./adminVerify.handler")); - return handler(opts); - }), listMembers: authedProcedure.input(ZListMembersSchema).query(async (opts) => { const handler = await importHandler(namespaced("listMembers"), () => import("./listMembers.handler")); return handler(opts); @@ -114,7 +109,7 @@ export const viewerOrganizationsRouter = router({ const handler = await importHandler(namespaced("getOtherTeam"), () => import("./getOtherTeam.handler")); return handler(opts); }), - listOtherTeams: authedOrgAdminProcedure.query(async (opts) => { + listOtherTeams: authedProcedure.query(async (opts) => { const handler = await importHandler( namespaced("listOtherTeams"), () => import("./listOtherTeams.handler") @@ -125,4 +120,25 @@ export const viewerOrganizationsRouter = router({ const handler = await importHandler(namespaced("deleteTeam"), () => import("./deleteTeam.handler")); return handler(opts); }), + + adminGetAll: authedAdminProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("adminGetAll"), () => import("./adminGetAll.handler")); + return handler(opts); + }), + adminGet: authedAdminProcedure.input(ZAdminGet).query(async (opts) => { + const handler = await importHandler(namespaced("adminGet"), () => import("./adminGet.handler")); + return handler(opts); + }), + adminUpdate: authedAdminProcedure.input(ZAdminUpdate).mutation(async (opts) => { + const handler = await importHandler(namespaced("adminUpdate"), () => import("./adminUpdate.handler")); + return handler(opts); + }), + adminVerify: authedAdminProcedure.input(ZAdminVerifyInput).mutation(async (opts) => { + const handler = await importHandler(namespaced("adminVerify"), () => import("./adminVerify.handler")); + return handler(opts); + }), + adminDelete: authedAdminProcedure.input(ZAdminDeleteInput).mutation(async (opts) => { + const handler = await importHandler(namespaced("adminDelete"), () => import("./adminDelete.handler")); + return handler(opts); + }), }); diff --git a/packages/trpc/server/routers/viewer/organizations/adminDelete.handler.ts b/packages/trpc/server/routers/viewer/organizations/adminDelete.handler.ts new file mode 100644 index 0000000000..2cb9cd423f --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminDelete.handler.ts @@ -0,0 +1,80 @@ +import { deleteDomain } from "@calcom/lib/domainManager/organization"; +import logger from "@calcom/lib/logger"; +import { prisma } from "@calcom/prisma"; + +import { TRPCError } from "@trpc/server"; + +import type { TrpcSessionUser } from "../../../trpc"; +import type { TAdminDeleteInput } from "./adminDelete.schema"; + +const log = logger.getSubLogger({ prefix: ["organizations/adminDelete"] }); +type AdminDeleteOption = { + ctx: { + user: NonNullable<TrpcSessionUser>; + }; + input: TAdminDeleteInput; +}; + +export const adminDeleteHandler = async ({ input }: AdminDeleteOption) => { + const foundOrg = await prisma.team.findUnique({ + where: { + id: input.orgId, + metadata: { + path: ["isOrganization"], + equals: true, + }, + }, + include: { + members: { + select: { + user: true, + }, + }, + }, + }); + + if (!foundOrg) + throw new TRPCError({ + code: "FORBIDDEN", + message: "Organization not found", + }); + + if (foundOrg.slug) { + await deleteDomain(foundOrg.slug); + } + + await renameUsersToAvoidUsernameConflicts(foundOrg.members.map((member) => member.user)); + await prisma.team.delete({ + where: { + id: input.orgId, + }, + }); + + return { + ok: true, + message: `Organization ${foundOrg.name} deleted.`, + }; +}; + +export default adminDeleteHandler; + +async function renameUsersToAvoidUsernameConflicts(users: { id: number; username: string | null }[]) { + for (const user of users) { + let currentUsername = user.username; + + if (!currentUsername) { + currentUsername = "no-username"; + log.warn(`User ${user.id} has no username, defaulting to ${currentUsername}`); + } + + await prisma.user.update({ + where: { + id: user.id, + }, + data: { + // user.id being auto-incremented, we can safely assume that the username will be unique + username: `${currentUsername}-${user.id}`, + }, + }); + } +} diff --git a/packages/trpc/server/routers/viewer/organizations/adminDelete.schema.ts b/packages/trpc/server/routers/viewer/organizations/adminDelete.schema.ts new file mode 100644 index 0000000000..8b8de86174 --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminDelete.schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const ZAdminDeleteInput = z.object({ + orgId: z.number(), +}); + +export type TAdminDeleteInput = z.infer<typeof ZAdminDeleteInput>; diff --git a/packages/trpc/server/routers/viewer/organizations/adminGet.handler.ts b/packages/trpc/server/routers/viewer/organizations/adminGet.handler.ts new file mode 100644 index 0000000000..6574f8e6ee --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminGet.handler.ts @@ -0,0 +1,59 @@ +import { prisma } from "@calcom/prisma"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { TRPCError } from "@trpc/server"; + +import type { TrpcSessionUser } from "../../../trpc"; +import type { TAdminGet } from "./adminGet.schema"; + +type AdminGetOptions = { + ctx: { + user: NonNullable<TrpcSessionUser>; + }; + input: TAdminGet; +}; + +export const adminGetHandler = async ({ input }: AdminGetOptions) => { + const org = await prisma.team.findUnique({ + where: { + id: input.id, + }, + select: { + id: true, + name: true, + slug: true, + metadata: true, + members: { + where: { + role: "OWNER", + }, + select: { + user: { + select: { + id: true, + name: true, + email: true, + }, + }, + }, + }, + }, + }); + + if (!org) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Organization not found", + }); + } + const parsedMetadata = teamMetadataSchema.parse(org.metadata); + if (!parsedMetadata?.isOrganization) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Organization not found", + }); + } + return { ...org, metadata: parsedMetadata }; +}; + +export default adminGetHandler; diff --git a/packages/trpc/server/routers/viewer/organizations/adminGet.schema.ts b/packages/trpc/server/routers/viewer/organizations/adminGet.schema.ts new file mode 100644 index 0000000000..c4173cef23 --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminGet.schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const ZAdminGet = z.object({ + id: z.number(), +}); + +export type TAdminGet = z.infer<typeof ZAdminGet>; diff --git a/packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts b/packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts new file mode 100644 index 0000000000..54e6b6c5bf --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminUpdate.handler.ts @@ -0,0 +1,94 @@ +import type { Prisma } from "@prisma/client"; + +import { renameDomain } from "@calcom/lib/domainManager/organization"; +import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers"; +import { HttpError } from "@calcom/lib/http-error"; +import { prisma } from "@calcom/prisma"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import type { TrpcSessionUser } from "../../../trpc"; +import type { TAdminUpdate } from "./adminUpdate.schema"; + +type AdminUpdateOptions = { + ctx: { + user: NonNullable<TrpcSessionUser>; + }; + input: TAdminUpdate; +}; + +export const adminUpdateHandler = async ({ input }: AdminUpdateOptions) => { + const { id, ...restInput } = input; + const existingOrg = await prisma.team.findUnique({ + where: { + id: id, + }, + }); + + if (!existingOrg) { + throw new HttpError({ + message: "Organization not found", + statusCode: 404, + }); + } + + const { mergeMetadata } = getMetadataHelpers(teamMetadataSchema.unwrap(), existingOrg.metadata); + const data: Prisma.TeamUpdateArgs["data"] = { + ...restInput, + metadata: mergeMetadata({ ...restInput.metadata }), + }; + + if (restInput.slug) { + await throwIfSlugConflicts({ id, slug: restInput.slug }); + const isSlugChanged = restInput.slug !== existingOrg.slug; + if (isSlugChanged) { + // If slug is changed, we need to rename the domain first + // If renaming fails, we don't want to update the new slug in DB + await renameDomain(existingOrg.slug, restInput.slug); + } + data.slug = input.slug; + data.metadata = mergeMetadata({ + // If we save slug, we don't need the requestedSlug anymore + requestedSlug: undefined, + ...input.metadata, + }); + } + + const updatedOrganisation = await prisma.team.update({ + where: { id }, + data, + }); + + return updatedOrganisation; +}; + +export default adminUpdateHandler; + +async function throwIfSlugConflicts({ id, slug }: { id: number; slug: string }) { + const organizationsWithSameSlug = await prisma.team.findMany({ + where: { + slug: slug, + parentId: null, + }, + }); + + if (organizationsWithSameSlug.length > 1) { + throw new HttpError({ + message: "There can only be one organization with a given slug", + statusCode: 400, + }); + } + + const foundOrg = organizationsWithSameSlug[0]; + if (!foundOrg) { + // No org with same slug found - So, no conflict. + return; + } + + // If foundOrg isn't same as the org being edited + if (foundOrg.id !== id) { + throw new HttpError({ + message: "Organization with same slug already exists", + statusCode: 400, + }); + } +} diff --git a/packages/trpc/server/routers/viewer/organizations/adminUpdate.schema.ts b/packages/trpc/server/routers/viewer/organizations/adminUpdate.schema.ts new file mode 100644 index 0000000000..82ca9a3572 --- /dev/null +++ b/packages/trpc/server/routers/viewer/organizations/adminUpdate.schema.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +export const ZAdminUpdate = z.object({ + id: z.number(), + name: z.string().optional(), + slug: z.string().nullish(), + metadata: teamMetadataSchema.optional(), +}); + +export type TAdminUpdate = z.infer<typeof ZAdminUpdate>; diff --git a/packages/trpc/server/routers/viewer/organizations/adminVerify.handler.ts b/packages/trpc/server/routers/viewer/organizations/adminVerify.handler.ts index a73e58a48e..52d795edaf 100644 --- a/packages/trpc/server/routers/viewer/organizations/adminVerify.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/adminVerify.handler.ts @@ -42,7 +42,7 @@ export const adminVerifyHandler = async ({ input }: AdminVerifyOptions) => { const acceptedEmailDomain = foundOrg.members[0].user.email.split("@")[1]; - const metaDataParsed = teamMetadataSchema.parse(foundOrg.metadata); + const existingMetadataParsed = teamMetadataSchema.parse(foundOrg.metadata); await prisma.team.update({ where: { @@ -50,9 +50,8 @@ export const adminVerifyHandler = async ({ input }: AdminVerifyOptions) => { }, data: { metadata: { - ...metaDataParsed, + ...existingMetadataParsed, isOrganizationVerified: true, - orgAutoAcceptEmail: acceptedEmailDomain, }, }, }); diff --git a/packages/trpc/server/routers/viewer/organizations/adminVerify.schema.ts b/packages/trpc/server/routers/viewer/organizations/adminVerify.schema.ts index a4d27ce75d..a03eeaa882 100644 --- a/packages/trpc/server/routers/viewer/organizations/adminVerify.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/adminVerify.schema.ts @@ -1,10 +1,7 @@ import { z } from "zod"; -const statusSchema = z.enum(["ACCEPT", "DENY"] as const); - export const ZAdminVerifyInput = z.object({ orgId: z.number(), - status: statusSchema, }); export type TAdminVerifyInput = z.infer<typeof ZAdminVerifyInput>; diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts index 1b5509afb5..365644fb18 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts @@ -5,9 +5,9 @@ import { totp } from "otplib"; import { sendOrganizationEmailVerification } from "@calcom/emails"; import { sendAdminOrganizationNotification } from "@calcom/emails"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; -import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; import { IS_TEAM_BILLING_ENABLED, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/constants"; +import { createDomain } from "@calcom/lib/domainManager/organization"; import { getTranslation } from "@calcom/lib/server/i18n"; import slugify from "@calcom/lib/slugify"; import { prisma } from "@calcom/prisma"; @@ -34,30 +34,6 @@ const getIPAddress = async (url: string): Promise<string> => { }); }; -const vercelCreateDomain = async (domain: string) => { - const response = await fetch( - `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, - { - body: JSON.stringify({ name: `${domain}.${subdomainSuffix()}` }), - headers: { - Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, - "Content-Type": "application/json", - }, - method: "POST", - } - ); - - const data = await response.json(); - - // Domain is already owned by another team but you can request delegation to access it - if (data.error?.code === "forbidden") return false; - - // Domain is already being used by a different project - if (data.error?.code === "domain_taken") return false; - - return true; -}; - export const createHandler = async ({ input, ctx }: CreateOptions) => { const { slug, name, adminEmail, adminUsername, check } = input; @@ -67,17 +43,22 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { }, }); - // An org doesn't have a parentId. A team that isn't part of an org also doesn't have a parentId. - // So, an org can't have the same slug as a non-org team. - // There is a unique index on [slug, parentId] in Team because we don't add the slug to the team always. We only add metadata.requestedSlug in some cases. So, DB won't prevent creation of such an organization. - const hasANonOrgTeamOrOrgWithSameSlug = await prisma.team.findFirst({ + const hasAnOrgWithSameSlug = await prisma.team.findFirst({ where: { slug: slug, parentId: null, + metadata: { + path: ["isOrganization"], + equals: true, + }, }, }); - if (hasANonOrgTeamOrOrgWithSameSlug || RESERVED_SUBDOMAINS.includes(slug)) + // Allow creating an organization with same requestedSlug as a non-org Team's slug + // It is needed so that later we can migrate the non-org Team(with the conflicting slug) to the newly created org + // Publishing the organization would fail if the team with the same slug is not migrated first + + if (hasAnOrgWithSameSlug || RESERVED_SUBDOMAINS.includes(slug)) throw new TRPCError({ code: "BAD_REQUEST", message: "organization_url_taken" }); if (userCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "admin_email_taken" }); @@ -93,12 +74,9 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { let isOrganizationConfigured = false; if (check === false) { - // eslint-disable-next-line turbo/no-undeclared-env-vars - if (process.env.VERCEL) { - // We only want to proceed to register the subdomain for the org in Vercel - // within a Vercel context - isOrganizationConfigured = await vercelCreateDomain(slug); - } else { + isOrganizationConfigured = await createDomain(slug); + + if (!isOrganizationConfigured) { // Otherwise, we proceed to send an administrative email to admins regarding // the need to configure DNS registry to support the newly created org const instanceAdmins = await prisma.user.findMany({ @@ -148,7 +126,7 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { metadata: { ...(IS_TEAM_BILLING_ENABLED ? { requestedSlug: slug } : {}), isOrganization: true, - isOrganizationVerified: false, + isOrganizationVerified: true, isOrganizationConfigured, orgAutoAcceptEmail: emailDomain, }, diff --git a/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts index 1225ffdabf..2a29b001d0 100644 --- a/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts @@ -50,6 +50,24 @@ export const getMembersHandler = async ({ input, ctx }: CreateOptions) => { }, }, }); + + if (teamIdToExclude && teamQuery?.members) { + const excludedteamUsers = await prisma.team.findUnique({ + where: { + id: teamIdToExclude, + }, + select: { + members: { + select: { + userId: true, + }, + }, + }, + }); + const excludedUserIds = excludedteamUsers?.members.map((item) => item.userId) ?? []; + teamQuery.members = teamQuery?.members.filter((member) => !excludedUserIds.includes(member.userId)); + } + return teamQuery?.members || []; }; diff --git a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts index 1c4d332e62..0aad9bdc39 100644 --- a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts @@ -1,6 +1,7 @@ import type { Prisma } from "@prisma/client"; import z from "zod"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import { prisma } from "@calcom/prisma"; import type { TrpcSessionUser } from "../../../trpc"; @@ -8,8 +9,9 @@ import type { TrpcSessionUser } from "../../../trpc"; export const ZListOtherTeamMembersSchema = z.object({ teamId: z.number(), query: z.string().optional(), - limit: z.number().optional(), + limit: z.number(), offset: z.number().optional(), + cursor: z.number().nullish(), // <-- "cursor" needs to exist when using useInfiniteQuery, but can be any type }); export type TListOtherTeamMembersSchema = z.infer<typeof ZListOtherTeamMembersSchema>; @@ -25,11 +27,12 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => { const whereConditional: Prisma.MembershipWhereInput = { teamId: input.teamId, }; - const { limit = 20 } = input; - let { offset = 0 } = input; + // const { limit = 20 } = input; + // let { offset = 0 } = input; + + const { cursor, limit } = input; if (input.query) { - offset = 0; whereConditional.user = { OR: [ { @@ -68,16 +71,31 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => { name: true, email: true, avatar: true, + organization: true, + organizationId: true, }, }, }, distinct: ["userId"], orderBy: { role: "desc" }, - take: limit, - skip: offset, + cursor: cursor ? { id: cursor } : undefined, + take: limit + 1, // We take +1 as itll be used for the next cursor }); + let nextCursor: typeof cursor | undefined = undefined; + if (members && members.length > limit) { + const nextItem = members.pop(); + nextCursor = nextItem?.id || null; + } - return members; + return { + rows: members.map((m) => { + return { + ...m, + bookerUrl: getBookerBaseUrlSync(m.user.organization?.slug || ""), + }; + }), + nextCursor, + }; }; export default listOtherTeamMembers; diff --git a/packages/trpc/server/routers/viewer/organizations/listOtherTeams.handler.ts b/packages/trpc/server/routers/viewer/organizations/listOtherTeams.handler.ts index 3bb2a30d02..5ff010140e 100644 --- a/packages/trpc/server/routers/viewer/organizations/listOtherTeams.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/listOtherTeams.handler.ts @@ -8,15 +8,18 @@ type ListOptions = { }; }; -export const listOtherTeamHandler = async ({ ctx }: ListOptions) => { +export const listOtherTeamHandler = async ({ ctx: { user } }: ListOptions) => { + if (!user?.organization?.isOrgAdmin) { + return []; + } const teamsInOrgIamNotPartOf = await prisma.team.findMany({ where: { parent: { - id: ctx.user?.organization?.id, + id: user?.organization?.id, }, members: { none: { - userId: ctx.user.id, + userId: user.id, }, }, }, diff --git a/packages/trpc/server/routers/viewer/organizations/update.handler.ts b/packages/trpc/server/routers/viewer/organizations/update.handler.ts index 83968d2873..2049455820 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.handler.ts @@ -61,6 +61,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { const data: Prisma.TeamUpdateArgs["data"] = { name: input.name, logo: input.logo, + calVideoLogo: input.calVideoLogo, bio: input.bio, hideBranding: input.hideBranding, hideBookATeamMember: input.hideBookATeamMember, diff --git a/packages/trpc/server/routers/viewer/organizations/update.schema.ts b/packages/trpc/server/routers/viewer/organizations/update.schema.ts index 91d1069b31..40e577de6d 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.schema.ts @@ -16,6 +16,11 @@ export const ZUpdateInputSchema = z.object({ .optional() .nullable() .transform((v) => v || null), + calVideoLogo: z + .string() + .optional() + .nullable() + .transform((v) => v || null), slug: z.string().optional(), hideBranding: z.boolean().optional(), hideBookATeamMember: z.boolean().optional(), diff --git a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts index 35f17b9435..b83a10c60f 100644 --- a/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/updateUser.handler.ts @@ -15,6 +15,20 @@ type UpdateUserOptions = { input: TUpdateUserInputSchema; }; +const applyRoleToAllTeams = async (userId: number, teamIds: number[], role: MembershipRole) => { + await prisma.membership.updateMany({ + where: { + userId, + teamId: { + in: teamIds, + }, + }, + data: { + role, + }, + }); +}; + export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { const { user } = ctx; const { id: userId, organizationId } = user; @@ -35,6 +49,24 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { teamId: organizationId, accepted: true, }, + include: { + team: { + include: { + children: { + where: { + members: { + some: { + userId: input.userId, + }, + }, + }, + include: { + members: true, + }, + }, + }, + }, + }, }); if (!requestedMember) @@ -72,6 +104,13 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => { }), ]); + if (input.role === MembershipRole.ADMIN || input.role === MembershipRole.OWNER) { + const teamIds = requestedMember.team.children + .map((sub_team) => sub_team.members.find((item) => item.userId === input.userId)?.teamId) + .filter(Boolean) as number[]; //filter out undefined + + await applyRoleToAllTeams(input.userId, teamIds, input.role); + } // TODO: audit log this return { diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index fbd63249b1..52e0f6932c 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -157,6 +157,7 @@ export async function getEventType( periodType: true, periodStartDate: true, periodEndDate: true, + onlyShowFirstAvailableSlot: true, periodCountCalendarDays: true, periodDays: true, metadata: true, @@ -292,7 +293,7 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) { }` ); const getStartTime = (startTimeInput: string, timeZone?: string) => { - const startTimeMin = dayjs.utc().add(eventType.minimumBookingNotice, "minutes"); + const startTimeMin = dayjs.utc().add(eventType.minimumBookingNotice || 1, "minutes"); const startTime = timeZone === "Etc/GMT" ? dayjs.utc(startTimeInput) : dayjs(startTimeInput).tz(timeZone); return startTimeMin.isAfter(startTime) ? startTimeMin.tz(timeZone) : startTime; @@ -327,8 +328,8 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) { input.rescheduleUid && durationToUse ? endTime.add(durationToUse, "minute").toDate() : endTime.toDate(); const sharedQuery = { - startTime: { gte: startTimeDate }, - endTime: { lte: endTimeDate }, + startTime: { lte: endTimeDate }, + endTime: { gte: startTimeDate }, status: { in: [BookingStatus.ACCEPTED], }, @@ -368,6 +369,7 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) { eventType: { select: { id: true, + onlyShowFirstAvailableSlot: true, afterEventBuffer: true, beforeEventBuffer: true, seatsPerTimeSlot: true, @@ -577,6 +579,9 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) { const dateString = formatter.format(time.toDate()); r[dateString] = r[dateString] || []; + if (eventType.onlyShowFirstAvailableSlot && r[dateString].length > 0) { + return r; + } r[dateString].push({ ...passThroughProps, time: time.toISOString(), diff --git a/packages/trpc/server/routers/viewer/teams/_router.tsx b/packages/trpc/server/routers/viewer/teams/_router.tsx index 5c43eb899b..174dd335a0 100644 --- a/packages/trpc/server/routers/viewer/teams/_router.tsx +++ b/packages/trpc/server/routers/viewer/teams/_router.tsx @@ -1,5 +1,5 @@ import authedProcedure from "../../../procedures/authedProcedure"; -import { router } from "../../../trpc"; +import { importHandler, router } from "../../../trpc"; import { ZAcceptOrLeaveInputSchema } from "./acceptOrLeave.schema"; import { ZChangeMemberRoleInputSchema } from "./changeMemberRole.schema"; import { ZCreateInputSchema } from "./create.schema"; @@ -20,457 +20,139 @@ import { ZSetInviteExpirationInputSchema } from "./setInviteExpiration.schema"; import { ZUpdateInputSchema } from "./update.schema"; import { ZUpdateMembershipInputSchema } from "./updateMembership.schema"; -type TeamsRouterHandlerCache = { - get?: typeof import("./get.handler").getHandler; - list?: typeof import("./list.handler").listHandler; - listOwnedTeams?: typeof import("./listOwnedTeams.handler").listOwnedTeamsHandler; - create?: typeof import("./create.handler").createHandler; - update?: typeof import("./update.handler").updateHandler; - delete?: typeof import("./delete.handler").deleteHandler; - removeMember?: typeof import("./removeMember.handler").removeMemberHandler; - inviteMember?: typeof import("./inviteMember/inviteMember.handler").inviteMemberHandler; - acceptOrLeave?: typeof import("./acceptOrLeave.handler").acceptOrLeaveHandler; - changeMemberRole?: typeof import("./changeMemberRole.handler").changeMemberRoleHandler; - getMemberAvailability?: typeof import("./getMemberAvailability.handler").getMemberAvailabilityHandler; - getMembershipbyUser?: typeof import("./getMembershipbyUser.handler").getMembershipbyUserHandler; - updateMembership?: typeof import("./updateMembership.handler").updateMembershipHandler; - publish?: typeof import("./publish.handler").publishHandler; - getUpgradeable?: typeof import("./getUpgradeable.handler").getUpgradeableHandler; - listMembers?: typeof import("./listMembers.handler").listMembersHandler; - hasTeamPlan?: typeof import("./hasTeamPlan.handler").hasTeamPlanHandler; - listInvites?: typeof import("./listInvites.handler").listInvitesHandler; - createInvite?: typeof import("./createInvite.handler").createInviteHandler; - setInviteExpiration?: typeof import("./setInviteExpiration.handler").setInviteExpirationHandler; - deleteInvite?: typeof import("./deleteInvite.handler").deleteInviteHandler; - inviteMemberByToken?: typeof import("./inviteMemberByToken.handler").inviteMemberByTokenHandler; - hasEditPermissionForUser?: typeof import("./hasEditPermissionForUser.handler").hasEditPermissionForUser; - resendInvitation?: typeof import("./resendInvitation.handler").resendInvitationHandler; -}; - -const UNSTABLE_HANDLER_CACHE: TeamsRouterHandlerCache = {}; +const NAMESPACE = "teams"; +const namespaced = (s: string) => `${NAMESPACE}.${s}`; export const viewerTeamsRouter = router({ // Retrieves team by id - get: authedProcedure.input(ZGetInputSchema).query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.get) { - UNSTABLE_HANDLER_CACHE.get = await import("./get.handler").then((mod) => mod.getHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.get) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.get({ - ctx, - input, - }); + get: authedProcedure.input(ZGetInputSchema).query(async (opts) => { + const handler = await importHandler(namespaced("get"), () => import("./get.handler")); + return handler(opts); }), - // Returns teams I a member of - list: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.list) { - UNSTABLE_HANDLER_CACHE.list = await import("./list.handler").then((mod) => mod.listHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.list) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.list({ - ctx, - }); + list: authedProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("list"), () => import("./list.handler")); + return handler(opts); }), // Returns Teams I am a owner/admin of - listOwnedTeams: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.listOwnedTeams) { - UNSTABLE_HANDLER_CACHE.listOwnedTeams = await import("./listOwnedTeams.handler").then( - (mod) => mod.listOwnedTeamsHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.listOwnedTeams) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.listOwnedTeams({ - ctx, - }); + listOwnedTeams: authedProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("list"), () => import("./list.handler")); + return handler(opts); }), - - create: authedProcedure.input(ZCreateInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.create) { - UNSTABLE_HANDLER_CACHE.create = await import("./create.handler").then((mod) => mod.createHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.create) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.create({ - ctx, - input, - }); + create: authedProcedure.input(ZCreateInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("create"), () => import("./create.handler")); + return handler(opts); }), - // Allows team owner to update team metadata - update: authedProcedure.input(ZUpdateInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.update) { - UNSTABLE_HANDLER_CACHE.update = await import("./update.handler").then((mod) => mod.updateHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.update) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.update({ - ctx, - input, - }); + update: authedProcedure.input(ZUpdateInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("update"), () => import("./update.handler")); + return handler(opts); }), - - delete: authedProcedure.input(ZDeleteInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.delete) { - UNSTABLE_HANDLER_CACHE.delete = await import("./delete.handler").then((mod) => mod.deleteHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.delete) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.delete({ - ctx, - input, - }); + delete: authedProcedure.input(ZDeleteInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("delete"), () => import("./delete.handler")); + return handler(opts); }), - - removeMember: authedProcedure.input(ZRemoveMemberInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.removeMember) { - UNSTABLE_HANDLER_CACHE.removeMember = await import("./removeMember.handler").then( - (mod) => mod.removeMemberHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.removeMember) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.removeMember({ - ctx, - input, - }); + removeMember: authedProcedure.input(ZRemoveMemberInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("removeMember"), () => import("./removeMember.handler")); + return handler(opts); }), - - inviteMember: authedProcedure.input(ZInviteMemberInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.inviteMember) { - UNSTABLE_HANDLER_CACHE.inviteMember = await import("./inviteMember/inviteMember.handler").then( - (mod) => mod.inviteMemberHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.inviteMember) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.inviteMember({ - ctx, - input, - }); + inviteMember: authedProcedure.input(ZInviteMemberInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("inviteMember"), + () => import("./inviteMember/inviteMember.handler") + ); + return handler(opts); }), - - acceptOrLeave: authedProcedure.input(ZAcceptOrLeaveInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.acceptOrLeave) { - UNSTABLE_HANDLER_CACHE.acceptOrLeave = await import("./acceptOrLeave.handler").then( - (mod) => mod.acceptOrLeaveHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.acceptOrLeave) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.acceptOrLeave({ - ctx, - input, - }); + acceptOrLeave: authedProcedure.input(ZAcceptOrLeaveInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("acceptOrLeave"), () => import("./acceptOrLeave.handler")); + return handler(opts); }), - - changeMemberRole: authedProcedure.input(ZChangeMemberRoleInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.changeMemberRole) { - UNSTABLE_HANDLER_CACHE.changeMemberRole = await import("./changeMemberRole.handler").then( - (mod) => mod.changeMemberRoleHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.changeMemberRole) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.changeMemberRole({ - ctx, - input, - }); + changeMemberRole: authedProcedure.input(ZChangeMemberRoleInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("changeMemberRole"), + () => import("./changeMemberRole.handler") + ); + return handler(opts); }), - - getMemberAvailability: authedProcedure - .input(ZGetMemberAvailabilityInputSchema) - .query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.getMemberAvailability) { - UNSTABLE_HANDLER_CACHE.getMemberAvailability = await import("./getMemberAvailability.handler").then( - (mod) => mod.getMemberAvailabilityHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.getMemberAvailability) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.getMemberAvailability({ - ctx, - input, - }); - }), - - getMembershipbyUser: authedProcedure - .input(ZGetMembershipbyUserInputSchema) - .query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.getMembershipbyUser) { - UNSTABLE_HANDLER_CACHE.getMembershipbyUser = await import("./getMembershipbyUser.handler").then( - (mod) => mod.getMembershipbyUserHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.getMembershipbyUser) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.getMembershipbyUser({ - ctx, - input, - }); - }), - - updateMembership: authedProcedure.input(ZUpdateMembershipInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.updateMembership) { - UNSTABLE_HANDLER_CACHE.updateMembership = await import("./updateMembership.handler").then( - (mod) => mod.updateMembershipHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.updateMembership) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.updateMembership({ - ctx, - input, - }); + getMemberAvailability: authedProcedure.input(ZGetMemberAvailabilityInputSchema).query(async (opts) => { + const handler = await importHandler( + namespaced("getMemberAvailability"), + () => import("./getMemberAvailability.handler") + ); + return handler(opts); }), - - publish: authedProcedure.input(ZPublishInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.publish) { - UNSTABLE_HANDLER_CACHE.publish = await import("./publish.handler").then((mod) => mod.publishHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.publish) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.publish({ - ctx, - input, - }); + getMembershipbyUser: authedProcedure.input(ZGetMembershipbyUserInputSchema).query(async (opts) => { + const handler = await importHandler( + namespaced("getMembershipbyUser"), + () => import("./getMembershipbyUser.handler") + ); + return handler(opts); + }), + updateMembership: authedProcedure.input(ZUpdateMembershipInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("updateMembership"), + () => import("./updateMembership.handler") + ); + return handler(opts); + }), + publish: authedProcedure.input(ZPublishInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("publish"), () => import("./publish.handler")); + return handler(opts); }), - /** This is a temporal endpoint so we can progressively upgrade teams to the new billing system. */ - getUpgradeable: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.getUpgradeable) { - UNSTABLE_HANDLER_CACHE.getUpgradeable = await import("./getUpgradeable.handler").then( - (mod) => mod.getUpgradeableHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.getUpgradeable) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.getUpgradeable({ - ctx, - }); + getUpgradeable: authedProcedure.query(async (opts) => { + const handler = await importHandler( + namespaced("getUpgradeable"), + () => import("./getUpgradeable.handler") + ); + return handler(opts); }), - - listMembers: authedProcedure.input(ZListMembersInputSchema).query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.listMembers) { - UNSTABLE_HANDLER_CACHE.listMembers = await import("./listMembers.handler").then( - (mod) => mod.listMembersHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.listMembers) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.listMembers({ - ctx, - input, - }); + listMembers: authedProcedure.input(ZListMembersInputSchema).query(async (opts) => { + const handler = await importHandler(namespaced("listMembers"), () => import("./listMembers.handler")); + return handler(opts); }), - - hasTeamPlan: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.hasTeamPlan) { - UNSTABLE_HANDLER_CACHE.hasTeamPlan = await import("./hasTeamPlan.handler").then( - (mod) => mod.hasTeamPlanHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.hasTeamPlan) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.hasTeamPlan({ - ctx, - }); + hasTeamPlan: authedProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("hasTeamPlan"), () => import("./hasTeamPlan.handler")); + return handler(opts); }), - - listInvites: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.listInvites) { - UNSTABLE_HANDLER_CACHE.listInvites = await import("./listInvites.handler").then( - (mod) => mod.listInvitesHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.listInvites) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.listInvites({ - ctx, - }); + listInvites: authedProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("listInvites"), () => import("./listInvites.handler")); + return handler(opts); }), - - createInvite: authedProcedure.input(ZCreateInviteInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.createInvite) { - UNSTABLE_HANDLER_CACHE.createInvite = await import("./createInvite.handler").then( - (mod) => mod.createInviteHandler - ); - } - - if (!UNSTABLE_HANDLER_CACHE.createInvite) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.createInvite({ - ctx, - input, - }); + createInvite: authedProcedure.input(ZCreateInviteInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("createInvite"), () => import("./createInvite.handler")); + return handler(opts); }), - - setInviteExpiration: authedProcedure - .input(ZSetInviteExpirationInputSchema) - .mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.setInviteExpiration) { - UNSTABLE_HANDLER_CACHE.setInviteExpiration = await import("./setInviteExpiration.handler").then( - (mod) => mod.setInviteExpirationHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.setInviteExpiration) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.setInviteExpiration({ - ctx, - input, - }); - }), - deleteInvite: authedProcedure.input(ZDeleteInviteInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.deleteInvite) { - UNSTABLE_HANDLER_CACHE.deleteInvite = await import("./deleteInvite.handler").then( - (mod) => mod.deleteInviteHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.deleteInvite) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.deleteInvite({ - ctx, - input, - }); + setInviteExpiration: authedProcedure.input(ZSetInviteExpirationInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("setInviteExpiration"), + () => import("./setInviteExpiration.handler") + ); + return handler(opts); }), - inviteMemberByToken: authedProcedure - .input(ZInviteMemberByTokenSchemaInputSchema) - .mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.inviteMemberByToken) { - UNSTABLE_HANDLER_CACHE.inviteMemberByToken = await import("./inviteMemberByToken.handler").then( - (mod) => mod.inviteMemberByTokenHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.inviteMemberByToken) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.inviteMemberByToken({ - ctx, - input, - }); - }), - hasEditPermissionForUser: authedProcedure - .input(ZHasEditPermissionForUserSchema) - .query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.hasEditPermissionForUser) { - UNSTABLE_HANDLER_CACHE.hasEditPermissionForUser = await import( - "./hasEditPermissionForUser.handler" - ).then((mod) => mod.hasEditPermissionForUser); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.hasEditPermissionForUser) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.hasEditPermissionForUser({ - ctx, - input, - }); - }), - resendInvitation: authedProcedure.input(ZResendInvitationInputSchema).mutation(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.resendInvitation) { - UNSTABLE_HANDLER_CACHE.resendInvitation = await import("./resendInvitation.handler").then( - (mod) => mod.resendInvitationHandler - ); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.resendInvitation) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.resendInvitation({ - ctx, - input, - }); + deleteInvite: authedProcedure.input(ZDeleteInviteInputSchema).mutation(async (opts) => { + const handler = await importHandler(namespaced("deleteInvite"), () => import("./deleteInvite.handler")); + return handler(opts); + }), + inviteMemberByToken: authedProcedure.input(ZInviteMemberByTokenSchemaInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("inviteMemberByToken"), + () => import("./inviteMemberByToken.handler") + ); + return handler(opts); + }), + hasEditPermissionForUser: authedProcedure.input(ZHasEditPermissionForUserSchema).query(async (opts) => { + const handler = await importHandler( + namespaced("hasEditPermissionForUser"), + () => import("./hasEditPermissionForUser.handler") + ); + return handler(opts); + }), + resendInvitation: authedProcedure.input(ZResendInvitationInputSchema).mutation(async (opts) => { + const handler = await importHandler( + namespaced("resendInvitation"), + () => import("./resendInvitation.handler") + ); + return handler(opts); }), }); diff --git a/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts b/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts index d15e4dba47..9e25f31a02 100644 --- a/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts @@ -48,3 +48,5 @@ export const acceptOrLeaveHandler = async ({ ctx, input }: AcceptOrLeaveOptions) } } }; + +export default acceptOrLeaveHandler; diff --git a/packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts b/packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts index f2a6f23861..290a1be923 100644 --- a/packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/changeMemberRole.handler.ts @@ -71,3 +71,5 @@ export const changeMemberRoleHandler = async ({ ctx, input }: ChangeMemberRoleOp // Sync Services: Close.com closeComUpsertTeamUser(membership.team, membership.user, membership.role); }; + +export default changeMemberRoleHandler; diff --git a/packages/trpc/server/routers/viewer/teams/create.handler.ts b/packages/trpc/server/routers/viewer/teams/create.handler.ts index 4c3ffffd18..77e20a5f13 100644 --- a/packages/trpc/server/routers/viewer/teams/create.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/create.handler.ts @@ -117,3 +117,5 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => { team: createdTeam, }; }; + +export default createHandler; diff --git a/packages/trpc/server/routers/viewer/teams/createInvite.handler.ts b/packages/trpc/server/routers/viewer/teams/createInvite.handler.ts index 380882ab55..d1e6315fe2 100644 --- a/packages/trpc/server/routers/viewer/teams/createInvite.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/createInvite.handler.ts @@ -7,7 +7,6 @@ import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { TRPCError } from "@calcom/trpc/server"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; -import { getMembersHandler } from "../organizations/getMembers.handler"; import type { TCreateInviteInputSchema } from "./createInvite.schema"; type CreateInviteOptions = { @@ -23,11 +22,7 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) = if (!membership || !membership?.team) throw new TRPCError({ code: "UNAUTHORIZED" }); const teamMetadata = teamMetadataSchema.parse(membership.team.metadata); - const isOrg = !!(membership.team?.parentId === null && teamMetadata?.isOrganization); - const orgMembers = await getMembersHandler({ - ctx, - input: { teamIdToExclude: teamId, distinctUser: true }, - }); + const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || teamMetadata?.isOrganization); if (input.token) { const existingToken = await prisma.verificationToken.findFirst({ @@ -36,7 +31,7 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) = if (!existingToken) throw new TRPCError({ code: "NOT_FOUND" }); return { token: existingToken.token, - inviteLink: await getInviteLink(existingToken.token, isOrg, orgMembers?.length), + inviteLink: await getInviteLink(existingToken.token, isOrganizationOrATeamInOrganization), }; } @@ -50,12 +45,14 @@ export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) = }, }); - return { token, inviteLink: await getInviteLink(token, isOrg, orgMembers?.length) }; + return { token, inviteLink: await getInviteLink(token, isOrganizationOrATeamInOrganization) }; }; -async function getInviteLink(token = "", isOrg = false, orgMembers = 0) { +async function getInviteLink(token = "", isOrgContext = false) { const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`; const orgInviteLink = `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/getting-started`; - if (isOrg || orgMembers > 0) return orgInviteLink; + if (isOrgContext) return orgInviteLink; return teamInviteLink; } + +export default createInviteHandler; diff --git a/packages/trpc/server/routers/viewer/teams/delete.handler.ts b/packages/trpc/server/routers/viewer/teams/delete.handler.ts index 29d7d07c17..0076e36a9d 100644 --- a/packages/trpc/server/routers/viewer/teams/delete.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/delete.handler.ts @@ -1,6 +1,6 @@ -import { subdomainSuffix } from "@calcom/ee/organizations/lib/orgDomains"; import { cancelTeamSubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments"; -import { IS_PRODUCTION, IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { deleteDomain } from "@calcom/lib/domainManager/organization"; import { isTeamOwner } from "@calcom/lib/server/queries/teams"; import { closeComDeleteTeam } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; @@ -18,41 +18,6 @@ type DeleteOptions = { input: TDeleteInputSchema; }; -const deleteVercelDomain = async ({ - slug, - isOrganization, -}: { - slug?: string | null; - isOrganization?: boolean | null; -}) => { - if (!isOrganization || !slug) { - return false; - } - - const fullDomain = `${slug}.${subdomainSuffix()}`; - const response = await fetch( - `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${fullDomain}?teamId=${process.env.TEAM_ID_VERCEL}`, - { - headers: { - Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, - }, - method: "DELETE", - } - ); - - const data = await response.json(); - - // Domain is already owned by another team but you can request delegation to access it - if (data.error?.code === "forbidden") - throw new TRPCError({ code: "CONFLICT", message: "domain_taken_team" }); - - // Domain is already being used by a different project - if (data.error?.code === "domain_taken") - throw new TRPCError({ code: "CONFLICT", message: "domain_taken_project" }); - - return true; -}; - export const deleteHandler = async ({ ctx, input }: DeleteOptions) => { if (!(await isTeamOwner(ctx.user?.id, input.teamId))) throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -73,12 +38,10 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => { const deletedTeamMetadata = teamMetadataSchema.parse(deletedTeam.metadata); - if (IS_PRODUCTION) - deleteVercelDomain({ - slug: deletedTeam.slug, - isOrganization: deletedTeamMetadata?.isOrganization, - }); + if (deletedTeamMetadata?.isOrganization && deletedTeam.slug) deleteDomain(deletedTeam.slug); // Sync Services: Close.cm closeComDeleteTeam(deletedTeam); }; + +export default deleteHandler; diff --git a/packages/trpc/server/routers/viewer/teams/deleteInvite.handler.ts b/packages/trpc/server/routers/viewer/teams/deleteInvite.handler.ts index 5a92545dcb..78d2c9ca20 100644 --- a/packages/trpc/server/routers/viewer/teams/deleteInvite.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/deleteInvite.handler.ts @@ -31,3 +31,5 @@ export const deleteInviteHandler = async ({ ctx, input }: DeleteInviteOptions) = await prisma.verificationToken.delete({ where: { id: verificationToken.id } }); }; + +export default deleteInviteHandler; diff --git a/packages/trpc/server/routers/viewer/teams/get.handler.ts b/packages/trpc/server/routers/viewer/teams/get.handler.ts index 6cc6c52db5..bed27cda9b 100644 --- a/packages/trpc/server/routers/viewer/teams/get.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/get.handler.ts @@ -1,6 +1,5 @@ import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; -import type { MembershipRole } from "@calcom/prisma/enums"; import { TRPCError } from "@trpc/server"; @@ -27,12 +26,18 @@ export const getHandler = async ({ ctx, input }: GetOptions) => { const membership = team?.members.find((membership) => membership.id === ctx.user.id); + if (!membership) { + throw new TRPCError({ code: "NOT_FOUND", message: "Not a member of this team." }); + } + return { ...team, safeBio: markdownToSafeHTML(team.bio), membership: { - role: membership?.role as MembershipRole, - accepted: membership?.accepted, + role: membership.role, + accepted: membership.accepted, }, }; }; + +export default getHandler; diff --git a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts index 3545e6e321..0d7922a0dc 100644 --- a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts @@ -54,3 +54,5 @@ export const getMemberAvailabilityHandler = async ({ ctx, input }: GetMemberAvai { user: member.user } ); }; + +export default getMemberAvailabilityHandler; diff --git a/packages/trpc/server/routers/viewer/teams/getMembershipbyUser.handler.ts b/packages/trpc/server/routers/viewer/teams/getMembershipbyUser.handler.ts index 55794cb47f..eeedb362aa 100644 --- a/packages/trpc/server/routers/viewer/teams/getMembershipbyUser.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/getMembershipbyUser.handler.ts @@ -29,3 +29,5 @@ export const getMembershipbyUserHandler = async ({ ctx, input }: GetMembershipby }, }); }; + +export default getMembershipbyUserHandler; diff --git a/packages/trpc/server/routers/viewer/teams/getUpgradeable.handler.ts b/packages/trpc/server/routers/viewer/teams/getUpgradeable.handler.ts index 7587867e34..941c77a11e 100644 --- a/packages/trpc/server/routers/viewer/teams/getUpgradeable.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/getUpgradeable.handler.ts @@ -43,3 +43,5 @@ export const getUpgradeableHandler = async ({ ctx }: GetUpgradeableOptions) => { }); return teams; }; + +export default getUpgradeableHandler; diff --git a/packages/trpc/server/routers/viewer/teams/hasEditPermissionForUser.handler.ts b/packages/trpc/server/routers/viewer/teams/hasEditPermissionForUser.handler.ts index 882e922e30..ae5b9bc66c 100644 --- a/packages/trpc/server/routers/viewer/teams/hasEditPermissionForUser.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/hasEditPermissionForUser.handler.ts @@ -17,3 +17,5 @@ export const hasEditPermissionForUser = async ({ ctx, input }: HasEditPermission input, }); }; + +export default hasEditPermissionForUser; diff --git a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts index 882876515c..f8c757b624 100644 --- a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts @@ -23,3 +23,5 @@ export const hasTeamPlanHandler = async ({ ctx }: HasTeamPlanOptions) => { }); return { hasTeamPlan: !!hasTeamPlan }; }; + +export default hasTeamPlanHandler; diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts index deb8a55019..114c9fca37 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts @@ -2,9 +2,13 @@ import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { isOrganisationOwner } from "@calcom/lib/server/queries/organisations"; import { prisma } from "@calcom/prisma"; +import { MembershipRole } from "@calcom/prisma/enums"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; +import { TRPCError } from "@trpc/server"; + import type { TInviteMemberInputSchema } from "./inviteMember.schema"; import { checkPermissions, @@ -40,6 +44,14 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = isOrg: input.isOrg, }); + // Only owners can award owner role in an organization. + if ( + input.isOrg && + input.role === MembershipRole.OWNER && + !(await isOrganisationOwner(ctx.user.id, input.teamId)) + ) + throw new TRPCError({ code: "UNAUTHORIZED" }); + const team = await getTeamOrThrow(input.teamId, input.isOrg); const { autoAcceptEmailDomain, orgVerified } = getIsOrgVerified(input.isOrg, team); const usernameOrEmailsToInvite = await getUsernameOrEmailsToInvite(input.usernameOrEmail); @@ -105,12 +117,28 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = // invited users can autojoin, create their memberships in org if (autoJoinUsers.length) { await prisma.membership.createMany({ - data: autoJoinUsers.map((userToAutoJoin) => ({ - userId: userToAutoJoin.id, - teamId: team.id, - accepted: true, - role: input.role, - })), + data: autoJoinUsers.map((userToAutoJoin) => { + const organizationRole = userToAutoJoin.teams?.[0]?.role; + return { + userId: userToAutoJoin.id, + teamId: team.id, + accepted: true, + role: + organizationRole === MembershipRole.ADMIN || organizationRole === MembershipRole.OWNER + ? organizationRole + : input.role, + }; + }), + }); + + await sendTeamInviteEmails({ + currentUserName: ctx?.user?.name, + currentUserTeamName: team?.name, + existingUsersWithMembersips: autoJoinUsers, + language: translation, + isOrg: input.isOrg, + teamId: team.id, + currentUserParentTeamName: team?.parent?.name, }); } @@ -127,6 +155,7 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = language: translation, isOrg: input.isOrg, teamId: team.id, + currentUserParentTeamName: team?.parent?.name, }); } } @@ -140,3 +169,5 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = } return input; }; + +export default inviteMemberHandler; diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts index 19f354a59e..f081508bc5 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { MAX_NB_INVITES } from "@calcom/lib/constants"; import { MembershipRole } from "@calcom/prisma/enums"; export const ZInviteMemberInputSchema = z.object({ @@ -15,13 +16,13 @@ export const ZInviteMemberInputSchema = z.object({ .refine( (value) => { if (Array.isArray(value)) { - if (value.length > 100) { + if (value.length > MAX_NB_INVITES) { return false; } } return true; }, - { message: "You are limited to inviting a maximum of 100 users at once." } + { message: `You are limited to inviting a maximum of ${MAX_NB_INVITES} users at once.` } ) .refine( (value) => { diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index 1279996cb5..5768055791 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -10,7 +10,7 @@ import slugify from "@calcom/lib/slugify"; import { prisma } from "@calcom/prisma"; import type { Membership, Team } from "@calcom/prisma/client"; import { Prisma, type User } from "@calcom/prisma/client"; -import type { MembershipRole } from "@calcom/prisma/enums"; +import { MembershipRole } from "@calcom/prisma/enums"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { TRPCError } from "@trpc/server"; @@ -25,7 +25,7 @@ export type Invitee = Pick< >; export type UserWithMembership = Invitee & { - teams?: Pick<Membership, "userId" | "teamId" | "accepted">[]; + teams?: Pick<Membership, "userId" | "teamId" | "accepted" | "role">[]; }; export async function checkPermissions({ @@ -168,7 +168,7 @@ export async function getUsersToInvite({ completedOnboarding: true, identityProvider: true, teams: { - select: { teamId: true, userId: true, accepted: true }, + select: { teamId: true, userId: true, accepted: true, role: true }, where: { OR: memberships, }, @@ -286,12 +286,16 @@ export async function createProvisionalMemberships({ try { await prisma.membership.createMany({ data: invitees.flatMap((invitee) => { + const organizationRole = invitee?.teams?.[0]?.role; const data = []; // membership for the team data.push({ teamId: input.teamId, userId: invitee.id, - role: input.role as MembershipRole, + role: + organizationRole === MembershipRole.ADMIN || organizationRole === MembershipRole.OWNER + ? organizationRole + : input.role, }); // membership for the org @@ -364,10 +368,11 @@ export async function sendVerificationEmail({ language: translation, from: ctx.user.name || `${team.name}'s admin`, to: usernameOrEmail, - teamName: team?.parent?.name || team.name, + teamName: team.name, joinLink: `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/getting-started`, isCalcomMember: false, isOrg: input.isOrg, + parentTeamName: team?.parent?.name, }); } else { await sendOrganizationAutoJoinEmail({ @@ -474,12 +479,14 @@ export const sendTeamInviteEmails = async ({ language, currentUserTeamName, currentUserName, + currentUserParentTeamName, isOrg, teamId, }: { language: TFunction; existingUsersWithMembersips: UserWithMembership[]; currentUserTeamName?: string; + currentUserParentTeamName: string | undefined; currentUserName?: string | null; isOrg: boolean; teamId: number; @@ -525,6 +532,7 @@ export const sendTeamInviteEmails = async ({ teamName: currentUserTeamName, ...inviteTeamOptions, isOrg: isOrg, + parentTeamName: currentUserParentTeamName, }); } }); diff --git a/packages/trpc/server/routers/viewer/teams/inviteMemberByToken.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMemberByToken.handler.ts index 5518a18ec1..5f1d533c31 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMemberByToken.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMemberByToken.handler.ts @@ -64,3 +64,5 @@ export const inviteMemberByTokenHandler = async ({ ctx, input }: InviteMemberByT return verificationToken.team.name; }; + +export default inviteMemberByTokenHandler; diff --git a/packages/trpc/server/routers/viewer/teams/list.handler.ts b/packages/trpc/server/routers/viewer/teams/list.handler.ts index 44220804aa..23a29411ff 100644 --- a/packages/trpc/server/routers/viewer/teams/list.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/list.handler.ts @@ -22,6 +22,7 @@ export const listHandler = async ({ ctx }: ListOptions) => { team: { include: { inviteTokens: true, + parent: true, }, }, }, @@ -41,3 +42,5 @@ export const listHandler = async ({ ctx }: ListOptions) => { inviteToken: inviteTokens.find((token) => token.identifier === `invite-link-for-teamId-${_team.id}`), })); }; + +export default listHandler; diff --git a/packages/trpc/server/routers/viewer/teams/listInvites.handler.ts b/packages/trpc/server/routers/viewer/teams/listInvites.handler.ts index d4ffed6217..5d266f692b 100644 --- a/packages/trpc/server/routers/viewer/teams/listInvites.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/listInvites.handler.ts @@ -18,3 +18,5 @@ export const listInvitesHandler = async ({ ctx }: ListInvitesOptions) => { }, }); }; + +export default listInvitesHandler; diff --git a/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts b/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts index 29291c5723..8def528dca 100644 --- a/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/listMembers.handler.ts @@ -54,3 +54,5 @@ export const listMembersHandler = async ({ ctx, input }: ListMembersOptions) => return Object.values(users); }; + +export default listMembersHandler; diff --git a/packages/trpc/server/routers/viewer/teams/publish.handler.ts b/packages/trpc/server/routers/viewer/teams/publish.handler.ts index 5bb5dbb195..48e2e6e781 100644 --- a/packages/trpc/server/routers/viewer/teams/publish.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/publish.handler.ts @@ -157,3 +157,5 @@ export const publishHandler = async ({ ctx, input }: PublishOptions) => { message: "Team published successfully", }; }; + +export default publishHandler; diff --git a/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts b/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts index 96b7b3cc9d..b3171438bd 100644 --- a/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts @@ -1,4 +1,5 @@ import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments"; +import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { isTeamAdmin, isTeamOwner } from "@calcom/lib/server/queries/teams"; import { closeComDeleteTeamMembership } from "@calcom/lib/sync/SyncServiceManager"; @@ -14,11 +15,16 @@ type RemoveMemberOptions = { ctx: { user: NonNullable<TrpcSessionUser>; prisma: PrismaClient; + sourceIp?: string; }; input: TRemoveMemberInputSchema; }; export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) => { + await checkRateLimitAndThrowError({ + identifier: `removeMember.${ctx.sourceIp}`, + }); + const isAdmin = await isTeamAdmin(ctx.user.id, input.teamId); const isOrgAdmin = ctx.user.organizationId ? await isTeamAdmin(ctx.user.id, ctx.user.organizationId) @@ -121,3 +127,5 @@ export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) = closeComDeleteTeamMembership(membership.user); if (IS_TEAM_BILLING_ENABLED) await updateQuantitySubscriptionFromStripe(input.teamId); }; + +export default removeMemberHandler; diff --git a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts index a7c0cc9bd5..a3390fb8eb 100644 --- a/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/resendInvitation.handler.ts @@ -51,10 +51,13 @@ export const resendInvitationHandler = async ({ ctx, input }: InviteMemberOption language: translation, from: ctx.user.name || `${team.name}'s admin`, to: input.email, - teamName: team?.parent?.name || team.name, + teamName: team.name, ...inviteTeamOptions, isOrg: input.isOrg, + parentTeamName: team?.parent?.name, }); return input; }; + +export default resendInvitationHandler; diff --git a/packages/trpc/server/routers/viewer/teams/setInviteExpiration.handler.ts b/packages/trpc/server/routers/viewer/teams/setInviteExpiration.handler.ts index cbba00e2f1..28d0ba4676 100644 --- a/packages/trpc/server/routers/viewer/teams/setInviteExpiration.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/setInviteExpiration.handler.ts @@ -39,3 +39,5 @@ export const setInviteExpirationHandler = async ({ ctx, input }: SetInviteExpira }, }); }; + +export default setInviteExpirationHandler; diff --git a/packages/trpc/server/routers/viewer/teams/update.handler.ts b/packages/trpc/server/routers/viewer/teams/update.handler.ts index ff5692f0ac..bcb9fbce9e 100644 --- a/packages/trpc/server/routers/viewer/teams/update.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/update.handler.ts @@ -97,3 +97,5 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { darkBrandColor: updatedTeam.darkBrandColor, }; }; + +export default updateHandler; diff --git a/packages/trpc/server/routers/viewer/teams/updateMembership.handler.ts b/packages/trpc/server/routers/viewer/teams/updateMembership.handler.ts index 9a1b1cac9d..4b42b4a53f 100644 --- a/packages/trpc/server/routers/viewer/teams/updateMembership.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/updateMembership.handler.ts @@ -32,3 +32,5 @@ export const updateMembershipHandler = async ({ ctx, input }: UpdateMembershipOp }, }); }; + +export default updateMembershipHandler; diff --git a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts index c38c7d446b..e0a60bd8b9 100644 --- a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts +++ b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts @@ -1,4 +1,4 @@ -import { getBookerUrl } from "@calcom/lib/server/getBookerUrl"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import { prisma } from "@calcom/prisma"; import type { Webhook } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -39,6 +39,15 @@ export type WebhooksByViewer = { }[]; }; +const filterWebhooks = (webhook: Webhook) => { + const appIds = [ + "zapier", + // Add more if needed + ]; + + return !appIds.some((appId: string) => webhook.appId == appId); +}; + export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => { const user = await prisma.user.findUnique({ where: { @@ -80,9 +89,10 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); } - const userWebhooks = user.webhooks; + let userWebhooks = user.webhooks; + userWebhooks = userWebhooks.filter(filterWebhooks); let webhookGroups: WebhookGroup[] = []; - const bookerUrl = await getBookerUrl(user); + const bookerUrl = await getBookerBaseUrl(user); const image = user?.username ? `${bookerUrl}/${user.username}/avatar.png` : undefined; webhookGroups.push({ @@ -132,7 +142,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => { : MembershipRole.MEMBER : MembershipRole.MEMBER), }, - webhooks: membership.team.webhooks, + webhooks: membership.team.webhooks.filter(filterWebhooks), }; }); diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index a8f8c0e861..1068ab3fb4 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -10,7 +10,6 @@ import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder, } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; -import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/client"; @@ -197,54 +196,54 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType break; } - await scheduleEmailReminder( - bookingInfo, - eventTypeWorkflow.trigger, - step.action, - { + await scheduleEmailReminder({ + evt: bookingInfo, + triggerEvent: eventTypeWorkflow.trigger, + action: step.action, + timeSpan: { time: eventTypeWorkflow.time, timeUnit: eventTypeWorkflow.timeUnit, }, sendTo, - step.emailSubject || "", - step.reminderBody || "", - step.id, - step.template, - step.sender || SENDER_NAME - ); + emailSubject: step.emailSubject || "", + emailBody: step.reminderBody || "", + template: step.template, + sender: step.sender, + workflowStepId: step.id, + }); } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { - await scheduleSMSReminder( - bookingInfo, - step.sendTo, - eventTypeWorkflow.trigger, - step.action, - { + await scheduleSMSReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo, + triggerEvent: eventTypeWorkflow.trigger, + action: step.action, + timeSpan: { time: eventTypeWorkflow.time, timeUnit: eventTypeWorkflow.timeUnit, }, - step.reminderBody || "", - step.id, - step.template, - step.sender || SENDER_ID, - booking.userId, - eventTypeWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: step.id, + template: step.template, + sender: step.sender, + userId: booking.userId, + teamId: eventTypeWorkflow.teamId, + }); } else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) { - await scheduleWhatsappReminder( - bookingInfo, - step.sendTo, - eventTypeWorkflow.trigger, - step.action, - { + await scheduleWhatsappReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo, + triggerEvent: eventTypeWorkflow.trigger, + action: step.action, + timeSpan: { time: eventTypeWorkflow.time, timeUnit: eventTypeWorkflow.timeUnit, }, - step.reminderBody || "", - step.id, - step.template, - booking.userId, - eventTypeWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: step.id, + template: step.template, + userId: booking.userId, + teamId: eventTypeWorkflow.teamId, + }); } } } diff --git a/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx b/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx index 3bb74fbb1f..0f28959ada 100644 --- a/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx +++ b/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx @@ -1,3 +1,5 @@ +"use client"; + import { z } from "zod"; import { filterQuerySchemaStrict } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery"; diff --git a/packages/trpc/server/routers/viewer/workflows/update.handler.ts b/packages/trpc/server/routers/viewer/workflows/update.handler.ts index 5799f96756..f449a86f99 100644 --- a/packages/trpc/server/routers/viewer/workflows/update.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/update.handler.ts @@ -17,7 +17,7 @@ import { deleteScheduledWhatsappReminder, scheduleWhatsappReminder, } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; -import { IS_SELF_HOSTED, SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; +import { IS_SELF_HOSTED } from "@calcom/lib/constants"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import type { PrismaClient } from "@calcom/prisma"; @@ -262,7 +262,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }, }); - const promiseSteps = steps.map(async (step) => { + const promiseSteps = userWorkflow.steps.map(async (step) => { if ( step.action !== WorkflowActions.SMS_ATTENDEE && step.action !== WorkflowActions.WHATSAPP_ATTENDEE @@ -315,54 +315,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { sendTo = step.sendTo || "";*/ } - await scheduleEmailReminder( - bookingInfo, - trigger, - step.action, - { + await scheduleEmailReminder({ + evt: bookingInfo, + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, sendTo, - step.emailSubject || "", - step.reminderBody || "", - step.id, - step.template, - step.senderName || SENDER_NAME - ); + emailSubject: step.emailSubject || "", + emailBody: step.reminderBody || "", + template: step.template, + sender: step.sender, + workflowStepId: step.id, + }); } else if (step.action === WorkflowActions.SMS_NUMBER) { - await scheduleSMSReminder( - bookingInfo, - step.sendTo || "", - trigger, - step.action, - { + await scheduleSMSReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo || "", + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, - step.reminderBody || "", - step.id, - step.template, - step.sender || SENDER_ID, - user.id, - userWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: step.id, + template: step.template, + sender: step.sender, + userId: user.id, + teamId: userWorkflow.teamId, + }); } else if (step.action === WorkflowActions.WHATSAPP_NUMBER) { - await scheduleWhatsappReminder( - bookingInfo, - step.sendTo || "", - trigger, - step.action, - { + await scheduleWhatsappReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo || "", + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, - step.reminderBody || "", - step.id || 0, - step.template, - user.id, - userWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: step.id || 0, + template: step.template, + userId: user.id, + teamId: userWorkflow.teamId, + }); } }); await Promise.all(promiseScheduleReminders); @@ -552,54 +552,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { sendTo = newStep.sendTo || "";*/ } - await scheduleEmailReminder( - bookingInfo, - trigger, - newStep.action, - { + await scheduleEmailReminder({ + evt: bookingInfo, + triggerEvent: trigger, + action: newStep.action, + timeSpan: { time, timeUnit, }, sendTo, - newStep.emailSubject || "", - newStep.reminderBody || "", - newStep.id, - newStep.template, - newStep.senderName || SENDER_NAME - ); + emailSubject: newStep.emailSubject || "", + emailBody: newStep.reminderBody || "", + template: newStep.template, + sender: newStep.senderName, + workflowStepId: newStep.id, + }); } else if (newStep.action === WorkflowActions.SMS_NUMBER) { - await scheduleSMSReminder( - bookingInfo, - newStep.sendTo || "", - trigger, - newStep.action, - { + await scheduleSMSReminder({ + evt: bookingInfo, + reminderPhone: newStep.sendTo || "", + triggerEvent: trigger, + action: newStep.action, + timeSpan: { time, timeUnit, }, - newStep.reminderBody || "", - newStep.id || 0, - newStep.template, - newStep.sender || SENDER_ID, - user.id, - userWorkflow.teamId - ); + message: newStep.reminderBody || "", + workflowStepId: newStep.id || 0, + template: newStep.template, + sender: newStep.sender, + userId: user.id, + teamId: userWorkflow.teamId, + }); } else if (newStep.action === WorkflowActions.WHATSAPP_NUMBER) { - await scheduleWhatsappReminder( - bookingInfo, - newStep.sendTo || "", - trigger, - newStep.action, - { + await scheduleWhatsappReminder({ + evt: bookingInfo, + reminderPhone: newStep.sendTo || "", + triggerEvent: trigger, + action: newStep.action, + timeSpan: { time, timeUnit, }, - newStep.reminderBody || "", - newStep.id || 0, - newStep.template, - user.id, - userWorkflow.teamId - ); + message: newStep.reminderBody || "", + workflowStepId: newStep.id || 0, + template: newStep.template, + userId: user.id, + teamId: userWorkflow.teamId, + }); } }); await Promise.all(promiseScheduleReminders); @@ -619,11 +619,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { return stepToAdd; } }); - if (addedSteps) { - const eventTypesToCreateReminders = activeOn.filter( - (activeEventType) => activeEventType && !newEventTypes.includes(activeEventType) - ); const promiseAddedSteps = addedSteps.map(async (step) => { if (step) { const { senderName, ...newStep } = step; @@ -637,13 +633,12 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }); if ( (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) && - eventTypesToCreateReminders && step.action !== WorkflowActions.SMS_ATTENDEE && step.action !== WorkflowActions.WHATSAPP_ATTENDEE ) { const bookingsForReminders = await ctx.prisma.booking.findMany({ where: { - eventTypeId: { in: eventTypesToCreateReminders as number[] }, + eventTypeId: { in: activeOn }, status: BookingStatus.ACCEPTED, startTime: { gte: new Date(), @@ -703,54 +698,54 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { sendTo = step.sendTo || "";*/ } - await scheduleEmailReminder( - bookingInfo, - trigger, - step.action, - { + await scheduleEmailReminder({ + evt: bookingInfo, + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, sendTo, - step.emailSubject || "", - step.reminderBody || "", - createdStep.id, - step.template, - step.senderName || SENDER_NAME - ); + emailSubject: step.emailSubject || "", + emailBody: step.reminderBody || "", + template: step.template, + sender: step.senderName, + workflowStepId: createdStep.id, + }); } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) { - await scheduleSMSReminder( - bookingInfo, - step.sendTo, - trigger, - step.action, - { + await scheduleSMSReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo, + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, - step.reminderBody || "", - createdStep.id, - step.template, - step.sender || SENDER_ID, - user.id, - userWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: createdStep.id, + template: step.template, + sender: step.sender, + userId: user.id, + teamId: userWorkflow.teamId, + }); } else if (step.action === WorkflowActions.WHATSAPP_NUMBER && step.sendTo) { - await scheduleWhatsappReminder( - bookingInfo, - step.sendTo, - trigger, - step.action, - { + await scheduleWhatsappReminder({ + evt: bookingInfo, + reminderPhone: step.sendTo, + triggerEvent: trigger, + action: step.action, + timeSpan: { time, timeUnit, }, - step.reminderBody || "", - createdStep.id, - step.template, - user.id, - userWorkflow.teamId - ); + message: step.reminderBody || "", + workflowStepId: createdStep.id, + template: step.template, + userId: user.id, + teamId: userWorkflow.teamId, + }); } } } diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index 833855250c..811cfc923d 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -185,6 +185,7 @@ export interface CalendarEvent { seatsPerTimeSlot?: number | null; schedulingType?: SchedulingType | null; iCalUID?: string | null; + iCalSequence?: number | null; // It has responses to all the fields(system + user) responses?: CalEventResponses | null; diff --git a/packages/types/VideoApiAdapter.d.ts b/packages/types/VideoApiAdapter.d.ts index cc1dfd109e..45553f6cc4 100644 --- a/packages/types/VideoApiAdapter.d.ts +++ b/packages/types/VideoApiAdapter.d.ts @@ -24,6 +24,8 @@ export type VideoApiAdapter = getRecordings?(roomName: string): Promise<GetRecordingsResponseSchema>; getRecordingDownloadLink?(recordingId: string): Promise<GetAccessLinkResponseSchema>; + + createInstantCalVideoRoom?(endTime: string): Promise<VideoCallData>; } | undefined; diff --git a/packages/ui/components/avatar/AvatarGroup.tsx b/packages/ui/components/avatar/AvatarGroup.tsx index ca8cb95606..113886e67b 100644 --- a/packages/ui/components/avatar/AvatarGroup.tsx +++ b/packages/ui/components/avatar/AvatarGroup.tsx @@ -31,6 +31,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) { {displayedAvatars.map((item, idx) => ( <li key={idx} className="-mr-[4px] inline-block"> <Avatar + data-testid="avatar" className="border-subtle" imageSrc={item.image} title={item.title} diff --git a/packages/ui/components/avatar/UserAvatar.test.tsx b/packages/ui/components/avatar/UserAvatar.test.tsx new file mode 100644 index 0000000000..e41393a337 --- /dev/null +++ b/packages/ui/components/avatar/UserAvatar.test.tsx @@ -0,0 +1,37 @@ +/* eslint-disable playwright/missing-playwright-await */ +import { render } from "@testing-library/react"; + +import { AVATAR_FALLBACK } from "@calcom/lib/constants"; + +import { UserAvatar } from "./UserAvatar"; + +const mockUser = { + name: "John Doe", + username: "pro", + organizationId: null, +}; + +describe("tests for UserAvatar component", () => { + test("Should render the UsersAvatar Correctly", () => { + const { getByTestId } = render(<UserAvatar user={mockUser} data-testid="user-avatar-test" />); + const avatar = getByTestId("user-avatar-test"); + + expect(avatar).toBeInTheDocument(); + }); + + test("It should render the organization logo if a organization is passed in", () => { + const { getByTestId } = render( + <UserAvatar + user={mockUser} + organization={{ id: -1, requestedSlug: "steve", slug: "steve", logoUrl: AVATAR_FALLBACK }} + data-testid="user-avatar-test" + /> + ); + + const avatar = getByTestId("user-avatar-test"); + const organizationLogo = getByTestId("organization-logo"); + + expect(avatar).toBeInTheDocument(); + expect(organizationLogo).toBeInTheDocument(); + }); +}); diff --git a/packages/ui/components/avatar/UserAvatar.tsx b/packages/ui/components/avatar/UserAvatar.tsx new file mode 100644 index 0000000000..cc38e76020 --- /dev/null +++ b/packages/ui/components/avatar/UserAvatar.tsx @@ -0,0 +1,54 @@ +import { classNames } from "@calcom/lib"; +import { getOrgAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; +import type { User } from "@calcom/prisma/client"; +import { Avatar } from "@calcom/ui"; + +type Organization = { + id: number; + slug: string | null; + requestedSlug: string | null; + logoUrl?: string; +}; + +type UserAvatarProps = Omit<React.ComponentProps<typeof Avatar>, "alt" | "imageSrc"> & { + user: Pick<User, "organizationId" | "name" | "username">; + /** + * Useful when allowing the user to upload their own avatar and showing the avatar before it's uploaded + */ + previewSrc?: string | null; + organization?: Organization | null; + alt?: string | null; +}; + +function OrganizationIndicator({ + size, + organization, + user, +}: Pick<UserAvatarProps, "size" | "user"> & { organization: Organization }) { + const organizationUrl = organization.logoUrl ?? getOrgAvatarUrl(organization); + return ( + <div className={classNames("absolute bottom-0 right-0 z-10", size === "lg" ? "h-6 w-6" : "h-10 w-10")}> + <img + data-testId="organization-logo" + src={organizationUrl} + alt={user.username || ""} + className="flex h-full items-center justify-center rounded-full" + /> + </div> + ); +} + +/** + * It is aware of the user's organization to correctly show the avatar from the correct URL + */ +export function UserAvatar(props: UserAvatarProps) { + const { user, previewSrc = getUserAvatarUrl(user), ...rest } = props; + + const indicator = props.organization ? ( + <OrganizationIndicator size={props.size} organization={props.organization} user={props.user} /> + ) : ( + props.indicator + ); + + return <Avatar {...rest} alt={user.name || "Nameless User"} imageSrc={previewSrc} indicator={indicator} />; +} diff --git a/packages/ui/components/avatar/index.ts b/packages/ui/components/avatar/index.ts index d3908f8bc5..a24a507aae 100644 --- a/packages/ui/components/avatar/index.ts +++ b/packages/ui/components/avatar/index.ts @@ -1,4 +1,5 @@ export { Avatar } from "./Avatar"; +export { UserAvatar } from "./UserAvatar"; export type { AvatarProps } from "./Avatar"; export { AvatarGroup } from "./AvatarGroup"; export type { AvatarGroupProps } from "./AvatarGroup"; diff --git a/packages/ui/components/badge/badge.stories.mdx b/packages/ui/components/badge/badge.stories.mdx index f456c3cb97..03268a037e 100644 --- a/packages/ui/components/badge/badge.stories.mdx +++ b/packages/ui/components/badge/badge.stories.mdx @@ -15,7 +15,7 @@ import { Badge } from "./Badge"; <Meta title="UI/Badge" component={Badge} /> -<Title title="Bages" suffix="Brief" subtitle="Version 2.0 — Last Update: 22 Aug 2022" /> +<Title title="Badge" suffix="Brief" subtitle="Version 2.0 — Last Update: 22 Aug 2022" /> ## Definition diff --git a/packages/ui/components/breadcrumb/breadcrumb.stories.mdx b/packages/ui/components/breadcrumb/breadcrumb.stories.mdx index 8fbd56164d..25fddfa8d1 100644 --- a/packages/ui/components/breadcrumb/breadcrumb.stories.mdx +++ b/packages/ui/components/breadcrumb/breadcrumb.stories.mdx @@ -2,7 +2,7 @@ import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; import { Examples, Example, Note, Title, CustomArgsTable } from "@calcom/storybook/components"; -import { Breadcrumb } from "./Breadcrumb"; +import { Breadcrumb, BreadcrumbItem } from "./Breadcrumb"; <Meta title="UI/Breadcrumbs" component={Breadcrumb} /> diff --git a/packages/ui/components/button/Button.tsx b/packages/ui/components/button/Button.tsx index 17f0d41642..5306f6e3ae 100644 --- a/packages/ui/components/button/Button.tsx +++ b/packages/ui/components/button/Button.tsx @@ -50,7 +50,7 @@ export const buttonClasses = cva( primary: "bg-brand-default hover:bg-brand-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-brand-default text-brand disabled:bg-brand-subtle disabled:text-brand-subtle disabled:opacity-40 disabled:hover:bg-brand-subtle disabled:hover:text-brand-default disabled:hover:opacity-40", secondary: - "text-emphasis border border-default bg-default hover:bg-muted hover:border-emphasis focus-visible:bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-empthasis disabled:border-subtle disabled:bg-opacity-30 disabled:text-muted disabled:hover:bg-opacity-30 disabled:hover:text-muted disabled:hover:border-subtle disabled:hover:bg-default", + "text-emphasis border border-default bg-default hover:bg-muted hover:border-emphasis focus-visible:bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-empthasis disabled:border-subtle disabled:bg-opacity-30 disabled:text-muted disabled:hover:bg-opacity-30 disabled:hover:text-muted disabled:hover:border-subtle disabled:hover:bg-default", minimal: "text-emphasis hover:bg-subtle focus-visible:bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset focus-visible:ring-empthasis disabled:border-subtle disabled:bg-opacity-30 disabled:text-muted disabled:hover:bg-transparent disabled:hover:text-muted disabled:hover:border-subtle", destructive: diff --git a/packages/ui/components/createButton/CreateButton.tsx b/packages/ui/components/createButton/CreateButton.tsx index 51b4a3c973..eb4818983a 100644 --- a/packages/ui/components/createButton/CreateButton.tsx +++ b/packages/ui/components/createButton/CreateButton.tsx @@ -1,6 +1,5 @@ import { usePathname, useRouter } from "next/navigation"; -import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { ButtonColor } from "@calcom/ui"; @@ -19,7 +18,7 @@ import { Plus } from "@calcom/ui/components/icon"; export interface Option { teamId: number | null | undefined; // if undefined, then it's a profile label: string | null; - image?: string | null; + image: string | null; slug: string | null; } @@ -43,7 +42,6 @@ export function CreateButton(props: CreateBtnProps) { const router = useRouter(); const searchParams = useCompatSearchParams(); const pathname = usePathname(); - const bookerUrl = useBookerUrl(); const { createDialog, @@ -114,12 +112,7 @@ export function CreateButton(props: CreateBtnProps) { type="button" data-testid={`option${option.teamId ? "-team" : ""}-${idx}`} StartIcon={(props) => ( - <Avatar - alt={option.label || ""} - imageSrc={option.image || `${bookerUrl}/${option.label}/avatar.png`} // if no image, use default avatar - size="sm" - {...props} - /> + <Avatar alt={option.label || ""} imageSrc={option.image} size="sm" {...props} /> )} onClick={() => !!CreateDialog diff --git a/packages/ui/components/data-table/DataTableSelectionBar.tsx b/packages/ui/components/data-table/DataTableSelectionBar.tsx index 26c7f6797d..04f738bc8d 100644 --- a/packages/ui/components/data-table/DataTableSelectionBar.tsx +++ b/packages/ui/components/data-table/DataTableSelectionBar.tsx @@ -1,4 +1,5 @@ import type { Table } from "@tanstack/react-table"; +import { motion, AnimatePresence } from "framer-motion"; import { Fragment } from "react"; import type { SVGComponent } from "@calcom/types/SVGComponent"; @@ -24,23 +25,30 @@ interface DataTableSelectionBarProps<TData> { export function DataTableSelectionBar<TData>({ table, actions }: DataTableSelectionBarProps<TData>) { const numberOfSelectedRows = table.getSelectedRowModel().rows.length; - - if (numberOfSelectedRows === 0) return null; + const isVisible = numberOfSelectedRows > 0; return ( - <div className="bg-brand-default text-brand item-center absolute bottom-0 left-1/2 flex -translate-x-1/2 gap-4 rounded-lg p-2"> - <div className="text-brand-subtle my-auto px-2">{numberOfSelectedRows} selected</div> - {actions?.map((action, index) => ( - <Fragment key={index}> - {action.type === "action" ? ( - <Button aria-label={action.label} onClick={action.onClick} StartIcon={action.icon}> - {action.label} - </Button> - ) : action.type === "render" ? ( - action.render(table) - ) : null} - </Fragment> - ))} - </div> + <AnimatePresence> + {isVisible ? ( + <motion.div + initial={{ opacity: 0, y: 0 }} + animate={{ opacity: 1, y: 20 }} + exit={{ opacity: 0, y: 0 }} + className="bg-brand-default text-brand item-center fixed bottom-6 left-1/4 hidden gap-4 rounded-lg p-2 md:flex lg:left-1/2"> + <div className="text-brand-subtle my-auto px-2">{numberOfSelectedRows} selected</div> + {actions?.map((action, index) => ( + <Fragment key={index}> + {action.type === "action" ? ( + <Button aria-label={action.label} onClick={action.onClick} StartIcon={action.icon}> + {action.label} + </Button> + ) : action.type === "render" ? ( + action.render(table) + ) : null} + </Fragment> + ))} + </motion.div> + ) : null} + </AnimatePresence> ); } diff --git a/packages/ui/components/data-table/DataTableToolbar.tsx b/packages/ui/components/data-table/DataTableToolbar.tsx index df3370bed8..e5d6f6e9c5 100644 --- a/packages/ui/components/data-table/DataTableToolbar.tsx +++ b/packages/ui/components/data-table/DataTableToolbar.tsx @@ -4,6 +4,8 @@ import type { Table } from "@tanstack/react-table"; import type { LucideIcon } from "lucide-react"; import { X } from "lucide-react"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; + import { Button } from "../button"; import { Input } from "../form"; import { DataTableFilter } from "./DataTableFilter"; @@ -35,14 +37,16 @@ export function DataTableToolbar<TData>({ // If you select ALL filters for a column, the table is not filtered and we dont get a reset button const isFiltered = table.getState().columnFilters.length > 0; + const { t } = useLocale(); + return ( - <div className="bg-default sticky top-[3rem] z-10 flex items-center justify-end space-x-2 py-4 md:top-0"> + <div className="flex items-center justify-end space-x-2 py-4"> {searchKey && ( <Input className="max-w-64 mb-0 mr-auto rounded-md" placeholder="Search" value={(table.getColumn(searchKey)?.getFilterValue() as string) ?? ""} - onChange={(event) => table.getColumn(searchKey)?.setFilterValue(event.target.value)} + onChange={(event) => table.getColumn(searchKey)?.setFilterValue(event.target.value.trim())} /> )} {isFiltered && ( @@ -51,7 +55,7 @@ export function DataTableToolbar<TData>({ EndIcon={X} onClick={() => table.resetColumnFilters()} className="h-8 px-2 lg:px-3"> - Reset + {t("clear")} </Button> )} diff --git a/packages/ui/components/data-table/index.tsx b/packages/ui/components/data-table/index.tsx index 5a41ebcf4e..7f36f10ea9 100644 --- a/packages/ui/components/data-table/index.tsx +++ b/packages/ui/components/data-table/index.tsx @@ -38,6 +38,7 @@ export interface DataTableProps<TData, TValue> { onScroll?: (e: React.UIEvent<HTMLDivElement, UIEvent>) => void; CTA?: React.ReactNode; tableOverlay?: React.ReactNode; + variant?: "default" | "compact"; } export function DataTable<TData, TValue>({ @@ -50,6 +51,7 @@ export function DataTable<TData, TValue>({ tableContainerRef, isLoading, tableOverlay, + variant, /** This should only really be used if you dont have actions in a row. */ onRowMouseclick, onScroll, @@ -95,14 +97,14 @@ export function DataTable<TData, TValue>({ virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0; return ( - <div className="relative space-y-4"> + <div className="space-y-4"> <DataTableToolbar table={table} filterableItems={filterableItems} searchKey={searchKey} tableCTA={tableCTA} /> - <div className="border-subtle border" ref={tableContainerRef} onScroll={onScroll}> + <div ref={tableContainerRef} onScroll={onScroll}> <Table> <TableHeader> {table.getHeaderGroups().map((headerGroup) => ( @@ -128,16 +130,18 @@ export function DataTable<TData, TValue>({ {virtualRows && !isLoading ? ( virtualRows.map((virtualRow) => { const row = rows[virtualRow.index] as Row<TData>; - return ( <TableRow key={row.id} data-state={row.getIsSelected() && "selected"} onClick={() => onRowMouseclick && onRowMouseclick(row)} - className={classNames(onRowMouseclick && "hover:cursor-pointer")}> + className={classNames( + onRowMouseclick && "hover:cursor-pointer", + variant === "compact" && "!border-0" + )}> {row.getVisibleCells().map((cell) => { return ( - <TableCell key={cell.id}> + <TableCell key={cell.id} className={classNames(variant === "compact" && "p-1.5")}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell> ); diff --git a/packages/ui/components/dialog/Dialog.docs.mdx b/packages/ui/components/dialog/Dialog.docs.mdx new file mode 100644 index 0000000000..eef8469f8a --- /dev/null +++ b/packages/ui/components/dialog/Dialog.docs.mdx @@ -0,0 +1,52 @@ +import { Meta } from "@storybook/blocks"; +import { Title, CustomArgsTable } from "@calcom/storybook/components"; +import { Dialog, DialogContent, DialogFooter, DialogClose, DialogHeader } from "./Dialog"; +import * as DialogStories from "./Dialog.stories"; + +<Meta of={DialogStories} /> + +<Title title="Dialog" suffix="Brief" subtitle="Version 1.0 — Last Update: 18 Aug 2023" /> + +## Definition + +The `Dialog` component provides a flexible way to create dialogs in your application. + +## Structure + +The `Dialog` component is composed of the following components: + +- `Dialog`: The main component that wraps the entire dialog. It manages the dialog's open and close states. + +- `DialogContent`: Represents the content of the dialog. It can have different sizes, types, and an optional icon. + +- `DialogHeader`: Renders the header of the dialog, including the title and subtitle. + +- `DialogFooter`: Renders the footer of the dialog, which can contain action buttons. + +- `DialogClose`: Renders a close button for the dialog. + +## Components Arguments + +### Dialog + +<CustomArgsTable of={Dialog} /> + +### DialogContent + +<CustomArgsTable of={DialogContent} /> + +### DialogHeader + +<CustomArgsTable of={DialogHeader} /> + +### DialogFooter + +<CustomArgsTable of={DialogFooter} /> + +### DialogClose + +<CustomArgsTable of={DialogClose} /> + +{/* ## Dialog Story + +<Canvas of={DialogStories.Default}/> */} diff --git a/packages/ui/components/dialog/Dialog.stories.tsx b/packages/ui/components/dialog/Dialog.stories.tsx new file mode 100644 index 0000000000..71abf446ab --- /dev/null +++ b/packages/ui/components/dialog/Dialog.stories.tsx @@ -0,0 +1,87 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import type { ComponentProps } from "react"; + +import { Dialog, DialogContent, DialogFooter, DialogClose, DialogHeader } from "./Dialog"; + +type StoryArgs = ComponentProps<typeof Dialog> & + ComponentProps<typeof DialogContent> & + ComponentProps<typeof DialogHeader> & + ComponentProps<typeof DialogFooter> & + ComponentProps<typeof DialogClose> & { + onClick: (...args: unknown[]) => void; + }; + +const meta: Meta<StoryArgs> = { + component: Dialog, + parameters: { + nextjs: { + appDirectory: true, + }, + }, + title: "UI/Dialog", + argTypes: { + title: { + control: "text", + }, + description: { + control: "text", + }, + type: { + options: ["creation", "confirmation"], + control: { + type: "select", + }, + }, + open: { + control: "boolean", + }, + showDivider: { + control: "boolean", + }, + disabled: { + control: "boolean", + }, + color: { + options: ["minimal", "primary", "secondary", "emphasis"], + control: { + type: "select", + }, + }, + onClick: { action: "clicked" }, // this is a storybook action addons action + }, + render: ({ title, description, type, open, showDivider, disabled, color, onClick }) => ( + <Dialog open={open}> + <DialogContent type={type}> + <DialogHeader title={title} subtitle={description} /> + <DialogFooter showDivider={showDivider}> + <DialogClose + disabled={disabled} + color={color} + onClick={() => { + const currentUrl = new URL(window.location.href); + currentUrl.searchParams.set("args", "open:false"); + window.open(currentUrl.toString(), "_self"); + onClick(); + }} + /> + </DialogFooter> + </DialogContent> + </Dialog> + ), +}; + +export default meta; +type Story = StoryObj<StoryArgs>; + +export const Default: Story = { + name: "Dialog", + args: { + title: "Example Dialog", + description: "Example Dialog Description", + type: "creation", + open: true, + showDivider: false, + disabled: false, + color: "minimal", + }, +}; diff --git a/packages/ui/components/dialog/dialog.stories.mdx b/packages/ui/components/dialog/dialog.stories.mdx deleted file mode 100644 index e06fe3e75c..0000000000 --- a/packages/ui/components/dialog/dialog.stories.mdx +++ /dev/null @@ -1,114 +0,0 @@ -import { Canvas, Meta, Story } from "@storybook/addon-docs"; - -import { Title, CustomArgsTable } from "@calcom/storybook/components"; - -import { Dialog, DialogContent, DialogFooter, DialogClose, DialogHeader } from "./Dialog"; - -<Meta title="UI/Dialog" component={Dialog} /> - -<Title title="Dialog" suffix="Brief" subtitle="Version 1.0 — Last Update: 18 Aug 2023" /> - -## Definition - -The `Dialog` component provides a flexible way to create dialogs in your application. - -## Structure - -The `Dialog` component is composed of the following components: - -- `Dialog`: The main component that wraps the entire dialog. It manages the dialog's open and close states. - -- `DialogContent`: Represents the content of the dialog. It can have different sizes, types, and an optional icon. - -- `DialogHeader`: Renders the header of the dialog, including the title and subtitle. - -- `DialogFooter`: Renders the footer of the dialog, which can contain action buttons. - -- `DialogClose`: Renders a close button for the dialog. - -## Components Arguments - -### Dialog - -<CustomArgsTable of={Dialog} /> - -### DialogContent - -<CustomArgsTable of={DialogContent} /> - -### DialogHeader - -<CustomArgsTable of={DialogHeader} /> - -### DialogFooter - -<CustomArgsTable of={DialogFooter} /> - -### DialogClose - -<CustomArgsTable of={DialogClose} /> - -## Dialog Story - -<Canvas> - <Story - name="Dialog" - args={{ - title: "Example Dialog", - description: "Example Dialog Description", - type: "creation", - open: true, - showDivider: false, - disabled: false, - color: "minimal", - }} - argTypes={{ - title: { - control: "text", - }, - description: { - control: "text", - }, - type: { - control: { - type: "select", - options: ["creation", "confirmation"], - }, - }, - open: { - control: "boolean", - }, - showDivider: { - control: "boolean", - }, - disabled: { - control: "boolean", - }, - color: { - control: { - type: "select", - options: ["minimal", "primary", "secondary", "emphasis"], - }, - }, - onClick: { action: "clicked" }, - }}> - {({ title, description, type, open, showDivider, disabled, color, onClick }) => ( - <Dialog title="Default" open={open}> - <DialogContent type={type}> - <DialogHeader title={title} subtitle={description} /> - <DialogFooter showDivider={showDivider}> - <DialogClose - disabled={disabled} - color={color} - onClick={() => { - const newPath = "?path=/story/ui-dialog--dialog&args=open:false"; - window.open(newPath, "_self"); - onClick(); - }} - /> - </DialogFooter> - </DialogContent> - </Dialog> - )} - </Story> -</Canvas> diff --git a/packages/ui/components/form/checkbox/Checkbox.tsx b/packages/ui/components/form/checkbox/Checkbox.tsx index f1a4a3ea4d..5386c8c196 100644 --- a/packages/ui/components/form/checkbox/Checkbox.tsx +++ b/packages/ui/components/form/checkbox/Checkbox.tsx @@ -80,7 +80,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>( disabled={disabled} id={rest.id ? rest.id : id} className={classNames( - "text-primary-600 focus:ring-primary-500 border-default bg-default focus:bg-default active:bg-default h-4 w-4 rounded checked:hover:bg-gray-600 focus:outline-none focus:ring-0 ltr:mr-2 rtl:ml-2", + "text-emphasis focus:ring-emphasis dark:text-muted border-default bg-default focus:bg-default active:bg-default h-4 w-4 rounded checked:hover:bg-gray-600 focus:outline-none focus:ring-0 ltr:mr-2 rtl:ml-2", !error && disabled ? "cursor-not-allowed bg-gray-300 checked:bg-gray-300 hover:bg-gray-300 hover:checked:bg-gray-300" : "hover:bg-subtle hover:border-emphasis checked:bg-gray-800", diff --git a/packages/ui/components/form/checkbox/MultiSelectCheckboxes.tsx b/packages/ui/components/form/checkbox/MultiSelectCheckboxes.tsx index 3aaf68e8ab..d06c4c57ee 100644 --- a/packages/ui/components/form/checkbox/MultiSelectCheckboxes.tsx +++ b/packages/ui/components/form/checkbox/MultiSelectCheckboxes.tsx @@ -35,7 +35,7 @@ const InputOption: React.FC<OptionProps<unknown, boolean, GroupBase<unknown>>> = innerProps={props}> <input type="checkbox" - className="text-primary-600 focus:ring-primary-500 border-default h-4 w-4 rounded ltr:mr-2 rtl:ml-2" + className="text-emphasis focus:ring-emphasis dark:text-muted border-default h-4 w-4 rounded ltr:mr-2 rtl:ml-2" checked={isSelected} readOnly /> diff --git a/packages/ui/components/form/color-picker/colorpicker.stories.mdx b/packages/ui/components/form/color-picker/colorpicker.stories.mdx index 39398708b3..a3b7213c1e 100644 --- a/packages/ui/components/form/color-picker/colorpicker.stories.mdx +++ b/packages/ui/components/form/color-picker/colorpicker.stories.mdx @@ -1,4 +1,4 @@ -import { TooltipProvider } from "@radix-ui/react-tooltip"; +import { Tooltip } from "@radix-ui/react-tooltip"; import { Canvas, Meta, Story } from "@storybook/addon-docs"; import { @@ -66,17 +66,15 @@ The `Color Picker` takes in several props {({ defaultValue, onChange, resetDefaultValue, className, popoverAlign }) => ( <VariantsTable titles={["Default"]} columnMinWidth={150}> <VariantRow> - <TooltipProvider> - <Tooltip content="color picker"> - <ColorPicker - defaultValue={defaultValue} - onChange={onChange} - resetDefaultValue={resetDefaultValue} - className={className} - popoverAlign={popoverAlign} - /> - </Tooltip> - </TooltipProvider> + <Tooltip content="color picker"> + <ColorPicker + defaultValue={defaultValue} + onChange={onChange} + resetDefaultValue={resetDefaultValue} + className={className} + popoverAlign={popoverAlign} + /> + </Tooltip> </VariantRow> </VariantsTable> )} diff --git a/packages/ui/components/form/color-picker/colorpicker.test.tsx b/packages/ui/components/form/color-picker/colorpicker.test.tsx index 9ea9bef86e..20a2411dab 100644 --- a/packages/ui/components/form/color-picker/colorpicker.test.tsx +++ b/packages/ui/components/form/color-picker/colorpicker.test.tsx @@ -46,14 +46,9 @@ describe("Tests for ColorPicker component", () => { render(<ColorPicker defaultValue={defaultValue} onChange={onChange} />); const colorInput = screen.getByRole("textbox"); - await act(async () => { - userEvent.clear(colorInput); - }); + await act(async () => userEvent.clear(colorInput)); const newColorValue = "#00FF00"; - await act(async () => { - userEvent.type(colorInput, newColorValue); - }); - + await act(async () => await userEvent.type(colorInput, newColorValue)); expect(screen.getByRole("button", { name: "pick colors" })).toHaveStyle( `background-color: ${newColorValue}` ); diff --git a/packages/ui/components/form/dropdown/Dropdown.tsx b/packages/ui/components/form/dropdown/Dropdown.tsx index fcc0e55971..8445706af1 100644 --- a/packages/ui/components/form/dropdown/Dropdown.tsx +++ b/packages/ui/components/form/dropdown/Dropdown.tsx @@ -137,7 +137,7 @@ export const DropdownItem = (props: DropdownItemProps) => { <ButtonOrLink {...rest} className={classNames( - "hover:text-emphasis text-default inline-flex w-full items-center space-x-2 px-3 py-2 transition disabled:cursor-not-allowed", + "hover:text-emphasis text-default inline-flex w-full items-center space-x-2 px-3 py-2 disabled:cursor-not-allowed", color === "destructive" ? "hover:bg-error hover:text-red-700 dark:hover:text-red-100" : "hover:bg-subtle", diff --git a/packages/ui/components/form/inputs/Input.tsx b/packages/ui/components/form/inputs/Input.tsx index 0b87432d60..a259c21769 100644 --- a/packages/ui/components/form/inputs/Input.tsx +++ b/packages/ui/components/form/inputs/Input.tsx @@ -31,35 +31,33 @@ export const PasswordField = forwardRef<HTMLInputElement, InputFieldProps>(funct const textLabel = isPasswordVisible ? t("hide_password") : t("show_password"); return ( - <div className="[&_.group:hover_.addon-wrapper]:border-emphasis relative [&_.group:focus-within_.addon-wrapper]:border-neutral-300"> - <InputField - type={isPasswordVisible ? "text" : "password"} - placeholder={props.placeholder || "•••••••••••••"} - ref={ref} - {...props} - className={classNames( - "addon-wrapper mb-0 ltr:border-r-0 ltr:pr-10 rtl:border-l-0 rtl:pl-10", - props.className - )} - addOnFilled={false} - addOnSuffix={ - <Tooltip content={textLabel}> - <button - className="text-emphasis h-9" - tabIndex={-1} - type="button" - onClick={() => toggleIsPasswordVisible()}> - {isPasswordVisible ? ( - <EyeOff className="h-4 stroke-[2.5px]" /> - ) : ( - <Eye className="h-4 stroke-[2.5px]" /> - )} - <span className="sr-only">{textLabel}</span> - </button> - </Tooltip> - } - /> - </div> + <InputField + type={isPasswordVisible ? "text" : "password"} + placeholder={props.placeholder || "•••••••••••••"} + ref={ref} + {...props} + className={classNames( + "addon-wrapper mb-0 ltr:border-r-0 ltr:pr-10 rtl:border-l-0 rtl:pl-10", + props.className + )} + addOnFilled={false} + addOnSuffix={ + <Tooltip content={textLabel}> + <button + className="text-emphasis h-9" + tabIndex={-1} + type="button" + onClick={() => toggleIsPasswordVisible()}> + {isPasswordVisible ? ( + <EyeOff className="h-4 stroke-[2.5px]" /> + ) : ( + <Eye className="h-4 stroke-[2.5px]" /> + )} + <span className="sr-only">{textLabel}</span> + </button> + </Tooltip> + } + /> ); }); diff --git a/packages/ui/components/form/inputs/Label.tsx b/packages/ui/components/form/inputs/Label.tsx index f786a59a40..eda371a89b 100644 --- a/packages/ui/components/form/inputs/Label.tsx +++ b/packages/ui/components/form/inputs/Label.tsx @@ -4,7 +4,10 @@ export function Label(props: JSX.IntrinsicElements["label"]) { const { className, ...restProps } = props; return ( <label - className={classNames("text-default text-emphasis mb-2 block text-sm font-medium", className)} + className={classNames( + "text-default text-emphasis mb-2 block text-sm font-medium leading-none", + className + )} {...restProps}> {props.children} </label> diff --git a/packages/ui/components/form/inputs/TextField.tsx b/packages/ui/components/form/inputs/TextField.tsx index 1fc0b36f1f..07b0a046cd 100644 --- a/packages/ui/components/form/inputs/TextField.tsx +++ b/packages/ui/components/form/inputs/TextField.tsx @@ -107,7 +107,9 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function dir="ltr" className="focus-within:ring-brand-default group relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2"> {addOnLeading && ( - <Addon isFilled={addOnFilled} className={classNames("rounded-l-md border-r-0", addOnClassname)}> + <Addon + isFilled={addOnFilled} + className={classNames("ltr:rounded-l-md rtl:rounded-r-md", addOnClassname)}> {addOnLeading} </Addon> )} diff --git a/packages/ui/components/form/switch/Switch.tsx b/packages/ui/components/form/switch/Switch.tsx index b37305f9e6..846a679b6f 100644 --- a/packages/ui/components/form/switch/Switch.tsx +++ b/packages/ui/components/form/switch/Switch.tsx @@ -42,7 +42,7 @@ const Switch = ( {LockedIcon && <div className="mr-2">{LockedIcon}</div>} <PrimitiveSwitch.Root className={cx( - isChecked ? "bg-brand-default" : "bg-emphasis", + isChecked ? "bg-brand-default dark:bg-brand-emphasis" : "bg-emphasis", primitiveProps.disabled && "cursor-not-allowed", "focus:ring-brand-default h-5 w-[34px] rounded-full shadow-none focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1", props.className diff --git a/packages/ui/components/form/toggleGroup/ToggleGroup.tsx b/packages/ui/components/form/toggleGroup/ToggleGroup.tsx index 1247830d40..8f7357ed3e 100644 --- a/packages/ui/components/form/toggleGroup/ToggleGroup.tsx +++ b/packages/ui/components/form/toggleGroup/ToggleGroup.tsx @@ -54,7 +54,7 @@ export const ToggleGroup = ({ options, onValueChange, isFullWidth, ...props }: T "aria-checked:bg-emphasis relative rounded-[4px] px-3 py-1 text-sm leading-tight transition-colors", option.disabled ? "text-gray-400 hover:cursor-not-allowed" - : "text-default [&[aria-checked='false']]:hover:bg-emphasis", + : "text-default [&[aria-checked='false']]:hover:text-emphasis", isFullWidth && "w-full" )}> <div className="item-center flex justify-center "> diff --git a/packages/ui/components/form/wizard/wizard.stories.mdx b/packages/ui/components/form/wizard/wizard.stories.mdx index 1b06fdc77b..e31277b354 100644 --- a/packages/ui/components/form/wizard/wizard.stories.mdx +++ b/packages/ui/components/form/wizard/wizard.stories.mdx @@ -1,5 +1,4 @@ import { Canvas, Meta, Story } from "@storybook/addon-docs"; -import { useRouter } from "next/router"; import { CustomArgsTable, Title, VariantsTable, VariantRow } from "@calcom/storybook/components"; @@ -27,6 +26,11 @@ To observe the actual step navigation behavior, please refer to the Storybook st <Canvas> <Story + parameters={{ + nextjs: { + appDirectory: true, + }, + }} name="Basic" args={{ href: "/wizard", diff --git a/packages/ui/components/image-uploader/ImageUploader.tsx b/packages/ui/components/image-uploader/ImageUploader.tsx index 6f77756f07..7764997c8c 100644 --- a/packages/ui/components/image-uploader/ImageUploader.tsx +++ b/packages/ui/components/image-uploader/ImageUploader.tsx @@ -68,6 +68,8 @@ type ImageUploaderProps = { imageSrc?: string; target: string; triggerButtonColor?: ButtonColor; + uploadInstruction?: string; + disabled?: boolean; }; interface FileEvent<T = Element> extends FormEvent<T> { @@ -122,6 +124,8 @@ export default function ImageUploader({ handleAvatarChange, triggerButtonColor, imageSrc, + uploadInstruction, + disabled = false, }: ImageUploaderProps) { const { t } = useLocale(); const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null); @@ -173,8 +177,9 @@ export default function ImageUploader({ <Button color={triggerButtonColor ?? "secondary"} type="button" + disabled={disabled} data-testid="open-upload-avatar-dialog" - className="py-1 text-sm"> + className="cursor-pointer py-1 text-sm"> {buttonMsg} </Button> </DialogTrigger> @@ -196,7 +201,7 @@ export default function ImageUploader({ {result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />} <label data-testid="open-upload-image-filechooser" - className="bg-subtle hover:bg-muted hover:text-emphasis border-subtle text-default mt-8 rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1"> + className="bg-subtle hover:bg-muted hover:text-emphasis border-subtle text-default mt-8 cursor-pointer rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-1"> <input onInput={onInputFile} type="file" @@ -207,6 +212,9 @@ export default function ImageUploader({ /> {t("choose_a_file")} </label> + {uploadInstruction && ( + <p className="text-muted mt-4 text-center text-sm">({uploadInstruction})</p> + )} </div> </div> <DialogFooter className="relative"> diff --git a/packages/ui/components/image-uploader/imageUploader.stories.mdx b/packages/ui/components/image-uploader/imageUploader.stories.mdx index 9260a70b9b..a0c863b69f 100644 --- a/packages/ui/components/image-uploader/imageUploader.stories.mdx +++ b/packages/ui/components/image-uploader/imageUploader.stories.mdx @@ -22,6 +22,11 @@ Below are the props for `Image uploader` <Canvas> <Story + parameters={{ + nextjs: { + appDirectory: true, + }, + }} name="ImageUploader" play={({ canvasElement }) => { const darkVariantContainer = canvasElement.querySelector("#dark-variant"); diff --git a/packages/ui/components/layout/spacing.stories.mdx b/packages/ui/components/layout/spacing.stories.mdx index 11c1472ec8..c6431317c1 100644 --- a/packages/ui/components/layout/spacing.stories.mdx +++ b/packages/ui/components/layout/spacing.stories.mdx @@ -26,7 +26,7 @@ Defines the spacing guide used in Cal.coms design system <Examples title="Spacing"> <TooltipPrimitive.Provider> - <> + <div className="flex flex-row gap-6"> <Example title="0"></Example> <Example title="px"> <Tooltip content="1px"> @@ -78,6 +78,6 @@ Defines the spacing guide used in Cal.coms design system <div className="bg-inverted h-4 w-10 rounded-sm"> </div> </Tooltip> </Example> - </> + </div> </TooltipPrimitive.Provider> </Examples> diff --git a/packages/ui/components/navigation/tabs/__stories__/verticalTabs.stories.mdx b/packages/ui/components/navigation/tabs/__stories__/verticalTabs.stories.mdx index 6b7b3ea2c6..bfc8ea9c5a 100644 --- a/packages/ui/components/navigation/tabs/__stories__/verticalTabs.stories.mdx +++ b/packages/ui/components/navigation/tabs/__stories__/verticalTabs.stories.mdx @@ -1,4 +1,4 @@ -import { Meta, Story } from "@storybook/addon-docs/blocks"; +import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks"; import { Title, @@ -6,7 +6,7 @@ import { Examples, Example, VariantsTable, - VariantsRow, + VariantRow, } from "@calcom/storybook/components"; import { Plus } from "@calcom/ui/components/icon"; diff --git a/packages/ui/components/popover/AnimatedPopover.tsx b/packages/ui/components/popover/AnimatedPopover.tsx index 33735a68f8..b0d43a5ef2 100644 --- a/packages/ui/components/popover/AnimatedPopover.tsx +++ b/packages/ui/components/popover/AnimatedPopover.tsx @@ -13,6 +13,7 @@ export const AnimatedPopover = ({ children, Trigger, defaultOpen, + prefix, }: { text: string; count?: number; @@ -20,9 +21,10 @@ export const AnimatedPopover = ({ popoverTriggerClassNames?: string; Trigger?: React.ReactNode; defaultOpen?: boolean; + prefix?: string; }) => { const [open, setOpen] = React.useState(defaultOpen ?? false); - const ref = React.useRef<HTMLDivElement>(null); + const ref = React.useRef<HTMLButtonElement>(null); // calculate which aligment to open the popover with based on which half of the screen it is on (left or right) const [align, setAlign] = React.useState<"start" | "end">("start"); React.useEffect(() => { @@ -48,7 +50,7 @@ export const AnimatedPopover = ({ return ( <Popover.Root defaultOpen={defaultOpen} onOpenChange={setOpen} modal={true}> <Popover.Trigger asChild> - <div + <button ref={ref} className={classNames( "hover:border-emphasis border-default text-default hover:text-emphasis radix-state-open:border-emphasis radix-state-open:outline-none radix-state-open:ring-2 radix-state-open:ring-emphasis mb-4 flex h-9 max-h-72 items-center justify-between whitespace-nowrap rounded-md border px-3 py-2 text-sm hover:cursor-pointer", @@ -58,8 +60,9 @@ export const AnimatedPopover = ({ Trigger ) : ( <div className="max-w-36 flex items-center"> - <Tooltip content={text}> + <Tooltip content={`${prefix}${text}`}> <div className="flex select-none truncate font-medium"> + {prefix && <span className="text-subtle">{prefix} </span>} {text} {count && count > 0 && ( <div className="text-emphasis flex items-center justify-center rounded-full font-semibold"> @@ -74,7 +77,7 @@ export const AnimatedPopover = ({ /> </div> )} - </div> + </button> </Popover.Trigger> <Popover.Content side="bottom" align={align} asChild> <div diff --git a/packages/ui/components/table/TableNew.tsx b/packages/ui/components/table/TableNew.tsx index 146e2297d5..c0504ccdf3 100644 --- a/packages/ui/components/table/TableNew.tsx +++ b/packages/ui/components/table/TableNew.tsx @@ -5,7 +5,11 @@ import { classNames } from "@calcom/lib"; const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>( ({ className, ...props }, ref) => ( <div className="w-full overflow-auto md:overflow-visible"> - <table ref={ref} className={classNames("w-full caption-bottom text-sm", className)} {...props} /> + <table + ref={ref} + className={classNames("border-subtle w-full caption-bottom border text-sm", className)} + {...props} + /> </div> ) ); @@ -68,7 +72,7 @@ const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes< ({ className, ...props }, ref) => ( <td ref={ref} - className={classNames("text-default p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} + className={classNames("text-default px-2 py-2.5 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} /> ) diff --git a/packages/ui/components/tooltip/Tooltip.docs.mdx b/packages/ui/components/tooltip/Tooltip.docs.mdx new file mode 100644 index 0000000000..fd7fe7d344 --- /dev/null +++ b/packages/ui/components/tooltip/Tooltip.docs.mdx @@ -0,0 +1,20 @@ +import { Canvas, Meta } from "@storybook/blocks"; +import { Title, CustomArgsTable } from "@calcom/storybook/components"; +import { TooltipProvider } from "@radix-ui/react-tooltip"; +import { Tooltip } from "./Tooltip"; + +import * as TooltipStories from "./Tooltip.stories"; + +<Meta of={TooltipStories} /> + +<Title title="Tooltip" suffix="Brief" subtitle="Version 2.0 — Last Update: 06 Jan 2023" /> + +## Definition + +Tooltip components can be used to provide additional information about an element when the user hovers over or focuses on the element. + +<TooltipProvider> + <CustomArgsTable of={Tooltip} /> +</TooltipProvider> + +<Canvas of={TooltipStories.Default}/> diff --git a/packages/ui/components/tooltip/Tooltip.stories.tsx b/packages/ui/components/tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..f7d79d1f47 --- /dev/null +++ b/packages/ui/components/tooltip/Tooltip.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { VariantRow, VariantsTable } from "@calcom/storybook/components"; + +import { Tooltip } from "./Tooltip"; + +const meta: Meta<typeof Tooltip> = { + component: Tooltip, + title: "UI/Tooltip", +}; + +export default meta; +type Story = StoryObj<typeof Tooltip>; + +export const Default: Story = { + name: "Tooltip", + parameters: { + hoverMsg: "Click to copy", + alertMsg: "Copied!", + content: "Copy me!", + }, + render: (_, { parameters: { content, hoverMsg, alertMsg } }) => ( + <VariantsTable titles={[]}> + <VariantRow variant="Default"> + <Tooltip content={`${hoverMsg}`}> + <span + className="dark:text-darkgray-50 bg-brand-default dark:bg-darkgray-900 rounded-md p-2 text-gray-100 hover:cursor-pointer" + onClick={() => { + alert(`${alertMsg}`); + }}> + {content} + </span> + </Tooltip> + </VariantRow> + </VariantsTable> + ), +}; diff --git a/packages/ui/components/tooltip/tooltip.stories.mdx b/packages/ui/components/tooltip/tooltip.stories.mdx deleted file mode 100644 index a70c518c39..0000000000 --- a/packages/ui/components/tooltip/tooltip.stories.mdx +++ /dev/null @@ -1,69 +0,0 @@ -import { TooltipProvider } from "@radix-ui/react-tooltip"; -import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; - -import { - Examples, - Example, - Note, - Title, - CustomArgsTable, - VariantsTable, - VariantRow, -} from "@calcom/storybook/components"; - -import Tooltip from "./Tooltip"; - -<Meta title="UI/Tooltip" component={Tooltip} /> - -<Title title="Tooltip" suffix="Brief" subtitle="Version 2.0 — Last Update: 06 Jan 2023" /> - -## Definition - -Tooltip components can be used to provide additional information about an element when the user hovers over or focuses on the element. - -<CustomArgsTable of={Tooltip} /> - -<Canvas> - <Story - name="Tooltip" - args={{ - alertMsg: "Copied!", - hoverMsg: "Copy to clipboard", - content: "Hover Me", - }} - argTypes={{ - alertMsg: { - control: { - type: "text", - }, - }, - hoverMsg: { - control: { - type: "text", - }, - }, - content: { - control: { - type: "text", - }, - }, - }}> - {({ alertMsg, hoverMsg, content }) => ( - <TooltipProvider> - <VariantsTable titles={[""]} columnMinWidth={150}> - <VariantRow variant="Default"> - <Tooltip content={`${hoverMsg}`}> - <span - className="dark:text-darkgray-50 bg-brand-default dark:bg-darkgray-900 rounded-md p-2 text-gray-100 hover:cursor-pointer" - onClick={() => { - alert(`${alertMsg}`); - }}> - {content} - </span> - </Tooltip> - </VariantRow> - </VariantsTable> - </TooltipProvider> - )} - </Story> -</Canvas> diff --git a/packages/ui/components/top-banner/TopBanner.tsx b/packages/ui/components/top-banner/TopBanner.tsx index 2ab3155936..69646b1441 100644 --- a/packages/ui/components/top-banner/TopBanner.tsx +++ b/packages/ui/components/top-banner/TopBanner.tsx @@ -1,6 +1,7 @@ import classNames from "classnames"; import type { ComponentType, ReactNode } from "react"; +import { TOP_BANNER_HEIGHT } from "@calcom/lib/constants"; import type { LucideIcon, LucideProps } from "@calcom/ui/components/icon"; import { AlertTriangle, Info } from "@calcom/ui/components/icon"; @@ -40,8 +41,9 @@ export function TopBanner(props: TopBannerProps) { return ( <div data-testid="banner" + style={{ minHeight: TOP_BANNER_HEIGHT }} className={classNames( - "flex min-h-[40px] w-full items-start justify-between gap-8 px-4 py-2 text-center lg:items-center", + "flex w-full items-start justify-between gap-8 px-4 py-2 text-center lg:items-center", variantClassName[variant] )}> <div className="flex flex-1 flex-col items-start justify-center gap-2 p-1 lg:flex-row lg:items-center"> diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index c0922cb2aa..7db5db6658 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -1,4 +1,4 @@ -export { Avatar, AvatarGroup } from "./components/avatar"; +export { Avatar, AvatarGroup, UserAvatar } from "./components/avatar"; export type { AvatarProps, AvatarGroupProps } from "./components/avatar"; export { ArrowButton } from "./components/arrow-button"; export type { ArrowButtonProps } from "./components/arrow-button"; @@ -164,3 +164,5 @@ export { } from "./components/command"; export { Popover, PopoverContent, PopoverTrigger } from "./components/popover"; + +export { StorybookTrpcProvider } from "./components/mocks/trpc"; diff --git a/packages/ui/package.json b/packages/ui/package.json index 7e44b2f922..caf64f41ca 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -10,7 +10,9 @@ "./components/icon": "./components/icon/index.ts", "./components/icon/Discord": "./components/icon/Discord.tsx", "./components/icon/SatSymbol": "./components/icon/SatSymbol.tsx", - "./components/icon/Spinner": "./components/icon/Spinner.tsx" + "./components/icon/Spinner": "./components/icon/Spinner.tsx", + "./components/unpublished-entity/UnpublishedEntity": "./components/unpublished-entity/index.ts", + "./components/form/timezone-select/TimezoneSelect": "./components/form/timezone-select/index.ts" }, "types": "./index.tsx", "license": "MIT", @@ -24,13 +26,15 @@ "dependencies": { "@calcom/lib": "*", "@calcom/trpc": "*", - "@formkit/auto-animate": "^1.0.0-beta.5", + "@formkit/auto-animate": "^0.8.1", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-popover": "^1.0.2", "@radix-ui/react-portal": "^1.0.0", "@radix-ui/react-select": "^0.1.1", "@react-icons/all-files": "^4.1.0", + "@storybook/blocks": "^7.6.3", + "@storybook/react": "^7.6.3", "@tanstack/react-query": "^4.3.9", "@tanstack/react-table": "^8.9.3", "@wojtekmaj/react-daterange-picker": "^3.3.1", diff --git a/turbo.json b/turbo.json index 5fabef8bca..dc8e958a65 100644 --- a/turbo.json +++ b/turbo.json @@ -112,6 +112,7 @@ "cache": false }, "dx": { + "dependsOn": ["//#env-check:common", "//#env-check:app-store"], "cache": false }, "lint": { @@ -198,7 +199,20 @@ "ALLOWED_HOSTNAMES", "ANALYZE", "API_KEY_PREFIX", + "APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED", + "APP_ROUTER_APPS_CATEGORIES_ENABLED", + "APP_ROUTER_APPS_ENABLED", + "APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED", + "APP_ROUTER_APPS_SLUG_ENABLED", + "APP_ROUTER_APPS_SLUG_SETUP_ENABLED", + "APP_ROUTER_BOOKINGS_STATUS_ENABLED", "APP_ROUTER_EVENT_TYPES_ENABLED", + "APP_ROUTER_GETTING_STARTED_STEP_ENABLED", + "APP_ROUTER_SETTINGS_ADMIN_ENABLED", + "APP_ROUTER_SETTINGS_TEAMS_ENABLED", + "APP_ROUTER_WORKFLOWS_ENABLED", + "APP_ROUTER_VIDEO_ENABLED", + "APP_ROUTER_TEAMS_ENABLED", "APP_USER_NAME", "BASECAMP3_CLIENT_ID", "BASECAMP3_CLIENT_SECRET", @@ -221,11 +235,14 @@ "CRON_ENABLE_APP_SYNC", "DAILY_API_KEY", "DAILY_SCALE_PLAN", + "DAILY_WEBHOOK_SECRET", "DEBUG", "E2E_TEST_APPLE_CALENDAR_EMAIL", "E2E_TEST_APPLE_CALENDAR_PASSWORD", "E2E_TEST_CALCOM_QA_EMAIL", "E2E_TEST_CALCOM_QA_PASSWORD", + "E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS", + "E2E_TEST_CALCOM_GCAL_KEYS", "E2E_TEST_MAILHOG_ENABLED", "E2E_TEST_OIDC_CLIENT_ID", "E2E_TEST_OIDC_CLIENT_SECRET", @@ -241,6 +258,9 @@ "EMAIL_SERVER_USER", "EMAIL_SERVER", "EXCHANGE_DEFAULT_EWS_URL", + "FORMBRICKS_HOST_URL", + "FORMBRICKS_ENVIRONMENT_ID", + "FORMBRICKS_FEEDBACK_SURVEY_ID", "GIPHY_API_KEY", "GITHUB_API_REPO_TOKEN", "GOOGLE_API_CREDENTIALS", @@ -303,6 +323,7 @@ "SENDGRID_API_KEY", "SENDGRID_EMAIL", "SENDGRID_SYNC_API_KEY", + "SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN", "SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET", "SLACK_SIGNING_SECRET", @@ -320,6 +341,7 @@ "TELEMETRY_DEBUG", "TWILIO_MESSAGING_SID", "TWILIO_PHONE_NUMBER", + "TWILIO_WHATSAPP_PHONE_NUMBER", "TWILIO_SID", "TWILIO_TOKEN", "TWILIO_VERIFY_SID", @@ -336,6 +358,15 @@ "ZOHOCRM_CLIENT_ID", "ZOHOCRM_CLIENT_SECRET", "ZOOM_CLIENT_ID", - "ZOOM_CLIENT_SECRET" + "ZOOM_CLIENT_SECRET", + "REVERT_API_KEY", + "REVERT_API_URL", + "REVERT_PUBLIC_TOKEN", + "RESEND_API_KEY", + "LOCAL_TESTING_DOMAIN_VERCEL", + "AUTH_BEARER_TOKEN_CLOUDFLARE", + "CLOUDFLARE_ZONE_ID", + "CLOUDFLARE_VERCEL_CNAME", + "CLOUDFLARE_DNS" ] } diff --git a/yarn.lock b/yarn.lock index ecb1e05084..60e839c4f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,13 +81,13 @@ __metadata: linkType: hard "@algora/sdk@npm:^0.1.2": - version: 0.1.2 - resolution: "@algora/sdk@npm:0.1.2" + version: 0.1.3 + resolution: "@algora/sdk@npm:0.1.3" dependencies: "@trpc/client": ^10.0.0 "@trpc/server": ^10.0.0 superjson: ^1.9.1 - checksum: 0ef5c0ac7fa09c57f685a61859e263f4b5c2d69e6ee72b395f88490106fa6213a0308ca11e5673368e5b33dc4dff38411c0d226f9c65856ef91aa58bc2e0e61c + checksum: 1b99e0f155181beefe12b625969166f4ecfa42d334706224d0a9a4e9ad72e2cda7335712c47290df7aeeeb003a9773ff5babce7cbee8fb8d1c5ded4ad81c80c1 languageName: node linkType: hard @@ -238,6 +238,17 @@ __metadata: languageName: node linkType: hard +"@aw-web-design/x-default-browser@npm:1.4.126": + version: 1.4.126 + resolution: "@aw-web-design/x-default-browser@npm:1.4.126" + dependencies: + default-browser-id: 3.0.0 + bin: + x-default-browser: bin/x-default-browser.js + checksum: f63b68a0ff41c8fe478b1b4822e169cac0d26c61b123c0400d5e16a8a5987732b85795aff16d6b21936f9c955f0d32bffbfc166890d3446f74a72a7a2c9633ea + languageName: node + linkType: hard + "@aws-crypto/ie11-detection@npm:^3.0.0": version: 3.0.0 resolution: "@aws-crypto/ie11-detection@npm:3.0.0" @@ -1305,7 +1316,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.22.10, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.22.5, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.22.10, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.22.5": version: 7.22.13 resolution: "@babel/code-frame@npm:7.22.13" dependencies: @@ -1315,38 +1326,54 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9": +"@babel/code-frame@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/code-frame@npm:7.23.5" + dependencies: + "@babel/highlight": ^7.23.4 + chalk: ^2.4.2 + checksum: d90981fdf56a2824a9b14d19a4c0e8db93633fd488c772624b4e83e0ceac6039a27cd298a247c3214faa952bf803ba23696172ae7e7235f3b97f43ba278c569a + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.23.3, @babel/compat-data@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/compat-data@npm:7.23.5" + checksum: 06ce244cda5763295a0ea924728c09bae57d35713b675175227278896946f922a63edf803c322f855a3878323d48d0255a2a3023409d2a123483c8a69ebb4744 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9": version: 7.22.9 resolution: "@babel/compat-data@npm:7.22.9" checksum: bed77d9044ce948b4327b30dd0de0779fa9f3a7ed1f2d31638714ed00229fa71fc4d1617ae0eb1fad419338d3658d0e9a5a083297451e09e73e078d0347ff808 languageName: node linkType: hard -"@babel/core@npm:7.12.9": - version: 7.12.9 - resolution: "@babel/core@npm:7.12.9" +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.18.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2": + version: 7.23.5 + resolution: "@babel/core@npm:7.23.5" dependencies: - "@babel/code-frame": ^7.10.4 - "@babel/generator": ^7.12.5 - "@babel/helper-module-transforms": ^7.12.1 - "@babel/helpers": ^7.12.5 - "@babel/parser": ^7.12.7 - "@babel/template": ^7.12.7 - "@babel/traverse": ^7.12.9 - "@babel/types": ^7.12.7 - convert-source-map: ^1.7.0 + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.5 + "@babel/helper-compilation-targets": ^7.22.15 + "@babel/helper-module-transforms": ^7.23.3 + "@babel/helpers": ^7.23.5 + "@babel/parser": ^7.23.5 + "@babel/template": ^7.22.15 + "@babel/traverse": ^7.23.5 + "@babel/types": ^7.23.5 + convert-source-map: ^2.0.0 debug: ^4.1.0 - gensync: ^1.0.0-beta.1 - json5: ^2.1.2 - lodash: ^4.17.19 - resolve: ^1.3.2 - semver: ^5.4.1 - source-map: ^0.5.0 - checksum: 4d34eca4688214a4eb6bd5dde906b69a7824f17b931f52cd03628a8ac94d8fbe15565aebffdde106e974c8738cd64ac62c6a6060baa7139a06db1f18c4ff872d + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: 5e5dfb1e61f298676f1fca18c646dbf6fb164ca1056b0169b8d42b7f5c35e026d81823582ccb2358e93a61b035e22b3ad37e2abaae4bf43f1ffb93b6ce19466e languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.10, @babel/core@npm:^7.19.6, @babel/core@npm:^7.7.5": +"@babel/core@npm:^7.12.3, @babel/core@npm:^7.19.6": version: 7.22.11 resolution: "@babel/core@npm:7.22.11" dependencies: @@ -1370,25 +1397,25 @@ __metadata: linkType: hard "@babel/core@npm:^7.14.0, @babel/core@npm:^7.22.9": - version: 7.23.0 - resolution: "@babel/core@npm:7.23.0" + version: 7.23.6 + resolution: "@babel/core@npm:7.23.6" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.22.13 - "@babel/generator": ^7.23.0 - "@babel/helper-compilation-targets": ^7.22.15 - "@babel/helper-module-transforms": ^7.23.0 - "@babel/helpers": ^7.23.0 - "@babel/parser": ^7.23.0 + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.6 + "@babel/helper-compilation-targets": ^7.23.6 + "@babel/helper-module-transforms": ^7.23.3 + "@babel/helpers": ^7.23.6 + "@babel/parser": ^7.23.6 "@babel/template": ^7.22.15 - "@babel/traverse": ^7.23.0 - "@babel/types": ^7.23.0 + "@babel/traverse": ^7.23.6 + "@babel/types": ^7.23.6 convert-source-map: ^2.0.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.3 semver: ^6.3.1 - checksum: cebd9b48dbc970a7548522f207f245c69567e5ea17ebb1a4e4de563823cf20a01177fe8d2fe19b6e1461361f92fa169fd0b29f8ee9d44eeec84842be1feee5f2 + checksum: 4bddd1b80394a64b2ee33eeb216e8a2a49ad3d74f0ca9ba678c84a37f4502b2540662d72530d78228a2a349fda837fa852eea5cd3ae28465d1188acc6055868e languageName: node linkType: hard @@ -1403,7 +1430,19 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.17.3, @babel/generator@npm:^7.22.10": +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/generator@npm:7.23.6" + dependencies: + "@babel/types": ^7.23.6 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 1a1a1c4eac210f174cd108d479464d053930a812798e09fee069377de39a893422df5b5b146199ead7239ae6d3a04697b45fc9ac6e38e0f6b76374390f91fc6c + languageName: node + linkType: hard + +"@babel/generator@npm:^7.17.3, @babel/generator@npm:^7.22.10": version: 7.22.10 resolution: "@babel/generator@npm:7.22.10" dependencies: @@ -1415,7 +1454,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.23.0": +"@babel/generator@npm:^7.23.0": version: 7.23.0 resolution: "@babel/generator@npm:7.23.0" dependencies: @@ -1427,7 +1466,19 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.22.5": +"@babel/generator@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/generator@npm:7.23.5" + dependencies: + "@babel/types": ^7.23.5 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 845ddda7cf38a3edf4be221cc8a439dee9ea6031355146a1a74047aa8007bc030305b27d8c68ec9e311722c910610bde38c0e13a9ce55225251e7cb7e7f3edc8 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" dependencies: @@ -1436,16 +1487,29 @@ __metadata: languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.5": - version: 7.22.10 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.10" +"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.15" dependencies: - "@babel/types": ^7.22.10 - checksum: 6de4a1f30e6244f9a1efdfcbe89df39923df3d165be606da5ad11319f8a11c12c72c60d9dc5fb696363281e2d6f741444c1af51f525fc7cf1d2a90fe23370bd9 + "@babel/types": ^7.22.15 + checksum: 639c697a1c729f9fafa2dd4c9af2e18568190299b5907bd4c2d0bc818fcbd1e83ffeecc2af24327a7faa7ac4c34edd9d7940510a5e66296c19bad17001cf5c7a languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.10, @babel/helper-compilation-targets@npm:^7.22.5, @babel/helper-compilation-targets@npm:^7.22.6": +"@babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/helper-compilation-targets@npm:7.23.6" + dependencies: + "@babel/compat-data": ^7.23.5 + "@babel/helper-validator-option": ^7.23.5 + browserslist: ^4.22.2 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: c630b98d4527ac8fe2c58d9a06e785dfb2b73ec71b7c4f2ddf90f814b5f75b547f3c015f110a010fd31f76e3864daaf09f3adcd2f6acdbfb18a8de3a48717590 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.22.10, @babel/helper-compilation-targets@npm:^7.22.6": version: 7.22.10 resolution: "@babel/helper-compilation-targets@npm:7.22.10" dependencies: @@ -1471,7 +1535,45 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.22.10, @babel/helper-create-class-features-plugin@npm:^7.22.11, @babel/helper-create-class-features-plugin@npm:^7.22.5": +"@babel/helper-create-class-features-plugin@npm:^7.18.6": + version: 7.23.6 + resolution: "@babel/helper-create-class-features-plugin@npm:7.23.6" + dependencies: + "@babel/helper-annotate-as-pure": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-member-expression-to-functions": ^7.23.0 + "@babel/helper-optimise-call-expression": ^7.22.5 + "@babel/helper-replace-supers": ^7.22.20 + "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 356b71b9f4a3a95917432bf6a452f475a292d394d9310e9c8b23c8edb564bee91e40d4290b8aa8779d2987a7c39ae717b2d76edc7c952078b8952df1a20259e3 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/helper-create-class-features-plugin@npm:7.23.5" + dependencies: + "@babel/helper-annotate-as-pure": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-member-expression-to-functions": ^7.23.0 + "@babel/helper-optimise-call-expression": ^7.22.5 + "@babel/helper-replace-supers": ^7.22.20 + "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: fe7c6c0baca1838bba76ac1330df47b661d932354115ea9e2ea65b179f80b717987d3c3da7e1525fd648e5f2d86c620efc959cabda4d7562b125a27c3ac780d0 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.22.5": version: 7.22.11 resolution: "@babel/helper-create-class-features-plugin@npm:7.22.11" dependencies: @@ -1503,27 +1605,22 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.1.5": - version: 0.1.5 - resolution: "@babel/helper-define-polyfill-provider@npm:0.1.5" +"@babel/helper-create-regexp-features-plugin@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.15" dependencies: - "@babel/helper-compilation-targets": ^7.13.0 - "@babel/helper-module-imports": ^7.12.13 - "@babel/helper-plugin-utils": ^7.13.0 - "@babel/traverse": ^7.13.0 - debug: ^4.1.1 - lodash.debounce: ^4.0.8 - resolve: ^1.14.2 - semver: ^6.1.2 + "@babel/helper-annotate-as-pure": ^7.22.5 + regexpu-core: ^5.3.1 + semver: ^6.3.1 peerDependencies: - "@babel/core": ^7.4.0-0 - checksum: 6f8b61b41730bedc9c4511035b7f2407ea30176c379107dd735aac7d010317a99171bf420959ba37418fb8a857dac7c0e36e1c8576a6560bdd9b690eb4314a95 + "@babel/core": ^7.0.0 + checksum: 0243b8d4854f1dc8861b1029a46d3f6393ad72f366a5a08e36a4648aa682044f06da4c6e87a456260e1e1b33c999f898ba591a0760842c1387bcc93fbf2151a6 languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.4.2": - version: 0.4.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.2" +"@babel/helper-define-polyfill-provider@npm:^0.4.3": + version: 0.4.3 + resolution: "@babel/helper-define-polyfill-provider@npm:0.4.3" dependencies: "@babel/helper-compilation-targets": ^7.22.6 "@babel/helper-plugin-utils": ^7.22.5 @@ -1532,7 +1629,7 @@ __metadata: resolve: ^1.14.2 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 1f6dec0c5d0876d278fe15b71238eccc5f74c4e2efa2c78aaafa8bc2cc96336b8e68d94cd1a78497356c96e8b91b8c1f4452179820624d1702aee2f9832e6569 + checksum: 5d21e3f47b320e4b5b644195ec405e7ebc3739e48e65899efc808c5fa9c3bf5b06ce0d8ff5246ca99d1411e368f4557bc66730196c5781a5c4e986ee703bee79 languageName: node linkType: hard @@ -1579,6 +1676,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.22.15, @babel/helper-member-expression-to-functions@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" + dependencies: + "@babel/types": ^7.23.0 + checksum: 494659361370c979ada711ca685e2efe9460683c36db1b283b446122596602c901e291e09f2f980ecedfe6e0f2bd5386cb59768285446530df10c14df1024e75 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-member-expression-to-functions@npm:7.22.5" @@ -1606,7 +1712,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.22.5, @babel/helper-module-transforms@npm:^7.22.9": +"@babel/helper-module-transforms@npm:^7.22.9": version: 7.22.9 resolution: "@babel/helper-module-transforms@npm:7.22.9" dependencies: @@ -1621,9 +1727,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-module-transforms@npm:7.23.0" +"@babel/helper-module-transforms@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/helper-module-transforms@npm:7.23.3" dependencies: "@babel/helper-environment-visitor": ^7.22.20 "@babel/helper-module-imports": ^7.22.15 @@ -1632,7 +1738,7 @@ __metadata: "@babel/helper-validator-identifier": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0 - checksum: 6e2afffb058cf3f8ce92f5116f710dda4341c81cfcd872f9a0197ea594f7ce0ab3cb940b0590af2fe99e60d2e5448bfba6bca8156ed70a2ed4be2adc8586c891 + checksum: 5d0895cfba0e16ae16f3aa92fee108517023ad89a855289c4eb1d46f7aef4519adf8e6f971e1d55ac20c5461610e17213f1144097a8f932e768a9132e2278d71 languageName: node linkType: hard @@ -1645,34 +1751,40 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:7.10.4": - version: 7.10.4 - resolution: "@babel/helper-plugin-utils@npm:7.10.4" - checksum: 639ed8fc462b97a83226cee6bb081b1d77e7f73e8b033d2592ed107ee41d96601e321e5ea53a33e47469c7f1146b250a3dcda5ab873c7de162ab62120c341a41 - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": version: 7.22.5 resolution: "@babel/helper-plugin-utils@npm:7.22.5" checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.22.5, @babel/helper-remap-async-to-generator@npm:^7.22.9": - version: 7.22.9 - resolution: "@babel/helper-remap-async-to-generator@npm:7.22.9" +"@babel/helper-remap-async-to-generator@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-remap-async-to-generator@npm:7.22.20" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-wrap-function": ^7.22.9 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-wrap-function": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0 - checksum: 05538079447829b13512157491cc77f9cf1ea7e1680e15cff0682c3ed9ee162de0c4862ece20a6d6b2df28177a1520bcfe45993fbeccf2747a81795a7c3f6290 + checksum: 2fe6300a6f1b58211dffa0aed1b45d4958506d096543663dba83bd9251fe8d670fa909143a65b45e72acb49e7e20fbdb73eae315d9ddaced467948c3329986e7 languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.22.5, @babel/helper-replace-supers@npm:^7.22.9": +"@babel/helper-replace-supers@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-replace-supers@npm:7.22.20" + dependencies: + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-member-expression-to-functions": ^7.22.15 + "@babel/helper-optimise-call-expression": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: a0008332e24daedea2e9498733e3c39b389d6d4512637e000f96f62b797e702ee24a407ccbcd7a236a551590a38f31282829a8ef35c50a3c0457d88218cae639 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.22.9": version: 7.22.9 resolution: "@babel/helper-replace-supers@npm:7.22.9" dependencies: @@ -1694,7 +1806,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" dependencies: @@ -1719,6 +1831,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/helper-string-parser@npm:7.23.4" + checksum: c0641144cf1a7e7dc93f3d5f16d5327465b6cf5d036b48be61ecba41e1eece161b48f46b7f960951b67f8c3533ce506b16dece576baef4d8b3b49f8c65410f90 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-identifier@npm:7.22.5" @@ -1747,18 +1866,25 @@ __metadata: languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.22.9": - version: 7.22.10 - resolution: "@babel/helper-wrap-function@npm:7.22.10" - dependencies: - "@babel/helper-function-name": ^7.22.5 - "@babel/template": ^7.22.5 - "@babel/types": ^7.22.10 - checksum: 854bd85fc1de1d4c633f04aa1f5b6b022fbc013b47d012b6a11a7a9125a1f4a2a4f13a3e0d7a7056fe7eda8a9ecd1ea3daf8af685685a2d1b16578768cfdd28f +"@babel/helper-validator-option@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/helper-validator-option@npm:7.23.5" + checksum: 537cde2330a8aede223552510e8a13e9c1c8798afee3757995a7d4acae564124fe2bf7e7c3d90d62d3657434a74340a274b3b3b1c6f17e9a2be1f48af29cb09e languageName: node linkType: hard -"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.22.11": +"@babel/helper-wrap-function@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-wrap-function@npm:7.22.20" + dependencies: + "@babel/helper-function-name": ^7.22.5 + "@babel/template": ^7.22.15 + "@babel/types": ^7.22.19 + checksum: 221ed9b5572612aeb571e4ce6a256f2dee85b3c9536f1dd5e611b0255e5f59a3d0ec392d8d46d4152149156a8109f92f20379b1d6d36abb613176e0e33f05fca + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.22.11": version: 7.22.11 resolution: "@babel/helpers@npm:7.22.11" dependencies: @@ -1769,14 +1895,25 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.0": - version: 7.23.1 - resolution: "@babel/helpers@npm:7.23.1" +"@babel/helpers@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/helpers@npm:7.23.5" dependencies: "@babel/template": ^7.22.15 - "@babel/traverse": ^7.23.0 - "@babel/types": ^7.23.0 - checksum: acfc345102045c24ea2a4d60e00dcf8220e215af3add4520e2167700661338e6a80bd56baf44bb764af05ec6621101c9afc315dc107e18c61fa6da8acbdbb893 + "@babel/traverse": ^7.23.5 + "@babel/types": ^7.23.5 + checksum: c16dc8a3bb3d0e02c7ee1222d9d0865ed4b92de44fb8db43ff5afd37a0fc9ea5e2906efa31542c95b30c1a3a9540d66314663c9a23b5bb9b5ec76e8ebc896064 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/helpers@npm:7.23.6" + dependencies: + "@babel/template": ^7.22.15 + "@babel/traverse": ^7.23.6 + "@babel/types": ^7.23.6 + checksum: c5ba62497e1d717161d107c4b3de727565c68b6b9f50f59d6298e613afeca8895799b227c256e06d362e565aec34e26fb5c675b9c3d25055c52b945a21c21e21 languageName: node linkType: hard @@ -1791,7 +1928,36 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.22.11, @babel/parser@npm:^7.22.5": +"@babel/highlight@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/highlight@npm:7.23.4" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: 643acecdc235f87d925979a979b539a5d7d1f31ae7db8d89047269082694122d11aa85351304c9c978ceeb6d250591ccadb06c366f358ccee08bb9c122476b89 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/parser@npm:7.23.5" + bin: + parser: ./bin/babel-parser.js + checksum: ea763629310f71580c4a3ea9d3705195b7ba994ada2cc98f9a584ebfdacf54e92b2735d351672824c2c2b03c7f19206899f4d95650d85ce514a822b19a8734c7 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/parser@npm:7.23.6" + bin: + parser: ./bin/babel-parser.js + checksum: 140801c43731a6c41fd193f5c02bc71fd647a0360ca616b23d2db8be4b9739b9f951a03fc7c2db4f9b9214f4b27c1074db0f18bc3fa653783082d5af7c8860d5 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.22.11, @babel/parser@npm:^7.22.5": version: 7.22.14 resolution: "@babel/parser@npm:7.22.14" bin: @@ -1800,7 +1966,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0": +"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0": version: 7.23.0 resolution: "@babel/parser@npm:7.23.0" bin: @@ -1809,31 +1975,43 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.5" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0 - checksum: 1e353a060fb2cd8f1256d28cd768f16fb02513f905b9b6d656fb0242c96c341a196fa188b27c2701506a6e27515359fbcc1a5ca7fa8b9b530cf88fbd137baefc + checksum: ddbaf2c396b7780f15e80ee01d6dd790db076985f3dfeb6527d1a8d4cacf370e49250396a3aa005b2c40233cac214a106232f83703d5e8491848bde273938232 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.5" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 - "@babel/plugin-transform-optional-chaining": ^7.22.5 + "@babel/plugin-transform-optional-chaining": ^7.23.3 peerDependencies: "@babel/core": ^7.13.0 - checksum: 16e7a5f3bf2f2ac0ca032a70bf0ebd7e886d84dbb712b55c0643c04c495f0f221fbcbca14b5f8f8027fa6c87a3dafae0934022ad2b409384af6c5c356495b7bd + checksum: 434b9d710ae856fa1a456678cc304fbc93915af86d581ee316e077af746a709a741ea39d7e1d4f5b98861b629cc7e87f002d3138f5e836775632466d4c74aef2 languageName: node linkType: hard -"@babel/plugin-proposal-class-properties@npm:^7.0.0, @babel/plugin-proposal-class-properties@npm:^7.12.1": +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.23.3" + dependencies: + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 4690123f0ef7c11d6bf1a9579e4f463ce363563b75ec3f6ca66cf68687e39d8d747a82c833847653962f79da367eca895d9095c60d8ebb224a1d4277003acc11 + languageName: node + linkType: hard + +"@babel/plugin-proposal-class-properties@npm:^7.0.0": version: 7.18.6 resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" dependencies: @@ -1845,59 +2023,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-decorators@npm:^7.12.12": - version: 7.22.10 - resolution: "@babel/plugin-proposal-decorators@npm:7.22.10" - dependencies: - "@babel/helper-create-class-features-plugin": ^7.22.10 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.9 - "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/plugin-syntax-decorators": ^7.22.10 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: dc5d220f94d3d02a4a68d2e29abc038fe1731634569279956438ba4476e930dab76eac71524be89c9f199731f730e7d2537f947acbefaf29a51130a0e714ecbd - languageName: node - linkType: hard - -"@babel/plugin-proposal-export-default-from@npm:^7.12.1": - version: 7.22.5 - resolution: "@babel/plugin-proposal-export-default-from@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/plugin-syntax-export-default-from": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: fc0ddcebdd1991ad4c82a3a797b7c8ef21bb76dc09df52a58f23a648bfa0db5c3cd580ade9ac28fcecdb1b3cd61f53fbee504b0d99c689929c33da0a7dd4e4f4 - languageName: node - linkType: hard - -"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1": - version: 7.18.6 - resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 949c9ddcdecdaec766ee610ef98f965f928ccc0361dd87cf9f88cf4896a6ccd62fce063d4494778e50da99dea63d270a1be574a62d6ab81cbe9d85884bf55a7d - languageName: node - linkType: hard - -"@babel/plugin-proposal-object-rest-spread@npm:7.12.1": - version: 7.12.1 - resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.12.1" - dependencies: - "@babel/helper-plugin-utils": ^7.10.4 - "@babel/plugin-syntax-object-rest-spread": ^7.8.0 - "@babel/plugin-transform-parameters": ^7.12.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 221a41630c9a7162bf0416c71695b3f7f38482078a1d0d3af7abdc4f07ea1c9feed890399158d56c1d0278c971fe6f565ce822e9351e4481f7d98e9ff735dced - languageName: node - linkType: hard - -"@babel/plugin-proposal-object-rest-spread@npm:^7.0.0, @babel/plugin-proposal-object-rest-spread@npm:^7.12.1": +"@babel/plugin-proposal-object-rest-spread@npm:^7.0.0": version: 7.20.7 resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" dependencies: @@ -1912,31 +2038,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.12.7": - version: 7.21.0 - resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 - "@babel/plugin-syntax-optional-chaining": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 11c5449e01b18bb8881e8e005a577fa7be2fe5688e2382c8822d51f8f7005342a301a46af7b273b1f5645f9a7b894c428eee8526342038a275ef6ba4c8d8d746 - languageName: node - linkType: hard - -"@babel/plugin-proposal-private-methods@npm:^7.12.1": - version: 7.18.6 - resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" - dependencies: - "@babel/helper-create-class-features-plugin": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 22d8502ee96bca99ad2c8393e8493e2b8d4507576dd054490fd8201a36824373440106f5b098b6d821b026c7e72b0424ff4aeca69ed5f42e48f029d3a156d5ad - languageName: node - linkType: hard - "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2": version: 7.21.0-placeholder-for-preset-env.2 resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2" @@ -1946,20 +2047,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1": - version: 7.21.11 - resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.11" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-create-class-features-plugin": ^7.21.0 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/plugin-syntax-private-property-in-object": ^7.14.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 1b880543bc5f525b360b53d97dd30807302bb82615cd42bf931968f59003cac75629563d6b104868db50abd22235b3271fdf679fea5db59a267181a99cc0c265 - languageName: node - linkType: hard - "@babel/plugin-syntax-async-generators@npm:^7.8.4": version: 7.8.4 resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" @@ -1971,6 +2058,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3a10849d83e47aec50f367a9e56a6b22d662ddce643334b087f9828f4c3dd73bdc5909aaeabe123fed78515767f9ca43498a0e621c438d1cd2802d7fae3c9648 + languageName: node + linkType: hard + "@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" @@ -1993,17 +2091,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-decorators@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-syntax-decorators@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: baaa10fa52d76ee8b9447f7aedb1c8df7cf2ef83ae29c085c07444e691685aa8b1a326dfb7a3a0e3ae4d5f9fd083175e46ea5e2316d8200f0278f3fd54a58696 - languageName: node - linkType: hard - "@babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" @@ -2015,17 +2102,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-export-default-from@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-export-default-from@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 5b66dea77f9e8e6307b01827a229a49bd98f0928e21bffadf538201fd2705838e621bc80d712fbe48f9f6b1348b78aa95c1e5d5ab75773521ccc399d26152de7 - languageName: node - linkType: hard - "@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" @@ -2037,18 +2113,29 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-flow@npm:^7.0.0, @babel/plugin-syntax-flow@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-flow@npm:7.22.5" +"@babel/plugin-syntax-flow@npm:^7.0.0, @babel/plugin-syntax-flow@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-syntax-flow@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 84c8c40fcfe8e78cecdd6fb90e8f97f419e3f3b27a33de8324ae97d5ce1b87cdd98a636fa21a68d4d2c37c7d63f3a279bb84b6956b849921affed6b806b6ffe7 + checksum: c6e6f355d6ace5f4a9e7bb19f1fed2398aeb9b62c4c671a189d81b124f9f5bb77c4225b6e85e19339268c60a021c1e49104e450375de5e6bb70612190d9678af languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.20.0, @babel/plugin-syntax-import-assertions@npm:^7.22.5": +"@babel/plugin-syntax-import-assertions@npm:^7.20.0, @babel/plugin-syntax-import-assertions@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.23.3" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 883e6b35b2da205138caab832d54505271a3fee3fc1e8dc0894502434fc2b5d517cbe93bbfbfef8068a0fb6ec48ebc9eef3f605200a489065ba43d8cddc1c9a7 + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-assertions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" dependencies: @@ -2059,14 +2146,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.22.5" +"@babel/plugin-syntax-import-attributes@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 197b3c5ea2a9649347f033342cb222ab47f4645633695205c0250c6bf2af29e643753b8bb24a2db39948bef08e7c540babfd365591eb57fc110cb30b425ffc47 + checksum: 9aed7661ffb920ca75df9f494757466ca92744e43072e0848d87fa4aa61a3f2ee5a22198ac1959856c036434b5614a8f46f1fb70298835dbe28220cdd1d4c11e languageName: node linkType: hard @@ -2092,18 +2179,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:7.12.1": - version: 7.12.1 - resolution: "@babel/plugin-syntax-jsx@npm:7.12.1" +"@babel/plugin-syntax-jsx@npm:^7.0.0, @babel/plugin-syntax-jsx@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-syntax-jsx@npm:7.23.3" dependencies: - "@babel/helper-plugin-utils": ^7.10.4 + "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d4b9b589c484b2e0856799770f060dff34c67b24d7f4526f66309a0e0e9cf388a5c1f2c0da329d1973cc87d1b2cede8f3dc8facfac59e785d6393a003bcdd0f9 + checksum: 89037694314a74e7f0e7a9c8d3793af5bf6b23d80950c29b360db1c66859d67f60711ea437e70ad6b5b4b29affe17eababda841b6c01107c2b638e0493bafb4e languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.0.0, @babel/plugin-syntax-jsx@npm:^7.12.13, @babel/plugin-syntax-jsx@npm:^7.22.5": +"@babel/plugin-syntax-jsx@npm:^7.12.13, @babel/plugin-syntax-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" dependencies: @@ -2147,7 +2234,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-object-rest-spread@npm:7.8.3, @babel/plugin-syntax-object-rest-spread@npm:^7.0.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3": +"@babel/plugin-syntax-object-rest-spread@npm:^7.0.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" dependencies: @@ -2202,14 +2289,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" +"@babel/plugin-syntax-typescript@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-syntax-typescript@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8ab7718fbb026d64da93681a57797d60326097fd7cb930380c8bffd9eb101689e90142c760a14b51e8e69c88a73ba3da956cb4520a3b0c65743aee5c71ef360a + checksum: abfad3a19290d258b028e285a1f34c9b8a0cbe46ef79eafed4ed7ffce11b5d0720b5e536c82f91cbd8442cde35a3dd8e861fa70366d87ff06fdc0d4756e30876 languageName: node linkType: hard @@ -2225,74 +2312,63 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.0.0, @babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.22.5" +"@babel/plugin-transform-arrow-functions@npm:^7.0.0, @babel/plugin-transform-arrow-functions@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 35abb6c57062802c7ce8bd96b2ef2883e3124370c688bbd67609f7d2453802fb73944df8808f893b6c67de978eb2bcf87bbfe325e46d6f39b5fcb09ece11d01a + checksum: 1e99118176e5366c2636064d09477016ab5272b2a92e78b8edb571d20bc3eaa881789a905b20042942c3c2d04efc530726cf703f937226db5ebc495f5d067e66 languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.22.11" +"@babel/plugin-transform-async-generator-functions@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.23.4" dependencies: - "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.20 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-remap-async-to-generator": ^7.22.9 + "@babel/helper-remap-async-to-generator": ^7.22.20 "@babel/plugin-syntax-async-generators": ^7.8.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f11227a1d2831972a7fe28ed54a618ee251547632dc384b2f291f9d8d6aae1177a68c6bbd7709ab78275fa84e757ae795ec08061d94f6f01826f02a35ee875d4 + checksum: e2fc132c9033711d55209f4781e1fc73f0f4da5e0ca80a2da73dec805166b73c92a6e83571a8994cd2c893a28302e24107e90856202b24781bab734f800102bb languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" +"@babel/plugin-transform-async-to-generator@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.23.3" dependencies: - "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-module-imports": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-remap-async-to-generator": ^7.22.5 + "@babel/helper-remap-async-to-generator": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b95f23f99dcb379a9f0a1c2a3bbea3f8dc0e1b16dc1ac8b484fe378370169290a7a63d520959a9ba1232837cf74a80e23f6facbe14fd42a3cda6d3c2d7168e62 + checksum: 2e9d9795d4b3b3d8090332104e37061c677f29a1ce65bcbda4099a32d243e5d9520270a44bbabf0fb1fb40d463bd937685b1a1042e646979086c546d55319c3c languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" +"@babel/plugin-transform-block-scoped-functions@npm:^7.0.0, @babel/plugin-transform-block-scoped-functions@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 416b1341858e8ca4e524dee66044735956ced5f478b2c3b9bc11ec2285b0c25d7dbb96d79887169eb938084c95d0a89338c8b2fe70d473bd9dc92e5d9db1732c + checksum: e63b16d94ee5f4d917e669da3db5ea53d1e7e79141a2ec873c1e644678cdafe98daa556d0d359963c827863d6b3665d23d4938a94a4c5053a1619c4ebd01d020 languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.0.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.23.0" +"@babel/plugin-transform-block-scoping@npm:^7.0.0, @babel/plugin-transform-block-scoping@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-block-scoping@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0cfe925cc3b5a3ad407e2253fab3ceeaa117a4b291c9cb245578880872999bca91bd83ffa0128ae9ca356330702e1ef1dcb26804f28d2cef678239caf629f73e - languageName: node - linkType: hard - -"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-block-scoping@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: b1d06f358dedcb748a57e5feea4b9285c60593fb2912b921f22898c57c552c78fe18128678c8f84dd4ea1d4e5aebede8783830b24cd63f22c30261156d78bc77 + checksum: fc4b2100dd9f2c47d694b4b35ae8153214ccb4e24ef545c259a9db17211b18b6a430f22799b56db8f6844deaeaa201af45a03331d0c80cc28b0c4e3c814570e4 languageName: node linkType: hard @@ -2308,135 +2384,117 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-class-static-block@npm:7.22.11" +"@babel/plugin-transform-class-properties@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-class-properties@npm:7.23.3" dependencies: - "@babel/helper-create-class-features-plugin": ^7.22.11 + "@babel/helper-create-class-features-plugin": ^7.22.15 + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 9c6f8366f667897541d360246de176dd29efc7a13d80a5b48361882f7173d9173be4646c3b7d9b003ccc0e01e25df122330308f33db921fa553aa17ad544b3fc + languageName: node + linkType: hard + +"@babel/plugin-transform-class-static-block@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-class-static-block@npm:7.23.4" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-class-static-block": ^7.14.5 peerDependencies: "@babel/core": ^7.12.0 - checksum: 69f040506fad66f1c6918d288d0e0edbc5c8a07c8b4462c1184ad2f9f08995d68b057126c213871c0853ae0c72afc60ec87492049dfacb20902e32346a448bcb + checksum: c8bfaba19a674fc2eb54edad71e958647360474e3163e8226f1acd63e4e2dbec32a171a0af596c1dc5359aee402cc120fea7abd1fb0e0354b6527f0fc9e8aa1e languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0": - version: 7.22.15 - resolution: "@babel/plugin-transform-classes@npm:7.22.15" +"@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/plugin-transform-classes@npm:7.23.5" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 "@babel/helper-compilation-targets": ^7.22.15 - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 "@babel/helper-optimise-call-expression": ^7.22.5 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.9 + "@babel/helper-replace-supers": ^7.22.20 "@babel/helper-split-export-declaration": ^7.22.6 globals: ^11.1.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d3f4d0c107dd8a3557ea3575cc777fab27efa92958b41e4a9822f7499725c1f554beae58855de16ddec0a7b694e45f59a26cea8fbde4275563f72f09c6e039a0 + checksum: 6d0dd3b0828e84a139a51b368f33f315edee5688ef72c68ba25e0175c68ea7357f9c8810b3f61713e368a3063cdcec94f3a2db952e453b0b14ef428a34aa8169 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.22.6": - version: 7.22.6 - resolution: "@babel/plugin-transform-classes@npm:7.22.6" - dependencies: - "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-compilation-targets": ^7.22.6 - "@babel/helper-environment-visitor": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 - "@babel/helper-optimise-call-expression": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.5 - "@babel/helper-split-export-declaration": ^7.22.6 - globals: ^11.1.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 8380e855c01033dbc7460d9acfbc1fc37c880350fa798c2de8c594ef818ade0e4c96173ec72f05f2a4549d8d37135e18cb62548352d51557b45a0fb4388d2f3f - languageName: node - linkType: hard - -"@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" +"@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-computed-properties@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/template": ^7.22.5 + "@babel/template": ^7.22.15 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c2a77a0f94ec71efbc569109ec14ea2aa925b333289272ced8b33c6108bdbb02caf01830ffc7e49486b62dec51911924d13f3a76f1149f40daace1898009e131 + checksum: 80452661dc25a0956f89fe98cb562e8637a9556fb6c00d312c57653ce7df8798f58d138603c7e1aad96614ee9ccd10c47e50ab9ded6b6eded5adeb230d2a982e languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.0.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-destructuring@npm:7.23.0" +"@babel/plugin-transform-destructuring@npm:^7.0.0, @babel/plugin-transform-destructuring@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-destructuring@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cd6dd454ccc2766be551e4f8a04b1acc2aa539fa19e5c7501c56cc2f8cc921dd41a7ffb78455b4c4b2f954fcab8ca4561ba7c9c7bd5af9f19465243603d18cc3 + checksum: 9e015099877272501162419bfe781689aec5c462cd2aec752ee22288f209eec65969ff11b8fdadca2eaddea71d705d3bba5b9c60752fcc1be67874fcec687105 languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-destructuring@npm:7.22.10" +"@babel/plugin-transform-dotall-regex@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.23.3" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.22.15 + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a2dbbf7f1ea16a97948c37df925cb364337668c41a3948b8d91453f140507bd8a3429030c7ce66d09c299987b27746c19a2dd18b6f17dcb474854b14fd9159a3 + languageName: node + linkType: hard + +"@babel/plugin-transform-duplicate-keys@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 011707801bd0029fd4f0523d24d06fdc0cbe8c9da280d75728f76713d639c4dc976e1b56a1ba7bff25468f86867efb71c9b4cac81140adbdd0abf2324b19a8bb + checksum: c2a21c34dc0839590cd945192cbc46fde541a27e140c48fe1808315934664cdbf18db64889e23c4eeb6bad9d3e049482efdca91d29de5734ffc887c4fbabaa16 languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 409b658d11e3082c8f69e9cdef2d96e4d6d11256f005772425fb230cc48fd05945edbfbcb709dab293a1a2f01f9c8a5bb7b4131e632b23264039d9f95864b453 - languageName: node - linkType: hard - -"@babel/plugin-transform-duplicate-keys@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: bb1280fbabaab6fab2ede585df34900712698210a3bd413f4df5bae6d8c24be36b496c92722ae676a7a67d060a4624f4d6c23b923485f906bfba8773c69f55b4 - languageName: node - linkType: hard - -"@babel/plugin-transform-dynamic-import@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.22.11" +"@babel/plugin-transform-dynamic-import@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-dynamic-import": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 78fc9c532210bf9e8f231747f542318568ac360ee6c27e80853962c984283c73da3f8f8aebe83c2096090a435b356b092ed85de617a156cbe0729d847632be45 + checksum: 57a722604c430d9f3dacff22001a5f31250e34785d4969527a2ae9160fa86858d0892c5b9ff7a06a04076f8c76c9e6862e0541aadca9c057849961343aab0845 languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.22.5" +"@babel/plugin-transform-exponentiation-operator@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.23.3" dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": ^7.22.5 + "@babel/helper-builder-binary-assignment-operator-visitor": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f2d660c1b1d51ad5fec1cd5ad426a52187204068c4158f8c4aa977b31535c61b66898d532603eef21c15756827be8277f724c869b888d560f26d7fe848bb5eae + checksum: 00d05ab14ad0f299160fcf9d8f55a1cc1b740e012ab0b5ce30207d2365f091665115557af7d989cd6260d075a252d9e4283de5f2b247dfbbe0e42ae586e6bf66 languageName: node linkType: hard @@ -2452,160 +2510,160 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-flow-strip-types@npm:7.22.5" +"@babel/plugin-transform-export-namespace-from@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/plugin-syntax-flow": ^7.22.5 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 1ba48187d6f33814be01c6870489f0b1858256cf2b9dd7e62f02af8b30049bf375112f1d44692c5fed3cb9cd26ee2fb32e358cd79b6ad2360a51e8f993e861bf + checksum: 9f770a81bfd03b48d6ba155d452946fd56d6ffe5b7d871e9ec2a0b15e0f424273b632f3ed61838b90015b25bbda988896b7a46c7d964fbf8f6feb5820b309f93 + languageName: node + linkType: hard + +"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-flow-strip-types@npm:7.23.3" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + "@babel/plugin-syntax-flow": ^7.23.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: de38cc5cf948bc19405ea041292181527a36f59f08d787a590415fac36e9b0c7992f0d3e2fd3b9402089bafdaa1a893291a0edf15beebfd29bdedbbe582fee9b languageName: node linkType: hard "@babel/plugin-transform-for-of@npm:^7.0.0": - version: 7.22.15 - resolution: "@babel/plugin-transform-for-of@npm:7.22.15" + version: 7.23.6 + resolution: "@babel/plugin-transform-for-of@npm:7.23.6" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 228c060aa61f6aa89dc447170075f8214863b94f830624e74ade99c1a09316897c12d76e848460b0b506593e58dbc42739af6dc4cb0fe9b84dffe4a596050a36 + languageName: node + linkType: hard + +"@babel/plugin-transform-for-of@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-for-of@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f395ae7bce31e14961460f56cf751b5d6e37dd27d7df5b1f4e49fec1c11b6f9cf71991c7ffbe6549878591e87df0d66af798cf26edfa4bfa6b4c3dba1fb2f73a + checksum: a6288122a5091d96c744b9eb23dc1b2d4cce25f109ac1e26a0ea03c4ea60330e6f3cc58530b33ba7369fa07163b71001399a145238b7e92bff6270ef3b9c32a0 languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-for-of@npm:7.22.5" +"@babel/plugin-transform-function-name@npm:^7.0.0, @babel/plugin-transform-function-name@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-function-name@npm:7.23.3" dependencies: + "@babel/helper-compilation-targets": ^7.22.15 + "@babel/helper-function-name": ^7.23.0 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d7b8d4db010bce7273674caa95c4e6abd909362866ce297e86a2ecaa9ae636e05d525415811db9b3c942155df7f3651d19b91dd6c41f142f7308a97c7cb06023 + checksum: 355c6dbe07c919575ad42b2f7e020f320866d72f8b79181a16f8e0cd424a2c761d979f03f47d583d9471b55dcd68a8a9d829b58e1eebcd572145b934b48975a6 languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.0.0, @babel/plugin-transform-function-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-function-name@npm:7.22.5" - dependencies: - "@babel/helper-compilation-targets": ^7.22.5 - "@babel/helper-function-name": ^7.22.5 - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: cff3b876357999cb8ae30e439c3ec6b0491a53b0aa6f722920a4675a6dd5b53af97a833051df4b34791fe5b3dd326ccf769d5c8e45b322aa50ee11a660b17845 - languageName: node - linkType: hard - -"@babel/plugin-transform-json-strings@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-json-strings@npm:7.22.11" +"@babel/plugin-transform-json-strings@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-json-strings@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-json-strings": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 50665e5979e66358c50e90a26db53c55917f78175127ac2fa05c7888d156d418ffb930ec0a109353db0a7c5f57c756ce01bfc9825d24cbfd2b3ec453f2ed8cba + checksum: f9019820233cf8955d8ba346df709a0683c120fe86a24ed1c9f003f2db51197b979efc88f010d558a12e1491210fc195a43cd1c7fee5e23b92da38f793a875de languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.0.0, @babel/plugin-transform-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-literals@npm:7.22.5" +"@babel/plugin-transform-literals@npm:^7.0.0, @babel/plugin-transform-literals@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-literals@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ec37cc2ffb32667af935ab32fe28f00920ec8a1eb999aa6dc6602f2bebd8ba205a558aeedcdccdebf334381d5c57106c61f52332045730393e73410892a9735b + checksum: 519a544cd58586b9001c4c9b18da25a62f17d23c48600ff7a685d75ca9eb18d2c5e8f5476f067f0a8f1fea2a31107eff950b9864833061e6076dcc4bdc3e71ed languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.22.11" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c664e9798e85afa7f92f07b867682dee7392046181d82f5d21bae6f2ca26dfe9c8375cdc52b7483c3fc09a983c1989f60eff9fbc4f373b0c0a74090553d05739 + checksum: 2ae1dc9b4ff3bf61a990ff3accdecb2afe3a0ca649b3e74c010078d1cdf29ea490f50ac0a905306a2bcf9ac177889a39ac79bdcc3a0fdf220b3b75fac18d39b5 languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.22.5" +"@babel/plugin-transform-member-expression-literals@npm:^7.0.0, @babel/plugin-transform-member-expression-literals@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ec4b0e07915ddd4fda0142fd104ee61015c208608a84cfa13643a95d18760b1dc1ceb6c6e0548898b8c49e5959a994e46367260176dbabc4467f729b21868504 + checksum: 95cec13c36d447c5aa6b8e4c778b897eeba66dcb675edef01e0d2afcec9e8cb9726baf4f81b4bbae7a782595aed72e6a0d44ffb773272c3ca180fada99bf92db languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-modules-amd@npm:7.22.5" +"@babel/plugin-transform-modules-amd@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-modules-amd@npm:7.23.3" dependencies: - "@babel/helper-module-transforms": ^7.22.5 + "@babel/helper-module-transforms": ^7.23.3 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7da4c4ebbbcf7d182abb59b2046b22d86eee340caf8a22a39ef6a727da2d8acfec1f714fcdcd5054110b280e4934f735e80a6848d192b6834c5d4459a014f04d + checksum: d163737b6a3d67ea579c9aa3b83d4df4b5c34d9dcdf25f415f027c0aa8cded7bac2750d2de5464081f67a042ad9e1c03930c2fab42acd79f9e57c00cf969ddff languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.0.0": - version: 7.23.0 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.23.0" +"@babel/plugin-transform-modules-commonjs@npm:^7.0.0, @babel/plugin-transform-modules-commonjs@npm:^7.23.0, @babel/plugin-transform-modules-commonjs@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.23.3" dependencies: - "@babel/helper-module-transforms": ^7.23.0 + "@babel/helper-module-transforms": ^7.23.3 "@babel/helper-plugin-utils": ^7.22.5 "@babel/helper-simple-access": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 7fb25997194053e167c4207c319ff05362392da841bd9f42ddb3caf9c8798a5d203bd926d23ddf5830fdf05eddc82c2810f40d1287e3a4f80b07eff13d1024b5 + checksum: 720a231ceade4ae4d2632478db4e7fecf21987d444942b72d523487ac8d715ca97de6c8f415c71e939595e1a4776403e7dc24ed68fe9125ad4acf57753c9bff7 languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.22.11" - dependencies: - "@babel/helper-module-transforms": ^7.22.9 - "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-simple-access": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c15ad7f1234a930cab214224bb85f6b3a3f301fa1d4d15bef193e5c11c614ce369551e5cbb708fde8d3f7e1cb84b05e9798a3647a11b56c3d67580e362a712d4 - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-systemjs@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.22.11" +"@babel/plugin-transform-modules-systemjs@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.3" dependencies: "@babel/helper-hoist-variables": ^7.22.5 - "@babel/helper-module-transforms": ^7.22.9 + "@babel/helper-module-transforms": ^7.23.3 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-identifier": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d0991e4bdc3352b6a9f4d12b6662e3645d892cd5c3c005ba5f14e65f1e218c6a8f7f4497e64a51d82a046e507aaa7db3143b800b0270dca1824cbd214ff3363d + checksum: 0d2fdd993c785aecac9e0850cd5ed7f7d448f0fbb42992a950cc0590167144df25d82af5aac9a5c99ef913d2286782afa44e577af30c10901c5ee8984910fa1f languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-modules-umd@npm:7.22.5" +"@babel/plugin-transform-modules-umd@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-modules-umd@npm:7.23.3" dependencies: - "@babel/helper-module-transforms": ^7.22.5 + "@babel/helper-module-transforms": ^7.23.3 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 46622834c54c551b231963b867adbc80854881b3e516ff29984a8da989bd81665bd70e8cba6710345248e97166689310f544aee1a5773e262845a8f1b3e5b8b4 + checksum: 586a7a2241e8b4e753a37af9466a9ffa8a67b4ba9aa756ad7500712c05d8fa9a8c1ed4f7bd25fae2a8265e6cf8fe781ec85a8ee885dd34cf50d8955ee65f12dc languageName: node linkType: hard @@ -2621,14 +2679,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-new-target@npm:7.22.5" +"@babel/plugin-transform-new-target@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-new-target@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6b72112773487a881a1d6ffa680afde08bad699252020e86122180ee7a88854d5da3f15d9bca3331cf2e025df045604494a8208a2e63b486266b07c14e2ffbf3 + checksum: e5053389316fce73ad5201b7777437164f333e24787fbcda4ae489cd2580dbbbdfb5694a7237bad91fabb46b591d771975d69beb1c740b82cb4761625379f00b languageName: node linkType: hard @@ -2644,6 +2702,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.23.4" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a27d73ea134d3d9560a6b2e26ab60012fba15f1db95865aa0153c18f5ec82cfef6a7b3d8df74e3c2fca81534fa5efeb6cacaf7b08bdb7d123e3dafdd079886a3 + languageName: node + linkType: hard + "@babel/plugin-transform-numeric-separator@npm:^7.22.11": version: 7.22.11 resolution: "@babel/plugin-transform-numeric-separator@npm:7.22.11" @@ -2656,77 +2726,78 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.22.11" +"@babel/plugin-transform-numeric-separator@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.23.4" dependencies: - "@babel/compat-data": ^7.22.9 - "@babel/helper-compilation-targets": ^7.22.10 + "@babel/helper-plugin-utils": ^7.22.5 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6ba0e5db3c620a3ec81f9e94507c821f483c15f196868df13fa454cbac719a5449baf73840f5b6eb7d77311b24a2cf8e45db53700d41727f693d46f7caf3eec3 + languageName: node + linkType: hard + +"@babel/plugin-transform-object-rest-spread@npm:^7.22.15, @babel/plugin-transform-object-rest-spread@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.23.4" + dependencies: + "@babel/compat-data": ^7.23.3 + "@babel/helper-compilation-targets": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-object-rest-spread": ^7.8.3 - "@babel/plugin-transform-parameters": ^7.22.5 + "@babel/plugin-transform-parameters": ^7.23.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b9c9ed8df8d6d7563eb42844d8e3e6748ba8f7568998230f7317bc49304db65828df48fc4b93bf4421772a6c9f7b389f3dd1c4e84379c17dd9ee223fb3fc5245 + checksum: 73fec495e327ca3959c1c03d07a621be09df00036c69fff0455af9a008291677ee9d368eec48adacdc6feac703269a649747568b4af4c4e9f134aa71cc5b378d languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-object-super@npm:7.22.5" +"@babel/plugin-transform-object-super@npm:^7.0.0, @babel/plugin-transform-object-super@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-object-super@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-replace-supers": ^7.22.5 + "@babel/helper-replace-supers": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b71887877d74cb64dbccb5c0324fa67e31171e6a5311991f626650e44a4083e5436a1eaa89da78c0474fb095d4ec322d63ee778b202d33aa2e4194e1ed8e62d7 + checksum: e495497186f621fa79026e183b4f1fbb172fd9df812cbd2d7f02c05b08adbe58012b1a6eb6dd58d11a30343f6ec80d0f4074f9b501d70aa1c94df76d59164c53 languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.22.11" +"@babel/plugin-transform-optional-catch-binding@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f17abd90e1de67c84d63afea29c8021c74abb2794d3a6eeafb0bbe7372d3db32aefca386e392116ec63884537a4a2815d090d26264d259bacc08f6e3ed05294c + checksum: d50b5ee142cdb088d8b5de1ccf7cea85b18b85d85b52f86618f6e45226372f01ad4cdb29abd4fd35ea99a71fefb37009e0107db7a787dcc21d4d402f97470faf languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.22.12, @babel/plugin-transform-optional-chaining@npm:^7.22.5": - version: 7.22.12 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.22.12" +"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.23.3, @babel/plugin-transform-optional-chaining@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.23.4" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 "@babel/plugin-syntax-optional-chaining": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 47065439bb721a0967cdcc83895700bb7b18b146b2ef27e43449d7b5a7130a2497afadddc42c616253858cac6732546646b9f0c581f4bb8a3d362baeb4c30bbb + checksum: e7a4c08038288057b7a08d68c4d55396ada9278095509ca51ed8dfb72a7f13f26bdd7c5185de21079fe0a9d60d22c227cb32e300d266c1bda40f70eee9f4bc1e languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.0.0": - version: 7.22.15 - resolution: "@babel/plugin-transform-parameters@npm:7.22.15" +"@babel/plugin-transform-parameters@npm:^7.0.0, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-parameters@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 541188bb7d1876cad87687b5c7daf90f63d8208ae83df24acb1e2b05020ad1c78786b2723ca4054a83fcb74fb6509f30c4cacc5b538ee684224261ad5fb047c1 - languageName: node - linkType: hard - -"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-parameters@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": ^7.22.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: b44f89cf97daf23903776ba27c2ab13b439d80d8c8a95be5c476ab65023b1e0c0e94c28d3745f3b60a58edc4e590fa0cd4287a0293e51401ca7d29a2ddb13b8e + checksum: a735b3e85316d17ec102e3d3d1b6993b429bdb3b494651c9d754e3b7d270462ee1f1a126ccd5e3d871af5e683727e9ef98c9d34d4a42204fffaabff91052ed16 languageName: node linkType: hard @@ -2742,39 +2813,51 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.22.11" +"@babel/plugin-transform-private-methods@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-private-methods@npm:7.23.3" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.22.15 + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cedc1285c49b5a6d9a3d0e5e413b756ac40b3ac2f8f68bdfc3ae268bc8d27b00abd8bb0861c72756ff5dd8bf1eb77211b7feb5baf4fdae2ebbaabe49b9adc1d0 + languageName: node + linkType: hard + +"@babel/plugin-transform-private-property-in-object@npm:^7.23.4": + version: 7.23.4 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.23.4" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-create-class-features-plugin": ^7.22.11 + "@babel/helper-create-class-features-plugin": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 "@babel/plugin-syntax-private-property-in-object": ^7.14.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4d029d84901e53c46dead7a46e2990a7bc62470f4e4ca58a0d063394f86652fd58fe4eea1eb941da3669cd536b559b9d058b342b59300026346b7a2a51badac8 + checksum: fb7adfe94ea97542f250a70de32bddbc3e0b802381c92be947fec83ebffda57e68533c4d0697152719a3496fdd3ebf3798d451c024cd4ac848fc15ac26b70aa7 languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-property-literals@npm:7.22.5" +"@babel/plugin-transform-property-literals@npm:^7.0.0, @babel/plugin-transform-property-literals@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-property-literals@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 796176a3176106f77fcb8cd04eb34a8475ce82d6d03a88db089531b8f0453a2fb8b0c6ec9a52c27948bc0ea478becec449893741fc546dfc3930ab927e3f9f2e + checksum: 16b048c8e87f25095f6d53634ab7912992f78e6997a6ff549edc3cf519db4fca01c7b4e0798530d7f6a05228ceee479251245cdd850a5531c6e6f404104d6cc9 languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.0.0, @babel/plugin-transform-react-display-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" +"@babel/plugin-transform-react-display-name@npm:^7.0.0, @babel/plugin-transform-react-display-name@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-react-display-name@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a12bfd1e4e93055efca3ace3c34722571bda59d9740dca364d225d9c6e3ca874f134694d21715c42cc63d79efd46db9665bd4a022998767f9245f1e29d5d204d + checksum: 7f86964e8434d3ddbd3c81d2690c9b66dbf1cd8bd9512e2e24500e9fa8cf378bc52c0853270b3b82143aba5965aec04721df7abdb768f952b44f5c6e0b198779 languageName: node linkType: hard @@ -2800,7 +2883,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-source@npm:^7.18.6, @babel/plugin-transform-react-jsx-source@npm:^7.19.6": +"@babel/plugin-transform-react-jsx-source@npm:^7.19.6": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-source@npm:7.22.5" dependencies: @@ -2811,22 +2894,22 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.0.0": - version: 7.22.15 - resolution: "@babel/plugin-transform-react-jsx@npm:7.22.15" +"@babel/plugin-transform-react-jsx@npm:^7.0.0, @babel/plugin-transform-react-jsx@npm:^7.22.15": + version: 7.23.4 + resolution: "@babel/plugin-transform-react-jsx@npm:7.23.4" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 "@babel/helper-module-imports": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/plugin-syntax-jsx": ^7.22.5 - "@babel/types": ^7.22.15 + "@babel/plugin-syntax-jsx": ^7.23.3 + "@babel/types": ^7.23.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3899054e89550c3a0ef041af7c47ee266e2e934f498ee80fefeda778a6aa177b48aa8b4d2a8bf5848de977fec564571699ab952d9fa089c4c19b45ddb121df09 + checksum: d8b8c52e8e22e833bf77c8d1a53b0a57d1fd52ba9596a319d572de79446a8ed9d95521035bc1175c1589d1a6a34600d2e678fa81d81bac8fac121137097f1f0a languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.18.10, @babel/plugin-transform-react-jsx@npm:^7.19.0, @babel/plugin-transform-react-jsx@npm:^7.22.5": +"@babel/plugin-transform-react-jsx@npm:^7.19.0, @babel/plugin-transform-react-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5" dependencies: @@ -2841,176 +2924,193 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" +"@babel/plugin-transform-react-pure-annotations@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.23.3" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 092021c4f404e267002099ec20b3f12dd730cb90b0d83c5feed3dc00dbe43b9c42c795a18e7c6c7d7bddea20c7dd56221b146aec81b37f2e7eb5137331c61120 + checksum: 9ea3698b1d422561d93c0187ac1ed8f2367e4250b10e259785ead5aa643c265830fd0f4cf5087a5bedbc4007444c06da2f2006686613220acf0949895f453666 languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-regenerator@npm:7.22.10" +"@babel/plugin-transform-regenerator@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-regenerator@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 regenerator-transform: ^0.15.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e13678d62d6fa96f11cb8b863f00e8693491e7adc88bfca3f2820f80cbac8336e7dec3a596eee6a1c4663b7ececc3564f2cd7fb44ed6d4ce84ac2bb7f39ecc6e + checksum: 7fdacc7b40008883871b519c9e5cdea493f75495118ccc56ac104b874983569a24edd024f0f5894ba1875c54ee2b442f295d6241c3280e61c725d0dd3317c8e6 languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" +"@babel/plugin-transform-reserved-words@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-reserved-words@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3ffd7dbc425fe8132bfec118b9817572799cab1473113a635d25ab606c1f5a2341a636c04cf6b22df3813320365ed5a965b5eeb3192320a10e4cc2c137bd8bfc + checksum: 298c4440ddc136784ff920127cea137168e068404e635dc946ddb5d7b2a27b66f1dd4c4acb01f7184478ff7d5c3e7177a127279479926519042948fb7fa0fa48 languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.22.5" +"@babel/plugin-transform-runtime@npm:^7.23.2": + version: 7.23.4 + resolution: "@babel/plugin-transform-runtime@npm:7.23.4" + dependencies: + "@babel/helper-module-imports": ^7.22.15 + "@babel/helper-plugin-utils": ^7.22.5 + babel-plugin-polyfill-corejs2: ^0.4.6 + babel-plugin-polyfill-corejs3: ^0.8.5 + babel-plugin-polyfill-regenerator: ^0.5.3 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a1693d27cd5ce17d0917280942a62bbf4ee27f6f0fe7beb33789bdc699cda21e5253997663248b32e8e36c01ccd202f96246413b9328b70a05d4cf64faa3191e + languageName: node + linkType: hard + +"@babel/plugin-transform-shorthand-properties@npm:^7.0.0, @babel/plugin-transform-shorthand-properties@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a5ac902c56ea8effa99f681340ee61bac21094588f7aef0bc01dff98246651702e677552fa6d10e548c4ac22a3ffad047dd2f8c8f0540b68316c2c203e56818b + checksum: 5d677a03676f9fff969b0246c423d64d77502e90a832665dc872a5a5e05e5708161ce1effd56bb3c0f2c20a1112fca874be57c8a759d8b08152755519281f326 languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.0.0, @babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-spread@npm:7.22.5" +"@babel/plugin-transform-spread@npm:^7.0.0, @babel/plugin-transform-spread@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-spread@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 5587f0deb60b3dfc9b274e269031cc45ec75facccf1933ea2ea71ced9fd3ce98ed91bb36d6cd26817c14474b90ed998c5078415f0eab531caf301496ce24c95c + checksum: 8fd5cac201e77a0b4825745f4e07a25f923842f282f006b3a79223c00f61075c8868d12eafec86b2642cd0b32077cdd32314e27bcb75ee5e6a68c0144140dcf2 languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" +"@babel/plugin-transform-sticky-regex@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 63b2c575e3e7f96c32d52ed45ee098fb7d354b35c2223b8c8e76840b32cc529ee0c0ceb5742fd082e56e91e3d82842a367ce177e82b05039af3d602c9627a729 + checksum: 53e55eb2575b7abfdb4af7e503a2bf7ef5faf8bf6b92d2cd2de0700bdd19e934e5517b23e6dfed94ba50ae516b62f3f916773ef7d9bc81f01503f585051e2949 languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" +"@babel/plugin-transform-template-literals@npm:^7.0.0, @babel/plugin-transform-template-literals@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-template-literals@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 27e9bb030654cb425381c69754be4abe6a7c75b45cd7f962cd8d604b841b2f0fb7b024f2efc1c25cc53f5b16d79d5e8cfc47cacbdaa983895b3aeefa3e7e24ff + checksum: b16c5cb0b8796be0118e9c144d15bdc0d20a7f3f59009c6303a6e9a8b74c146eceb3f05186f5b97afcba7cfa87e34c1585a22186e3d5b22f2fd3d27d959d92b2 languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.22.5" +"@babel/plugin-transform-typeof-symbol@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 82a53a63ffc3010b689ca9a54e5f53b2718b9f4b4a9818f36f9b7dba234f38a01876680553d2716a645a61920b5e6e4aaf8d4a0064add379b27ca0b403049512 + checksum: 0af7184379d43afac7614fc89b1bdecce4e174d52f4efaeee8ec1a4f2c764356c6dba3525c0685231f1cbf435b6dd4ee9e738d7417f3b10ce8bbe869c32f4384 languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.22.11": - version: 7.22.11 - resolution: "@babel/plugin-transform-typescript@npm:7.22.11" +"@babel/plugin-transform-typescript@npm:^7.23.3": + version: 7.23.5 + resolution: "@babel/plugin-transform-typescript@npm:7.23.5" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 - "@babel/helper-create-class-features-plugin": ^7.22.11 + "@babel/helper-create-class-features-plugin": ^7.23.5 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/plugin-syntax-typescript": ^7.22.5 + "@babel/plugin-syntax-typescript": ^7.23.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a0dc3c2427b55602944705c9a91b4c074524badd5ea87edb603ddeabe7fae531bcbe68475106d7a00079b67bb422dbf2e9f50e15c25ac24d7e9fe77f37ebcfb4 + checksum: d77b5cc22cf48fe461de07e4f058dc9c0d8c4e3ca49de0e3a336a94ab39bfa4f4732598e36c479bec0dd1bf4aff9154bc2dcbfbe3145a751e4771ccae5afaaf8 languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.22.10" +"@babel/plugin-transform-unicode-escapes@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 807f40ed1324c8cb107c45358f1903384ca3f0ef1d01c5a3c5c9b271c8d8eec66936a3dcc8d75ddfceea9421420368c2e77ae3adef0a50557e778dfe296bf382 + checksum: 561c429183a54b9e4751519a3dfba6014431e9cdc1484fad03bdaf96582dfc72c76a4f8661df2aeeae7c34efd0fa4d02d3b83a2f63763ecf71ecc925f9cc1f60 languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-property-regex@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.23.3" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2495e5f663cb388e3d888b4ba3df419ac436a5012144ac170b622ddfc221f9ea9bdba839fa2bc0185cb776b578030666406452ec7791cbf0e7a3d4c88ae9574c + checksum: 2298461a194758086d17c23c26c7de37aa533af910f9ebf31ebd0893d4aa317468043d23f73edc782ec21151d3c46cf0ff8098a83b725c49a59de28a1d4d6225 languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-regex@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.23.3" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6b5d1404c8c623b0ec9bd436c00d885a17d6a34f3f2597996343ddb9d94f6379705b21582dfd4cec2c47fd34068872e74ab6b9580116c0566b3f9447e2a7fa06 + checksum: c5f835d17483ba899787f92e313dfa5b0055e3deab332f1d254078a2bba27ede47574b6599fcf34d3763f0c048ae0779dc21d2d8db09295edb4057478dc80a9a languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.22.5" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.23.3": + version: 7.23.3 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.23.3" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.22.5 + "@babel/helper-create-regexp-features-plugin": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0 - checksum: c042070f980b139547f8b0179efbc049ac5930abec7fc26ed7a41d89a048d8ab17d362200e204b6f71c3c20d6991a0e74415e1a412a49adc8131c2a40c04822e + checksum: 79d0b4c951955ca68235c87b91ab2b393c96285f8aeaa34d6db416d2ddac90000c9bd6e8c4d82b60a2b484da69930507245035f28ba63c6cae341cf3ba68fdef languageName: node linkType: hard -"@babel/preset-env@npm:^7.12.11": - version: 7.22.14 - resolution: "@babel/preset-env@npm:7.22.14" +"@babel/preset-env@npm:^7.23.2": + version: 7.23.5 + resolution: "@babel/preset-env@npm:7.23.5" dependencies: - "@babel/compat-data": ^7.22.9 - "@babel/helper-compilation-targets": ^7.22.10 + "@babel/compat-data": ^7.23.5 + "@babel/helper-compilation-targets": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.5 - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.22.5 - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.22.5 + "@babel/helper-validator-option": ^7.23.5 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.23.3 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.23.3 + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ^7.23.3 "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2 "@babel/plugin-syntax-async-generators": ^7.8.4 "@babel/plugin-syntax-class-properties": ^7.12.13 "@babel/plugin-syntax-class-static-block": ^7.14.5 "@babel/plugin-syntax-dynamic-import": ^7.8.3 "@babel/plugin-syntax-export-namespace-from": ^7.8.3 - "@babel/plugin-syntax-import-assertions": ^7.22.5 - "@babel/plugin-syntax-import-attributes": ^7.22.5 + "@babel/plugin-syntax-import-assertions": ^7.23.3 + "@babel/plugin-syntax-import-attributes": ^7.23.3 "@babel/plugin-syntax-import-meta": ^7.10.4 "@babel/plugin-syntax-json-strings": ^7.8.3 "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 @@ -3022,77 +3122,76 @@ __metadata: "@babel/plugin-syntax-private-property-in-object": ^7.14.5 "@babel/plugin-syntax-top-level-await": ^7.14.5 "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6 - "@babel/plugin-transform-arrow-functions": ^7.22.5 - "@babel/plugin-transform-async-generator-functions": ^7.22.11 - "@babel/plugin-transform-async-to-generator": ^7.22.5 - "@babel/plugin-transform-block-scoped-functions": ^7.22.5 - "@babel/plugin-transform-block-scoping": ^7.22.10 - "@babel/plugin-transform-class-properties": ^7.22.5 - "@babel/plugin-transform-class-static-block": ^7.22.11 - "@babel/plugin-transform-classes": ^7.22.6 - "@babel/plugin-transform-computed-properties": ^7.22.5 - "@babel/plugin-transform-destructuring": ^7.22.10 - "@babel/plugin-transform-dotall-regex": ^7.22.5 - "@babel/plugin-transform-duplicate-keys": ^7.22.5 - "@babel/plugin-transform-dynamic-import": ^7.22.11 - "@babel/plugin-transform-exponentiation-operator": ^7.22.5 - "@babel/plugin-transform-export-namespace-from": ^7.22.11 - "@babel/plugin-transform-for-of": ^7.22.5 - "@babel/plugin-transform-function-name": ^7.22.5 - "@babel/plugin-transform-json-strings": ^7.22.11 - "@babel/plugin-transform-literals": ^7.22.5 - "@babel/plugin-transform-logical-assignment-operators": ^7.22.11 - "@babel/plugin-transform-member-expression-literals": ^7.22.5 - "@babel/plugin-transform-modules-amd": ^7.22.5 - "@babel/plugin-transform-modules-commonjs": ^7.22.11 - "@babel/plugin-transform-modules-systemjs": ^7.22.11 - "@babel/plugin-transform-modules-umd": ^7.22.5 + "@babel/plugin-transform-arrow-functions": ^7.23.3 + "@babel/plugin-transform-async-generator-functions": ^7.23.4 + "@babel/plugin-transform-async-to-generator": ^7.23.3 + "@babel/plugin-transform-block-scoped-functions": ^7.23.3 + "@babel/plugin-transform-block-scoping": ^7.23.4 + "@babel/plugin-transform-class-properties": ^7.23.3 + "@babel/plugin-transform-class-static-block": ^7.23.4 + "@babel/plugin-transform-classes": ^7.23.5 + "@babel/plugin-transform-computed-properties": ^7.23.3 + "@babel/plugin-transform-destructuring": ^7.23.3 + "@babel/plugin-transform-dotall-regex": ^7.23.3 + "@babel/plugin-transform-duplicate-keys": ^7.23.3 + "@babel/plugin-transform-dynamic-import": ^7.23.4 + "@babel/plugin-transform-exponentiation-operator": ^7.23.3 + "@babel/plugin-transform-export-namespace-from": ^7.23.4 + "@babel/plugin-transform-for-of": ^7.23.3 + "@babel/plugin-transform-function-name": ^7.23.3 + "@babel/plugin-transform-json-strings": ^7.23.4 + "@babel/plugin-transform-literals": ^7.23.3 + "@babel/plugin-transform-logical-assignment-operators": ^7.23.4 + "@babel/plugin-transform-member-expression-literals": ^7.23.3 + "@babel/plugin-transform-modules-amd": ^7.23.3 + "@babel/plugin-transform-modules-commonjs": ^7.23.3 + "@babel/plugin-transform-modules-systemjs": ^7.23.3 + "@babel/plugin-transform-modules-umd": ^7.23.3 "@babel/plugin-transform-named-capturing-groups-regex": ^7.22.5 - "@babel/plugin-transform-new-target": ^7.22.5 - "@babel/plugin-transform-nullish-coalescing-operator": ^7.22.11 - "@babel/plugin-transform-numeric-separator": ^7.22.11 - "@babel/plugin-transform-object-rest-spread": ^7.22.11 - "@babel/plugin-transform-object-super": ^7.22.5 - "@babel/plugin-transform-optional-catch-binding": ^7.22.11 - "@babel/plugin-transform-optional-chaining": ^7.22.12 - "@babel/plugin-transform-parameters": ^7.22.5 - "@babel/plugin-transform-private-methods": ^7.22.5 - "@babel/plugin-transform-private-property-in-object": ^7.22.11 - "@babel/plugin-transform-property-literals": ^7.22.5 - "@babel/plugin-transform-regenerator": ^7.22.10 - "@babel/plugin-transform-reserved-words": ^7.22.5 - "@babel/plugin-transform-shorthand-properties": ^7.22.5 - "@babel/plugin-transform-spread": ^7.22.5 - "@babel/plugin-transform-sticky-regex": ^7.22.5 - "@babel/plugin-transform-template-literals": ^7.22.5 - "@babel/plugin-transform-typeof-symbol": ^7.22.5 - "@babel/plugin-transform-unicode-escapes": ^7.22.10 - "@babel/plugin-transform-unicode-property-regex": ^7.22.5 - "@babel/plugin-transform-unicode-regex": ^7.22.5 - "@babel/plugin-transform-unicode-sets-regex": ^7.22.5 + "@babel/plugin-transform-new-target": ^7.23.3 + "@babel/plugin-transform-nullish-coalescing-operator": ^7.23.4 + "@babel/plugin-transform-numeric-separator": ^7.23.4 + "@babel/plugin-transform-object-rest-spread": ^7.23.4 + "@babel/plugin-transform-object-super": ^7.23.3 + "@babel/plugin-transform-optional-catch-binding": ^7.23.4 + "@babel/plugin-transform-optional-chaining": ^7.23.4 + "@babel/plugin-transform-parameters": ^7.23.3 + "@babel/plugin-transform-private-methods": ^7.23.3 + "@babel/plugin-transform-private-property-in-object": ^7.23.4 + "@babel/plugin-transform-property-literals": ^7.23.3 + "@babel/plugin-transform-regenerator": ^7.23.3 + "@babel/plugin-transform-reserved-words": ^7.23.3 + "@babel/plugin-transform-shorthand-properties": ^7.23.3 + "@babel/plugin-transform-spread": ^7.23.3 + "@babel/plugin-transform-sticky-regex": ^7.23.3 + "@babel/plugin-transform-template-literals": ^7.23.3 + "@babel/plugin-transform-typeof-symbol": ^7.23.3 + "@babel/plugin-transform-unicode-escapes": ^7.23.3 + "@babel/plugin-transform-unicode-property-regex": ^7.23.3 + "@babel/plugin-transform-unicode-regex": ^7.23.3 + "@babel/plugin-transform-unicode-sets-regex": ^7.23.3 "@babel/preset-modules": 0.1.6-no-external-plugins - "@babel/types": ^7.22.11 - babel-plugin-polyfill-corejs2: ^0.4.5 - babel-plugin-polyfill-corejs3: ^0.8.3 - babel-plugin-polyfill-regenerator: ^0.5.2 + babel-plugin-polyfill-corejs2: ^0.4.6 + babel-plugin-polyfill-corejs3: ^0.8.5 + babel-plugin-polyfill-regenerator: ^0.5.3 core-js-compat: ^3.31.0 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a62b5717a86c41ab675ac0f4d46ff504088fb2cce2011aa36508453d2235a3ecf1f6d127aa57962310fcce4ec18bad840ccd09987a78c57e4d1b4339cedeaacd + checksum: adddd58d14fc1b2e5f8cf90995f522879362a0543e316afe9e5783f1bd715bb1e92300cd49d7ce3a95c64a96d60788d0089651e2cf4cac937f5469aac1087bb1 languageName: node linkType: hard -"@babel/preset-flow@npm:^7.12.1": - version: 7.22.5 - resolution: "@babel/preset-flow@npm:7.22.5" +"@babel/preset-flow@npm:^7.22.15": + version: 7.23.3 + resolution: "@babel/preset-flow@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.5 - "@babel/plugin-transform-flow-strip-types": ^7.22.5 + "@babel/helper-validator-option": ^7.22.15 + "@babel/plugin-transform-flow-strip-types": ^7.23.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0bf6f587e952f8945d348cf0f25cbc3e50697f2cdc4e1394badfb76cfdde0cc2f2c9250bda3d28ecc6c196b89de7c8e72b8ffbf3086e604b959cce352dd2b34e + checksum: 60b5dde79621ae89943af459c4dc5b6030795f595a20ca438c8100f8d82c9ebc986881719030521ff5925799518ac5aa7f3fe62af8c33ab96be3681a71f88d03 languageName: node linkType: hard @@ -3109,40 +3208,40 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.12.10": - version: 7.22.5 - resolution: "@babel/preset-react@npm:7.22.5" +"@babel/preset-react@npm:^7.22.15": + version: 7.23.3 + resolution: "@babel/preset-react@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.5 - "@babel/plugin-transform-react-display-name": ^7.22.5 - "@babel/plugin-transform-react-jsx": ^7.22.5 + "@babel/helper-validator-option": ^7.22.15 + "@babel/plugin-transform-react-display-name": ^7.23.3 + "@babel/plugin-transform-react-jsx": ^7.22.15 "@babel/plugin-transform-react-jsx-development": ^7.22.5 - "@babel/plugin-transform-react-pure-annotations": ^7.22.5 + "@babel/plugin-transform-react-pure-annotations": ^7.23.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b977c7ee83e93f62d77e61929ca3d97e5291e026e2f025a1b8b7ac9186486ed56c7d5bc36f0becabe0c24e8c42a4e4f2243a3cf841384cfafc3204c5d3e6c619 + checksum: 2d90961e7e627a74b44551e88ad36a440579e283e8dc27972bf2f50682152bbc77228673a3ea22c0e0d005b70cbc487eccd64897c5e5e0384e5ce18f300b21eb languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.12.7": - version: 7.22.11 - resolution: "@babel/preset-typescript@npm:7.22.11" +"@babel/preset-typescript@npm:^7.23.0, @babel/preset-typescript@npm:^7.23.2": + version: 7.23.3 + resolution: "@babel/preset-typescript@npm:7.23.3" dependencies: "@babel/helper-plugin-utils": ^7.22.5 - "@babel/helper-validator-option": ^7.22.5 - "@babel/plugin-syntax-jsx": ^7.22.5 - "@babel/plugin-transform-modules-commonjs": ^7.22.11 - "@babel/plugin-transform-typescript": ^7.22.11 + "@babel/helper-validator-option": ^7.22.15 + "@babel/plugin-syntax-jsx": ^7.23.3 + "@babel/plugin-transform-modules-commonjs": ^7.23.3 + "@babel/plugin-transform-typescript": ^7.23.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8ae7162c31db896f5eeecd6f67ab2e58555fdc06fe84e95fe4a3f60b64cd6f782d2d7dfbde0c0eac04b55dac18222752d91dd8786245cccedd7e42f080e07233 + checksum: 105a2d39bbc464da0f7e1ad7f535c77c5f62d6b410219355b20e552e7d29933567a5c55339b5d0aec1a5c7a0a7dfdf1b54aae601a4fe15a157d54dcbfcb3e854 languageName: node linkType: hard -"@babel/register@npm:^7.12.1": - version: 7.22.5 - resolution: "@babel/register@npm:7.22.5" +"@babel/register@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/register@npm:7.22.15" dependencies: clone-deep: ^4.0.1 find-cache-dir: ^2.0.0 @@ -3151,7 +3250,7 @@ __metadata: source-map-support: ^0.5.16 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 723ce27fdad6faee5b3f51ef4f5154f7f285d61da665367de14de85abbe1c81ccbac11f699671cd0ed6b755dd430f28a62364fed5d49f2527625a9ea3bf40056 + checksum: 5497be6773608cd2d874210edd14499fce464ddbea170219da55955afe4c9173adb591164193458fd639e43b7d1314088a6186f4abf241476c59b3f0da6afd6f languageName: node linkType: hard @@ -3172,16 +3271,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.7.2": - version: 7.7.2 - resolution: "@babel/runtime@npm:7.7.2" - dependencies: - regenerator-runtime: ^0.13.2 - checksum: 4319b6a1771f2e0181de26a983ae2788f1af428dcac3c0225d459be8a50167cee5be0d66856306b8eab53c1b3e1cae68cb481ef1be5ca2259f997ce3b12557b7 - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.22.11 resolution: "@babel/runtime@npm:7.22.11" dependencies: @@ -3191,31 +3281,20 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.21.0": - version: 7.23.1 - resolution: "@babel/runtime@npm:7.23.1" + version: 7.23.6 + resolution: "@babel/runtime@npm:7.23.6" dependencies: regenerator-runtime: ^0.14.0 - checksum: 0cd0d43e6e7dc7f9152fda8c8312b08321cda2f56ef53d6c22ebdd773abdc6f5d0a69008de90aa41908d00e2c1facb24715ff121274e689305c858355ff02c70 + checksum: 1a8eaf3d3a103ef5227b60ca7ab5c589118c36ca65ef2d64e65380b32a98a3f3b5b3ef96660fa0471b079a18b619a8317f3e7f03ab2b930c45282a8b69ed9a16 languageName: node linkType: hard -"@babel/runtime@npm:~7.5.4": - version: 7.5.5 - resolution: "@babel/runtime@npm:7.5.5" +"@babel/runtime@npm:^7.23.2": + version: 7.23.5 + resolution: "@babel/runtime@npm:7.23.5" dependencies: - regenerator-runtime: ^0.13.2 - checksum: b04ed65993bbf4371a880f7afc289f1f2a5f5a9e882c64448b41481aa289ed650a2b6155c6179f3d32fccf159f9be947989af5ff7a4f9b847f60f25e631ad533 - languageName: node - linkType: hard - -"@babel/template@npm:^7.12.7, @babel/template@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/template@npm:7.22.5" - dependencies: - "@babel/code-frame": ^7.22.5 - "@babel/parser": ^7.22.5 - "@babel/types": ^7.22.5 - checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 + regenerator-runtime: ^0.14.0 + checksum: 164d9802424f06908e62d29b8fd3a87db55accf82f46f964ac481dcead11ff7df8391e3696e5fa91a8ca10ea8845bf650acd730fa88cf13f8026cd8d5eec6936 languageName: node linkType: hard @@ -3230,6 +3309,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" + dependencies: + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 + languageName: node + linkType: hard + "@babel/traverse@npm:7.17.3": version: 7.17.3 resolution: "@babel/traverse@npm:7.17.3" @@ -3248,7 +3338,43 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.22.11": +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/traverse@npm:7.23.6" + dependencies: + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.6 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.6 + "@babel/types": ^7.23.6 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: 48f2eac0e86b6cb60dab13a5ea6a26ba45c450262fccdffc334c01089e75935f7546be195e260e97f6e43cea419862eda095018531a2718fef8189153d479f88 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/traverse@npm:7.23.5" + dependencies: + "@babel/code-frame": ^7.23.5 + "@babel/generator": ^7.23.5 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.5 + "@babel/types": ^7.23.5 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 0558b05360850c3ad6384e85bd55092126a8d5f93e29a8e227dd58fa1f9e1a4c25fd337c07c7ae509f0983e7a2b1e761ffdcfaa77a1e1bedbc867058e1de5a7d + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.22.11": version: 7.22.11 resolution: "@babel/traverse@npm:7.22.11" dependencies: @@ -3266,24 +3392,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/traverse@npm:7.23.0" - dependencies: - "@babel/code-frame": ^7.22.13 - "@babel/generator": ^7.23.0 - "@babel/helper-environment-visitor": ^7.22.20 - "@babel/helper-function-name": ^7.23.0 - "@babel/helper-hoist-variables": ^7.22.5 - "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/parser": ^7.23.0 - "@babel/types": ^7.23.0 - debug: ^4.1.0 - globals: ^11.1.0 - checksum: 0b17fae53269e1af2cd3edba00892bc2975ad5df9eea7b84815dab07dfec2928c451066d51bc65b4be61d8499e77db7e547ce69ef2a7b0eca3f96269cb43a0b0 - languageName: node - linkType: hard - "@babel/types@npm:7.17.0": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -3294,7 +3402,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.22.15, @babel/types@npm:^7.23.0": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.22.15, @babel/types@npm:^7.23.0": version: 7.23.0 resolution: "@babel/types@npm:7.23.0" dependencies: @@ -3305,7 +3413,18 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.11, @babel/types@npm:^7.22.5, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/types@npm:7.23.6" + dependencies: + "@babel/helper-string-parser": ^7.23.4 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 68187dbec0d637f79bc96263ac95ec8b06d424396678e7e225492be866414ce28ebc918a75354d4c28659be6efe30020b4f0f6df81cc418a2d30645b690a8de0 + languageName: node + linkType: hard + +"@babel/types@npm:^7.17.0, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.11, @babel/types@npm:^7.22.5, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.22.11 resolution: "@babel/types@npm:7.22.11" dependencies: @@ -3316,6 +3435,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.19, @babel/types@npm:^7.23.4, @babel/types@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/types@npm:7.23.5" + dependencies: + "@babel/helper-string-parser": ^7.23.4 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 3d21774480a459ef13b41c2e32700d927af649e04b70c5d164814d8e04ab584af66a93330602c2925e1a6925c2b829cc153418a613a4e7d79d011be1f29ad4b2 + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -3749,7 +3879,7 @@ __metadata: "@hookform/resolvers": ^2.9.7 "@sendgrid/client": ^7.7.0 "@sendgrid/mail": ^7.6.2 - libphonenumber-js: ^1.10.12 + libphonenumber-js: ^1.10.51 twilio: ^3.80.1 zod: ^3.22.2 languageName: unknown @@ -4074,6 +4204,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/matomo@workspace:packages/app-store/matomo": + version: 0.0.0-use.local + resolution: "@calcom/matomo@workspace:packages/app-store/matomo" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/metapixel@workspace:packages/app-store/metapixel": version: 0.0.0-use.local resolution: "@calcom/metapixel@workspace:packages/app-store/metapixel" @@ -4148,6 +4287,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/pipedrive-crm@workspace:packages/app-store/pipedrive-crm": + version: 0.0.0-use.local + resolution: "@calcom/pipedrive-crm@workspace:packages/app-store/pipedrive-crm" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/plausible@workspace:packages/app-store/plausible": version: 0.0.0-use.local resolution: "@calcom/plausible@workspace:packages/app-store/plausible" @@ -4200,6 +4348,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/roam@workspace:packages/app-store/roam": + version: 0.0.0-use.local + resolution: "@calcom/roam@workspace:packages/app-store/roam" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/routing-forms@workspace:packages/app-store/routing-forms": version: 0.0.0-use.local resolution: "@calcom/routing-forms@workspace:packages/app-store/routing-forms" @@ -4290,15 +4447,17 @@ __metadata: "@radix-ui/react-slider": ^1.0.0 "@radix-ui/react-switch": ^1.0.0 "@radix-ui/react-tooltip": ^1.0.0 - "@storybook/addon-actions": ^6.5.13 - "@storybook/addon-essentials": ^6.5.13 - "@storybook/addon-interactions": ^6.5.13 - "@storybook/addon-links": ^6.5.13 - "@storybook/builder-vite": ^0.2.4 - "@storybook/builder-webpack5": ^6.5.13 - "@storybook/manager-webpack5": ^6.5.13 - "@storybook/react": ^6.5.13 - "@storybook/testing-library": ^0.0.13 + "@storybook/addon-actions": ^7.6.3 + "@storybook/addon-designs": ^7.0.7 + "@storybook/addon-docs": ^7.6.3 + "@storybook/addon-essentials": ^7.6.3 + "@storybook/addon-interactions": ^7.6.3 + "@storybook/addon-links": ^7.6.3 + "@storybook/blocks": ^7.6.3 + "@storybook/nextjs": ^7.6.3 + "@storybook/preview-api": ^7.6.3 + "@storybook/react": ^7.6.3 + "@storybook/testing-library": ^0.2.2 "@types/react": 18.0.26 "@types/react-dom": ^18.0.9 "@vitejs/plugin-react": ^2.2.0 @@ -4311,11 +4470,9 @@ __metadata: react: ^18.2.0 react-dom: ^18.2.0 rollup-plugin-polyfill-node: ^0.10.2 - storybook-addon-designs: ^6.3.1 - storybook-addon-next: ^1.6.9 - storybook-addon-next-router: ^4.0.2 + storybook: ^7.6.3 storybook-addon-rtl-direction: ^0.0.19 - storybook-react-i18next: ^1.1.2 + storybook-react-i18next: ^2.0.9 tailwindcss: ^3.3.3 typescript: ^4.9.4 vite: ^4.1.2 @@ -4440,6 +4597,8 @@ __metadata: "@radix-ui/react-portal": ^1.0.0 "@radix-ui/react-select": ^0.1.1 "@react-icons/all-files": ^4.1.0 + "@storybook/blocks": ^7.6.3 + "@storybook/react": ^7.6.3 "@tanstack/react-query": ^4.3.9 "@tanstack/react-table": ^8.9.3 "@types/react": 18.0.26 @@ -4586,7 +4745,7 @@ __metadata: ics: ^2.37.0 jose: ^4.13.1 kbar: ^0.1.0-beta.36 - libphonenumber-js: ^1.10.12 + libphonenumber-js: ^1.10.51 lodash: ^4.17.21 lottie-react: ^2.3.1 markdown-it: ^13.0.1 @@ -4746,6 +4905,7 @@ __metadata: react-hook-form: ^7.43.3 react-hot-toast: ^2.3.0 react-live-chat-loader: ^2.8.1 + react-markdown: ^9.0.1 react-merge-refs: 1.1.0 react-resize-detector: ^9.1.0 react-twemoji: ^0.3.0 @@ -4754,7 +4914,7 @@ __metadata: remark: ^14.0.2 remark-html: ^14.0.1 remeda: ^1.24.1 - stripe: ^14.5.0 + stripe: ^9.16.0 tailwind-merge: ^1.13.2 tailwindcss: ^3.3.3 ts-node: ^10.9.1 @@ -5077,18 +5237,6 @@ __metadata: languageName: node linkType: hard -"@cnakazawa/watch@npm:^1.0.3": - version: 1.0.4 - resolution: "@cnakazawa/watch@npm:1.0.4" - dependencies: - exec-sh: ^0.3.2 - minimist: ^1.2.0 - bin: - watch: cli.js - checksum: 88f395ca0af2f3c0665b8ce7bb29e83647ec5d141e8735712aeeee4117081555436712966b6957aa1c461f6f826a4d23b0034e379c443a10e919f81c8748bf29 - languageName: node - linkType: hard - "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -5138,24 +5286,24 @@ __metadata: linkType: hard "@datocms/cma-client-node@npm:^2.0.0": - version: 2.0.1 - resolution: "@datocms/cma-client-node@npm:2.0.1" + version: 2.2.6 + resolution: "@datocms/cma-client-node@npm:2.2.6" dependencies: - "@datocms/cma-client": ^2.0.1 + "@datocms/cma-client": ^2.2.6 "@datocms/rest-client-utils": ^1.3.3 got: ^11.8.5 mime-types: ^2.1.35 tmp-promise: ^3.0.3 - checksum: 868fbb2c8003ad5eb68154f0e572d87df2aaac0321499ac703da8b1c5e3952b6394f295ecb90c58a8ffa4652b3c79129b5acdda572cd5e5e6e0dcaff55149234 + checksum: d18b568f5a4538abbd824091722f7df95c99cb5e550560bcae701654924e7933559b27d176fc7772056afe214516c99e8bb383319d4e492b053d20d3732df929 languageName: node linkType: hard -"@datocms/cma-client@npm:^2.0.1": - version: 2.0.1 - resolution: "@datocms/cma-client@npm:2.0.1" +"@datocms/cma-client@npm:^2.2.6": + version: 2.2.6 + resolution: "@datocms/cma-client@npm:2.2.6" dependencies: "@datocms/rest-client-utils": ^1.3.3 - checksum: ffd3f1b7e9f8bbc93f1d53625224093014cfca1e6efa90a7dd8e8252e6fc2f11f42a745482103a542e3d255cbcc847b20ebac666eb73f04d6b6a41404e8f685b + checksum: 52e65ab5cdc6b09859f6b07f87a5e8700ba2b2f0579894b7f3c6735c1360f83e41941783dfab78bd18d3f9e12bbd50436b953663a332c50fbcd12b167c567ed6 languageName: node linkType: hard @@ -5204,72 +5352,6 @@ __metadata: languageName: node linkType: hard -"@design-systems/utils@npm:2.12.0": - version: 2.12.0 - resolution: "@design-systems/utils@npm:2.12.0" - dependencies: - "@babel/runtime": ^7.11.2 - clsx: ^1.0.4 - focus-lock: ^0.8.0 - react-merge-refs: ^1.0.0 - peerDependencies: - "@types/react": "*" - react: ">= 16.8.6" - react-dom: ">= 16.8.6" - checksum: 6659eadb485b55d25d465b0b262c22e7d196f35b27aa1f8f7b6f3bacda9d7c5f6186a46afe56dd223c70f91c61392c81a541ea7dec588960499d049a8c06f3b7 - languageName: node - linkType: hard - -"@devtools-ds/object-inspector@npm:^1.1.2": - version: 1.2.0 - resolution: "@devtools-ds/object-inspector@npm:1.2.0" - dependencies: - "@babel/runtime": 7.7.2 - "@devtools-ds/object-parser": ^1.2.0 - "@devtools-ds/themes": ^1.2.0 - "@devtools-ds/tree": ^1.2.0 - clsx: 1.1.0 - peerDependencies: - react: ">= 16.8.6" - checksum: f5254fe95afae4bb9dcc7fa9b5c5460b3a566ed97df2d40b338571c4028e346d81516e058fa86cf0e0a005342d7920d8ccbefadf436fc29dfffb4977a43e3f8a - languageName: node - linkType: hard - -"@devtools-ds/object-parser@npm:^1.2.0": - version: 1.2.0 - resolution: "@devtools-ds/object-parser@npm:1.2.0" - dependencies: - "@babel/runtime": ~7.5.4 - checksum: 1fb1cb20f6697553ec41a7874e838a397881a6271bcd89ce59a87bf5d36612df9ee43cd7cecadc8bffeea62d21cc73de5528c8fe51f4499610ac6a04d0ee28d7 - languageName: node - linkType: hard - -"@devtools-ds/themes@npm:^1.2.0": - version: 1.2.0 - resolution: "@devtools-ds/themes@npm:1.2.0" - dependencies: - "@babel/runtime": ~7.5.4 - "@design-systems/utils": 2.12.0 - clsx: 1.1.0 - peerDependencies: - react: ">= 16.8.6" - checksum: fc1db88056b18481abacc1dcffb86419fc05d66ddab375684dc66a6365ed348388b34137c9480ae30a623cb6c0348e18ecc67b24ba6a4a0da48b45a0e1b51c45 - languageName: node - linkType: hard - -"@devtools-ds/tree@npm:^1.2.0": - version: 1.2.0 - resolution: "@devtools-ds/tree@npm:1.2.0" - dependencies: - "@babel/runtime": 7.7.2 - "@devtools-ds/themes": ^1.2.0 - clsx: 1.1.0 - peerDependencies: - react: ">= 16.8.6" - checksum: fd0b2c8ae606d9ff5ed722371f91278b62da06b0162cf5b104ac7d176a555fc531942370c084bf27bd3525d61981bacdd3f8ac71411dcbc4bc1827b07f1482a0 - languageName: node - linkType: hard - "@discoveryjs/json-ext@npm:^0.5.3": version: 0.5.7 resolution: "@discoveryjs/json-ext@npm:0.5.7" @@ -5405,6 +5487,15 @@ __metadata: languageName: node linkType: hard +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.0": + version: 1.0.1 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1" + peerDependencies: + react: ">=16.8.0" + checksum: 700b6e5bbb37a9231f203bb3af11295eed01d73b2293abece0bc2a2237015e944d7b5114d4887ad9a79776504aa51ed2a8b0ddbc117c54495dd01a6b22f93786 + languageName: node + linkType: hard + "@emotion/utils@npm:^1.0.0, @emotion/utils@npm:^1.1.0": version: 1.1.0 resolution: "@emotion/utils@npm:1.1.0" @@ -5426,6 +5517,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-arm64@npm:0.18.20" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/android-arm64@npm:0.19.8" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm@npm:0.17.19" @@ -5433,6 +5538,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-arm@npm:0.18.20" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/android-arm@npm:0.19.8" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-x64@npm:0.17.19" @@ -5440,6 +5559,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/android-x64@npm:0.18.20" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/android-x64@npm:0.19.8" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-arm64@npm:0.17.19" @@ -5447,6 +5580,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/darwin-arm64@npm:0.18.20" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/darwin-arm64@npm:0.19.8" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-x64@npm:0.17.19" @@ -5454,6 +5601,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/darwin-x64@npm:0.18.20" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/darwin-x64@npm:0.19.8" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-arm64@npm:0.17.19" @@ -5461,6 +5622,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/freebsd-arm64@npm:0.18.20" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/freebsd-arm64@npm:0.19.8" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-x64@npm:0.17.19" @@ -5468,6 +5643,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/freebsd-x64@npm:0.18.20" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/freebsd-x64@npm:0.19.8" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm64@npm:0.17.19" @@ -5475,6 +5664,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-arm64@npm:0.18.20" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-arm64@npm:0.19.8" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm@npm:0.17.19" @@ -5482,6 +5685,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-arm@npm:0.18.20" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-arm@npm:0.19.8" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ia32@npm:0.17.19" @@ -5489,6 +5706,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-ia32@npm:0.18.20" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-ia32@npm:0.19.8" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-loong64@npm:0.17.19" @@ -5496,6 +5727,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-loong64@npm:0.18.20" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-loong64@npm:0.19.8" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-mips64el@npm:0.17.19" @@ -5503,6 +5748,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-mips64el@npm:0.18.20" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-mips64el@npm:0.19.8" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ppc64@npm:0.17.19" @@ -5510,6 +5769,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-ppc64@npm:0.18.20" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-ppc64@npm:0.19.8" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-riscv64@npm:0.17.19" @@ -5517,6 +5790,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-riscv64@npm:0.18.20" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-riscv64@npm:0.19.8" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-s390x@npm:0.17.19" @@ -5524,6 +5811,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-s390x@npm:0.18.20" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-s390x@npm:0.19.8" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-x64@npm:0.17.19" @@ -5531,6 +5832,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/linux-x64@npm:0.18.20" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/linux-x64@npm:0.19.8" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/netbsd-x64@npm:0.17.19" @@ -5538,6 +5853,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/netbsd-x64@npm:0.18.20" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/netbsd-x64@npm:0.19.8" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/openbsd-x64@npm:0.17.19" @@ -5545,6 +5874,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/openbsd-x64@npm:0.18.20" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/openbsd-x64@npm:0.19.8" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/sunos-x64@npm:0.17.19" @@ -5552,6 +5895,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/sunos-x64@npm:0.18.20" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/sunos-x64@npm:0.19.8" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-arm64@npm:0.17.19" @@ -5559,6 +5916,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-arm64@npm:0.18.20" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/win32-arm64@npm:0.19.8" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-ia32@npm:0.17.19" @@ -5566,6 +5937,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-ia32@npm:0.18.20" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/win32-ia32@npm:0.19.8" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-x64@npm:0.17.19" @@ -5573,20 +5958,17 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.0.5": - version: 1.3.0 - resolution: "@eslint/eslintrc@npm:1.3.0" - dependencies: - ajv: ^6.12.4 - debug: ^4.3.2 - espree: ^9.3.2 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.2.1 - js-yaml: ^4.1.0 - minimatch: ^3.1.2 - strip-json-comments: ^3.1.1 - checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 +"@esbuild/win32-x64@npm:0.18.20": + version: 0.18.20 + resolution: "@esbuild/win32-x64@npm:0.18.20" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.19.8": + version: 0.19.8 + resolution: "@esbuild/win32-x64@npm:0.19.8" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -5643,6 +6025,20 @@ __metadata: languageName: node linkType: hard +"@fal-works/esbuild-plugin-global-externals@npm:^2.1.2": + version: 2.1.2 + resolution: "@fal-works/esbuild-plugin-global-externals@npm:2.1.2" + checksum: c59715902b9062aa7ff38973f298b509499fd146dbf564dc338b3f9e896da5bffb4ca676c27587fde79b3586003e24d65960acb62f009bca43dca34c76f8cbf7 + languageName: node + linkType: hard + +"@fastify/busboy@npm:^2.0.0": + version: 2.1.0 + resolution: "@fastify/busboy@npm:2.1.0" + checksum: 3233abd10f73e50668cb4bb278a79b7b3fadd30215ac6458299b0e5a09a29c3586ec07597aae6bd93f5cbedfcef43a8aeea51829cd28fc13850cdbcd324c28d5 + languageName: node + linkType: hard + "@figspec/components@npm:^1.0.0": version: 1.0.1 resolution: "@figspec/components@npm:1.0.1" @@ -5839,7 +6235,7 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.0.1, @gar/promisify@npm:^1.1.3": +"@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 @@ -6064,17 +6460,17 @@ __metadata: linkType: hard "@graphql-tools/code-file-loader@npm:^8.0.0": - version: 8.0.2 - resolution: "@graphql-tools/code-file-loader@npm:8.0.2" + version: 8.0.3 + resolution: "@graphql-tools/code-file-loader@npm:8.0.3" dependencies: - "@graphql-tools/graphql-tag-pluck": 8.0.2 + "@graphql-tools/graphql-tag-pluck": 8.1.0 "@graphql-tools/utils": ^10.0.0 globby: ^11.0.3 tslib: ^2.4.0 unixify: ^1.0.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: e8c8db47a61afbfd8ac0d1e607b945ddde56c99ca7df88a80d1e50d3c8910dbdba8979264277afe430fe0a97b69aa52cdb737afc4a4ab3d452d34d84d7317c01 + checksum: a0571b3f79ff091bde6672c9f4ae63bf6d81113939eead848e205b1c27ad7109f7f8a69a4c2a259e74dbb781185166964ad57354dad8c1502dc5705997307736 languageName: node linkType: hard @@ -6095,8 +6491,8 @@ __metadata: linkType: hard "@graphql-tools/executor-graphql-ws@npm:^1.0.0": - version: 1.1.0 - resolution: "@graphql-tools/executor-graphql-ws@npm:1.1.0" + version: 1.1.1 + resolution: "@graphql-tools/executor-graphql-ws@npm:1.1.1" dependencies: "@graphql-tools/utils": ^10.0.2 "@types/ws": ^8.0.0 @@ -6106,13 +6502,13 @@ __metadata: ws: ^8.13.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: fa76de4020de49ba2309341f5ee9b0fbf05c6a16e7e9ecf99fad2dea734021122576a7ad82f697299f10c2e2ea8da2e3f30a31c5da1edb0938c9769adfe5c646 + checksum: 30d29e2ef8fbedf07d7c279218f31a7279e714328f6c24d28ea76536fb4c5ed857ab5e486922000fcf9f85b83a9f3e995b8fd066b01ea4ab31d35efaa770c133 languageName: node linkType: hard -"@graphql-tools/executor-http@npm:^1.0.0": - version: 1.0.2 - resolution: "@graphql-tools/executor-http@npm:1.0.2" +"@graphql-tools/executor-http@npm:^1.0.0, @graphql-tools/executor-http@npm:^1.0.5": + version: 1.0.5 + resolution: "@graphql-tools/executor-http@npm:1.0.5" dependencies: "@graphql-tools/utils": ^10.0.2 "@repeaterjs/repeater": ^3.0.4 @@ -6123,22 +6519,22 @@ __metadata: value-or-promise: ^1.0.12 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 6e246bdfb6de8763e4ae7070c99339ae377ae0ef7acee864d8db2a76c6e0e4fa139769291c8ea5e1f07f698016f5f21915f1f5678720d196faa63bc7c3116b2a + checksum: 5f87daf9cc226ba86a828371af268a35f78c2b525a8d9eafab977bd9cf96ce66b453f8b59c692ce095bb49a688bd3004f73074272f40695cf9f31811179172b2 languageName: node linkType: hard "@graphql-tools/executor-legacy-ws@npm:^1.0.0": - version: 1.0.3 - resolution: "@graphql-tools/executor-legacy-ws@npm:1.0.3" + version: 1.0.5 + resolution: "@graphql-tools/executor-legacy-ws@npm:1.0.5" dependencies: "@graphql-tools/utils": ^10.0.0 "@types/ws": ^8.0.0 - isomorphic-ws: 5.0.0 + isomorphic-ws: ^5.0.0 tslib: ^2.4.0 - ws: 8.14.1 + ws: ^8.15.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 037f488a5e51181557c3e32e702ddf82cebb7d5f50727ba83bfe9930dbfa363ed40ec4d83e9d7778d116bce9b761d0d77862da260788e1bd03601f31541a8efb + checksum: ae5adb4196e99558f94c45fd5c28aced2a0ace8d121913636361a2e04ec53191a9236ede7022e6b28a23c47b66828a595dfb719dc16e7825f639a12809f6198f languageName: node linkType: hard @@ -6158,10 +6554,10 @@ __metadata: linkType: hard "@graphql-tools/git-loader@npm:^8.0.0": - version: 8.0.2 - resolution: "@graphql-tools/git-loader@npm:8.0.2" + version: 8.0.3 + resolution: "@graphql-tools/git-loader@npm:8.0.3" dependencies: - "@graphql-tools/graphql-tag-pluck": 8.0.2 + "@graphql-tools/graphql-tag-pluck": 8.1.0 "@graphql-tools/utils": ^10.0.0 is-glob: 4.0.3 micromatch: ^4.0.4 @@ -6169,7 +6565,7 @@ __metadata: unixify: ^1.0.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 8eba213006001e3c422b1ede3c9dfc04e1da79bc825881bdcee77f5a17881933666c3c55a3510add8e141edfefd760e09f1652427a8a115fd5ebf612cf3e00be + checksum: 1b9bb24a940ecacf45d726afebef237018623809e57d451b1c68eae7ce97619c7cab564dd834438c543ee62102690c06eb9d170dca9ede983b990dbd70120ed5 languageName: node linkType: hard @@ -6205,9 +6601,9 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/graphql-tag-pluck@npm:8.0.2, @graphql-tools/graphql-tag-pluck@npm:^8.0.0": - version: 8.0.2 - resolution: "@graphql-tools/graphql-tag-pluck@npm:8.0.2" +"@graphql-tools/graphql-tag-pluck@npm:8.1.0, @graphql-tools/graphql-tag-pluck@npm:^8.0.0": + version: 8.1.0 + resolution: "@graphql-tools/graphql-tag-pluck@npm:8.1.0" dependencies: "@babel/core": ^7.22.9 "@babel/parser": ^7.16.8 @@ -6218,7 +6614,7 @@ __metadata: tslib: ^2.4.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: ee1dbb3be2b6914d15a3b9e6a1875fe1a1be283a14ced17d38139fa2d168883e0c2258d167f2cb8174d9f74ab13978c78e66e1bde3b968f2e47a8fdf365aa91a + checksum: 494d50fe4ed1beb033dcbdbdccd603ee07b43292ff932f479dfd4109a0fc5533f869811d6862eff5319208b07047e5cef8fbfd468124e8a283cbbfc828553c50 languageName: node linkType: hard @@ -6250,28 +6646,28 @@ __metadata: linkType: hard "@graphql-tools/load@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/load@npm:8.0.0" + version: 8.0.1 + resolution: "@graphql-tools/load@npm:8.0.1" dependencies: "@graphql-tools/schema": ^10.0.0 - "@graphql-tools/utils": ^10.0.0 + "@graphql-tools/utils": ^10.0.11 p-limit: 3.1.0 tslib: ^2.4.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: e1126bef8917e8a7e89c12cc95b127de727c04502e57f379d93dd3027ce72dc38b16a52a417263959a938b4660a1d362836eed59f941cd30ee3d5452c793a50d + checksum: 007ff2a9ddc6eb4567b7ac55c29b2d1891f98a0c8084def67a9e76b4d611757fa56c5ca9e5d22fa409c7b0c3ba8ce8c8a46b65ba9cd50d41255b079125ca0273 languageName: node linkType: hard -"@graphql-tools/merge@npm:^9.0.0": - version: 9.0.0 - resolution: "@graphql-tools/merge@npm:9.0.0" +"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.1": + version: 9.0.1 + resolution: "@graphql-tools/merge@npm:9.0.1" dependencies: - "@graphql-tools/utils": ^10.0.0 + "@graphql-tools/utils": ^10.0.10 tslib: ^2.4.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: e93794260faa477979bdc22f05da26c0eff02bd82c11565a1197152b70b5ab1c73dc6f8a05caf06737bcb18cb244bd19ee99d62bfc41af3bffb3c17eddb70edf + checksum: f078628838f57dcd2988b46ec27ce4786daef6e7fdd07c012acec2fe52139f4a905a101883eb0fa7094d1ace6d1b10e6a8d40c03778496b50e85093b36316e4e languageName: node linkType: hard @@ -6287,11 +6683,11 @@ __metadata: linkType: hard "@graphql-tools/prisma-loader@npm:^8.0.0": - version: 8.0.1 - resolution: "@graphql-tools/prisma-loader@npm:8.0.1" + version: 8.0.2 + resolution: "@graphql-tools/prisma-loader@npm:8.0.2" dependencies: "@graphql-tools/url-loader": ^8.0.0 - "@graphql-tools/utils": ^10.0.0 + "@graphql-tools/utils": ^10.0.8 "@types/js-yaml": ^4.0.0 "@types/json-stable-stringify": ^1.0.32 "@whatwg-node/fetch": ^0.9.0 @@ -6301,7 +6697,7 @@ __metadata: graphql-request: ^6.0.0 http-proxy-agent: ^7.0.0 https-proxy-agent: ^7.0.0 - jose: ^4.11.4 + jose: ^5.0.0 js-yaml: ^4.0.0 json-stable-stringify: ^1.0.1 lodash: ^4.17.20 @@ -6310,7 +6706,7 @@ __metadata: yaml-ast-parser: ^0.0.43 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 71f2d888c28ae7e634b3b62320853d31ea1dabb649e9478bc161d44bb177abf9919063be4348829c41f47edc8d5fce4def6e4eadd2a4c50edbb6dd7881bc4a51 + checksum: d4842d8155d1170a7d54183b94b2d88a14d137ad871f0e557feb9befa93d0765ab1bc378fcbb4b7bd9afe894e50411c74f62abda1796c2ea597ceea336635f72 languageName: node linkType: hard @@ -6328,27 +6724,27 @@ __metadata: linkType: hard "@graphql-tools/schema@npm:^10.0.0": - version: 10.0.0 - resolution: "@graphql-tools/schema@npm:10.0.0" + version: 10.0.2 + resolution: "@graphql-tools/schema@npm:10.0.2" dependencies: - "@graphql-tools/merge": ^9.0.0 - "@graphql-tools/utils": ^10.0.0 + "@graphql-tools/merge": ^9.0.1 + "@graphql-tools/utils": ^10.0.10 tslib: ^2.4.0 value-or-promise: ^1.0.12 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 550e9a4528584a4d108892f1553fb5b2590e63e88b9a9d3c1ad80b01c974ca9947adb9d1448a6969230d90c15dc96e8e84d62f32ef0fde804c389b43ac5bd739 + checksum: fe977b1aee05b0a88cf6bb029f17d828d8707f784e1d42d446984b6ba649d78e16e3295c549ee352c09bbe88ad87c23bbe04b946c096b6815156c5be80d79a3f languageName: node linkType: hard "@graphql-tools/url-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/url-loader@npm:8.0.0" + version: 8.0.1 + resolution: "@graphql-tools/url-loader@npm:8.0.1" dependencies: "@ardatan/sync-fetch": ^0.0.1 "@graphql-tools/delegate": ^10.0.0 "@graphql-tools/executor-graphql-ws": ^1.0.0 - "@graphql-tools/executor-http": ^1.0.0 + "@graphql-tools/executor-http": ^1.0.5 "@graphql-tools/executor-legacy-ws": ^1.0.0 "@graphql-tools/utils": ^10.0.0 "@graphql-tools/wrap": ^10.0.0 @@ -6360,20 +6756,21 @@ __metadata: ws: ^8.12.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: ddb77848d62a1705e09058e2de16342b0c4f3cf4a3a690e06a65ccaf4d1f72d1fe19f1862cce445cc4a7e8891cb61d6f1aa81540138076cf59a6ceaf3696a7e1 + checksum: c9de4325ca962e369712e6003abe9315b69c1f7ac5464a609779cd1d758448b83228200b64da2d95df8082655a6670c7bf613ada354e144260312e83d9fe8d4a languageName: node linkType: hard -"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5": - version: 10.0.6 - resolution: "@graphql-tools/utils@npm:10.0.6" +"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.10, @graphql-tools/utils@npm:^10.0.11, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5, @graphql-tools/utils@npm:^10.0.8": + version: 10.0.11 + resolution: "@graphql-tools/utils@npm:10.0.11" dependencies: "@graphql-typed-document-node/core": ^3.1.1 + cross-inspect: 1.0.0 dset: ^3.1.2 tslib: ^2.4.0 peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 1eae5ff2056930edf1b5a6aa38a2b28c2b3da0260d4d6babbd3fb25f8638b04c7ea8a481b3e1d4d965d81f2123b577646afb0fe20da87e76362d6f7f099e4be9 + checksum: cb480d0b3f253f85b84415f5b9e2893013a8e8b72501ec04e338c54a26ac234c9723c6c2d697f1316e89589425e5b133fd45eab2504a52b84d1d75dc6a495863 languageName: node linkType: hard @@ -6485,17 +6882,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.9.2": - version: 0.9.5 - resolution: "@humanwhocodes/config-array@npm:0.9.5" - dependencies: - "@humanwhocodes/object-schema": ^1.2.1 - debug: ^4.1.1 - minimatch: ^3.0.4 - checksum: 8ba6281bc0590f6c6eadeefc14244b5a3e3f5903445aadd1a32099ed80e753037674026ce1b3c945ab93561bea5eb29e3c5bff67060e230c295595ba517a3492 - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -6571,39 +6957,35 @@ __metadata: languageName: node linkType: hard -"@jest/transform@npm:^26.6.2": - version: 26.6.2 - resolution: "@jest/transform@npm:26.6.2" +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" dependencies: - "@babel/core": ^7.1.0 - "@jest/types": ^26.6.2 - babel-plugin-istanbul: ^6.0.0 - chalk: ^4.0.0 - convert-source-map: ^1.4.0 - fast-json-stable-stringify: ^2.0.0 - graceful-fs: ^4.2.4 - jest-haste-map: ^26.6.2 - jest-regex-util: ^26.0.0 - jest-util: ^26.6.2 - micromatch: ^4.0.2 - pirates: ^4.0.1 - slash: ^3.0.0 - source-map: ^0.6.1 - write-file-atomic: ^3.0.0 - checksum: 31667b925a2f3b310d854495da0ab67be8f5da24df76ecfc51162e75f1140aed5d18069ba190cb5e0c7e492b04272c8c79076ddf5bbcff530ee80a16a02c4545 + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 languageName: node linkType: hard -"@jest/types@npm:^26.6.2": - version: 26.6.2 - resolution: "@jest/types@npm:26.6.2" +"@jest/transform@npm:^29.3.1": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" dependencies: - "@types/istanbul-lib-coverage": ^2.0.0 - "@types/istanbul-reports": ^3.0.0 - "@types/node": "*" - "@types/yargs": ^15.0.0 + "@babel/core": ^7.11.6 + "@jest/types": ^29.6.3 + "@jridgewell/trace-mapping": ^0.3.18 + babel-plugin-istanbul: ^6.1.1 chalk: ^4.0.0 - checksum: a0bd3d2f22f26ddb23f41fddf6e6a30bf4fab2ce79ec1cb6ce6fdfaf90a72e00f4c71da91ec61e13db3b10c41de22cf49d07c57ff2b59171d64b29f909c1d8d6 + convert-source-map: ^2.0.0 + fast-json-stable-stringify: ^2.1.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.7.0 + jest-regex-util: ^29.6.3 + jest-util: ^29.7.0 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.2 + checksum: 0f8ac9f413903b3cb6d240102db848f2a354f63971ab885833799a9964999dd51c388162106a807f810071f864302cdd8e3f0c241c29ce02d85a36f18f3f40ab languageName: node linkType: hard @@ -6634,6 +7016,20 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": ^29.6.3 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc + languageName: node + linkType: hard + "@jimp/bmp@npm:^0.16.1": version: 0.16.1 resolution: "@jimp/bmp@npm:0.16.1" @@ -7058,22 +7454,6 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.5": - version: 0.0.5 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.0.5" - dependencies: - "@rollup/pluginutils": ^4.2.1 - glob: ^7.2.0 - glob-promise: ^4.2.0 - magic-string: ^0.26.1 - react-docgen-typescript: ^2.1.1 - peerDependencies: - typescript: ">= 4.3.x" - vite: ">2.0.0-0" - checksum: e722b472413bf67e879bdd3969a43223e161b2d2b634848b7a88c89ddb507ad331959808ff7e17b6513527e797aae4aad973dda0bd3c7ab3348ebc691ab76b83 - languageName: node - linkType: hard - "@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -7085,13 +7465,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0": - version: 3.1.0 - resolution: "@jridgewell/resolve-uri@npm:3.1.0" - checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 - languageName: node - linkType: hard - "@jridgewell/resolve-uri@npm:^3.0.3": version: 3.0.5 resolution: "@jridgewell/resolve-uri@npm:3.0.5" @@ -7123,10 +7496,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14": - version: 1.4.14 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" - checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.5 + resolution: "@jridgewell/source-map@npm:0.3.5" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1ad4dec0bdafbade57920a50acec6634f88a0eb735851e0dda906fa9894e7f0549c492678aad1a10f8e144bfe87f238307bf2a914a1bc85b7781d345417e9f6f languageName: node linkType: hard @@ -7154,7 +7530,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.7, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.14 resolution: "@jridgewell/trace-mapping@npm:0.3.14" dependencies: @@ -7164,16 +7540,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.14": - version: 0.3.17 - resolution: "@jridgewell/trace-mapping@npm:0.3.17" - dependencies: - "@jridgewell/resolve-uri": 3.1.0 - "@jridgewell/sourcemap-codec": 1.4.14 - checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339 - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:^0.3.17": version: 0.3.19 resolution: "@jridgewell/trace-mapping@npm:0.3.19" @@ -7184,6 +7550,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.18": + version: 0.3.20 + resolution: "@jridgewell/trace-mapping@npm:0.3.20" + dependencies: + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: cd1a7353135f385909468ff0cf20bdd37e59f2ee49a13a966dedf921943e222082c583ade2b579ff6cd0d8faafcb5461f253e1bf2a9f48fec439211fdbe788f5 + languageName: node + linkType: hard + "@js-joda/core@npm:^5.2.0": version: 5.4.2 resolution: "@js-joda/core@npm:5.4.2" @@ -7198,13 +7574,20 @@ __metadata: languageName: node linkType: hard -"@juggle/resize-observer@npm:^3.4.0": +"@juggle/resize-observer@npm:^3.3.1, @juggle/resize-observer@npm:^3.4.0": version: 3.4.0 resolution: "@juggle/resize-observer@npm:3.4.0" checksum: 2505028c05cc2e17639fcad06218b1c4b60f932a4ebb4b41ab546ef8c157031ae377e3f560903801f6d01706dbefd4943b6c4704bf19ed86dfa1c62f1473a570 languageName: node linkType: hard +"@kamilkisiela/fast-url-parser@npm:^1.1.4": + version: 1.1.4 + resolution: "@kamilkisiela/fast-url-parser@npm:1.1.4" + checksum: 921d305eff1fce5c7c669aee5cfe39e50109968addb496c23f0a42253d030e3cd5865eb01b13245915923bee452db75ba8a8254e69b0d0575d3c168efce7091e + languageName: node + linkType: hard + "@lexical/clipboard@npm:0.9.1": version: 0.9.1 resolution: "@lexical/clipboard@npm:0.9.1" @@ -7488,46 +7871,15 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^1.6.22": - version: 1.6.22 - resolution: "@mdx-js/mdx@npm:1.6.22" +"@mdx-js/react@npm:^2.1.5": + version: 2.3.0 + resolution: "@mdx-js/react@npm:2.3.0" dependencies: - "@babel/core": 7.12.9 - "@babel/plugin-syntax-jsx": 7.12.1 - "@babel/plugin-syntax-object-rest-spread": 7.8.3 - "@mdx-js/util": 1.6.22 - babel-plugin-apply-mdx-type-prop: 1.6.22 - babel-plugin-extract-import-names: 1.6.22 - camelcase-css: 2.0.1 - detab: 2.0.4 - hast-util-raw: 6.0.1 - lodash.uniq: 4.5.0 - mdast-util-to-hast: 10.0.1 - remark-footnotes: 2.0.0 - remark-mdx: 1.6.22 - remark-parse: 8.0.3 - remark-squeeze-paragraphs: 4.0.0 - style-to-object: 0.3.0 - unified: 9.2.0 - unist-builder: 2.0.3 - unist-util-visit: 2.0.3 - checksum: 0839b4a3899416326ea6578fe9e470af319da559bc6d3669c60942e456b49a98eebeb3358c623007b4786a2175a450d2c51cd59df64639013c5a3d22366931a6 - languageName: node - linkType: hard - -"@mdx-js/react@npm:^1.6.22": - version: 1.6.22 - resolution: "@mdx-js/react@npm:1.6.22" + "@types/mdx": ^2.0.0 + "@types/react": ">=16" peerDependencies: - react: ^16.13.1 || ^17.0.0 - checksum: bc84bd514bc127f898819a0c6f1a6915d9541011bd8aefa1fcc1c9bea8939f31051409e546bdec92babfa5b56092a16d05ef6d318304ac029299df5181dc94c8 - languageName: node - linkType: hard - -"@mdx-js/util@npm:1.6.22": - version: 1.6.22 - resolution: "@mdx-js/util@npm:1.6.22" - checksum: 4b393907e39a1a75214f0314bf72a0adfa5e5adffd050dd5efe9c055b8549481a3cfc9f308c16dfb33311daf3ff63added7d5fd1fe52db614c004f886e0e559a + react: ">=16" + checksum: f45fe779556e6cd9a787f711274480e0638b63c460f192ebdcd77cc07ffa61e23c98cb46dd46e577093e1cb4997a232a848d1fb0ba850ae204422cf603add524 languageName: node linkType: hard @@ -7538,16 +7890,6 @@ __metadata: languageName: node linkType: hard -"@mrmlnc/readdir-enhanced@npm:^2.2.1": - version: 2.2.1 - resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1" - dependencies: - call-me-maybe: ^1.0.1 - glob-to-regexp: ^0.3.0 - checksum: d3b82b29368821154ce8e10bef5ccdbfd070d3e9601643c99ea4607e56f3daeaa4e755dd6d2355da20762c695c1b0570543d9f84b48f70c211ec09c4aaada2e1 - languageName: node - linkType: hard - "@mswjs/cookies@npm:^0.2.0": version: 0.2.1 resolution: "@mswjs/cookies@npm:0.2.1" @@ -7572,6 +7914,17 @@ __metadata: languageName: node linkType: hard +"@ndelangen/get-tarball@npm:^3.0.7": + version: 3.0.9 + resolution: "@ndelangen/get-tarball@npm:3.0.9" + dependencies: + gunzip-maybe: ^1.4.2 + pump: ^3.0.0 + tar-fs: ^2.1.1 + checksum: 7fa8ac40b4e85738a4ee6bf891bc27fce2445b65b4477e0ec86aed0fa62ab18bdf5d193ce04553ad9bfa639e1eef33b8b30da4ef3e7218f12bf95f24c8786e5b + languageName: node + linkType: hard + "@next-auth/prisma-adapter@npm:^1.0.4": version: 1.0.4 resolution: "@next-auth/prisma-adapter@npm:1.0.4" @@ -7789,13 +8142,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.stat@npm:^1.1.2": - version: 1.1.3 - resolution: "@nodelib/fs.stat@npm:1.1.3" - checksum: 318deab369b518a34778cdaa0054dd28a4381c0c78e40bbd20252f67d084b1d7bf9295fea4423de2c19ac8e1a34f120add9125f481b2a710f7068bcac7e3e305 - languageName: node - linkType: hard - "@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" @@ -7806,16 +8152,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^1.0.0": - version: 1.1.1 - resolution: "@npmcli/fs@npm:1.1.1" - dependencies: - "@gar/promisify": ^1.0.1 - semver: ^7.3.5 - checksum: f5ad92f157ed222e4e31c352333d0901df02c7c04311e42a81d8eb555d4ec4276ea9c635011757de20cc476755af33e91622838de573b17e52e2e7703f0a9965 - languageName: node - linkType: hard - "@npmcli/fs@npm:^2.1.0": version: 2.1.2 resolution: "@npmcli/fs@npm:2.1.2" @@ -7826,16 +8162,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/move-file@npm:^1.0.1": - version: 1.1.2 - resolution: "@npmcli/move-file@npm:1.1.2" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: c96381d4a37448ea280951e46233f7e541058cf57a57d4094dd4bdcaae43fa5872b5f2eb6bfb004591a68e29c5877abe3cdc210cb3588cbf20ab2877f31a7de7 - languageName: node - linkType: hard - "@npmcli/move-file@npm:^2.0.0": version: 2.0.1 resolution: "@npmcli/move-file@npm:2.0.1" @@ -8130,13 +8456,13 @@ __metadata: linkType: hard "@peculiar/asn1-schema@npm:^2.3.6": - version: 2.3.6 - resolution: "@peculiar/asn1-schema@npm:2.3.6" + version: 2.3.8 + resolution: "@peculiar/asn1-schema@npm:2.3.8" dependencies: asn1js: ^3.0.5 - pvtsutils: ^1.3.2 - tslib: ^2.4.0 - checksum: fc09387c6e3dea07fca21b54ea8c71ce3ec0f8c92377237e51aef729f0c2df92781aa7a18a546a6fe809519faeaa222df576ec21a35c6095037a78677204a55b + pvtsutils: ^1.3.5 + tslib: ^2.6.2 + checksum: 1f4dd421f1411df8bc52bca12b1cef710434c13ff0a8b5746ede42b10d62b5ad06a3925c4a6db53102aaf1e589947539a6955fa8554a9b8ebb1ffa38b0155a24 languageName: node linkType: hard @@ -8199,24 +8525,24 @@ __metadata: languageName: node linkType: hard -"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3": - version: 0.5.7 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7" +"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.11": + version: 0.5.11 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11" dependencies: ansi-html-community: ^0.0.8 common-path-prefix: ^3.0.0 - core-js-pure: ^3.8.1 + core-js-pure: ^3.23.3 error-stack-parser: ^2.0.6 find-up: ^5.0.0 html-entities: ^2.1.0 - loader-utils: ^2.0.0 + loader-utils: ^2.0.4 schema-utils: ^3.0.0 source-map: ^0.7.3 peerDependencies: "@types/webpack": 4.x || 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <3.0.0" + type-fest: ">=0.17.0 <5.0.0" webpack: ">=4.43.0 <6.0.0" webpack-dev-server: 3.x || 4.x webpack-hot-middleware: 2.x @@ -8234,7 +8560,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 3490649181878cc8808fb91f3870ef095e5a1fb9647b3ac83740df07379c9d1cf540f24bf2b09d5f26a3a8c805b2c6b9c5be7192bdb9317d0ffffa67426e9f66 + checksum: a82eced9519f4dcac424acae719f819ab4150bfcf2874ac7daaf25a4f1c409e3d8b9d693fea0c686c24d520a5473756df32da90d8b89739670f8f8084c600bb4 languageName: node linkType: hard @@ -8292,14 +8618,10 @@ __metadata: languageName: node linkType: hard -"@prisma/debug@npm:5.5.2": - version: 5.5.2 - resolution: "@prisma/debug@npm:5.5.2" - dependencies: - "@types/debug": 4.1.9 - debug: 4.3.4 - strip-ansi: 6.0.1 - checksum: ff082622d4ba1b6fe07edda85a7b4dfb499308857e015645b9fec2041288fb0247d73974386edda2c25bac7d5167b6008e118386f169d20215035a70d5742d2a +"@prisma/debug@npm:5.7.1": + version: 5.7.1 + resolution: "@prisma/debug@npm:5.7.1" + checksum: 7c3134416836ff8a2d1172a71a1d063d7520cbed445d967f495cdb62295730ec8e779157b744e1afac423b6cf36cba11a643c32b85a975119c78e52f977a80db languageName: node linkType: hard @@ -8371,14 +8693,11 @@ __metadata: linkType: hard "@prisma/generator-helper@npm:^5.0.0": - version: 5.5.2 - resolution: "@prisma/generator-helper@npm:5.5.2" + version: 5.7.1 + resolution: "@prisma/generator-helper@npm:5.7.1" dependencies: - "@prisma/debug": 5.5.2 - "@types/cross-spawn": 6.0.3 - cross-spawn: 7.0.3 - kleur: 4.1.5 - checksum: 4aef64ba4bcf3211358148fc1dc782541a33129154e5bb86687b60aff41674e1578ac8ccadee5a9e719d4d9b304963395ae7276d8b59760dec93bfa4517dff34 + "@prisma/debug": 5.7.1 + checksum: fab19ae14a4efadf5ef5b277a84f0a386401f1f387841919d4521b4735c47eb01a07a59f5760dd99d376edf5744a4eeb641de97d1073a75c02d99bd3f6d44553 languageName: node linkType: hard @@ -9501,6 +9820,66 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-select@npm:^1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-select@npm:1.2.2" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/number": 1.0.1 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-collection": 1.0.3 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-dismissable-layer": 1.0.4 + "@radix-ui/react-focus-guards": 1.0.1 + "@radix-ui/react-focus-scope": 1.0.3 + "@radix-ui/react-id": 1.0.1 + "@radix-ui/react-popper": 1.1.2 + "@radix-ui/react-portal": 1.0.3 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-slot": 1.0.2 + "@radix-ui/react-use-callback-ref": 1.0.1 + "@radix-ui/react-use-controllable-state": 1.0.1 + "@radix-ui/react-use-layout-effect": 1.0.1 + "@radix-ui/react-use-previous": 1.0.1 + "@radix-ui/react-visually-hidden": 1.0.3 + aria-hidden: ^1.1.1 + react-remove-scroll: 2.5.5 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: af7b63cc9e2c6006ec08163392d244941e9e03534e7add1b7c5a86059d0eb8a0398d4f3e80d43ff22126874a02b985e44f1722d1de9218922f7aa653d09412e3 + languageName: node + linkType: hard + +"@radix-ui/react-separator@npm:1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-separator@npm:1.0.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-primitive": 1.0.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 42f8c95e404de2ce9387040d78049808a48d423cd4c3bad8cca92c4b0bcbdcb3566b5b52a920d4e939a74b51188697f20a012221f0e630fc7f56de64096c15d2 + languageName: node + linkType: hard + "@radix-ui/react-slider@npm:^1.0.0": version: 1.1.2 resolution: "@radix-ui/react-slider@npm:1.1.2" @@ -9624,7 +10003,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-toggle-group@npm:^1.0.0": +"@radix-ui/react-toggle-group@npm:1.0.4, @radix-ui/react-toggle-group@npm:^1.0.0": version: 1.0.4 resolution: "@radix-ui/react-toggle-group@npm:1.0.4" dependencies: @@ -9672,6 +10051,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-toolbar@npm:^1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-toolbar@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-roving-focus": 1.0.4 + "@radix-ui/react-separator": 1.0.3 + "@radix-ui/react-toggle-group": 1.0.4 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 7ebee1f8add6510108979433c5b38627e2de9d48ef2172ca15274b9edbbc106ff43bcd47ff733b03ed2215b92e7af364ff82c79e5a1728374847e2b1e315552c + languageName: node + linkType: hard + "@radix-ui/react-tooltip@npm:^1.0.0": version: 1.0.6 resolution: "@radix-ui/react-tooltip@npm:1.0.6" @@ -10068,9 +10473,9 @@ __metadata: linkType: hard "@repeaterjs/repeater@npm:^3.0.4": - version: 3.0.4 - resolution: "@repeaterjs/repeater@npm:3.0.4" - checksum: cca0db3e802bc26fcce0b4a574074d9956da53bf43094de03c0e4732d05e13441279a92f0b96e2a7a39da50933684947a138c1213406eaafe39cfd4683d6c0df + version: 3.0.5 + resolution: "@repeaterjs/repeater@npm:3.0.5" + checksum: 4f66020679a2e7a93fbd43d40a7ae6a187d6d7d148b019cca025791dade452599848bd20cd225861a65629571806c551a18cd40190426eb74b050710ac3ae865 languageName: node linkType: hard @@ -10145,16 +10550,6 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^4.2.1": - version: 4.2.1 - resolution: "@rollup/pluginutils@npm:4.2.1" - dependencies: - estree-walker: ^2.0.1 - picomatch: ^2.2.2 - checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12 - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^5.0.1": version: 5.0.2 resolution: "@rollup/pluginutils@npm:5.0.2" @@ -10171,6 +10566,90 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.6.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-android-arm64@npm:4.6.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.6.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.6.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.6.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.6.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.6.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.6.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.6.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.6.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.6.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.6.1": + version: 4.6.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.6.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rushstack/eslint-patch@npm:^1.1.3": version: 1.1.3 resolution: "@rushstack/eslint-patch@npm:1.1.3" @@ -10518,65 +10997,60 @@ __metadata: linkType: hard "@stablelib/utf8@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/utf8@npm:1.0.1" - checksum: 098d9446f38a641a8ee265a7fc3467fefd561fc46ca65e1216c1df7a9b4d004e616347ce79f4b83d62e944f0f91d6be4af029ad0b027a20c3271951921ebfac5 + version: 1.0.2 + resolution: "@stablelib/utf8@npm:1.0.2" + checksum: 3ab01baa4eb36eece44a310bf6b2e4313e8d585fc04dbcf8a5dc2d239f06071f34038b85aad6fbd98e9969f0b3b0584fcb541fe5e512c8f0cc0982b9fe1290a3 languageName: node linkType: hard -"@storybook/addon-actions@npm:6.5.13, @storybook/addon-actions@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-actions@npm:6.5.13" +"@storybook/addon-actions@npm:7.6.3, @storybook/addon-actions@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-actions@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 + "@storybook/core-events": 7.6.3 + "@storybook/global": ^5.0.0 + "@types/uuid": ^9.0.1 + dequal: ^2.0.2 polished: ^4.2.2 - prop-types: ^15.7.2 - react-inspector: ^5.1.0 - regenerator-runtime: ^0.13.7 - telejson: ^6.0.8 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - uuid-browser: ^3.1.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: 2679174b1467281860cd6f11fac5ba505e44629eb11ee90713d1c954569cfb54d80aa822a0ed1be2f92f11ca62889fec3dca96f61b89cf238503e0cea4b1db9a + uuid: ^9.0.0 + checksum: f9712937bc2ea22f0b8c04c60babe503cdda3844a9ddbbd7fcee44a3f227f7f77e433690bf17e495bd137a91460bf4a70f03edb06cd3f4de33c7fd27bffeba78 languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-backgrounds@npm:6.5.13" +"@storybook/addon-backgrounds@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-backgrounds@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 + "@storybook/global": ^5.0.0 memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 + checksum: 800a78de9e3af6e1ea63496b7b20e3ccf26ce73968f01b84c631ea23a7441d2fde7295690612de460858fda7ef1604437105f6d6c6b5083851d128696b6757dc + languageName: node + linkType: hard + +"@storybook/addon-controls@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-controls@npm:7.6.3" + dependencies: + "@storybook/blocks": 7.6.3 + lodash: ^4.17.21 + ts-dedent: ^2.0.0 + checksum: 7b234b048777107512bb96fc7636bfc064964b00dd3e8d7723e3d13dba608bb1674ada8824da06607cdd7cbe1a3f870bfe5e98bfc3b000f597b89e1c70370744 + languageName: node + linkType: hard + +"@storybook/addon-designs@npm:^7.0.7": + version: 7.0.7 + resolution: "@storybook/addon-designs@npm:7.0.7" + dependencies: + "@figspec/react": ^1.0.0 peerDependencies: + "@storybook/addon-docs": ^7.0.0 + "@storybook/addons": ^7.0.0 + "@storybook/components": ^7.0.0 + "@storybook/manager-api": ^7.0.0 + "@storybook/preview-api": ^7.0.0 + "@storybook/theming": ^7.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: @@ -10584,1591 +11058,841 @@ __metadata: optional: true react-dom: optional: true - checksum: 1b18255e0d8dceca56b8d6cf672506176eedad3809f11879d9754652538bf004f90637c0bf557de5e7bc24d5db5410da79e61f131493cb87b27340611cb31cf5 + checksum: 14b8c98588ab3f28ae5ee417ac2485c6892c39b7eb70c799059c5dc894f7766a6fe404d4b452af96244411142d2a00ebbf5cd269df2c1a5860a59f6622bd242c languageName: node linkType: hard -"@storybook/addon-controls@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-controls@npm:6.5.13" +"@storybook/addon-docs@npm:7.6.3, @storybook/addon-docs@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-docs@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/node-logger": 6.5.13 - "@storybook/store": 6.5.13 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - lodash: ^4.17.21 - ts-dedent: ^2.0.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: a4f86332686b5681366ad1f1eebb50cb9939ce3424ade64c0043b94827df15a34da0190101c8dc9a2b4b9af67fafd9d3fb50a5b98ff7a24fed6a37a2f8f37f27 - languageName: node - linkType: hard - -"@storybook/addon-docs@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-docs@npm:6.5.13" - dependencies: - "@babel/plugin-transform-react-jsx": ^7.12.12 - "@babel/preset-env": ^7.12.11 - "@jest/transform": ^26.6.2 - "@mdx-js/react": ^1.6.22 - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/docs-tools": 6.5.13 - "@storybook/mdx1-csf": ^0.0.1 - "@storybook/node-logger": 6.5.13 - "@storybook/postinstall": 6.5.13 - "@storybook/preview-web": 6.5.13 - "@storybook/source-loader": 6.5.13 - "@storybook/store": 6.5.13 - "@storybook/theming": 6.5.13 - babel-loader: ^8.0.0 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - regenerator-runtime: ^0.13.7 + "@jest/transform": ^29.3.1 + "@mdx-js/react": ^2.1.5 + "@storybook/blocks": 7.6.3 + "@storybook/client-logger": 7.6.3 + "@storybook/components": 7.6.3 + "@storybook/csf-plugin": 7.6.3 + "@storybook/csf-tools": 7.6.3 + "@storybook/global": ^5.0.0 + "@storybook/mdx2-csf": ^1.0.0 + "@storybook/node-logger": 7.6.3 + "@storybook/postinstall": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@storybook/react-dom-shim": 7.6.3 + "@storybook/theming": 7.6.3 + "@storybook/types": 7.6.3 + fs-extra: ^11.1.0 remark-external-links: ^8.0.0 remark-slug: ^6.0.0 ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 peerDependencies: - "@storybook/mdx2-csf": ^0.0.3 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@storybook/mdx2-csf": - optional: true - react: - optional: true - react-dom: - optional: true - checksum: 41755593a28497172d18efa623d6dd3054d5fe867f2a4bf02be3875a0d22ca5cf10f5bf3946c4fa6b392f955952aaf8c90b4ae88d0622a1e236c7333b58902c3 + checksum: a80c37e1454793088487c32d2837032acc2258c18260a20c61f865fb2704fb4f4792fae0b83d7e95386a6b6b9841a9bc132f50d73aeb5b7d7f0d7c64effa8916 languageName: node linkType: hard -"@storybook/addon-essentials@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-essentials@npm:6.5.13" +"@storybook/addon-essentials@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-essentials@npm:7.6.3" dependencies: - "@storybook/addon-actions": 6.5.13 - "@storybook/addon-backgrounds": 6.5.13 - "@storybook/addon-controls": 6.5.13 - "@storybook/addon-docs": 6.5.13 - "@storybook/addon-measure": 6.5.13 - "@storybook/addon-outline": 6.5.13 - "@storybook/addon-toolbars": 6.5.13 - "@storybook/addon-viewport": 6.5.13 - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/node-logger": 6.5.13 - core-js: ^3.8.2 - regenerator-runtime: ^0.13.7 + "@storybook/addon-actions": 7.6.3 + "@storybook/addon-backgrounds": 7.6.3 + "@storybook/addon-controls": 7.6.3 + "@storybook/addon-docs": 7.6.3 + "@storybook/addon-highlight": 7.6.3 + "@storybook/addon-measure": 7.6.3 + "@storybook/addon-outline": 7.6.3 + "@storybook/addon-toolbars": 7.6.3 + "@storybook/addon-viewport": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/manager-api": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/preview-api": 7.6.3 ts-dedent: ^2.0.0 peerDependencies: - "@babel/core": ^7.9.6 - peerDependenciesMeta: - "@storybook/angular": - optional: true - "@storybook/builder-manager4": - optional: true - "@storybook/builder-manager5": - optional: true - "@storybook/builder-webpack4": - optional: true - "@storybook/builder-webpack5": - optional: true - "@storybook/html": - optional: true - "@storybook/vue": - optional: true - "@storybook/vue3": - optional: true - "@storybook/web-components": - optional: true - lit: - optional: true - lit-html: - optional: true - react: - optional: true - react-dom: - optional: true - svelte: - optional: true - sveltedoc-parser: - optional: true - vue: - optional: true - webpack: - optional: true - checksum: a7b1b34c7fbf0d863cf8dab3160cc267516cbcbeb155c4f91aab2c1d6005b23d5662a9104be0d733476ba4a0c8328ea899ae46bf55a9bddda5ce41996737b358 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: ba67659f6560a234b1a85092cc5a62e06761f7d711148a0a19586ca1d160327f24244f35a165bb77d5a06ea6c4bf1d9e17a388dacc767facacfccfb339ebc0ff languageName: node linkType: hard -"@storybook/addon-interactions@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-interactions@npm:6.5.13" +"@storybook/addon-highlight@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-highlight@npm:7.6.3" dependencies: - "@devtools-ds/object-inspector": ^1.1.2 - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/instrumenter": 6.5.13 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 + "@storybook/global": ^5.0.0 + checksum: 33c74bcc2277c8bca522a5e902db957d7bbeffdcf07d3d73490bce214cfb13ebdf6e4caab937b36e834c0425a06cb8574520c8f25fa862614ba5c5acf6cf65ea + languageName: node + linkType: hard + +"@storybook/addon-interactions@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-interactions@npm:7.6.3" + dependencies: + "@storybook/global": ^5.0.0 + "@storybook/types": 7.6.3 jest-mock: ^27.0.6 polished: ^4.2.2 ts-dedent: ^2.2.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: 5c940dc55b3bde6432f392f456785028c73c2dd893271b8efcf2ad2b30d10432666eec1c728b95bf94a81a967ac5860da2072dea013062e0eb86d6c783455dd3 + checksum: 383ee42788c605373b6938a0816caac802d8d38826bb8d0a9349ce57142e0dbacec4d4a49aedfa1fcba37f2249612e75e57fc89b5bd05954a195dbcab0a50663 languageName: node linkType: hard -"@storybook/addon-links@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-links@npm:6.5.13" +"@storybook/addon-links@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-links@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.13 - "@types/qs": ^6.9.5 - core-js: ^3.8.2 - global: ^4.4.0 - prop-types: ^15.7.2 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 + "@storybook/csf": ^0.1.2 + "@storybook/global": ^5.0.0 ts-dedent: ^2.0.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: react: optional: true - react-dom: - optional: true - checksum: 0bbe14652320c77dbcbcd0a6f45e4776de35475cca43b9825c36424e6ccc250fac4a172cdf01304debdc3c93d74ad2ea3bafb8a752e9a52a5e860f5bb9a397c2 + checksum: 52f706e6025dc970bfd51c0fb5e5bfa2d35c69736860161c5bd84ecac2ce9466a968d1706a3176df1bc593a3d6c480aba6853628d4c32e8793f7977edd48f44d languageName: node linkType: hard -"@storybook/addon-measure@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-measure@npm:6.5.13" +"@storybook/addon-measure@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-measure@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - core-js: ^3.8.2 - global: ^4.4.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: 4bf1823f83a51e773cb941329bd1e637fba7d84c238a2af442eba4ab9950bda2d199bfa18194c62d7e25891166d8c89bf130303fc6c94d5aafa2eb0aaaeafa27 + "@storybook/global": ^5.0.0 + tiny-invariant: ^1.3.1 + checksum: 222ca83d76b49840754cdcc7f3a111d5f6f1ac1050e6b77319f41c66495e9344f0d8e8035a0d68093214dd7376a23c8abd2cdfa83a876e2c7b999e5d2b0fd695 languageName: node linkType: hard -"@storybook/addon-outline@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-outline@npm:6.5.13" +"@storybook/addon-outline@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-outline@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - core-js: ^3.8.2 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 + "@storybook/global": ^5.0.0 ts-dedent: ^2.0.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: f9ea9278ba7dd118b9db2dc8b695d661d2d289d4e52f5af02ba0e30f13245043f0c565a71925d8111694a3a02e6b8b459fbd42786fe794895cdb5c8345adf5f5 + checksum: 29a14e5d0675e94aae59d4ee6a98862557d3164567188bf03563eeb7127f6d09b2fafb1d9613cb608953fcfdceb12e349de849690cbb11a8cf9d2e110e452618 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-toolbars@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: ec85023ffda5bdefe96cf6d6954c86fba67be190bb31cf3bb777d0a2493d606c78bed84d4bb19611d4114a35ba94f1efcc8331bc6032e0f9376a6fb797dc3ec2 +"@storybook/addon-toolbars@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-toolbars@npm:7.6.3" + checksum: dc54de28c8c3e5912ec4ff1125e8f83bce0b4e62a6eaac4e97a984ac0b29b849ccb1d72ad4611cb19fc3fa620091550dcbc13152254cdb44d0c70d083c3444a1 languageName: node linkType: hard -"@storybook/addon-viewport@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addon-viewport@npm:6.5.13" +"@storybook/addon-viewport@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/addon-viewport@npm:7.6.3" dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 memoizerific: ^1.11.3 - prop-types: ^15.7.2 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: ff602c8080c26a8513f76b9ba2cb2e287e31df598a5235aee8fa8c0337bb7e8e7d65620d03f2aa452801653c41d35ddf9dbfdb6a26d4f2b4d0cc7cff7b580915 + checksum: b75e5c8f5cbeccc193354794c0abe6e2d269cf0f1a6252f1ef88de13796487dbf740479c7b08e52247f19b2163f357054b01e0d5c4c58624184372c1ba4a3da5 languageName: node linkType: hard -"@storybook/addons@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/addons@npm:6.5.10" +"@storybook/blocks@npm:7.6.3, @storybook/blocks@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/blocks@npm:7.6.3" dependencies: - "@storybook/api": 6.5.10 - "@storybook/channels": 6.5.10 - "@storybook/client-logger": 6.5.10 - "@storybook/core-events": 6.5.10 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.10 - "@storybook/theming": 6.5.10 - "@types/webpack-env": ^1.16.0 - core-js: ^3.8.2 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 9143908c77ab77064a5da3de1fcfb218e5f0e561f4b8a083e59b4104e442567c87fb571a752bb11c469317fc3bbcb9c2e42ebd9a5a41f825b3fd67a920d90621 - languageName: node - linkType: hard - -"@storybook/addons@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/addons@npm:6.5.13" - dependencies: - "@storybook/api": 6.5.13 - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.13 - "@storybook/theming": 6.5.13 - "@types/webpack-env": ^1.16.0 - core-js: ^3.8.2 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 28589da00e8a26b44d4ed8a1938fe934187a85b187e2a0dcd3e6d114460ed5a07fbfe0a89a4d899739767b379015cbabadd47c5c266457922a8c4255e3d769a4 - languageName: node - linkType: hard - -"@storybook/addons@npm:^6.4.10": - version: 6.5.9 - resolution: "@storybook/addons@npm:6.5.9" - dependencies: - "@storybook/api": 6.5.9 - "@storybook/channels": 6.5.9 - "@storybook/client-logger": 6.5.9 - "@storybook/core-events": 6.5.9 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.9 - "@storybook/theming": 6.5.9 - "@types/webpack-env": ^1.16.0 - core-js: ^3.8.2 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 50e0579df27aa7d405e25c0f057e4cd2d37c091ee4b88ab7969238255738ab5eb7f8c5af3100eaeaea74f916288ed862291f517b8a05e30578d7d1fd254d9f8c - languageName: node - linkType: hard - -"@storybook/api@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/api@npm:6.5.10" - dependencies: - "@storybook/channels": 6.5.10 - "@storybook/client-logger": 6.5.10 - "@storybook/core-events": 6.5.10 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.10 - "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.10 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 + "@storybook/channels": 7.6.3 + "@storybook/client-logger": 7.6.3 + "@storybook/components": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/csf": ^0.1.2 + "@storybook/docs-tools": 7.6.3 + "@storybook/global": ^5.0.0 + "@storybook/manager-api": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@storybook/theming": 7.6.3 + "@storybook/types": 7.6.3 + "@types/lodash": ^4.14.167 + color-convert: ^2.0.1 + dequal: ^2.0.2 lodash: ^4.17.21 + markdown-to-jsx: ^7.1.8 memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - store2: ^2.12.0 - telejson: ^6.0.8 + polished: ^4.2.2 + react-colorful: ^5.1.2 + telejson: ^7.2.0 + tocbot: ^4.20.1 ts-dedent: ^2.0.0 util-deprecate: ^1.0.2 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 49e01f35fa6de776329407533c0449aac84bbc9404bf717b1cebff5dc8961618956d7ba0003361c4e6cdc24e898619f778fea15db5a30eb320fc73a4b53adb40 + checksum: f9ffa35975b79250f04eef1f8150e862d289f2e721d4f71c32897d925a0062c501c3723faf83902377e7f2e11bc4bd6e8e118be393820676c8817fcaf0c25d45 languageName: node linkType: hard -"@storybook/api@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/api@npm:6.5.13" +"@storybook/builder-manager@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/builder-manager@npm:7.6.3" dependencies: - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.13 - "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - store2: ^2.12.0 - telejson: ^6.0.8 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: dd7c8db0cdea2a47ab835c02217f10f99c54bfbf6d826deadf0b160ece4c94b1cb2558cfbaff4e4244c5c776095028a164762bd8de19fcfe10ae318fe0a3fbb4 - languageName: node - linkType: hard - -"@storybook/api@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/api@npm:6.5.9" - dependencies: - "@storybook/channels": 6.5.9 - "@storybook/client-logger": 6.5.9 - "@storybook/core-events": 6.5.9 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.9 - "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.9 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - store2: ^2.12.0 - telejson: ^6.0.8 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 72d720eba7a5f6645c92a18884e267b57d4ba145d9aafd891f3a9c7651e8ea1418ada7cf7f6d5d963db100526103d5fceac8fb0a82e8099478b02dc8f33a1fd7 - languageName: node - linkType: hard - -"@storybook/builder-vite@npm:^0.2.4": - version: 0.2.4 - resolution: "@storybook/builder-vite@npm:0.2.4" - dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": 0.0.5 - "@storybook/core-common": ^6.4.3 - "@storybook/mdx1-csf": ^0.0.4 - "@storybook/node-logger": ^6.4.3 - "@storybook/semver": ^7.3.2 - "@storybook/source-loader": ^6.4.3 - "@vitejs/plugin-react": ^2.0.0 - ast-types: ^0.14.2 - es-module-lexer: ^0.9.3 - glob: ^7.2.0 - glob-promise: ^4.2.0 - magic-string: ^0.26.1 - react-docgen: ^6.0.0-alpha.0 - slash: ^3.0.0 - sveltedoc-parser: ^4.2.1 - peerDependencies: - "@storybook/mdx2-csf": ^0.0.3 - "@sveltejs/vite-plugin-svelte": ^1.0.0 - "@vitejs/plugin-vue": ^3.0.0 - vite: ">= 3.0.0" - vue-docgen-api: ^4.40.0 - peerDependenciesMeta: - "@storybook/mdx2-csf": - optional: true - "@sveltejs/vite-plugin-svelte": - optional: true - "@vitejs/plugin-vue": - optional: true - vue-docgen-api: - optional: true - checksum: 23edba92697dcccfee2590e35e680b6cce50ba733e4b58ef29c555868de14e30bbf870a0defd66050838f6e2929b3daf03d4f9e6091988ead7ea075fab634778 - languageName: node - linkType: hard - -"@storybook/builder-webpack4@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/builder-webpack4@npm:6.5.13" - dependencies: - "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/channel-postmessage": 6.5.13 - "@storybook/channels": 6.5.13 - "@storybook/client-api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/preview-web": 6.5.13 - "@storybook/router": 6.5.13 - "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.13 - "@storybook/theming": 6.5.13 - "@storybook/ui": 6.5.13 - "@types/node": ^14.0.10 || ^16.0.0 - "@types/webpack": ^4.41.26 - autoprefixer: ^9.8.6 - babel-loader: ^8.0.0 - case-sensitive-paths-webpack-plugin: ^2.3.0 - core-js: ^3.8.2 - css-loader: ^3.6.0 - file-loader: ^6.2.0 - find-up: ^5.0.0 - fork-ts-checker-webpack-plugin: ^4.1.6 - glob: ^7.1.6 - glob-promise: ^3.4.0 - global: ^4.4.0 - html-webpack-plugin: ^4.0.0 - pnp-webpack-plugin: 1.6.4 - postcss: ^7.0.36 - postcss-flexbugs-fixes: ^4.2.1 - postcss-loader: ^4.2.0 - raw-loader: ^4.0.2 - stable: ^0.1.8 - style-loader: ^1.3.0 - terser-webpack-plugin: ^4.2.3 - ts-dedent: ^2.0.0 - url-loader: ^4.1.1 - util-deprecate: ^1.0.2 - webpack: 4 - webpack-dev-middleware: ^3.7.3 - webpack-filter-warnings-plugin: ^1.2.1 - webpack-hot-middleware: ^2.25.1 - webpack-virtual-modules: ^0.2.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: a95fea3951479d7724155a2ddbf2b04a8bfc0e7fddbf8415caed508b94ad71f7dc8d5d25464061ef26f3b4670f49f6ed40198b48b3f646d7af17d8daee5e89cb - languageName: node - linkType: hard - -"@storybook/builder-webpack5@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/builder-webpack5@npm:6.5.13" - dependencies: - "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/channel-postmessage": 6.5.13 - "@storybook/channels": 6.5.13 - "@storybook/client-api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/preview-web": 6.5.13 - "@storybook/router": 6.5.13 - "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.13 - "@storybook/theming": 6.5.13 - "@types/node": ^14.0.10 || ^16.0.0 - babel-loader: ^8.0.0 - babel-plugin-named-exports-order: ^0.0.2 + "@fal-works/esbuild-plugin-global-externals": ^2.1.2 + "@storybook/core-common": 7.6.3 + "@storybook/manager": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@types/ejs": ^3.1.1 + "@types/find-cache-dir": ^3.2.1 + "@yarnpkg/esbuild-plugin-pnp": ^3.0.0-rc.10 browser-assert: ^1.2.1 - case-sensitive-paths-webpack-plugin: ^2.3.0 - core-js: ^3.8.2 - css-loader: ^5.0.1 - fork-ts-checker-webpack-plugin: ^6.0.4 - glob: ^7.1.6 - glob-promise: ^3.4.0 - html-webpack-plugin: ^5.0.0 + ejs: ^3.1.8 + esbuild: ^0.18.0 + esbuild-plugin-alias: ^0.2.1 + express: ^4.17.3 + find-cache-dir: ^3.0.0 + fs-extra: ^11.1.0 + process: ^0.11.10 + util: ^0.12.4 + checksum: ada9bcbf71956df90e17937f6c6a8a5a8182cdeaaa220ae4591223caf0510bbf5c826de25fd7e547f7d89c3b594a62020bb178ba2c18c7524575c5ed9268ccce + languageName: node + linkType: hard + +"@storybook/builder-webpack5@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/builder-webpack5@npm:7.6.3" + dependencies: + "@babel/core": ^7.23.2 + "@storybook/channels": 7.6.3 + "@storybook/client-logger": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/core-webpack": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/preview": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@swc/core": ^1.3.82 + "@types/node": ^18.0.0 + "@types/semver": ^7.3.4 + babel-loader: ^9.0.0 + browser-assert: ^1.2.1 + case-sensitive-paths-webpack-plugin: ^2.4.0 + constants-browserify: ^1.0.0 + css-loader: ^6.7.1 + es-module-lexer: ^1.4.1 + express: ^4.17.3 + fork-ts-checker-webpack-plugin: ^8.0.0 + fs-extra: ^11.1.0 + html-webpack-plugin: ^5.5.0 + magic-string: ^0.30.5 path-browserify: ^1.0.1 process: ^0.11.10 - stable: ^0.1.8 - style-loader: ^2.0.0 - terser-webpack-plugin: ^5.0.3 + semver: ^7.3.7 + style-loader: ^3.3.1 + swc-loader: ^0.2.3 + terser-webpack-plugin: ^5.3.1 ts-dedent: ^2.0.0 + url: ^0.11.0 + util: ^0.12.4 util-deprecate: ^1.0.2 - webpack: ^5.9.0 - webpack-dev-middleware: ^4.1.0 + webpack: 5 + webpack-dev-middleware: ^6.1.1 webpack-hot-middleware: ^2.25.1 - webpack-virtual-modules: ^0.4.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + webpack-virtual-modules: ^0.5.0 peerDependenciesMeta: typescript: optional: true - checksum: f980ab5c832bac9584f80eb0229934bb58ceb4cf24f756043acea4cb397f52f7af49c1fd53ac897aefb5ffc9c808bcb8a0c4867afd2c2a21b7ba2f0a56ae0089 + checksum: 508d5393ef56559c72ba5c83a94d15a911941c666e081c921d3ab7ba981c55c2d9768d0c258626c663abe2561901d846c618fe37b3a78192567e1ada51c7f4de languageName: node linkType: hard -"@storybook/channel-postmessage@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/channel-postmessage@npm:6.5.13" +"@storybook/channels@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/channels@npm:7.6.3" dependencies: - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 + "@storybook/client-logger": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/global": ^5.0.0 qs: ^6.10.0 - telejson: ^6.0.8 - checksum: 8d6ccfff2aeafaae30b5fc1af856be8d06b3703b96841ecc0d70959e51542514901763e1a291e1d0278afe31b23cc5c0a5b351994f321e9bd03490be5b51e2d0 + telejson: ^7.2.0 + tiny-invariant: ^1.3.1 + checksum: 5ba5f69897363a63b0da2533cf6b2e742fa0dde2b7b70d60910f75ce6e56786a2642c4351a95062d71aa961ec1f2f999704952237f26aaf12053035257253aab languageName: node linkType: hard -"@storybook/channel-websocket@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/channel-websocket@npm:6.5.13" +"@storybook/cli@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/cli@npm:7.6.3" dependencies: - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 - telejson: ^6.0.8 - checksum: 16e3b1a51a1af093f6c78ab7ca9c4c69ed05b45fd9bbdefb3050809e92064ccbf7a46f6800d21df2555e5d110d64735dd8d35155e54c3118fa7b4efe6b3b0457 - languageName: node - linkType: hard - -"@storybook/channels@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/channels@npm:6.5.10" - dependencies: - core-js: ^3.8.2 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - checksum: 3837d2aff1575aa8d5af77162781b2824b909f18a7e7d3b961e6a14854b58011a56bd4f6c92bf065b8856fbcf7925a5849ffc56e42badac240701a560a26c627 - languageName: node - linkType: hard - -"@storybook/channels@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/channels@npm:6.5.13" - dependencies: - core-js: ^3.8.2 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - checksum: 5b8881a2799a4c5ceafea40bc2c8bad1a31649036341eec8da5a77acf79a9d610afeaa5b4ed5d06022ed3c74cb9562dcfc5046d62fd8d27cd65bcba09aa5e903 - languageName: node - linkType: hard - -"@storybook/channels@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/channels@npm:6.5.9" - dependencies: - core-js: ^3.8.2 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - checksum: b51767553a3e00f4da8e9684c798348c230d5553a43886ca560c7e2f249e15ab9e3d7bbeb947d394413505261806c79c629551f9d722f83f00e15d9e19b6617c - languageName: node - linkType: hard - -"@storybook/client-api@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/client-api@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/channel-postmessage": 6.5.13 - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.13 - "@types/qs": ^6.9.5 - "@types/webpack-env": ^1.16.0 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - store2: ^2.12.0 - synchronous-promise: ^2.0.15 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: b0af25786b9144a55ebaa7754dd1b3701f5f8796770eaf59e7bc6d21ada12911fcbe4bf0da037d01bdda2c46138f265a948befe1f3de356fdc0ae3af80973388 - languageName: node - linkType: hard - -"@storybook/client-logger@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/client-logger@npm:6.5.10" - dependencies: - core-js: ^3.8.2 - global: ^4.4.0 - checksum: 6aa15e27e1f805b34332f647545eb53277c87492044073daf31ac6151b274cb7da6d2c8b3831484bb0c4c410f8adc1bb13322c3b80ee2f88e30856721c7d9ab1 - languageName: node - linkType: hard - -"@storybook/client-logger@npm:6.5.13, @storybook/client-logger@npm:^6.4.0": - version: 6.5.13 - resolution: "@storybook/client-logger@npm:6.5.13" - dependencies: - core-js: ^3.8.2 - global: ^4.4.0 - checksum: 0252d9364a0b2a8faae588fdb29aaf458f660904c330ec7af790f63a668710926ece8f087f58f9b1bebb052e2fe517b8b74867e7500567499cc710ab71ccbbab - languageName: node - linkType: hard - -"@storybook/client-logger@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/client-logger@npm:6.5.9" - dependencies: - core-js: ^3.8.2 - global: ^4.4.0 - checksum: 5b72d93a57fae8d188bb40db0a3af3ce9f3ccc58751e90d38e0786b58f26a5358d10339916455646a8d60e2cc749d761990927fdeb06e5f09e68d48fe50a5de7 - languageName: node - linkType: hard - -"@storybook/components@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/components@npm:6.5.13" - dependencies: - "@storybook/client-logger": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 5d01c0f445f6574ccadcfa79afd99c078bd1f81d65e59186361100dc57bd73ccbb877e5a8bbc49dd6551bce1b32fbe6f135c2bea15c0126a83faf222cfaed878 - languageName: node - linkType: hard - -"@storybook/core-client@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/core-client@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/channel-postmessage": 6.5.13 - "@storybook/channel-websocket": 6.5.13 - "@storybook/client-api": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/preview-web": 6.5.13 - "@storybook/store": 6.5.13 - "@storybook/ui": 6.5.13 - airbnb-js-shims: ^2.2.1 - ansi-to-html: ^0.6.11 - core-js: ^3.8.2 - global: ^4.4.0 - lodash: ^4.17.21 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - ts-dedent: ^2.0.0 - unfetch: ^4.2.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - webpack: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: c4350b1b579f0781a239fdede79f1d0975e297ecb61ba4096834d62bd553420615231dc9146c446d0178088e83863fd9dc720fbb4485b5779fac7d99ce3eeb9e - languageName: node - linkType: hard - -"@storybook/core-common@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/core-common@npm:6.5.13" - dependencies: - "@babel/core": ^7.12.10 - "@babel/plugin-proposal-class-properties": ^7.12.1 - "@babel/plugin-proposal-decorators": ^7.12.12 - "@babel/plugin-proposal-export-default-from": ^7.12.1 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.12.1 - "@babel/plugin-proposal-object-rest-spread": ^7.12.1 - "@babel/plugin-proposal-optional-chaining": ^7.12.7 - "@babel/plugin-proposal-private-methods": ^7.12.1 - "@babel/plugin-proposal-private-property-in-object": ^7.12.1 - "@babel/plugin-syntax-dynamic-import": ^7.8.3 - "@babel/plugin-transform-arrow-functions": ^7.12.1 - "@babel/plugin-transform-block-scoping": ^7.12.12 - "@babel/plugin-transform-classes": ^7.12.1 - "@babel/plugin-transform-destructuring": ^7.12.1 - "@babel/plugin-transform-for-of": ^7.12.1 - "@babel/plugin-transform-parameters": ^7.12.1 - "@babel/plugin-transform-shorthand-properties": ^7.12.1 - "@babel/plugin-transform-spread": ^7.12.1 - "@babel/preset-env": ^7.12.11 - "@babel/preset-react": ^7.12.10 - "@babel/preset-typescript": ^7.12.7 - "@babel/register": ^7.12.1 - "@storybook/node-logger": 6.5.13 - "@storybook/semver": ^7.3.2 - "@types/node": ^14.0.10 || ^16.0.0 - "@types/pretty-hrtime": ^1.0.0 - babel-loader: ^8.0.0 - babel-plugin-macros: ^3.0.1 - babel-plugin-polyfill-corejs3: ^0.1.0 + "@babel/core": ^7.23.2 + "@babel/preset-env": ^7.23.2 + "@babel/types": ^7.23.0 + "@ndelangen/get-tarball": ^3.0.7 + "@storybook/codemod": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/core-server": 7.6.3 + "@storybook/csf-tools": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/telemetry": 7.6.3 + "@storybook/types": 7.6.3 + "@types/semver": ^7.3.4 + "@yarnpkg/fslib": 2.10.3 + "@yarnpkg/libzip": 2.3.0 chalk: ^4.1.0 - core-js: ^3.8.2 - express: ^4.17.1 - file-system-cache: ^1.0.5 + commander: ^6.2.1 + cross-spawn: ^7.0.3 + detect-indent: ^6.1.0 + envinfo: ^7.7.3 + execa: ^5.0.0 + express: ^4.17.3 find-up: ^5.0.0 - fork-ts-checker-webpack-plugin: ^6.0.4 - fs-extra: ^9.0.1 - glob: ^7.1.6 + fs-extra: ^11.1.0 + get-npm-tarball-url: ^2.0.3 + get-port: ^5.1.1 + giget: ^1.0.0 + globby: ^11.0.2 + jscodeshift: ^0.15.1 + leven: ^3.1.0 + ora: ^5.4.1 + prettier: ^2.8.0 + prompts: ^2.4.0 + puppeteer-core: ^2.1.1 + read-pkg-up: ^7.0.1 + semver: ^7.3.7 + simple-update-notifier: ^2.0.0 + strip-json-comments: ^3.0.1 + tempy: ^1.0.1 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + bin: + getstorybook: ./bin/index.js + sb: ./bin/index.js + checksum: 2b95a22935ea78f1cd0e31f4b5d96dc4185c873e491518bf34cfb605f8bbdce45ba10352597334e7ecfd4bdbbd867840d42942abf20ceee62c6dc0cfceee9ded + languageName: node + linkType: hard + +"@storybook/client-logger@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/client-logger@npm:7.6.3" + dependencies: + "@storybook/global": ^5.0.0 + checksum: cafb2c3c6d639d341585526e52d917a5fbb147e07be5296a94e31ed85aa48d6a4f083a33fca3f2a08a81e7f2057459f516678804615be3cf0997eb3b0cf1ba5d + languageName: node + linkType: hard + +"@storybook/codemod@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/codemod@npm:7.6.3" + dependencies: + "@babel/core": ^7.23.2 + "@babel/preset-env": ^7.23.2 + "@babel/types": ^7.23.0 + "@storybook/csf": ^0.1.2 + "@storybook/csf-tools": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/types": 7.6.3 + "@types/cross-spawn": ^6.0.2 + cross-spawn: ^7.0.3 + globby: ^11.0.2 + jscodeshift: ^0.15.1 + lodash: ^4.17.21 + prettier: ^2.8.0 + recast: ^0.23.1 + checksum: cb33a65b5162890b361b911ae0ac023df416615dc66eed76fb4a278c92dadcf7d715516d95ec784409d5b79c34265e5443472b29c1dc8743ca75ff5283158723 + languageName: node + linkType: hard + +"@storybook/components@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/components@npm:7.6.3" + dependencies: + "@radix-ui/react-select": ^1.2.2 + "@radix-ui/react-toolbar": ^1.0.4 + "@storybook/client-logger": 7.6.3 + "@storybook/csf": ^0.1.2 + "@storybook/global": ^5.0.0 + "@storybook/theming": 7.6.3 + "@storybook/types": 7.6.3 + memoizerific: ^1.11.3 + use-resize-observer: ^9.1.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 8e79e0153592fa8530d02fca7a26d92a411990de4bd5732e80f9fb11ebdd9160a7ddb46cef8ad26b42af71ef6ac14c64744c61377a02bdeb1a00294008669d76 + languageName: node + linkType: hard + +"@storybook/core-client@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/core-client@npm:7.6.3" + dependencies: + "@storybook/client-logger": 7.6.3 + "@storybook/preview-api": 7.6.3 + checksum: 29e56be0119587fbb4c6ed0800128d710ef89c87bf8f93120a62e67482db9dcc1f793d56fcab27c5b5d1d2ab4f833c0aad995cc98e514d8b0767acd2c32742ad + languageName: node + linkType: hard + +"@storybook/core-common@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/core-common@npm:7.6.3" + dependencies: + "@storybook/core-events": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/types": 7.6.3 + "@types/find-cache-dir": ^3.2.1 + "@types/node": ^18.0.0 + "@types/node-fetch": ^2.6.4 + "@types/pretty-hrtime": ^1.0.0 + chalk: ^4.1.0 + esbuild: ^0.18.0 + esbuild-register: ^3.5.0 + file-system-cache: 2.3.0 + find-cache-dir: ^3.0.0 + find-up: ^5.0.0 + fs-extra: ^11.1.0 + glob: ^10.0.0 handlebars: ^4.7.7 - interpret: ^2.2.0 - json5: ^2.1.3 - lazy-universal-dotenv: ^3.0.1 + lazy-universal-dotenv: ^4.0.0 + node-fetch: ^2.0.0 picomatch: ^2.3.0 pkg-dir: ^5.0.0 pretty-hrtime: ^1.0.3 resolve-from: ^5.0.0 - slash: ^3.0.0 - telejson: ^6.0.8 ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - webpack: 4 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 369fbe41e9ac657410a8e7fb4668be0e77c50b84c29b352397cc26b72d79397a7e84dcbf7a94f2d02d819d395a66e30a3915de40e85936d7b7dc50bb426aeabb + checksum: 89bd7b36d2e5c0f8bcd599353811942d52a490b7331c67464f408b4a2e91ecd95bc5d8201706ef53f352eaf5bae47a30ad5745c478b84daf407de792bf8788e9 languageName: node linkType: hard -"@storybook/core-common@npm:^6.4.3": - version: 6.5.10 - resolution: "@storybook/core-common@npm:6.5.10" +"@storybook/core-events@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/core-events@npm:7.6.3" dependencies: - "@babel/core": ^7.12.10 - "@babel/plugin-proposal-class-properties": ^7.12.1 - "@babel/plugin-proposal-decorators": ^7.12.12 - "@babel/plugin-proposal-export-default-from": ^7.12.1 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.12.1 - "@babel/plugin-proposal-object-rest-spread": ^7.12.1 - "@babel/plugin-proposal-optional-chaining": ^7.12.7 - "@babel/plugin-proposal-private-methods": ^7.12.1 - "@babel/plugin-proposal-private-property-in-object": ^7.12.1 - "@babel/plugin-syntax-dynamic-import": ^7.8.3 - "@babel/plugin-transform-arrow-functions": ^7.12.1 - "@babel/plugin-transform-block-scoping": ^7.12.12 - "@babel/plugin-transform-classes": ^7.12.1 - "@babel/plugin-transform-destructuring": ^7.12.1 - "@babel/plugin-transform-for-of": ^7.12.1 - "@babel/plugin-transform-parameters": ^7.12.1 - "@babel/plugin-transform-shorthand-properties": ^7.12.1 - "@babel/plugin-transform-spread": ^7.12.1 - "@babel/preset-env": ^7.12.11 - "@babel/preset-react": ^7.12.10 - "@babel/preset-typescript": ^7.12.7 - "@babel/register": ^7.12.1 - "@storybook/node-logger": 6.5.10 - "@storybook/semver": ^7.3.2 - "@types/node": ^14.0.10 || ^16.0.0 - "@types/pretty-hrtime": ^1.0.0 - babel-loader: ^8.0.0 - babel-plugin-macros: ^3.0.1 - babel-plugin-polyfill-corejs3: ^0.1.0 - chalk: ^4.1.0 - core-js: ^3.8.2 - express: ^4.17.1 - file-system-cache: ^1.0.5 - find-up: ^5.0.0 - fork-ts-checker-webpack-plugin: ^6.0.4 - fs-extra: ^9.0.1 - glob: ^7.1.6 - handlebars: ^4.7.7 - interpret: ^2.2.0 - json5: ^2.1.3 - lazy-universal-dotenv: ^3.0.1 - picomatch: ^2.3.0 - pkg-dir: ^5.0.0 - pretty-hrtime: ^1.0.3 - resolve-from: ^5.0.0 - slash: ^3.0.0 - telejson: ^6.0.8 ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - webpack: 4 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: b3b95214a427c1ff34464c1638219fd34aa8a98b60541ec3e13d84b095be79773e5de64c958903da877e6ec52b88ff05dd9a8cd7ab0fde548ffa0db762a4ea4e + checksum: 32cc9e16809f30317671d0043aec6fb3bc78c7d942b056d35f9f6a5413896566a5c31f4aa634df85333c74c95f37bfd28e75f6130c74270e15af6f33a68ecb7e languageName: node linkType: hard -"@storybook/core-events@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/core-events@npm:6.5.10" - dependencies: - core-js: ^3.8.2 - checksum: 89139f3f34a4ea0f2bbc02ebaa2968664cdc17abd88cc2e0467a0dfb1c11577e85fa402e5804fe4d6a99edd696d365abf93d30c396fc177563478cdbb68bcb85 - languageName: node - linkType: hard - -"@storybook/core-events@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/core-events@npm:6.5.13" - dependencies: - core-js: ^3.8.2 - checksum: 2afeaf5fd658a4e9eedb9ad458ba0bcc73ad4ae5ba0e9971434818258db01d9b48b604d4db396ebfc1e1571dace3f6659e9ed61ac35428a792a4e24bbc08b29c - languageName: node - linkType: hard - -"@storybook/core-events@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/core-events@npm:6.5.9" - dependencies: - core-js: ^3.8.2 - checksum: b28af71de1e7f66a6fdf26c384c976640220ea1a6d807523ec368ecdc1b9dd3c87d5e1fcc5bd443d1059c408c17288afb415f8160e69ebb6cb2f3914a2db5f1d - languageName: node - linkType: hard - -"@storybook/core-server@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/core-server@npm:6.5.13" +"@storybook/core-server@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/core-server@npm:7.6.3" dependencies: + "@aw-web-design/x-default-browser": 1.4.126 "@discoveryjs/json-ext": ^0.5.3 - "@storybook/builder-webpack4": 6.5.13 - "@storybook/core-client": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/csf-tools": 6.5.13 - "@storybook/manager-webpack4": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.13 - "@storybook/telemetry": 6.5.13 - "@types/node": ^14.0.10 || ^16.0.0 - "@types/node-fetch": ^2.5.7 + "@storybook/builder-manager": 7.6.3 + "@storybook/channels": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/csf": ^0.1.2 + "@storybook/csf-tools": 7.6.3 + "@storybook/docs-mdx": ^0.1.0 + "@storybook/global": ^5.0.0 + "@storybook/manager": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@storybook/telemetry": 7.6.3 + "@storybook/types": 7.6.3 + "@types/detect-port": ^1.3.0 + "@types/node": ^18.0.0 "@types/pretty-hrtime": ^1.0.0 - "@types/webpack": ^4.41.26 - better-opn: ^2.1.1 - boxen: ^5.1.2 + "@types/semver": ^7.3.4 + better-opn: ^3.0.2 chalk: ^4.1.0 cli-table3: ^0.6.1 - commander: ^6.2.1 compression: ^1.7.4 - core-js: ^3.8.2 - cpy: ^8.1.2 detect-port: ^1.3.0 - express: ^4.17.1 - fs-extra: ^9.0.1 - global: ^4.4.0 + express: ^4.17.3 + fs-extra: ^11.1.0 globby: ^11.0.2 ip: ^2.0.0 lodash: ^4.17.21 - node-fetch: ^2.6.7 open: ^8.4.0 pretty-hrtime: ^1.0.3 prompts: ^2.4.0 - regenerator-runtime: ^0.13.7 - serve-favicon: ^2.5.0 - slash: ^3.0.0 - telejson: ^6.0.8 + read-pkg-up: ^7.0.1 + semver: ^7.3.7 + telejson: ^7.2.0 + tiny-invariant: ^1.3.1 ts-dedent: ^2.0.0 + util: ^0.12.4 util-deprecate: ^1.0.2 watchpack: ^2.2.0 - webpack: 4 ws: ^8.2.3 - x-default-browser: ^0.4.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@storybook/builder-webpack5": - optional: true - "@storybook/manager-webpack5": - optional: true - typescript: - optional: true - checksum: 142b13ef4fef21a68c8255f35f42ca5c3b9f636b51986f61dc6ae95485788d0f581552604b8a4a796e7cd2ad9548b87317ac9647ea44be58f64b91c440bb71ea + checksum: f72931d3945be4c1f9fb02bd3b7107a871585b1de8623ff95e68caca27037de0a95276fc945f594f26b29d15c4aa1c60da5b9fac7f64201c5fd03383ebdc3259 languageName: node linkType: hard -"@storybook/core@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/core@npm:6.5.13" +"@storybook/core-webpack@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/core-webpack@npm:7.6.3" dependencies: - "@storybook/core-client": 6.5.13 - "@storybook/core-server": 6.5.13 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - webpack: "*" - peerDependenciesMeta: - "@storybook/builder-webpack5": - optional: true - "@storybook/manager-webpack5": - optional: true - typescript: - optional: true - checksum: e0dbe5d8d52f2a12ab63db965d5d15ee671029a03b7204756954e2bc3253b560c8e430aaab5559ccbddcfd3e97d2bc1c0c58ed370aa593912724402aa86996b8 - languageName: node - linkType: hard - -"@storybook/csf-tools@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/csf-tools@npm:6.5.13" - dependencies: - "@babel/core": ^7.12.10 - "@babel/generator": ^7.12.11 - "@babel/parser": ^7.12.11 - "@babel/plugin-transform-react-jsx": ^7.12.12 - "@babel/preset-env": ^7.12.11 - "@babel/traverse": ^7.12.11 - "@babel/types": ^7.12.11 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/mdx1-csf": ^0.0.1 - core-js: ^3.8.2 - fs-extra: ^9.0.1 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 + "@storybook/core-common": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/types": 7.6.3 + "@types/node": ^18.0.0 ts-dedent: ^2.0.0 - peerDependencies: - "@storybook/mdx2-csf": ^0.0.3 - peerDependenciesMeta: - "@storybook/mdx2-csf": - optional: true - checksum: 2b8a5bed04ea89084334742e1095c4565b0b7367b5126e3a9b6648224b59c2136a9d57cbb9067264fc3951e9db58df40b23b975170180d171cce35dfabf2a090 + checksum: a641f32a560a5b537142b4765afd55e8003784fc08a5d06a29022541d3732deaf90b32afe5f7c26a1e64bd2d238fea72238bb36fc41247d769019998e1a78718 languageName: node linkType: hard -"@storybook/csf@npm:0.0.2--canary.4566f4d.1": - version: 0.0.2--canary.4566f4d.1 - resolution: "@storybook/csf@npm:0.0.2--canary.4566f4d.1" +"@storybook/csf-plugin@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/csf-plugin@npm:7.6.3" dependencies: - lodash: ^4.17.15 - checksum: afac948e1eae72f020b3708538dd2553524f291bc129ecb2941983668fd62b17448e52f9c9be5b8edeea7a64d96f620bbac78b8acc10ece11b8279930a1deb03 + "@storybook/csf-tools": 7.6.3 + unplugin: ^1.3.1 + checksum: 6c396b323d9b19b1e5a65fb8bcbc90d270fce08db80b00b297568bc7d3ae1d68bfef8e0df2600e1f6d5acf3b2e3ac671aecdd7d2bfeebd7d98c368cfa3e597ce languageName: node linkType: hard -"@storybook/docs-tools@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/docs-tools@npm:6.5.13" +"@storybook/csf-tools@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/csf-tools@npm:7.6.3" dependencies: - "@babel/core": ^7.12.10 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.13 - core-js: ^3.8.2 + "@babel/generator": ^7.23.0 + "@babel/parser": ^7.23.0 + "@babel/traverse": ^7.23.2 + "@babel/types": ^7.23.0 + "@storybook/csf": ^0.1.2 + "@storybook/types": 7.6.3 + fs-extra: ^11.1.0 + recast: ^0.23.1 + ts-dedent: ^2.0.0 + checksum: ee6f8e7b579692d340fff8e2d6585520ebf22cea18d5bc09d8601b0699b18a3d11b89607701213e1985f5152e5de859120fb01337e779246a2b1b0c156cbfc67 + languageName: node + linkType: hard + +"@storybook/csf@npm:^0.1.2": + version: 0.1.2 + resolution: "@storybook/csf@npm:0.1.2" + dependencies: + type-fest: ^2.19.0 + checksum: 22038dfd5e46cd9565c3dec615918c0712eb5fc5f56e9ec89cfa75d7b48667b8fcbf7e9d1f46c9f4d440eee074f1d23a84dc56a937add37b28ddf890fdedfb8a + languageName: node + linkType: hard + +"@storybook/docs-mdx@npm:^0.1.0": + version: 0.1.0 + resolution: "@storybook/docs-mdx@npm:0.1.0" + checksum: a7770842c3947a761bcbe776a9c4fd35163d30c3274fca034169f69ff614242eaa4cacaa2c95fd215827081ef9a43f4774d521a6f43a4d063ea5f4ea14b1d69a + languageName: node + linkType: hard + +"@storybook/docs-tools@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/docs-tools@npm:7.6.3" + dependencies: + "@storybook/core-common": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@storybook/types": 7.6.3 + "@types/doctrine": ^0.0.3 + assert: ^2.1.0 doctrine: ^3.0.0 lodash: ^4.17.21 - regenerator-runtime: ^0.13.7 - checksum: d3ad4674922025aaf6e4e2b7c2ac6f4eaec8f5692dc9a792a15d2d5e38dcd2e1c138daffb31a3da04da1e04c645b3cd8d921890f9ba318bfee7b6a12f263b48a + checksum: f01b4edc3d4d029c105d2d4424b374fc831d4e0f476b6af72408175b248e09959a52fb5152a714092e1d2e8e112b2608dd17f11dbd53a77d82a09cb3547c2c77 languageName: node linkType: hard -"@storybook/instrumenter@npm:6.5.13, @storybook/instrumenter@npm:^6.4.0": - version: 6.5.13 - resolution: "@storybook/instrumenter@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - core-js: ^3.8.2 - global: ^4.4.0 - checksum: bc4be0d2b2666eb8fe81f01dd47b459882c8fde0ad9701f6149f9c7bba29d11c88081334b4512638dda1f09e4df0c4afb1a3092d5a999005e20acf1723e07e3c +"@storybook/global@npm:^5.0.0": + version: 5.0.0 + resolution: "@storybook/global@npm:5.0.0" + checksum: ede0ad35ec411fe31c61150dbd118fef344d1d0e72bf5d3502368e35cf68126f6b7ae4a0ab5e2ffe2f0baa3b4286f03ad069ba3e098e1725449ef08b7e154ba8 languageName: node linkType: hard -"@storybook/manager-webpack4@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/manager-webpack4@npm:6.5.13" +"@storybook/manager-api@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/manager-api@npm:7.6.3" dependencies: - "@babel/core": ^7.12.10 - "@babel/plugin-transform-template-literals": ^7.12.1 - "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.13 - "@storybook/core-client": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/theming": 6.5.13 - "@storybook/ui": 6.5.13 - "@types/node": ^14.0.10 || ^16.0.0 - "@types/webpack": ^4.41.26 - babel-loader: ^8.0.0 - case-sensitive-paths-webpack-plugin: ^2.3.0 - chalk: ^4.1.0 - core-js: ^3.8.2 - css-loader: ^3.6.0 - express: ^4.17.1 - file-loader: ^6.2.0 - find-up: ^5.0.0 - fs-extra: ^9.0.1 - html-webpack-plugin: ^4.0.0 - node-fetch: ^2.6.7 - pnp-webpack-plugin: 1.6.4 - read-pkg-up: ^7.0.1 - regenerator-runtime: ^0.13.7 - resolve-from: ^5.0.0 - style-loader: ^1.3.0 - telejson: ^6.0.8 - terser-webpack-plugin: ^4.2.3 + "@storybook/channels": 7.6.3 + "@storybook/client-logger": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/csf": ^0.1.2 + "@storybook/global": ^5.0.0 + "@storybook/router": 7.6.3 + "@storybook/theming": 7.6.3 + "@storybook/types": 7.6.3 + dequal: ^2.0.2 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + semver: ^7.3.7 + store2: ^2.14.2 + telejson: ^7.2.0 ts-dedent: ^2.0.0 - url-loader: ^4.1.1 - util-deprecate: ^1.0.2 - webpack: 4 - webpack-dev-middleware: ^3.7.3 - webpack-virtual-modules: ^0.2.2 + checksum: 5d9fa45b8129ec2a8651ee46101b5f8ef66c36b8085f6c2d9be7faebf45cf393e07ad33f95d6ed1d9a256b89fd3ec08e6363df020aa08696a7568506baa6011f + languageName: node + linkType: hard + +"@storybook/manager@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/manager@npm:7.6.3" + checksum: affc4bf55f66874e7e1e26e193212d51b1df24bc5e2ac907bfb14fc0d17a7f111394dd432cf398ffcf72563e8d15e8eea2961459eb1b6ed05810ef88663e8a7f + languageName: node + linkType: hard + +"@storybook/mdx2-csf@npm:^1.0.0": + version: 1.1.0 + resolution: "@storybook/mdx2-csf@npm:1.1.0" + checksum: 5ccdb13f4e59b989499f76e54ffaffb96b5710a696346efe19989b3373f375703adf516780894b270fa64a7e765b55274dc18575fc4a84e7fa92b844a4467c5d + languageName: node + linkType: hard + +"@storybook/nextjs@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/nextjs@npm:7.6.3" + dependencies: + "@babel/core": ^7.23.2 + "@babel/plugin-syntax-bigint": ^7.8.3 + "@babel/plugin-syntax-dynamic-import": ^7.8.3 + "@babel/plugin-syntax-import-assertions": ^7.22.5 + "@babel/plugin-transform-class-properties": ^7.22.5 + "@babel/plugin-transform-export-namespace-from": ^7.22.11 + "@babel/plugin-transform-numeric-separator": ^7.22.11 + "@babel/plugin-transform-object-rest-spread": ^7.22.15 + "@babel/plugin-transform-runtime": ^7.23.2 + "@babel/preset-env": ^7.23.2 + "@babel/preset-react": ^7.22.15 + "@babel/preset-typescript": ^7.23.2 + "@babel/runtime": ^7.23.2 + "@storybook/addon-actions": 7.6.3 + "@storybook/builder-webpack5": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/preset-react-webpack": 7.6.3 + "@storybook/preview-api": 7.6.3 + "@storybook/react": 7.6.3 + "@types/node": ^18.0.0 + css-loader: ^6.7.3 + find-up: ^5.0.0 + fs-extra: ^11.1.0 + image-size: ^1.0.0 + loader-utils: ^3.2.1 + node-polyfill-webpack-plugin: ^2.0.1 + pnp-webpack-plugin: ^1.7.0 + postcss: ^8.4.21 + postcss-loader: ^7.0.2 + resolve-url-loader: ^5.0.0 + sass-loader: ^12.4.0 + semver: ^7.3.5 + sharp: ^0.32.6 + style-loader: ^3.3.1 + styled-jsx: 5.1.1 + ts-dedent: ^2.0.0 + tsconfig-paths: ^4.0.0 + tsconfig-paths-webpack-plugin: ^4.0.1 peerDependencies: + "@next/font": ^13.0.0|| ^14.0.0 + next: ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + webpack: ^5.0.0 + peerDependenciesMeta: + "@next/font": + optional: true + typescript: + optional: true + webpack: + optional: true + checksum: 031ac99da47c5cc868b7f09194244166932acf06266b7356985f5df38836e3d3a2de40f5335cad3032930057c0616ed5928dbb51396fc5da4629335fe9512585 + languageName: node + linkType: hard + +"@storybook/node-logger@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/node-logger@npm:7.6.3" + checksum: 2288fcc23e9240e1b236cb074fd1be932ccee38e75aab2884725548366f1be6b757ce0736af05c1e584b9a30fe999e15737cd7a2e6007db57a6baa796b04b127 + languageName: node + linkType: hard + +"@storybook/postinstall@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/postinstall@npm:7.6.3" + checksum: 65e43b47fab9fd4388173c2aa72646cf49e283c55b8e2c3e4c3a6358a8c55e68131e44b8ab84d8c1853128eaaf20643613b783ceaf381ef413e3d64ff75417ed + languageName: node + linkType: hard + +"@storybook/preset-react-webpack@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/preset-react-webpack@npm:7.6.3" + dependencies: + "@babel/preset-flow": ^7.22.15 + "@babel/preset-react": ^7.22.15 + "@pmmmwh/react-refresh-webpack-plugin": ^0.5.11 + "@storybook/core-webpack": 7.6.3 + "@storybook/docs-tools": 7.6.3 + "@storybook/node-logger": 7.6.3 + "@storybook/react": 7.6.3 + "@storybook/react-docgen-typescript-plugin": 1.0.6--canary.9.0c3f3b7.0 + "@types/node": ^18.0.0 + "@types/semver": ^7.3.4 + babel-plugin-add-react-displayname: ^0.0.5 + fs-extra: ^11.1.0 + magic-string: ^0.30.5 + react-docgen: ^7.0.0 + react-refresh: ^0.14.0 + semver: ^7.3.7 + webpack: 5 + peerDependencies: + "@babel/core": ^7.22.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: + "@babel/core": + optional: true typescript: optional: true - checksum: 6645f30b6199d0badb2097aca16478e4d8bc88210b55e60c4da86962e8b0a3441128f1e696cddf8a535591920617a7abf5f1f9771dabc486479e76b8d4b20fbd + checksum: 2dba48673e9697ab3b15d9b00a06318627515c9686ab35af4ebeaa726c04c18f0d8f36a795ca838bb61662419a4c06e9d4b88f09df8b80f2d02686d5699f294a languageName: node linkType: hard -"@storybook/manager-webpack5@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/manager-webpack5@npm:6.5.13" +"@storybook/preview-api@npm:7.6.3, @storybook/preview-api@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/preview-api@npm:7.6.3" dependencies: - "@babel/core": ^7.12.10 - "@babel/plugin-transform-template-literals": ^7.12.1 - "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.13 - "@storybook/core-client": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/theming": 6.5.13 - "@storybook/ui": 6.5.13 - "@types/node": ^14.0.10 || ^16.0.0 - babel-loader: ^8.0.0 - case-sensitive-paths-webpack-plugin: ^2.3.0 - chalk: ^4.1.0 - core-js: ^3.8.2 - css-loader: ^5.0.1 - express: ^4.17.1 - find-up: ^5.0.0 - fs-extra: ^9.0.1 - html-webpack-plugin: ^5.0.0 - node-fetch: ^2.6.7 - process: ^0.11.10 - read-pkg-up: ^7.0.1 - regenerator-runtime: ^0.13.7 - resolve-from: ^5.0.0 - style-loader: ^2.0.0 - telejson: ^6.0.8 - terser-webpack-plugin: ^5.0.3 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - webpack: ^5.9.0 - webpack-dev-middleware: ^4.1.0 - webpack-virtual-modules: ^0.4.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 95e720d00e8869f8ec5e8a36f50e5da6aaecdc9a1ea306ae3d7b50f2f07b60a9eb5cb3787e88262c664c958b1f56b7f34e9bf027eb627a96d127b5771ff6a137 - languageName: node - linkType: hard - -"@storybook/mdx1-csf@npm:^0.0.1": - version: 0.0.1 - resolution: "@storybook/mdx1-csf@npm:0.0.1" - dependencies: - "@babel/generator": ^7.12.11 - "@babel/parser": ^7.12.11 - "@babel/preset-env": ^7.12.11 - "@babel/types": ^7.12.11 - "@mdx-js/mdx": ^1.6.22 - "@types/lodash": ^4.14.167 - js-string-escape: ^1.0.1 - loader-utils: ^2.0.0 - lodash: ^4.17.21 - prettier: ">=2.2.1 <=2.3.0" - ts-dedent: ^2.0.0 - checksum: 34f952f4d00d4fbf680aadea53ca0d9b02b10c94ea492a47a6df916474ea1e36d08eece70ffaba760a4cdf6f634a8684360dc49355cf8a1461050b8a470d2666 - languageName: node - linkType: hard - -"@storybook/mdx1-csf@npm:^0.0.4": - version: 0.0.4 - resolution: "@storybook/mdx1-csf@npm:0.0.4" - dependencies: - "@babel/generator": ^7.12.11 - "@babel/parser": ^7.12.11 - "@babel/preset-env": ^7.12.11 - "@babel/types": ^7.12.11 - "@mdx-js/mdx": ^1.6.22 - "@mdx-js/react": ^1.6.22 - "@types/lodash": ^4.14.167 - js-string-escape: ^1.0.1 - loader-utils: ^2.0.0 - lodash: ^4.17.21 - prettier: ">=2.2.1 <=2.3.0" - ts-dedent: ^2.0.0 - checksum: 834dcf6eb063c559f2768ec3ce669cff651096f7658739c95de1fff190e86fe0253c1e91858f2405b2c6fc218561102837b8d7e951a2f5997ed86f0a72ee5b9e - languageName: node - linkType: hard - -"@storybook/node-logger@npm:6.5.10, @storybook/node-logger@npm:^6.4.3": - version: 6.5.10 - resolution: "@storybook/node-logger@npm:6.5.10" - dependencies: - "@types/npmlog": ^4.1.2 - chalk: ^4.1.0 - core-js: ^3.8.2 - npmlog: ^5.0.1 - pretty-hrtime: ^1.0.3 - checksum: 684eddeadccb632dd0aa7d2bca62a374f71a15f07037788ee82f4d57e18ce7616304e5d8084b96dff742fe2b810843c44f26d53d4ff8f7d0706cdd81d0060fee - languageName: node - linkType: hard - -"@storybook/node-logger@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/node-logger@npm:6.5.13" - dependencies: - "@types/npmlog": ^4.1.2 - chalk: ^4.1.0 - core-js: ^3.8.2 - npmlog: ^5.0.1 - pretty-hrtime: ^1.0.3 - checksum: bcd1d98822687580e39f27003e16c73e3c775cdfe6e9f8fd8fbe9f4626a82f3f63fe281f9c894f3917faa52202ccb8217916978032d27ba6dbfa9720064e7739 - languageName: node - linkType: hard - -"@storybook/postinstall@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/postinstall@npm:6.5.13" - dependencies: - core-js: ^3.8.2 - checksum: 87e57e55c7973ea5794b439484d6a99c078b816c0a865c989ae31979000abda6fcfe670671e999a36630b52a91d54e6c0d174b41410cf876e20db976e5a23f56 - languageName: node - linkType: hard - -"@storybook/preview-web@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/preview-web@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/channel-postmessage": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.13 - ansi-to-html: ^0.6.11 - core-js: ^3.8.2 - global: ^4.4.0 + "@storybook/channels": 7.6.3 + "@storybook/client-logger": 7.6.3 + "@storybook/core-events": 7.6.3 + "@storybook/csf": ^0.1.2 + "@storybook/global": ^5.0.0 + "@storybook/types": 7.6.3 + "@types/qs": ^6.9.5 + dequal: ^2.0.2 lodash: ^4.17.21 + memoizerific: ^1.11.3 qs: ^6.10.0 - regenerator-runtime: ^0.13.7 synchronous-promise: ^2.0.15 ts-dedent: ^2.0.0 - unfetch: ^4.2.0 util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: d66d29667a936ee80d15de07bdeec0c6cb2a476fdaa59f262297f5c7dc774acb57e4779c2dd77e61f5a6d9974ac3359babe587a2cd9baf6aa7673ec949b3234d + checksum: c4370d98904c30404f1ee554bdd14bd3b4f6c7121532edac105cd2d1cc7d7f28833b4c23779e672f0e9cf1dbce03e8251a7791168c39049058542343281921b3 languageName: node linkType: hard -"@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": - version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 - resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" +"@storybook/preview@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/preview@npm:7.6.3" + checksum: e51787c0b9d3bf48095916081979741cef95acea0ae0c3350596de1eaf7f767ba15e408b36b2d41b0a40feb464905d95d086008ee5c02e689fdf232e1486c406 + languageName: node + linkType: hard + +"@storybook/react-docgen-typescript-plugin@npm:1.0.6--canary.9.0c3f3b7.0": + version: 1.0.6--canary.9.0c3f3b7.0 + resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.6--canary.9.0c3f3b7.0" dependencies: debug: ^4.1.1 endent: ^2.0.1 find-cache-dir: ^3.3.1 flat-cache: ^3.0.4 micromatch: ^4.0.2 - react-docgen-typescript: ^2.1.1 + react-docgen-typescript: ^2.2.2 tslib: ^2.0.0 peerDependencies: - typescript: ">= 3.x" + typescript: ">= 4.x" webpack: ">= 4" - checksum: 91a3015d384e93d9ffb4def904cad51218eb1a9eaf504c758083f2988a97d8bf8748bc280aa629864eb26fd9f7fc05bd087df95383d719e0c914c722016804b9 + checksum: 38c59c1dd7f9cdf5533e5ffe1991034f563f8d33c59e3cd33fa86719c72f5fe922276fde50315dd24f23f225d1ad5f3a261ecf4c70e82522805d09782272faff languageName: node linkType: hard -"@storybook/react@npm:^6.5.13": - version: 6.5.13 - resolution: "@storybook/react@npm:6.5.13" +"@storybook/react-dom-shim@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/react-dom-shim@npm:7.6.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5563f1589357b2075073a492720d21cd453d72f794e8c7532fadc64995acd17dc55fd21381c2f7b75023f76abde85cf2ea362c301143fb592ba0e67e8620be0f + languageName: node + linkType: hard + +"@storybook/react@npm:7.6.3, @storybook/react@npm:^7.6.3": + version: 7.6.3 + resolution: "@storybook/react@npm:7.6.3" dependencies: - "@babel/preset-flow": ^7.12.1 - "@babel/preset-react": ^7.12.10 - "@pmmmwh/react-refresh-webpack-plugin": ^0.5.3 - "@storybook/addons": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core": 6.5.13 - "@storybook/core-common": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/docs-tools": 6.5.13 - "@storybook/node-logger": 6.5.13 - "@storybook/react-docgen-typescript-plugin": 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 - "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.13 + "@storybook/client-logger": 7.6.3 + "@storybook/core-client": 7.6.3 + "@storybook/docs-tools": 7.6.3 + "@storybook/global": ^5.0.0 + "@storybook/preview-api": 7.6.3 + "@storybook/react-dom-shim": 7.6.3 + "@storybook/types": 7.6.3 + "@types/escodegen": ^0.0.6 "@types/estree": ^0.0.51 - "@types/node": ^14.14.20 || ^16.0.0 - "@types/webpack-env": ^1.16.0 + "@types/node": ^18.0.0 acorn: ^7.4.1 acorn-jsx: ^5.3.1 acorn-walk: ^7.2.0 - babel-plugin-add-react-displayname: ^0.0.5 - babel-plugin-react-docgen: ^4.2.1 - core-js: ^3.8.2 - escodegen: ^2.0.0 - fs-extra: ^9.0.1 - global: ^4.4.0 + escodegen: ^2.1.0 html-tags: ^3.1.0 lodash: ^4.17.21 prop-types: ^15.7.2 - react-element-to-jsx-string: ^14.3.4 - react-refresh: ^0.11.0 - read-pkg-up: ^7.0.1 - regenerator-runtime: ^0.13.7 + react-element-to-jsx-string: ^15.0.0 ts-dedent: ^2.0.0 + type-fest: ~2.19 util-deprecate: ^1.0.2 - webpack: ">=4.43.0 <6.0.0" peerDependencies: - "@babel/core": ^7.11.5 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - require-from-string: ^2.0.2 + typescript: "*" peerDependenciesMeta: - "@babel/core": - optional: true - "@storybook/builder-webpack4": - optional: true - "@storybook/builder-webpack5": - optional: true - "@storybook/manager-webpack4": - optional: true - "@storybook/manager-webpack5": - optional: true typescript: optional: true - bin: - build-storybook: bin/build.js - start-storybook: bin/index.js - storybook-server: bin/index.js - checksum: 5a21e4e49a0aba7376dbaef5408e03537cf937d3025e735b45848bce7c9f476a025ca9af4260aa32982a3da668e14a217f66a0ec465ed820369ff2fab49b2ab3 + checksum: cb4a719612a62e22eb1585518c50df42cb2d14b708d3d33c25a5c081b03b1876fd8af2010fdc7b3086eb05f01edde3bac9ae1ea093315b7587ce1174a1575fc9 languageName: node linkType: hard -"@storybook/router@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/router@npm:6.5.10" +"@storybook/router@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/router@npm:7.6.3" dependencies: - "@storybook/client-logger": 6.5.10 - core-js: ^3.8.2 + "@storybook/client-logger": 7.6.3 memoizerific: ^1.11.3 qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 118598867067344607cff7ef6fdef7b7a18a3e08a53f75fc4beaa65013f435ae18d800d25eea52376662bc1d98a2822a143531e701d8cea7130d42dc48e2cce7 + checksum: e1af3895b7945fd62c159caea87c042d41fd0e4e929f9838e9369b08bcf94bafee01222e1af742442a8b1a589abf70613dbb93a393982dde892c1106e0164e04 languageName: node linkType: hard -"@storybook/router@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/router@npm:6.5.13" +"@storybook/telemetry@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/telemetry@npm:7.6.3" dependencies: - "@storybook/client-logger": 6.5.13 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: ca144b2f6e3a46d5ac9d449b068d0905e9c72939c8574f095d8d7f7307b172a0c5c13f56ff08d5d5bff540292d9ed13eeecc3a4e600e282ea31e70ed763b735a - languageName: node - linkType: hard - -"@storybook/router@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/router@npm:6.5.9" - dependencies: - "@storybook/client-logger": 6.5.9 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10acf6d67fa245ca10d8e377d593405ab1505d22b3bb2e7ce7dc45bc5be2074d7bb89f9266b7550b84063c907e2188742b355fc8af05f7cf4554a0770915d12e - languageName: node - linkType: hard - -"@storybook/semver@npm:^7.3.2": - version: 7.3.2 - resolution: "@storybook/semver@npm:7.3.2" - dependencies: - core-js: ^3.6.5 - find-up: ^4.1.0 - bin: - semver: bin/semver.js - checksum: c98225817af5539654ef547e33e4496edccc04a88b6091d4a5601f81b71743109074dc71cc444813f43c112273c9d54d5f99416e9ad08ee89b4913318e6aea90 - languageName: node - linkType: hard - -"@storybook/source-loader@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/source-loader@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - core-js: ^3.8.2 - estraverse: ^5.2.0 - global: ^4.4.0 - loader-utils: ^2.0.0 - lodash: ^4.17.21 - prettier: ">=2.2.1 <=2.3.0" - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 93da14a367a954f664d233f64bd9e7d983475e489b8fbe5c90b723ff793279ab899d86f967eb6882f7997296f4286f6f5c82c17ca83e9294c91f8e4aa0ec0a1d - languageName: node - linkType: hard - -"@storybook/source-loader@npm:^6.4.3": - version: 6.5.10 - resolution: "@storybook/source-loader@npm:6.5.10" - dependencies: - "@storybook/addons": 6.5.10 - "@storybook/client-logger": 6.5.10 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - core-js: ^3.8.2 - estraverse: ^5.2.0 - global: ^4.4.0 - loader-utils: ^2.0.0 - lodash: ^4.17.21 - prettier: ">=2.2.1 <=2.3.0" - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 77d7a0255cace96fc9953518fe54162ce4b2167b53eb744f498cf2098ba4af8074d75f572940621675303043b69e2281e8a5479ce2d331d47aa86c189cdd53bb - languageName: node - linkType: hard - -"@storybook/store@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/store@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/csf": 0.0.2--canary.4566f4d.1 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - slash: ^3.0.0 - stable: ^0.1.8 - synchronous-promise: ^2.0.15 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 69f55927bd3569ec9d87f4351879fd07654d51524a0f9da05c64c7f7f3b50e33024f1554fa59668997e47189e9838b85b691de422ab539bd71126b56922d9381 - languageName: node - linkType: hard - -"@storybook/telemetry@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/telemetry@npm:6.5.13" - dependencies: - "@storybook/client-logger": 6.5.13 - "@storybook/core-common": 6.5.13 + "@storybook/client-logger": 7.6.3 + "@storybook/core-common": 7.6.3 + "@storybook/csf-tools": 7.6.3 chalk: ^4.1.0 - core-js: ^3.8.2 detect-package-manager: ^2.0.1 fetch-retry: ^5.0.2 - fs-extra: ^9.0.1 - global: ^4.4.0 - isomorphic-unfetch: ^3.1.0 - nanoid: ^3.3.1 + fs-extra: ^11.1.0 read-pkg-up: ^7.0.1 - regenerator-runtime: ^0.13.7 - checksum: 94ad6fb58b09c8073600ad95b2a48f476524ea7bc6155aee8e9682966a99ac875a9923ee6512252238e8c56eeef725a712e94ba87e1060d7dca9ab196a9051a6 + checksum: 8d9b345c29c594e9cfdf9370953cbba44a7fd86bd94e7850c2cc94510ee57fb0a468ded97c1526db528e865072b74c806e4aea9a1999a6da2cd0d6242dc39982 languageName: node linkType: hard -"@storybook/testing-library@npm:^0.0.13": - version: 0.0.13 - resolution: "@storybook/testing-library@npm:0.0.13" +"@storybook/testing-library@npm:^0.2.2": + version: 0.2.2 + resolution: "@storybook/testing-library@npm:0.2.2" dependencies: - "@storybook/client-logger": ^6.4.0 - "@storybook/instrumenter": ^6.4.0 - "@testing-library/dom": ^8.3.0 - "@testing-library/user-event": ^13.2.1 + "@testing-library/dom": ^9.0.0 + "@testing-library/user-event": ^14.4.0 ts-dedent: ^2.2.0 - checksum: 759361ad3fbc89bdfddfa6d5a15eef06ed6fa9110bfa40c08fcf2497e7acd85e8d5c73c26ea4a46934168b21db294256befb55755fee4292d3d277c576284a1c + checksum: 8ccdc1fbbb3472264c56b0aaf2f1c5d273f1ae9b230a53adf9cf82bf82c1a555550894f0e8869c206fa07b1fe8423da4d56590377756c58de3ec560b35a96c46 languageName: node linkType: hard -"@storybook/theming@npm:6.5.10": - version: 6.5.10 - resolution: "@storybook/theming@npm:6.5.10" +"@storybook/theming@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/theming@npm:7.6.3" dependencies: - "@storybook/client-logger": 6.5.10 - core-js: ^3.8.2 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.0 + "@storybook/client-logger": 7.6.3 + "@storybook/global": ^5.0.0 memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 2082d7847785a307a18eb605282468d844af01f57752916766a60047b5543cf6f0c6664b9c7a693809b4fdc121415989c2170833d3de7ca8b07fa056741787d0 + checksum: deeae48afe9fdbbab2c6b11a0e7ea725414c6a3da3638a6cc2475d9e1cc800a93a255a732b2293966975f998fc22e76f4b502f740d618e7d6ed9aaaec3c60bc2 languageName: node linkType: hard -"@storybook/theming@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/theming@npm:6.5.13" +"@storybook/types@npm:7.6.3": + version: 7.6.3 + resolution: "@storybook/types@npm:7.6.3" dependencies: - "@storybook/client-logger": 6.5.13 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: f7a59c7d81b87f3fbf65c5eb72f5db5a5c1707236c92350d46bc7e1dcf848d57522ca5dcdd4fe19336d4611bd20727e0d900c4b591d2e8e1dd8a754cb9c56aa3 - languageName: node - linkType: hard - -"@storybook/theming@npm:6.5.9": - version: 6.5.9 - resolution: "@storybook/theming@npm:6.5.9" - dependencies: - "@storybook/client-logger": 6.5.9 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 0c0d034864bcf7289778aa549dd9d830c75b90e416cbd2ee8bc9be946f1699141a7b695916aa134c38d156edcfac3a1378e3490ac02b470b89d168625618d073 - languageName: node - linkType: hard - -"@storybook/ui@npm:6.5.13": - version: 6.5.13 - resolution: "@storybook/ui@npm:6.5.13" - dependencies: - "@storybook/addons": 6.5.13 - "@storybook/api": 6.5.13 - "@storybook/channels": 6.5.13 - "@storybook/client-logger": 6.5.13 - "@storybook/components": 6.5.13 - "@storybook/core-events": 6.5.13 - "@storybook/router": 6.5.13 - "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.13 - core-js: ^3.8.2 - memoizerific: ^1.11.3 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - resolve-from: ^5.0.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: d2866987f51d945246776d42628bc2b79e701f1e59fd511bb1e590c83c4b9d9adee5c7e1a11ecb2ef12b9192613e2d576694bbc9aeb1df5545567f2aa44c0145 + "@storybook/channels": 7.6.3 + "@types/babel__core": ^7.0.0 + "@types/express": ^4.7.0 + file-system-cache: 2.3.0 + checksum: fabbdc48937fa8b352389e87d12c156b07a1803481467ff4038ca113dfb0d928635c07ec6cde5a0fd06e347f7644540186d7149e13aa5423ad91b22e4ede33b9 languageName: node linkType: hard @@ -12192,6 +11916,119 @@ __metadata: languageName: node linkType: hard +"@swc/core-darwin-arm64@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-darwin-arm64@npm:1.3.100" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-darwin-x64@npm:1.3.100" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.100" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.100" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.100" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-linux-x64-musl@npm:1.3.100" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.100" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.100" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.3.100": + version: 1.3.100 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.100" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.3.82": + version: 1.3.100 + resolution: "@swc/core@npm:1.3.100" + dependencies: + "@swc/core-darwin-arm64": 1.3.100 + "@swc/core-darwin-x64": 1.3.100 + "@swc/core-linux-arm64-gnu": 1.3.100 + "@swc/core-linux-arm64-musl": 1.3.100 + "@swc/core-linux-x64-gnu": 1.3.100 + "@swc/core-linux-x64-musl": 1.3.100 + "@swc/core-win32-arm64-msvc": 1.3.100 + "@swc/core-win32-ia32-msvc": 1.3.100 + "@swc/core-win32-x64-msvc": 1.3.100 + "@swc/counter": ^0.1.1 + "@swc/types": ^0.1.5 + peerDependencies: + "@swc/helpers": ^0.5.0 + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: b68553db388c36b1859f0e8b5231a608277e17ae7fd7a928ea8b448432532176914f84675426d40aa9710617b2e7fb626aaa8358b6b2506b3c43f82db725c41e + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.1": + version: 0.1.2 + resolution: "@swc/counter@npm:0.1.2" + checksum: 8427c594f1f0cf44b83885e9c8fe1e370c9db44ae96e07a37c117a6260ee97797d0709483efbcc244e77bac578690215f45b23254c4cd8a70fb25ddbb50bf33e + languageName: node + linkType: hard + "@swc/helpers@npm:0.5.2": version: 0.5.2 resolution: "@swc/helpers@npm:0.5.2" @@ -12201,6 +12038,13 @@ __metadata: languageName: node linkType: hard +"@swc/types@npm:^0.1.5": + version: 0.1.5 + resolution: "@swc/types@npm:0.1.5" + checksum: 6aee11f62d3d805a64848e0bd5f0e0e615f958e327a9e1260056c368d7d28764d89e38bd8005a536c9bf18afbcd303edd84099d60df34a2975d62540f61df13b + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -12317,22 +12161,6 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^8.3.0": - version: 8.19.0 - resolution: "@testing-library/dom@npm:8.19.0" - dependencies: - "@babel/code-frame": ^7.10.4 - "@babel/runtime": ^7.12.5 - "@types/aria-query": ^4.2.0 - aria-query: ^5.0.0 - chalk: ^4.1.0 - dom-accessibility-api: ^0.5.9 - lz-string: ^1.4.4 - pretty-format: ^27.0.2 - checksum: 6bb93fef96703b6c47cf1b7cc8f71d402a9576084a94ba4e9926f51bd7bb1287fbb4f6942d82bd03fc6f3d998ae97e60f6aea4618f3a1ce6139597d2a4ecb7b9 - languageName: node - linkType: hard - "@testing-library/dom@npm:^8.5.0": version: 8.17.1 resolution: "@testing-library/dom@npm:8.17.1" @@ -12349,6 +12177,22 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:^9.0.0": + version: 9.3.3 + resolution: "@testing-library/dom@npm:9.3.3" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/runtime": ^7.12.5 + "@types/aria-query": ^5.0.1 + aria-query: 5.1.3 + chalk: ^4.1.0 + dom-accessibility-api: ^0.5.9 + lz-string: ^1.5.0 + pretty-format: ^27.0.2 + checksum: 34e0a564da7beb92aa9cc44a9080221e2412b1a132eb37be3d513fe6c58027674868deb9f86195756d98d15ba969a30fe00632a4e26e25df2a5a4f6ac0686e37 + languageName: node + linkType: hard + "@testing-library/jest-dom@npm:^5.16.5": version: 5.17.0 resolution: "@testing-library/jest-dom@npm:5.17.0" @@ -12402,14 +12246,12 @@ __metadata: languageName: node linkType: hard -"@testing-library/user-event@npm:^13.2.1": - version: 13.5.0 - resolution: "@testing-library/user-event@npm:13.5.0" - dependencies: - "@babel/runtime": ^7.12.5 +"@testing-library/user-event@npm:^14.4.0": + version: 14.5.1 + resolution: "@testing-library/user-event@npm:14.5.1" peerDependencies: "@testing-library/dom": ">=7.21.4" - checksum: 16319de685fbb7008f1ba667928f458b2d08196918002daca56996de80ef35e6d9de26e9e1ece7d00a004692b95a597cf9142fff0dc53f2f51606a776584f549 + checksum: 3e6bc9fd53dfe2f3648190193ed2fd4bca2a1bfb47f68810df3b33f05412526e5fd5c4ef9dc5375635e0f4cdf1859916867b597eed22bda1321e04242ea6c519 languageName: node linkType: hard @@ -12463,11 +12305,11 @@ __metadata: linkType: hard "@trpc/client@npm:^10.0.0": - version: 10.38.4 - resolution: "@trpc/client@npm:10.38.4" + version: 10.44.1 + resolution: "@trpc/client@npm:10.44.1" peerDependencies: - "@trpc/server": 10.38.4 - checksum: 017f61d8cea5362d565ba5c935fbf2dcf32e3478bafc52583053685a454a16da6cc8f577454ca3a2fe032eabc472848ba87c3be144c71f19c419a1a6b19a7114 + "@trpc/server": 10.44.1 + checksum: c3277e9f6e1ff650e9da82b4f6ec1718bca86ad1d17f083a200e29ad635da7d988c9223c0ccd0c575ed1ab3cb59b47e1d83c659e3ae8f6537d3c54ea34010f2a languageName: node linkType: hard @@ -12511,9 +12353,9 @@ __metadata: linkType: hard "@trpc/server@npm:^10.0.0": - version: 10.38.4 - resolution: "@trpc/server@npm:10.38.4" - checksum: 9cb1ad5395d5ab059a76209f61d6922d1dee3de5fe412417f3fe28c46e29914a20e0342ec3d9798f24a6b37ff7771263bb797be2962f09f1ee6a92f118417c82 + version: 10.44.1 + resolution: "@trpc/server@npm:10.44.1" + checksum: e52675281f62829a976d3568b0409ed035914fcef9320a2f8b4eff0b8eaaffa55c7ecfdf2f9e55f90cab9630fc92b8426c4e55616b7bd88c7ea4e72e0b37dcc9 languageName: node linkType: hard @@ -12610,6 +12452,13 @@ __metadata: languageName: node linkType: hard +"@types/aria-query@npm:^5.0.1": + version: 5.0.4 + resolution: "@types/aria-query@npm:5.0.4" + checksum: ad8b87e4ad64255db5f0a73bc2b4da9b146c38a3a8ab4d9306154334e0fc67ae64e76bfa298eebd1e71830591fb15987e5de7111bdb36a2221bdc379e3415fb0 + languageName: node + linkType: hard + "@types/async@npm:^3.2.15": version: 3.2.15 resolution: "@types/async@npm:3.2.15" @@ -12617,6 +12466,47 @@ __metadata: languageName: node linkType: hard +"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.18.0": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: a3226f7930b635ee7a5e72c8d51a357e799d19cbf9d445710fa39ab13804f79ab1a54b72ea7d8e504659c7dfc50675db974b526142c754398d7413aa4bc30845 + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.7 + resolution: "@types/babel__generator@npm:7.6.7" + dependencies: + "@babel/types": ^7.0.0 + checksum: 03e96ea327a5238f00c38394a05cc01619b9f5f3ea57371419a1c25cf21676a6d327daf802435819f8cb3b8fa10e938a94bcbaf79a38c132068c813a1807ff93 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": ^7.1.0 + "@babel/types": ^7.0.0 + checksum: d7a02d2a9b67e822694d8e6a7ddb8f2b71a1d6962dfd266554d2513eefbb205b33ca71a0d163b1caea3981ccf849211f9964d8bd0727124d18ace45aa6c9ae29 + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.18.0": + version: 7.20.4 + resolution: "@types/babel__traverse@npm:7.20.4" + dependencies: + "@babel/types": ^7.20.7 + checksum: f044ba80e00d07e46ee917c44f96cfc268fcf6d3871f7dfb8db8d3c6dab1508302f3e6bc508352a4a3ae627d2522e3fc500fa55907e0410a08e2e0902a8f3576 + languageName: node + linkType: hard + "@types/bcryptjs@npm:^2.4.2": version: 2.4.2 resolution: "@types/bcryptjs@npm:2.4.2" @@ -12705,6 +12595,15 @@ __metadata: languageName: node linkType: hard +"@types/cross-spawn@npm:^6.0.2": + version: 6.0.6 + resolution: "@types/cross-spawn@npm:6.0.6" + dependencies: + "@types/node": "*" + checksum: b4172927cd1387cf037c3ade785ef46c87537b7bc2803d7f6663b4904d0c5d6f726415d1adb2fee4fecb21746738f11336076449265d46be4ce110cc3a8c8436 + languageName: node + linkType: hard + "@types/d3-array@npm:^3.0.3": version: 3.0.4 resolution: "@types/d3-array@npm:3.0.4" @@ -12775,9 +12674,9 @@ __metadata: linkType: hard "@types/debounce@npm:^1.2.1": - version: 1.2.2 - resolution: "@types/debounce@npm:1.2.2" - checksum: 990d856e5b8f721031e13f8a49865af0b3ffde986a469dea7dfc2bb89bbca00d2b8928b22f99ca78544d98266003804d6bd8b578464c792a6bd8f96456030f66 + version: 1.2.4 + resolution: "@types/debounce@npm:1.2.4" + checksum: decef3eee65d681556d50f7fac346f1b33134f6b21f806d41326f9dfb362fa66b0282ff0640ae6791b690694c9dc3dad4e146e909e707e6f96650f3aa325b9da languageName: node linkType: hard @@ -12799,7 +12698,7 @@ __metadata: languageName: node linkType: hard -"@types/debug@npm:4.1.9, @types/debug@npm:^4.0.0": +"@types/debug@npm:4.1.9": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" dependencies: @@ -12808,6 +12707,22 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.0.0": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "*" + checksum: 47876a852de8240bfdaf7481357af2b88cb660d30c72e73789abf00c499d6bc7cd5e52f41c915d1b9cd8ec9fef5b05688d7b7aef17f7f272c2d04679508d1053 + languageName: node + linkType: hard + +"@types/detect-port@npm:^1.3.0": + version: 1.3.5 + resolution: "@types/detect-port@npm:1.3.5" + checksum: 923cf04c6a05af59090743baeb9948f1938ceb98c1f7ea93db7ac310210426b385aa00005d23039ebb8019a9d13e141f5246e9c733b290885018d722a4787921 + languageName: node + linkType: hard + "@types/detect-port@npm:^1.3.2": version: 1.3.2 resolution: "@types/detect-port@npm:1.3.2" @@ -12815,6 +12730,41 @@ __metadata: languageName: node linkType: hard +"@types/doctrine@npm:^0.0.3": + version: 0.0.3 + resolution: "@types/doctrine@npm:0.0.3" + checksum: 7ca9c8ff4d2da437785151c9eef0dd80b8fa12e0ff0fcb988458a78de4b6f0fc92727ba5bbee446e1df615a91f03053c5783b30b7c21ab6ceab6a42557e93e50 + languageName: node + linkType: hard + +"@types/doctrine@npm:^0.0.9": + version: 0.0.9 + resolution: "@types/doctrine@npm:0.0.9" + checksum: 3909eaca42e7386b2ab866f082b78da3e00718d2fa323597e254feb0556c678b41f2c490729067433630083ac9c806ec6ae1e146754f7f8ba7d3e43ed68d6500 + languageName: node + linkType: hard + +"@types/ejs@npm:^3.1.1": + version: 3.1.5 + resolution: "@types/ejs@npm:3.1.5" + checksum: e142266283051f27a7f79329871b311687dede19ae20268d882e4de218c65e1311d28a300b85579ca67157a8d601b7234daa50c2f99b252b121d27b4e5b21468 + languageName: node + linkType: hard + +"@types/emscripten@npm:^1.39.6": + version: 1.39.10 + resolution: "@types/emscripten@npm:1.39.10" + checksum: 1721da76593f9194e0b7c90a581e2d31c23bd4eb28f93030cd1dc58216cdf1e692c045274f2eedaed29c652c25c9a4dff2e503b11bd1258d07095c009a1956b1 + languageName: node + linkType: hard + +"@types/escodegen@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/escodegen@npm:0.0.6" + checksum: 7b25aeedd48dbef68345224082c6bc774845cbfc1d9b2ce91a477130fe7ccabf33da126c1d6d55e5dfd838db429a7c80890628a167e5aa55b6a4620974da38d3 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.3": version: 3.7.4 resolution: "@types/eslint-scope@npm:3.7.4" @@ -12835,6 +12785,15 @@ __metadata: languageName: node linkType: hard +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/estree-jsx@npm:1.0.3" + dependencies: + "@types/estree": "*" + checksum: 6887a134308b6db4a33a147b56c9d0a47c17ea7e810bdd7c498c306a0fd00bcf2619cb0f57f74009d03dda974b3cd7e414767f85332b1d1b2be30a3ef9e1cca9 + languageName: node + linkType: hard + "@types/estree@npm:*, @types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" @@ -12877,6 +12836,18 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.17.41 + resolution: "@types/express-serve-static-core@npm:4.17.41" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: 12750f6511dd870bbaccfb8208ad1e79361cf197b147f62a3bedc19ec642f3a0f9926ace96705f4bc88ec2ae56f61f7ca8c2438e6b22f5540842b5569c28a121 + languageName: node + linkType: hard + "@types/express-unless@npm:*": version: 0.5.3 resolution: "@types/express-unless@npm:0.5.3" @@ -12898,6 +12869,25 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.7.0": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: fb238298630370a7392c7abdc80f495ae6c716723e114705d7e3fb67e3850b3859bbfd29391463a3fb8c0b32051847935933d99e719c0478710f8098ee7091c5 + languageName: node + linkType: hard + +"@types/find-cache-dir@npm:^3.2.1": + version: 3.2.1 + resolution: "@types/find-cache-dir@npm:3.2.1" + checksum: bf5c4e96da40247cd9e6327f54dfccda961a0fb2d70e3c71bd05def94de4c2e6fb310fe8ecb0f04ecf5dbc52214e184b55a2337b0f87250d4ae1e2e7d58321e4 + languageName: node + linkType: hard + "@types/glidejs__glide@npm:^3.4.2": version: 3.4.2 resolution: "@types/glidejs__glide@npm:3.4.2" @@ -12905,22 +12895,12 @@ __metadata: languageName: node linkType: hard -"@types/glob@npm:*, @types/glob@npm:^7.1.1, @types/glob@npm:^7.1.3": - version: 7.2.0 - resolution: "@types/glob@npm:7.2.0" - dependencies: - "@types/minimatch": "*" - "@types/node": "*" - checksum: 6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19 - languageName: node - linkType: hard - -"@types/graceful-fs@npm:^4.1.2": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" dependencies: "@types/node": "*" - checksum: d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a + checksum: 79d746a8f053954bba36bd3d94a90c78de995d126289d656fb3271dd9f1229d33f678da04d10bce6be440494a5a73438e2e363e92802d16b8315b051036c5256 languageName: node linkType: hard @@ -12940,6 +12920,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/hast@npm:3.0.3" + dependencies: + "@types/unist": "*" + checksum: ca204207550fd6848ee20b5ba2018fd54f515d59a8b80375cdbe392ba2b4b130dac25fdfbaf9f2a70d2aec9d074a34dc14d4d59d31fa3ede80ef9850afad5d3c + languageName: node + linkType: hard + "@types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1": version: 3.3.1 resolution: "@types/hoist-non-react-statics@npm:3.3.1" @@ -12950,13 +12939,6 @@ __metadata: languageName: node linkType: hard -"@types/html-minifier-terser@npm:^5.0.0": - version: 5.1.2 - resolution: "@types/html-minifier-terser@npm:5.1.2" - checksum: 4bca779c44d2aebe4cc4036c5db370abe7466249038e9c5996cb3c192debeff1c75b7a2ab78e5fd2a014ad24ebf0f357f9a174a4298540dc1e1317d43aa69cfa - languageName: node - linkType: hard - "@types/html-minifier-terser@npm:^6.0.0": version: 6.1.0 resolution: "@types/html-minifier-terser@npm:6.1.0" @@ -12965,9 +12947,9 @@ __metadata: linkType: hard "@types/http-cache-semantics@npm:*": - version: 4.0.1 - resolution: "@types/http-cache-semantics@npm:4.0.1" - checksum: 1048aacf627829f0d5f00184e16548205cd9f964bf0841c29b36bc504509230c40bc57c39778703a1c965a6f5b416ae2cbf4c1d4589c889d2838dd9dbfccf6e9 + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 7f4dd832e618bc1e271be49717d7b4066d77c2d4eed5b81198eb987e532bb3e1c7e02f45d77918185bad936f884b700c10cebe06305f50400f382ab75055f9e8 languageName: node linkType: hard @@ -12980,13 +12962,6 @@ __metadata: languageName: node linkType: hard -"@types/is-function@npm:^1.0.0": - version: 1.0.1 - resolution: "@types/is-function@npm:1.0.1" - checksum: dfbb591936dfebd4686b109603bc3e2d23a17087d6ec913fb35cd6b5a4ef908ed68ab93cb27d508f1546d312edf03e663cb6738d3b67d420c68da961ac2b3d1f - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -13030,9 +13005,9 @@ __metadata: linkType: hard "@types/js-yaml@npm:^4.0.0": - version: 4.0.6 - resolution: "@types/js-yaml@npm:4.0.6" - checksum: d4439ec2cc830d355c2a1d7508f49888931b2cfe59d786d27621edd530bf06314b1dceeaa759e21f9b035a0ff623a0ca306752ebb73df9485c02abe045bd962c + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: e5e5e49b5789a29fdb1f7d204f82de11cb9e8f6cb24ab064c616da5d6e1b3ccfbf95aa5d1498a9fbd3b9e745564e69b4a20b6c530b5a8bbb2d4eb830cda9bc69 languageName: node linkType: hard @@ -13056,13 +13031,6 @@ __metadata: languageName: node linkType: hard -"@types/json-buffer@npm:~3.0.0": - version: 3.0.0 - resolution: "@types/json-buffer@npm:3.0.0" - checksum: 6b0a371dd603f0eec9d00874574bae195382570e832560dadf2193ee0d1062b8e0694bbae9798bc758632361c227b1e3b19e3bd914043b498640470a2da38b77 - languageName: node - linkType: hard - "@types/json-logic-js@npm:^1.2.1": version: 1.2.1 resolution: "@types/json-logic-js@npm:1.2.1" @@ -13070,7 +13038,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d @@ -13078,9 +13046,9 @@ __metadata: linkType: hard "@types/json-stable-stringify@npm:^1.0.32": - version: 1.0.34 - resolution: "@types/json-stable-stringify@npm:1.0.34" - checksum: 45767ecef0f6aae5680c3be6488d5c493f16046e34f182d7e6a2c69a667aab035799752c6f03017c883b134ad3f80e3f78d7e7da81a9c1f3d01676126baf5d0e + version: 1.0.36 + resolution: "@types/json-stable-stringify@npm:1.0.36" + checksum: 765b07589e11a3896c3d06bb9e3a9be681e7edd95adf27370df0647a91bd2bfcfaf0e091fd4a13729343b388973f73f7e789d6cc62ab988240518a2d27c4a4e2 languageName: node linkType: hard @@ -13158,11 +13126,20 @@ __metadata: linkType: hard "@types/mdast@npm:^3.0.0": - version: 3.0.10 - resolution: "@types/mdast@npm:3.0.10" + version: 3.0.15 + resolution: "@types/mdast@npm:3.0.15" + dependencies: + "@types/unist": ^2 + checksum: af85042a4e3af3f879bde4059fa9e76c71cb552dffc896cdcc6cf9dc1fd38e37035c2dbd6245cfa6535b433f1f0478f5549696234ccace47a64055a10c656530 + languageName: node + linkType: hard + +"@types/mdast@npm:^4.0.0": + version: 4.0.3 + resolution: "@types/mdast@npm:4.0.3" dependencies: "@types/unist": "*" - checksum: 3f587bfc0a9a2403ecadc220e61031b01734fedaf82e27eb4d5ba039c0eb54db8c85681ccc070ab4df3f7ec711b736a82b990e69caa14c74bf7ac0ccf2ac7313 + checksum: 345c5a22fccf05f35239ea6313ee4aaf6ebed5927c03ac79744abccb69b9ba5e692f9b771e36a012b79e17429082cada30f579e9c43b8a54e0ffb365431498b6 languageName: node linkType: hard @@ -13174,9 +13151,16 @@ __metadata: linkType: hard "@types/mdurl@npm:^1.0.0": - version: 1.0.3 - resolution: "@types/mdurl@npm:1.0.3" - checksum: 5bbed4f0eb9f60040fa26be77aa2158ca468b6423876cec0d2043e7f8298e83b8e5b95fb66056327b02d747c4d376aed16c11ff3fdc4cb3dca327a6931a71f18 + version: 1.0.5 + resolution: "@types/mdurl@npm:1.0.5" + checksum: e8e872e8da8f517a9c748b06cec61c947cb73fd3069e8aeb0926670ec5dfac5d30549b3d0f1634950401633e812f9b7263f2d5dbe7e98fce12bcb2c659aa4b21 + languageName: node + linkType: hard + +"@types/mdx@npm:^2.0.0": + version: 2.0.10 + resolution: "@types/mdx@npm:2.0.10" + checksum: 3e2fb24b7bfae739a59573344171292b6c31256ad9afddc00232e9de4fbc97b270e1a11d13cb935cba0d9bbb9bc7348793eda82ee752233c5d2289f4b897f719 languageName: node linkType: hard @@ -13196,6 +13180,13 @@ __metadata: languageName: node linkType: hard +"@types/mime-types@npm:^2.1.0": + version: 2.1.4 + resolution: "@types/mime-types@npm:2.1.4" + checksum: f8c521c54ee0c0b9f90a65356a80b1413ed27ccdc94f5c7ebb3de5d63cedb559cd2610ea55b4100805c7349606a920d96e54f2d16b2f0afa6b7cd5253967ccc9 + languageName: node + linkType: hard + "@types/mime-types@npm:^2.1.1": version: 2.1.1 resolution: "@types/mime-types@npm:2.1.1" @@ -13210,13 +13201,6 @@ __metadata: languageName: node linkType: hard -"@types/minimatch@npm:*": - version: 3.0.5 - resolution: "@types/minimatch@npm:3.0.5" - checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 - languageName: node - linkType: hard - "@types/minimist@npm:^1.2.0": version: 1.2.2 resolution: "@types/minimist@npm:1.2.2" @@ -13238,16 +13222,6 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:^2.5.7": - version: 2.6.2 - resolution: "@types/node-fetch@npm:2.6.2" - dependencies: - "@types/node": "*" - form-data: ^3.0.0 - checksum: 6f73b1470000d303d25a6fb92875ea837a216656cb7474f66cdd67bb014aa81a5a11e7ac9c21fe19bee9ecb2ef87c1962bceeaec31386119d1ac86e4c30ad7a6 - languageName: node - linkType: hard - "@types/node-fetch@npm:^2.6.4": version: 2.6.4 resolution: "@types/node-fetch@npm:2.6.4" @@ -13290,13 +13264,6 @@ __metadata: languageName: node linkType: hard -"@types/npmlog@npm:^4.1.2": - version: 4.1.4 - resolution: "@types/npmlog@npm:4.1.4" - checksum: 740f7431ccfc0e127aa8d162fe05c6ce8aa71290be020d179b2824806d19bd2c706c7e0c9a3c9963cefcdf2ceacb1dec6988c394c3694451387759dafe0aa927 - languageName: node - linkType: hard - "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -13304,13 +13271,6 @@ __metadata: languageName: node linkType: hard -"@types/parse5@npm:^5.0.0": - version: 5.0.3 - resolution: "@types/parse5@npm:5.0.3" - checksum: d6b7495cb1850f9f2e9c5e103ede9f2d30a5320669707b105c403868adc9e4bf8d3a7ff314cc23f67826bbbbbc0e6147346ce9062ab429f099dba7a01f463919 - languageName: node - linkType: hard - "@types/parse5@npm:^6.0.0": version: 6.0.3 resolution: "@types/parse5@npm:6.0.3" @@ -13374,9 +13334,9 @@ __metadata: linkType: hard "@types/react-gtm-module@npm:^2.0.1": - version: 2.0.1 - resolution: "@types/react-gtm-module@npm:2.0.1" - checksum: cd57eece80d80453e79b16c977e13d51dc22c19af69a16ac6ea1eda01ab471f4dbe46ce80ce04ef13a0ecd0082372addec4c2e394978d1ce7fe86dbda4dfb922 + version: 2.0.3 + resolution: "@types/react-gtm-module@npm:2.0.3" + checksum: b4b892c9efe93f6f624a42ffe5de37ef7615139191eccc127f7dc2006a70b0540aacb0dc882e3452c344498fdc7f2d4eafc53fe3a33696c1e60fc6852ac650f5 languageName: node linkType: hard @@ -13435,12 +13395,19 @@ __metadata: languageName: node linkType: hard +"@types/resolve@npm:^1.20.2": + version: 1.20.6 + resolution: "@types/resolve@npm:1.20.6" + checksum: dc35f5517606b6687cd971c0281ac58bdee2c50c051b030f04647d3991688be2259c304ee97e5b5d4b9936072c36767eb5933b54611a407d6557972bb6fea4f6 + languageName: node + linkType: hard + "@types/responselike@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/responselike@npm:1.0.0" + version: 1.0.3 + resolution: "@types/responselike@npm:1.0.3" dependencies: "@types/node": "*" - checksum: e99fc7cc6265407987b30deda54c1c24bb1478803faf6037557a774b2f034c5b097ffd65847daa87e82a61a250d919f35c3588654b0fdaa816906650f596d1b0 + checksum: 6ac4b35723429b11b117e813c7acc42c3af8b5554caaf1fc750404c1ae59f9b7376bc69b9e9e194a5a97357a597c2228b7173d317320f0360d617b6425212f58 languageName: node linkType: hard @@ -13481,6 +13448,23 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.3.4": + version: 7.5.6 + resolution: "@types/semver@npm:7.5.6" + checksum: 563a0120ec0efcc326567db2ed920d5d98346f3638b6324ea6b50222b96f02a8add3c51a916b6897b51523aad8ac227d21d3dcf8913559f1bfc6c15b14d23037 + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": ^1 + "@types/node": "*" + checksum: cf4db48251bbb03cd6452b4de6e8e09e2d75390a92fd798eca4a803df06444adc94ed050246c94c7ed46fb97be1f63607f0e1f13c3ce83d71788b3e08640e5e0 + languageName: node + linkType: hard + "@types/serve-static@npm:*": version: 1.13.10 resolution: "@types/serve-static@npm:1.13.10" @@ -13500,13 +13484,6 @@ __metadata: languageName: node linkType: hard -"@types/source-list-map@npm:*": - version: 0.1.2 - resolution: "@types/source-list-map@npm:0.1.2" - checksum: fda8f37537aca9d3ed860d559289ab1dddb6897e642e6f53e909bbd18a7ac3129a8faa2a7d093847c91346cf09c86ef36e350c715406fba1f2271759b449adf6 - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -13523,13 +13500,6 @@ __metadata: languageName: node linkType: hard -"@types/tapable@npm:^1, @types/tapable@npm:^1.0.5": - version: 1.0.8 - resolution: "@types/tapable@npm:1.0.8" - checksum: b4b754dd0822c407b8f29ef6b766490721c276880f9e976d92ee2b3ef915f11a05a2442ae36c8978bcd872ad6bc833b0a2c4d267f2d611590668a366bad50652 - languageName: node - linkType: hard - "@types/testing-library__jest-dom@npm:^5.9.1": version: 5.14.8 resolution: "@types/testing-library__jest-dom@npm:5.14.8" @@ -13567,22 +13537,27 @@ __metadata: languageName: node linkType: hard -"@types/uglify-js@npm:*": - version: 3.16.0 - resolution: "@types/uglify-js@npm:3.16.0" - dependencies: - source-map: ^0.6.1 - checksum: 10b0c4a5f361b1389cdef0b705747586ff7ddd37894e55921b8ed02718bc64ee608f4f5493c571f95ce29a3fe8d3538b7236185974dad93c750d8c05b7bceab4 - languageName: node - linkType: hard - -"@types/unist@npm:*, @types/unist@npm:^2.0.0, @types/unist@npm:^2.0.2, @types/unist@npm:^2.0.3": +"@types/unist@npm:*, @types/unist@npm:^2.0.0": version: 2.0.6 resolution: "@types/unist@npm:2.0.6" checksum: 25cb860ff10dde48b54622d58b23e66214211a61c84c0f15f88d38b61aa1b53d4d46e42b557924a93178c501c166aa37e28d7f6d994aba13d24685326272d5db languageName: node linkType: hard +"@types/unist@npm:^2": + version: 2.0.10 + resolution: "@types/unist@npm:2.0.10" + checksum: e2924e18dedf45f68a5c6ccd6015cd62f1643b1b43baac1854efa21ae9e70505db94290434a23da1137d9e31eb58e54ca175982005698ac37300a1c889f6c4aa + languageName: node + linkType: hard + +"@types/unist@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/unist@npm:3.0.2" + checksum: 3d04d0be69316e5f14599a0d993a208606c12818cf631fd399243d1dc7a9bd8a3917d6066baa6abc290814afbd744621484756803c80cba892c39cd4b4a85616 + languageName: node + linkType: hard + "@types/uuid@npm:8.3.1": version: 8.3.1 resolution: "@types/uuid@npm:8.3.1" @@ -13604,38 +13579,6 @@ __metadata: languageName: node linkType: hard -"@types/webpack-env@npm:^1.16.0": - version: 1.17.0 - resolution: "@types/webpack-env@npm:1.17.0" - checksum: 9ad4d208c4429c9427191d1f4c92e4c43e530384c17a6bc298acb89003fc47fcde1d8372e50acefa3061e9100e57fd9d616e96def875afd06c0c2afe508f298e - languageName: node - linkType: hard - -"@types/webpack-sources@npm:*": - version: 3.2.0 - resolution: "@types/webpack-sources@npm:3.2.0" - dependencies: - "@types/node": "*" - "@types/source-list-map": "*" - source-map: ^0.7.3 - checksum: fa23dcfb99f79cc0ba8e6ca41cb8dedb406f8d7772e8e3d3d9b443bfb36557a1a78f4de2b97905554db98beee1a2ef6f930e188977adde6452392a64dd4b7c2a - languageName: node - linkType: hard - -"@types/webpack@npm:^4.41.26, @types/webpack@npm:^4.41.8": - version: 4.41.32 - resolution: "@types/webpack@npm:4.41.32" - dependencies: - "@types/node": "*" - "@types/tapable": ^1 - "@types/uglify-js": "*" - "@types/webpack-sources": "*" - anymatch: ^3.0.0 - source-map: ^0.6.0 - checksum: e594a1357cbbc2f7c6ca47785c5a11adb5591a774a69afaeab07cd6f6bff6c6aea2030bd37b32bdd19d0ec2336a346db754e8d8d236ba8effeab542716fb32b7 - languageName: node - linkType: hard - "@types/whatwg-url@npm:^8.2.1": version: 8.2.1 resolution: "@types/whatwg-url@npm:8.2.1" @@ -13647,20 +13590,20 @@ __metadata: linkType: hard "@types/ws@npm:^8.0.0": - version: 8.5.6 - resolution: "@types/ws@npm:8.5.6" + version: 8.5.10 + resolution: "@types/ws@npm:8.5.10" dependencies: "@types/node": "*" - checksum: 7addb0c5fa4e7713d5209afb8a90f1852b12c02cb537395adf7a05fbaf21205dc5f7c110fd5ad6f3dbf147112cbff33fb11d8633059cb344f0c14f595b1ea1fb + checksum: 3ec416ea2be24042ebd677932a462cf16d2080393d8d7d0b1b3f5d6eaa4a7387aaf0eefb99193c0bfd29444857cf2e0c3ac89899e130550dc6c14ada8a46d25e languageName: node linkType: hard "@types/xml2js@npm:^0.4.11": - version: 0.4.12 - resolution: "@types/xml2js@npm:0.4.12" + version: 0.4.14 + resolution: "@types/xml2js@npm:0.4.14" dependencies: "@types/node": "*" - checksum: 6197f6d51d70ba7e6d3169ef6d58adacfeddeb2d8cfe4d2bb24eda86223c7dd77c82896c3aa3c636b3b1442cebc6d05959d1e65fa206dac2caeb99aa2c943716 + checksum: df9f106b9953dcdec7ba3304ebc56d6c2f61d49bf556d600bed439f94a1733f73ca0bf2d0f64330b402191622862d9d6058bab9d7e3dcb5b0fe51ebdc4372aac languageName: node linkType: hard @@ -13671,15 +13614,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^15.0.0": - version: 15.0.14 - resolution: "@types/yargs@npm:15.0.14" - dependencies: - "@types/yargs-parser": "*" - checksum: 8e358aeb8f0c3758e59e2b8fcfdee5627ab2fe3d92f50f380503d966c7f33287be3322155516a50d27727fde1ad3878f48f60cd6648439126d4b0bbb1a1153ed - languageName: node - linkType: hard - "@types/yargs@npm:^16.0.0": version: 16.0.4 resolution: "@types/yargs@npm:16.0.4" @@ -13923,6 +13857,13 @@ __metadata: languageName: node linkType: hard +"@ungap/structured-clone@npm:^1.0.0": + version: 1.2.0 + resolution: "@ungap/structured-clone@npm:1.2.0" + checksum: 4f656b7b4672f2ce6e272f2427d8b0824ed11546a601d8d5412b9d7704e83db38a8d9f402ecdf2b9063fc164af842ad0ec4a55819f621ed7e7ea4d1efcc74524 + languageName: node + linkType: hard + "@upstash/core-analytics@npm:^0.0.6": version: 0.0.6 resolution: "@upstash/core-analytics@npm:0.0.6" @@ -13991,23 +13932,6 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-react@npm:^2.0.0": - version: 2.0.1 - resolution: "@vitejs/plugin-react@npm:2.0.1" - dependencies: - "@babel/core": ^7.18.10 - "@babel/plugin-transform-react-jsx": ^7.18.10 - "@babel/plugin-transform-react-jsx-development": ^7.18.6 - "@babel/plugin-transform-react-jsx-self": ^7.18.6 - "@babel/plugin-transform-react-jsx-source": ^7.18.6 - magic-string: ^0.26.2 - react-refresh: ^0.14.0 - peerDependencies: - vite: ^3.0.0 - checksum: 90702768ee34bd7e5021398ab827c682cfe1ebfce0988a532a678b664d80b9ad991d1c24f81045626b811c9aa2aae7d9d0fd563db5c6b7b8fd36c8eecdfc04b9 - languageName: node - linkType: hard - "@vitejs/plugin-react@npm:^2.2.0": version: 2.2.0 resolution: "@vitejs/plugin-react@npm:2.2.0" @@ -14025,388 +13949,207 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:0.34.3": - version: 0.34.3 - resolution: "@vitest/expect@npm:0.34.3" +"@vitest/expect@npm:0.34.6": + version: 0.34.6 + resolution: "@vitest/expect@npm:0.34.6" dependencies: - "@vitest/spy": 0.34.3 - "@vitest/utils": 0.34.3 - chai: ^4.3.7 - checksum: 79afaa37d2efb7bb5503332caf389860b2261f198dbe61557e8061262b628d18658e59eb51d1808ecd35fc59f4bb4d04c0e0f97a27c7db02584ab5b424147b8d + "@vitest/spy": 0.34.6 + "@vitest/utils": 0.34.6 + chai: ^4.3.10 + checksum: 37a526f4af7e73fc56b71ba1139d6d93ff1972315d0e0691de967179298d2ad086e8803d2b28defe0e97a1326d808cd886e4b802d1691d8894cb234e35ed5185 languageName: node linkType: hard -"@vitest/runner@npm:0.34.3": - version: 0.34.3 - resolution: "@vitest/runner@npm:0.34.3" +"@vitest/runner@npm:0.34.6": + version: 0.34.6 + resolution: "@vitest/runner@npm:0.34.6" dependencies: - "@vitest/utils": 0.34.3 + "@vitest/utils": 0.34.6 p-limit: ^4.0.0 pathe: ^1.1.1 - checksum: 945580eaa58e8edbe29a64059bc2a524a9e85117b6d600fdb457cfe84cbfb81bf6d7e98e1227e7cb4e7399992c8fe8d83d0791d0385ff005dc1a4d9da125443b + checksum: 0357f0a11f4e1e170099f9125e379bbe8049a59faa7b34b919b3e5ee8927f30824c2b3ebb814b6a77c75ec35a30bf9adb8ec2b5e051525b4edd0d17be15725cc languageName: node linkType: hard -"@vitest/snapshot@npm:0.34.3": - version: 0.34.3 - resolution: "@vitest/snapshot@npm:0.34.3" +"@vitest/snapshot@npm:0.34.6": + version: 0.34.6 + resolution: "@vitest/snapshot@npm:0.34.6" dependencies: magic-string: ^0.30.1 pathe: ^1.1.1 pretty-format: ^29.5.0 - checksum: 234893e91a1efd4bdbbde047a68de40975e02ead8407724ce8ca4a24edf0fb2d725f8a3efceb104965388407b598faf22407aadfbf4164cc74b3cf1e0e9f4543 + checksum: c2f164b23741cdf10f449575a0f9996cf385675d0f76d2eb696f53b614743811f2fbefdc5eb0fd3f9544ccfbb566d57a5c50a70595167458579d56429b09151f languageName: node linkType: hard -"@vitest/spy@npm:0.34.3": - version: 0.34.3 - resolution: "@vitest/spy@npm:0.34.3" +"@vitest/spy@npm:0.34.6": + version: 0.34.6 + resolution: "@vitest/spy@npm:0.34.6" dependencies: tinyspy: ^2.1.1 - checksum: a2b64b9c357a56ad2f2340ecd225ffe787e61afba4ffb24a6670aad3fc90ea2606ed48daa188ed62b3ef67d55c0259fda6b101143d6c91b58c9ac4298d8be4f9 + checksum: b05e5906f2f489a3234a0380a21cb48635915aa7f28eac92a595e78e9ceefb95340311635e39684b32fff20f9c58fdc33488eeddee39a660cd94c9c6bc2febf7 languageName: node linkType: hard -"@vitest/utils@npm:0.34.3": - version: 0.34.3 - resolution: "@vitest/utils@npm:0.34.3" +"@vitest/utils@npm:0.34.6": + version: 0.34.6 + resolution: "@vitest/utils@npm:0.34.6" dependencies: diff-sequences: ^29.4.3 loupe: ^2.3.6 pretty-format: ^29.5.0 - checksum: aeb8ef7fd98b32cb6c403796880d0aa8f5411bbdb249bb23b3301a70e1b7d1ee025ddb204aae8c1db5756f6ac428c49ebbb8e2ed23ce185c8a659b67413efa85 + checksum: acf716af2bab66037e49bd6d3e8bae40b605b9bff515d4926c46d6f8cc2366decfac5a1756ea55029968e71fba1da1f992764c3a57c9b46eccce3f6db7197bd6 languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/ast@npm:1.11.1" +"@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/ast@npm:1.11.6" dependencies: - "@webassemblyjs/helper-numbers": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - checksum: 1eee1534adebeece635362f8e834ae03e389281972611408d64be7895fc49f48f98fddbbb5339bf8a72cb101bcb066e8bca3ca1bf1ef47dadf89def0395a8d87 + "@webassemblyjs/helper-numbers": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + checksum: 38ef1b526ca47c210f30975b06df2faf1a8170b1636ce239fc5738fc231ce28389dd61ecedd1bacfc03cbe95b16d1af848c805652080cb60982836eb4ed2c6cf languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/ast@npm:1.9.0" +"@webassemblyjs/floating-point-hex-parser@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.6" + checksum: 29b08758841fd8b299c7152eda36b9eb4921e9c584eb4594437b5cd90ed6b920523606eae7316175f89c20628da14326801090167cc7fbffc77af448ac84b7e2 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-api-error@npm:1.11.6" + checksum: e8563df85161096343008f9161adb138a6e8f3c2cc338d6a36011aa55eabb32f2fd138ffe63bc278d009ada001cc41d263dadd1c0be01be6c2ed99076103689f + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" + checksum: b14d0573bf680d22b2522e8a341ec451fddd645d1f9c6bd9012ccb7e587a2973b86ab7b89fe91e1c79939ba96095f503af04369a3b356c8023c13a5893221644 + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-numbers@npm:1.11.6" dependencies: - "@webassemblyjs/helper-module-context": 1.9.0 - "@webassemblyjs/helper-wasm-bytecode": 1.9.0 - "@webassemblyjs/wast-parser": 1.9.0 - checksum: 8a9838dc7fdac358aee8daa75eefa35934ab18dafb594092ff7be79c467ebe9dabb2543e58313c905fd802bdcc3cb8320e4e19af7444e49853a7a24e25138f75 - languageName: node - linkType: hard - -"@webassemblyjs/floating-point-hex-parser@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1" - checksum: b8efc6fa08e4787b7f8e682182d84dfdf8da9d9c77cae5d293818bc4a55c1f419a87fa265ab85252b3e6c1fd323d799efea68d825d341a7c365c64bc14750e97 - languageName: node - linkType: hard - -"@webassemblyjs/floating-point-hex-parser@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.9.0" - checksum: d3aeb19bc30da26f639698daa28e44e0c18d5aa135359ef3c54148e194eec46451a912d0506099d479a71a94bc3eef6ef52d6ec234799528a25a9744789852de - languageName: node - linkType: hard - -"@webassemblyjs/helper-api-error@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-api-error@npm:1.11.1" - checksum: 0792813f0ed4a0e5ee0750e8b5d0c631f08e927f4bdfdd9fe9105dc410c786850b8c61bff7f9f515fdfb149903bec3c976a1310573a4c6866a94d49bc7271959 - languageName: node - linkType: hard - -"@webassemblyjs/helper-api-error@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-api-error@npm:1.9.0" - checksum: 9179d3148639cc202e89a118145b485cf834613260679a99af6ec487bbc15f238566ca713207394b336160a41bf8c1b75cf2e853b3e96f0cc73c1e5c735b3f64 - languageName: node - linkType: hard - -"@webassemblyjs/helper-buffer@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-buffer@npm:1.11.1" - checksum: a337ee44b45590c3a30db5a8b7b68a717526cf967ada9f10253995294dbd70a58b2da2165222e0b9830cd4fc6e4c833bf441a721128d1fe2e9a7ab26b36003ce - languageName: node - linkType: hard - -"@webassemblyjs/helper-buffer@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-buffer@npm:1.9.0" - checksum: dcb85f630f8a2e22b7346ad4dd58c3237a2cad1457699423e8fd19592a0bd3eacbc2639178a1b9a873c3ac217bfc7a23a134ff440a099496b590e82c7a4968d5 - languageName: node - linkType: hard - -"@webassemblyjs/helper-code-frame@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-code-frame@npm:1.9.0" - dependencies: - "@webassemblyjs/wast-printer": 1.9.0 - checksum: a28fa057f7beff0fd14bff716561520f8edb8c9c56c7a5559451e6765acfb70aaeb8af718ea2bd2262e7baeba597545af407e28eb2eff8329235afe8605f20d1 - languageName: node - linkType: hard - -"@webassemblyjs/helper-fsm@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-fsm@npm:1.9.0" - checksum: 374cc510c8f5a7a07d4fe9eb7036cc475a96a670b5d25c31f16757ac8295be8d03a2f29657ff53eaefa9e8315670a48824d430ed910e7c1835788ac79f93124e - languageName: node - linkType: hard - -"@webassemblyjs/helper-module-context@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-module-context@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - checksum: 55e8f89c7ea1beaa78fad88403f3753b8413b0f3b6bb32d898ce95078b3e1d1b48ade0919c00b82fc2e3813c0ab6901e415f7a4d4fa9be50944e2431adde75a5 - languageName: node - linkType: hard - -"@webassemblyjs/helper-numbers@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-numbers@npm:1.11.1" - dependencies: - "@webassemblyjs/floating-point-hex-parser": 1.11.1 - "@webassemblyjs/helper-api-error": 1.11.1 + "@webassemblyjs/floating-point-hex-parser": 1.11.6 + "@webassemblyjs/helper-api-error": 1.11.6 "@xtuc/long": 4.2.2 - checksum: 44d2905dac2f14d1e9b5765cf1063a0fa3d57295c6d8930f6c59a36462afecc6e763e8a110b97b342a0f13376166c5d41aa928e6ced92e2f06b071fd0db59d3a + checksum: f4b562fa219f84368528339e0f8d273ad44e047a07641ffcaaec6f93e5b76fd86490a009aa91a294584e1436d74b0a01fa9fde45e333a4c657b58168b04da424 languageName: node linkType: hard -"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" - checksum: eac400113127832c88f5826bcc3ad1c0db9b3dbd4c51a723cfdb16af6bfcbceb608170fdaac0ab7731a7e18b291be7af68a47fcdb41cfe0260c10857e7413d97 +"@webassemblyjs/helper-wasm-bytecode@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6" + checksum: 3535ef4f1fba38de3475e383b3980f4bbf3de72bbb631c2b6584c7df45be4eccd62c6ff48b5edd3f1bcff275cfd605a37679ec199fc91fd0a7705d7f1e3972dc languageName: node linkType: hard -"@webassemblyjs/helper-wasm-bytecode@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.9.0" - checksum: 280da4df3c556f73a1a02053277f8a4be481de32df4aa21050b015c8f4d27c46af89f0417eb88e486df117e5df4bccffae593f78cb1e79f212d3b3d4f3ed0f04 - languageName: node - linkType: hard - -"@webassemblyjs/helper-wasm-section@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1" +"@webassemblyjs/helper-wasm-section@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - checksum: 617696cfe8ecaf0532763162aaf748eb69096fb27950219bb87686c6b2e66e11cd0614d95d319d0ab1904bc14ebe4e29068b12c3e7c5e020281379741fe4bedf + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + checksum: b2cf751bf4552b5b9999d27bbb7692d0aca75260140195cb58ea6374d7b9c2dc69b61e10b211a0e773f66209c3ddd612137ed66097e3684d7816f854997682e9 languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-buffer": 1.9.0 - "@webassemblyjs/helper-wasm-bytecode": 1.9.0 - "@webassemblyjs/wasm-gen": 1.9.0 - checksum: b8f7bb45d4194074c82210211a5d3e402a5b5fa63ecae26d2c356ae3978af5a530e91192fb260f32f9d561b18e2828b3da2e2f41c59efadb5f3c6d72446807f0 - languageName: node - linkType: hard - -"@webassemblyjs/ieee754@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/ieee754@npm:1.11.1" +"@webassemblyjs/ieee754@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/ieee754@npm:1.11.6" dependencies: "@xtuc/ieee754": ^1.2.0 - checksum: 23a0ac02a50f244471631802798a816524df17e56b1ef929f0c73e3cde70eaf105a24130105c60aff9d64a24ce3b640dad443d6f86e5967f922943a7115022ec + checksum: 13574b8e41f6ca39b700e292d7edf102577db5650fe8add7066a320aa4b7a7c09a5056feccac7a74eb68c10dea9546d4461412af351f13f6b24b5f32379b49de languageName: node linkType: hard -"@webassemblyjs/ieee754@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/ieee754@npm:1.9.0" - dependencies: - "@xtuc/ieee754": ^1.2.0 - checksum: 7fe4a217ba0f7051e2cfef92919d4a64fac1a63c65411763779bd50907820f33f440255231a474fe3ba03bd1d9ee0328662d1eae3fce4c59b91549d6b62b839b - languageName: node - linkType: hard - -"@webassemblyjs/leb128@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/leb128@npm:1.11.1" +"@webassemblyjs/leb128@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/leb128@npm:1.11.6" dependencies: "@xtuc/long": 4.2.2 - checksum: 33ccc4ade2f24de07bf31690844d0b1ad224304ee2062b0e464a610b0209c79e0b3009ac190efe0e6bd568b0d1578d7c3047fc1f9d0197c92fc061f56224ff4a + checksum: 7ea942dc9777d4b18a5ebfa3a937b30ae9e1d2ce1fee637583ed7f376334dd1d4274f813d2e250056cca803e0952def4b954913f1a3c9068bcd4ab4ee5143bf0 languageName: node linkType: hard -"@webassemblyjs/leb128@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/leb128@npm:1.9.0" +"@webassemblyjs/utf8@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/utf8@npm:1.11.6" + checksum: 807fe5b5ce10c390cfdd93e0fb92abda8aebabb5199980681e7c3743ee3306a75729bcd1e56a3903980e96c885ee53ef901fcbaac8efdfa480f9c0dae1d08713 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-edit@npm:1.11.6" dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/helper-wasm-section": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + "@webassemblyjs/wasm-opt": 1.11.6 + "@webassemblyjs/wasm-parser": 1.11.6 + "@webassemblyjs/wast-printer": 1.11.6 + checksum: 29ce75870496d6fad864d815ebb072395a8a3a04dc9c3f4e1ffdc63fc5fa58b1f34304a1117296d8240054cfdbc38aca88e71fb51483cf29ffab0a61ef27b481 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/ieee754": 1.11.6 + "@webassemblyjs/leb128": 1.11.6 + "@webassemblyjs/utf8": 1.11.6 + checksum: a645a2eecbea24833c3260a249704a7f554ef4a94c6000984728e94bb2bc9140a68dfd6fd21d5e0bbb09f6dfc98e083a45760a83ae0417b41a0196ff6d45a23a + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/wasm-gen": 1.11.6 + "@webassemblyjs/wasm-parser": 1.11.6 + checksum: b4557f195487f8e97336ddf79f7bef40d788239169aac707f6eaa2fa5fe243557c2d74e550a8e57f2788e70c7ae4e7d32f7be16101afe183d597b747a3bdd528 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": + version: 1.11.6 + resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/helper-api-error": 1.11.6 + "@webassemblyjs/helper-wasm-bytecode": 1.11.6 + "@webassemblyjs/ieee754": 1.11.6 + "@webassemblyjs/leb128": 1.11.6 + "@webassemblyjs/utf8": 1.11.6 + checksum: 8200a8d77c15621724a23fdabe58d5571415cda98a7058f542e670ea965dd75499f5e34a48675184947c66f3df23adf55df060312e6d72d57908e3f049620d8a + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.11.6": + version: 1.11.6 + resolution: "@webassemblyjs/wast-printer@npm:1.11.6" + dependencies: + "@webassemblyjs/ast": 1.11.6 "@xtuc/long": 4.2.2 - checksum: 4ca7cbb869530d78d42a414f34ae53249364cb1ecebbfb6ed5d562c2f209fce857502f088822ee82a23876f653a262ddc34ab64e45a7962510a263d39bb3f51a - languageName: node - linkType: hard - -"@webassemblyjs/utf8@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/utf8@npm:1.11.1" - checksum: 972c5cfc769d7af79313a6bfb96517253a270a4bf0c33ba486aa43cac43917184fb35e51dfc9e6b5601548cd5931479a42e42c89a13bb591ffabebf30c8a6a0b - languageName: node - linkType: hard - -"@webassemblyjs/utf8@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/utf8@npm:1.9.0" - checksum: e328a30ac8a503bbd015d32e75176e0dedcb45a21d4be051c25dfe89a00035ca7a6dbd8937b442dd5b4b334de3959d4f5fe0b330037bd226a28b9814cd49e84f - languageName: node - linkType: hard - -"@webassemblyjs/wasm-edit@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-edit@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/helper-wasm-section": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - "@webassemblyjs/wasm-opt": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - "@webassemblyjs/wast-printer": 1.11.1 - checksum: 6d7d9efaec1227e7ef7585a5d7ff0be5f329f7c1c6b6c0e906b18ed2e9a28792a5635e450aca2d136770d0207225f204eff70a4b8fd879d3ac79e1dcc26dbeb9 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-edit@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wasm-edit@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-buffer": 1.9.0 - "@webassemblyjs/helper-wasm-bytecode": 1.9.0 - "@webassemblyjs/helper-wasm-section": 1.9.0 - "@webassemblyjs/wasm-gen": 1.9.0 - "@webassemblyjs/wasm-opt": 1.9.0 - "@webassemblyjs/wasm-parser": 1.9.0 - "@webassemblyjs/wast-printer": 1.9.0 - checksum: 1997e0c2f4051c33239587fb143242919320bc861a0af03a873c7150a27d6404bd2e063c658193288b0aa88c35aadbe0c4fde601fe642bae0743a8c8eda52717 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-gen@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/ieee754": 1.11.1 - "@webassemblyjs/leb128": 1.11.1 - "@webassemblyjs/utf8": 1.11.1 - checksum: 1f6921e640293bf99fb16b21e09acb59b340a79f986c8f979853a0ae9f0b58557534b81e02ea2b4ef11e929d946708533fd0693c7f3712924128fdafd6465f5b - languageName: node - linkType: hard - -"@webassemblyjs/wasm-gen@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wasm-gen@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-wasm-bytecode": 1.9.0 - "@webassemblyjs/ieee754": 1.9.0 - "@webassemblyjs/leb128": 1.9.0 - "@webassemblyjs/utf8": 1.9.0 - checksum: 2456e84e8e6bedb7ab47f6333a0ee170f7ef62842c90862ca787c08528ca8041061f3f8bc257fc2a01bf6e8d1a76fddaddd43418c738f681066e5b50f88fe7df - languageName: node - linkType: hard - -"@webassemblyjs/wasm-opt@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-opt@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - checksum: 21586883a20009e2b20feb67bdc451bbc6942252e038aae4c3a08e6f67b6bae0f5f88f20bfc7bd0452db5000bacaf5ab42b98cf9aa034a6c70e9fc616142e1db - languageName: node - linkType: hard - -"@webassemblyjs/wasm-opt@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wasm-opt@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-buffer": 1.9.0 - "@webassemblyjs/wasm-gen": 1.9.0 - "@webassemblyjs/wasm-parser": 1.9.0 - checksum: 91242205bdbd1aa8045364a5338bfb34880cb2c65f56db8dd19382894209673699fb31a0e5279f25c7e5bcd8f3097d6c9ca84d8969d9613ef2cf166450cc3515 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-parser@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-parser@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-api-error": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/ieee754": 1.11.1 - "@webassemblyjs/leb128": 1.11.1 - "@webassemblyjs/utf8": 1.11.1 - checksum: 1521644065c360e7b27fad9f4bb2df1802d134dd62937fa1f601a1975cde56bc31a57b6e26408b9ee0228626ff3ba1131ae6f74ffb7d718415b6528c5a6dbfc2 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-parser@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wasm-parser@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-api-error": 1.9.0 - "@webassemblyjs/helper-wasm-bytecode": 1.9.0 - "@webassemblyjs/ieee754": 1.9.0 - "@webassemblyjs/leb128": 1.9.0 - "@webassemblyjs/utf8": 1.9.0 - checksum: 493f6cfc63a5e16073056c81ff0526a9936f461327379ef3c83cc841000e03623b6352704f6bf9f7cb5b3610f0032020a61f9cca78c91b15b8e995854b29c098 - languageName: node - linkType: hard - -"@webassemblyjs/wast-parser@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wast-parser@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/floating-point-hex-parser": 1.9.0 - "@webassemblyjs/helper-api-error": 1.9.0 - "@webassemblyjs/helper-code-frame": 1.9.0 - "@webassemblyjs/helper-fsm": 1.9.0 - "@xtuc/long": 4.2.2 - checksum: 705dd48fbbceec7f6bed299b8813631b242fd9312f9594dbb2985dda86c9688048692357d684f6080fc2c5666287cefaa26b263d01abadb6a9049d4c8978b9db - languageName: node - linkType: hard - -"@webassemblyjs/wast-printer@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wast-printer@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@xtuc/long": 4.2.2 - checksum: f15ae4c2441b979a3b4fce78f3d83472fb22350c6dc3fd34bfe7c3da108e0b2360718734d961bba20e7716cb8578e964b870da55b035e209e50ec9db0378a3f7 - languageName: node - linkType: hard - -"@webassemblyjs/wast-printer@npm:1.9.0": - version: 1.9.0 - resolution: "@webassemblyjs/wast-printer@npm:1.9.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/wast-parser": 1.9.0 - "@xtuc/long": 4.2.2 - checksum: 3d1e1b2e84745a963f69acd1c02425b321dd2e608e11dabc467cae0c9a808962bc769ec9afc46fbcea7188cc1e47d72370da762d258f716fb367cb1a7865c54b + checksum: d2fa6a4c427325ec81463e9c809aa6572af6d47f619f3091bf4c4a6fc34f1da3df7caddaac50b8e7a457f8784c62cd58c6311b6cb69b0162ccd8d4c072f79cf8 languageName: node linkType: hard @@ -14461,12 +14204,12 @@ __metadata: linkType: hard "@whatwg-node/fetch@npm:^0.9.0": - version: 0.9.13 - resolution: "@whatwg-node/fetch@npm:0.9.13" + version: 0.9.14 + resolution: "@whatwg-node/fetch@npm:0.9.14" dependencies: - "@whatwg-node/node-fetch": ^0.4.17 + "@whatwg-node/node-fetch": ^0.5.0 urlpattern-polyfill: ^9.0.0 - checksum: ba46887d9ab6e486066d4cf3911dcfaa7d76ee458abfa6d039e5a34415c8be2383fcbb56b2488f97edb941d9d58d3285c4c293def094f1caac8aed06b7a56da9 + checksum: 9dc7c49742df03f8072e8caa1c2f602f9487ee5fd42c8daa23da24efe73c538f452f15852a6ce4aa12a933b70ce4c8b1fbd036a065b7e18ffbffa9b29f403746 languageName: node linkType: hard @@ -14483,16 +14226,16 @@ __metadata: languageName: node linkType: hard -"@whatwg-node/node-fetch@npm:^0.4.17": - version: 0.4.19 - resolution: "@whatwg-node/node-fetch@npm:0.4.19" +"@whatwg-node/node-fetch@npm:^0.5.0": + version: 0.5.3 + resolution: "@whatwg-node/node-fetch@npm:0.5.3" dependencies: + "@kamilkisiela/fast-url-parser": ^1.1.4 "@whatwg-node/events": ^0.1.0 busboy: ^1.6.0 fast-querystring: ^1.1.1 - fast-url-parser: ^1.1.3 tslib: ^2.3.1 - checksum: 51f6520075d14a4456c773c4105d9b91cbcc1ba819faf5f26e9bf821255a1cd278bf0690e0f323768e54d5389a5b0d0beb0d81fb0ff9f5ab4f877ffb4cf40c2b + checksum: 43e87406d80c8e5df86080fca03da5129c77567cae20f6c8a733b7c5c95175d2ec038496d0ba142cc1ca22d77e0a9bc0dbce9e290390eb8061946f1b4ca8310d languageName: node linkType: hard @@ -14555,6 +14298,37 @@ __metadata: languageName: node linkType: hard +"@yarnpkg/esbuild-plugin-pnp@npm:^3.0.0-rc.10": + version: 3.0.0-rc.15 + resolution: "@yarnpkg/esbuild-plugin-pnp@npm:3.0.0-rc.15" + dependencies: + tslib: ^2.4.0 + peerDependencies: + esbuild: ">=0.10.0" + checksum: 04da15355a99773b441742814ba4d0f3453a83df47aa07e215f167e156f109ab8e971489c8b1a4ddf3c79d568d35213f496ad52e97298228597e1aacc22680aa + languageName: node + linkType: hard + +"@yarnpkg/fslib@npm:2.10.3": + version: 2.10.3 + resolution: "@yarnpkg/fslib@npm:2.10.3" + dependencies: + "@yarnpkg/libzip": ^2.3.0 + tslib: ^1.13.0 + checksum: 0ca693f61d47bcf165411a121ed9123f512b1b5bfa5e1c6c8f280b4ffdbea9bf2a6db418f99ecfc9624587fdc695b2b64eb0fe7b4028e44095914b25ca99655e + languageName: node + linkType: hard + +"@yarnpkg/libzip@npm:2.3.0, @yarnpkg/libzip@npm:^2.3.0": + version: 2.3.0 + resolution: "@yarnpkg/libzip@npm:2.3.0" + dependencies: + "@types/emscripten": ^1.39.6 + tslib: ^1.13.0 + checksum: 533a4883f69bb013f955d80dc19719881697e6849ea5f0cbe6d87ef1d582b05cbae8a453802f92ad0c852f976296cac3ff7834be79a7e415b65cdf213e448110 + languageName: node + linkType: hard + "WipeMyCal@workspace:packages/app-store/wipemycalother": version: 0.0.0-use.local resolution: "WipeMyCal@workspace:packages/app-store/wipemycalother" @@ -14614,12 +14388,12 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.7.6": - version: 1.8.0 - resolution: "acorn-import-assertions@npm:1.8.0" +"acorn-import-assertions@npm:^1.9.0": + version: 1.9.0 + resolution: "acorn-import-assertions@npm:1.9.0" peerDependencies: acorn: ^8 - checksum: 5c4cf7c850102ba7ae0eeae0deb40fb3158c8ca5ff15c0bca43b5c47e307a1de3d8ef761788f881343680ea374631ae9e9615ba8876fee5268dbe068c98bcba6 + checksum: 944fb2659d0845c467066bdcda2e20c05abe3aaf11972116df457ce2627628a81764d800dd55031ba19de513ee0d43bb771bc679cc0eda66dc8b4fade143bc0c languageName: node linkType: hard @@ -14655,15 +14429,6 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^6.4.1": - version: 6.4.2 - resolution: "acorn@npm:6.4.2" - bin: - acorn: bin/acorn - checksum: 44b07053729db7f44d28343eed32247ed56dc4a6ec6dff2b743141ecd6b861406bbc1c20bf9d4f143ea7dd08add5dc8c290582756539bc03a8db605050ce2fb4 - languageName: node - linkType: hard - "acorn@npm:^7.4.1": version: 7.4.1 resolution: "acorn@npm:7.4.1" @@ -14691,6 +14456,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.11.2": + version: 8.11.2 + resolution: "acorn@npm:8.11.2" + bin: + acorn: bin/acorn + checksum: 818450408684da89423e3daae24e4dc9b68692db8ab49ea4569c7c5abb7a3f23669438bf129cc81dfdada95e1c9b944ee1bfca2c57a05a4dc73834a612fbf6a7 + languageName: node + linkType: hard + "acorn@npm:^8.5.0": version: 8.7.1 resolution: "acorn@npm:8.7.1" @@ -14700,7 +14474,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0": +"acorn@npm:^8.7.1, acorn@npm:^8.8.0": version: 8.8.0 resolution: "acorn@npm:8.8.0" bin: @@ -14742,6 +14516,13 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:5": + version: 5.1.1 + resolution: "agent-base@npm:5.1.1" + checksum: 61ae789f3019f1dc10e8cba6d3ae9826949299a4e54aaa1cfa2fa37c95a108e70e95423b963bb987d7891a703fd9a5c383a506f4901819f3ee56f3147c0aa8ab + languageName: node + linkType: hard + "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -14781,40 +14562,6 @@ __metadata: languageName: node linkType: hard -"airbnb-js-shims@npm:^2.2.1": - version: 2.2.1 - resolution: "airbnb-js-shims@npm:2.2.1" - dependencies: - array-includes: ^3.0.3 - array.prototype.flat: ^1.2.1 - array.prototype.flatmap: ^1.2.1 - es5-shim: ^4.5.13 - es6-shim: ^0.35.5 - function.prototype.name: ^1.1.0 - globalthis: ^1.0.0 - object.entries: ^1.1.0 - object.fromentries: ^2.0.0 || ^1.0.0 - object.getownpropertydescriptors: ^2.0.3 - object.values: ^1.1.0 - promise.allsettled: ^1.0.0 - promise.prototype.finally: ^3.1.0 - string.prototype.matchall: ^4.0.0 || ^3.0.1 - string.prototype.padend: ^3.0.0 - string.prototype.padstart: ^3.0.0 - symbol.prototype.description: ^1.0.0 - checksum: bdd96e4cac75a8a942fb93cb8b7150573363a9fb40ab8528997bc067f24ae83d3031165635075b1326e463dcf840cc036b2ceb554563e75a38faf0ca288407a3 - languageName: node - linkType: hard - -"ajv-errors@npm:^1.0.0": - version: 1.0.1 - resolution: "ajv-errors@npm:1.0.1" - peerDependencies: - ajv: ">=5.0.0" - checksum: 2c9fc02cf58f9aae5bace61ebd1b162e1ea372ae9db5999243ba5e32a9a78c0d635d29ae085f652c61c941a43af0b2b1acdb255e29d44dc43a6e021085716d8c - languageName: node - linkType: hard - "ajv-formats@npm:^2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" @@ -14829,7 +14576,7 @@ __metadata: languageName: node linkType: hard -"ajv-keywords@npm:^3.1.0, ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": +"ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" peerDependencies: @@ -14849,7 +14596,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.1.0, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": +"ajv@npm:^6.10.0, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -14885,22 +14632,6 @@ __metadata: languageName: node linkType: hard -"ansi-align@npm:^3.0.0": - version: 3.0.1 - resolution: "ansi-align@npm:3.0.1" - dependencies: - string-width: ^4.1.0 - checksum: 6abfa08f2141d231c257162b15292467081fa49a208593e055c866aa0455b57f3a86b5a678c190c618faa79b4c59e254493099cb700dd9cf2293c6be2c8f5d8d - languageName: node - linkType: hard - -"ansi-colors@npm:^3.0.0": - version: 3.2.4 - resolution: "ansi-colors@npm:3.2.4" - checksum: 026c51880e9f8eb59b112669a87dbea4469939ff94b131606303bbd697438a6691b16b9db3027aa9bf132a244214e83ab1508b998496a34d2aea5b437ac9e62d - languageName: node - linkType: hard - "ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" @@ -14993,17 +14724,6 @@ __metadata: languageName: node linkType: hard -"ansi-to-html@npm:^0.6.11": - version: 0.6.15 - resolution: "ansi-to-html@npm:0.6.15" - dependencies: - entities: ^2.0.0 - bin: - ansi-to-html: bin/ansi-to-html - checksum: c899362a29b92c8ae075b72168b826f7c233875b475719304942f80695e0ce4a6812845021192da5fb0ac80b10209b4fae5aede42620a1b1b3d3b30f3ef77a86 - languageName: node - linkType: hard - "ansicolors@npm:~0.3.2": version: 0.3.2 resolution: "ansicolors@npm:0.3.2" @@ -15025,17 +14745,7 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^2.0.0": - version: 2.0.0 - resolution: "anymatch@npm:2.0.0" - dependencies: - micromatch: ^3.1.4 - normalize-path: ^2.1.1 - checksum: f7bb1929842b4585cdc28edbb385767d499ce7d673f96a8f11348d2b2904592ffffc594fe9229b9a1e9e4dccb9329b7692f9f45e6a11dcefbb76ecdc9ab740f6 - languageName: node - linkType: hard - -"anymatch@npm:^3.0.0, anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": +"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" dependencies: @@ -15073,13 +14783,6 @@ __metadata: languageName: node linkType: hard -"aproba@npm:^1.1.1": - version: 1.2.0 - resolution: "aproba@npm:1.2.0" - checksum: 0fca141966559d195072ed047658b6e6c4fe92428c385dd38e288eacfc55807e7b4989322f030faff32c0f46bb0bc10f1e0ac32ec22d25315a1e5bbc0ebb76dc - languageName: node - linkType: hard - "archiver-utils@npm:^2.1.0": version: 2.1.0 resolution: "archiver-utils@npm:2.1.0" @@ -15131,16 +14834,6 @@ __metadata: languageName: node linkType: hard -"are-we-there-yet@npm:^2.0.0": - version: 2.0.0 - resolution: "are-we-there-yet@npm:2.0.0" - dependencies: - delegates: ^1.0.0 - readable-stream: ^3.6.0 - checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c - languageName: node - linkType: hard - "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -15206,6 +14899,15 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:5.1.3": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: ^2.0.5 + checksum: 929ff95f02857b650fb4cbcd2f41072eee2f46159a6605ea03bf63aa572e35ffdff43d69e815ddc462e16e07de8faba3978afc2813650b4448ee18c9895d982b + languageName: node + linkType: hard + "aria-query@npm:^4.2.2": version: 4.2.2 resolution: "aria-query@npm:4.2.2" @@ -15223,20 +14925,6 @@ __metadata: languageName: node linkType: hard -"arr-diff@npm:^4.0.0": - version: 4.0.0 - resolution: "arr-diff@npm:4.0.0" - checksum: ea7c8834842ad3869297f7915689bef3494fd5b102ac678c13ffccab672d3d1f35802b79e90c4cfec2f424af3392e44112d1ccf65da34562ed75e049597276a0 - languageName: node - linkType: hard - -"arr-flatten@npm:^1.1.0": - version: 1.1.0 - resolution: "arr-flatten@npm:1.1.0" - checksum: 963fe12564fca2f72c055f3f6c206b9e031f7c433a0c66ca9858b484821f248c5b1e5d53c8e4989d80d764cd776cf6d9b160ad05f47bdc63022bfd63b5455e22 - languageName: node - linkType: hard - "arr-rotate@npm:^1.0.0": version: 1.0.0 resolution: "arr-rotate@npm:1.0.0" @@ -15244,17 +14932,13 @@ __metadata: languageName: node linkType: hard -"arr-union@npm:^3.1.0": - version: 3.1.0 - resolution: "arr-union@npm:3.1.0" - checksum: b5b0408c6eb7591143c394f3be082fee690ddd21f0fdde0a0a01106799e847f67fcae1b7e56b0a0c173290e29c6aca9562e82b300708a268bc8f88f3d6613cb9 - languageName: node - linkType: hard - -"array-find-index@npm:^1.0.1": - version: 1.0.2 - resolution: "array-find-index@npm:1.0.2" - checksum: aac128bf369e1ac6c06ff0bb330788371c0e256f71279fb92d745e26fb4b9db8920e485b4ec25e841c93146bf71a34dcdbcefa115e7e0f96927a214d237b7081 +"array-buffer-byte-length@npm:^1.0.0": + version: 1.0.0 + resolution: "array-buffer-byte-length@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + is-array-buffer: ^3.0.1 + checksum: 044e101ce150f4804ad19c51d6c4d4cfa505c5b2577bd179256e4aa3f3f6a0a5e9874c78cd428ee566ac574c8a04d7ce21af9fe52e844abfdccb82b33035a7c3 languageName: node linkType: hard @@ -15272,19 +14956,6 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.0.3, array-includes@npm:^3.1.5": - version: 3.1.5 - resolution: "array-includes@npm:3.1.5" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.4 - es-abstract: ^1.19.5 - get-intrinsic: ^1.1.1 - is-string: ^1.0.7 - checksum: f6f24d834179604656b7bec3e047251d5cc87e9e87fab7c175c61af48e80e75acd296017abcde21fb52292ab6a2a449ab2ee37213ee48c8709f004d75983f9c5 - languageName: node - linkType: hard - "array-includes@npm:^3.1.4": version: 3.1.4 resolution: "array-includes@npm:3.1.4" @@ -15298,6 +14969,19 @@ __metadata: languageName: node linkType: hard +"array-includes@npm:^3.1.5": + version: 3.1.5 + resolution: "array-includes@npm:3.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.19.5 + get-intrinsic: ^1.1.1 + is-string: ^1.0.7 + checksum: f6f24d834179604656b7bec3e047251d5cc87e9e87fab7c175c61af48e80e75acd296017abcde21fb52292ab6a2a449ab2ee37213ee48c8709f004d75983f9c5 + languageName: node + linkType: hard + "array-includes@npm:^3.1.6": version: 3.1.6 resolution: "array-includes@npm:3.1.6" @@ -15311,15 +14995,6 @@ __metadata: languageName: node linkType: hard -"array-union@npm:^1.0.2": - version: 1.0.2 - resolution: "array-union@npm:1.0.2" - dependencies: - array-uniq: ^1.0.1 - checksum: 82cec6421b6e6766556c484835a6d476a873f1b71cace5ab2b4f1b15b1e3162dc4da0d16f7a2b04d4aec18146c6638fe8f661340b31ba8e469fd811a1b45dc8d - languageName: node - linkType: hard - "array-union@npm:^2.1.0": version: 2.1.0 resolution: "array-union@npm:2.1.0" @@ -15327,32 +15002,6 @@ __metadata: languageName: node linkType: hard -"array-uniq@npm:^1.0.1": - version: 1.0.3 - resolution: "array-uniq@npm:1.0.3" - checksum: 1625f06b093d8bf279b81adfec6e72951c0857d65b5e3f65f053fffe9f9dd61c2fc52cff57e38a4700817e7e3f01a4faa433d505ea9e33cdae4514c334e0bf9e - languageName: node - linkType: hard - -"array-unique@npm:^0.3.2": - version: 0.3.2 - resolution: "array-unique@npm:0.3.2" - checksum: da344b89cfa6b0a5c221f965c21638bfb76b57b45184a01135382186924f55973cd9b171d4dad6bf606c6d9d36b0d721d091afdc9791535ead97ccbe78f8a888 - languageName: node - linkType: hard - -"array.prototype.flat@npm:^1.2.1": - version: 1.3.0 - resolution: "array.prototype.flat@npm:1.3.0" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 - es-shim-unscopables: ^1.0.0 - checksum: 2a652b3e8dc0bebb6117e42a5ab5738af0203a14c27341d7bb2431467bdb4b348e2c5dc555dfcda8af0a5e4075c400b85311ded73861c87290a71a17c3e0a257 - languageName: node - linkType: hard - "array.prototype.flat@npm:^1.2.3": version: 1.3.1 resolution: "array.prototype.flat@npm:1.3.1" @@ -15376,7 +15025,7 @@ __metadata: languageName: node linkType: hard -"array.prototype.flatmap@npm:^1.2.1, array.prototype.flatmap@npm:^1.3.0": +"array.prototype.flatmap@npm:^1.3.0": version: 1.3.0 resolution: "array.prototype.flatmap@npm:1.3.0" dependencies: @@ -15400,32 +15049,6 @@ __metadata: languageName: node linkType: hard -"array.prototype.map@npm:^1.0.4": - version: 1.0.4 - resolution: "array.prototype.map@npm:1.0.4" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.0 - es-array-method-boxes-properly: ^1.0.0 - is-string: ^1.0.7 - checksum: 08c8065ae9e60585c1262e54556da2340cd140dc799d790843c1f4ad3a3f458e9866d147c8ff0308741e8316904313f682803ca15c179f65cb2f5b993fa71a82 - languageName: node - linkType: hard - -"array.prototype.reduce@npm:^1.0.4": - version: 1.0.4 - resolution: "array.prototype.reduce@npm:1.0.4" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 - es-array-method-boxes-properly: ^1.0.0 - is-string: ^1.0.7 - checksum: 6a57a1a2d3b77a9543db139cd52211f43a5af8e8271cb3c173be802076e3a6f71204ba8f090f5937ebc0842d5876db282f0f63dffd0e86b153e6e5a45681e4a5 - languageName: node - linkType: hard - "array.prototype.tosorted@npm:^1.1.1": version: 1.1.1 resolution: "array.prototype.tosorted@npm:1.1.1" @@ -15446,7 +15069,7 @@ __metadata: languageName: node linkType: hard -"arrify@npm:^2.0.0, arrify@npm:^2.0.1": +"arrify@npm:^2.0.0": version: 2.0.1 resolution: "arrify@npm:2.0.1" checksum: 067c4c1afd182806a82e4c1cb8acee16ab8b5284fbca1ce29408e6e91281c36bb5b612f6ddfbd40a0f7a7e0c75bf2696eb94c027f6e328d6e9c52465c98e4209 @@ -15499,13 +15122,16 @@ __metadata: languageName: node linkType: hard -"assert@npm:^1.1.1": - version: 1.5.0 - resolution: "assert@npm:1.5.0" +"assert@npm:^2.0.0, assert@npm:^2.1.0": + version: 2.1.0 + resolution: "assert@npm:2.1.0" dependencies: - object-assign: ^4.1.1 - util: 0.10.3 - checksum: 9be48435f726029ae7020c5888a3566bf4d617687aab280827f2e4029644b6515a9519ea10d018b342147c02faf73d9e9419e780e8937b3786ee4945a0ca71e5 + call-bind: ^1.0.2 + is-nan: ^1.3.2 + object-is: ^1.1.5 + object.assign: ^4.1.4 + util: ^0.12.5 + checksum: 1ed1cabba9abe55f4109b3f7292b4e4f3cf2953aad8dc148c0b3c3bd676675c31b1abb32ef563b7d5a19d1715bf90d1e5f09fad2a4ee655199468902da80f7c2 languageName: node linkType: hard @@ -15516,13 +15142,6 @@ __metadata: languageName: node linkType: hard -"assign-symbols@npm:^1.0.0": - version: 1.0.0 - resolution: "assign-symbols@npm:1.0.0" - checksum: c0eb895911d05b6b2d245154f70461c5e42c107457972e5ebba38d48967870dee53bcdf6c7047990586daa80fab8dab3cc6300800fbd47b454247fdedd859a2c - languageName: node - linkType: hard - "ast-types-flow@npm:^0.0.7": version: 0.0.7 resolution: "ast-types-flow@npm:0.0.7" @@ -15530,12 +15149,12 @@ __metadata: languageName: node linkType: hard -"ast-types@npm:^0.14.2": - version: 0.14.2 - resolution: "ast-types@npm:0.14.2" +"ast-types@npm:^0.16.1": + version: 0.16.1 + resolution: "ast-types@npm:0.16.1" dependencies: tslib: ^2.0.1 - checksum: 8674a77307764979f0a0b2006b7223a4b789abffaa7acbf6a1132650a799252155170173a1ff6a7fb6897f59437fc955f2707bdfc391b0797750898876e6c9ed + checksum: 21c186da9fdb1d8087b1b7dabbc4059f91aa5a1e593a9776b4393cc1eaa857e741b2dda678d20e34b16727b78fef3ab59cf8f0c75ed1ba649c78fe194e5c114b languageName: node linkType: hard @@ -15546,10 +15165,10 @@ __metadata: languageName: node linkType: hard -"async-each@npm:^1.0.1": - version: 1.0.3 - resolution: "async-each@npm:1.0.3" - checksum: 868651cfeb209970b367fbb96df1e1c8dc0b22c681cda7238417005ab2a5fbd944ee524b43f2692977259a57b7cc2547e03ff68f2b5113dbdf953d48cc078dc3 +"async-limiter@npm:~1.0.0": + version: 1.0.1 + resolution: "async-limiter@npm:1.0.1" + checksum: 2b849695b465d93ad44c116220dee29a5aeb63adac16c1088983c339b0de57d76e82533e8e364a93a9f997f28bbfc6a92948cefc120652bd07f3b59f8d75cf2b languageName: node linkType: hard @@ -15590,15 +15209,6 @@ __metadata: languageName: node linkType: hard -"atob@npm:^2.1.2": - version: 2.1.2 - resolution: "atob@npm:2.1.2" - bin: - atob: bin/atob.js - checksum: dfeeeb70090c5ebea7be4b9f787f866686c645d9f39a0d184c817252d0cf08455ed25267d79c03254d3be1f03ac399992a792edcd5ffb9c91e097ab5ef42833a - languageName: node - linkType: hard - "atomically@npm:^1.7.0": version: 1.7.0 resolution: "atomically@npm:1.7.0" @@ -15655,20 +15265,10 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^9.8.6": - version: 9.8.8 - resolution: "autoprefixer@npm:9.8.8" - dependencies: - browserslist: ^4.12.0 - caniuse-lite: ^1.0.30001109 - normalize-range: ^0.1.2 - num2fraction: ^1.2.2 - picocolors: ^0.2.1 - postcss: ^7.0.32 - postcss-value-parser: ^4.1.0 - bin: - autoprefixer: bin/autoprefixer - checksum: 8f017672fbac248db0cf4e86aa707d8b148d9abadb842b5cf4c6be306d80fa6a654fadefd17e46213234c1f0947612acce2864f93e903f3e736b183fc1aedc45 +"available-typed-arrays@npm:^1.0.5": + version: 1.0.5 + resolution: "available-typed-arrays@npm:1.0.5" + checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a languageName: node linkType: hard @@ -15772,6 +15372,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.6.1": + version: 1.6.2 + resolution: "axios@npm:1.6.2" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 4a7429e2b784be0f2902ca2680964391eae7236faa3967715f30ea45464b98ae3f1c6f631303b13dfe721b17126b01f486c7644b9ef276bfc63112db9fd379f8 + languageName: node + linkType: hard + "axobject-query@npm:^2.2.0": version: 2.2.0 resolution: "axobject-query@npm:2.2.0" @@ -15779,7 +15390,23 @@ __metadata: languageName: node linkType: hard -"babel-loader@npm:^8.0.0, babel-loader@npm:^8.2.5": +"b4a@npm:^1.6.4": + version: 1.6.4 + resolution: "b4a@npm:1.6.4" + checksum: 81b086f9af1f8845fbef4476307236bda3d660c158c201db976f19cdce05f41f93110ab6b12fd7a2696602a490cc43d5410ee36a56d6eef93afb0d6ca69ac3b2 + languageName: node + linkType: hard + +"babel-core@npm:^7.0.0-bridge.0": + version: 7.0.0-bridge.0 + resolution: "babel-core@npm:7.0.0-bridge.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2a1cb879019dffb08d17bec36e13c3a6d74c94773f41c1fd8b14de13f149cc34b705b0a1e07b42fcf35917b49d78db6ff0c5c3b00b202a5235013d517b5c6bbb + languageName: node + linkType: hard + +"babel-loader@npm:^8.2.5": version: 8.2.5 resolution: "babel-loader@npm:8.2.5" dependencies: @@ -15794,6 +15421,19 @@ __metadata: languageName: node linkType: hard +"babel-loader@npm:^9.0.0": + version: 9.1.3 + resolution: "babel-loader@npm:9.1.3" + dependencies: + find-cache-dir: ^4.0.0 + schema-utils: ^4.0.0 + peerDependencies: + "@babel/core": ^7.12.0 + webpack: ">=5" + checksum: b168dde5b8cf11206513371a79f86bb3faa7c714e6ec9fffd420876b61f3d7f5f4b976431095ef6a14bc4d324505126deb91045fd41e312ba49f4deaa166fe28 + languageName: node + linkType: hard + "babel-plugin-add-react-displayname@npm:^0.0.5": version: 0.0.5 resolution: "babel-plugin-add-react-displayname@npm:0.0.5" @@ -15801,28 +15441,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-apply-mdx-type-prop@npm:1.6.22": - version: 1.6.22 - resolution: "babel-plugin-apply-mdx-type-prop@npm:1.6.22" - dependencies: - "@babel/helper-plugin-utils": 7.10.4 - "@mdx-js/util": 1.6.22 - peerDependencies: - "@babel/core": ^7.11.6 - checksum: 43e2100164a8f3e46fddd76afcbfb1f02cbebd5612cfe63f3d344a740b0afbdc4d2bf5659cffe9323dd2554c7b86b23ebedae9dadcec353b6594f4292a1a28e2 - languageName: node - linkType: hard - -"babel-plugin-extract-import-names@npm:1.6.22": - version: 1.6.22 - resolution: "babel-plugin-extract-import-names@npm:1.6.22" - dependencies: - "@babel/helper-plugin-utils": 7.10.4 - checksum: 145ccf09c96d36411d340e78086555f8d4d5924ea39fcb0eca461c066cfa98bc4344982bb35eb85d054ef88f8d4dfc0205ba27370c1d8fcc78191b02908d044d - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^6.0.0": +"babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" dependencies: @@ -15846,80 +15465,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-macros@npm:^3.0.1": - version: 3.1.0 - resolution: "babel-plugin-macros@npm:3.1.0" - dependencies: - "@babel/runtime": ^7.12.5 - cosmiconfig: ^7.0.0 - resolve: ^1.19.0 - checksum: 765de4abebd3e4688ebdfbff8571ddc8cd8061f839bb6c3e550b0344a4027b04c60491f843296ce3f3379fb356cc873d57a9ee6694262547eb822c14a25be9a6 - languageName: node - linkType: hard - -"babel-plugin-named-exports-order@npm:^0.0.2": - version: 0.0.2 - resolution: "babel-plugin-named-exports-order@npm:0.0.2" - checksum: d918390a09c0148893ea93bdc9c4fc6a03447c688eaf40bed0f0682d036e985ecee830b90fec2ab149b8dc0cb3220a2c0ac5054e42626bdfe0b436b505b7ef22 - languageName: node - linkType: hard - -"babel-plugin-polyfill-corejs2@npm:^0.4.5": - version: 0.4.5 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.5" +"babel-plugin-polyfill-corejs2@npm:^0.4.6": + version: 0.4.6 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.6" dependencies: "@babel/compat-data": ^7.22.6 - "@babel/helper-define-polyfill-provider": ^0.4.2 + "@babel/helper-define-polyfill-provider": ^0.4.3 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 33a8e06aa54e2858d211c743d179f0487b03222f9ca1bfd7c4865bca243fca942a3358cb75f6bb894ed476cbddede834811fbd6903ff589f055821146f053e1a + checksum: 08896811df31530be6a9bcdd630cb9fd4b5ae5181039d18db3796efbc54e38d57a42af460845c10a04434e1bc45c0d47743c7e6c860383cc6b141083cde22030 languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.1.0": - version: 0.1.7 - resolution: "babel-plugin-polyfill-corejs3@npm:0.1.7" +"babel-plugin-polyfill-corejs3@npm:^0.8.5": + version: 0.8.6 + resolution: "babel-plugin-polyfill-corejs3@npm:0.8.6" dependencies: - "@babel/helper-define-polyfill-provider": ^0.1.5 - core-js-compat: ^3.8.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 5c420590a6e18688a868218fa1f5025e9294d093968d2fe1e6aa86981776d66826182f9b36cdd1c41741e9c401bf76164313aab6661efb56741348ed0e98448d - languageName: node - linkType: hard - -"babel-plugin-polyfill-corejs3@npm:^0.8.3": - version: 0.8.3 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.3" - dependencies: - "@babel/helper-define-polyfill-provider": ^0.4.2 - core-js-compat: ^3.31.0 + "@babel/helper-define-polyfill-provider": ^0.4.3 + core-js-compat: ^3.33.1 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: dcbb30e551702a82cfd4d2c375da2c317658e55f95e9edcda93b9bbfdcc8fb6e5344efcb144e04d3406859e7682afce7974c60ededd9f12072a48a83dd22a0da + checksum: 36951c2edac42ac0f05b200502e90d77bf66ccee5b52e2937d23496c6ef2372cce31b8c64144da374b77bd3eb65e2721703a52eac56cad16a152326c092cbf77 languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.5.2": - version: 0.5.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.5.2" +"babel-plugin-polyfill-regenerator@npm:^0.5.3": + version: 0.5.3 + resolution: "babel-plugin-polyfill-regenerator@npm:0.5.3" dependencies: - "@babel/helper-define-polyfill-provider": ^0.4.2 + "@babel/helper-define-polyfill-provider": ^0.4.3 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: d962200f604016a9a09bc9b4aaf60a3db7af876bb65bcefaeac04d44ac9d9ec4037cf24ce117760cc141d7046b6394c7eb0320ba9665cb4a2ee64df2be187c93 - languageName: node - linkType: hard - -"babel-plugin-react-docgen@npm:^4.2.1": - version: 4.2.1 - resolution: "babel-plugin-react-docgen@npm:4.2.1" - dependencies: - ast-types: ^0.14.2 - lodash: ^4.17.15 - react-docgen: ^5.0.0 - checksum: 6126d358ac2cb27a9a7f145ab586b7a28cb19ef09ca37c4f08a853246a101328ffe6c87813e95b1b4ba05beb627285199f7d0ba16abfb61b35cc4febb6d5eabd + checksum: 2bb546582cda1870d19e646a7183baeb2cccd56e0ef3e4eaeabd28e120daf17cb87399194a9ccdcf32506bcaa68d23e73440fc8ab990a7a0f8c5a77c12d5d4bc languageName: node linkType: hard @@ -15967,13 +15545,6 @@ __metadata: languageName: node linkType: hard -"bail@npm:^1.0.0": - version: 1.0.5 - resolution: "bail@npm:1.0.5" - checksum: 6c334940d7eaa4e656a12fb12407b6555649b6deb6df04270fa806e0da82684ebe4a4e47815b271c794b40f8d6fa286e0c248b14ddbabb324a917fab09b7301a - languageName: node - linkType: hard - "bail@npm:^2.0.0": version: 2.0.2 resolution: "bail@npm:2.0.2" @@ -16009,7 +15580,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -16023,21 +15594,6 @@ __metadata: languageName: node linkType: hard -"base@npm:^0.11.1": - version: 0.11.2 - resolution: "base@npm:0.11.2" - dependencies: - cache-base: ^1.0.1 - class-utils: ^0.3.5 - component-emitter: ^1.2.1 - define-property: ^1.0.0 - isobject: ^3.0.1 - mixin-deep: ^1.2.0 - pascalcase: ^0.1.1 - checksum: a4a146b912e27eea8f66d09cb0c9eab666f32ce27859a7dfd50f38cd069a2557b39f16dba1bc2aecb3b44bf096738dd207b7970d99b0318423285ab1b1994edd - languageName: node - linkType: hard - "bcp-47-match@npm:^2.0.3": version: 2.0.3 resolution: "bcp-47-match@npm:2.0.3" @@ -16061,12 +15617,12 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:^2.1.1": - version: 2.1.1 - resolution: "better-opn@npm:2.1.1" +"better-opn@npm:^3.0.2": + version: 3.0.2 + resolution: "better-opn@npm:3.0.2" dependencies: - open: ^7.0.3 - checksum: 3d1a945d125cbbc6e6a841bef7540435d77d5aa61fc4d345896f5f0b3780fcf9c7145373deaedf62d674a427b187ae973f4410884f9fea0c15f7f01f9dc339c7 + open: ^8.0.4 + checksum: 1471552fa7f733561e7f49e812be074b421153006ca744de985fb6d38939807959fc5fe9cb819cf09f864782e294704fd3b31711ea14c115baf3330a2f1135de languageName: node linkType: hard @@ -16079,10 +15635,10 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.7": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 +"big-integer@npm:^1.6.44": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 6e86885787a20fed96521958ae9086960e4e4b5e74d04f3ef7513d4d0ad631a9f3bde2730fc8aaa4b00419fc865f6ec573e5320234531ef37505da7da192c40b languageName: node linkType: hard @@ -16100,13 +15656,6 @@ __metadata: languageName: node linkType: hard -"binary-extensions@npm:^1.0.0": - version: 1.13.1 - resolution: "binary-extensions@npm:1.13.1" - checksum: ad7747f33c07e94ba443055de130b50c8b8b130a358bca064c580d91769ca6a69c7ac65ca008ff044ed4541d2c6ad45496e1fadbef5218a68770996b6a2194d7 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0, binary-extensions@npm:^2.2.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -16121,15 +15670,6 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.5.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: 1.0.0 - checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7 - languageName: node - linkType: hard - "biskviit@npm:1.0.1": version: 1.0.1 resolution: "biskviit@npm:1.0.1" @@ -16170,7 +15710,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:^3.4.6, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": +"bluebird@npm:^3.4.6, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -16198,9 +15738,9 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.0": - version: 1.20.0 - resolution: "body-parser@npm:1.20.0" +"body-parser@npm:1.20.1": + version: 1.20.1 + resolution: "body-parser@npm:1.20.1" dependencies: bytes: 3.1.2 content-type: ~1.0.4 @@ -16210,11 +15750,11 @@ __metadata: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.10.3 + qs: 6.11.0 raw-body: 2.5.1 type-is: ~1.6.18 unpipe: 1.0.0 - checksum: 12fffdeac82fe20dddcab7074215d5156e7d02a69ae90cbe9fee1ca3efa2f28ef52097cbea76685ee0a1509c71d85abd0056a08e612c09077cad6277a644cf88 + checksum: f1050dbac3bede6a78f0b87947a8d548ce43f91ccc718a50dd774f3c81f2d8b04693e52acf62659fad23101827dd318da1fb1363444ff9a8482b886a3e4a5266 languageName: node linkType: hard @@ -16239,28 +15779,12 @@ __metadata: languageName: node linkType: hard -"boxen@npm:^5.1.2": - version: 5.1.2 - resolution: "boxen@npm:5.1.2" +"bplist-parser@npm:^0.2.0": + version: 0.2.0 + resolution: "bplist-parser@npm:0.2.0" dependencies: - ansi-align: ^3.0.0 - camelcase: ^6.2.0 - chalk: ^4.1.0 - cli-boxes: ^2.2.1 - string-width: ^4.2.2 - type-fest: ^0.20.2 - widest-line: ^3.1.0 - wrap-ansi: ^7.0.0 - checksum: 82d03e42a72576ff235123f17b7c505372fe05c83f75f61e7d4fa4bcb393897ec95ce766fecb8f26b915f0f7a7227d66e5ec7cef43f5b2bd9d3aeed47ec55877 - languageName: node - linkType: hard - -"bplist-parser@npm:^0.1.0": - version: 0.1.1 - resolution: "bplist-parser@npm:0.1.1" - dependencies: - big-integer: ^1.6.7 - checksum: 1501d52f009c9f23ecee6855940e84ac55a6120c0f05570b1f51c8d494023416ec12f4d91b5ac97d6c0941d96dd41d7cb0bc1a9c0a02092df5b4b511acb8dda5 + big-integer: ^1.6.44 + checksum: d5339dd16afc51de6c88f88f58a45b72ed6a06aa31f5557d09877575f220b7c1d3fbe375da0b62e6a10d4b8ed80523567e351f24014f5bc886ad523758142cdd languageName: node linkType: hard @@ -16283,24 +15807,6 @@ __metadata: languageName: node linkType: hard -"braces@npm:^2.3.1, braces@npm:^2.3.2": - version: 2.3.2 - resolution: "braces@npm:2.3.2" - dependencies: - arr-flatten: ^1.1.0 - array-unique: ^0.3.2 - extend-shallow: ^2.0.1 - fill-range: ^4.0.0 - isobject: ^3.0.1 - repeat-element: ^1.1.2 - snapdragon: ^0.8.1 - snapdragon-node: ^2.0.1 - split-string: ^3.0.2 - to-regex: ^3.0.1 - checksum: e30dcb6aaf4a31c8df17d848aa283a65699782f75ad61ae93ec25c9729c66cf58e66f0000a9fec84e4add1135bb7da40f7cb9601b36bebcfa9ca58e8d5c07de0 - languageName: node - linkType: hard - "braces@npm:^3.0.2, braces@npm:~3.0.2": version: 3.0.2 resolution: "braces@npm:3.0.2" @@ -16408,6 +15914,15 @@ __metadata: languageName: node linkType: hard +"browserify-zlib@npm:^0.1.4": + version: 0.1.4 + resolution: "browserify-zlib@npm:0.1.4" + dependencies: + pako: ~0.2.0 + checksum: abee4cb4349e8a21391fd874564f41b113fe691372913980e6fa06a777e4ea2aad4e942af14ab99bce190d5ac8f5328201432f4ef0eae48c6d02208bc212976f + languageName: node + linkType: hard + "browserify-zlib@npm:^0.2.0": version: 0.2.0 resolution: "browserify-zlib@npm:0.2.0" @@ -16417,7 +15932,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.1": +"browserslist@npm:^4.14.5": version: 4.21.1 resolution: "browserslist@npm:4.21.1" dependencies: @@ -16459,6 +15974,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.22.2": + version: 4.22.2 + resolution: "browserslist@npm:4.22.2" + dependencies: + caniuse-lite: ^1.0.30001565 + electron-to-chromium: ^1.4.601 + node-releases: ^2.0.14 + update-browserslist-db: ^1.0.13 + bin: + browserslist: cli.js + checksum: 33ddfcd9145220099a7a1ac533cecfe5b7548ffeb29b313e1b57be6459000a1f8fa67e781cf4abee97268ac594d44134fcc4a6b2b4750ceddc9796e3a22076d9 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -16491,7 +16020,7 @@ __metadata: languageName: node linkType: hard -"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13": +"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" checksum: 06252347ae6daca3453b94e4b2f1d3754a3b146a111d81c68924c22d91889a40623264e95e67955b1cb4a68cbedf317abeabb5140a9766ed248973096db5ce1c @@ -16540,17 +16069,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^4.3.0": - version: 4.9.2 - resolution: "buffer@npm:4.9.2" - dependencies: - base64-js: ^1.0.2 - ieee754: ^1.1.4 - isarray: ^1.0.0 - checksum: 8801bc1ba08539f3be70eee307a8b9db3d40f6afbfd3cf623ab7ef41dffff1d0a31de0addbe1e66e0ca5f7193eeb667bfb1ecad3647f8f1b0750de07c13295c3 - languageName: node - linkType: hard - "buffer@npm:^5.2.0, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" @@ -16637,28 +16155,6 @@ __metadata: languageName: node linkType: hard -"c8@npm:^7.6.0": - version: 7.11.3 - resolution: "c8@npm:7.11.3" - dependencies: - "@bcoe/v8-coverage": ^0.2.3 - "@istanbuljs/schema": ^0.1.3 - find-up: ^5.0.0 - foreground-child: ^2.0.0 - istanbul-lib-coverage: ^3.2.0 - istanbul-lib-report: ^3.0.0 - istanbul-reports: ^3.1.4 - rimraf: ^3.0.2 - test-exclude: ^6.0.0 - v8-to-istanbul: ^9.0.0 - yargs: ^16.2.0 - yargs-parser: ^20.2.9 - bin: - c8: bin/c8.js - checksum: 9f7272bb5fd3d4f7d1c2f7fb986c1025a09c3afefce168c3ba62497dd6294f887c1678d23736126485ec534263ec6b4ed9b4bd2a05aa8d1682c949c3db1f5359 - languageName: node - linkType: hard - "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -16666,55 +16162,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^12.0.2": - version: 12.0.4 - resolution: "cacache@npm:12.0.4" - dependencies: - bluebird: ^3.5.5 - chownr: ^1.1.1 - figgy-pudding: ^3.5.1 - glob: ^7.1.4 - graceful-fs: ^4.1.15 - infer-owner: ^1.0.3 - lru-cache: ^5.1.1 - mississippi: ^3.0.0 - mkdirp: ^0.5.1 - move-concurrently: ^1.0.1 - promise-inflight: ^1.0.1 - rimraf: ^2.6.3 - ssri: ^6.0.1 - unique-filename: ^1.1.1 - y18n: ^4.0.0 - checksum: c88a72f36939b2523533946ffb27828443db5bf5995d761b35ae17af1eb6c8e20ac55b00b74c2ca900b2e1e917f0afba6847bf8cc16bee05ccca6aa150e0830c - languageName: node - linkType: hard - -"cacache@npm:^15.0.5": - version: 15.3.0 - resolution: "cacache@npm:15.3.0" - dependencies: - "@npmcli/fs": ^1.0.0 - "@npmcli/move-file": ^1.0.1 - chownr: ^2.0.0 - fs-minipass: ^2.0.0 - glob: ^7.1.4 - infer-owner: ^1.0.4 - lru-cache: ^6.0.0 - minipass: ^3.1.1 - minipass-collect: ^1.0.2 - minipass-flush: ^1.0.5 - minipass-pipeline: ^1.2.2 - mkdirp: ^1.0.3 - p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^8.0.1 - tar: ^6.0.2 - unique-filename: ^1.1.1 - checksum: a07327c27a4152c04eb0a831c63c00390d90f94d51bb80624a66f4e14a6b6360bbf02a84421267bd4d00ca73ac9773287d8d7169e8d2eafe378d2ce140579db8 - languageName: node - linkType: hard - "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -16741,23 +16188,6 @@ __metadata: languageName: node linkType: hard -"cache-base@npm:^1.0.1": - version: 1.0.1 - resolution: "cache-base@npm:1.0.1" - dependencies: - collection-visit: ^1.0.0 - component-emitter: ^1.2.1 - get-value: ^2.0.6 - has-value: ^1.0.0 - isobject: ^3.0.1 - set-value: ^2.0.0 - to-object-path: ^0.3.0 - union-value: ^1.0.0 - unset-value: ^1.0.0 - checksum: 9114b8654fe2366eedc390bad0bcf534e2f01b239a888894e2928cb58cdc1e6ea23a73c6f3450dcfd2058aa73a8a981e723cd1e7c670c047bf11afdc65880107 - languageName: node - linkType: hard - "cacheable-lookup@npm:^5.0.3": version: 5.0.4 resolution: "cacheable-lookup@npm:5.0.4" @@ -16766,8 +16196,8 @@ __metadata: linkType: hard "cacheable-request@npm:^7.0.2": - version: 7.0.2 - resolution: "cacheable-request@npm:7.0.2" + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" dependencies: clone-response: ^1.0.2 get-stream: ^5.1.0 @@ -16776,7 +16206,7 @@ __metadata: lowercase-keys: ^2.0.0 normalize-url: ^6.0.1 responselike: ^2.0.0 - checksum: 6152813982945a5c9989cb457a6c499f12edcc7ade323d2fbfd759abc860bdbd1306e08096916bb413c3c47e812f8e4c0a0cc1e112c8ce94381a960f115bc77f + checksum: 0de9df773fd4e7dd9bd118959878f8f2163867e2e1ab3575ffbecbe6e75e80513dd0c68ba30005e5e5a7b377cc6162bbc00ab1db019bb4e9cb3c2f3f7a6f1ee4 languageName: node linkType: hard @@ -16809,7 +16239,7 @@ __metadata: tsc-absolute: ^1.0.0 turbo: ^1.10.1 typescript: ^4.9.4 - vitest: ^0.34.3 + vitest: ^0.34.6 vitest-fetch-mock: ^0.2.2 vitest-mock-extended: ^1.1.3 languageName: unknown @@ -16825,6 +16255,17 @@ __metadata: languageName: node linkType: hard +"call-bind@npm:^1.0.4, call-bind@npm:^1.0.5": + version: 1.0.5 + resolution: "call-bind@npm:1.0.5" + dependencies: + function-bind: ^1.1.2 + get-intrinsic: ^1.2.1 + set-function-length: ^1.1.1 + checksum: 449e83ecbd4ba48e7eaac5af26fea3b50f8f6072202c2dd7c5a6e7a6308f2421abe5e13a3bbd55221087f76320c5e09f25a8fdad1bab2b77c68ae74d92234ea5 + languageName: node + linkType: hard + "call-me-maybe@npm:^1.0.1": version: 1.0.1 resolution: "call-me-maybe@npm:1.0.1" @@ -16849,7 +16290,7 @@ __metadata: languageName: node linkType: hard -"camel-case@npm:^4.1.1, camel-case@npm:^4.1.2": +"camel-case@npm:^4.1.2": version: 4.1.2 resolution: "camel-case@npm:4.1.2" dependencies: @@ -16859,23 +16300,13 @@ __metadata: languageName: node linkType: hard -"camelcase-css@npm:2.0.1, camelcase-css@npm:^2.0.1": +"camelcase-css@npm:^2.0.1": version: 2.0.1 resolution: "camelcase-css@npm:2.0.1" checksum: 1cec2b3b3dcb5026688a470b00299a8db7d904c4802845c353dbd12d9d248d3346949a814d83bfd988d4d2e5b9904c07efe76fecd195a1d4f05b543e7c0b56b1 languageName: node linkType: hard -"camelcase-keys@npm:^2.0.0": - version: 2.1.0 - resolution: "camelcase-keys@npm:2.1.0" - dependencies: - camelcase: ^2.0.0 - map-obj: ^1.0.0 - checksum: 97d2993da5db44d45e285910c70a54ce7f83a2be05afceaafd9831f7aeaf38a48dcdede5ca3aae2b2694852281d38dc459706e346942c5df0bf755f4133f5c39 - languageName: node - linkType: hard - "camelcase-keys@npm:^6.2.2": version: 6.2.2 resolution: "camelcase-keys@npm:6.2.2" @@ -16887,20 +16318,13 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:6, camelcase@npm:^6.2.0": +"camelcase@npm:6": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d languageName: node linkType: hard -"camelcase@npm:^2.0.0": - version: 2.1.1 - resolution: "camelcase@npm:2.1.1" - checksum: 20a3ef08f348de832631d605362ffe447d883ada89617144a82649363ed5860923b021f8e09681624ef774afb93ff3597cfbcf8aaf0574f65af7648f1aea5e50 - languageName: node - linkType: hard - "camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" @@ -16915,7 +16339,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001359": +"caniuse-lite@npm:^1.0.30001359": version: 1.0.30001365 resolution: "caniuse-lite@npm:1.0.30001365" checksum: 5d043006e9bd9de1ae06c0e12c31997f0ed26f889f47ea6403dc2d08f46a5bd4bf0fe1a5b1099561fc447201ddf13083f277de68829e77fd238ff2af8c05e0a6 @@ -16943,6 +16367,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001565": + version: 1.0.30001566 + resolution: "caniuse-lite@npm:1.0.30001566" + checksum: 0f9084bf9f7d5c0a9ddb200c2baddb25dd2ad5a2f205f01e7b971f3e98e9a7bb23c2d86bae48237e9bc9782b682cffaaf3406d936937ab9844987dbe2a6401f2 + languageName: node + linkType: hard + "capital-case@npm:^1.0.4": version: 1.0.4 resolution: "capital-case@npm:1.0.4" @@ -16954,15 +16385,6 @@ __metadata: languageName: node linkType: hard -"capture-exit@npm:^2.0.0": - version: 2.0.0 - resolution: "capture-exit@npm:2.0.0" - dependencies: - rsvp: ^4.8.4 - checksum: 0b9f10daca09e521da9599f34c8e7af14ad879c336e2bdeb19955b375398ae1c5bcc91ac9f2429944343057ee9ed028b1b2fb28816c384e0e55d70c439b226f4 - languageName: node - linkType: hard - "cardinal@npm:^2.1.1": version: 2.1.1 resolution: "cardinal@npm:2.1.1" @@ -16975,7 +16397,7 @@ __metadata: languageName: node linkType: hard -"case-sensitive-paths-webpack-plugin@npm:^2.3.0": +"case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" checksum: bcf469446eeee9ac0046e30860074ebb9aa4803aab9140e6bb72b600b23b1d70635690754be4504ce35cd99cdf05226bee8d894ba362a3f5485d5f6310fc6d02 @@ -16989,13 +16411,6 @@ __metadata: languageName: node linkType: hard -"ccount@npm:^1.0.0": - version: 1.1.0 - resolution: "ccount@npm:1.1.0" - checksum: b335a79d0aa4308919cf7507babcfa04ac63d389ebed49dbf26990d4607c8a4713cde93cc83e707d84571ddfe1e7615dad248be9bc422ae4c188210f71b08b78 - languageName: node - linkType: hard - "ccount@npm:^2.0.0": version: 2.0.1 resolution: "ccount@npm:2.0.1" @@ -17003,18 +16418,18 @@ __metadata: languageName: node linkType: hard -"chai@npm:^4.3.7": - version: 4.3.7 - resolution: "chai@npm:4.3.7" +"chai@npm:^4.3.10": + version: 4.3.10 + resolution: "chai@npm:4.3.10" dependencies: assertion-error: ^1.1.0 - check-error: ^1.0.2 - deep-eql: ^4.1.2 - get-func-name: ^2.0.0 - loupe: ^2.3.1 + check-error: ^1.0.3 + deep-eql: ^4.1.3 + get-func-name: ^2.0.2 + loupe: ^2.3.6 pathval: ^1.1.1 - type-detect: ^4.0.5 - checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c + type-detect: ^4.0.8 + checksum: 536668c60a0d985a0fbd94418028e388d243a925d7c5e858c7443e334753511614a3b6a124bac9ca077dfc4c37acc367d62f8c294960f440749536dc181dfc6d languageName: node linkType: hard @@ -17176,6 +16591,13 @@ __metadata: languageName: node linkType: hard +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 98d3b1a52ae510b7329e6ee7f6210df14f1e318c5415975d4c9e7ee0ef4c07875d47c6e74230c64551f12f556b4a8ccc24d9f3691a2aa197019e72a95e9297ee + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -17197,10 +16619,12 @@ __metadata: languageName: node linkType: hard -"check-error@npm:^1.0.2": - version: 1.0.2 - resolution: "check-error@npm:1.0.2" - checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: ^2.0.2 + checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 languageName: node linkType: hard @@ -17261,30 +16685,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^2.1.8": - version: 2.1.8 - resolution: "chokidar@npm:2.1.8" - dependencies: - anymatch: ^2.0.0 - async-each: ^1.0.1 - braces: ^2.3.2 - fsevents: ^1.2.7 - glob-parent: ^3.1.0 - inherits: ^2.0.3 - is-binary-path: ^1.0.0 - is-glob: ^4.0.0 - normalize-path: ^3.0.0 - path-is-absolute: ^1.0.0 - readdirp: ^2.2.1 - upath: ^1.1.1 - dependenciesMeta: - fsevents: - optional: true - checksum: 0c43e89cbf0268ef1e1f41ce8ec5233c7ba022c6f3282c2ef6530e351d42396d389a1148c5a040f291cf1f4083a4c6b2f51dad3f31c726442ea9a337de316bcf - languageName: node - linkType: hard - -"chokidar@npm:^3.4.1, chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": +"chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -17364,18 +16765,6 @@ __metadata: languageName: node linkType: hard -"class-utils@npm:^0.3.5": - version: 0.3.6 - resolution: "class-utils@npm:0.3.6" - dependencies: - arr-union: ^3.1.0 - define-property: ^0.2.5 - isobject: ^3.0.0 - static-extend: ^0.1.1 - checksum: be108900801e639e50f96a7e4bfa8867c753a7750a7603879f3981f8b0a89cba657497a2d5f40cd4ea557ff15d535a100818bb486baf6e26fe5d7872e75f1078 - languageName: node - linkType: hard - "class-variance-authority@npm:^0.4.0": version: 0.4.0 resolution: "class-variance-authority@npm:0.4.0" @@ -17402,15 +16791,6 @@ __metadata: languageName: node linkType: hard -"clean-css@npm:^4.2.3": - version: 4.2.4 - resolution: "clean-css@npm:4.2.4" - dependencies: - source-map: ~0.6.0 - checksum: 045ff6fcf4b5c76a084b24e1633e0c78a13b24080338fc8544565a9751559aa32ff4ee5886d9e52c18a644a6ff119bd8e37bc58e574377c05382a1fb7dbe39f8 - languageName: node - linkType: hard - "clean-css@npm:^5.2.2": version: 5.3.1 resolution: "clean-css@npm:5.3.1" @@ -17446,7 +16826,7 @@ __metadata: languageName: node linkType: hard -"cli-boxes@npm:^2.2.0, cli-boxes@npm:^2.2.1": +"cli-boxes@npm:^2.2.0": version: 2.2.1 resolution: "cli-boxes@npm:2.2.1" checksum: be79f8ec23a558b49e01311b39a1ea01243ecee30539c880cf14bf518a12e223ef40c57ead0cb44f509bffdffc5c129c746cd50d863ab879385370112af4f585 @@ -17586,11 +16966,11 @@ __metadata: linkType: hard "clone-response@npm:^1.0.2": - version: 1.0.2 - resolution: "clone-response@npm:1.0.2" + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" dependencies: mimic-response: ^1.0.0 - checksum: 2d0e61547fc66276e0903be9654ada422515f5a15741691352000d47e8c00c226061221074ce2c0064d12e975e84a8687cfd35d8b405750cb4e772f87b256eda + checksum: 4e671cac39b11c60aa8ba0a450657194a5d6504df51bca3fac5b3bd0145c4f8e8464898f87c8406b83232e3bc5cca555f51c1f9c8ac023969ebfbf7f6bdabb2e languageName: node linkType: hard @@ -17615,20 +16995,6 @@ __metadata: languageName: node linkType: hard -"clsx@npm:1.1.0": - version: 1.1.0 - resolution: "clsx@npm:1.1.0" - checksum: 50e889839a557b8a2fca063ee7ea22ba8c261e7f9f7aadc257065fc77f16fa0a98ce826fb2b126d05fb736560333971dbb882874054df7bb8f4317e224ec1978 - languageName: node - linkType: hard - -"clsx@npm:^1.0.4, clsx@npm:^1.2.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 - languageName: node - linkType: hard - "clsx@npm:^1.1.1": version: 1.1.1 resolution: "clsx@npm:1.1.1" @@ -17636,6 +17002,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^1.2.1": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 + languageName: node + linkType: hard + "cluster-key-slot@npm:1.1.2": version: 1.1.2 resolution: "cluster-key-slot@npm:1.1.2" @@ -17709,23 +17082,6 @@ __metadata: languageName: node linkType: hard -"collapse-white-space@npm:^1.0.2": - version: 1.0.6 - resolution: "collapse-white-space@npm:1.0.6" - checksum: 9673fb797952c5c888341435596c69388b22cd5560c8cd3f40edb72734a9c820f56a7c9525166bcb7068b5d5805372e6fd0c4b9f2869782ad070cb5d3faf26e7 - languageName: node - linkType: hard - -"collection-visit@npm:^1.0.0": - version: 1.0.0 - resolution: "collection-visit@npm:1.0.0" - dependencies: - map-visit: ^1.0.0 - object-visit: ^1.0.0 - checksum: 15d9658fe6eb23594728346adad5433b86bb7a04fd51bbab337755158722f9313a5376ef479de5b35fbc54140764d0d39de89c339f5d25b959ed221466981da9 - languageName: node - linkType: hard - "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -17751,14 +17107,24 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-support@npm:^1.1.2, color-support@npm:^1.1.3": +"color-string@npm:^1.9.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: ^1.0.0 + simple-swizzle: ^0.2.2 + checksum: c13fe7cff7885f603f49105827d621ce87f4571d78ba28ef4a3f1a104304748f620615e6bf065ecd2145d0d9dad83a3553f52bb25ede7239d18e9f81622f1cc5 + languageName: node + linkType: hard + +"color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -17767,10 +17133,20 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^1.2.2": - version: 1.4.0 - resolution: "colorette@npm:1.4.0" - checksum: 01c3c16058b182a4ab4c126a65a75faa4d38a20fa7c845090b25453acec6c371bb2c5dceb0a2338511f17902b9d1a9af0cadd8509c9403894b79311032c256c3 +"color@npm:^4.2.3": + version: 4.2.3 + resolution: "color@npm:4.2.3" + dependencies: + color-convert: ^2.0.1 + color-string: ^1.9.0 + checksum: 0579629c02c631b426780038da929cca8e8d80a40158b09811a0112a107c62e10e4aad719843b791b1e658ab4e800558f2e87ca4522c8b32349d497ecb6adeb4 + languageName: node + linkType: hard + +"colorette@npm:^2.0.10, colorette@npm:^2.0.20": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d languageName: node linkType: hard @@ -17832,14 +17208,14 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.7.1, commander@npm:^2.9.0": +"commander@npm:^2.20.0, commander@npm:^2.7.1, commander@npm:^2.9.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e languageName: node linkType: hard -"commander@npm:^4.0.0, commander@npm:^4.1.1": +"commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" checksum: d7b9913ff92cae20cb577a4ac6fcc121bd6223319e54a40f51a14740a681ad5c574fd29a57da478a5f234a6fa6c52cbf0b7c641353e03c648b1ae85ba670b977 @@ -17919,23 +17295,13 @@ __metadata: languageName: node linkType: hard -"component-emitter@npm:^1.2.1, component-emitter@npm:^1.3.0": +"component-emitter@npm:^1.3.0": version: 1.3.0 resolution: "component-emitter@npm:1.3.0" checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b languageName: node linkType: hard -"compress-brotli@npm:^1.3.8": - version: 1.3.8 - resolution: "compress-brotli@npm:1.3.8" - dependencies: - "@types/json-buffer": ~3.0.0 - json-buffer: ~3.0.1 - checksum: de7589d692d40eb362f6c91070b5e51bc10b05a89eabb4a7c76c1aa21b625756f8c101c6999e4df0c4dc6199c5ca2e1353573bfdcca5615810f27485394162a5 - languageName: node - linkType: hard - "compress-commons@npm:^4.1.2": version: 4.1.2 resolution: "compress-commons@npm:4.1.2" @@ -17986,7 +17352,7 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.4.4, concat-stream@npm:^1.5.0": +"concat-stream@npm:^1.4.4, concat-stream@npm:^1.6.2": version: 1.6.2 resolution: "concat-stream@npm:1.6.2" dependencies: @@ -18048,14 +17414,14 @@ __metadata: languageName: node linkType: hard -"console-browserify@npm:^1.1.0": +"console-browserify@npm:^1.2.0": version: 1.2.0 resolution: "console-browserify@npm:1.2.0" checksum: 226591eeff8ed68e451dffb924c1fb750c654d54b9059b3b261d360f369d1f8f70650adecf2c7136656236a4bfeb55c39281b5d8a55d792ebbb99efd3d848d52 languageName: node linkType: hard -"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed @@ -18113,7 +17479,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" dependencies: @@ -18173,27 +17539,6 @@ __metadata: languageName: node linkType: hard -"copy-concurrently@npm:^1.0.0": - version: 1.0.5 - resolution: "copy-concurrently@npm:1.0.5" - dependencies: - aproba: ^1.1.1 - fs-write-stream-atomic: ^1.0.8 - iferr: ^0.1.5 - mkdirp: ^0.5.1 - rimraf: ^2.5.4 - run-queue: ^1.0.0 - checksum: 63c169f582e09445260988f697b2d07793d439dfc31e97c8999707bd188dd94d1c7f2ca3533c7786fb75f03a3f2f54ad1ee08055f95f61bb8d2e862498c1d460 - languageName: node - linkType: hard - -"copy-descriptor@npm:^0.1.0": - version: 0.1.1 - resolution: "copy-descriptor@npm:0.1.1" - checksum: d4b7b57b14f1d256bb9aa0b479241048afd7f5bcf22035fc7b94e8af757adeae247ea23c1a774fe44869fd5694efba4a969b88d966766c5245fdee59837fe45b - languageName: node - linkType: hard - "copy-to-clipboard@npm:^3": version: 3.3.1 resolution: "copy-to-clipboard@npm:3.3.1" @@ -18228,13 +17573,19 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.8.1": - version: 3.23.4 - resolution: "core-js-compat@npm:3.23.4" +"core-js-compat@npm:^3.33.1": + version: 3.34.0 + resolution: "core-js-compat@npm:3.34.0" dependencies: - browserslist: ^4.21.1 - semver: 7.0.0 - checksum: cf9d48496576ed297b00ff78ef64f6da01681fa810e3e3283034d097be9de4ff113151eb5da1f40212fc1dc882749156db9b311d8dbad289e0e9172d05cc83de + browserslist: ^4.22.2 + checksum: 6281f7f57a72f254c06611ec088445e11cf84e0b4edfb5f43dece1a1ff8b0ed0e81ed0bc291024761cd90c39d0f007d8bc46548265139808081d311c7cbc9c81 + languageName: node + linkType: hard + +"core-js-pure@npm:^3.23.3": + version: 3.34.0 + resolution: "core-js-pure@npm:3.34.0" + checksum: 4c44ac4beff42e07f41eef3c9ecefc8ee3f9e91e1b9f278bf8520cc1fb37afb663cff77c182541dc42d58737f93ab0f30a33a5fe661fb161fdd8aa7fe78a5edf languageName: node linkType: hard @@ -18245,13 +17596,6 @@ __metadata: languageName: node linkType: hard -"core-js-pure@npm:^3.8.1": - version: 3.23.4 - resolution: "core-js-pure@npm:3.23.4" - checksum: 54afc79508ded6c1b59aacdf32fc1621f0246b10401e6228e7b145fe3960335c8863580e6ce8560bb8004aa69ea2328a5baa11d2e15965b6333b8fd839657601 - languageName: node - linkType: hard - "core-js@npm:^3": version: 3.21.1 resolution: "core-js@npm:3.21.1" @@ -18259,13 +17603,6 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.0.4, core-js@npm:^3.6.5, core-js@npm:^3.8.2": - version: 3.23.4 - resolution: "core-js@npm:3.23.4" - checksum: 1317591dbd4a6dc357b68da324dfab52ffecc0193fe577c55bedc058af3ec96a47c7d68dff4dc914badf398ca120c0b58815fca3a162a497abf73166910d834c - languageName: node - linkType: hard - "core-util-is@npm:1.0.2": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" @@ -18293,20 +17630,20 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^7.0.0": - version: 7.0.1 - resolution: "cosmiconfig@npm:7.0.1" +"cosmiconfig@npm:^7.0.1": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" dependencies: "@types/parse-json": ^4.0.0 import-fresh: ^3.2.1 parse-json: ^5.0.0 path-type: ^4.0.0 yaml: ^1.10.0 - checksum: 4be63e7117955fd88333d7460e4c466a90f556df6ef34efd59034d2463484e339666c41f02b523d574a797ec61f4a91918c5b89a316db2ea2f834e0d2d09465b + checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f languageName: node linkType: hard -"cosmiconfig@npm:^8.1.0, cosmiconfig@npm:^8.1.3": +"cosmiconfig@npm:^8.1.0, cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -18330,35 +17667,6 @@ __metadata: languageName: node linkType: hard -"cp-file@npm:^7.0.0": - version: 7.0.0 - resolution: "cp-file@npm:7.0.0" - dependencies: - graceful-fs: ^4.1.2 - make-dir: ^3.0.0 - nested-error-stacks: ^2.0.0 - p-event: ^4.1.0 - checksum: dd60ed8d865d25a69548e15b21dd0d2fc66f10371e4970aa21b626a7578ebf419f44f386977ed3b3726c07401d4a64ee679cf1da566d8f66f01e9a359b85201f - languageName: node - linkType: hard - -"cpy@npm:^8.1.2": - version: 8.1.2 - resolution: "cpy@npm:8.1.2" - dependencies: - arrify: ^2.0.1 - cp-file: ^7.0.0 - globby: ^9.2.0 - has-glob: ^1.0.0 - junk: ^3.1.0 - nested-error-stacks: ^2.1.0 - p-all: ^2.1.0 - p-filter: ^2.1.0 - p-map: ^3.0.0 - checksum: e121f13f2b6af4a7c00de17984086a45b67eaaeeb0286a5cf67f2fdaf18d8ce6c2a9fe4ccfa37953e6982f55772f384f040f45f1961530655838c2b7486788a7 - languageName: node - linkType: hard - "crc-32@npm:^1.2.0": version: 1.2.1 resolution: "crc-32@npm:1.2.1" @@ -18465,6 +17773,15 @@ __metadata: languageName: node linkType: hard +"cross-inspect@npm:1.0.0": + version: 1.0.0 + resolution: "cross-inspect@npm:1.0.0" + dependencies: + tslib: ^2.4.0 + checksum: 975c81799549627027254eb70f1c349cefb14435d580bea6f351f510c839dcb1a9288983407bac2ad317e6eff29cf1e99299606da21f404562bfa64cec502239 + languageName: node + linkType: hard + "cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -18507,7 +17824,7 @@ __metadata: languageName: node linkType: hard -"crypto-browserify@npm:^3.11.0": +"crypto-browserify@npm:^3.11.0, crypto-browserify@npm:^3.12.0": version: 3.12.0 resolution: "crypto-browserify@npm:3.12.0" dependencies: @@ -18577,46 +17894,21 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:^3.6.0": - version: 3.6.0 - resolution: "css-loader@npm:3.6.0" - dependencies: - camelcase: ^5.3.1 - cssesc: ^3.0.0 - icss-utils: ^4.1.1 - loader-utils: ^1.2.3 - normalize-path: ^3.0.0 - postcss: ^7.0.32 - postcss-modules-extract-imports: ^2.0.0 - postcss-modules-local-by-default: ^3.0.2 - postcss-modules-scope: ^2.2.0 - postcss-modules-values: ^3.0.0 - postcss-value-parser: ^4.1.0 - schema-utils: ^2.7.0 - semver: ^6.3.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: a45d7ee8105eea7a76caa45286f4b31f9413520511ae99a78886c522305a94c8adf289951f989d239919a9ffc08ea8cac2bf9c362f21b65d6f54f6812e904cc0 - languageName: node - linkType: hard - -"css-loader@npm:^5.0.1": - version: 5.2.7 - resolution: "css-loader@npm:5.2.7" +"css-loader@npm:^6.7.1, css-loader@npm:^6.7.3": + version: 6.8.1 + resolution: "css-loader@npm:6.8.1" dependencies: icss-utils: ^5.1.0 - loader-utils: ^2.0.0 - postcss: ^8.2.15 + postcss: ^8.4.21 postcss-modules-extract-imports: ^3.0.0 - postcss-modules-local-by-default: ^4.0.0 + postcss-modules-local-by-default: ^4.0.3 postcss-modules-scope: ^3.0.0 postcss-modules-values: ^4.0.0 - postcss-value-parser: ^4.1.0 - schema-utils: ^3.0.0 - semver: ^7.3.5 + postcss-value-parser: ^4.2.0 + semver: ^7.3.8 peerDependencies: - webpack: ^4.27.0 || ^5.0.0 - checksum: fb0742b30ac0919f94b99a323bdefe6d48ae46d66c7d966aae59031350532f368f8bba5951fcd268f2e053c5e6e4655551076268e9073ccb58e453f98ae58f8e + webpack: ^5.0.0 + checksum: 7c1784247bdbe76dc5c55fb1ac84f1d4177a74c47259942c9cfdb7a8e6baef11967a0bc85ac285f26bd26d5059decb848af8154a03fdb4f4894f41212f45eef3 languageName: node linkType: hard @@ -18745,22 +18037,6 @@ __metadata: languageName: node linkType: hard -"currently-unhandled@npm:^0.4.1": - version: 0.4.1 - resolution: "currently-unhandled@npm:0.4.1" - dependencies: - array-find-index: ^1.0.1 - checksum: 1f59fe10b5339b54b1a1eee110022f663f3495cf7cf2f480686e89edc7fa8bfe42dbab4b54f85034bc8b092a76cc7becbc2dad4f9adad332ab5831bec39ad540 - languageName: node - linkType: hard - -"cyclist@npm:^1.0.1": - version: 1.0.1 - resolution: "cyclist@npm:1.0.1" - checksum: 3cc2fdeb358599ca0ea96f5ecf2fc530ccab7ed1f8aa1a894aebfacd2009281bd7380cb9b30db02a18cdd00b3ed1d7ce81a3b11fe56e33a6a0fe4424dc592fbe - languageName: node - linkType: hard - "d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": version: 3.2.3 resolution: "d3-array@npm:3.2.3" @@ -18980,7 +18256,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.0, debug@npm:^2.6.9": +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.6.0, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -19001,7 +18277,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.0.0, debug@npm:^3.2.7": +"debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -19020,7 +18296,7 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^1.1.0, decamelize@npm:^1.1.2, decamelize@npm:^1.2.0": +"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa @@ -19050,13 +18326,6 @@ __metadata: languageName: node linkType: hard -"decode-uri-component@npm:^0.2.0": - version: 0.2.0 - resolution: "decode-uri-component@npm:0.2.0" - checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94 - languageName: node - linkType: hard - "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -19073,7 +18342,7 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^4.1.2": +"deep-eql@npm:^4.1.3": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" dependencies: @@ -19082,14 +18351,40 @@ __metadata: languageName: node linkType: hard -"deep-extend@npm:0.6.0": +"deep-equal@npm:^2.0.5": + version: 2.2.3 + resolution: "deep-equal@npm:2.2.3" + dependencies: + array-buffer-byte-length: ^1.0.0 + call-bind: ^1.0.5 + es-get-iterator: ^1.1.3 + get-intrinsic: ^1.2.2 + is-arguments: ^1.1.1 + is-array-buffer: ^3.0.2 + is-date-object: ^1.0.5 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + isarray: ^2.0.5 + object-is: ^1.1.5 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.5.1 + side-channel: ^1.0.4 + which-boxed-primitive: ^1.0.2 + which-collection: ^1.0.1 + which-typed-array: ^1.1.13 + checksum: ee8852f23e4d20a5626c13b02f415ba443a1b30b4b3d39eaf366d59c4a85e6545d7ec917db44d476a85ae5a86064f7e5f7af7479f38f113995ba869f3a1ddc53 + languageName: node + linkType: hard + +"deep-extend@npm:0.6.0, deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 languageName: node linkType: hard -"deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": +"deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" checksum: edb65dd0d7d1b9c40b2f50219aef30e116cedd6fc79290e740972c132c09106d2e80aa0bc8826673dd5a00222d4179c84b36a790eef63a4c4bca75a37ef90804 @@ -19117,16 +18412,13 @@ __metadata: languageName: node linkType: hard -"default-browser-id@npm:^1.0.4": - version: 1.0.4 - resolution: "default-browser-id@npm:1.0.4" +"default-browser-id@npm:3.0.0": + version: 3.0.0 + resolution: "default-browser-id@npm:3.0.0" dependencies: - bplist-parser: ^0.1.0 - meow: ^3.1.0 - untildify: ^2.0.0 - bin: - default-browser-id: cli.js - checksum: c6576428ebdd304d209e09c40803c974de3236232fdfa564d82bd1e985246a0d0f0b344f2b207fcbf663b925c20d30ab4d77fbe2755d2be3a6073f12620b9056 + bplist-parser: ^0.2.0 + untildify: ^4.0.0 + checksum: 279c7ad492542e5556336b6c254a4eaf31b2c63a5433265655ae6e47301197b6cfb15c595a6fdc6463b2ff8e1a1a1ed3cba56038a60e1527ba4ab1628c6b9941 languageName: node linkType: hard @@ -19155,6 +18447,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.1": + version: 1.1.1 + resolution: "define-data-property@npm:1.1.1" + dependencies: + get-intrinsic: ^1.2.1 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.0 + checksum: a29855ad3f0630ea82e3c5012c812efa6ca3078d5c2aa8df06b5f597c1cde6f7254692df41945851d903e05a1668607b6d34e778f402b9ff9ffb38111f1a3f0d + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -19162,16 +18465,6 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.2, define-properties@npm:^1.1.4": - version: 1.1.4 - resolution: "define-properties@npm:1.1.4" - dependencies: - has-property-descriptors: ^1.0.0 - object-keys: ^1.1.1 - checksum: ce0aef3f9eb193562b5cfb79b2d2c86b6a109dfc9fdcb5f45d680631a1a908c06824ddcdb72b7573b54e26ace07f0a23420aaba0d5c627b34d2c1de8ef527e2b - languageName: node - linkType: hard - "define-properties@npm:^1.1.3": version: 1.1.3 resolution: "define-properties@npm:1.1.3" @@ -19181,31 +18474,31 @@ __metadata: languageName: node linkType: hard -"define-property@npm:^0.2.5": - version: 0.2.5 - resolution: "define-property@npm:0.2.5" +"define-properties@npm:^1.1.4": + version: 1.1.4 + resolution: "define-properties@npm:1.1.4" dependencies: - is-descriptor: ^0.1.0 - checksum: 85af107072b04973b13f9e4128ab74ddfda48ec7ad2e54b193c0ffb57067c4ce5b7786a7b4ae1f24bd03e87c5d18766b094571810b314d7540f86d4354dbd394 + has-property-descriptors: ^1.0.0 + object-keys: ^1.1.1 + checksum: ce0aef3f9eb193562b5cfb79b2d2c86b6a109dfc9fdcb5f45d680631a1a908c06824ddcdb72b7573b54e26ace07f0a23420aaba0d5c627b34d2c1de8ef527e2b languageName: node linkType: hard -"define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "define-property@npm:1.0.0" +"define-properties@npm:^1.2.0": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" dependencies: - is-descriptor: ^1.0.0 - checksum: 5fbed11dace44dd22914035ba9ae83ad06008532ca814d7936a53a09e897838acdad5b108dd0688cc8d2a7cf0681acbe00ee4136cf36743f680d10517379350a + define-data-property: ^1.0.1 + has-property-descriptors: ^1.0.0 + object-keys: ^1.1.1 + checksum: b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12 languageName: node linkType: hard -"define-property@npm:^2.0.2": - version: 2.0.2 - resolution: "define-property@npm:2.0.2" - dependencies: - is-descriptor: ^1.0.2 - isobject: ^3.0.1 - checksum: 3217ed53fc9eed06ba8da6f4d33e28c68a82e2f2a8ab4d562c4920d8169a166fe7271453675e6c69301466f36a65d7f47edf0cf7f474b9aa52a5ead9c1b13c99 +"defu@npm:^6.1.2": + version: 6.1.3 + resolution: "defu@npm:6.1.3" + checksum: c857a0cf854632e8528dad36454fd1c812bff8f5f091d5a6892e75d7f6b76d8319afbbfb8c504daab84ac86e40037ff37c544d50f89ed5b5419ba1989a226777 languageName: node linkType: hard @@ -19291,15 +18584,6 @@ __metadata: languageName: node linkType: hard -"detab@npm:2.0.4": - version: 2.0.4 - resolution: "detab@npm:2.0.4" - dependencies: - repeat-string: ^1.5.4 - checksum: 34b077521ecd4c6357d32ff7923be644d34aa6f6b7d717d40ec4a9168243eefaea2b512a75a460a6f70c31b0bbc31ff90f820a891803b4ddaf99e9d04d0d389d - languageName: node - linkType: hard - "detect-element-overflow@npm:^1.2.0": version: 1.2.0 resolution: "detect-element-overflow@npm:1.2.0" @@ -19307,13 +18591,20 @@ __metadata: languageName: node linkType: hard -"detect-indent@npm:^6.0.0": +"detect-indent@npm:^6.0.0, detect-indent@npm:^6.1.0": version: 6.1.0 resolution: "detect-indent@npm:6.1.0" checksum: ab953a73c72dbd4e8fc68e4ed4bfd92c97eb6c43734af3900add963fd3a9316f3bc0578b018b24198d4c31a358571eff5f0656e81a1f3b9ad5c547d58b2d093d languageName: node linkType: hard +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.2": + version: 2.0.2 + resolution: "detect-libc@npm:2.0.2" + checksum: 2b2cd3649b83d576f4be7cc37eb3b1815c79969c8b1a03a40a4d55d83bc74d010753485753448eacb98784abf22f7dbd3911fd3b60e29fda28fed2d1a997944d + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -19343,6 +18634,15 @@ __metadata: languageName: node linkType: hard +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -19399,15 +18699,6 @@ __metadata: languageName: node linkType: hard -"dir-glob@npm:^2.2.2": - version: 2.2.2 - resolution: "dir-glob@npm:2.2.2" - dependencies: - path-type: ^3.0.0 - checksum: 3aa48714a9f7845ffc30ab03a5c674fe760477cc55e67b0847333371549227d93953e6627ec160f75140c5bea5c5f88d13c01de79bd1997a588efbcf06980842 - languageName: node - linkType: hard - "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -19513,10 +18804,10 @@ __metadata: languageName: node linkType: hard -"domain-browser@npm:^1.1.1": - version: 1.2.0 - resolution: "domain-browser@npm:1.2.0" - checksum: 8f1235c7f49326fb762f4675795246a6295e7dd566b4697abec24afdba2460daa7dfbd1a73d31efbf5606b3b7deadb06ce47cf06f0a476e706153d62a4ff2b90 +"domain-browser@npm:^4.22.0": + version: 4.23.0 + resolution: "domain-browser@npm:4.23.0" + checksum: 95b772f5fa88300240694380e06e03868573acdf86ca392a58c78602d6536dca2097ad2469a1500bd23a1329b09992de846e0b66c364cbf5711a7fee3ee5dac9 languageName: node linkType: hard @@ -19536,15 +18827,6 @@ __metadata: languageName: node linkType: hard -"domhandler@npm:^3.0.0": - version: 3.3.0 - resolution: "domhandler@npm:3.3.0" - dependencies: - domelementtype: ^2.0.1 - checksum: 850e5e9fee7834ab4314811e18bc1f4294d7eafbf6a79ad03cbe50cf964108935c97257ac248944d72a9312b4a18dfa8323e857d23278964dc83b1f124467fa3 - languageName: node - linkType: hard - "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": version: 4.3.1 resolution: "domhandler@npm:4.3.1" @@ -19577,7 +18859,7 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^2.0.0, domutils@npm:^2.5.2, domutils@npm:^2.8.0": +"domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" dependencies: @@ -19657,10 +18939,10 @@ __metadata: languageName: node linkType: hard -"dotenv-expand@npm:^5.1.0": - version: 5.1.0 - resolution: "dotenv-expand@npm:5.1.0" - checksum: 8017675b7f254384915d55f9eb6388e577cf0a1231a28d54b0ca03b782be9501b0ac90ac57338636d395fa59051e6209e9b44b8ddf169ce6076dffb5dea227d3 +"dotenv-expand@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv-expand@npm:10.0.0" + checksum: 2a38b470efe0abcb1ac8490421a55e1d764dc9440fd220942bce40965074f3fb00b585f4346020cb0f0f219966ee6b4ee5023458b3e2953fe5b3214de1b314ee languageName: node linkType: hard @@ -19699,13 +18981,6 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^8.0.0": - version: 8.6.0 - resolution: "dotenv@npm:8.6.0" - checksum: 38e902c80b0666ab59e9310a3d24ed237029a7ce34d976796349765ac96b8d769f6df19090f1f471b77a25ca391971efde8a1ea63bb83111bd8bec8e5cc9b2cd - languageName: node - linkType: hard - "downshift@npm:^6.1.9": version: 6.1.9 resolution: "downshift@npm:6.1.9" @@ -19729,9 +19004,9 @@ __metadata: linkType: hard "dset@npm:^3.1.2": - version: 3.1.2 - resolution: "dset@npm:3.1.2" - checksum: 4f8066f517aa0a70af688c66e9a0a5590f0aada76f6edc7ba9ddb309e27d3a6d65c0a2e31ab2a84005d4c791e5327773cdde59b8ab169050330a0dc283663e87 + version: 3.1.3 + resolution: "dset@npm:3.1.3" + checksum: 5db964a36c60c51aa3f7088bfe1dc5c0eedd9a6ef3b216935bb70ef4a7b8fc40fd2f9bb16b9a4692c9c9772cea60cfefb108d2d09fbd53c85ea8f6cd54502d6a languageName: node linkType: hard @@ -19742,7 +19017,7 @@ __metadata: languageName: node linkType: hard -"duplexify@npm:^3.4.2, duplexify@npm:^3.6.0": +"duplexify@npm:^3.5.0, duplexify@npm:^3.6.0": version: 3.7.1 resolution: "duplexify@npm:3.7.1" dependencies: @@ -19838,6 +19113,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.601": + version: 1.4.605 + resolution: "electron-to-chromium@npm:1.4.605" + checksum: 2d42afd5d8cfc053d68944891f02fb007931fae85c0a19bc4f458bb3430e793b8a76664b058e1b06ab99e784f73e4c6b56493eaede2ff6012dbcaf8781fec4a7 + languageName: node + linkType: hard + "elliptic@npm:^6.5.3": version: 6.5.4 resolution: "elliptic@npm:6.5.4" @@ -19940,18 +19222,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^4.5.0": - version: 4.5.0 - resolution: "enhanced-resolve@npm:4.5.0" - dependencies: - graceful-fs: ^4.1.2 - memory-fs: ^0.5.0 - tapable: ^1.0.0 - checksum: 4d87488584c4d67d356ef4ba04978af4b2d4d18190cb859efac8e8475a34d5d6c069df33faa5a0a22920b0586dbf330f6a08d52bb15a8771a9ce4d70a2da74ba - languageName: node - linkType: hard - -"enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.9.3": +"enhanced-resolve@npm:^5.10.0": version: 5.10.0 resolution: "enhanced-resolve@npm:5.10.0" dependencies: @@ -19961,6 +19232,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.15.0": + version: 5.15.0 + resolution: "enhanced-resolve@npm:5.15.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.7.0": version: 5.9.2 resolution: "enhanced-resolve@npm:5.9.2" @@ -19971,7 +19252,7 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.0, enquirer@npm:^2.3.5": +"enquirer@npm:^2.3.0": version: 2.3.6 resolution: "enquirer@npm:2.3.6" dependencies: @@ -20030,6 +19311,15 @@ __metadata: languageName: node linkType: hard +"envinfo@npm:^7.7.3": + version: 7.11.0 + resolution: "envinfo@npm:7.11.0" + bin: + envinfo: dist/cli.js + checksum: c45a7d20409d5f4cda72483b150d3816b15b434f2944d72c1495d8838bd7c4e7b2f32c12128ffb9b92b5f66f436237b8a525eb3a9a5da2d20013bc4effa28aef + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -20037,7 +19327,7 @@ __metadata: languageName: node linkType: hard -"errno@npm:^0.1.1, errno@npm:^0.1.3, errno@npm:~0.1.1, errno@npm:~0.1.7": +"errno@npm:^0.1.1, errno@npm:~0.1.1": version: 0.1.8 resolution: "errno@npm:0.1.8" dependencies: @@ -20048,7 +19338,7 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.2.0, error-ex@npm:^1.3.1": +"error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" dependencies: @@ -20094,7 +19384,7 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.1": +"es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5": version: 1.20.1 resolution: "es-abstract@npm:1.20.1" dependencies: @@ -20172,33 +19462,27 @@ __metadata: languageName: node linkType: hard -"es-array-method-boxes-properly@npm:^1.0.0": - version: 1.0.0 - resolution: "es-array-method-boxes-properly@npm:1.0.0" - checksum: 2537fcd1cecf187083890bc6f5236d3a26bf39237433587e5bf63392e88faae929dbba78ff0120681a3f6f81c23fe3816122982c160d63b38c95c830b633b826 - languageName: node - linkType: hard - -"es-get-iterator@npm:^1.0.2": - version: 1.1.2 - resolution: "es-get-iterator@npm:1.1.2" +"es-get-iterator@npm:^1.1.3": + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" dependencies: call-bind: ^1.0.2 - get-intrinsic: ^1.1.0 - has-symbols: ^1.0.1 - is-arguments: ^1.1.0 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 is-map: ^2.0.2 is-set: ^2.0.2 - is-string: ^1.0.5 + is-string: ^1.0.7 isarray: ^2.0.5 - checksum: f75e66acb6a45686fa08b3ade9c9421a70d36a0c43ed4363e67f4d7aab2226cb73dd977cb48abbaf75721b946d3cd810682fcf310c7ad0867802fbf929b17dcf + stop-iteration-iterator: ^1.0.0 + checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d languageName: node linkType: hard -"es-module-lexer@npm:^0.9.0, es-module-lexer@npm:^0.9.3": - version: 0.9.3 - resolution: "es-module-lexer@npm:0.9.3" - checksum: 84bbab23c396281db2c906c766af58b1ae2a1a2599844a504df10b9e8dc77ec800b3211fdaa133ff700f5703d791198807bba25d9667392d27a5e9feda344da8 +"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.4.1": + version: 1.4.1 + resolution: "es-module-lexer@npm:1.4.1" + checksum: a11b5a256d4e8e9c7d94c2fd87415ccd1591617b6edd847e064503f8eaece2d25e2e9078a02c5ce3ed5e83bb748f5b4820efbe78072c8beb07ac619c2edec35d languageName: node linkType: hard @@ -20222,13 +19506,6 @@ __metadata: languageName: node linkType: hard -"es5-shim@npm:^4.5.13": - version: 4.6.7 - resolution: "es5-shim@npm:4.6.7" - checksum: f2f60cf3d9c682106c51a70d27d41273d2edb3b90fa8795a2765be4a214574b71ddf9147a7972eb82998d94f96ca015d29f5915efd3af0a6c09673abd4299ee8 - languageName: node - linkType: hard - "es6-promise@npm:^4.2.4": version: 4.2.8 resolution: "es6-promise@npm:4.2.8" @@ -20236,10 +19513,21 @@ __metadata: languageName: node linkType: hard -"es6-shim@npm:^0.35.5": - version: 0.35.6 - resolution: "es6-shim@npm:0.35.6" - checksum: 31b27a7ce0432dd97c523da97e43dbcbf607093ac139697ac2e70d7ab67a90e9c362477a85f36961ebb0d09d0ffdaace45f5c9807f788849b28cc6a847e68c53 +"esbuild-plugin-alias@npm:^0.2.1": + version: 0.2.1 + resolution: "esbuild-plugin-alias@npm:0.2.1" + checksum: afe2d2c8b5f09d5321cb8d9c0825e8a9f6e03c2d50df92f953a291d4620cc29eddb3da9e33b238f6d8f77738e0277bdcb831f127399449fecf78fb84c04e5da9 + languageName: node + linkType: hard + +"esbuild-register@npm:^3.5.0": + version: 3.5.0 + resolution: "esbuild-register@npm:3.5.0" + dependencies: + debug: ^4.3.4 + peerDependencies: + esbuild: ">=0.12 <1" + checksum: f4307753c9672a2c901d04a1165031594a854f0a4c6f4c1db08aa393b68a193d38f2df483dc8ca0513e89f7b8998415e7e26fb9830989fb8cdccc5fb5f181c6b languageName: node linkType: hard @@ -20320,6 +19608,160 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.18.0": + version: 0.18.20 + resolution: "esbuild@npm:0.18.20" + dependencies: + "@esbuild/android-arm": 0.18.20 + "@esbuild/android-arm64": 0.18.20 + "@esbuild/android-x64": 0.18.20 + "@esbuild/darwin-arm64": 0.18.20 + "@esbuild/darwin-x64": 0.18.20 + "@esbuild/freebsd-arm64": 0.18.20 + "@esbuild/freebsd-x64": 0.18.20 + "@esbuild/linux-arm": 0.18.20 + "@esbuild/linux-arm64": 0.18.20 + "@esbuild/linux-ia32": 0.18.20 + "@esbuild/linux-loong64": 0.18.20 + "@esbuild/linux-mips64el": 0.18.20 + "@esbuild/linux-ppc64": 0.18.20 + "@esbuild/linux-riscv64": 0.18.20 + "@esbuild/linux-s390x": 0.18.20 + "@esbuild/linux-x64": 0.18.20 + "@esbuild/netbsd-x64": 0.18.20 + "@esbuild/openbsd-x64": 0.18.20 + "@esbuild/sunos-x64": 0.18.20 + "@esbuild/win32-arm64": 0.18.20 + "@esbuild/win32-ia32": 0.18.20 + "@esbuild/win32-x64": 0.18.20 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 5d253614e50cdb6ec22095afd0c414f15688e7278a7eb4f3720a6dd1306b0909cf431e7b9437a90d065a31b1c57be60130f63fe3e8d0083b588571f31ee6ec7b + languageName: node + linkType: hard + +"esbuild@npm:^0.19.3": + version: 0.19.8 + resolution: "esbuild@npm:0.19.8" + dependencies: + "@esbuild/android-arm": 0.19.8 + "@esbuild/android-arm64": 0.19.8 + "@esbuild/android-x64": 0.19.8 + "@esbuild/darwin-arm64": 0.19.8 + "@esbuild/darwin-x64": 0.19.8 + "@esbuild/freebsd-arm64": 0.19.8 + "@esbuild/freebsd-x64": 0.19.8 + "@esbuild/linux-arm": 0.19.8 + "@esbuild/linux-arm64": 0.19.8 + "@esbuild/linux-ia32": 0.19.8 + "@esbuild/linux-loong64": 0.19.8 + "@esbuild/linux-mips64el": 0.19.8 + "@esbuild/linux-ppc64": 0.19.8 + "@esbuild/linux-riscv64": 0.19.8 + "@esbuild/linux-s390x": 0.19.8 + "@esbuild/linux-x64": 0.19.8 + "@esbuild/netbsd-x64": 0.19.8 + "@esbuild/openbsd-x64": 0.19.8 + "@esbuild/sunos-x64": 0.19.8 + "@esbuild/win32-arm64": 0.19.8 + "@esbuild/win32-ia32": 0.19.8 + "@esbuild/win32-x64": 0.19.8 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 1dff99482ecbfcc642ec66c71e4dc5c73ce6aef68e8158a4937890b570e86a95959ac47e0f14785ba70df5a673ae4289df88a162e9759b02367ed28074cee8ba + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -20355,14 +19797,13 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^2.0.0": - version: 2.0.0 - resolution: "escodegen@npm:2.0.0" +"escodegen@npm:^2.1.0": + version: 2.1.0 + resolution: "escodegen@npm:2.1.0" dependencies: esprima: ^4.0.1 estraverse: ^5.2.0 esutils: ^2.0.2 - optionator: ^0.8.1 source-map: ~0.6.1 dependenciesMeta: source-map: @@ -20370,7 +19811,7 @@ __metadata: bin: escodegen: bin/escodegen.js esgenerate: bin/esgenerate.js - checksum: 5aa6b2966fafe0545e4e77936300cc94ad57cfe4dc4ebff9950492eaba83eef634503f12d7e3cbd644ecc1bab388ad0e92b06fd32222c9281a75d1cf02ec6cef + checksum: 096696407e161305cd05aebb95134ad176708bc5cb13d0dcc89a5fcbb959b8ed757e7f2591a5f8036f8f4952d4a724de0df14cd419e29212729fa6df5ce16bf6 languageName: node linkType: hard @@ -20647,17 +20088,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^4.0.3": - version: 4.0.3 - resolution: "eslint-scope@npm:4.0.3" - dependencies: - esrecurse: ^4.1.0 - estraverse: ^4.1.1 - checksum: c5f835f681884469991fe58d76a554688d9c9e50811299ccd4a8f79993a039f5bcb0ee6e8de2b0017d97c794b5832ef3b21c9aac66228e3aa0f7a0485bcfb65b - languageName: node - linkType: hard - -"eslint-scope@npm:^7.1.0, eslint-scope@npm:^7.1.1": +"eslint-scope@npm:^7.1.1": version: 7.1.1 resolution: "eslint-scope@npm:7.1.1" dependencies: @@ -20685,7 +20116,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.1.0, eslint-visitor-keys@npm:^3.3.0": +"eslint-visitor-keys@npm:^3.3.0": version: 3.3.0 resolution: "eslint-visitor-keys@npm:3.3.0" checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808 @@ -20699,54 +20130,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:8.4.1": - version: 8.4.1 - resolution: "eslint@npm:8.4.1" - dependencies: - "@eslint/eslintrc": ^1.0.5 - "@humanwhocodes/config-array": ^0.9.2 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.3.2 - doctrine: ^3.0.0 - enquirer: ^2.3.5 - escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.0 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.1.0 - espree: ^9.2.0 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^6.0.1 - globals: ^13.6.0 - ignore: ^4.0.6 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^4.1.0 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.0.4 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - progress: ^2.0.0 - regexpp: ^3.2.0 - semver: ^7.2.1 - strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: d962cd7cd0f68ddc2412f47154b8992ad3af987cf47fa6e60e51a2b7d32a91f934388f7d29e2c45b16b7ac69f0d220d0a483189ec6ba43a8a480110c34f158f9 - languageName: node - linkType: hard - "eslint@npm:^8.34.0": version: 8.34.0 resolution: "eslint@npm:8.34.0" @@ -20803,39 +20186,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:9.2.0": - version: 9.2.0 - resolution: "espree@npm:9.2.0" - dependencies: - acorn: ^8.6.0 - acorn-jsx: ^5.3.1 - eslint-visitor-keys: ^3.1.0 - checksum: ae533a058036e3efeeac43a0ee39c74ab347e2a73bbe2946fba33cc0d84aca657e675bc317ed9afd95338f79d5d5a862afec2f717d2539ae13fa9f1638371761 - languageName: node - linkType: hard - -"espree@npm:^9.2.0": - version: 9.3.3 - resolution: "espree@npm:9.3.3" - dependencies: - acorn: ^8.8.0 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 33e8a36fc15d082e68672e322e22a53856b564d60aad8f291a667bfc21b2c900c42412d37dd3c7a0f18b9d0d8f8858dabe8776dbd4b4c2f72c5cf4d6afeabf65 - languageName: node - linkType: hard - -"espree@npm:^9.3.2": - version: 9.3.2 - resolution: "espree@npm:9.3.2" - dependencies: - acorn: ^8.7.1 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 - languageName: node - linkType: hard - "espree@npm:^9.4.0": version: 9.4.1 resolution: "espree@npm:9.4.1" @@ -20866,7 +20216,7 @@ __metadata: languageName: node linkType: hard -"esrecurse@npm:^4.1.0, esrecurse@npm:^4.3.0": +"esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" dependencies: @@ -20889,14 +20239,10 @@ __metadata: languageName: node linkType: hard -"estree-to-babel@npm:^3.1.0": - version: 3.2.1 - resolution: "estree-to-babel@npm:3.2.1" - dependencies: - "@babel/traverse": ^7.1.6 - "@babel/types": ^7.2.0 - c8: ^7.6.0 - checksum: a4584d0c60b80ce41abe91b11052f5d48635e811c67839942c4ebd51aa33d9f9b156ad615f71ceae2a8260b5e3054f36d73db6d0d2a3b9951483f4b6187495c8 +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: ea3909f0188ea164af0aadeca87c087e3e5da78d76da5ae9c7954ff1340ea3e4679c4653bbf4299ffb70caa9b322218cc1128db2541f3d2976eb9704f9857787 languageName: node linkType: hard @@ -20995,14 +20341,7 @@ __metadata: languageName: node linkType: hard -"exec-sh@npm:^0.3.2": - version: 0.3.6 - resolution: "exec-sh@npm:0.3.6" - checksum: 0be4f06929c8e4834ea4812f29fe59e2dfcc1bc3fc4b4bb71acb38a500c3b394628a05ef7ba432520bc6c5ec4fadab00cc9c513c4ff6a32104965af302e998e0 - languageName: node - linkType: hard - -"execa@npm:1.0.0, execa@npm:^1.0.0": +"execa@npm:1.0.0": version: 1.0.0 resolution: "execa@npm:1.0.0" dependencies: @@ -21017,7 +20356,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:5.1.1, execa@npm:^5.1.1": +"execa@npm:5.1.1, execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -21048,18 +20387,10 @@ __metadata: languageName: node linkType: hard -"expand-brackets@npm:^2.1.4": - version: 2.1.4 - resolution: "expand-brackets@npm:2.1.4" - dependencies: - debug: ^2.3.3 - define-property: ^0.2.5 - extend-shallow: ^2.0.1 - posix-character-classes: ^0.1.0 - regex-not: ^1.0.0 - snapdragon: ^0.8.1 - to-regex: ^3.0.1 - checksum: 1781d422e7edfa20009e2abda673cadb040a6037f0bd30fcd7357304f4f0c284afd420d7622722ca4a016f39b6d091841ab57b401c1f7e2e5131ac65b9f14fa1 +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099 languageName: node linkType: hard @@ -21084,13 +20415,13 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.17.1": - version: 4.18.1 - resolution: "express@npm:4.18.1" +"express@npm:^4.17.3": + version: 4.18.2 + resolution: "express@npm:4.18.2" dependencies: accepts: ~1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.0 + body-parser: 1.20.1 content-disposition: 0.5.4 content-type: ~1.0.4 cookie: 0.5.0 @@ -21109,7 +20440,7 @@ __metadata: parseurl: ~1.3.3 path-to-regexp: 0.1.7 proxy-addr: ~2.0.7 - qs: 6.10.3 + qs: 6.11.0 range-parser: ~1.2.1 safe-buffer: 5.2.1 send: 0.18.0 @@ -21119,7 +20450,7 @@ __metadata: type-is: ~1.6.18 utils-merge: 1.0.1 vary: ~1.1.2 - checksum: c3d44c92e48226ef32ec978becfedb0ecf0ca21316bfd33674b3c5d20459840584f2325726a4f17f33d9c99f769636f728982d1c5433a5b6fe6eb95b8cf0c854 + checksum: 3c4b9b076879442f6b968fe53d85d9f1eeacbb4f4c41e5f16cc36d77ce39a2b0d81b3f250514982110d815b2f7173f5561367f9110fcc541f9371948e8c8b037 languageName: node linkType: hard @@ -21132,16 +20463,6 @@ __metadata: languageName: node linkType: hard -"extend-shallow@npm:^3.0.0, extend-shallow@npm:^3.0.2": - version: 3.0.2 - resolution: "extend-shallow@npm:3.0.2" - dependencies: - assign-symbols: ^1.0.0 - is-extendable: ^1.0.1 - checksum: a920b0cd5838a9995ace31dfd11ab5e79bf6e295aa566910ce53dff19f4b1c0fda2ef21f26b28586c7a2450ca2b42d97bd8c0f5cec9351a819222bf861e02461 - languageName: node - linkType: hard - "extend@npm:^3.0.0, extend@npm:^3.0.2, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -21167,22 +20488,6 @@ __metadata: languageName: node linkType: hard -"extglob@npm:^2.0.4": - version: 2.0.4 - resolution: "extglob@npm:2.0.4" - dependencies: - array-unique: ^0.3.2 - define-property: ^1.0.0 - expand-brackets: ^2.1.4 - extend-shallow: ^2.0.1 - fragment-cache: ^0.2.1 - regex-not: ^1.0.0 - snapdragon: ^0.8.1 - to-regex: ^3.0.1 - checksum: a41531b8934735b684cef5e8c5a01d0f298d7d384500ceca38793a9ce098125aab04ee73e2d75d5b2901bc5dddd2b64e1b5e3bf19139ea48bac52af4a92f1d00 - languageName: node - linkType: hard - "extract-files@npm:^11.0.0": version: 11.0.0 resolution: "extract-files@npm:11.0.0" @@ -21190,6 +20495,20 @@ __metadata: languageName: node linkType: hard +"extract-zip@npm:^1.6.6": + version: 1.7.0 + resolution: "extract-zip@npm:1.7.0" + dependencies: + concat-stream: ^1.6.2 + debug: ^2.6.9 + mkdirp: ^0.5.4 + yauzl: ^2.10.0 + bin: + extract-zip: cli.js + checksum: 011bab660d738614555773d381a6ba4815d98c1cfcdcdf027e154ebcc9fc8c9ef637b3ea5c9b2144013100071ee41722ed041fc9aacc60f6198ef747cac0c073 + languageName: node + linkType: hard + "extsprintf@npm:1.3.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -21246,17 +20565,10 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^2.2.6": - version: 2.2.7 - resolution: "fast-glob@npm:2.2.7" - dependencies: - "@mrmlnc/readdir-enhanced": ^2.2.1 - "@nodelib/fs.stat": ^1.1.2 - glob-parent: ^3.1.0 - is-glob: ^4.0.0 - merge2: ^1.2.3 - micromatch: ^3.1.10 - checksum: 304ccff1d437fcc44ae0168b0c3899054b92e0fd6af6ad7c3ccc82ab4ddd210b99c7c739d60ee3686da2aa165cd1a31810b31fd91f7c2a575d297342a9fc0534 +"fast-fifo@npm:^1.1.0, fast-fifo@npm:^1.2.0": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 languageName: node linkType: hard @@ -21287,15 +20599,15 @@ __metadata: linkType: hard "fast-glob@npm:^3.3.0": - version: 3.3.1 - resolution: "fast-glob@npm:3.3.1" + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" dependencies: "@nodelib/fs.stat": ^2.0.2 "@nodelib/fs.walk": ^1.2.3 glob-parent: ^5.1.2 merge2: ^1.3.0 micromatch: ^4.0.4 - checksum: b6f3add6403e02cf3a798bfbb1183d0f6da2afd368f27456010c0bc1f9640aea308243d4cb2c0ab142f618276e65ecb8be1661d7c62a7b4e5ba774b9ce5432e5 + checksum: 900e4979f4dbc3313840078419245621259f349950411ca2fa445a2f9a1a6d98c3b5e7e0660c5ccd563aa61abe133a21765c6c0dec8e57da1ba71d8000b05ec1 languageName: node linkType: hard @@ -21320,7 +20632,7 @@ __metadata: languageName: node linkType: hard -"fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6": +"fast-levenshtein@npm:^2.0.6": version: 2.0.6 resolution: "fast-levenshtein@npm:2.0.6" checksum: 92cfec0a8dfafd9c7a15fba8f2cc29cd0b62b85f056d99ce448bbcd9f708e18ab2764bda4dd5158364f4145a7c72788538994f0d1787b956ef0d1062b0f7c24c @@ -21403,9 +20715,9 @@ __metadata: linkType: hard "fathom-client@npm:^3.5.0": - version: 3.5.0 - resolution: "fathom-client@npm:3.5.0" - checksum: 16299bd50dc6bcc716a0d6c81fdad65272ff4f08d68695b64b00e19a04de14771c96ddc1205ce7c4220eddd0d3cf70c65ee9c8fb32b8b776ef155faf61e44479 + version: 3.6.0 + resolution: "fathom-client@npm:3.6.0" + checksum: 0cbb6c3f4051a5c0264ae8c8e881a94f51042d6e411f5d41331a7a141fb52250e556bf202770b116216ea93fde9b67429c7a1646d83895b529701f14d40867d6 languageName: node linkType: hard @@ -21472,6 +20784,15 @@ __metadata: languageName: node linkType: hard +"fd-slicer@npm:~1.1.0": + version: 1.1.0 + resolution: "fd-slicer@npm:1.1.0" + dependencies: + pend: ~1.2.0 + checksum: c8585fd5713f4476eb8261150900d2cb7f6ff2d87f8feb306ccc8a1122efd152f1783bdb2b8dc891395744583436bfd8081d8e63ece0ec8687eeefea394d4ff2 + languageName: node + linkType: hard + "fetch-retry@npm:^5.0.2": version: 5.0.3 resolution: "fetch-retry@npm:5.0.3" @@ -21507,13 +20828,6 @@ __metadata: languageName: node linkType: hard -"figgy-pudding@npm:^3.5.1": - version: 3.5.2 - resolution: "figgy-pudding@npm:3.5.2" - checksum: 4090bd66193693dcda605e44d6b8715d8fb5c92a67acd57826e55cf816a342f550d57e5638f822b39366e1b2fdb244e99b3068a37213aa1d6c1bf602b8fde5ae - languageName: node - linkType: hard - "figures@npm:^3.0.0, figures@npm:^3.2.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -21532,25 +20846,13 @@ __metadata: languageName: node linkType: hard -"file-loader@npm:^6.2.0": - version: 6.2.0 - resolution: "file-loader@npm:6.2.0" +"file-system-cache@npm:2.3.0": + version: 2.3.0 + resolution: "file-system-cache@npm:2.3.0" dependencies: - loader-utils: ^2.0.0 - schema-utils: ^3.0.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: faf43eecf233f4897b0150aaa874eeeac214e4f9de49738a9e0ef734a30b5260059e85b7edadf852b98e415f875bd5f12587768a93fd52aaf2e479ecf95fab20 - languageName: node - linkType: hard - -"file-system-cache@npm:^1.0.5": - version: 1.1.0 - resolution: "file-system-cache@npm:1.1.0" - dependencies: - fs-extra: ^10.1.0 - ramda: ^0.28.0 - checksum: d60d7aadf2e9d1629c20dd423f9e1fc3a9719f80dc4e08017a1aa06a8f8d8f66cf140a63ab68a72f07edd9684786ce7409ef4177b43ed0209cd6bcdbb39dab00 + fs-extra: 11.1.1 + ramda: 0.29.0 + checksum: 74afa2870a062500643d41e02d1fbd47a3f30100f9e153dec5233d59f05545f4c8ada6085629d624e043479ac28c0cafc31824f7b49a3f997efab8cc5d05bfee languageName: node linkType: hard @@ -21561,13 +20863,6 @@ __metadata: languageName: node linkType: hard -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 - languageName: node - linkType: hard - "filelist@npm:^1.0.4": version: 1.0.4 resolution: "filelist@npm:1.0.4" @@ -21577,18 +20872,6 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^4.0.0": - version: 4.0.0 - resolution: "fill-range@npm:4.0.0" - dependencies: - extend-shallow: ^2.0.1 - is-number: ^3.0.0 - repeat-string: ^1.6.1 - to-regex-range: ^2.1.0 - checksum: dbb5102467786ab42bc7a3ec7380ae5d6bfd1b5177b2216de89e4a541193f8ba599a6db84651bd2c58c8921db41b8cc3d699ea83b477342d3ce404020f73c298 - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -21598,6 +20881,13 @@ __metadata: languageName: node linkType: hard +"filter-obj@npm:^2.0.2": + version: 2.0.2 + resolution: "filter-obj@npm:2.0.2" + checksum: e0d71ebc89515a4305db5158aeb78c9f9a4bfef4bacf272e7de8cadf0d3b694191f6fdbd3b507ee330c266c4287f21804defa8c80693d8c6ad60f1cbfad4f477 + languageName: node + linkType: hard + "finalhandler@npm:1.2.0": version: 1.2.0 resolution: "finalhandler@npm:1.2.0" @@ -21613,7 +20903,7 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:3.3.2, find-cache-dir@npm:^3.3.1": +"find-cache-dir@npm:3.3.2, find-cache-dir@npm:^3.0.0, find-cache-dir@npm:^3.3.1": version: 3.3.2 resolution: "find-cache-dir@npm:3.3.2" dependencies: @@ -21624,7 +20914,7 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^2.0.0, find-cache-dir@npm:^2.1.0": +"find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" dependencies: @@ -21635,6 +20925,16 @@ __metadata: languageName: node linkType: hard +"find-cache-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "find-cache-dir@npm:4.0.0" + dependencies: + common-path-prefix: ^3.0.0 + pkg-dir: ^7.0.0 + checksum: 52a456a80deeb27daa3af6e06059b63bdb9cc4af4d845fc6d6229887e505ba913cd56000349caa60bc3aa59dacdb5b4c37903d4ba34c75102d83cab330b70d2f + languageName: node + linkType: hard + "find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" @@ -21652,16 +20952,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^1.0.0": - version: 1.1.2 - resolution: "find-up@npm:1.1.2" - dependencies: - path-exists: ^2.0.0 - pinkie-promise: ^2.0.0 - checksum: a2cb9f4c9f06ee3a1e92ed71d5aed41ac8ae30aefa568132f6c556fac7678a5035126153b59eaec68da78ac409eef02503b2b059706bdbf232668d7245e3240a - languageName: node - linkType: hard - "find-up@npm:^2.1.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -21690,6 +20980,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^6.3.0": + version: 6.3.0 + resolution: "find-up@npm:6.3.0" + dependencies: + locate-path: ^7.1.0 + path-exists: ^5.0.0 + checksum: 9a21b7f9244a420e54c6df95b4f6fc3941efd3c3e5476f8274eb452f6a85706e7a6a90de71353ee4f091fcb4593271a6f92810a324ec542650398f928783c280 + languageName: node + linkType: hard + "find-yarn-workspace-root2@npm:1.2.16": version: 1.2.16 resolution: "find-yarn-workspace-root2@npm:1.2.16" @@ -21726,22 +21026,10 @@ __metadata: languageName: node linkType: hard -"flush-write-stream@npm:^1.0.0": - version: 1.1.1 - resolution: "flush-write-stream@npm:1.1.1" - dependencies: - inherits: ^2.0.3 - readable-stream: ^2.3.6 - checksum: 42e07747f83bcd4e799da802e621d6039787749ffd41f5517f8c4f786ee967e31ba32b09f8b28a9c6f67bd4f5346772e604202df350e8d99f4141771bae31279 - languageName: node - linkType: hard - -"focus-lock@npm:^0.8.0": - version: 0.8.1 - resolution: "focus-lock@npm:0.8.1" - dependencies: - tslib: ^1.9.3 - checksum: 3b25b06bb8e23a3a826a8eda89e547593a688486df531db92f6b767d96d397dc1efed4529ec3a44cb3ec1fbdd44abe50a30d0ce498f732501b36f5f18b619003 +"flow-parser@npm:0.*": + version: 0.223.3 + resolution: "flow-parser@npm:0.223.3" + checksum: 4c0df178ff2e9140f57b5e7321b11e1cb827e00fbad6f342742c5834bd313f1bb0fe84d919d36e0c593e916c4ae5c8a93fcdb42c4328d6c5d77d75c8eed12090 languageName: node linkType: hard @@ -21765,10 +21053,12 @@ __metadata: languageName: node linkType: hard -"for-in@npm:^1.0.2": - version: 1.0.2 - resolution: "for-in@npm:1.0.2" - checksum: 09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: ^1.1.3 + checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 languageName: node linkType: hard @@ -21813,49 +21103,26 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^4.1.6": - version: 4.1.6 - resolution: "fork-ts-checker-webpack-plugin@npm:4.1.6" +"fork-ts-checker-webpack-plugin@npm:^8.0.0": + version: 8.0.0 + resolution: "fork-ts-checker-webpack-plugin@npm:8.0.0" dependencies: - "@babel/code-frame": ^7.5.5 - chalk: ^2.4.1 - micromatch: ^3.1.10 - minimatch: ^3.0.4 - semver: ^5.6.0 - tapable: ^1.0.0 - worker-rpc: ^0.1.0 - checksum: 4cc4fa7919dd9a0d765514d064c86e3a6f9cea8e700996b3e775cfcc0280f606a2dd16203d9b7e294b64e900795b0d80eb41fc8c192857d3350e407f14ef3eed - languageName: node - linkType: hard - -"fork-ts-checker-webpack-plugin@npm:^6.0.4": - version: 6.5.2 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.2" - dependencies: - "@babel/code-frame": ^7.8.3 - "@types/json-schema": ^7.0.5 - chalk: ^4.1.0 - chokidar: ^3.4.2 - cosmiconfig: ^6.0.0 + "@babel/code-frame": ^7.16.7 + chalk: ^4.1.2 + chokidar: ^3.5.3 + cosmiconfig: ^7.0.1 deepmerge: ^4.2.2 - fs-extra: ^9.0.0 - glob: ^7.1.6 - memfs: ^3.1.2 + fs-extra: ^10.0.0 + memfs: ^3.4.1 minimatch: ^3.0.4 - schema-utils: 2.7.0 - semver: ^7.3.2 - tapable: ^1.0.0 + node-abort-controller: ^3.0.1 + schema-utils: ^3.1.1 + semver: ^7.3.5 + tapable: ^2.2.1 peerDependencies: - eslint: ">= 6" - typescript: ">= 2.7" - vue-template-compiler: "*" - webpack: ">= 4" - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - checksum: c823de02ee258a26ea5c0c488b2f1825b941f72292417478689862468a9140b209ad7df52f67bd134228fe9f40e9115b604fc8f88a69338929fe52be869469b6 + typescript: ">3.6.0" + webpack: ^5.11.0 + checksum: aad4cbc5b802e6281a2700a379837697c93ad95288468f9595219d91d9c26674736d37852bb4c4341e9122f26181e9e05fc1a362e8d029fdd88e99de7816037b languageName: node linkType: hard @@ -21983,15 +21250,6 @@ __metadata: languageName: node linkType: hard -"fragment-cache@npm:^0.2.1": - version: 0.2.1 - resolution: "fragment-cache@npm:0.2.1" - dependencies: - map-cache: ^0.2.2 - checksum: 1cbbd0b0116b67d5790175de0038a11df23c1cd2e8dcdbade58ebba5594c2d641dade6b4f126d82a7b4a6ffc2ea12e3d387dbb64ea2ae97cf02847d436f60fdc - languageName: node - linkType: hard - "framer-motion@npm:^10.12.8": version: 10.12.8 resolution: "framer-motion@npm:10.12.8" @@ -22020,16 +21278,6 @@ __metadata: languageName: node linkType: hard -"from2@npm:^2.1.0": - version: 2.3.0 - resolution: "from2@npm:2.3.0" - dependencies: - inherits: ^2.0.1 - readable-stream: ^2.0.0 - checksum: 6080eba0793dce32f475141fb3d54cc15f84ee52e420ee22ac3ab0ad639dc95a1875bc6eb9c0e1140e94972a36a89dc5542491b85f1ab8df0c126241e0f1a61b - languageName: node - linkType: hard - "from@npm:~0": version: 0.1.7 resolution: "from@npm:0.1.7" @@ -22055,7 +21303,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^10.1.0": +"fs-extra@npm:^10.0.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" dependencies: @@ -22066,6 +21314,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.1.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: b12e42fa40ba47104202f57b8480dd098aa931c2724565e5e70779ab87605665594e76ee5fb00545f772ab9ace167fe06d2ab009c416dc8c842c5ae6df7aa7e8 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -22088,7 +21347,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^9.0, fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": +"fs-extra@npm:^9.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" dependencies: @@ -22118,10 +21377,10 @@ __metadata: languageName: node linkType: hard -"fs-monkey@npm:^1.0.3": - version: 1.0.3 - resolution: "fs-monkey@npm:1.0.3" - checksum: cf50804833f9b88a476911ae911fe50f61a98d986df52f890bd97e7262796d023698cb2309fa9b74fdd8974f04315b648748a0a8ee059e7d5257b293bfc409c0 +"fs-monkey@npm:^1.0.4": + version: 1.0.5 + resolution: "fs-monkey@npm:1.0.5" + checksum: 424b67f65b37fe66117ae2bb061f790fe6d4b609e1d160487c74b3d69fbf42f262c665ccfba32e8b5f113f96f92e9a58fcdebe42d5f6649bdfc72918093a3119 languageName: node linkType: hard @@ -22134,18 +21393,6 @@ __metadata: languageName: node linkType: hard -"fs-write-stream-atomic@npm:^1.0.8": - version: 1.0.10 - resolution: "fs-write-stream-atomic@npm:1.0.10" - dependencies: - graceful-fs: ^4.1.2 - iferr: ^0.1.5 - imurmurhash: ^0.1.4 - readable-stream: 1 || 2 - checksum: 43c2d6817b72127793abc811ebf87a135b03ac7cbe41cdea9eeacf59b23e6e29b595739b083e9461303d525687499a1aaefcec3e5ff9bc82b170edd3dc467ccc - languageName: node - linkType: hard - "fs-writefile-promise@npm:^1.0.3": version: 1.0.3 resolution: "fs-writefile-promise@npm:1.0.3" @@ -22170,7 +21417,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.2, fsevents@npm:^2.1.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -22180,18 +21427,17 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^1.2.7": - version: 1.2.13 - resolution: "fsevents@npm:1.2.13" +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" dependencies: - bindings: ^1.5.0 - nan: ^2.12.1 - checksum: ae855aa737aaa2f9167e9f70417cf6e45a5cd11918e1fee9923709a0149be52416d765433b4aeff56c789b1152e718cd1b13ddec6043b78cdda68260d86383c1 + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 conditions: os=darwin languageName: node linkType: hard -"fsevents@patch:fsevents@2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@^2.1.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>": +"fsevents@patch:fsevents@2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=df0bf1" dependencies: @@ -22200,12 +21446,11 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^1.2.7#~builtin<compat/fsevents>": - version: 1.2.13 - resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin<compat/fsevents>::version=1.2.13&hash=d11327" +"fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.3#~builtin<compat/fsevents>": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" dependencies: - bindings: ^1.5.0 - nan: ^2.12.1 + node-gyp: latest conditions: os=darwin languageName: node linkType: hard @@ -22217,7 +21462,14 @@ __metadata: languageName: node linkType: hard -"function.prototype.name@npm:^1.1.0, function.prototype.name@npm:^1.1.5": +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.5": version: 1.1.5 resolution: "function.prototype.name@npm:1.1.5" dependencies: @@ -22229,13 +21481,6 @@ __metadata: languageName: node linkType: hard -"functional-red-black-tree@npm:^1.0.1": - version: 1.0.1 - resolution: "functional-red-black-tree@npm:1.0.1" - checksum: ca6c170f37640e2d94297da8bb4bf27a1d12bea3e00e6a3e007fd7aa32e37e000f5772acf941b4e4f3cf1c95c3752033d0c509af157ad8f526e7f00723b9eb9f - languageName: node - linkType: hard - "functions-have-names@npm:^1.2.2, functions-have-names@npm:^1.2.3": version: 1.2.3 resolution: "functions-have-names@npm:1.2.3" @@ -22252,23 +21497,6 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^3.0.0": - version: 3.0.2 - resolution: "gauge@npm:3.0.2" - dependencies: - aproba: ^1.0.3 || ^2.0.0 - color-support: ^1.1.2 - console-control-strings: ^1.0.0 - has-unicode: ^2.0.1 - object-assign: ^4.1.1 - signal-exit: ^3.0.0 - string-width: ^4.2.3 - strip-ansi: ^6.0.1 - wide-align: ^1.1.2 - checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9 - languageName: node - linkType: hard - "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -22324,7 +21552,7 @@ __metadata: languageName: node linkType: hard -"gensync@npm:^1.0.0-beta.1, gensync@npm:^1.0.0-beta.2": +"gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec @@ -22345,6 +21573,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": version: 1.1.1 resolution: "get-intrinsic@npm:1.1.1" @@ -22367,6 +21602,18 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2": + version: 1.2.2 + resolution: "get-intrinsic@npm:1.2.2" + dependencies: + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 447ff0724df26829908dc033b62732359596fcf66027bc131ab37984afb33842d9cd458fd6cecadfe7eac22fd8a54b349799ed334cf2726025c921c7250e7417 + languageName: node + linkType: hard + "get-nonce@npm:^1.0.0": version: 1.0.1 resolution: "get-nonce@npm:1.0.1" @@ -22374,6 +21621,13 @@ __metadata: languageName: node linkType: hard +"get-npm-tarball-url@npm:^2.0.3": + version: 2.1.0 + resolution: "get-npm-tarball-url@npm:2.1.0" + checksum: 02b96993ad5a04cbd0ef0577ac3cc9e2e78a7c60db6bb5e6c8fe78950fc1fc3d093314987629a2fda3083228d91a93670bde321767ca2cf89ce7f463c9e44071 + languageName: node + linkType: hard + "get-own-enumerable-property-symbols@npm:^3.0.0": version: 3.0.2 resolution: "get-own-enumerable-property-symbols@npm:3.0.2" @@ -22388,10 +21642,10 @@ __metadata: languageName: node linkType: hard -"get-stdin@npm:^4.0.1": - version: 4.0.1 - resolution: "get-stdin@npm:4.0.1" - checksum: 4f73d3fe0516bc1f3dc7764466a68ad7c2ba809397a02f56c2a598120e028430fcff137a648a01876b2adfb486b4bc164119f98f1f7d7c0abd63385bdaa0113f +"get-port@npm:^5.1.1": + version: 5.1.1 + resolution: "get-port@npm:5.1.1" + checksum: 0162663ffe5c09e748cd79d97b74cd70e5a5c84b760a475ce5767b357fb2a57cb821cee412d646aa8a156ed39b78aab88974eddaa9e5ee926173c036c0713787 languageName: node linkType: hard @@ -22446,13 +21700,6 @@ __metadata: languageName: node linkType: hard -"get-value@npm:^2.0.3, get-value@npm:^2.0.6": - version: 2.0.6 - resolution: "get-value@npm:2.0.6" - checksum: 5c3b99cb5398ea8016bf46ff17afc5d1d286874d2ad38ca5edb6e87d75c0965b0094cb9a9dddef2c59c23d250702323539a7fbdd870620db38c7e7d7ec87c1eb - languageName: node - linkType: hard - "getpass@npm:^0.1.1": version: 0.1.7 resolution: "getpass@npm:0.1.7" @@ -22472,6 +21719,23 @@ __metadata: languageName: node linkType: hard +"giget@npm:^1.0.0": + version: 1.1.3 + resolution: "giget@npm:1.1.3" + dependencies: + colorette: ^2.0.20 + defu: ^6.1.2 + https-proxy-agent: ^7.0.2 + mri: ^1.2.0 + node-fetch-native: ^1.4.0 + pathe: ^1.1.1 + tar: ^6.2.0 + bin: + giget: dist/cli.mjs + checksum: 1a88b29e3eed2c3593a60f92f54512c9b885117b12c3bb8febd6b504c3f101030b7b0270a912c30b6cb9b177539af3c64cddd2c8a5dbda5a155f65426bd3fbf7 + languageName: node + linkType: hard + "git-repo-info@npm:2.1.1": version: 2.1.1 resolution: "git-repo-info@npm:2.1.1" @@ -22486,6 +21750,13 @@ __metadata: languageName: node linkType: hard +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 14e448192a35c1e42efee94c9d01a10f42fe790375891a24b25261246ce9336ab9df5d274585aedd4568f7922246c2a78b8a8cd2571bfe99c693a9718e7dd0e3 + languageName: node + linkType: hard + "github-slugger@npm:^1.0.0": version: 1.4.0 resolution: "github-slugger@npm:1.4.0" @@ -22493,16 +21764,6 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^3.1.0": - version: 3.1.0 - resolution: "glob-parent@npm:3.1.0" - dependencies: - is-glob: ^3.1.0 - path-dirname: ^1.0.0 - checksum: 653d559237e89a11b9934bef3f392ec42335602034c928590544d383ff5ef449f7b12f3cfa539708e74bc0a6c28ab1fe51d663cc07463cdf899ba92afd85a855 - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -22521,35 +21782,6 @@ __metadata: languageName: node linkType: hard -"glob-promise@npm:^3.4.0": - version: 3.4.0 - resolution: "glob-promise@npm:3.4.0" - dependencies: - "@types/glob": "*" - peerDependencies: - glob: "*" - checksum: 84a2c076e7581c9f8aa7a8a151ad5f9352c4118ba03c5673ecfcf540f4c53aa75f8d32fe493c2286d471dccd7a75932b9bfe97bf782564c1f4a50b9c7954e3b6 - languageName: node - linkType: hard - -"glob-promise@npm:^4.2.0": - version: 4.2.2 - resolution: "glob-promise@npm:4.2.2" - dependencies: - "@types/glob": ^7.1.3 - peerDependencies: - glob: ^7.1.6 - checksum: c1a3d95f7c8393e4151d4899ec4e42bb2e8237160f840ad1eccbe9247407da8b6c13e28f463022e011708bc40862db87b9b77236d35afa3feb8aa86d518f2dfe - languageName: node - linkType: hard - -"glob-to-regexp@npm:^0.3.0": - version: 0.3.0 - resolution: "glob-to-regexp@npm:0.3.0" - checksum: d34b3219d860042d508c4893b67617cd16e2668827e445ff39cff9f72ef70361d3dc24f429e003cdfb6607c75c9664b8eadc41d2eeb95690af0b0d3113c1b23b - languageName: node - linkType: hard - "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -22600,7 +21832,22 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.1, glob@npm:^7.2.0, glob@npm:^7.2.3": +"glob@npm:^10.0.0": + version: 10.3.10 + resolution: "glob@npm:10.3.10" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.3.5 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/esm/bin.mjs + checksum: 4f2fe2511e157b5a3f525a54092169a5f92405f24d2aed3142f4411df328baca13059f4182f1db1bf933e2c69c0bd89e57ae87edd8950cba8c7ccbe84f721cf3 + languageName: node + linkType: hard + +"glob@npm:^7.1.1, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -22650,7 +21897,7 @@ __metadata: languageName: node linkType: hard -"global@npm:^4.4.0, global@npm:~4.4.0": +"global@npm:~4.4.0": version: 4.4.0 resolution: "global@npm:4.4.0" dependencies: @@ -22667,15 +21914,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.15.0 - resolution: "globals@npm:13.15.0" - dependencies: - type-fest: ^0.20.2 - checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae - languageName: node - linkType: hard - "globals@npm:^13.19.0": version: 13.20.0 resolution: "globals@npm:13.20.0" @@ -22685,16 +21923,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.6.0": - version: 13.16.0 - resolution: "globals@npm:13.16.0" - dependencies: - type-fest: ^0.20.2 - checksum: e571b28462b8922a29ac78c8df89848cfd5dc9bdd5d8077440c022864f512a4aae82e7561a2f366337daa86fd4b366aec16fd3f08686de387e4089b01be6cb14 - languageName: node - linkType: hard - -"globalthis@npm:^1.0.0, globalthis@npm:^1.0.3": +"globalthis@npm:^1.0.3": version: 1.0.3 resolution: "globalthis@npm:1.0.3" dependencies: @@ -22763,22 +21992,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^9.2.0": - version: 9.2.0 - resolution: "globby@npm:9.2.0" - dependencies: - "@types/glob": ^7.1.1 - array-union: ^1.0.2 - dir-glob: ^2.2.2 - fast-glob: ^2.2.6 - glob: ^7.1.3 - ignore: ^4.0.3 - pify: ^4.0.1 - slash: ^2.0.0 - checksum: 9b4cb70aa0b43bf89b18cf0e543695185e16d8dd99c17bdc6a1df0a9f88ff9dc8d2467aebace54c3842fc451a564882948c87a3b4fbdb1cacf3e05fd54b6ac5d - languageName: node - linkType: hard - "globrex@npm:^0.1.2": version: 0.1.2 resolution: "globrex@npm:0.1.2" @@ -22847,6 +22060,15 @@ __metadata: languageName: node linkType: hard +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 + languageName: node + linkType: hard + "got@npm:^11.8.5": version: 11.8.6 resolution: "got@npm:11.8.6" @@ -22905,8 +22127,8 @@ __metadata: linkType: hard "graphql-config@npm:^5.0.2": - version: 5.0.2 - resolution: "graphql-config@npm:5.0.2" + version: 5.0.3 + resolution: "graphql-config@npm:5.0.3" dependencies: "@graphql-tools/graphql-file-loader": ^8.0.0 "@graphql-tools/json-file-loader": ^8.0.0 @@ -22925,7 +22147,7 @@ __metadata: peerDependenciesMeta: cosmiconfig-toml-loader: optional: true - checksum: 3349131e18ace3871d7d9752ba9ff78f625c19ee254524469232b6e8e81d69596b2d90e0309fcfeced387b4abfb793f77be87c556eedffa7eae6c08d0432d6d7 + checksum: 3d079d48ccc624d16bee58d15802267d65e856f4d1ba278ededb3ac66a565d4f205cd60ac1f19ed8159bfa2d944c453ae58512c6513a8004754bea9964924485 languageName: node linkType: hard @@ -22953,11 +22175,11 @@ __metadata: linkType: hard "graphql-ws@npm:^5.14.0": - version: 5.14.0 - resolution: "graphql-ws@npm:5.14.0" + version: 5.14.3 + resolution: "graphql-ws@npm:5.14.3" peerDependencies: graphql: ">=0.11 <=16" - checksum: 7b622944823fa12a77ea490656121a77e1a1daf08114a6a0b027922113f4481d95f4fe380a5de369a51657ef777d35757dc31f63e41071c21f3e97ca47e4205a + checksum: c5bfdeb6d06f528e2222e71bf830b2f4f3e5b95419453d3b650cef9fe012e0126f121e4858d950edf3db1fb209a056b592643751624d1bc1fc71ecbe546d53d5 languageName: node linkType: hard @@ -22988,9 +22210,9 @@ __metadata: linkType: hard "gsap@npm:^3.11.0": - version: 3.12.2 - resolution: "gsap@npm:3.12.2" - checksum: e3e9709f9ca60cee0ef7fd91f45a232a48b550f95b6df090dfffe2511fccc651408476de575b69c1c2ce80b6089b6ad0d4854beb58ea427f14e51a30b0640a70 + version: 3.12.4 + resolution: "gsap@npm:3.12.4" + checksum: 7b78fb46b3250c09a1856da32109bae16091be41bd3ec3a17ce2b6dea444fbecd2c57b9c677a09c6b4c022d3024e9ae39d377fab5f63302417a88a6b43231381 languageName: node linkType: hard @@ -23005,6 +22227,22 @@ __metadata: languageName: node linkType: hard +"gunzip-maybe@npm:^1.4.2": + version: 1.4.2 + resolution: "gunzip-maybe@npm:1.4.2" + dependencies: + browserify-zlib: ^0.1.4 + is-deflate: ^1.0.0 + is-gzip: ^1.0.0 + peek-stream: ^1.1.0 + pumpify: ^1.3.3 + through2: ^2.0.3 + bin: + gunzip-maybe: bin.js + checksum: bc4d4977c24a2860238df271de75d53dd72a359d19f1248d1c613807dc221d3b8ae09624e3085c8106663e3e1b59db62a85b261d1138c2cc24efad9df577d4e1 + languageName: node + linkType: hard + "gzip-size@npm:^6.0.0": version: 6.0.0 resolution: "gzip-size@npm:6.0.0" @@ -23093,15 +22331,6 @@ __metadata: languageName: node linkType: hard -"has-glob@npm:^1.0.0": - version: 1.0.0 - resolution: "has-glob@npm:1.0.0" - dependencies: - is-glob: ^3.0.0 - checksum: cafad93e599f49f676a9ab444ec90210fcda35ac14ad6c9bb96c08057ad18a1318f1116b053aa6bdc744f19252537006872d3fc76785e842bbe8cc4312447fc8 - languageName: node - linkType: hard - "has-property-descriptors@npm:^1.0.0": version: 1.0.0 resolution: "has-property-descriptors@npm:1.0.0" @@ -23111,6 +22340,13 @@ __metadata: languageName: node linkType: hard +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + "has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" @@ -23134,45 +22370,6 @@ __metadata: languageName: node linkType: hard -"has-value@npm:^0.3.1": - version: 0.3.1 - resolution: "has-value@npm:0.3.1" - dependencies: - get-value: ^2.0.3 - has-values: ^0.1.4 - isobject: ^2.0.0 - checksum: 29e2a1e6571dad83451b769c7ce032fce6009f65bccace07c2962d3ad4d5530b6743d8f3229e4ecf3ea8e905d23a752c5f7089100c1f3162039fa6dc3976558f - languageName: node - linkType: hard - -"has-value@npm:^1.0.0": - version: 1.0.0 - resolution: "has-value@npm:1.0.0" - dependencies: - get-value: ^2.0.6 - has-values: ^1.0.0 - isobject: ^3.0.0 - checksum: b9421d354e44f03d3272ac39fd49f804f19bc1e4fa3ceef7745df43d6b402053f828445c03226b21d7d934a21ac9cf4bc569396dc312f496ddff873197bbd847 - languageName: node - linkType: hard - -"has-values@npm:^0.1.4": - version: 0.1.4 - resolution: "has-values@npm:0.1.4" - checksum: ab1c4bcaf811ccd1856c11cfe90e62fca9e2b026ebe474233a3d282d8d67e3b59ed85b622c7673bac3db198cb98bd1da2b39300a2f98e453729b115350af49bc - languageName: node - linkType: hard - -"has-values@npm:^1.0.0": - version: 1.0.0 - resolution: "has-values@npm:1.0.0" - dependencies: - is-number: ^3.0.0 - kind-of: ^4.0.0 - checksum: 77e6693f732b5e4cf6c38dfe85fdcefad0fab011af74995c3e83863fabf5e3a836f406d83565816baa0bc0a523c9410db8b990fe977074d61aeb6d8f4fcffa11 - languageName: node - linkType: hard - "has@npm:^1.0.3": version: 1.0.3 resolution: "has@npm:1.0.3" @@ -23213,32 +22410,12 @@ __metadata: languageName: node linkType: hard -"hast-to-hyperscript@npm:^9.0.0": - version: 9.0.1 - resolution: "hast-to-hyperscript@npm:9.0.1" +"hasown@npm:^2.0.0": + version: 2.0.0 + resolution: "hasown@npm:2.0.0" dependencies: - "@types/unist": ^2.0.3 - comma-separated-tokens: ^1.0.0 - property-information: ^5.3.0 - space-separated-tokens: ^1.0.0 - style-to-object: ^0.3.0 - unist-util-is: ^4.0.0 - web-namespaces: ^1.0.0 - checksum: de570d789853018fff2fd38fc096549b9814e366b298f60c90c159a57018230eefc44d46a246027b0e2426ed9e99f2e270050bc183d5bdfe4c9487c320b392cd - languageName: node - linkType: hard - -"hast-util-from-parse5@npm:^6.0.0": - version: 6.0.1 - resolution: "hast-util-from-parse5@npm:6.0.1" - dependencies: - "@types/parse5": ^5.0.0 - hastscript: ^6.0.0 - property-information: ^5.0.0 - vfile: ^4.0.0 - vfile-location: ^3.2.0 - web-namespaces: ^1.0.0 - checksum: 4daa78201468af7779161e7caa2513c329830778e0528481ab16b3e1bcef4b831f6285b526aacdddbee802f3bd9d64df55f80f010591ea1916da535e3a923b83 + function-bind: ^1.1.2 + checksum: 6151c75ca12554565098641c98a40f4cc86b85b0fd5b6fe92360967e4605a4f9610f7757260b4e8098dd1c2ce7f4b095f2006fe72a570e3b6d2d28de0298c176 languageName: node linkType: hard @@ -23273,24 +22450,6 @@ __metadata: languageName: node linkType: hard -"hast-util-raw@npm:6.0.1": - version: 6.0.1 - resolution: "hast-util-raw@npm:6.0.1" - dependencies: - "@types/hast": ^2.0.0 - hast-util-from-parse5: ^6.0.0 - hast-util-to-parse5: ^6.0.0 - html-void-elements: ^1.0.0 - parse5: ^6.0.0 - unist-util-position: ^3.0.0 - vfile: ^4.0.0 - web-namespaces: ^1.0.0 - xtend: ^4.0.0 - zwitch: ^1.0.0 - checksum: f6d960644f9fbbe0b92d0227b20a24d659cce021d5f9fd218e077154931b4524ee920217b7fd5a45ec2736ec1dee53de9209fe449f6f89454c01d225ff0e7851 - languageName: node - linkType: hard - "hast-util-raw@npm:^7.0.0": version: 7.2.3 resolution: "hast-util-raw@npm:7.2.3" @@ -23338,16 +22497,26 @@ __metadata: languageName: node linkType: hard -"hast-util-to-parse5@npm:^6.0.0": - version: 6.0.0 - resolution: "hast-util-to-parse5@npm:6.0.0" +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.0 + resolution: "hast-util-to-jsx-runtime@npm:2.3.0" dependencies: - hast-to-hyperscript: ^9.0.0 - property-information: ^5.0.0 - web-namespaces: ^1.0.0 - xtend: ^4.0.0 - zwitch: ^1.0.0 - checksum: 91a36244e37df1d63c8b7e865ab0c0a25bb7396155602be005cf71d95c348e709568f80e0f891681a3711d733ad896e70642dc41a05b574eddf2e07d285408a8 + "@types/estree": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + comma-separated-tokens: ^2.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 + hast-util-whitespace: ^3.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + style-to-object: ^1.0.0 + unist-util-position: ^5.0.0 + vfile-message: ^4.0.0 + checksum: 599a97c6ec61c1430776813d7fb42e6f96032bf4a04dfcbb8eceef3bc8d1845ecf242387a4426b9d3f52320dbbfa26450643b81124b3d6a0b9bbb0fff4d0ba83 languageName: node linkType: hard @@ -23372,6 +22541,15 @@ __metadata: languageName: node linkType: hard +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + "hastscript@npm:^6.0.0": version: 6.0.0 resolution: "hastscript@npm:6.0.0" @@ -23507,23 +22685,6 @@ __metadata: languageName: node linkType: hard -"html-minifier-terser@npm:^5.0.1": - version: 5.1.1 - resolution: "html-minifier-terser@npm:5.1.1" - dependencies: - camel-case: ^4.1.1 - clean-css: ^4.2.3 - commander: ^4.1.1 - he: ^1.2.0 - param-case: ^3.0.3 - relateurl: ^0.2.7 - terser: ^4.6.3 - bin: - html-minifier-terser: cli.js - checksum: 75ff3ff886631b9ecb3035acb8e7dd98c599bb4d4618ad6f7e487ee9752987dddcf6848dc3c1ab1d7fc1ad4484337c2ce39c19eac17b0342b4b15e4294c8a904 - languageName: node - linkType: hard - "html-minifier-terser@npm:^6.0.2": version: 6.1.0 resolution: "html-minifier-terser@npm:6.1.0" @@ -23570,10 +22731,10 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^1.0.0": - version: 1.0.5 - resolution: "html-void-elements@npm:1.0.5" - checksum: 1a56f4f6cfbeb994c21701ff72b4b7f556fe784a70e5e554d1566ff775af83b91ea93f10664f039a67802d9f7b40d4a7f1ed20312bab47bd88d89bd792ea84ca +"html-url-attributes@npm:^3.0.0": + version: 3.0.0 + resolution: "html-url-attributes@npm:3.0.0" + checksum: 9f499d33e6ddff6c2d2766fd73d2f22f3c370b4e485a92b0b2938303665b306dc7f36b2724c9466764e8f702351c01f342f5ec933be41a31c1fa40b72087b91d languageName: node linkType: hard @@ -23584,28 +22745,9 @@ __metadata: languageName: node linkType: hard -"html-webpack-plugin@npm:^4.0.0": - version: 4.5.2 - resolution: "html-webpack-plugin@npm:4.5.2" - dependencies: - "@types/html-minifier-terser": ^5.0.0 - "@types/tapable": ^1.0.5 - "@types/webpack": ^4.41.8 - html-minifier-terser: ^5.0.1 - loader-utils: ^1.2.3 - lodash: ^4.17.20 - pretty-error: ^2.1.1 - tapable: ^1.1.3 - util.promisify: 1.0.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 25ca0b341234501c64754ba8f9bb84f978e50f3f90affc199d18d04511cdc2c0c8ef8a975901a0fbcfe5bae32f80e8fd5ef52f1ce3672d3ff5307057ccb5a063 - languageName: node - linkType: hard - -"html-webpack-plugin@npm:^5.0.0": - version: 5.5.0 - resolution: "html-webpack-plugin@npm:5.5.0" +"html-webpack-plugin@npm:^5.5.0": + version: 5.5.4 + resolution: "html-webpack-plugin@npm:5.5.4" dependencies: "@types/html-minifier-terser": ^6.0.0 html-minifier-terser: ^6.0.2 @@ -23614,19 +22756,7 @@ __metadata: tapable: ^2.0.0 peerDependencies: webpack: ^5.20.0 - checksum: f3d84d0df71fe2f5bac533cc74dce41ab058558cdcc6ff767d166a2abf1cf6fb8491d54d60ddbb34e95c00394e379ba52e0468e0284d1d0cc6a42987056e8219 - languageName: node - linkType: hard - -"htmlparser2-svelte@npm:4.1.0": - version: 4.1.0 - resolution: "htmlparser2-svelte@npm:4.1.0" - dependencies: - domelementtype: ^2.0.1 - domhandler: ^3.0.0 - domutils: ^2.0.0 - entities: ^2.0.0 - checksum: ae0dfa2e28fd75c256e58a4b629af8c5ea117999d3c012d28377f5976f45c394075bb9595a8c2f00d87f5c68bb1be43b6288bac021b185ec3b18cc94e07fd47b + checksum: b49befb73d67a3716fd0e6f7776b108d2b0b7050fb8221f05cd114cbae13c03150a13b7cdf5e76170be040ce7936a1cf76f7a4bfd9ebe1552b72d7889a74c374 languageName: node linkType: hard @@ -23654,14 +22784,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.0.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 @@ -23795,6 +22918,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^4.0.0": + version: 4.0.0 + resolution: "https-proxy-agent@npm:4.0.0" + dependencies: + agent-base: 5 + debug: 4 + checksum: 19471d5aae3e747b1c98b17556647e2a1362e68220c6b19585a8527498f32e62e03c41d2872d059d8720d56846bd7460a80ac06f876bccfa786468ff40dd5eef + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -23805,7 +22938,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.0": +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.2": version: 7.0.2 resolution: "https-proxy-agent@npm:7.0.2" dependencies: @@ -23937,15 +23070,6 @@ __metadata: languageName: node linkType: hard -"icss-utils@npm:^4.0.0, icss-utils@npm:^4.1.1": - version: 4.1.1 - resolution: "icss-utils@npm:4.1.1" - dependencies: - postcss: ^7.0.14 - checksum: a4ca2c6b82cb3eb879d635bd4028d74bca174edc49ee48ef5f01988489747d340a389d5a0ac6f6887a5c24ab8fc4386c781daab32a7ade5344a2edff66207635 - languageName: node - linkType: hard - "icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": version: 5.1.0 resolution: "icss-utils@npm:5.1.0" @@ -23962,20 +23086,13 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13, ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e languageName: node linkType: hard -"iferr@npm:^0.1.5": - version: 0.1.5 - resolution: "iferr@npm:0.1.5" - checksum: a18d19b6ad06a2d5412c0d37f6364869393ef6d1688d59d00082c1f35c92399094c031798340612458cd832f4f2e8b13bc9615934a7d8b0c53061307a3816aa1 - languageName: node - linkType: hard - "iframe-resizer-react@npm:^1.1.0": version: 1.1.0 resolution: "iframe-resizer-react@npm:1.1.0" @@ -23991,9 +23108,9 @@ __metadata: linkType: hard "iframe-resizer@npm:^4.3.0": - version: 4.3.7 - resolution: "iframe-resizer@npm:4.3.7" - checksum: 252f459d0350223f3db6ec01649f79c51088653cf2d8edf932542fe92fab79b2a968403cc685af253ea168097f102dbc5ac8c579ece772e05be83fa39a6026bc + version: 4.3.9 + resolution: "iframe-resizer@npm:4.3.9" + checksum: 8f40a6e9bf27bf60682d9a80f12c60d96a3b724f5e9ef5bdbcf5f2ac58b1b17f6924e34cfc54210a0a065d305380e77d79e28c551f54bd3b1dac1da2624a2144 languageName: node linkType: hard @@ -24006,13 +23123,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^4.0.3, ignore@npm:^4.0.6": - version: 4.0.6 - resolution: "ignore@npm:4.0.6" - checksum: 248f82e50a430906f9ee7f35e1158e3ec4c3971451dd9f99c9bc1548261b4db2b99709f60ac6c6cac9333494384176cc4cc9b07acbe42d52ac6a09cad734d800 - languageName: node - linkType: hard - "ignore@npm:^5.2.0": version: 5.2.0 resolution: "ignore@npm:5.2.0" @@ -24021,9 +23131,9 @@ __metadata: linkType: hard "ignore@npm:^5.2.4": - version: 5.2.4 - resolution: "ignore@npm:5.2.4" - checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef + version: 5.3.0 + resolution: "ignore@npm:5.3.0" + checksum: 2736da6621f14ced652785cb05d86301a66d70248597537176612bd0c8630893564bd5f6421f8806b09e8472e75c591ef01672ab8059c07c6eb2c09cefe04bf9 languageName: node linkType: hard @@ -24106,15 +23216,6 @@ __metadata: languageName: node linkType: hard -"indent-string@npm:^2.1.0": - version: 2.1.0 - resolution: "indent-string@npm:2.1.0" - dependencies: - repeating: ^2.0.0 - checksum: 2fe7124311435f4d7a98f0a314d8259a4ec47ecb221110a58e2e2073e5f75c8d2b4f775f2ed199598fbe20638917e57423096539455ca8bff8eab113c9bee12c - languageName: node - linkType: hard - "indexof@npm:~0.0.1": version: 0.0.1 resolution: "indexof@npm:0.0.1" @@ -24122,7 +23223,7 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.3, infer-owner@npm:^1.0.4": +"infer-owner@npm:^1.0.4": version: 1.0.4 resolution: "infer-owner@npm:1.0.4" checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 @@ -24139,27 +23240,13 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.0, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 languageName: node linkType: hard -"inherits@npm:2.0.1": - version: 2.0.1 - resolution: "inherits@npm:2.0.1" - checksum: 6536b9377296d4ce8ee89c5c543cb75030934e61af42dba98a428e7d026938c5985ea4d1e3b87743a5b834f40ed1187f89c2d7479e9d59e41d2d1051aefba07b - languageName: node - linkType: hard - -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0 - languageName: node - linkType: hard - "ini@npm:2.0.0": version: 2.0.0 resolution: "ini@npm:2.0.0" @@ -24167,6 +23254,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 + languageName: node + linkType: hard + "ink-select-input@npm:^4.2.1": version: 4.2.1 resolution: "ink-select-input@npm:4.2.1" @@ -24231,10 +23325,10 @@ __metadata: languageName: node linkType: hard -"inline-style-parser@npm:0.1.1": - version: 0.1.1 - resolution: "inline-style-parser@npm:0.1.1" - checksum: 5d545056a3e1f2bf864c928a886a0e1656a3517127d36917b973de581bd54adc91b4bf1febcb0da054f204b4934763f1a4e09308b4d55002327cf1d48ac5d966 +"inline-style-parser@npm:0.2.2": + version: 0.2.2 + resolution: "inline-style-parser@npm:0.2.2" + checksum: 698893d6542d4e7c0377936a1c7daec34a197765bd77c5599384756a95ce8804e6b79347b783aa591d5e9c6f3d33dae74c6d4cad3a94647eb05f3a785e927a3f languageName: node linkType: hard @@ -24326,6 +23420,17 @@ __metadata: languageName: node linkType: hard +"internal-slot@npm:^1.0.4": + version: 1.0.6 + resolution: "internal-slot@npm:1.0.6" + dependencies: + get-intrinsic: ^1.2.2 + hasown: ^2.0.0 + side-channel: ^1.0.4 + checksum: 7872454888047553ce97a3fa1da7cc054a28ec5400a9c2e9f4dbe4fe7c1d041cb8e8301467614b80d4246d50377aad2fb58860b294ed74d6700cc346b6f89549 + languageName: node + linkType: hard + "internmap@npm:1 - 2": version: 2.0.3 resolution: "internmap@npm:2.0.3" @@ -24333,13 +23438,6 @@ __metadata: languageName: node linkType: hard -"interpret@npm:^2.2.0": - version: 2.2.0 - resolution: "interpret@npm:2.2.0" - checksum: f51efef7cb8d02da16408ffa3504cd6053014c5aeb7bb8c223727e053e4235bf565e45d67028b0c8740d917c603807aa3c27d7bd2f21bf20b6417e2bb3e5fd6e - languageName: node - linkType: hard - "intl-messageformat@npm:9.13.0": version: 9.13.0 resolution: "intl-messageformat@npm:9.13.0" @@ -24392,31 +23490,20 @@ __metadata: languageName: node linkType: hard -"is-accessor-descriptor@npm:^0.1.6": - version: 0.1.6 - resolution: "is-accessor-descriptor@npm:0.1.6" - dependencies: - kind-of: ^3.0.2 - checksum: 3d629a086a9585bc16a83a8e8a3416f400023301855cafb7ccc9a1d63145b7480f0ad28877dcc2cce09492c4ec1c39ef4c071996f24ee6ac626be4217b8ffc8a - languageName: node - linkType: hard - -"is-accessor-descriptor@npm:^1.0.0": - version: 1.0.0 - resolution: "is-accessor-descriptor@npm:1.0.0" - dependencies: - kind-of: ^6.0.0 - checksum: 8e475968e9b22f9849343c25854fa24492dbe8ba0dea1a818978f9f1b887339190b022c9300d08c47fe36f1b913d70ce8cbaca00369c55a56705fdb7caed37fe - languageName: node - linkType: hard - -"is-alphabetical@npm:1.0.4, is-alphabetical@npm:^1.0.0": +"is-alphabetical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphabetical@npm:1.0.4" checksum: 6508cce44fd348f06705d377b260974f4ce68c74000e7da4045f0d919e568226dc3ce9685c5a2af272195384df6930f748ce9213fc9f399b5d31b362c66312cb languageName: node linkType: hard +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 56207db8d9de0850f0cd30f4966bf731eb82cedfe496cbc2e97e7c3bacaf66fc54a972d2d08c0d93bb679cb84976a05d24c5ad63de56fabbfc60aadae312edaa + languageName: node + linkType: hard + "is-alphanumerical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphanumerical@npm:1.0.4" @@ -24427,6 +23514,16 @@ __metadata: languageName: node linkType: hard +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: ^2.0.0 + is-decimal: ^2.0.0 + checksum: 87acc068008d4c9c4e9f5bd5e251041d42e7a50995c77b1499cf6ed248f971aadeddb11f239cabf09f7975ee58cac7a48ffc170b7890076d8d227b24a68663c9 + languageName: node + linkType: hard + "is-any-array@npm:^2.0.0": version: 2.0.1 resolution: "is-any-array@npm:2.0.1" @@ -24434,7 +23531,7 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.0": +"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" dependencies: @@ -24444,6 +23541,17 @@ __metadata: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": + version: 3.0.2 + resolution: "is-array-buffer@npm:3.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.0 + is-typed-array: ^1.1.10 + checksum: dcac9dda66ff17df9cabdc58214172bf41082f956eab30bb0d86bc0fab1e44b690fc8e1f855cf2481245caf4e8a5a006a982a71ddccec84032ed41f9d8da8c14 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -24451,6 +23559,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 977e64f54d91c8f169b59afcd80ff19227e9f5c791fa28fa2e5bce355cbaf6c2c356711b734656e80c9dd4a854dd7efcf7894402f1031dfc5de5d620775b4d5f + languageName: node + linkType: hard + "is-bigint@npm:^1.0.1": version: 1.0.4 resolution: "is-bigint@npm:1.0.4" @@ -24460,15 +23575,6 @@ __metadata: languageName: node linkType: hard -"is-binary-path@npm:^1.0.0": - version: 1.0.1 - resolution: "is-binary-path@npm:1.0.1" - dependencies: - binary-extensions: ^1.0.0 - checksum: a803c99e9d898170c3b44a86fbdc0736d3d7fcbe737345433fb78e810b9fe30c982657782ad0e676644ba4693ddf05601a7423b5611423218663d6b533341ac9 - languageName: node - linkType: hard - "is-binary-path@npm:~2.1.0": version: 2.1.0 resolution: "is-binary-path@npm:2.1.0" @@ -24488,13 +23594,6 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.6": - version: 1.1.6 - resolution: "is-buffer@npm:1.1.6" - checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707 - languageName: node - linkType: hard - "is-buffer@npm:^2.0.0": version: 2.0.5 resolution: "is-buffer@npm:2.0.5" @@ -24502,6 +23601,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:~1.1.6": + version: 1.1.6 + resolution: "is-buffer@npm:1.1.6" + checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707 + languageName: node + linkType: hard + "is-builtin-module@npm:^3.2.0": version: 3.2.1 resolution: "is-builtin-module@npm:3.2.1" @@ -24511,6 +23617,13 @@ __metadata: languageName: node linkType: hard +"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac + languageName: node + linkType: hard + "is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": version: 1.2.4 resolution: "is-callable@npm:1.2.4" @@ -24518,13 +23631,6 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac - languageName: node - linkType: hard - "is-ci@npm:^2.0.0": version: 2.0.0 resolution: "is-ci@npm:2.0.0" @@ -24583,25 +23689,7 @@ __metadata: languageName: node linkType: hard -"is-data-descriptor@npm:^0.1.4": - version: 0.1.4 - resolution: "is-data-descriptor@npm:0.1.4" - dependencies: - kind-of: ^3.0.2 - checksum: 5c622e078ba933a78338ae398a3d1fc5c23332b395312daf4f74bab4afb10d061cea74821add726cb4db8b946ba36217ee71a24fe71dd5bca4632edb7f6aad87 - languageName: node - linkType: hard - -"is-data-descriptor@npm:^1.0.0": - version: 1.0.0 - resolution: "is-data-descriptor@npm:1.0.0" - dependencies: - kind-of: ^6.0.0 - checksum: e705e6816241c013b05a65dc452244ee378d1c3e3842bd140beabe6e12c0d700ef23c91803f971aa7b091fb0573c5da8963af34a2b573337d87bc3e1f53a4e6d - languageName: node - linkType: hard - -"is-date-object@npm:^1.0.1": +"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" dependencies: @@ -24617,25 +23705,17 @@ __metadata: languageName: node linkType: hard -"is-descriptor@npm:^0.1.0": - version: 0.1.6 - resolution: "is-descriptor@npm:0.1.6" - dependencies: - is-accessor-descriptor: ^0.1.6 - is-data-descriptor: ^0.1.4 - kind-of: ^5.0.0 - checksum: 0f780c1b46b465f71d970fd7754096ffdb7b69fd8797ca1f5069c163eaedcd6a20ec4a50af669075c9ebcfb5266d2e53c8b227e485eefdb0d1fee09aa1dd8ab6 +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 97132de7acdce77caa7b797632970a2ecd649a88e715db0e4dbc00ab0708b5e7574ba5903962c860cd4894a14fd12b100c0c4ac8aed445cf6f55c6cf747a4158 languageName: node linkType: hard -"is-descriptor@npm:^1.0.0, is-descriptor@npm:^1.0.2": - version: 1.0.2 - resolution: "is-descriptor@npm:1.0.2" - dependencies: - is-accessor-descriptor: ^1.0.0 - is-data-descriptor: ^1.0.0 - kind-of: ^6.0.2 - checksum: 2ed623560bee035fb67b23e32ce885700bef8abe3fbf8c909907d86507b91a2c89a9d3a4d835a4d7334dd5db0237a0aeae9ca109c1e4ef1c0e7b577c0846ab5a +"is-deflate@npm:^1.0.0": + version: 1.0.0 + resolution: "is-deflate@npm:1.0.0" + checksum: c2f9f2d3db79ac50c5586697d1e69a55282a2b0cc5e437b3c470dd47f24e40b6216dcd7e024511e21381607bf57afa019343e3bd0e08a119032818b596004262 languageName: node linkType: hard @@ -24658,36 +23738,20 @@ __metadata: languageName: node linkType: hard -"is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1": +"is-extendable@npm:^0.1.0": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" checksum: 3875571d20a7563772ecc7a5f36cb03167e9be31ad259041b4a8f73f33f885441f778cee1f1fe0085eb4bc71679b9d8c923690003a36a6a5fdf8023e6e3f0672 languageName: node linkType: hard -"is-extendable@npm:^1.0.1": - version: 1.0.1 - resolution: "is-extendable@npm:1.0.1" - dependencies: - is-plain-object: ^2.0.4 - checksum: db07bc1e9de6170de70eff7001943691f05b9d1547730b11be01c0ebfe67362912ba743cf4be6fd20a5e03b4180c685dad80b7c509fe717037e3eee30ad8e84f - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.0, is-extglob@npm:^2.1.1": +"is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" checksum: df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 languageName: node linkType: hard -"is-finite@npm:^1.0.0": - version: 1.1.0 - resolution: "is-finite@npm:1.1.0" - checksum: 532b97ed3d03e04c6bd203984d9e4ba3c0c390efee492bad5d1d1cd1802a68ab27adbd3ef6382f6312bed6c8bb1bd3e325ea79a8dc8fe080ed7a06f5f97b93e7 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -24702,13 +23766,22 @@ __metadata: languageName: node linkType: hard -"is-function@npm:^1.0.1, is-function@npm:^1.0.2": +"is-function@npm:^1.0.1": version: 1.0.2 resolution: "is-function@npm:1.0.2" checksum: 7d564562e07b4b51359547d3ccc10fb93bb392fd1b8177ae2601ee4982a0ece86d952323fc172a9000743a3971f09689495ab78a1d49a9b14fc97a7e28521dc0 languageName: node linkType: hard +"is-generator-function@npm:^1.0.7": + version: 1.0.10 + resolution: "is-generator-function@npm:1.0.10" + dependencies: + has-tostringtag: ^1.0.0 + checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b + languageName: node + linkType: hard + "is-glob@npm:4.0.3, is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -24718,12 +23791,10 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^3.0.0, is-glob@npm:^3.1.0": - version: 3.1.0 - resolution: "is-glob@npm:3.1.0" - dependencies: - is-extglob: ^2.1.0 - checksum: 9d483bca84f16f01230f7c7c8c63735248fe1064346f292e0f6f8c76475fd20c6f50fc19941af5bec35f85d6bf26f4b7768f39a48a5f5fdc72b408dc74e07afc +"is-gzip@npm:^1.0.0": + version: 1.0.0 + resolution: "is-gzip@npm:1.0.0" + checksum: 0d28931c1f445fa29c900cf9f48e06e9d1d477a3bf7bd7332e7ce68f1333ccd8cb381de2f0f62a9a262d9c0912608a9a71b4a40e788e201b3dbd67072bb20d86 languageName: node linkType: hard @@ -24734,6 +23805,13 @@ __metadata: languageName: node linkType: hard +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 66a2ea85994c622858f063f23eda506db29d92b52580709eb6f4c19550552d4dcf3fb81952e52f7cf972097237959e00adc7bb8c9400cd12886e15bf06145321 + languageName: node + linkType: hard + "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -24766,7 +23844,7 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.2": +"is-map@npm:^2.0.1, is-map@npm:^2.0.2": version: 2.0.2 resolution: "is-map@npm:2.0.2" checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728 @@ -24813,15 +23891,6 @@ __metadata: languageName: node linkType: hard -"is-number@npm:^3.0.0": - version: 3.0.0 - resolution: "is-number@npm:3.0.0" - dependencies: - kind-of: ^3.0.2 - checksum: 0c62bf8e9d72c4dd203a74d8cfc751c746e75513380fef420cda8237e619a988ee43e678ddb23c87ac24d91ac0fe9f22e4ffb1301a50310c697e9d73ca3994e9 - languageName: node - linkType: hard - "is-number@npm:^7.0.0": version: 7.0.0 resolution: "is-number@npm:7.0.0" @@ -24878,13 +23947,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^2.0.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: cec9100678b0a9fe0248a81743041ed990c2d4c99f893d935545cfbc42876cbe86d207f3b895700c690ad2fa520e568c44afc1605044b535a7820c1d40e38daa - languageName: node - linkType: hard - "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -24899,7 +23961,7 @@ __metadata: languageName: node linkType: hard -"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": +"is-plain-object@npm:^2.0.4": version: 2.0.4 resolution: "is-plain-object@npm:2.0.4" dependencies: @@ -24931,7 +23993,7 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.1.2, is-regex@npm:^1.1.4": +"is-regex@npm:^1.1.4": version: 1.1.4 resolution: "is-regex@npm:1.1.4" dependencies: @@ -24971,7 +24033,7 @@ __metadata: languageName: node linkType: hard -"is-set@npm:^2.0.2": +"is-set@npm:^2.0.1, is-set@npm:^2.0.2": version: 2.0.2 resolution: "is-set@npm:2.0.2" checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57 @@ -25028,7 +24090,16 @@ __metadata: languageName: node linkType: hard -"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3": + version: 1.1.12 + resolution: "is-typed-array@npm:1.1.12" + dependencies: + which-typed-array: ^1.1.11 + checksum: 4c89c4a3be07186caddadf92197b17fda663a9d259ea0d44a85f171558270d36059d1c386d34a12cba22dfade5aba497ce22778e866adc9406098c8fc4771796 + languageName: node + linkType: hard + +"is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" checksum: 3508c6cd0a9ee2e0df2fa2e9baabcdc89e911c7bd5cf64604586697212feec525aa21050e48affb5ffc3df20f0f5d2e2cf79b08caa64e1ccc9578e251763aef7 @@ -25069,10 +24140,10 @@ __metadata: languageName: node linkType: hard -"is-utf8@npm:^0.2.0": - version: 0.2.1 - resolution: "is-utf8@npm:0.2.1" - checksum: 167ccd2be869fc228cc62c1a28df4b78c6b5485d15a29027d3b5dceb09b383e86a3522008b56dcac14b592b22f0a224388718c2505027a994fd8471465de54b3 +"is-weakmap@npm:^2.0.1": + version: 2.0.1 + resolution: "is-weakmap@npm:2.0.1" + checksum: 1222bb7e90c32bdb949226e66d26cb7bce12e1e28e3e1b40bfa6b390ba3e08192a8664a703dff2a00a84825f4e022f9cd58c4599ff9981ab72b1d69479f4f7f6 languageName: node linkType: hard @@ -25085,6 +24156,16 @@ __metadata: languageName: node linkType: hard +"is-weakset@npm:^2.0.1": + version: 2.0.2 + resolution: "is-weakset@npm:2.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.1 + checksum: 5d8698d1fa599a0635d7ca85be9c26d547b317ed8fd83fc75f03efbe75d50001b5eececb1e9971de85fcde84f69ae6f8346bc92d20d55d46201d328e4c74a367 + languageName: node + linkType: hard + "is-what@npm:^4.1.6": version: 4.1.7 resolution: "is-what@npm:4.1.7" @@ -25092,13 +24173,6 @@ __metadata: languageName: node linkType: hard -"is-whitespace-character@npm:^1.0.0": - version: 1.0.4 - resolution: "is-whitespace-character@npm:1.0.4" - checksum: adab8ad9847ccfcb6f1b7000b8f622881b5ba2a09ce8be2794a6d2b10c3af325b469fc562c9fb889f468eed27be06e227ac609d0aa1e3a59b4dbcc88e2b0418e - languageName: node - linkType: hard - "is-window@npm:^1.0.2": version: 1.0.2 resolution: "is-window@npm:1.0.2" @@ -25106,20 +24180,13 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:1.0.2, is-windows@npm:^1.0.0, is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": +"is-windows@npm:1.0.2, is-windows@npm:^1.0.0, is-windows@npm:^1.0.1": version: 1.0.2 resolution: "is-windows@npm:1.0.2" checksum: 438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 languageName: node linkType: hard -"is-word-character@npm:^1.0.0": - version: 1.0.4 - resolution: "is-word-character@npm:1.0.4" - checksum: 1821d6c6abe5bc0b3abe3fdc565d66d7c8a74ea4e93bc77b4a47d26e2e2a306d6ab7d92b353b0d2b182869e3ecaa8f4a346c62d0e31d38ebc0ceaf7cae182c3f - languageName: node - linkType: hard - "is-wsl@npm:2.2.0, is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -25150,13 +24217,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab - languageName: node - linkType: hard - "isarray@npm:2.0.5, isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -25164,6 +24224,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + "isbuffer@npm:~0.0.0": version: 0.0.0 resolution: "isbuffer@npm:0.0.0" @@ -25178,29 +24245,13 @@ __metadata: languageName: node linkType: hard -"isobject@npm:^2.0.0": - version: 2.1.0 - resolution: "isobject@npm:2.1.0" - dependencies: - isarray: 1.0.0 - checksum: 811c6f5a866877d31f0606a88af4a45f282544de886bf29f6a34c46616a1ae2ed17076cc6bf34c0128f33eecf7e1fcaa2c82cf3770560d3e26810894e96ae79f - languageName: node - linkType: hard - -"isobject@npm:^3.0.0, isobject@npm:^3.0.1": +"isobject@npm:^3.0.1": version: 3.0.1 resolution: "isobject@npm:3.0.1" checksum: db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 languageName: node linkType: hard -"isobject@npm:^4.0.0": - version: 4.0.0 - resolution: "isobject@npm:4.0.0" - checksum: bbcb522e46d54fb22418ba49fb9a82057ffa201c8401fb6e018c042e2c98cf7d9c7b185aff88e035ec8adea0814506dc2aeff2d08891bbc158e1671a49e99c06 - languageName: node - linkType: hard - "isomorphic-fetch@npm:^3.0.0": version: 3.0.0 resolution: "isomorphic-fetch@npm:3.0.0" @@ -25211,17 +24262,7 @@ __metadata: languageName: node linkType: hard -"isomorphic-unfetch@npm:^3.1.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: ^2.6.1 - unfetch: ^4.2.0 - checksum: 82b92fe4ec2823a81ab0fc0d11bd94d710e6f9a940d56b3cba31896d4345ec9ffc7949f4ff31ebcae84f6b95f7ebf3474c4c7452b834eb4078ea3f2c37e459c5 - languageName: node - linkType: hard - -"isomorphic-ws@npm:5.0.0, isomorphic-ws@npm:^5.0.0": +"isomorphic-ws@npm:^5.0.0": version: 5.0.0 resolution: "isomorphic-ws@npm:5.0.0" peerDependencies: @@ -25278,24 +24319,7 @@ __metadata: languageName: node linkType: hard -"iterate-iterator@npm:^1.0.1": - version: 1.0.2 - resolution: "iterate-iterator@npm:1.0.2" - checksum: 97b3ed4f2bebe038be57d03277879e406b2c537ceeeab7f82d4167f9a3cff872cc2cc5da3dc9920ff544ca247329d2a4d44121bb8ef8d0807a72176bdbc17c84 - languageName: node - linkType: hard - -"iterate-value@npm:^1.0.2": - version: 1.0.2 - resolution: "iterate-value@npm:1.0.2" - dependencies: - es-get-iterator: ^1.0.2 - iterate-iterator: ^1.0.1 - checksum: 446a4181657df1872e5020713206806757157db6ab375dee05eb4565b66e1244d7a99cd36ce06862261ad4bd059e66ba8192f62b5d1ff41d788c3b61953af6c3 - languageName: node - linkType: hard - -"jackspeak@npm:^2.0.3": +"jackspeak@npm:^2.0.3, jackspeak@npm:^2.3.5": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" dependencies: @@ -25360,28 +24384,26 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-haste-map@npm:26.6.2" +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" dependencies: - "@jest/types": ^26.6.2 - "@types/graceful-fs": ^4.1.2 + "@jest/types": ^29.6.3 + "@types/graceful-fs": ^4.1.3 "@types/node": "*" anymatch: ^3.0.3 fb-watchman: ^2.0.0 - fsevents: ^2.1.2 - graceful-fs: ^4.2.4 - jest-regex-util: ^26.0.0 - jest-serializer: ^26.6.2 - jest-util: ^26.6.2 - jest-worker: ^26.6.2 - micromatch: ^4.0.2 - sane: ^4.0.3 - walker: ^1.0.7 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^29.6.3 + jest-util: ^29.7.0 + jest-worker: ^29.7.0 + micromatch: ^4.0.4 + walker: ^1.0.8 dependenciesMeta: fsevents: optional: true - checksum: 8ad5236d5646d2388d2bd58a57ea53698923434f43d59ea9ebdc58bce4d0b8544c8de2f7acaa9a6d73171f04460388b2b6d7d6b6c256aea4ebb8780140781596 + checksum: c2c8f2d3e792a963940fbdfa563ce14ef9e14d4d86da645b96d3cd346b8d35c5ce0b992ee08593939b5f718cf0a1f5a90011a056548a1dbf58397d4356786f01 languageName: node linkType: hard @@ -25424,34 +24446,10 @@ __metadata: languageName: node linkType: hard -"jest-regex-util@npm:^26.0.0": - version: 26.0.0 - resolution: "jest-regex-util@npm:26.0.0" - checksum: 930a00665e8dfbedc29140678b4a54f021b41b895cf35050f76f557c1da3ac48ff42dd7b18ba2ccba6f4e518c6445d6753730d03ec7049901b93992db1ef0483 - languageName: node - linkType: hard - -"jest-serializer@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-serializer@npm:26.6.2" - dependencies: - "@types/node": "*" - graceful-fs: ^4.2.4 - checksum: dbecfb0d01462fe486a0932cf1680cf6abb204c059db2a8f72c6c2a7c9842a82f6d256874112774cea700764ed8f38fc9e3db982456c138d87353e3390e746fe - languageName: node - linkType: hard - -"jest-util@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-util@npm:26.6.2" - dependencies: - "@jest/types": ^26.6.2 - "@types/node": "*" - chalk: ^4.0.0 - graceful-fs: ^4.2.4 - is-ci: ^2.0.0 - micromatch: ^4.0.2 - checksum: 3c6a5fba05c4c6892cd3a9f66196ea8867087b77a5aa1a3f6cd349c785c3f1ca24abfd454664983aed1a165cab7846688e44fe8630652d666ba326b08625bc3d +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a languageName: node linkType: hard @@ -25469,14 +24467,17 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-worker@npm:26.6.2" +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" dependencies: + "@jest/types": ^29.6.3 "@types/node": "*" - merge-stream: ^2.0.0 - supports-color: ^7.0.0 - checksum: f9afa3b88e3f12027901e4964ba3ff048285b5783b5225cab28fac25b4058cea8ad54001e9a1577ee2bed125fac3ccf5c80dc507b120300cc1bbcb368796533e + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: 042ab4980f4ccd4d50226e01e5c7376a8556b472442ca6091a8f102488c0f22e6e8b89ea874111d2328a2080083bf3225c86f3788c52af0bd0345a00eb57a3ca languageName: node linkType: hard @@ -25491,6 +24492,18 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "*" + jest-util: ^29.7.0 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 30fff60af49675273644d408b650fc2eb4b5dcafc5a0a455f238322a8f9d8a98d847baca9d51ff197b6747f54c7901daa2287799230b856a0f48287d131f8c13 + languageName: node + linkType: hard + "jimp@npm:^0.16.1": version: 0.16.1 resolution: "jimp@npm:0.16.1" @@ -25504,7 +24517,16 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.17.1, jiti@npm:^1.18.2": +"jiti@npm:^1.17.1": + version: 1.21.0 + resolution: "jiti@npm:1.21.0" + bin: + jiti: bin/jiti.js + checksum: a7bd5d63921c170eaec91eecd686388181c7828e1fa0657ab374b9372bfc1f383cf4b039e6b272383d5cb25607509880af814a39abdff967322459cca41f2961 + languageName: node + linkType: hard + +"jiti@npm:^1.18.2": version: 1.20.0 resolution: "jiti@npm:1.20.0" bin: @@ -25513,16 +24535,16 @@ __metadata: languageName: node linkType: hard -"joi@npm:^17.7.0": - version: 17.10.2 - resolution: "joi@npm:17.10.2" +"joi@npm:^17.11.0": + version: 17.11.0 + resolution: "joi@npm:17.11.0" dependencies: "@hapi/hoek": ^9.0.0 "@hapi/topo": ^5.0.0 "@sideway/address": ^4.1.3 "@sideway/formula": ^3.0.1 "@sideway/pinpoint": ^2.0.0 - checksum: 2fac59e83b35465d04ffcd33a937c39795264bdd3d392bebee8034710f84631a400cd320a3bb0bb736e70ce930abb1ea551bc3ffbeca023b53417d864eb216a4 + checksum: 3a4e9ecba345cdafe585e7ed8270a44b39718e11dff3749aa27e0001a63d578b75100c062be28e6f48f960b594864034e7a13833f33fbd7ad56d5ce6b617f9bf languageName: node linkType: hard @@ -25554,6 +24576,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.0.0": + version: 5.1.3 + resolution: "jose@npm:5.1.3" + checksum: c0225c3408b1c3fcfc40c68161e5e14d554c606ffa428250e0db618469df52f4ce32c69918e296c8c299db1081981638096be838426f61d926269d35602fd814 + languageName: node + linkType: hard + "jpeg-js@npm:0.4.2": version: 0.4.2 resolution: "jpeg-js@npm:0.4.2" @@ -25596,13 +24625,6 @@ __metadata: languageName: node linkType: hard -"js-string-escape@npm:^1.0.1": - version: 1.0.1 - resolution: "js-string-escape@npm:1.0.1" - checksum: f11e0991bf57e0c183b55c547acec85bd2445f043efc9ea5aa68b41bd2a3e7d3ce94636cb233ae0d84064ba4c1a505d32e969813c5b13f81e7d4be12c59256fe - languageName: node - linkType: hard - "js-tiktoken@npm:^1.0.7": version: 1.0.7 resolution: "js-tiktoken@npm:1.0.7" @@ -25656,6 +24678,41 @@ __metadata: languageName: node linkType: hard +"jscodeshift@npm:^0.15.1": + version: 0.15.1 + resolution: "jscodeshift@npm:0.15.1" + dependencies: + "@babel/core": ^7.23.0 + "@babel/parser": ^7.23.0 + "@babel/plugin-transform-class-properties": ^7.22.5 + "@babel/plugin-transform-modules-commonjs": ^7.23.0 + "@babel/plugin-transform-nullish-coalescing-operator": ^7.22.11 + "@babel/plugin-transform-optional-chaining": ^7.23.0 + "@babel/plugin-transform-private-methods": ^7.22.5 + "@babel/preset-flow": ^7.22.15 + "@babel/preset-typescript": ^7.23.0 + "@babel/register": ^7.22.15 + babel-core: ^7.0.0-bridge.0 + chalk: ^4.1.2 + flow-parser: 0.* + graceful-fs: ^4.2.4 + micromatch: ^4.0.4 + neo-async: ^2.5.0 + node-dir: ^0.1.17 + recast: ^0.23.3 + temp: ^0.8.4 + write-file-atomic: ^2.3.0 + peerDependencies: + "@babel/preset-env": ^7.1.6 + peerDependenciesMeta: + "@babel/preset-env": + optional: true + bin: + jscodeshift: bin/jscodeshift.js + checksum: d760dee2b634fa8a4610bdbdf787ce117a9a6bcc73e9ae55a38be77e380698d928d34a375a93ed4685e8bbdecfbd3cdbb87eb4b7e22fc58381db3d59fb554687 + languageName: node + linkType: hard + "jsdom@npm:^22.0.0": version: 22.0.0 resolution: "jsdom@npm:22.0.0" @@ -25744,7 +24801,7 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1, json-buffer@npm:~3.0.1": +"json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" checksum: 9026b03edc2847eefa2e37646c579300a1f3a4586cfb62bf857832b60c852042d0d6ae55d1afb8926163fa54c2b01d83ae24705f34990348bdac6273a29d4581 @@ -25758,7 +24815,7 @@ __metadata: languageName: node linkType: hard -"json-parse-better-errors@npm:^1.0.1, json-parse-better-errors@npm:^1.0.2": +"json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" checksum: ff2b5ba2a70e88fd97a3cb28c1840144c5ce8fae9cbeeddba15afa333a5c407cf0e42300cd0a2885dbb055227fe68d405070faad941beeffbfde9cf3b2c78c5d @@ -25817,11 +24874,14 @@ __metadata: linkType: hard "json-stable-stringify@npm:^1.0.1": - version: 1.0.2 - resolution: "json-stable-stringify@npm:1.0.2" + version: 1.1.0 + resolution: "json-stable-stringify@npm:1.1.0" dependencies: + call-bind: ^1.0.5 + isarray: ^2.0.5 jsonify: ^0.0.1 - checksum: ec10863493fb728481ed7576551382768a173d5b884758db530def00523b862083a3fd70fee24b39e2f47f5f502e22f9a1489dd66da3535b63bf6241dbfca800 + object-keys: ^1.1.1 + checksum: 98e74dd45d3e93aa7cb5351b9f55475e15a8a7b57f401897373a1a1bbe41a6757f8b8d24f2bff0594893eccde616efe71bbaea2c1fdc1f67e8c39bcb9ee993e2 languageName: node linkType: hard @@ -25853,7 +24913,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.1": +"json5@npm:^2.1.2, json5@npm:^2.2.1": version: 2.2.1 resolution: "json5@npm:2.2.1" bin: @@ -25862,7 +24922,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.2.3": +"json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -25970,13 +25030,6 @@ __metadata: languageName: node linkType: hard -"junk@npm:^3.1.0": - version: 3.1.0 - resolution: "junk@npm:3.1.0" - checksum: 6c4d68e8f8bc25b546baed802cd0e7be6a971e92f1e885c92cbfe98946d5690b961a32f8e7909e77765d3204c3e556d13c17f73e31697ffae1db07a58b9e68c0 - languageName: node - linkType: hard - "jwa@npm:^1.4.1": version: 1.4.1 resolution: "jwa@npm:1.4.1" @@ -26082,37 +25135,11 @@ __metadata: linkType: hard "keyv@npm:^4.0.0": - version: 4.3.3 - resolution: "keyv@npm:4.3.3" + version: 4.5.4 + resolution: "keyv@npm:4.5.4" dependencies: - compress-brotli: ^1.3.8 json-buffer: 3.0.1 - checksum: bcc946eeec3407fb3b42d831ce985357162113c5f07a8c45c12ede39704ba2d99be4c3dded76d2d2d2a2366627e42440bdde24393216164156928399949c12a1 - languageName: node - linkType: hard - -"kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": - version: 3.2.2 - resolution: "kind-of@npm:3.2.2" - dependencies: - is-buffer: ^1.1.5 - checksum: e898df8ca2f31038f27d24f0b8080da7be274f986bc6ed176f37c77c454d76627619e1681f6f9d2e8d2fd7557a18ecc419a6bb54e422abcbb8da8f1a75e4b386 - languageName: node - linkType: hard - -"kind-of@npm:^4.0.0": - version: 4.0.0 - resolution: "kind-of@npm:4.0.0" - dependencies: - is-buffer: ^1.1.5 - checksum: 1b9e7624a8771b5a2489026e820f3bbbcc67893e1345804a56b23a91e9069965854d2a223a7c6ee563c45be9d8c6ff1ef87f28ed5f0d1a8d00d9dcbb067c529f - languageName: node - linkType: hard - -"kind-of@npm:^5.0.0": - version: 5.1.0 - resolution: "kind-of@npm:5.1.0" - checksum: f2a0102ae0cf19c4a953397e552571bad2b588b53282874f25fca7236396e650e2db50d41f9f516bd402536e4df968dbb51b8e69e4d5d4a7173def78448f7bab + checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72 languageName: node linkType: hard @@ -26137,7 +25164,7 @@ __metadata: languageName: node linkType: hard -"klona@npm:^2.0.4, klona@npm:^2.0.5": +"klona@npm:^2.0.4": version: 2.0.5 resolution: "klona@npm:2.0.5" checksum: 8c976126ea252b766e648a4866e1bccff9d3b08432474ad80c559f6c7265cf7caede2498d463754d8c88c4759895edd8210c85c0d3155e6aae4968362889466f @@ -26429,16 +25456,14 @@ __metadata: languageName: node linkType: hard -"lazy-universal-dotenv@npm:^3.0.1": - version: 3.0.1 - resolution: "lazy-universal-dotenv@npm:3.0.1" +"lazy-universal-dotenv@npm:^4.0.0": + version: 4.0.0 + resolution: "lazy-universal-dotenv@npm:4.0.0" dependencies: - "@babel/runtime": ^7.5.0 app-root-dir: ^1.0.2 - core-js: ^3.0.4 - dotenv: ^8.0.0 - dotenv-expand: ^5.1.0 - checksum: a80509d8cb40dafcfab5859335920754a21814320aa16115e58c0ae5ef3b1d8bd4daa96349ea548e2833f2f89269ddbb103ebd55be06cfdba00e0af6785b5ba7 + dotenv: ^16.0.0 + dotenv-expand: ^10.0.0 + checksum: 196e0d701100144fbfe078d604a477573413ebf38dfe8d543748605e6a7074978508a3bb9f8135acd319db4fa947eef78836497163617d15a22163c59a00996b languageName: node linkType: hard @@ -26568,6 +25593,13 @@ __metadata: languageName: node linkType: hard +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 638401d534585261b6003db9d99afd244dfe82d75ddb6db5c0df412842d5ab30b2ef18de471aaec70fe69a46f17b4ae3c7f01d8a4e6580ef7adb9f4273ad1e55 + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -26578,16 +25610,6 @@ __metadata: languageName: node linkType: hard -"levn@npm:~0.3.0": - version: 0.3.0 - resolution: "levn@npm:0.3.0" - dependencies: - prelude-ls: ~1.1.2 - type-check: ~0.3.2 - checksum: 0d084a524231a8246bb10fec48cdbb35282099f6954838604f3c7fc66f2e16fa66fd9cc2f3f20a541a113c4dafdf181e822c887c8a319c9195444e6c64ac395e - languageName: node - linkType: hard - "lexical@npm:^0.9.0": version: 0.9.1 resolution: "lexical@npm:0.9.1" @@ -26626,17 +25648,17 @@ __metadata: languageName: node linkType: hard -"libphonenumber-js@npm:1.10.12": - version: 1.10.12 - resolution: "libphonenumber-js@npm:1.10.12" - checksum: 8b8789b8b46f59e540108cdd3b925990e722a52134e03ab78a360312b7a001ab7f8211320338ddd60b8c68dd49d56075e16567f68aa22698e2218b69300fad42 +"libphonenumber-js@npm:1.10.51": + version: 1.10.51 + resolution: "libphonenumber-js@npm:1.10.51" + checksum: e3fd98e3cb7b2197e949f2a53744610942c831a9c3b864c6ce128f0b0ff6884b28ffd3f5f6fc6cb804a2556dfac3a07aa8c63b4bf9d36288a553bae2faea48d0 languageName: node linkType: hard -"libphonenumber-js@patch:libphonenumber-js@npm%3A1.10.12#./.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch::locator=calcom-monorepo%40workspace%3A.": - version: 1.10.12 - resolution: "libphonenumber-js@patch:libphonenumber-js@npm%3A1.10.12#./.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch::version=1.10.12&hash=1f76a8&locator=calcom-monorepo%40workspace%3A." - checksum: 9c0940ae91f19a6b69c42f685af0584b273bf7f96984ee9d7a26ec945b0473fb03546638a442cbb3ee1c76ad78801b57bec9d737b6b59ab681a909bec1edfbba +"libphonenumber-js@patch:libphonenumber-js@npm%3A1.10.51#./.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch::locator=calcom-monorepo%40workspace%3A.": + version: 1.10.51 + resolution: "libphonenumber-js@patch:libphonenumber-js@npm%3A1.10.51#./.yarn/patches/libphonenumber-js-npm-1.10.51-4ff79b15f8.patch::version=1.10.51&hash=1455d3&locator=calcom-monorepo%40workspace%3A." + checksum: 6730b41757b106a2cbd298bb51e2a794e80c37c08942530f65e5e3c54aabf2b673c551a3dc17ccfe983dd948764fd7665048584b862b439a213f5ccb2fc3355b languageName: node linkType: hard @@ -26803,19 +25825,6 @@ __metadata: languageName: node linkType: hard -"load-json-file@npm:^1.0.0": - version: 1.1.0 - resolution: "load-json-file@npm:1.1.0" - dependencies: - graceful-fs: ^4.1.2 - parse-json: ^2.2.0 - pify: ^2.0.0 - pinkie-promise: ^2.0.0 - strip-bom: ^2.0.0 - checksum: 0e4e4f380d897e13aa236246a917527ea5a14e4fc34d49e01ce4e7e2a1e08e2740ee463a03fb021c04f594f29a178f4adb994087549d7c1c5315fcd29bf9934b - languageName: node - linkType: hard - "load-json-file@npm:^4.0.0": version: 4.0.0 resolution: "load-json-file@npm:4.0.0" @@ -26853,13 +25862,6 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^2.4.0": - version: 2.4.0 - resolution: "loader-runner@npm:2.4.0" - checksum: e27eebbca5347a03f6b1d1bce5b2736a4984fb742f872c0a4d68e62de10f7637613e79a464d3bcd77c246d9c70fcac112bb4a3123010eb527e8b203a614647db - languageName: node - linkType: hard - "loader-runner@npm:^4.2.0": version: 4.3.0 resolution: "loader-runner@npm:4.3.0" @@ -26867,17 +25869,6 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^1.2.3": - version: 1.4.2 - resolution: "loader-utils@npm:1.4.2" - dependencies: - big.js: ^5.2.2 - emojis-list: ^3.0.0 - json5: ^1.0.1 - checksum: eb6fb622efc0ffd1abdf68a2022f9eac62bef8ec599cf8adb75e94d1d338381780be6278534170e99edc03380a6d29bc7eb1563c89ce17c5fed3a0b17f1ad804 - languageName: node - linkType: hard - "loader-utils@npm:^2.0.0": version: 2.0.2 resolution: "loader-utils@npm:2.0.2" @@ -26889,10 +25880,21 @@ __metadata: languageName: node linkType: hard -"loader-utils@npm:^3.2.0": - version: 3.2.0 - resolution: "loader-utils@npm:3.2.0" - checksum: c7b9a8dc4b3bc19e9ef563c48e3a18ea9f8bb2da1ad38a12e4b88358cfba5f148a7baf12d78fe78ffcb718ce1e062ab31fcf5c148459f1247a672a4213471e80 +"loader-utils@npm:^2.0.4": + version: 2.0.4 + resolution: "loader-utils@npm:2.0.4" + dependencies: + big.js: ^5.2.2 + emojis-list: ^3.0.0 + json5: ^2.1.2 + checksum: a5281f5fff1eaa310ad5e1164095689443630f3411e927f95031ab4fb83b4a98f388185bb1fe949e8ab8d4247004336a625e9255c22122b815bb9a4c5d8fc3b7 + languageName: node + linkType: hard + +"loader-utils@npm:^3.2.1": + version: 3.2.1 + resolution: "loader-utils@npm:3.2.1" + checksum: 4e3ea054cdc8be1ab1f1238f49f42fdf0483039eff920fb1d442039f3f0ad4ebd11fb8e584ccdf2cb7e3c56b3d40c1832416e6408a55651b843da288960cc792 languageName: node linkType: hard @@ -26950,6 +25952,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^7.1.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: ^6.0.0 + checksum: c1b653bdf29beaecb3d307dfb7c44d98a2a98a02ebe353c9ad055d1ac45d6ed4e1142563d222df9b9efebc2bcb7d4c792b507fad9e7150a04c29530b7db570f8 + languageName: node + linkType: hard + "lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" @@ -27111,13 +26122,6 @@ __metadata: languageName: node linkType: hard -"lodash.uniq@npm:4.5.0": - version: 4.5.0 - resolution: "lodash.uniq@npm:4.5.0" - checksum: a4779b57a8d0f3c441af13d9afe7ecff22dd1b8ce1129849f71d9bbc8e8ee4e46dfb4b7c28f7ad3d67481edd6e51126e4e2a6ee276e25906d10f7140187c392d - languageName: node - linkType: hard - "lodash@npm:4.17.21, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.0": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -27198,17 +26202,7 @@ __metadata: languageName: node linkType: hard -"loud-rejection@npm:^1.0.0": - version: 1.6.0 - resolution: "loud-rejection@npm:1.6.0" - dependencies: - currently-unhandled: ^0.4.1 - signal-exit: ^3.0.0 - checksum: 750e12defde34e8cbf263c2bff16f028a89b56e022ad6b368aa7c39495b5ac33f2349a8d00665a9b6d25c030b376396524d8a31eb0dde98aaa97956d7324f927 - languageName: node - linkType: hard - -"loupe@npm:^2.3.1, loupe@npm:^2.3.6": +"loupe@npm:^2.3.6": version: 2.3.6 resolution: "loupe@npm:2.3.6" dependencies: @@ -27390,6 +26384,15 @@ __metadata: languageName: node linkType: hard +"lz-string@npm:^1.5.0": + version: 1.5.0 + resolution: "lz-string@npm:1.5.0" + bin: + lz-string: bin/bin.js + checksum: 1ee98b4580246fd90dd54da6e346fb1caefcf05f677c686d9af237a157fdea3fd7c83a4bc58f858cd5b10a34d27afe0fdcbd0505a47e0590726a873dc8b8f65d + languageName: node + linkType: hard + "magic-string@npm:^0.25.7": version: 0.25.9 resolution: "magic-string@npm:0.25.9" @@ -27399,15 +26402,6 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.26.1, magic-string@npm:^0.26.2": - version: 0.26.2 - resolution: "magic-string@npm:0.26.2" - dependencies: - sourcemap-codec: ^1.4.8 - checksum: b4db4e2b370ac8d9ffc6443a2b591b75364bf1fc9121b5a4068d5b89804abff6709d1fa4a0e0c2d54f2e61e0e44db83efdfe219a5ab0ba6d25ee1f2b51fbed55 - languageName: node - linkType: hard - "magic-string@npm:^0.26.7": version: 0.26.7 resolution: "magic-string@npm:0.26.7" @@ -27435,6 +26429,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.5": + version: 0.30.5 + resolution: "magic-string@npm:0.30.5" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.15 + checksum: da10fecff0c0a7d3faf756913ce62bd6d5e7b0402be48c3b27bfd651b90e29677e279069a63b764bcdc1b8ecdcdb898f29a5c5ec510f2323e8d62ee057a6eb18 + languageName: node + linkType: hard + "mailhog@npm:^4.16.0": version: 4.16.0 resolution: "mailhog@npm:4.16.0" @@ -27550,23 +26553,14 @@ __metadata: languageName: node linkType: hard -"map-age-cleaner@npm:^0.1.3": - version: 0.1.3 - resolution: "map-age-cleaner@npm:0.1.3" - dependencies: - p-defer: ^1.0.0 - checksum: cb2804a5bcb3cbdfe4b59066ea6d19f5e7c8c196cd55795ea4c28f792b192e4c442426ae52524e5e1acbccf393d3bddacefc3d41f803e66453f6c4eda3650bc1 - languageName: node - linkType: hard - -"map-cache@npm:^0.2.0, map-cache@npm:^0.2.2": +"map-cache@npm:^0.2.0": version: 0.2.2 resolution: "map-cache@npm:0.2.2" checksum: 3067cea54285c43848bb4539f978a15dedc63c03022abeec6ef05c8cb6829f920f13b94bcaf04142fc6a088318e564c4785704072910d120d55dbc2e0c421969 languageName: node linkType: hard -"map-obj@npm:^1.0.0, map-obj@npm:^1.0.1": +"map-obj@npm:^1.0.0": version: 1.0.1 resolution: "map-obj@npm:1.0.1" checksum: 9949e7baec2a336e63b8d4dc71018c117c3ce6e39d2451ccbfd3b8350c547c4f6af331a4cbe1c83193d7c6b786082b6256bde843db90cb7da2a21e8fcc28afed @@ -27594,22 +26588,6 @@ __metadata: languageName: node linkType: hard -"map-visit@npm:^1.0.0": - version: 1.0.0 - resolution: "map-visit@npm:1.0.0" - dependencies: - object-visit: ^1.0.0 - checksum: c27045a5021c344fc19b9132eb30313e441863b2951029f8f8b66f79d3d8c1e7e5091578075a996f74e417479506fe9ede28c44ca7bc351a61c9d8073daec36a - languageName: node - linkType: hard - -"markdown-escapes@npm:^1.0.0": - version: 1.0.4 - resolution: "markdown-escapes@npm:1.0.4" - checksum: 6833a93d72d3f70a500658872312c6fa8015c20cc835a85ae6901fa232683fbc6ed7118ebe920fea7c80039a560f339c026597d96eee0e9de602a36921804997 - languageName: node - linkType: hard - "markdown-it@npm:^13.0.1": version: 13.0.1 resolution: "markdown-it@npm:13.0.1" @@ -27625,6 +26603,15 @@ __metadata: languageName: node linkType: hard +"markdown-to-jsx@npm:^7.1.8": + version: 7.3.2 + resolution: "markdown-to-jsx@npm:7.3.2" + peerDependencies: + react: ">= 0.14.0" + checksum: 8885c6343b71570b0a7ec16cd85a49b853a830234790ee7430e2517ea5d8d361ff138bd52147f650790f3e7b3a28a15c755fc16f8856dd01ddf09a6161782e06 + languageName: node + linkType: hard + "marked@npm:4.2.12": version: 4.2.12 resolution: "marked@npm:4.2.12" @@ -27656,15 +26643,6 @@ __metadata: languageName: node linkType: hard -"mdast-squeeze-paragraphs@npm:^4.0.0": - version: 4.0.0 - resolution: "mdast-squeeze-paragraphs@npm:4.0.0" - dependencies: - unist-util-remove: ^2.0.0 - checksum: dfe8ec8e8a62171f020e82b088cc35cb9da787736dc133a3b45ce8811782a93e69bf06d147072e281079f09fac67be8a36153ffffd9bfbf89ed284e4c4f56f75 - languageName: node - linkType: hard - "mdast-util-definitions@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-definitions@npm:4.0.0" @@ -27705,6 +26683,75 @@ __metadata: languageName: node linkType: hard +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-from-markdown@npm:2.0.0" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + mdast-util-to-string: ^4.0.0 + micromark: ^4.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 4e8d8a46b4b588486c41b80c39da333a91593bc8d60cd7421c6cd3c22003b8e5a62478292fb7bc97b9255b6301a2250cca32340ef43c309156e215453c5b92be + languageName: node + linkType: hard + +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-mdx-expression@npm:2.0.0" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 4e1183000e183e07a7264e192889b4fd57372806103031c71b9318967f85fd50a5dd0f92ef14f42c331e77410808f5de3341d7bc8ad4ee91b7fa8f0a30043a8a + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-mdx-jsx@npm:3.0.0" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + ccount: ^2.0.0 + devlop: ^1.1.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + parse-entities: ^4.0.0 + stringify-entities: ^4.0.0 + unist-util-remove-position: ^5.0.0 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 48fe1ba617205f3776ca2030d195adbdb42bb6c53326534db3f5bdd28abe7895103af8c4dfda7cbe2911e8cd71921bc8a82fe40856565e57af8b4f8a79c8c126 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 1f9dad04d31d59005332e9157ea9510dc1d03092aadbc607a10475c7eec1c158b475aa0601a3a4f74e13097ca735deb8c2d9d37928ddef25d3029fd7c9e14dc3 + languageName: node + linkType: hard + "mdast-util-phrasing@npm:^3.0.0": version: 3.0.1 resolution: "mdast-util-phrasing@npm:3.0.1" @@ -27715,19 +26762,13 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:10.0.1": - version: 10.0.1 - resolution: "mdast-util-to-hast@npm:10.0.1" +"mdast-util-phrasing@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-phrasing@npm:4.0.0" dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - mdast-util-definitions: ^4.0.0 - mdurl: ^1.0.0 - unist-builder: ^2.0.0 - unist-util-generated: ^1.0.0 - unist-util-position: ^3.0.0 - unist-util-visit: ^2.0.0 - checksum: e5f385757df7e9b37db4d6f326bf7b4fc1b40f9ad01fc335686578f44abe0ba46d3e60af4d5e5b763556d02e65069ef9a09c49db049b52659203a43e7fa9084d + "@types/mdast": ^4.0.0 + unist-util-is: ^6.0.0 + checksum: 95d5d8e18d5ea6dbfe2ee4ed1045961372efae9077e5c98e10bfef7025ee3fd9449f9a82840068ff50aa98fa43af0a0a14898ae10b5e46e96edde01e2797df34 languageName: node linkType: hard @@ -27748,6 +26789,22 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-hast@npm:^13.0.0": + version: 13.0.2 + resolution: "mdast-util-to-hast@npm:13.0.2" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 + trim-lines: ^3.0.0 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + checksum: 8fef6c3752476461d9c00b1dea4f141bc7d980e1b3bac7bd965bc68f532b6d30fb1c9e810433327c167176e68e071b8f4ab5a45355954857dc095c878421f35e + languageName: node + linkType: hard + "mdast-util-to-markdown@npm:^1.0.0": version: 1.5.0 resolution: "mdast-util-to-markdown@npm:1.5.0" @@ -27764,6 +26821,22 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-markdown@npm:^2.0.0": + version: 2.1.0 + resolution: "mdast-util-to-markdown@npm:2.1.0" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + longest-streak: ^3.0.0 + mdast-util-phrasing: ^4.0.0 + mdast-util-to-string: ^4.0.0 + micromark-util-decode-string: ^2.0.0 + unist-util-visit: ^5.0.0 + zwitch: ^2.0.0 + checksum: 3a2cf3957e23b34e2e092e6e76ae72ee0b8745955bd811baba6814cf3a3d916c3fd52264b4b58f3bb3d512a428f84a1e998b6fc7e28434e388a9ae8fb6a9c173 + languageName: node + linkType: hard + "mdast-util-to-string@npm:^1.0.0": version: 1.1.0 resolution: "mdast-util-to-string@npm:1.1.0" @@ -27780,6 +26853,15 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": ^4.0.0 + checksum: 35489fb5710d58cbc2d6c8b6547df161a3f81e0f28f320dfb3548a9393555daf07c310c0c497708e67ed4dfea4a06e5655799e7d631ca91420c288b4525d6c29 + languageName: node + linkType: hard + "mdurl@npm:^1.0.0, mdurl@npm:^1.0.1": version: 1.0.1 resolution: "mdurl@npm:1.0.1" @@ -27794,31 +26876,12 @@ __metadata: languageName: node linkType: hard -"mem@npm:^8.1.1": - version: 8.1.1 - resolution: "mem@npm:8.1.1" +"memfs@npm:^3.4.1, memfs@npm:^3.4.12": + version: 3.5.3 + resolution: "memfs@npm:3.5.3" dependencies: - map-age-cleaner: ^0.1.3 - mimic-fn: ^3.1.0 - checksum: c41bc97f6f82b91899206058989e34bcb1543af40413c2ab59e5a8e97e4f8f2188d62e7bd95b2d575d5b0d823d5034a0f274a0676f6d11a0e0b973898b06c8b1 - languageName: node - linkType: hard - -"memfs@npm:^3.1.2": - version: 3.4.7 - resolution: "memfs@npm:3.4.7" - dependencies: - fs-monkey: ^1.0.3 - checksum: fab88266dc576dc4999e38bdf531d703fb798affac2e0dd3fc17470878486844027b2766008ba80c0103b443f52cf9068a5c00f4e1ecf04106f4b29c11855822 - languageName: node - linkType: hard - -"memfs@npm:^3.2.2": - version: 3.4.9 - resolution: "memfs@npm:3.4.9" - dependencies: - fs-monkey: ^1.0.3 - checksum: 575dfde73f4105db42ffc2fdcd06efb9037541de095bd4fe9fb29134a1a775dfe922839b30db632de76cb29354d79d8f82a5e4f5f588e21bc47358b6d058a9c6 + fs-monkey: ^1.0.4 + checksum: 18dfdeacad7c8047b976a6ccd58bc98ba76e122ad3ca0e50a21837fe2075fc0d9aafc58ab9cf2576c2b6889da1dd2503083f2364191b695273f40969db2ecc44 languageName: node linkType: hard @@ -27845,26 +26908,6 @@ __metadata: languageName: node linkType: hard -"memory-fs@npm:^0.4.1": - version: 0.4.1 - resolution: "memory-fs@npm:0.4.1" - dependencies: - errno: ^0.1.3 - readable-stream: ^2.0.1 - checksum: 6db6c8682eff836664ca9b5b6052ae38d21713dda9d0ef4700fa5c0599a8bc16b2093bee75ac3dedbe59fb2222d368f25bafaa62ba143c41051359cbcb005044 - languageName: node - linkType: hard - -"memory-fs@npm:^0.5.0": - version: 0.5.0 - resolution: "memory-fs@npm:0.5.0" - dependencies: - errno: ^0.1.3 - readable-stream: ^2.0.1 - checksum: a9f25b0a8ecfb7324277393f19ef68e6ba53b9e6e4b526bbf2ba23055c5440fbf61acc7bf66bfd980e9eb4951a4790f6f777a9a3abd36603f22c87e8a64d3d6b - languageName: node - linkType: hard - "memory-pager@npm:^1.0.2": version: 1.5.0 resolution: "memory-pager@npm:1.5.0" @@ -27879,24 +26922,6 @@ __metadata: languageName: node linkType: hard -"meow@npm:^3.1.0": - version: 3.7.0 - resolution: "meow@npm:3.7.0" - dependencies: - camelcase-keys: ^2.0.0 - decamelize: ^1.1.2 - loud-rejection: ^1.0.0 - map-obj: ^1.0.1 - minimist: ^1.1.3 - normalize-package-data: ^2.3.4 - object-assign: ^4.0.1 - read-pkg-up: ^1.0.1 - redent: ^1.0.0 - trim-newlines: ^1.0.0 - checksum: 65a412e5d0d643615508007a9292799bb3e4e690597d54c9e98eb0ca3adb7b8ca8899f41ea7cb7d8277129cdcd9a1a60202b31f88e0034e6aaae02894d80999a - languageName: node - linkType: hard - "meow@npm:^6.0.0": version: 6.1.1 resolution: "meow@npm:6.1.1" @@ -27964,7 +26989,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.2.3, merge2@npm:^1.3.0, merge2@npm:^1.4.1": +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 @@ -28003,13 +27028,6 @@ __metadata: languageName: node linkType: hard -"microevent.ts@npm:~0.1.1": - version: 0.1.1 - resolution: "microevent.ts@npm:0.1.1" - checksum: 7874fcdb3f0dfa4e996d3ea63b3b9882874ae7d22be28d51ae20da24c712e9e28e5011d988095c27dd2b32e37c0ad7425342a71b89adb8e808ec7194fadf4a7a - languageName: node - linkType: hard - "micromark-core-commonmark@npm:^1.0.1": version: 1.1.0 resolution: "micromark-core-commonmark@npm:1.1.0" @@ -28034,6 +27052,30 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-core-commonmark@npm:2.0.0" + dependencies: + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-factory-destination: ^2.0.0 + micromark-factory-label: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-factory-title: ^2.0.0 + micromark-factory-whitespace: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-html-tag-name: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 9c12fb580cf4ce71f60872043bd2794efe129f44d7b2b73afa155bbc0a66b7bc35655ba8cef438a6bd068441837ed3b6dc6ad7e5a18f815462c1750793e03a42 + languageName: node + linkType: hard + "micromark-factory-destination@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-destination@npm:1.1.0" @@ -28045,6 +27087,17 @@ __metadata: languageName: node linkType: hard +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-factory-destination@npm:2.0.0" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: d36e65ed1c072ff4148b016783148ba7c68a078991154625723e24bda3945160268fb91079fb28618e1613c2b6e70390a8ddc544c45410288aa27b413593071a + languageName: node + linkType: hard + "micromark-factory-label@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-label@npm:1.1.0" @@ -28057,6 +27110,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-label@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-factory-label@npm:2.0.0" + dependencies: + devlop: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: c021dbd0ed367610d35f2bae21209bc804d1a6d1286ffce458fd6a717f4d7fe581a7cba7d5c2d7a63757c44eb927c80d6a571d6ea7969fae1b48ab6461d109c4 + languageName: node + linkType: hard + "micromark-factory-space@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-space@npm:1.1.0" @@ -28067,6 +27132,16 @@ __metadata: languageName: node linkType: hard +"micromark-factory-space@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-factory-space@npm:2.0.0" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 4ffdcdc2f759887bbb356500cb460b3915ecddcb5d85c3618d7df68ad05d13ed02b1153ee1845677b7d8126df8f388288b84fcf0d943bd9c92bcc71cd7222e37 + languageName: node + linkType: hard + "micromark-factory-title@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-title@npm:1.1.0" @@ -28079,6 +27154,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-title@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-factory-title@npm:2.0.0" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 39e1ac23af3554e6e652e56065579bc7faf21ade7b8704b29c175871b4152b7109b790bb3cae0f7e088381139c6bac9553b8400772c3d322e4fa635f813a3578 + languageName: node + linkType: hard + "micromark-factory-whitespace@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-whitespace@npm:1.1.0" @@ -28091,6 +27178,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-factory-whitespace@npm:2.0.0" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 9587c2546d1a58b4d5472b42adf05463f6212d0449455285662d63cd8eaed89c6b159ac82713fcee5f9dd88628c24307d9533cccd8971a2f3f4d48702f8f850a + languageName: node + linkType: hard + "micromark-util-character@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-character@npm:1.2.0" @@ -28101,6 +27200,16 @@ __metadata: languageName: node linkType: hard +"micromark-util-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-character@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 318d6e16fdcbe9d89e18b8e7796568d986abbb10a9f3037b7ac9b92a236bcc962f3cd380e26a7c49df40fd1d9ca33eb546268956345b662f4c4ca4962c7695f2 + languageName: node + linkType: hard + "micromark-util-chunked@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-chunked@npm:1.1.0" @@ -28110,6 +27219,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-chunked@npm:2.0.0" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: 324f95cccdae061332a8241936eaba6ef0782a1e355bac5c607ad2564fd3744929be7dc81651315a2921535747a33243e6a5606bcb64b7a56d49b6d74ea1a3d4 + languageName: node + linkType: hard + "micromark-util-classify-character@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-classify-character@npm:1.1.0" @@ -28121,6 +27239,17 @@ __metadata: languageName: node linkType: hard +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-classify-character@npm:2.0.0" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 086e52904deffebb793fb1c08c94aabb8901f76958142dfc3a6282890ebaa983b285e69bd602b9d507f1b758ed38e75a994d2ad9fbbefa7de2584f67a16af405 + languageName: node + linkType: hard + "micromark-util-combine-extensions@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-combine-extensions@npm:1.1.0" @@ -28131,6 +27260,16 @@ __metadata: languageName: node linkType: hard +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-combine-extensions@npm:2.0.0" + dependencies: + micromark-util-chunked: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 107c47700343f365b4ed81551e18bc3458b573c500e56ac052b2490bd548adc475216e41d2271633a8867fac66fc22ba3e0a2d74a31ed79b9870ca947eb4e3ba + languageName: node + linkType: hard + "micromark-util-decode-numeric-character-reference@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" @@ -28140,6 +27279,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: 9512507722efd2033a9f08715eeef787fbfe27e23edf55db21423d46d82ab46f76c89b4f960be3f5e50a2d388d89658afc0647989cf256d051e9ea01277a1adb + languageName: node + linkType: hard + "micromark-util-decode-string@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-string@npm:1.1.0" @@ -28152,6 +27300,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-decode-string@npm:2.0.0" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: a75daf32a4a6b549e9f19b4d833ebfeb09a32a9a1f9ce50f35dec6b6a3e4f9f121f49024ba7f9c91c55ebe792f7c7a332fc9604795181b6a612637df0df5b959 + languageName: node + linkType: hard + "micromark-util-encode@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-encode@npm:1.1.0" @@ -28159,6 +27319,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-encode@npm:2.0.0" + checksum: 853a3f33fce72aaf4ffa60b7f2b6fcfca40b270b3466e1b96561b02185d2bd8c01dd7948bc31a24ac014f4cc854e545ca9a8e9cf7ea46262f9d24c9e88551c66 + languageName: node + linkType: hard + "micromark-util-html-tag-name@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-html-tag-name@npm:1.2.0" @@ -28166,6 +27333,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-html-tag-name@npm:2.0.0" + checksum: d786d4486f93eb0ac5b628779809ca97c5dc60f3c9fc03eb565809831db181cf8cb7f05f9ac76852f3eb35461af0f89fa407b46f3a03f4f97a96754d8dc540d8 + languageName: node + linkType: hard + "micromark-util-normalize-identifier@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-normalize-identifier@npm:1.1.0" @@ -28175,6 +27349,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-normalize-identifier@npm:2.0.0" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: b36da2d3fd102053dadd953ce5c558328df12a63a8ac0e5aad13d4dda8e43b6a5d4a661baafe0a1cd8a260bead4b4a8e6e0e74193dd651e8484225bd4f4e68aa + languageName: node + linkType: hard + "micromark-util-resolve-all@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-resolve-all@npm:1.1.0" @@ -28184,6 +27367,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-resolve-all@npm:2.0.0" + dependencies: + micromark-util-types: ^2.0.0 + checksum: 31fe703b85572cb3f598ebe32750e59516925c7ff1f66cfe6afaebe0771a395a9eaa770787f2523d3c46082ea80e6c14f83643303740b3d650af7c96ebd30ccc + languageName: node + linkType: hard + "micromark-util-sanitize-uri@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-sanitize-uri@npm:1.2.0" @@ -28195,6 +27387,17 @@ __metadata: languageName: node linkType: hard +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-sanitize-uri@npm:2.0.0" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: ea4c28bbffcf2430e9aff2d18554296789a8b0a1f54ac24020d1dde76624a7f93e8f2a83e88cd5a846b6d2c4287b71b1142d1b89fa7f1b0363a9b33711a141fe + languageName: node + linkType: hard + "micromark-util-subtokenize@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-subtokenize@npm:1.1.0" @@ -28207,6 +27410,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-subtokenize@npm:2.0.0" + dependencies: + devlop: ^1.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 77d9c7d59c05a20468d49ce2a3640e9cb268c083ccad02322f26c84e1094c25b44f4b8139ef0a247ca11a4fef7620c5bf82fbffd98acdb2989e79cbe7bd8f1db + languageName: node + linkType: hard + "micromark-util-symbol@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-symbol@npm:1.1.0" @@ -28214,6 +27429,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-symbol@npm:2.0.0" + checksum: fa4a05bff575d9fbf0ad96a1013003e3bb6087ed6b34b609a141b6c0d2137b57df594aca409a95f4c5fda199f227b56a7d8b1f82cea0768df161d8a3a3660764 + languageName: node + linkType: hard + "micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": version: 1.1.0 resolution: "micromark-util-types@npm:1.1.0" @@ -28221,6 +27443,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-types@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-util-types@npm:2.0.0" + checksum: 819fef3ab5770c37893d2a60381fb2694396c8d22803b6e103c830c3a1bc1490363c2b0470bb2acaaddad776dfbc2fc1fcfde39cb63c4f54d95121611672e3d0 + languageName: node + linkType: hard + "micromark@npm:^3.0.0": version: 3.2.0 resolution: "micromark@npm:3.2.0" @@ -28246,24 +27475,28 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^3.1.10, micromatch@npm:^3.1.4": - version: 3.1.10 - resolution: "micromatch@npm:3.1.10" +"micromark@npm:^4.0.0": + version: 4.0.0 + resolution: "micromark@npm:4.0.0" dependencies: - arr-diff: ^4.0.0 - array-unique: ^0.3.2 - braces: ^2.3.1 - define-property: ^2.0.2 - extend-shallow: ^3.0.2 - extglob: ^2.0.4 - fragment-cache: ^0.2.1 - kind-of: ^6.0.2 - nanomatch: ^1.2.9 - object.pick: ^1.3.0 - regex-not: ^1.0.0 - snapdragon: ^0.8.1 - to-regex: ^3.0.2 - checksum: ad226cba4daa95b4eaf47b2ca331c8d2e038d7b41ae7ed0697cde27f3f1d6142881ab03d4da51b65d9d315eceb5e4cdddb3fbb55f5f72cfa19cf3ea469d054dc + "@types/debug": ^4.0.0 + debug: ^4.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: b84ab5ab1a0b28c063c52e9c2c9d7d44b954507235c10c9492d66e0b38f7de24bf298f914a1fbdf109f2a57a88cf0412de217c84cfac5fd60e3e42a74dbac085 languageName: node linkType: hard @@ -28296,7 +27529,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.30, mime-types@npm:^2.1.35, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.25, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -28314,7 +27547,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.4.4, mime@npm:^2.4.6": +"mime@npm:^2.0.3, mime@npm:^2.4.6": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -28330,7 +27563,7 @@ __metadata: languageName: node linkType: hard -"mimic-fn@npm:^3.0.0, mimic-fn@npm:^3.1.0": +"mimic-fn@npm:^3.0.0": version: 3.1.0 resolution: "mimic-fn@npm:3.1.0" checksum: f7b167f9115b8bbdf2c3ee55dce9149d14be9e54b237259c4bc1d8d0512ea60f25a1b323f814eb1fe8f5a541662804bcfcfff3202ca58df143edb986849d58db @@ -28360,7 +27593,7 @@ __metadata: languageName: node linkType: hard -"min-indent@npm:^1.0.0": +"min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" checksum: bfc6dd03c5eaf623a4963ebd94d087f6f4bbbfd8c41329a7f09706b0cb66969c4ddd336abeb587bc44bc6f08e13bf90f0b374f9d71f9f01e04adc2cd6f083ef1 @@ -28437,14 +27670,14 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.1.0, minimist@npm:^1.2.7": +"minimist@npm:^1.1.0, minimist@npm:^1.2.3, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 languageName: node linkType: hard -"minimist@npm:^1.1.1, minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.7 resolution: "minimist@npm:1.2.7" checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec @@ -28484,7 +27717,7 @@ __metadata: languageName: node linkType: hard -"minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4": +"minipass-pipeline@npm:^1.2.4": version: 1.2.4 resolution: "minipass-pipeline@npm:1.2.4" dependencies: @@ -28527,6 +27760,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + "minipass@npm:^5.0.0 || ^6.0.2": version: 6.0.2 resolution: "minipass@npm:6.0.2" @@ -28551,34 +27791,6 @@ __metadata: languageName: node linkType: hard -"mississippi@npm:^3.0.0": - version: 3.0.0 - resolution: "mississippi@npm:3.0.0" - dependencies: - concat-stream: ^1.5.0 - duplexify: ^3.4.2 - end-of-stream: ^1.1.0 - flush-write-stream: ^1.0.0 - from2: ^2.1.0 - parallel-transform: ^1.1.0 - pump: ^3.0.0 - pumpify: ^1.3.3 - stream-each: ^1.1.0 - through2: ^2.0.0 - checksum: 84b3d9889621d293f9a596bafe60df863b330c88fc19215ced8f603c605fc7e1bf06f8e036edf301bd630a03fd5d9d7d23d5d6b9a4802c30ca864d800f0bd9f8 - languageName: node - linkType: hard - -"mixin-deep@npm:^1.2.0": - version: 1.3.2 - resolution: "mixin-deep@npm:1.3.2" - dependencies: - for-in: ^1.0.2 - is-extendable: ^1.0.1 - checksum: 820d5a51fcb7479f2926b97f2c3bb223546bc915e6b3a3eb5d906dda871bba569863595424a76682f2b15718252954644f3891437cb7e3f220949bed54b1750d - languageName: node - linkType: hard - "mixme@npm:^0.5.1": version: 0.5.9 resolution: "mixme@npm:0.5.9" @@ -28595,6 +27807,13 @@ __metadata: languageName: node linkType: hard +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + "mkdirp-promise@npm:^1.0.0": version: 1.1.0 resolution: "mkdirp-promise@npm:1.1.0" @@ -28604,7 +27823,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.5": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -28778,20 +27997,6 @@ __metadata: languageName: node linkType: hard -"move-concurrently@npm:^1.0.1": - version: 1.0.1 - resolution: "move-concurrently@npm:1.0.1" - dependencies: - aproba: ^1.1.1 - copy-concurrently: ^1.0.0 - fs-write-stream-atomic: ^1.0.8 - mkdirp: ^0.5.1 - rimraf: ^2.5.4 - run-queue: ^1.0.3 - checksum: 4ea3296c150b09e798177847f673eb5783f8ca417ba806668d2c631739f653e1a735f19fb9b6e2f5e25ee2e4c0a6224732237a8e4f84c764e99d7462d258209e - languageName: node - linkType: hard - "mqtt-packet@npm:^6.8.0": version: 6.10.0 resolution: "mqtt-packet@npm:6.10.0" @@ -28832,7 +28037,7 @@ __metadata: languageName: node linkType: hard -"mri@npm:^1.1.0": +"mri@npm:^1.1.0, mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" checksum: 83f515abbcff60150873e424894a2f65d68037e5a7fcde8a9e2b285ee9c13ac581b63cfc1e6826c4732de3aeb84902f7c1e16b7aff46cd3f897a0f757a894e85 @@ -28853,13 +28058,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.1": - version: 2.1.1 - resolution: "ms@npm:2.1.1" - checksum: 0078a23cd916a9a7435c413caa14c57d4b4f6e2470e0ab554b6964163c8a4436448ac7ae020e883685475da6b6796cc396b670f579cb275db288a21e3e57721e - languageName: node - linkType: hard - "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -28978,15 +28176,6 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.12.1": - version: 2.16.0 - resolution: "nan@npm:2.16.0" - dependencies: - node-gyp: latest - checksum: cb16937273ea55b01ea47df244094c12297ce6b29b36e845d349f1f7c268b8d7c5abd126a102c5678a1e1afd0d36bba35ea0cc959e364928ce60561c9306064a - languageName: node - linkType: hard - "nanoclone@npm:^0.2.1": version: 0.2.1 resolution: "nanoclone@npm:0.2.1" @@ -29003,7 +28192,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.1, nanoid@npm:^3.3.4": +"nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" bin: @@ -29021,22 +28210,19 @@ __metadata: languageName: node linkType: hard -"nanomatch@npm:^1.2.9": - version: 1.2.13 - resolution: "nanomatch@npm:1.2.13" - dependencies: - arr-diff: ^4.0.0 - array-unique: ^0.3.2 - define-property: ^2.0.2 - extend-shallow: ^3.0.2 - fragment-cache: ^0.2.1 - is-windows: ^1.0.2 - kind-of: ^6.0.2 - object.pick: ^1.3.0 - regex-not: ^1.0.0 - snapdragon: ^0.8.1 - to-regex: ^3.0.1 - checksum: 54d4166d6ef08db41252eb4e96d4109ebcb8029f0374f9db873bd91a1f896c32ec780d2a2ea65c0b2d7caf1f28d5e1ea33746a470f32146ac8bba821d80d38d8 +"nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" + bin: + nanoid: bin/nanoid.cjs + checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2 + languageName: node + linkType: hard + +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 06c14271ee966e108d55ae109f340976a9556c8603e888037145d6522726aebe89dd0c861b4b83947feaf6d39e79e08817559e8693deedc2c94e82c5cbd090c7 languageName: node linkType: hard @@ -29075,20 +28261,13 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.5.0, neo-async@npm:^2.6.0, neo-async@npm:^2.6.1, neo-async@npm:^2.6.2": +"neo-async@npm:^2.5.0, neo-async@npm:^2.6.0, neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 languageName: node linkType: hard -"nested-error-stacks@npm:^2.0.0, nested-error-stacks@npm:^2.1.0": - version: 2.1.1 - resolution: "nested-error-stacks@npm:2.1.1" - checksum: 5f452fad75db8480b4db584e1602894ff5977f8bf3d2822f7ba5cb7be80e89adf1fffa34dada3347ef313a4288850b4486eb0635b315c32bdfb505577e8880e3 - languageName: node - linkType: hard - "new-github-issue-url@npm:0.2.1": version: 0.2.1 resolution: "new-github-issue-url@npm:0.2.1" @@ -29375,6 +28554,15 @@ __metadata: languageName: node linkType: hard +"node-abi@npm:^3.3.0": + version: 3.52.0 + resolution: "node-abi@npm:3.52.0" + dependencies: + semver: ^7.3.5 + checksum: 2ef47937d058fa1f0817294fe5ac3ec1d370d3f8eb4931ea920b7e147033390058d3bc35b64d9161036ad2fda191aa1155005cea20ec50984312637221559354 + languageName: node + linkType: hard + "node-abort-controller@npm:^3.0.1": version: 3.0.1 resolution: "node-abort-controller@npm:3.0.1" @@ -29382,7 +28570,16 @@ __metadata: languageName: node linkType: hard -"node-dir@npm:^0.1.10": +"node-addon-api@npm:^6.1.0": + version: 6.1.0 + resolution: "node-addon-api@npm:6.1.0" + dependencies: + node-gyp: latest + checksum: 3a539510e677cfa3a833aca5397300e36141aca064cdc487554f2017110709a03a95da937e98c2a14ec3c626af7b2d1b6dabe629a481f9883143d0d5bff07bf2 + languageName: node + linkType: hard + +"node-dir@npm:^0.1.17": version: 0.1.17 resolution: "node-dir@npm:0.1.17" dependencies: @@ -29398,6 +28595,13 @@ __metadata: languageName: node linkType: hard +"node-fetch-native@npm:^1.4.0": + version: 1.4.1 + resolution: "node-fetch-native@npm:1.4.1" + checksum: 339001ad3235a09b195198df8be71b591eec4064a2fcfb7f54b9f0716f6ccb3bda5828e1746f809a6d2edb062a0330e5798f408396c33b3b88339c73d6e9575d + languageName: node + linkType: hard + "node-fetch@npm:2.6.12": version: 2.6.12 resolution: "node-fetch@npm:2.6.12" @@ -29426,7 +28630,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.0.0, node-fetch@npm:^2.6.12": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -29496,37 +28700,6 @@ __metadata: languageName: node linkType: hard -"node-libs-browser@npm:^2.2.1": - version: 2.2.1 - resolution: "node-libs-browser@npm:2.2.1" - dependencies: - assert: ^1.1.1 - browserify-zlib: ^0.2.0 - buffer: ^4.3.0 - console-browserify: ^1.1.0 - constants-browserify: ^1.0.0 - crypto-browserify: ^3.11.0 - domain-browser: ^1.1.1 - events: ^3.0.0 - https-browserify: ^1.0.0 - os-browserify: ^0.3.0 - path-browserify: 0.0.1 - process: ^0.11.10 - punycode: ^1.2.4 - querystring-es3: ^0.2.0 - readable-stream: ^2.3.3 - stream-browserify: ^2.0.1 - stream-http: ^2.7.2 - string_decoder: ^1.0.0 - timers-browserify: ^2.0.4 - tty-browserify: 0.0.0 - url: ^0.11.0 - util: ^0.11.0 - vm-browserify: ^1.0.1 - checksum: 41fa7927378edc0cb98a8cc784d3f4a47e43378d3b42ec57a23f81125baa7287c4b54d6d26d062072226160a3ce4d8b7a62e873d2fb637aceaddf71f5a26eca0 - languageName: node - linkType: hard - "node-mocks-http@npm:^1.11.0": version: 1.12.2 resolution: "node-mocks-http@npm:1.12.2" @@ -29545,6 +28718,41 @@ __metadata: languageName: node linkType: hard +"node-polyfill-webpack-plugin@npm:^2.0.1": + version: 2.0.1 + resolution: "node-polyfill-webpack-plugin@npm:2.0.1" + dependencies: + assert: ^2.0.0 + browserify-zlib: ^0.2.0 + buffer: ^6.0.3 + console-browserify: ^1.2.0 + constants-browserify: ^1.0.0 + crypto-browserify: ^3.12.0 + domain-browser: ^4.22.0 + events: ^3.3.0 + filter-obj: ^2.0.2 + https-browserify: ^1.0.0 + os-browserify: ^0.3.0 + path-browserify: ^1.0.1 + process: ^0.11.10 + punycode: ^2.1.1 + querystring-es3: ^0.2.1 + readable-stream: ^4.0.0 + stream-browserify: ^3.0.0 + stream-http: ^3.2.0 + string_decoder: ^1.3.0 + timers-browserify: ^2.0.12 + tty-browserify: ^0.0.1 + type-fest: ^2.14.0 + url: ^0.11.0 + util: ^0.12.4 + vm-browserify: ^1.1.2 + peerDependencies: + webpack: ">=5" + checksum: 0efb27ba224449d530d837bf7cd22f033ee5d0498e31898968896e2bb6844ffbfb26af976623268a052671f2b927df7561859faf5cbf3d646677ad0e914e1427 + languageName: node + linkType: hard + "node-releases@npm:^2.0.13": version: 2.0.13 resolution: "node-releases@npm:2.0.13" @@ -29552,6 +28760,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.14": + version: 2.0.14 + resolution: "node-releases@npm:2.0.14" + checksum: 59443a2f77acac854c42d321bf1b43dea0aef55cd544c6a686e9816a697300458d4e82239e2d794ea05f7bbbc8a94500332e2d3ac3f11f52e4b16cbe638b3c41 + languageName: node + linkType: hard + "node-releases@npm:^2.0.5, node-releases@npm:^2.0.6": version: 2.0.6 resolution: "node-releases@npm:2.0.6" @@ -29595,7 +28810,7 @@ __metadata: languageName: node linkType: hard -"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.3.4, normalize-package-data@npm:^2.5.0": +"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.5.0": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" dependencies: @@ -29738,18 +28953,6 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^5.0.1": - version: 5.0.1 - resolution: "npmlog@npm:5.0.1" - dependencies: - are-we-there-yet: ^2.0.0 - console-control-strings: ^1.1.0 - gauge: ^3.0.0 - set-blocking: ^2.0.0 - checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f - languageName: node - linkType: hard - "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -29785,13 +28988,6 @@ __metadata: languageName: node linkType: hard -"num2fraction@npm:^1.2.2": - version: 1.2.2 - resolution: "num2fraction@npm:1.2.2" - checksum: 1da9c6797b505d3f5b17c7f694c4fa31565bdd5c0e5d669553253aed848a580804cd285280e8a73148bd9628839267daee4967f24b53d4e893e44b563e412635 - languageName: node - linkType: hard - "number-allocator@npm:^1.0.9": version: 1.0.14 resolution: "number-allocator@npm:1.0.14" @@ -29837,17 +29033,6 @@ __metadata: languageName: node linkType: hard -"object-copy@npm:^0.1.0": - version: 0.1.0 - resolution: "object-copy@npm:0.1.0" - dependencies: - copy-descriptor: ^0.1.0 - define-property: ^0.2.5 - kind-of: ^3.0.3 - checksum: a9e35f07e3a2c882a7e979090360d1a20ab51d1fa19dfdac3aa8873b328a7c4c7683946ee97c824ae40079d848d6740a3788fa14f2185155dab7ed970a72c783 - languageName: node - linkType: hard - "object-hash@npm:^2.0.1": version: 2.2.0 resolution: "object-hash@npm:2.2.0" @@ -29876,6 +29061,16 @@ __metadata: languageName: node linkType: hard +"object-is@npm:^1.1.5": + version: 1.1.5 + resolution: "object-is@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + checksum: 989b18c4cba258a6b74dc1d74a41805c1a1425bce29f6cabb50dcb1a6a651ea9104a1b07046739a49a5bb1bc49727bcb00efd5c55f932f6ea04ec8927a7901fe + languageName: node + linkType: hard + "object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -29915,15 +29110,6 @@ __metadata: languageName: node linkType: hard -"object-visit@npm:^1.0.0": - version: 1.0.1 - resolution: "object-visit@npm:1.0.1" - dependencies: - isobject: ^3.0.0 - checksum: b0ee07f5bf3bb881b881ff53b467ebbde2b37ebb38649d6944a6cd7681b32eedd99da9bd1e01c55facf81f54ed06b13af61aba6ad87f0052982995e09333f790 - languageName: node - linkType: hard - "object.assign@npm:^4.1.2": version: 4.1.2 resolution: "object.assign@npm:4.1.2" @@ -29948,7 +29134,7 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.0, object.entries@npm:^1.1.5": +"object.entries@npm:^1.1.5": version: 1.1.5 resolution: "object.entries@npm:1.1.5" dependencies: @@ -29970,7 +29156,7 @@ __metadata: languageName: node linkType: hard -"object.fromentries@npm:^2.0.0 || ^1.0.0, object.fromentries@npm:^2.0.5": +"object.fromentries@npm:^2.0.5": version: 2.0.5 resolution: "object.fromentries@npm:2.0.5" dependencies: @@ -29992,18 +29178,6 @@ __metadata: languageName: node linkType: hard -"object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.2": - version: 2.1.4 - resolution: "object.getownpropertydescriptors@npm:2.1.4" - dependencies: - array.prototype.reduce: ^1.0.4 - call-bind: ^1.0.2 - define-properties: ^1.1.4 - es-abstract: ^1.20.1 - checksum: 988c466fe49fc4f19a28d2d1d894c95c6abfe33c94674ec0b14d96eed71f453c7ad16873d430dc2acbb1760de6d3d2affac4b81237a306012cc4dc49f7539e7f - languageName: node - linkType: hard - "object.hasown@npm:^1.1.1": version: 1.1.1 resolution: "object.hasown@npm:1.1.1" @@ -30024,16 +29198,7 @@ __metadata: languageName: node linkType: hard -"object.pick@npm:^1.3.0": - version: 1.3.0 - resolution: "object.pick@npm:1.3.0" - dependencies: - isobject: ^3.0.1 - checksum: 77fb6eed57c67adf75e9901187e37af39f052ef601cb4480386436561357eb9e459e820762f01fd02c5c1b42ece839ad393717a6d1850d848ee11fbabb3e580a - languageName: node - linkType: hard - -"object.values@npm:^1.1.0, object.values@npm:^1.1.5": +"object.values@npm:^1.1.5": version: 1.1.5 resolution: "object.values@npm:1.1.5" dependencies: @@ -30117,7 +29282,7 @@ __metadata: languageName: node linkType: hard -"open@npm:7.4.2, open@npm:^7.0.3": +"open@npm:7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" dependencies: @@ -30138,6 +29303,17 @@ __metadata: languageName: node linkType: hard +"open@npm:^8.0.4": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: ^2.0.0 + is-docker: ^2.1.1 + is-wsl: ^2.2.0 + checksum: 6388bfff21b40cb9bd8f913f9130d107f2ed4724ea81a8fd29798ee322b361ca31fa2cdfb491a5c31e43a3996cfe9566741238c7a741ada8d7af1cb78d85cf26 + languageName: node + linkType: hard + "openai@npm:^3.3.0": version: 3.3.0 resolution: "openai@npm:3.3.0" @@ -30205,20 +29381,6 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.8.1": - version: 0.8.3 - resolution: "optionator@npm:0.8.3" - dependencies: - deep-is: ~0.1.3 - fast-levenshtein: ~2.0.6 - levn: ~0.3.0 - prelude-ls: ~1.1.2 - type-check: ~0.3.2 - word-wrap: ~1.2.3 - checksum: b8695ddf3d593203e25ab0900e265d860038486c943ff8b774f596a310f8ceebdb30c6832407a8198ba3ec9debe1abe1f51d4aad94843612db3b76d690c61d34 - languageName: node - linkType: hard - "optionator@npm:^0.9.1": version: 0.9.1 resolution: "optionator@npm:0.9.1" @@ -30264,13 +29426,6 @@ __metadata: languageName: node linkType: hard -"os-homedir@npm:^1.0.0": - version: 1.0.2 - resolution: "os-homedir@npm:1.0.2" - checksum: af609f5a7ab72de2f6ca9be6d6b91a599777afc122ac5cad47e126c1f67c176fe9b52516b9eeca1ff6ca0ab8587fe66208bc85e40a3940125f03cdb91408e9d2 - languageName: node - linkType: hard - "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -30303,15 +29458,6 @@ __metadata: languageName: node linkType: hard -"p-all@npm:^2.1.0": - version: 2.1.0 - resolution: "p-all@npm:2.1.0" - dependencies: - p-map: ^2.0.0 - checksum: 6c20134eb3f16dca270d04a40cd14d2d05012b5a5762ca4f89962ae03a5fc13e13b09f64626a780f10bbe4e204b9370f708c6d8c079296bd2512d7e15462c76f - languageName: node - linkType: hard - "p-cancelable@npm:^2.0.0": version: 2.1.1 resolution: "p-cancelable@npm:2.1.1" @@ -30319,22 +29465,6 @@ __metadata: languageName: node linkType: hard -"p-defer@npm:^1.0.0": - version: 1.0.0 - resolution: "p-defer@npm:1.0.0" - checksum: 4271b935c27987e7b6f229e5de4cdd335d808465604644cb7b4c4c95bef266735859a93b16415af8a41fd663ee9e3b97a1a2023ca9def613dba1bad2a0da0c7b - languageName: node - linkType: hard - -"p-event@npm:^4.1.0": - version: 4.2.0 - resolution: "p-event@npm:4.2.0" - dependencies: - p-timeout: ^3.1.0 - checksum: 8a3588f7a816a20726a3262dfeee70a631e3997e4773d23219176333eda55cce9a76219e3d2b441b331eb746e14fdb381eb2694ab9ff2fcf87c846462696fe89 - languageName: node - linkType: hard - "p-filter@npm:2.1.0, p-filter@npm:^2.1.0": version: 2.1.0 resolution: "p-filter@npm:2.1.0" @@ -30423,6 +29553,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: ^4.0.0 + checksum: 2bfe5234efa5e7a4e74b30a5479a193fdd9236f8f6b4d2f3f69e3d286d9a7d7ab0c118a2a50142efcf4e41625def635bd9332d6cbf9cc65d85eb0718c579ab38 + languageName: node + linkType: hard + "p-map@npm:4.0.0, p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -30439,15 +29578,6 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^3.0.0": - version: 3.0.0 - resolution: "p-map@npm:3.0.0" - dependencies: - aggregate-error: ^3.0.0 - checksum: 49b0fcbc66b1ef9cd379de1b4da07fa7a9f84b41509ea3f461c31903623aaba8a529d22f835e0d77c7cb9fcc16e4fae71e308fd40179aea514ba68f27032b5d5 - languageName: node - linkType: hard - "p-queue@npm:6.6.2, p-queue@npm:^6.6.2": version: 6.6.2 resolution: "p-queue@npm:6.6.2" @@ -30468,7 +29598,7 @@ __metadata: languageName: node linkType: hard -"p-timeout@npm:^3.1.0, p-timeout@npm:^3.2.0": +"p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" dependencies: @@ -30498,7 +29628,7 @@ __metadata: languageName: node linkType: hard -"pako@npm:^0.2.5": +"pako@npm:^0.2.5, pako@npm:~0.2.0": version: 0.2.9 resolution: "pako@npm:0.2.9" checksum: 055f9487cd57fbb78df84315873bbdd089ba286f3499daed47d2effdc6253e981f5db6898c23486de76d4a781559f890d643bd3a49f70f1b4a18019c98aa5125 @@ -30512,17 +29642,6 @@ __metadata: languageName: node linkType: hard -"parallel-transform@npm:^1.1.0": - version: 1.2.0 - resolution: "parallel-transform@npm:1.2.0" - dependencies: - cyclist: ^1.0.1 - inherits: ^2.0.3 - readable-stream: ^2.1.5 - checksum: ab6ddc1a662cefcfb3d8d546a111763d3b223f484f2e9194e33aefd8f6760c319d0821fd22a00a3adfbd45929b50d2c84cc121389732f013c2ae01c226269c27 - languageName: node - linkType: hard - "param-case@npm:^1.1.0": version: 1.1.2 resolution: "param-case@npm:1.1.2" @@ -30532,7 +29651,7 @@ __metadata: languageName: node linkType: hard -"param-case@npm:^3.0.3, param-case@npm:^3.0.4": +"param-case@npm:^3.0.4": version: 3.0.4 resolution: "param-case@npm:3.0.4" dependencies: @@ -30609,6 +29728,22 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.1 + resolution: "parse-entities@npm:4.0.1" + dependencies: + "@types/unist": ^2.0.0 + character-entities: ^2.0.0 + character-entities-legacy: ^3.0.0 + character-reference-invalid: ^2.0.0 + decode-named-character-reference: ^1.0.0 + is-alphanumerical: ^2.0.0 + is-decimal: ^2.0.0 + is-hexadecimal: ^2.0.0 + checksum: 32a6ff5b9acb9d2c4d71537308521fd265e685b9215691df73feedd9edfe041bb6da9f89bd0c35c4a2bc7d58e3e76e399bb6078c2fd7d2a343ff1dd46edbf1bd + languageName: node + linkType: hard + "parse-filepath@npm:^1.0.2": version: 1.0.2 resolution: "parse-filepath@npm:1.0.2" @@ -30627,15 +29762,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^2.2.0": - version: 2.2.0 - resolution: "parse-json@npm:2.2.0" - dependencies: - error-ex: ^1.2.0 - checksum: dda78a63e57a47b713a038630868538f718a7ca0cd172a36887b0392ccf544ed0374902eb28f8bf3409e8b71d62b79d17062f8543afccf2745f9b0b2d2bb80ca - languageName: node - linkType: hard - "parse-json@npm:^4.0.0": version: 4.0.0 resolution: "parse-json@npm:4.0.0" @@ -30707,7 +29833,7 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 @@ -30734,13 +29860,6 @@ __metadata: languageName: node linkType: hard -"pascalcase@npm:^0.1.1": - version: 0.1.1 - resolution: "pascalcase@npm:0.1.1" - checksum: f83681c3c8ff75fa473a2bb2b113289952f802ff895d435edd717e7cb898b0408cbdb247117a938edcbc5d141020909846cc2b92c47213d764e2a94d2ad2b925 - languageName: node - linkType: hard - "password-prompt@npm:^1.1.2": version: 1.1.3 resolution: "password-prompt@npm:1.1.3" @@ -30758,13 +29877,6 @@ __metadata: languageName: node linkType: hard -"path-browserify@npm:0.0.1": - version: 0.0.1 - resolution: "path-browserify@npm:0.0.1" - checksum: ae8dcd45d0d3cfbaf595af4f206bf3ed82d77f72b4877ae7e77328079e1468c84f9386754bb417d994d5a19bf47882fd253565c18441cd5c5c90ae5187599e35 - languageName: node - linkType: hard - "path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -30791,22 +29903,6 @@ __metadata: languageName: node linkType: hard -"path-dirname@npm:^1.0.0": - version: 1.0.2 - resolution: "path-dirname@npm:1.0.2" - checksum: 0d2f6604ae05a252a0025318685f290e2764ecf9c5436f203cdacfc8c0b17c24cdedaa449d766beb94ab88cc7fc70a09ec21e7933f31abc2b719180883e5e33f - languageName: node - linkType: hard - -"path-exists@npm:^2.0.0": - version: 2.1.0 - resolution: "path-exists@npm:2.1.0" - dependencies: - pinkie-promise: ^2.0.0 - checksum: fdb734f1d00f225f7a0033ce6d73bff6a7f76ea08936abf0e5196fa6e54a645103538cd8aedcb90d6d8c3fa3705ded0c58a4da5948ae92aa8834892c1ab44a84 - languageName: node - linkType: hard - "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -30821,6 +29917,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 8ca842868cab09423994596eb2c5ec2a971c17d1a3cb36dbf060592c730c725cd524b9067d7d2a1e031fef9ba7bd2ac6dc5ec9fb92aa693265f7be3987045254 + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -30865,7 +29968,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.0": +"path-scurry@npm:^1.10.0, path-scurry@npm:^1.10.1": version: 1.10.1 resolution: "path-scurry@npm:1.10.1" dependencies: @@ -30889,17 +29992,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^1.0.0": - version: 1.1.0 - resolution: "path-type@npm:1.1.0" - dependencies: - graceful-fs: ^4.1.2 - pify: ^2.0.0 - pinkie-promise: ^2.0.0 - checksum: 59a4b2c0e566baf4db3021a1ed4ec09a8b36fca960a490b54a6bcefdb9987dafe772852982b6011cd09579478a96e57960a01f75fa78a794192853c9d468fc79 - languageName: node - linkType: hard - "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -30966,6 +30058,24 @@ __metadata: languageName: node linkType: hard +"peek-stream@npm:^1.1.0": + version: 1.1.3 + resolution: "peek-stream@npm:1.1.3" + dependencies: + buffer-from: ^1.0.0 + duplexify: ^3.5.0 + through2: ^2.0.3 + checksum: a0e09d6d1a8a01158a3334f20d6b1cdd91747eba24eb06a1d742eefb620385593121a76d4378cc81f77cdce6a66df0575a41041b1189c510254aec91878afc99 + languageName: node + linkType: hard + +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 6c72f5243303d9c60bd98e6446ba7d30ae29e3d56fdb6fae8767e8ba6386f33ee284c97efe3230a0d0217e2b1723b8ab490b1bbf34fcbb2180dbc8a9de47850d + languageName: node + linkType: hard + "performance-now@npm:^2.1.0": version: 2.1.0 resolution: "performance-now@npm:2.1.0" @@ -31059,13 +30169,6 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^0.2.1": - version: 0.2.1 - resolution: "picocolors@npm:0.2.1" - checksum: 3b0f441f0062def0c0f39e87b898ae7461c3a16ffc9f974f320b44c799418cabff17780ee647fda42b856a1dc45897e2c62047e1b546d94d6d5c6962f45427b2 - languageName: node - linkType: hard - "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -31098,7 +30201,7 @@ __metadata: languageName: node linkType: hard -"pify@npm:^2.0.0, pify@npm:^2.3.0": +"pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" checksum: 9503aaeaf4577acc58642ad1d25c45c6d90288596238fb68f82811c08104c800e5a7870398e9f015d82b44ecbcbef3dc3d4251a1cbb582f6e5959fe09884b2ba @@ -31128,15 +30231,6 @@ __metadata: languageName: node linkType: hard -"pinkie-promise@npm:^2.0.0": - version: 2.0.1 - resolution: "pinkie-promise@npm:2.0.1" - dependencies: - pinkie: ^2.0.0 - checksum: b53a4a2e73bf56b6f421eef711e7bdcb693d6abb474d57c5c413b809f654ba5ee750c6a96dd7225052d4b96c4d053cdcb34b708a86fceed4663303abee52fcca - languageName: node - linkType: hard - "pinkie@npm:^1.0.0": version: 1.0.0 resolution: "pinkie@npm:1.0.0" @@ -31144,13 +30238,6 @@ __metadata: languageName: node linkType: hard -"pinkie@npm:^2.0.0": - version: 2.0.4 - resolution: "pinkie@npm:2.0.4" - checksum: b12b10afea1177595aab036fc220785488f67b4b0fc49e7a27979472592e971614fa1c728e63ad3e7eb748b4ec3c3dbd780819331dad6f7d635c77c10537b9db - languageName: node - linkType: hard - "pirates@npm:^4.0.1, pirates@npm:^4.0.5": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -31158,6 +30245,13 @@ __metadata: languageName: node linkType: hard +"pirates@npm:^4.0.4": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 + languageName: node + linkType: hard + "pixelmatch@npm:^4.0.2": version: 4.0.2 resolution: "pixelmatch@npm:4.0.2" @@ -31196,6 +30290,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^7.0.0": + version: 7.0.0 + resolution: "pkg-dir@npm:7.0.0" + dependencies: + find-up: ^6.3.0 + checksum: 94298b20a446bfbbd66604474de8a0cdd3b8d251225170970f15d9646f633e056c80520dd5b4c1d1050c9fed8f6a9e5054b141c93806439452efe72e57562c03 + languageName: node + linkType: hard + "pkg-types@npm:^1.0.3": version: 1.0.3 resolution: "pkg-types@npm:1.0.3" @@ -31226,11 +30329,11 @@ __metadata: linkType: hard "playwright-core@npm:^1.38.1": - version: 1.38.1 - resolution: "playwright-core@npm:1.38.1" + version: 1.40.1 + resolution: "playwright-core@npm:1.40.1" bin: playwright-core: cli.js - checksum: 66e83fe040f309b13ad94ba39dea40ac207bfcbbc22de13141af88dbdedd64e1c4e3ce1d0cb070d4efd8050d7e579953ec3681dd8a0acf2c1cc738d9c50e545e + checksum: 84d92fb9b86e3c225b16b6886bf858eb5059b4e60fa1205ff23336e56a06dcb2eac62650992dede72f406c8e70a7b6a5303e511f9b4bc0b85022ede356a01ee0 languageName: node linkType: hard @@ -31248,12 +30351,12 @@ __metadata: languageName: node linkType: hard -"pnp-webpack-plugin@npm:1.6.4": - version: 1.6.4 - resolution: "pnp-webpack-plugin@npm:1.6.4" +"pnp-webpack-plugin@npm:^1.7.0": + version: 1.7.0 + resolution: "pnp-webpack-plugin@npm:1.7.0" dependencies: ts-pnp: ^1.1.6 - checksum: 0606a63db96400b07f182300168298da9518727a843f9e10cf5045d2a102a4be06bb18c73dc481281e3e0f1ed8d04ef0d285a342b6dcd0eff1340e28e5d2328d + checksum: a41716d13607be5a3e06ba58b17e9e619cf07da3a0a7b10bd41cd89362873041054fd2b7966ad30a1b26b826cfb8fecc0469a95902d5b1b8ba8f591e2fe6b96d languageName: node linkType: hard @@ -31273,22 +30376,6 @@ __metadata: languageName: node linkType: hard -"posix-character-classes@npm:^0.1.0": - version: 0.1.1 - resolution: "posix-character-classes@npm:0.1.1" - checksum: dedb99913c60625a16050cfed2fb5c017648fc075be41ac18474e1c6c3549ef4ada201c8bd9bd006d36827e289c571b6092e1ef6e756cdbab2fd7046b25c6442 - languageName: node - linkType: hard - -"postcss-flexbugs-fixes@npm:^4.2.1": - version: 4.2.1 - resolution: "postcss-flexbugs-fixes@npm:4.2.1" - dependencies: - postcss: ^7.0.26 - checksum: 51a626bc80dbe42fcc8b0895b4f23a558bb809ec52cdc05aa27fb24cdffd4c9dc53f25218085ddf407c53d76573bc6d7568219c912161609f02532a8f5f59b43 - languageName: node - linkType: hard - "postcss-import@npm:^15.1.0": version: 15.1.0 resolution: "postcss-import@npm:15.1.0" @@ -31331,42 +30418,17 @@ __metadata: languageName: node linkType: hard -"postcss-loader@npm:^4.2.0": - version: 4.3.0 - resolution: "postcss-loader@npm:4.3.0" +"postcss-loader@npm:^7.0.2": + version: 7.3.3 + resolution: "postcss-loader@npm:7.3.3" dependencies: - cosmiconfig: ^7.0.0 - klona: ^2.0.4 - loader-utils: ^2.0.0 - schema-utils: ^3.0.0 - semver: ^7.3.4 - peerDependencies: - postcss: ^7.0.0 || ^8.0.1 - webpack: ^4.0.0 || ^5.0.0 - checksum: b8ba29789d48512c7ce10e9391b1e1512a4b8f8b4063ebff0f9ebdd0a3a01e433ccfa0d2db6dbdd63b126acf7692330f0773bef75e78d53f38eba556ca5f2aee - languageName: node - linkType: hard - -"postcss-loader@npm:^6.2.1": - version: 6.2.1 - resolution: "postcss-loader@npm:6.2.1" - dependencies: - cosmiconfig: ^7.0.0 - klona: ^2.0.5 - semver: ^7.3.5 + cosmiconfig: ^8.2.0 + jiti: ^1.18.2 + semver: ^7.3.8 peerDependencies: postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 - checksum: e40ae79c3e39df37014677a817b001bd115d8b10dedf53a07b97513d93b1533cd702d7a48831bdd77b9a9484b1ec84a5d4a723f80e83fb28682c75b5e65e8a90 - languageName: node - linkType: hard - -"postcss-modules-extract-imports@npm:^2.0.0": - version: 2.0.0 - resolution: "postcss-modules-extract-imports@npm:2.0.0" - dependencies: - postcss: ^7.0.5 - checksum: 154790fe5954aaa12f300aa9aa782fae8b847138459c8f533ea6c8f29439dd66b4d9a49e0bf6f8388fa0df898cc03d61c84678e3b0d4b47cac5a4334a7151a9f + checksum: c724044d6ae56334535c26bb4efc9c151431d44d60bc8300157c760747281a242757d8dab32db72738434531175b38a408cb0b270bb96207c07584dcfcd899ff languageName: node linkType: hard @@ -31379,38 +30441,16 @@ __metadata: languageName: node linkType: hard -"postcss-modules-local-by-default@npm:^3.0.2": - version: 3.0.3 - resolution: "postcss-modules-local-by-default@npm:3.0.3" - dependencies: - icss-utils: ^4.1.1 - postcss: ^7.0.32 - postcss-selector-parser: ^6.0.2 - postcss-value-parser: ^4.1.0 - checksum: 0267633eaf80e72a3abf391b6e34c5b344a1bdfb1421543d3ed43fc757e053e0fcc1a2eb06d959a8f435776e8dc80288b59bfc34d61e5e021d47b747c417c5a1 - languageName: node - linkType: hard - -"postcss-modules-local-by-default@npm:^4.0.0": - version: 4.0.0 - resolution: "postcss-modules-local-by-default@npm:4.0.0" +"postcss-modules-local-by-default@npm:^4.0.3": + version: 4.0.3 + resolution: "postcss-modules-local-by-default@npm:4.0.3" dependencies: icss-utils: ^5.0.0 postcss-selector-parser: ^6.0.2 postcss-value-parser: ^4.1.0 peerDependencies: postcss: ^8.1.0 - checksum: 6cf570badc7bc26c265e073f3ff9596b69bb954bc6ac9c5c1b8cba2995b80834226b60e0a3cbb87d5f399dbb52e6466bba8aa1d244f6218f99d834aec431a69d - languageName: node - linkType: hard - -"postcss-modules-scope@npm:^2.2.0": - version: 2.2.0 - resolution: "postcss-modules-scope@npm:2.2.0" - dependencies: - postcss: ^7.0.6 - postcss-selector-parser: ^6.0.0 - checksum: c611181df924275ca1ffea261149c229488d6921054896879ca98feeb0913f9b00f4f160654beb2cb243a2989036c269baa96778eeacaaa399a4604b6e2fea17 + checksum: 2f8083687f3d6067885f8863dd32dbbb4f779cfcc7e52c17abede9311d84faf6d3ed8760e7c54c6380281732ae1f78e5e56a28baf3c271b33f450a11c9e30485 languageName: node linkType: hard @@ -31425,16 +30465,6 @@ __metadata: languageName: node linkType: hard -"postcss-modules-values@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-values@npm:3.0.0" - dependencies: - icss-utils: ^4.0.0 - postcss: ^7.0.6 - checksum: f1aea0b9c6798b39ec02a6d2310924bb9bfbddb4579668c2d4e2205ca7a68c656b85d5720f9bba3629d611f36667fe04ab889ea3f9a6b569a0a0d57b4f2f4e99 - languageName: node - linkType: hard - "postcss-modules-values@npm:^4.0.0": version: 4.0.0 resolution: "postcss-modules-values@npm:4.0.0" @@ -31466,16 +30496,6 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": - version: 6.0.10 - resolution: "postcss-selector-parser@npm:6.0.10" - dependencies: - cssesc: ^3.0.0 - util-deprecate: ^1.0.2 - checksum: 46afaa60e3d1998bd7adf6caa374baf857cc58d3ff944e29459c9a9e4680a7fe41597bd5b755fc81d7c388357e9bf67c0251d047c640a09f148e13606b8a8608 - languageName: node - linkType: hard - "postcss-selector-parser@npm:^6.0.11": version: 6.0.11 resolution: "postcss-selector-parser@npm:6.0.11" @@ -31486,6 +30506,16 @@ __metadata: languageName: node linkType: hard +"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4": + version: 6.0.10 + resolution: "postcss-selector-parser@npm:6.0.10" + dependencies: + cssesc: ^3.0.0 + util-deprecate: ^1.0.2 + checksum: 46afaa60e3d1998bd7adf6caa374baf857cc58d3ff944e29459c9a9e4680a7fe41597bd5b755fc81d7c388357e9bf67c0251d047c640a09f148e13606b8a8608 + languageName: node + linkType: hard + "postcss-value-parser@npm:^3.3.0": version: 3.3.1 resolution: "postcss-value-parser@npm:3.3.1" @@ -31511,16 +30541,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^7.0.14, postcss@npm:^7.0.26, postcss@npm:^7.0.32, postcss@npm:^7.0.36, postcss@npm:^7.0.5, postcss@npm:^7.0.6": - version: 7.0.39 - resolution: "postcss@npm:7.0.39" - dependencies: - picocolors: ^0.2.1 - source-map: ^0.6.1 - checksum: 4ac793f506c23259189064bdc921260d869a115a82b5e713973c5af8e94fbb5721a5cc3e1e26840500d7e1f1fa42a209747c5b1a151918a9bc11f0d7ed9048e3 - languageName: node - linkType: hard - "postcss@npm:^8.2.14": version: 8.4.14 resolution: "postcss@npm:8.4.14" @@ -31532,7 +30552,18 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.2.15, postcss@npm:^8.4.18": +"postcss@npm:^8.3.11": + version: 8.4.21 + resolution: "postcss@npm:8.4.21" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 + languageName: node + linkType: hard + +"postcss@npm:^8.4.18": version: 8.4.18 resolution: "postcss@npm:8.4.18" dependencies: @@ -31543,14 +30574,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.3.11": - version: 8.4.21 - resolution: "postcss@npm:8.4.21" +"postcss@npm:^8.4.21, postcss@npm:^8.4.32": + version: 8.4.32 + resolution: "postcss@npm:8.4.32" dependencies: - nanoid: ^3.3.4 + nanoid: ^3.3.7 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 + checksum: 220d9d0bf5d65be7ed31006c523bfb11619461d296245c1231831f90150aeb4a31eab9983ac9c5c89759a3ca8b60b3e0d098574964e1691673c3ce5c494305ae languageName: node linkType: hard @@ -31624,6 +30655,28 @@ __metadata: languageName: node linkType: hard +"prebuild-install@npm:^7.1.1": + version: 7.1.1 + resolution: "prebuild-install@npm:7.1.1" + dependencies: + detect-libc: ^2.0.0 + expand-template: ^2.0.3 + github-from-package: 0.0.0 + minimist: ^1.2.3 + mkdirp-classic: ^0.5.3 + napi-build-utils: ^1.0.1 + node-abi: ^3.3.0 + pump: ^3.0.0 + rc: ^1.2.7 + simple-get: ^4.0.0 + tar-fs: ^2.0.0 + tunnel-agent: ^0.6.0 + bin: + prebuild-install: bin.js + checksum: dbf96d0146b6b5827fc8f67f72074d2e19c69628b9a7a0a17d0fad1bf37e9f06922896972e074197fc00a52eae912993e6ef5a0d471652f561df5cb516f3f467 + languageName: node + linkType: hard + "preferred-pm@npm:^3.0.0": version: 3.0.3 resolution: "preferred-pm@npm:3.0.3" @@ -31643,13 +30696,6 @@ __metadata: languageName: node linkType: hard -"prelude-ls@npm:~1.1.2": - version: 1.1.2 - resolution: "prelude-ls@npm:1.1.2" - checksum: c4867c87488e4a0c233e158e4d0d5565b609b105d75e4c05dc760840475f06b731332eb93cc8c9cecb840aa8ec323ca3c9a56ad7820ad2e63f0261dadcb154e4 - languageName: node - linkType: hard - "prettier-linter-helpers@npm:^1.0.0": version: 1.0.0 resolution: "prettier-linter-helpers@npm:1.0.0" @@ -31714,15 +30760,6 @@ __metadata: languageName: node linkType: hard -"prettier@npm:>=2.2.1 <=2.3.0": - version: 2.3.0 - resolution: "prettier@npm:2.3.0" - bin: - prettier: bin-prettier.js - checksum: e8851a45f60f2994775f96e07964646c299b8a8f9c64da4fbd8efafc20db3458bdcedac79aed34e1d5477540b3aa04f6499adc4979cb7937f8ebd058a767d8ff - languageName: node - linkType: hard - "prettier@npm:^2.7.1": version: 2.8.7 resolution: "prettier@npm:2.8.7" @@ -31732,6 +30769,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.8.0": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 + languageName: node + linkType: hard + "prettier@npm:^2.8.6": version: 2.8.6 resolution: "prettier@npm:2.8.6" @@ -31741,16 +30787,6 @@ __metadata: languageName: node linkType: hard -"pretty-error@npm:^2.1.1": - version: 2.1.2 - resolution: "pretty-error@npm:2.1.2" - dependencies: - lodash: ^4.17.20 - renderkid: ^2.0.4 - checksum: 16775d06f9a695d17103414d610b1281f9535ee1f2da1ce1e1b9be79584a114aa7eac6dcdcc5ef151756d3c014dfd4ac1c7303ed8016d0cec12437cfdf4021c6 - languageName: node - linkType: hard - "pretty-error@npm:^4.0.0": version: 4.0.0 resolution: "pretty-error@npm:4.0.0" @@ -31911,7 +30947,7 @@ __metadata: languageName: node linkType: hard -"progress@npm:2.0.3, progress@npm:^2.0.0, progress@npm:^2.0.3": +"progress@npm:2.0.3, progress@npm:^2.0.1, progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 @@ -31935,31 +30971,6 @@ __metadata: languageName: node linkType: hard -"promise.allsettled@npm:^1.0.0": - version: 1.0.5 - resolution: "promise.allsettled@npm:1.0.5" - dependencies: - array.prototype.map: ^1.0.4 - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.1 - get-intrinsic: ^1.1.1 - iterate-value: ^1.0.2 - checksum: 92775552d3a3487ed924852e5de00a217a202cefc833e8cc169283fe4f7dbe09953505b0c7471b2681e09aa7d064bdbd07b978d44ff536f712e4dcd7c9faba35 - languageName: node - linkType: hard - -"promise.prototype.finally@npm:^3.1.0": - version: 3.1.3 - resolution: "promise.prototype.finally@npm:3.1.3" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.1 - checksum: aba8af6ae8d076e2c344d2674409b44c8f98b3aba98b78619739aeb4a74ebac80dbba5f9338da7cf0108a34384799d3996c46697d2e21c6e998c04d68041213c - languageName: node - linkType: hard - "promise@npm:^7.1.1": version: 7.3.1 resolution: "promise@npm:7.3.1" @@ -31997,7 +31008,7 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^5.0.0, property-information@npm:^5.3.0": +"property-information@npm:^5.0.0": version: 5.6.0 resolution: "property-information@npm:5.6.0" dependencies: @@ -32007,9 +31018,9 @@ __metadata: linkType: hard "property-information@npm:^6.0.0": - version: 6.3.0 - resolution: "property-information@npm:6.3.0" - checksum: bf0a15dec097fd4324a42163cabd96b90819e48ef0d8d98756ef0420b2c579bf33646fe0b6e04aa9e79f5a2b5b5860ef11655a79cd8969d8eda58df23c4f96c9 + version: 6.4.0 + resolution: "property-information@npm:6.4.0" + checksum: b5aed9a40e87730995f3ceed29839f137fa73b2a4cccfb8ed72ab8bddb8881cad05c3487c4aa168d7cb49a53db8089790c9f00f59d15b8380d2bb5383cdd1f24 languageName: node linkType: hard @@ -32023,7 +31034,7 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:1.1.0, proxy-from-env@npm:^1.1.0": +"proxy-from-env@npm:1.1.0, proxy-from-env@npm:^1.0.0, proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 @@ -32110,7 +31121,7 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^1.2.4, punycode@npm:^1.3.2": +"punycode@npm:^1.3.2": version: 1.4.1 resolution: "punycode@npm:1.4.1" checksum: fa6e698cb53db45e4628559e557ddaf554103d2a96a1d62892c8f4032cd3bc8871796cae9eabc1bc700e2b6677611521ce5bb1d9a27700086039965d0cf34518 @@ -32131,7 +31142,25 @@ __metadata: languageName: node linkType: hard -"pvtsutils@npm:^1.3.2": +"puppeteer-core@npm:^2.1.1": + version: 2.1.1 + resolution: "puppeteer-core@npm:2.1.1" + dependencies: + "@types/mime-types": ^2.1.0 + debug: ^4.1.0 + extract-zip: ^1.6.6 + https-proxy-agent: ^4.0.0 + mime: ^2.0.3 + mime-types: ^2.1.25 + progress: ^2.0.1 + proxy-from-env: ^1.0.0 + rimraf: ^2.6.1 + ws: ^6.1.0 + checksum: 2ddb597ef1b2d162b4aa49833b977734129edf7c8fa558fc38c59d273e79aa1bd079481c642de87f7163665f7f37aa52683da2716bafb7d3cab68c262c36ec28 + languageName: node + linkType: hard + +"pvtsutils@npm:^1.3.2, pvtsutils@npm:^1.3.5": version: 1.3.5 resolution: "pvtsutils@npm:1.3.5" dependencies: @@ -32186,16 +31215,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.3, qs@npm:^6.10.2, qs@npm:^6.10.3, qs@npm:^6.7.0, qs@npm:^6.9.4": - version: 6.10.3 - resolution: "qs@npm:6.10.3" - dependencies: - side-channel: ^1.0.4 - checksum: 0fac5e6c7191d0295a96d0e83c851aeb015df7e990e4d3b093897d3ac6c94e555dbd0a599739c84d7fa46d7fee282d94ba76943983935cf33bba6769539b8019 - languageName: node - linkType: hard - -"qs@npm:^6.10.0": +"qs@npm:6.11.0, qs@npm:^6.10.0": version: 6.11.0 resolution: "qs@npm:6.11.0" dependencies: @@ -32204,12 +31224,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.0": - version: 6.11.2 - resolution: "qs@npm:6.11.2" +"qs@npm:^6.10.2, qs@npm:^6.10.3, qs@npm:^6.7.0, qs@npm:^6.9.4": + version: 6.10.3 + resolution: "qs@npm:6.10.3" dependencies: side-channel: ^1.0.4 - checksum: e812f3c590b2262548647d62f1637b6989cc56656dc960b893fe2098d96e1bd633f36576f4cd7564dfbff9db42e17775884db96d846bebe4f37420d073ecdc0b + checksum: 0fac5e6c7191d0295a96d0e83c851aeb015df7e990e4d3b093897d3ac6c94e555dbd0a599739c84d7fa46d7fee282d94ba76943983935cf33bba6769539b8019 languageName: node linkType: hard @@ -32220,7 +31240,7 @@ __metadata: languageName: node linkType: hard -"querystring-es3@npm:^0.2.0": +"querystring-es3@npm:^0.2.1": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" checksum: 691e8d6b8b157e7cd49ae8e83fcf86de39ab3ba948c25abaa94fba84c0986c641aa2f597770848c64abce290ed17a39c9df6df737dfa7e87c3b63acc7d225d61 @@ -32255,6 +31275,13 @@ __metadata: languageName: node linkType: hard +"queue-tick@npm:^1.0.1": + version: 1.0.1 + resolution: "queue-tick@npm:1.0.1" + checksum: 57c3292814b297f87f792fbeb99ce982813e4e54d7a8bdff65cf53d5c084113913289d4a48ec8bbc964927a74b847554f9f4579df43c969a6c8e0f026457ad01 + languageName: node + linkType: hard + "queue@npm:6.0.2, queue@npm:^6.0.2": version: 6.0.2 resolution: "queue@npm:6.0.2" @@ -32292,10 +31319,10 @@ __metadata: languageName: node linkType: hard -"ramda@npm:^0.28.0": - version: 0.28.0 - resolution: "ramda@npm:0.28.0" - checksum: 44ea6e5010bba70151b6a92d8114a91915e8b5a16105cce65fae58c9d7386b812c429645e35f21141d7087568550ce383bc10ee1a65cdec951f4b69ea457e6a4 +"ramda@npm:0.29.0": + version: 0.29.0 + resolution: "ramda@npm:0.29.0" + checksum: 9ab26c06eb7545cbb7eebcf75526d6ee2fcaae19e338f165b2bf32772121e7b28192d6664d1ba222ff76188ba26ab307342d66e805dbb02c860560adc4d5dd57 languageName: node linkType: hard @@ -32359,15 +31386,17 @@ __metadata: languageName: node linkType: hard -"raw-loader@npm:^4.0.2": - version: 4.0.2 - resolution: "raw-loader@npm:4.0.2" +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" dependencies: - loader-utils: ^2.0.0 - schema-utils: ^3.0.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 51cc1b0d0e8c37c4336b5318f3b2c9c51d6998ad6f56ea09612afcfefc9c1f596341309e934a744ae907177f28efc9f1654eacd62151e82853fcc6d37450e795 + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e languageName: node linkType: hard @@ -32435,6 +31464,16 @@ __metadata: languageName: node linkType: hard +"react-colorful@npm:^5.1.2": + version: 5.6.1 + resolution: "react-colorful@npm:5.6.1" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: e432b7cb0df57e8f0bcdc3b012d2e93fcbcb6092c9e0f85654788d5ebfc4442536d8cc35b2418061ba3c4afb8b7788cc101c606d86a1732407921de7a9244c8d + languageName: node + linkType: hard + "react-colorful@npm:^5.6.0": version: 5.6.0 resolution: "react-colorful@npm:5.6.0" @@ -32547,7 +31586,7 @@ __metadata: languageName: node linkType: hard -"react-docgen-typescript@npm:^2.1.1": +"react-docgen-typescript@npm:^2.2.2": version: 2.2.2 resolution: "react-docgen-typescript@npm:2.2.2" peerDependencies: @@ -32556,43 +31595,21 @@ __metadata: languageName: node linkType: hard -"react-docgen@npm:^5.0.0": - version: 5.4.3 - resolution: "react-docgen@npm:5.4.3" +"react-docgen@npm:^7.0.0": + version: 7.0.1 + resolution: "react-docgen@npm:7.0.1" dependencies: - "@babel/core": ^7.7.5 - "@babel/generator": ^7.12.11 - "@babel/runtime": ^7.7.6 - ast-types: ^0.14.2 - commander: ^2.19.0 + "@babel/core": ^7.18.9 + "@babel/traverse": ^7.18.9 + "@babel/types": ^7.18.9 + "@types/babel__core": ^7.18.0 + "@types/babel__traverse": ^7.18.0 + "@types/doctrine": ^0.0.9 + "@types/resolve": ^1.20.2 doctrine: ^3.0.0 - estree-to-babel: ^3.1.0 - neo-async: ^2.6.1 - node-dir: ^0.1.10 - strip-indent: ^3.0.0 - bin: - react-docgen: bin/react-docgen.js - checksum: cef935ba948195eaeec9126c62f53bc015b9a5ad3a7eeb4a4604668d5b12bd5d0c9058c279eaf33ee6b47f2a24ccf01818b67af64d7f61265c4d3a5aa4ff0a3a - languageName: node - linkType: hard - -"react-docgen@npm:^6.0.0-alpha.0": - version: 6.0.0-alpha.3 - resolution: "react-docgen@npm:6.0.0-alpha.3" - dependencies: - "@babel/core": ^7.7.5 - "@babel/generator": ^7.12.11 - ast-types: ^0.14.2 - commander: ^2.19.0 - doctrine: ^3.0.0 - estree-to-babel: ^3.1.0 - neo-async: ^2.6.1 - node-dir: ^0.1.10 - resolve: ^1.17.0 - strip-indent: ^3.0.0 - bin: - react-docgen: bin/react-docgen.js - checksum: db4c300910e2ef7b854ccf4f454bd701875b787d0bc0f444f89415223e7c288a5808d6cd0f7ef6346332c9de2d068d648bc801d16b6b07a1699c3e10670c4801 + resolve: ^1.22.1 + strip-indent: ^4.0.0 + checksum: 9c2b6cbd6bb94bf75a088dd5046707f7bcb2271c553081e7ec4daaadfd2ba7c5b68f81499212fde96192cdd6ce71dd31600fdc21c2e7ddb86dd5bf039971cd91 languageName: node linkType: hard @@ -32621,17 +31638,17 @@ __metadata: languageName: node linkType: hard -"react-element-to-jsx-string@npm:^14.3.4": - version: 14.3.4 - resolution: "react-element-to-jsx-string@npm:14.3.4" +"react-element-to-jsx-string@npm:^15.0.0": + version: 15.0.0 + resolution: "react-element-to-jsx-string@npm:15.0.0" dependencies: "@base2/pretty-print-object": 1.0.1 is-plain-object: 5.0.0 - react-is: 17.0.2 + react-is: 18.1.0 peerDependencies: - react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 - react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 - checksum: 42bcd4423f12e9ee21b2d3f0c2a28805ff4953bd82b6be4c1f5b5f9a371115aafa36a6f3d82726d43b4912179b79e99550c2b9a772c7fe6a5cd8f7e93ff34ceb + react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + checksum: c3907cc4c1d3e9ecc8ca7727058ebcba6ec89848d9e07bfd2c77ee8f28f1ad99bf55e38359dec8a1125de83d41ac09a2874f53c41415edc86ffa9840fa1b7856 languageName: node linkType: hard @@ -32647,12 +31664,12 @@ __metadata: linkType: hard "react-fast-marquee@npm:^1.3.5": - version: 1.6.1 - resolution: "react-fast-marquee@npm:1.6.1" + version: 1.6.2 + resolution: "react-fast-marquee@npm:1.6.2" peerDependencies: react: ">= 16.8.0 || 18.0.0" react-dom: ">= 16.8.0 || 18.0.0" - checksum: 35cb639d516ed87d950757ac80ca1ef84b63b64be8400802514225b4566870ead21a6cddcfe1bdd805724a0e173718dde4a9a4162efbabbfee6b6adc490a6eab + checksum: 5bdd2115f042a734c97317f45237450116665db999ed6fc63326c6b9f34dd93a06ea601c7a9a8157960b5da22fcd3516e75fe28ecb296631b24de94f22ad1efc languageName: node linkType: hard @@ -32762,7 +31779,7 @@ __metadata: languageName: node linkType: hard -"react-inspector@npm:^5.1.0, react-inspector@npm:^5.1.1": +"react-inspector@npm:^5.1.1": version: 5.1.1 resolution: "react-inspector@npm:5.1.1" dependencies: @@ -32808,10 +31825,10 @@ __metadata: languageName: node linkType: hard -"react-is@npm:17.0.2, react-is@npm:^17.0.1, react-is@npm:^17.0.2": - version: 17.0.2 - resolution: "react-is@npm:17.0.2" - checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8 +"react-is@npm:18.1.0": + version: 18.1.0 + resolution: "react-is@npm:18.1.0" + checksum: d206a0fe6790851bff168727bfb896de02c5591695afb0c441163e8630136a3e13ee1a7ddd59fdccddcc93968b4721ae112c10f790b194b03b35a3dc13a355ef languageName: node linkType: hard @@ -32822,6 +31839,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^17.0.1, react-is@npm:^17.0.2": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 9d6d111d8990dc98bc5402c1266a808b0459b5d54830bbea24c12d908b536df7883f268a7868cfaedde3dd9d4e0d574db456f84d2e6df9c4526f99bb4b5344d8 + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -32845,7 +31869,28 @@ __metadata: languageName: node linkType: hard -"react-merge-refs@npm:1.1.0, react-merge-refs@npm:^1.0.0": +"react-markdown@npm:^9.0.1": + version: 9.0.1 + resolution: "react-markdown@npm:9.0.1" + dependencies: + "@types/hast": ^3.0.0 + devlop: ^1.0.0 + hast-util-to-jsx-runtime: ^2.0.0 + html-url-attributes: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + remark-parse: ^11.0.0 + remark-rehype: ^11.0.0 + unified: ^11.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: ca1daa650d48b84a5a9771683cdb3f3d2d418247ce0faf73ede3207c65f2a21cdebb9df37afda67f6fc8f0f0a7b9ce00eb239781954a4d6c7ad88ea4df068add + languageName: node + linkType: hard + +"react-merge-refs@npm:1.1.0": version: 1.1.0 resolution: "react-merge-refs@npm:1.1.0" checksum: 90884352999002d868ab9f1bcfe3222fb0f2178ed629f1da7e98e5a9b02a2c96b4aa72800db92aabd69d2483211b4be57a2088e89a11a0b660e7ada744d4ddf7 @@ -32945,13 +31990,6 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:^0.11.0": - version: 0.11.0 - resolution: "react-refresh@npm:0.11.0" - checksum: 112178a05b1e0ffeaf5d9fb4e56b4410a34a73adeb04dbf13abdc50d9ac9df2ada83e81485156cca0b3fa296aa3612751b3d6cd13be4464642a43679b819cbc7 - languageName: node - linkType: hard - "react-refresh@npm:^0.14.0": version: 0.14.0 resolution: "react-refresh@npm:0.14.0" @@ -33335,27 +32373,6 @@ __metadata: languageName: node linkType: hard -"read-pkg-up@npm:^1.0.1": - version: 1.0.1 - resolution: "read-pkg-up@npm:1.0.1" - dependencies: - find-up: ^1.0.0 - read-pkg: ^1.0.0 - checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 - languageName: node - linkType: hard - -"read-pkg@npm:^1.0.0": - version: 1.1.0 - resolution: "read-pkg@npm:1.1.0" - dependencies: - load-json-file: ^1.0.0 - normalize-package-data: ^2.3.2 - path-type: ^1.0.0 - checksum: a0f5d5e32227ec8e6a028dd5c5134eab229768dcb7a5d9a41a284ed28ad4b9284fecc47383dc1593b5694f4de603a7ffaee84b738956b9b77e0999567485a366 - languageName: node - linkType: hard - "read-pkg@npm:^3.0.0": version: 3.0.0 resolution: "read-pkg@npm:3.0.0" @@ -33391,7 +32408,19 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:1 || 2, readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.1.0, readable-stream@npm:^2.1.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.6, readable-stream@npm:~2.3.6": +"readable-stream@npm:^1.0.26-4": + version: 1.1.14 + resolution: "readable-stream@npm:1.1.14" + dependencies: + core-util-is: ~1.0.0 + inherits: ~2.0.1 + isarray: 0.0.1 + string_decoder: ~0.10.x + checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 + languageName: node + linkType: hard + +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.5, readable-stream@npm:^2.1.0, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": version: 2.3.7 resolution: "readable-stream@npm:2.3.7" dependencies: @@ -33406,19 +32435,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^1.0.26-4": - version: 1.1.14 - resolution: "readable-stream@npm:1.1.14" - dependencies: - core-util-is: ~1.0.0 - inherits: ~2.0.1 - isarray: 0.0.1 - string_decoder: ~0.10.x - checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0 - languageName: node - linkType: hard - -"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1": +"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.5.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -33440,6 +32457,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.4.2 + resolution: "readable-stream@npm:4.4.2" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: 6f4063763dbdb52658d22d3f49ca976420e1fbe16bbd241f744383715845350b196a2f08b8d6330f8e219153dff34b140aeefd6296da828e1041a7eab1f20d5e + languageName: node + linkType: hard + "readable-stream@npm:~1.0.26, readable-stream@npm:~1.0.26-4": version: 1.0.34 resolution: "readable-stream@npm:1.0.34" @@ -33461,17 +32491,6 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:^2.2.1": - version: 2.2.1 - resolution: "readdirp@npm:2.2.1" - dependencies: - graceful-fs: ^4.1.11 - micromatch: ^3.1.10 - readable-stream: ^2.0.2 - checksum: 3879b20f1a871e0e004a14fbf1776e65ee0b746a62f5a416010808b37c272ac49b023c47042c7b1e281cba75a449696635bc64c397ed221ea81d853a8f2ed79a - languageName: node - linkType: hard - "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -33481,6 +32500,19 @@ __metadata: languageName: node linkType: hard +"recast@npm:^0.23.1, recast@npm:^0.23.3": + version: 0.23.4 + resolution: "recast@npm:0.23.4" + dependencies: + assert: ^2.0.0 + ast-types: ^0.16.1 + esprima: ~4.0.0 + source-map: ~0.6.1 + tslib: ^2.0.1 + checksum: edb63bbe0457e68c0f4892f55413000e92aa7c5c53f9e109ab975d1c801cd299a62511ea72734435791f4aea6f0edf560f6a275761f66b2b6069ff6d72686029 + languageName: node + linkType: hard + "recharts-scale@npm:^0.4.4": version: 0.4.5 resolution: "recharts-scale@npm:0.4.5" @@ -33511,16 +32543,6 @@ __metadata: languageName: node linkType: hard -"redent@npm:^1.0.0": - version: 1.0.0 - resolution: "redent@npm:1.0.0" - dependencies: - indent-string: ^2.1.0 - strip-indent: ^1.0.1 - checksum: 2bb8f76fda9c9f44e26620047b0ba9dd1834b0a80309d0badcc23fdcf7bb27a7ca74e66b683baa0d4b8cb5db787f11be086504036d63447976f409dd3e73fd7d - languageName: node - linkType: hard - "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -33625,7 +32647,7 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.2, regenerator-runtime@npm:^0.13.3, regenerator-runtime@npm:^0.13.7": +"regenerator-runtime@npm:^0.13.3": version: 0.13.9 resolution: "regenerator-runtime@npm:0.13.9" checksum: 65ed455fe5afd799e2897baf691ca21c2772e1a969d19bb0c4695757c2d96249eb74ee3553ea34a91062b2a676beedf630b4c1551cc6299afb937be1426ec55e @@ -33648,16 +32670,6 @@ __metadata: languageName: node linkType: hard -"regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": - version: 1.0.2 - resolution: "regex-not@npm:1.0.2" - dependencies: - extend-shallow: ^3.0.2 - safe-regex: ^1.1.0 - checksum: 3081403de79559387a35ef9d033740e41818a559512668cef3d12da4e8a29ef34ee13c8ed1256b07e27ae392790172e8a15c8a06b72962fd4550476cde3d8f77 - languageName: node - linkType: hard - "regex-parser@npm:^2.2.11": version: 2.2.11 resolution: "regex-parser@npm:2.2.11" @@ -33686,6 +32698,17 @@ __metadata: languageName: node linkType: hard +"regexp.prototype.flags@npm:^1.5.1": + version: 1.5.1 + resolution: "regexp.prototype.flags@npm:1.5.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.2.0 + set-function-name: ^2.0.0 + checksum: 869edff00288442f8d7fa4c9327f91d85f3b3acf8cbbef9ea7a220345cf23e9241b6def9263d2c1ebcf3a316b0aa52ad26a43a84aa02baca3381717b3e307f47 + languageName: node + linkType: hard + "regexpp@npm:^3.2.0": version: 3.2.0 resolution: "regexpp@npm:3.2.0" @@ -33756,13 +32779,6 @@ __metadata: languageName: node linkType: hard -"remark-footnotes@npm:2.0.0": - version: 2.0.0 - resolution: "remark-footnotes@npm:2.0.0" - checksum: f2f87ffd6fe25892373c7164d6584a7cb03ab0ea4f186af493a73df519e24b72998a556e7f16cb996f18426cdb80556b95ff252769e252cf3ccba0fd2ca20621 - languageName: node - linkType: hard - "remark-html@npm:^14.0.1": version: 14.0.1 resolution: "remark-html@npm:14.0.1" @@ -33776,46 +32792,6 @@ __metadata: languageName: node linkType: hard -"remark-mdx@npm:1.6.22": - version: 1.6.22 - resolution: "remark-mdx@npm:1.6.22" - dependencies: - "@babel/core": 7.12.9 - "@babel/helper-plugin-utils": 7.10.4 - "@babel/plugin-proposal-object-rest-spread": 7.12.1 - "@babel/plugin-syntax-jsx": 7.12.1 - "@mdx-js/util": 1.6.22 - is-alphabetical: 1.0.4 - remark-parse: 8.0.3 - unified: 9.2.0 - checksum: 45e62f8a821c37261f94448d54f295de1c5c393f762ff96cd4d4b730715037fafeb6c89ef94adf6a10a09edfa72104afe1431b93b5ae5e40ce2a7677e133c3d9 - languageName: node - linkType: hard - -"remark-parse@npm:8.0.3": - version: 8.0.3 - resolution: "remark-parse@npm:8.0.3" - dependencies: - ccount: ^1.0.0 - collapse-white-space: ^1.0.2 - is-alphabetical: ^1.0.0 - is-decimal: ^1.0.0 - is-whitespace-character: ^1.0.0 - is-word-character: ^1.0.0 - markdown-escapes: ^1.0.0 - parse-entities: ^2.0.0 - repeat-string: ^1.5.4 - state-toggle: ^1.0.0 - trim: 0.0.1 - trim-trailing-lines: ^1.0.0 - unherit: ^1.0.4 - unist-util-remove-position: ^2.0.0 - vfile-location: ^3.0.0 - xtend: ^4.0.1 - checksum: 2dfea250e7606ddfc9e223b9f41e0b115c5c701be4bd35181beaadd46ee59816bc00aadc6085a420f8df00b991ada73b590ea7fd34ace14557de4a0a41805be5 - languageName: node - linkType: hard - "remark-parse@npm:^10.0.0": version: 10.0.2 resolution: "remark-parse@npm:10.0.2" @@ -33827,6 +32803,31 @@ __metadata: languageName: node linkType: hard +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + micromark-util-types: ^2.0.0 + unified: ^11.0.0 + checksum: d83d245290fa84bb04fb3e78111f09c74f7417e7c012a64dd8dc04fccc3699036d828fbd8eeec8944f774b6c30cc1d925c98f8c46495ebcee7c595496342ab7f + languageName: node + linkType: hard + +"remark-rehype@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-rehype@npm:11.0.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + mdast-util-to-hast: ^13.0.0 + unified: ^11.0.0 + vfile: ^6.0.0 + checksum: 0ff0fd948759cbde9d507ca1581028d0b89da0b5f610b35a6cb0a511f8d11621449b6eca573b11ddaea77afd37edd4755f3f1eb086ad49a6f7b970b4a4634e13 + languageName: node + linkType: hard + "remark-slug@npm:^6.0.0": version: 6.1.0 resolution: "remark-slug@npm:6.1.0" @@ -33838,15 +32839,6 @@ __metadata: languageName: node linkType: hard -"remark-squeeze-paragraphs@npm:4.0.0": - version: 4.0.0 - resolution: "remark-squeeze-paragraphs@npm:4.0.0" - dependencies: - mdast-squeeze-paragraphs: ^4.0.0 - checksum: 2071eb74d0ecfefb152c4932690a9fd950c3f9f798a676f1378a16db051da68fb20bf288688cc153ba5019dded35408ff45a31dfe9686eaa7a9f1df9edbb6c81 - languageName: node - linkType: hard - "remark-stringify@npm:^10.0.0": version: 10.0.3 resolution: "remark-stringify@npm:10.0.3" @@ -33883,9 +32875,9 @@ __metadata: linkType: hard "remeda@npm:^1.24.1": - version: 1.27.0 - resolution: "remeda@npm:1.27.0" - checksum: 0298aba3ade1d5694bf5a8ca592c735f5ed17325d1f1ee8e4bfc5d80384de47cf6cc42093167f8c512dc4e453896f0ac59685598222328545d7c047c990120a4 + version: 1.33.0 + resolution: "remeda@npm:1.33.0" + checksum: 3d07b191e10b67bb9a008646b98031c12bd20caba25f280a7f491de4c9db7e10150f1243f831669586bb65c3829dfe3319e08c1a5a90414b3fd040c83f2034bf languageName: node linkType: hard @@ -33917,19 +32909,6 @@ __metadata: languageName: node linkType: hard -"renderkid@npm:^2.0.4": - version: 2.0.7 - resolution: "renderkid@npm:2.0.7" - dependencies: - css-select: ^4.1.3 - dom-converter: ^0.2.0 - htmlparser2: ^6.1.0 - lodash: ^4.17.21 - strip-ansi: ^3.0.1 - checksum: d3d7562531fb8104154d4aa6aa977707783616318014088378a6c5bbc36318ada9289543d380ede707e531b7f5b96229e87d1b8944f675e5ec3686e62692c7c7 - languageName: node - linkType: hard - "renderkid@npm:^3.0.0": version: 3.0.0 resolution: "renderkid@npm:3.0.0" @@ -33943,29 +32922,13 @@ __metadata: languageName: node linkType: hard -"repeat-element@npm:^1.1.2": - version: 1.1.4 - resolution: "repeat-element@npm:1.1.4" - checksum: 1edd0301b7edad71808baad226f0890ba709443f03a698224c9ee4f2494c317892dc5211b2ba8cbea7194a9ddbcac01e283bd66de0467ab24ee1fc1a3711d8a9 - languageName: node - linkType: hard - -"repeat-string@npm:^1.5.2, repeat-string@npm:^1.5.4, repeat-string@npm:^1.6.1": +"repeat-string@npm:^1.5.2": version: 1.6.1 resolution: "repeat-string@npm:1.6.1" checksum: 1b809fc6db97decdc68f5b12c4d1a671c8e3f65ec4a40c238bc5200e44e85bcc52a54f78268ab9c29fcf5fe4f1343e805420056d1f30fa9a9ee4c2d93e3cc6c0 languageName: node linkType: hard -"repeating@npm:^2.0.0": - version: 2.0.1 - resolution: "repeating@npm:2.0.1" - dependencies: - is-finite: ^1.0.0 - checksum: d2db0b69c5cb0c14dd750036e0abcd6b3c3f7b2da3ee179786b755cf737ca15fa0fff417ca72de33d6966056f4695440e680a352401fc02c95ade59899afbdd0 - languageName: node - linkType: hard - "replace-string@npm:3.1.0": version: 3.1.0 resolution: "replace-string@npm:3.1.0" @@ -34070,13 +33033,6 @@ __metadata: languageName: node linkType: hard -"resolve-url@npm:^0.2.1": - version: 0.2.1 - resolution: "resolve-url@npm:0.2.1" - checksum: 7b7035b9ed6e7bc7d289e90aef1eab5a43834539695dac6416ca6e91f1a94132ae4796bbd173cdacfdc2ade90b5f38a3fb6186bebc1b221cd157777a23b9ad14 - languageName: node - linkType: hard - "resolve@npm:1.22.2": version: 1.22.2 resolution: "resolve@npm:1.22.2" @@ -34103,7 +33059,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.7, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2": +"resolve@npm:^1.1.7, resolve@npm:^1.14.2, resolve@npm:^1.22.1": version: 1.22.1 resolution: "resolve@npm:1.22.1" dependencies: @@ -34165,7 +33121,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.1.7#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.17.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.3.2#~builtin<compat/resolve>": +"resolve@patch:resolve@^1.1.7#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>": version: 1.22.1 resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin<compat/resolve>::version=1.22.1&hash=c3c19d" dependencies: @@ -34244,13 +33200,6 @@ __metadata: languageName: node linkType: hard -"ret@npm:~0.1.10": - version: 0.1.15 - resolution: "ret@npm:0.1.15" - checksum: d76a9159eb8c946586567bd934358dfc08a36367b3257f7a3d7255fdd7b56597235af23c6afa0d7f0254159e8051f93c918809962ebd6df24ca2a83dbe4d4151 - languageName: node - linkType: hard - "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -34290,7 +33239,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^2.5.4, rimraf@npm:^2.6.3": +"rimraf@npm:^2.6.1": version: 2.7.1 resolution: "rimraf@npm:2.7.1" dependencies: @@ -34301,6 +33250,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:~2.6.2": + version: 2.6.3 + resolution: "rimraf@npm:2.6.3" + dependencies: + glob: ^7.1.3 + bin: + rimraf: ./bin.js + checksum: 3ea587b981a19016297edb96d1ffe48af7e6af69660e3b371dbfc73722a73a0b0e9be5c88089fbeeb866c389c1098e07f64929c7414290504b855f54f901ab10 + languageName: node + linkType: hard + "ripemd160@npm:2.0.2, ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" @@ -34362,6 +33322,56 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.2.0": + version: 4.6.1 + resolution: "rollup@npm:4.6.1" + dependencies: + "@rollup/rollup-android-arm-eabi": 4.6.1 + "@rollup/rollup-android-arm64": 4.6.1 + "@rollup/rollup-darwin-arm64": 4.6.1 + "@rollup/rollup-darwin-x64": 4.6.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.6.1 + "@rollup/rollup-linux-arm64-gnu": 4.6.1 + "@rollup/rollup-linux-arm64-musl": 4.6.1 + "@rollup/rollup-linux-x64-gnu": 4.6.1 + "@rollup/rollup-linux-x64-musl": 4.6.1 + "@rollup/rollup-win32-arm64-msvc": 4.6.1 + "@rollup/rollup-win32-ia32-msvc": 4.6.1 + "@rollup/rollup-win32-x64-msvc": 4.6.1 + fsevents: ~2.3.2 + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 1d66f7f61b242a2064f9f993192f19de370ee54d7b7fd7159b6ece2352079be01ed0b5d7e3f0bb7a9242f1dcad4898a6265f0990e91bba8169b81c52136cbc9f + languageName: node + linkType: hard + "rootpath@npm:^0.1.2": version: 0.1.2 resolution: "rootpath@npm:0.1.2" @@ -34398,13 +33408,6 @@ __metadata: languageName: node linkType: hard -"rsvp@npm:^4.8.4": - version: 4.8.5 - resolution: "rsvp@npm:4.8.5" - checksum: 2d8ef30d8febdf05bdf856ccca38001ae3647e41835ca196bc1225333f79b94ae44def733121ca549ccc36209c9b689f6586905e2a043873262609744da8efc1 - languageName: node - linkType: hard - "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -34421,16 +33424,7 @@ __metadata: languageName: node linkType: hard -"run-queue@npm:^1.0.0, run-queue@npm:^1.0.3": - version: 1.0.3 - resolution: "run-queue@npm:1.0.3" - dependencies: - aproba: ^1.1.1 - checksum: c4541e18b5e056af60f398f2f1b3d89aae5c093d1524bf817c5ee68bcfa4851ad9976f457a9aea135b1d0d72ee9a91c386e3d136bcd95b699c367cd09c70be53 - languageName: node - linkType: hard - -"rxjs@npm:^7.0.0, rxjs@npm:^7.8.0": +"rxjs@npm:^7.0.0, rxjs@npm:^7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -34471,13 +33465,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.1.1": - version: 5.1.1 - resolution: "safe-buffer@npm:5.1.1" - checksum: 7f117b604554c9daca713be76cecc6c52932ed1dd6303638274f21319038bfd760fbfd353e526cc83f11894935bc4beb71f5b7b9478c11bf9718c0e0d94c51cb - languageName: node - linkType: hard - "safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -34496,15 +33483,6 @@ __metadata: languageName: node linkType: hard -"safe-regex@npm:^1.1.0": - version: 1.1.0 - resolution: "safe-regex@npm:1.1.0" - dependencies: - ret: ~0.1.10 - checksum: 9a8bba57c87a841f7997b3b951e8e403b1128c1a4fd1182f40cc1a20e2d490593d7c2a21030fadfea320c8e859219019e136f678c6689ed5960b391b822f01d5 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -34512,25 +33490,6 @@ __metadata: languageName: node linkType: hard -"sane@npm:^4.0.3": - version: 4.1.0 - resolution: "sane@npm:4.1.0" - dependencies: - "@cnakazawa/watch": ^1.0.3 - anymatch: ^2.0.0 - capture-exit: ^2.0.0 - exec-sh: ^0.3.2 - execa: ^1.0.0 - fb-watchman: ^2.0.0 - micromatch: ^3.1.4 - minimist: ^1.1.1 - walker: ~1.0.5 - bin: - sane: ./src/cli.js - checksum: 97716502d456c0d38670a902a4ea943d196dcdf998d1e40532d8f3e24e25d7eddfd4c3579025a1eee8eac09a48dfd05fba61a2156c56704e7feaa450eb249f7c - languageName: node - linkType: hard - "sanitize-html@npm:^2.10.0": version: 2.10.0 resolution: "sanitize-html@npm:2.10.0" @@ -34639,29 +33598,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:2.7.0": - version: 2.7.0 - resolution: "schema-utils@npm:2.7.0" - dependencies: - "@types/json-schema": ^7.0.4 - ajv: ^6.12.2 - ajv-keywords: ^3.4.1 - checksum: 8889325b0ee1ae6a8f5d6aaa855c71e136ebbb7fd731b01a9d3ec8225dcb245f644c47c50104db4c741983b528cdff8558570021257d4d397ec6aaecd9172a8e - languageName: node - linkType: hard - -"schema-utils@npm:^1.0.0": - version: 1.0.0 - resolution: "schema-utils@npm:1.0.0" - dependencies: - ajv: ^6.1.0 - ajv-errors: ^1.0.0 - ajv-keywords: ^3.1.0 - checksum: e8273b4f6eff9ddf4a4f4c11daf7b96b900237bf8859c86fa1e9b4fab416b72d7ea92468f8db89c18a3499a1070206e1c8a750c83b42d5325fc659cbb55eee88 - languageName: node - linkType: hard - -"schema-utils@npm:^2.6.5, schema-utils@npm:^2.7.0": +"schema-utils@npm:^2.6.5": version: 2.7.1 resolution: "schema-utils@npm:2.7.1" dependencies: @@ -34672,7 +33609,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1": +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1": version: 3.1.1 resolution: "schema-utils@npm:3.1.1" dependencies: @@ -34683,6 +33620,17 @@ __metadata: languageName: node linkType: hard +"schema-utils@npm:^3.2.0": + version: 3.3.0 + resolution: "schema-utils@npm:3.3.0" + dependencies: + "@types/json-schema": ^7.0.8 + ajv: ^6.12.5 + ajv-keywords: ^3.5.2 + checksum: ea56971926fac2487f0757da939a871388891bc87c6a82220d125d587b388f1704788f3706e7f63a7b70e49fc2db974c41343528caea60444afd5ce0fe4b85c0 + languageName: node + linkType: hard + "schema-utils@npm:^4.0.0": version: 4.0.0 resolution: "schema-utils@npm:4.0.0" @@ -34737,16 +33685,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.0.0": - version: 7.0.0 - resolution: "semver@npm:7.0.0" - bin: - semver: bin/semver.js - checksum: 272c11bf8d083274ef79fe40a81c55c184dff84dd58e3c325299d0927ba48cece1f020793d138382b85f89bab5002a35a5ba59a3a68a7eebbb597eb733838778 - languageName: node - linkType: hard - -"semver@npm:^6.0.0, semver@npm:^6.1.2, semver@npm:^6.3.0": +"semver@npm:^6.0.0, semver@npm:^6.3.0": version: 6.3.0 resolution: "semver@npm:6.3.0" bin: @@ -34764,17 +33703,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.2.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" - dependencies: - lru-cache: ^6.0.0 - bin: - semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 - languageName: node - linkType: hard - "semver@npm:^7.3.2": version: 7.3.5 resolution: "semver@npm:7.3.5" @@ -34786,6 +33714,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": + version: 7.3.7 + resolution: "semver@npm:7.3.7" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + languageName: node + linkType: hard + "semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4": version: 7.5.4 resolution: "semver@npm:7.5.4" @@ -34870,24 +33809,6 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^4.0.0": - version: 4.0.0 - resolution: "serialize-javascript@npm:4.0.0" - dependencies: - randombytes: ^2.1.0 - checksum: 3273b3394b951671fcf388726e9577021870dfbf85e742a1183fb2e91273e6101bdccea81ff230724f6659a7ee4cef924b0ff9baca32b79d9384ec37caf07302 - languageName: node - linkType: hard - -"serialize-javascript@npm:^5.0.1": - version: 5.0.1 - resolution: "serialize-javascript@npm:5.0.1" - dependencies: - randombytes: ^2.1.0 - checksum: bb45a427690c3d2711e28499de0fbf25036af1e23c63c6a9237ed0aa572fd0941fcdefe50a2dccf26d9df8c8b86ae38659e19d8ba7afd3fbc1f1c7539a2a48d2 - languageName: node - linkType: hard - "serialize-javascript@npm:^6.0.0": version: 6.0.0 resolution: "serialize-javascript@npm:6.0.0" @@ -34897,16 +33818,12 @@ __metadata: languageName: node linkType: hard -"serve-favicon@npm:^2.5.0": - version: 2.5.0 - resolution: "serve-favicon@npm:2.5.0" +"serialize-javascript@npm:^6.0.1": + version: 6.0.1 + resolution: "serialize-javascript@npm:6.0.1" dependencies: - etag: ~1.8.1 - fresh: 0.5.2 - ms: 2.1.1 - parseurl: ~1.3.2 - safe-buffer: 5.1.1 - checksum: f4dd0fbee3b7e18d0a27ba6ba01d2f585f23f533010c9e8c74aad74615b19b12d8fbe714f14cb3579803f0bacecd67cdc858714cb56c6e28f8dd07ccc997aea4 + randombytes: ^2.1.0 + checksum: 3c4f4cb61d0893b988415bdb67243637333f3f574e9e9cc9a006a2ced0b390b0b3b44aef8d51c951272a9002ec50885eefdc0298891bc27eb2fe7510ea87dc4f languageName: node linkType: hard @@ -34936,15 +33853,26 @@ __metadata: languageName: node linkType: hard -"set-value@npm:^2.0.0, set-value@npm:^2.0.1": - version: 2.0.1 - resolution: "set-value@npm:2.0.1" +"set-function-length@npm:^1.1.1": + version: 1.1.1 + resolution: "set-function-length@npm:1.1.1" dependencies: - extend-shallow: ^2.0.1 - is-extendable: ^0.1.1 - is-plain-object: ^2.0.3 - split-string: ^3.0.1 - checksum: 09a4bc72c94641aeae950eb60dc2755943b863780fcc32e441eda964b64df5e3f50603d5ebdd33394ede722528bd55ed43aae26e9df469b4d32e2292b427b601 + define-data-property: ^1.1.1 + get-intrinsic: ^1.2.1 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.0 + checksum: c131d7569cd7e110cafdfbfbb0557249b538477624dfac4fc18c376d879672fa52563b74029ca01f8f4583a8acb35bb1e873d573a24edb80d978a7ee607c6e06 + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.0": + version: 2.0.1 + resolution: "set-function-name@npm:2.0.1" + dependencies: + define-data-property: ^1.0.1 + functions-have-names: ^1.2.3 + has-property-descriptors: ^1.0.0 + checksum: 4975d17d90c40168eee2c7c9c59d023429f0a1690a89d75656306481ece0c3c1fb1ebcc0150ea546d1913e35fbd037bace91372c69e543e51fc5d1f31a9fa126 languageName: node linkType: hard @@ -34990,6 +33918,23 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.32.6": + version: 0.32.6 + resolution: "sharp@npm:0.32.6" + dependencies: + color: ^4.2.3 + detect-libc: ^2.0.2 + node-addon-api: ^6.1.0 + node-gyp: latest + prebuild-install: ^7.1.1 + semver: ^7.5.4 + simple-get: ^4.0.1 + tar-fs: ^3.0.4 + tunnel-agent: ^0.6.0 + checksum: 0cca1d16b1920800c0e22d27bc6305f4c67c9ebe44f67daceb30bf645ae39e7fb7dfbd7f5d6cd9f9eebfddd87ac3f7e2695f4eb906d19b7a775286238e6a29fc + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" @@ -35085,6 +34030,42 @@ __metadata: languageName: node linkType: hard +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0, simple-get@npm:^4.0.1": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: ^6.0.0 + once: ^1.3.1 + simple-concat: ^1.0.0 + checksum: e4132fd27cf7af230d853fa45c1b8ce900cb430dd0a3c6d3829649fe4f2b26574c803698076c4006450efb0fad2ba8c5455fbb5755d4b0a5ec42d4f12b31d27e + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: ^0.3.1 + checksum: a7f3f2ab5c76c4472d5c578df892e857323e452d9f392e1b5cf74b74db66e6294a1e1b8b390b519fa1b96b5b613f2a37db6cffef52c3f1f8f3c5ea64eb2d54c0 + languageName: node + linkType: hard + +"simple-update-notifier@npm:^2.0.0": + version: 2.0.0 + resolution: "simple-update-notifier@npm:2.0.0" + dependencies: + semver: ^7.5.3 + checksum: 9ba00d38ce6a29682f64a46213834e4eb01634c2f52c813a9a7b8873ca49cdbb703696f3290f3b27dc067de6d9418b0b84bef22c3eb074acf352529b2d6c27fd + languageName: node + linkType: hard + "sirv@npm:^1.0.7": version: 1.0.19 resolution: "sirv@npm:1.0.19" @@ -35103,13 +34084,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^2.0.0": - version: 2.0.0 - resolution: "slash@npm:2.0.0" - checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6 - languageName: node - linkType: hard - "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -35198,42 +34172,6 @@ __metadata: languageName: node linkType: hard -"snapdragon-node@npm:^2.0.1": - version: 2.1.1 - resolution: "snapdragon-node@npm:2.1.1" - dependencies: - define-property: ^1.0.0 - isobject: ^3.0.0 - snapdragon-util: ^3.0.1 - checksum: 9bb57d759f9e2a27935dbab0e4a790137adebace832b393e350a8bf5db461ee9206bb642d4fe47568ee0b44080479c8b4a9ad0ebe3712422d77edf9992a672fd - languageName: node - linkType: hard - -"snapdragon-util@npm:^3.0.1": - version: 3.0.1 - resolution: "snapdragon-util@npm:3.0.1" - dependencies: - kind-of: ^3.2.0 - checksum: 684997dbe37ec995c03fd3f412fba2b711fc34cb4010452b7eb668be72e8811a86a12938b511e8b19baf853b325178c56d8b78d655305e5cfb0bb8b21677e7b7 - languageName: node - linkType: hard - -"snapdragon@npm:^0.8.1": - version: 0.8.2 - resolution: "snapdragon@npm:0.8.2" - dependencies: - base: ^0.11.1 - debug: ^2.2.0 - define-property: ^0.2.5 - extend-shallow: ^2.0.1 - map-cache: ^0.2.2 - source-map: ^0.5.6 - source-map-resolve: ^0.5.0 - use: ^3.1.0 - checksum: a197f242a8f48b11036563065b2487e9b7068f50a20dd81d9161eca6af422174fc158b8beeadbe59ce5ef172aa5718143312b3aebaae551c124b7824387c8312 - languageName: node - linkType: hard - "socks-proxy-agent@npm:^7.0.0": version: 7.0.0 resolution: "socks-proxy-agent@npm:7.0.0" @@ -35262,13 +34200,6 @@ __metadata: languageName: node linkType: hard -"source-list-map@npm:^2.0.0": - version: 2.0.1 - resolution: "source-list-map@npm:2.0.1" - checksum: 806efc6f75e7cd31e4815e7a3aaf75a45c704871ea4075cb2eb49882c6fca28998f44fc5ac91adb6de03b2882ee6fb02f951fdc85e6a22b338c32bfe19557938 - languageName: node - linkType: hard - "source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2" @@ -35276,20 +34207,7 @@ __metadata: languageName: node linkType: hard -"source-map-resolve@npm:^0.5.0": - version: 0.5.3 - resolution: "source-map-resolve@npm:0.5.3" - dependencies: - atob: ^2.1.2 - decode-uri-component: ^0.2.0 - resolve-url: ^0.2.1 - source-map-url: ^0.4.0 - urix: ^0.1.0 - checksum: c73fa44ac00783f025f6ad9e038ab1a2e007cd6a6b86f47fe717c3d0765b4a08d264f6966f3bd7cd9dbcd69e4832783d5472e43247775b2a550d6f2155d24bae - languageName: node - linkType: hard - -"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -35299,13 +34217,6 @@ __metadata: languageName: node linkType: hard -"source-map-url@npm:^0.4.0": - version: 0.4.1 - resolution: "source-map-url@npm:0.4.1" - checksum: 64c5c2c77aff815a6e61a4120c309ae4cac01298d9bcbb3deb1b46a4dd4c46d4a1eaeda79ec9f684766ae80e8dc86367b89326ce9dd2b89947bd9291fc1ac08c - languageName: node - linkType: hard - "source-map@npm:0.6.1, source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" @@ -35313,7 +34224,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.5.0, source-map@npm:^0.5.6, source-map@npm:^0.5.7": +"source-map@npm:^0.5.0, source-map@npm:^0.5.7": version: 0.5.7 resolution: "source-map@npm:0.5.7" checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d @@ -35422,15 +34333,6 @@ __metadata: languageName: node linkType: hard -"split-string@npm:^3.0.1, split-string@npm:^3.0.2": - version: 3.1.0 - resolution: "split-string@npm:3.1.0" - dependencies: - extend-shallow: ^3.0.0 - checksum: ae5af5c91bdc3633628821bde92fdf9492fa0e8a63cf6a0376ed6afde93c701422a1610916f59be61972717070119e848d10dfbbd5024b7729d6a71972d2a84c - languageName: node - linkType: hard - "split2@npm:^3.1.0": version: 3.2.2 resolution: "split2@npm:3.2.2" @@ -35507,24 +34409,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^6.0.1": - version: 6.0.2 - resolution: "ssri@npm:6.0.2" - dependencies: - figgy-pudding: ^3.5.1 - checksum: 7c2e5d442f6252559c8987b7114bcf389fe5614bf65de09ba3e6f9a57b9b65b2967de348fcc3acccff9c069adb168140dd2c5fc2f6f4a779e604a27ef1f7d551 - languageName: node - linkType: hard - -"ssri@npm:^8.0.1": - version: 8.0.1 - resolution: "ssri@npm:8.0.1" - dependencies: - minipass: ^3.1.1 - checksum: bc447f5af814fa9713aa201ec2522208ae0f4d8f3bda7a1f445a797c7b929a02720436ff7c478fb5edc4045adb02b1b88d2341b436a80798734e2494f1067b36 - languageName: node - linkType: hard - "ssri@npm:^9.0.0": version: 9.0.1 resolution: "ssri@npm:9.0.1" @@ -35534,13 +34418,6 @@ __metadata: languageName: node linkType: hard -"stable@npm:^0.1.8": - version: 0.1.8 - resolution: "stable@npm:0.1.8" - checksum: 2ff482bb100285d16dd75cd8f7c60ab652570e8952c0bfa91828a2b5f646a0ff533f14596ea4eabd48bb7f4aeea408dce8f8515812b975d958a4cc4fa6b9dfeb - languageName: node - linkType: hard - "stack-utils@npm:^2.0.2": version: 2.0.5 resolution: "stack-utils@npm:2.0.5" @@ -35582,23 +34459,6 @@ __metadata: languageName: node linkType: hard -"state-toggle@npm:^1.0.0": - version: 1.0.3 - resolution: "state-toggle@npm:1.0.3" - checksum: 17398af928413e8d8b866cf0c81fd1b1348bb7d65d8983126ff6ff2317a80d6ee023484fba0c54d8169f5aa544f125434a650ae3a71eddc935cae307d4692b4f - languageName: node - linkType: hard - -"static-extend@npm:^0.1.1": - version: 0.1.2 - resolution: "static-extend@npm:0.1.2" - dependencies: - define-property: ^0.2.5 - object-copy: ^0.1.0 - checksum: 8657485b831f79e388a437260baf22784540417a9b29e11572c87735df24c22b84eda42107403a64b30861b2faf13df9f7fc5525d51f9d1d2303aba5cbf4e12c - languageName: node - linkType: hard - "statuses@npm:2.0.1, statuses@npm:^2.0.0": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -35620,6 +34480,15 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: ^1.0.4 + checksum: d04173690b2efa40e24ab70e5e51a3ff31d56d699550cfad084104ab3381390daccb36652b25755e420245f3b0737de66c1879eaa2a8d4fc0a78f9bf892fcb42 + languageName: node + linkType: hard + "stoppable@npm:^1.1.0": version: 1.1.0 resolution: "stoppable@npm:1.1.0" @@ -35627,58 +34496,10 @@ __metadata: languageName: node linkType: hard -"store2@npm:^2.12.0": - version: 2.13.2 - resolution: "store2@npm:2.13.2" - checksum: 9e760ea2a7f56eae47d5bafe507511b25ad983bba901e1e0c5f65713e631c15aafb8e031c658047af53c2008a5d21cb6c43f2383673b3493144e8e1ead5c8f91 - languageName: node - linkType: hard - -"storybook-addon-designs@npm:^6.3.1": - version: 6.3.1 - resolution: "storybook-addon-designs@npm:6.3.1" - dependencies: - "@figspec/react": ^1.0.0 - checksum: 63d91358540a59801028b4fe2fe61a2cad52ac2354efde0959f081379045230af4ea6dea6a8abddae2a7eb14699fd04024dec44b6fd396065e0e585d31980635 - languageName: node - linkType: hard - -"storybook-addon-next-router@npm:^4.0.2": - version: 4.0.2 - resolution: "storybook-addon-next-router@npm:4.0.2" - dependencies: - tslib: 2.4.0 - peerDependencies: - "@storybook/addon-actions": ^6.0.0 - "@storybook/addons": ^6.0.0 - "@storybook/client-api": ^6.0.0 - next: ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 5e3d1b2510700c21d175e10d56bcad970a8b390715bbd8e9767528e60165c4bc651ef94a7a854f042185454c51c57d884b03c625c0ee28000d3916e11a6d865f - languageName: node - linkType: hard - -"storybook-addon-next@npm:^1.6.9": - version: 1.6.9 - resolution: "storybook-addon-next@npm:1.6.9" - dependencies: - "@storybook/addons": ^6.4.10 - image-size: ^1.0.0 - loader-utils: ^3.2.0 - postcss-loader: ^6.2.1 - resolve-url-loader: ^5.0.0 - sass-loader: ^12.4.0 - semver: ^7.3.5 - tsconfig-paths: ^4.0.0 - tsconfig-paths-webpack-plugin: ^4.0.0 - peerDependencies: - "@storybook/addon-actions": ^6.0.0 - "@storybook/addons": ^6.0.0 - next: ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 040cc66a835b51449267eb339303635dd0f88f8dcb49e198a3fadd2ef220acd80e778ad55827d64bc372db7a470d58a8c68bf68e32608adef97780ce59f9900c +"store2@npm:^2.14.2": + version: 2.14.2 + resolution: "store2@npm:2.14.2" + checksum: 6f270fc5bab99b63f45fcc7bd8b99c2714b4adf880f557ed7ffb5ed3987131251165bccde425a00928aaf044870aee79ddeef548576d093c68703ed2edec45d7 languageName: node linkType: hard @@ -35702,15 +34523,14 @@ __metadata: languageName: node linkType: hard -"storybook-i18n@npm:^1.1.2": - version: 1.1.4 - resolution: "storybook-i18n@npm:1.1.4" +"storybook-i18n@npm:2.0.13": + version: 2.0.13 + resolution: "storybook-i18n@npm:2.0.13" peerDependencies: - "@storybook/addons": ">=6.5.0" - "@storybook/api": ">=6.5.0" - "@storybook/components": ">=6.5.0" - "@storybook/core-events": ">=6.5.0" - "@storybook/theming": ">=6.5.0" + "@storybook/components": ^7.0.0 + "@storybook/manager-api": ^7.0.0 + "@storybook/preview-api": ^7.0.0 + "@storybook/types": ^7.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: @@ -35718,43 +34538,54 @@ __metadata: optional: true react-dom: optional: true - checksum: bcc79a35677936ff3ab9545ea0ef348e025c37fa5bb4e7a60a8560144969c21ebd5472857265e175a1b049b1462e90c1b4dfde645b2cca419c43e97cdbd8e4d0 + checksum: 024a6d2bf1c3c416e55c7242abc8caa5ecea7f03e59d581b88a73d603831a05ae68888ffb712f37fedd15dc50274275bbcf0c692824e0f491ceb0b2ac348a3d7 languageName: node linkType: hard -"storybook-react-i18next@npm:^1.1.2": - version: 1.1.2 - resolution: "storybook-react-i18next@npm:1.1.2" +"storybook-react-i18next@npm:^2.0.9": + version: 2.0.9 + resolution: "storybook-react-i18next@npm:2.0.9" dependencies: - storybook-i18n: ^1.1.2 + storybook-i18n: 2.0.13 peerDependencies: - "@storybook/addons": ">=6.5.0" - "@storybook/api": ">=6.5.0" - "@storybook/components": ">=6.5.0" - "@storybook/core-events": ">=6.5.0" - "@storybook/theming": ">=6.5.0" - i18next: ">=21.0.0" - i18next-browser-languagedetector: ^6.1.4 - i18next-http-backend: ^1.4.0 + "@storybook/components": ^7.0.0 + "@storybook/manager-api": ^7.0.0 + "@storybook/preview-api": ^7.0.0 + "@storybook/types": ^7.0.0 + i18next: ^22.0.0 || ^23.0.0 + i18next-browser-languagedetector: ^7.0.0 + i18next-http-backend: ^2.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-i18next: ^11.17.1 + react-i18next: ^12.0.0 || ^13.0.0 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: 05688901b5af0fed702ca4b80422f5e1d91beb334c562a0bbe7fccb5f16447ebca328b64a8f3deaee69e19bc0873769a4ea5e7429e6832d8a6fa434986d4f4b8 + checksum: 87f201a8fb61b1c3c1270a14ace69e1aadb0d4e39cca2e2a000385fd056771e3b652adba44b7d2983605af69ac20ef2f54972d372fcdf52ae55fb77a1b0eeec4 languageName: node linkType: hard -"stream-browserify@npm:^2.0.1": - version: 2.0.2 - resolution: "stream-browserify@npm:2.0.2" +"storybook@npm:^7.6.3": + version: 7.6.3 + resolution: "storybook@npm:7.6.3" dependencies: - inherits: ~2.0.1 - readable-stream: ^2.0.2 - checksum: 8de7bcab5582e9a931ae1a4768be7efe8fa4b0b95fd368d16d8cf3e494b897d6b0a7238626de5d71686e53bddf417fd59d106cfa3af0ec055f61a8d1f8fc77b3 + "@storybook/cli": 7.6.3 + bin: + sb: ./index.js + storybook: ./index.js + checksum: b4b4641c6ce745379fa3a253182322692274d8e340be53c772e6e65b29566d6a72376422f5714c869f0e9239d83491539a7ff5a9a269440c4897d7cec6c0fb4e + languageName: node + linkType: hard + +"stream-browserify@npm:^3.0.0": + version: 3.0.0 + resolution: "stream-browserify@npm:3.0.0" + dependencies: + inherits: ~2.0.4 + readable-stream: ^3.5.0 + checksum: 4c47ef64d6f03815a9ca3874e2319805e8e8a85f3550776c47ce523b6f4c6cd57f40e46ec6a9ab8ad260fde61863c2718f250d3bedb3fe9052444eb9abfd9921 languageName: node linkType: hard @@ -35767,26 +34598,15 @@ __metadata: languageName: node linkType: hard -"stream-each@npm:^1.1.0": - version: 1.2.3 - resolution: "stream-each@npm:1.2.3" - dependencies: - end-of-stream: ^1.1.0 - stream-shift: ^1.0.0 - checksum: f243de78e9fcc60757994efc4e8ecae9f01a4b2c6a505d786b11fcaa68b1a75ca54afc1669eac9e08f19ff0230792fc40d0f3e3e2935d76971b4903af18b76ab - languageName: node - linkType: hard - -"stream-http@npm:^2.7.2": - version: 2.8.3 - resolution: "stream-http@npm:2.8.3" +"stream-http@npm:^3.2.0": + version: 3.2.0 + resolution: "stream-http@npm:3.2.0" dependencies: builtin-status-codes: ^3.0.0 - inherits: ^2.0.1 - readable-stream: ^2.3.6 - to-arraybuffer: ^1.0.0 - xtend: ^4.0.0 - checksum: f57dfaa21a015f72e6ce6b199cf1762074cfe8acf0047bba8f005593754f1743ad0a91788f95308d9f3829ad55742399ad27b4624432f2752a08e62ef4346e05 + inherits: ^2.0.4 + readable-stream: ^3.6.0 + xtend: ^4.0.2 + checksum: c9b78453aeb0c84fcc59555518ac62bacab9fa98e323e7b7666e5f9f58af8f3155e34481078509b02929bd1268427f664d186604cdccee95abc446099b339f83 languageName: node linkType: hard @@ -35813,6 +34633,16 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0": + version: 2.15.5 + resolution: "streamx@npm:2.15.5" + dependencies: + fast-fifo: ^1.1.0 + queue-tick: ^1.0.1 + checksum: 52e0ec94026d67c9e2e2e1090f05e5b138c2f2822462d9a8ef4a4805625a31d103e55ea5267fcd9bfe041374926424e42aec2dda28a85cb9de42c2a16d416d94 + languageName: node + linkType: hard + "strict-event-emitter@npm:^0.2.0, strict-event-emitter@npm:^0.2.4": version: 0.2.4 resolution: "strict-event-emitter@npm:0.2.4" @@ -35879,7 +34709,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.matchall@npm:^4.0.0 || ^3.0.1, string.prototype.matchall@npm:^4.0.7": +"string.prototype.matchall@npm:^4.0.7": version: 4.0.7 resolution: "string.prototype.matchall@npm:4.0.7" dependencies: @@ -35922,17 +34752,6 @@ __metadata: languageName: node linkType: hard -"string.prototype.padstart@npm:^3.0.0": - version: 3.1.3 - resolution: "string.prototype.padstart@npm:3.1.3" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.1 - checksum: 8bf8bc1d25edc79c4db285aa8dfd5d269dac4024631e8ae13202c2126348a07e00b153d6bf7b858c5bd716e44675a7fbb50baedd3e8970e1034bb86be22c9475 - languageName: node - linkType: hard - "string.prototype.trimend@npm:^1.0.4": version: 1.0.4 resolution: "string.prototype.trimend@npm:1.0.4" @@ -35975,7 +34794,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -36030,7 +34849,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": +"strip-ansi@npm:^3.0.0": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" dependencies: @@ -36055,15 +34874,6 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-bom@npm:2.0.0" - dependencies: - is-utf8: ^0.2.0 - checksum: 08efb746bc67b10814cd03d79eb31bac633393a782e3f35efbc1b61b5165d3806d03332a97f362822cf0d4dd14ba2e12707fcff44fe1c870c48a063a0c9e4944 - languageName: node - linkType: hard - "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -36094,24 +34904,29 @@ __metadata: languageName: node linkType: hard -"strip-indent@npm:^1.0.1": - version: 1.0.1 - resolution: "strip-indent@npm:1.0.1" +"strip-indent@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-indent@npm:4.0.0" dependencies: - get-stdin: ^4.0.1 - bin: - strip-indent: cli.js - checksum: 81ad9a0b8a558bdbd05b66c6c437b9ab364aa2b5479ed89969ca7908e680e21b043d40229558c434b22b3d640622e39b66288e0456d601981ac9289de9700fbd + min-indent: ^1.0.1 + checksum: 06cbcd93da721c46bc13caeb1c00af93a9b18146a1c95927672d2decab6a25ad83662772417cea9317a2507fb143253ecc23c4415b64f5828cef9b638a744598 languageName: node linkType: hard -"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "strip-literal@npm:^1.0.1": version: 1.0.1 resolution: "strip-literal@npm:1.0.1" @@ -36131,16 +34946,6 @@ __metadata: languageName: node linkType: hard -"stripe@npm:^14.5.0": - version: 14.5.0 - resolution: "stripe@npm:14.5.0" - dependencies: - "@types/node": ">=8.1.0" - qs: ^6.11.0 - checksum: ad2f0205d1b0ce4691bf54dfd0953e197b2fb3c37c489e9f1799dfa75fbd21a49cb5ddb7bcf0a708242db565500ba494927e321aee91980f360c389a60048a0c - languageName: node - linkType: hard - "stripe@npm:^9.16.0": version: 9.16.0 resolution: "stripe@npm:9.16.0" @@ -36165,36 +34970,21 @@ __metadata: languageName: node linkType: hard -"style-loader@npm:^1.3.0": - version: 1.3.0 - resolution: "style-loader@npm:1.3.0" - dependencies: - loader-utils: ^2.0.0 - schema-utils: ^2.7.0 +"style-loader@npm:^3.3.1": + version: 3.3.3 + resolution: "style-loader@npm:3.3.3" peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 1be9e8705307f5b8eb89e80f3703fa27296dccec349d790eace7aabe212f08c7c8f3ea6b6cb97bc53e82fbebfb9aa0689259671a8315f4655e24a850781e062a + webpack: ^5.0.0 + checksum: f59c953f56f6a935bd6a1dfa409f1128fed2b66b48ce4a7a75b85862a7156e5e90ab163878962762f528ec4d510903d828da645e143fbffd26f055dc1c094078 languageName: node linkType: hard -"style-loader@npm:^2.0.0": - version: 2.0.0 - resolution: "style-loader@npm:2.0.0" +"style-to-object@npm:^1.0.0": + version: 1.0.5 + resolution: "style-to-object@npm:1.0.5" dependencies: - loader-utils: ^2.0.0 - schema-utils: ^3.0.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 21425246a5a8f14d1625a657a3a56f8a323193fa341a71af818a2ed2a429efa2385a328b4381cf2f12c2d0e6380801eb9e0427ed9c3a10ff95c86e383184d632 - languageName: node - linkType: hard - -"style-to-object@npm:0.3.0, style-to-object@npm:^0.3.0": - version: 0.3.0 - resolution: "style-to-object@npm:0.3.0" - dependencies: - inline-style-parser: 0.1.1 - checksum: 4d7084015207f2a606dfc10c29cb5ba569f2fe8005551df7396110dd694d6ff650f2debafa95bd5d147dfb4ca50f57868e2a7f91bf5d11ef734fe7ccbd7abf59 + inline-style-parser: 0.2.2 + checksum: 6201063204b6a94645f81b189452b2ca3e63d61867ec48523f4d52609c81e96176739fa12020d97fbbf023efb57a6f7ec3a15fb3a7fb7eb3ffea0b52b9dd6b8c languageName: node linkType: hard @@ -36268,11 +35058,11 @@ __metadata: linkType: hard "superjson@npm:^1.9.1": - version: 1.13.1 - resolution: "superjson@npm:1.13.1" + version: 1.13.3 + resolution: "superjson@npm:1.13.3" dependencies: copy-anything: ^3.0.2 - checksum: 9c8c664a924ce097250112428805ccc8b500018b31a91042e953d955108b8481c156005d836b413940c9fa5f124a3195f55f3a518fe76510a254a59f9151a204 + checksum: f5aeb010f24163cb871a4bc402d9164112201c059afc247a75b03131c274aea6eec9cf08be9e4a9465fe4961689009a011584528531d52f7cc91c077e07e5c75 languageName: node linkType: hard @@ -36334,17 +35124,6 @@ __metadata: languageName: node linkType: hard -"sveltedoc-parser@npm:^4.2.1": - version: 4.2.1 - resolution: "sveltedoc-parser@npm:4.2.1" - dependencies: - eslint: 8.4.1 - espree: 9.2.0 - htmlparser2-svelte: 4.1.0 - checksum: 26929081b32474df5db3ddde6d12c6732f6f40bfa353da6b988ca9d15260ee5234ba4a2e2294e5d6da741522905b8ec6ff0058d502ab88f5e76f92231ef98527 - languageName: node - linkType: hard - "svix-fetch@npm:^3.0.0": version: 3.0.0 resolution: "svix-fetch@npm:3.0.0" @@ -36490,6 +35269,16 @@ __metadata: languageName: node linkType: hard +"swc-loader@npm:^0.2.3": + version: 0.2.3 + resolution: "swc-loader@npm:0.2.3" + peerDependencies: + "@swc/core": ^1.2.147 + webpack: ">=2" + checksum: 010d84d399525c0185d36d62c86c55ae017e7a90046bc8a39be4b7e07526924037868049f6037bc966da98151cb2600934b96a66279b742d3c413a718b427251 + languageName: node + linkType: hard + "swr@npm:^1.2.2": version: 1.3.0 resolution: "swr@npm:1.3.0" @@ -36506,18 +35295,6 @@ __metadata: languageName: node linkType: hard -"symbol.prototype.description@npm:^1.0.0": - version: 1.0.5 - resolution: "symbol.prototype.description@npm:1.0.5" - dependencies: - call-bind: ^1.0.2 - get-symbol-description: ^1.0.0 - has-symbols: ^1.0.2 - object.getownpropertydescriptors: ^2.1.2 - checksum: 2bf20a5fbc74bdda7133e0915b978bf50bf5e2a48dd2174885ba6cd623d001ca18f7dbb1e01a3f3ea3a34f05030175ebee3dcb357f099a61af6e964f3281e9b9 - languageName: node - linkType: hard - "synchronous-promise@npm:^2.0.15": version: 2.0.15 resolution: "synchronous-promise@npm:2.0.15" @@ -36614,21 +35391,37 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^1.0.0, tapable@npm:^1.1.3": - version: 1.1.3 - resolution: "tapable@npm:1.1.3" - checksum: 53ff4e7c3900051c38cc4faab428ebfd7e6ad0841af5a7ac6d5f3045c5b50e88497bfa8295b4b3fbcadd94993c9e358868b78b9fb249a76cb8b018ac8dccafd7 - languageName: node - linkType: hard - -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0": +"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51 languageName: node linkType: hard -"tar-stream@npm:^2.2.0": +"tar-fs@npm:^2.0.0, tar-fs@npm:^2.1.1": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d + languageName: node + linkType: hard + +"tar-fs@npm:^3.0.4": + version: 3.0.4 + resolution: "tar-fs@npm:3.0.4" + dependencies: + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^3.1.5 + checksum: dcf4054f9e92ca0efe61c2b3f612914fb259a47900aa908a63106513a6d006c899b426ada53eb88d9dbbf089b5724c8e90b96a2c4ca6171845fa14203d734e30 + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -36641,17 +35434,14 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.2": - version: 6.1.11 - resolution: "tar@npm:6.1.11" +"tar-stream@npm:^3.1.5": + version: 3.1.6 + resolution: "tar-stream@npm:3.1.6" dependencies: - chownr: ^2.0.0 - fs-minipass: ^2.0.0 - minipass: ^3.0.0 - minizlib: ^2.1.1 - mkdirp: ^1.0.3 - yallist: ^4.0.0 - checksum: a04c07bb9e2d8f46776517d4618f2406fb977a74d914ad98b264fc3db0fe8224da5bec11e5f8902c5b9bcb8ace22d95fbe3c7b36b8593b7dfc8391a25898f32f + b4a: ^1.6.4 + fast-fifo: ^1.2.0 + streamx: ^2.15.0 + checksum: f3627f918581976e954ff03cb8d370551053796b82564f8c7ca8fac84c48e4d042026d0854fc222171a34ff9c682b72fae91be9c9b0a112d4c54f9e4f443e9c5 languageName: node linkType: hard @@ -36669,6 +35459,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^6.2.0": + version: 6.2.0 + resolution: "tar@npm:6.2.0" + dependencies: + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + minipass: ^5.0.0 + minizlib: ^2.1.1 + mkdirp: ^1.0.3 + yallist: ^4.0.0 + checksum: db4d9fe74a2082c3a5016630092c54c8375ff3b280186938cfd104f2e089c4fd9bad58688ef6be9cf186a889671bf355c7cda38f09bbf60604b281715ca57f5c + languageName: node + linkType: hard + "tarn@npm:^3.0.2": version: 3.0.2 resolution: "tarn@npm:3.0.2" @@ -36696,19 +35500,12 @@ __metadata: languageName: node linkType: hard -"telejson@npm:^6.0.8": - version: 6.0.8 - resolution: "telejson@npm:6.0.8" +"telejson@npm:^7.2.0": + version: 7.2.0 + resolution: "telejson@npm:7.2.0" dependencies: - "@types/is-function": ^1.0.0 - global: ^4.4.0 - is-function: ^1.0.2 - is-regex: ^1.1.2 - is-symbol: ^1.0.3 - isobject: ^4.0.0 - lodash: ^4.17.21 memoizerific: ^1.11.3 - checksum: 7411a5e78a35720bd0654a544409d3ce467b1dbb2073c73f36476b4c0905d97dbf539d6cbae737bb1fd8c872c2058f2a5450163a15117ed3fa031b2a2b8b33f6 + checksum: 55a3380c9ff3c5ad84581bb6bda28fc33c6b7c4a0c466894637da687639b8db0d21b0ff4c1bc1a7a92ae6b70662549d09e7b9e8b1ec334b2ef93078762ecdfb9 languageName: node linkType: hard @@ -36719,7 +35516,16 @@ __metadata: languageName: node linkType: hard -"tempy@npm:1.0.1": +"temp@npm:^0.8.4": + version: 0.8.4 + resolution: "temp@npm:0.8.4" + dependencies: + rimraf: ~2.6.2 + checksum: f35bed78565355dfdf95f730b7b489728bd6b7e35071bcc6497af7c827fb6c111fbe9063afc7b8cbc19522a072c278679f9a0ee81e684aa2c8617cc0f2e9c191 + languageName: node + linkType: hard + +"tempy@npm:1.0.1, tempy@npm:^1.0.1": version: 1.0.1 resolution: "tempy@npm:1.0.1" dependencies: @@ -36756,53 +35562,15 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^1.4.3": - version: 1.4.5 - resolution: "terser-webpack-plugin@npm:1.4.5" +"terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.7": + version: 5.3.9 + resolution: "terser-webpack-plugin@npm:5.3.9" dependencies: - cacache: ^12.0.2 - find-cache-dir: ^2.1.0 - is-wsl: ^1.1.0 - schema-utils: ^1.0.0 - serialize-javascript: ^4.0.0 - source-map: ^0.6.1 - terser: ^4.1.2 - webpack-sources: ^1.4.0 - worker-farm: ^1.7.0 - peerDependencies: - webpack: ^4.0.0 - checksum: 02aada80927d3c8105d69cb00384d307b73aed67d180db5d20023a8d649149f3803ad50f9cd2ef9eb2622005de87e677198ecc5088f51422bfac5d4d57472d0e - languageName: node - linkType: hard - -"terser-webpack-plugin@npm:^4.2.3": - version: 4.2.3 - resolution: "terser-webpack-plugin@npm:4.2.3" - dependencies: - cacache: ^15.0.5 - find-cache-dir: ^3.3.1 - jest-worker: ^26.5.0 - p-limit: ^3.0.2 - schema-utils: ^3.0.0 - serialize-javascript: ^5.0.1 - source-map: ^0.6.1 - terser: ^5.3.4 - webpack-sources: ^1.4.3 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: ec1b3a85e2645c57e359d5e4831f3e1d78eca2a0c94b156db70eb846ae35b5e6e98ad8784b12e153fc273e57445ce69d017075bbe9fc42868a258ef121f11537 - languageName: node - linkType: hard - -"terser-webpack-plugin@npm:^5.0.3": - version: 5.3.6 - resolution: "terser-webpack-plugin@npm:5.3.6" - dependencies: - "@jridgewell/trace-mapping": ^0.3.14 + "@jridgewell/trace-mapping": ^0.3.17 jest-worker: ^27.4.5 schema-utils: ^3.1.1 - serialize-javascript: ^6.0.0 - terser: ^5.14.1 + serialize-javascript: ^6.0.1 + terser: ^5.16.8 peerDependencies: webpack: ^5.1.0 peerDependenciesMeta: @@ -36812,46 +35580,11 @@ __metadata: optional: true uglify-js: optional: true - checksum: 8f3448d7fdb0434ce6a0c09d95c462bfd2f4a5a430233d854163337f734a7f5c07c74513d16081e06d4ca33d366d5b1a36f5444219bc41a7403afd6162107bad + checksum: 41705713d6f9cb83287936b21e27c658891c78c4392159f5148b5623f0e8c48559869779619b058382a4c9758e7820ea034695e57dc7c474b4962b79f553bc5f languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.1.3": - version: 5.3.3 - resolution: "terser-webpack-plugin@npm:5.3.3" - dependencies: - "@jridgewell/trace-mapping": ^0.3.7 - jest-worker: ^27.4.5 - schema-utils: ^3.1.1 - serialize-javascript: ^6.0.0 - terser: ^5.7.2 - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@swc/core": - optional: true - esbuild: - optional: true - uglify-js: - optional: true - checksum: 4b8d508d8a0f6e604addb286975f1fa670f8c3964a67abc03a7cfcfd4cdeca4b07dda6655e1c4425427fb62e4d2b0ca59d84f1b2cd83262ff73616d5d3ccdeb5 - languageName: node - linkType: hard - -"terser@npm:^4.1.2, terser@npm:^4.6.3": - version: 4.8.0 - resolution: "terser@npm:4.8.0" - dependencies: - commander: ^2.20.0 - source-map: ~0.6.1 - source-map-support: ~0.5.12 - bin: - terser: bin/terser - checksum: f980789097d4f856c1ef4b9a7ada37beb0bb022fb8aa3057968862b5864ad7c244253b3e269c9eb0ab7d0caf97b9521273f2d1cf1e0e942ff0016e0583859c71 - languageName: node - linkType: hard - -"terser@npm:^5.10.0, terser@npm:^5.14.1": +"terser@npm:^5.10.0": version: 5.15.1 resolution: "terser@npm:5.15.1" dependencies: @@ -36865,17 +35598,17 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.3.4, terser@npm:^5.7.2": - version: 5.14.1 - resolution: "terser@npm:5.14.1" +"terser@npm:^5.16.8": + version: 5.25.0 + resolution: "terser@npm:5.25.0" dependencies: - "@jridgewell/source-map": ^0.3.2 - acorn: ^8.5.0 + "@jridgewell/source-map": ^0.3.3 + acorn: ^8.8.2 commander: ^2.20.0 source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: 7b0e51f3d193a11cad82f07e3b0c1d62122eec786f809bdf2a54b865aaa1450872c3a7b6c33b5a40e264834060ffc1d4e197f971a76da5b0137997d756eb7548 + checksum: ddc5ba020060cea976105ea83f5832385297f5091198f10143c1224e35bbb4ad9dfc40ee95d51a8f1290d0a4c7910d66e0ecc4b596402e94ba829bfc58022151 languageName: node linkType: hard @@ -36922,7 +35655,7 @@ __metadata: languageName: node linkType: hard -"through2@npm:^2.0.0": +"through2@npm:^2.0.3": version: 2.0.5 resolution: "through2@npm:2.0.5" dependencies: @@ -36939,7 +35672,7 @@ __metadata: languageName: node linkType: hard -"timers-browserify@npm:^2.0.4": +"timers-browserify@npm:^2.0.12": version: 2.0.12 resolution: "timers-browserify@npm:2.0.12" dependencies: @@ -36986,6 +35719,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.3.1": + version: 1.3.1 + resolution: "tiny-invariant@npm:1.3.1" + checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c + languageName: node + linkType: hard + "tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.3": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3" @@ -37093,13 +35833,6 @@ __metadata: languageName: node linkType: hard -"to-arraybuffer@npm:^1.0.0": - version: 1.0.1 - resolution: "to-arraybuffer@npm:1.0.1" - checksum: 31433c10b388722729f5da04c6b2a06f40dc84f797bb802a5a171ced1e599454099c6c5bc5118f4b9105e7d049d3ad9d0f71182b77650e4fdb04539695489941 - languageName: node - linkType: hard - "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -37107,25 +35840,6 @@ __metadata: languageName: node linkType: hard -"to-object-path@npm:^0.3.0": - version: 0.3.0 - resolution: "to-object-path@npm:0.3.0" - dependencies: - kind-of: ^3.0.2 - checksum: 9425effee5b43e61d720940fa2b889623f77473d459c2ce3d4a580a4405df4403eec7be6b857455908070566352f9e2417304641ed158dda6f6a365fe3e66d70 - languageName: node - linkType: hard - -"to-regex-range@npm:^2.1.0": - version: 2.1.1 - resolution: "to-regex-range@npm:2.1.1" - dependencies: - is-number: ^3.0.0 - repeat-string: ^1.6.1 - checksum: 46093cc14be2da905cc931e442d280b2e544e2bfdb9a24b3cf821be8d342f804785e5736c108d5be026021a05d7b38144980a61917eee3c88de0a5e710e10320 - languageName: node - linkType: hard - "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -37135,15 +35849,10 @@ __metadata: languageName: node linkType: hard -"to-regex@npm:^3.0.1, to-regex@npm:^3.0.2": - version: 3.0.2 - resolution: "to-regex@npm:3.0.2" - dependencies: - define-property: ^2.0.2 - extend-shallow: ^3.0.2 - regex-not: ^1.0.2 - safe-regex: ^1.1.0 - checksum: 4ed4a619059b64e204aad84e4e5f3ea82d97410988bcece7cf6cbfdbf193d11bff48cf53842d88b8bb00b1bfc0d048f61f20f0709e6f393fd8fe0122662d9db4 +"tocbot@npm:^4.20.1": + version: 4.23.0 + resolution: "tocbot@npm:4.23.0" + checksum: 04289b9ae5f2b2c6e64342bc549fedf44c81a08070feec547fc073a08f3351f7e08f67f5913c992b664caec335509213787143c8739fbba9e14c486669b62968 languageName: node linkType: hard @@ -37245,10 +35954,10 @@ __metadata: languageName: node linkType: hard -"trim-newlines@npm:^1.0.0": - version: 1.0.0 - resolution: "trim-newlines@npm:1.0.0" - checksum: ed96eea318581c6f894c0a98d0c4f16dcce11a41794ce140a79db55f1cab709cd9117578ee5e49a9b52f41e9cd93eaf3efa6c4bddbc77afbf91128b396fadbc1 +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed languageName: node linkType: hard @@ -37259,27 +35968,6 @@ __metadata: languageName: node linkType: hard -"trim-trailing-lines@npm:^1.0.0": - version: 1.1.4 - resolution: "trim-trailing-lines@npm:1.1.4" - checksum: 5d39d21c0d4b258667012fcd784f73129e148ea1c213b1851d8904f80499fc91df6710c94c7dd49a486a32da2b9cb86020dda79f285a9a2586cfa622f80490c2 - languageName: node - linkType: hard - -"trim@npm:0.0.1": - version: 0.0.1 - resolution: "trim@npm:0.0.1" - checksum: 2b4646dff99a222e8e1526edd4e3a43bbd925af0b8e837c340455d250157e7deefaa4da49bb891ab841e5c27b1afc5e9e32d4b57afb875d2dfcabf4e319b8f7f - languageName: node - linkType: hard - -"trough@npm:^1.0.0": - version: 1.0.5 - resolution: "trough@npm:1.0.5" - checksum: d6c8564903ed00e5258bab92134b020724dbbe83148dc72e4bf6306c03ed8843efa1bcc773fa62410dd89161ecb067432dd5916501793508a9506cacbc408e25 - languageName: node - linkType: hard - "trough@npm:^2.0.0": version: 2.1.0 resolution: "trough@npm:2.1.0" @@ -37413,14 +36101,14 @@ __metadata: languageName: node linkType: hard -"tsconfig-paths-webpack-plugin@npm:^4.0.0": - version: 4.0.0 - resolution: "tsconfig-paths-webpack-plugin@npm:4.0.0" +"tsconfig-paths-webpack-plugin@npm:^4.0.1": + version: 4.1.0 + resolution: "tsconfig-paths-webpack-plugin@npm:4.1.0" dependencies: chalk: ^4.1.0 enhanced-resolve: ^5.7.0 - tsconfig-paths: ^4.0.0 - checksum: 7ff7d63c1153e6dd30ce660b006495ae5cb5cbb78b30bb59b3cb627567325d4af52c7a69dda8aec7b94d576f5385581a69307472992954c40fe6949564155397 + tsconfig-paths: ^4.1.2 + checksum: f6e9a8a407e1a405b0f2531184296d9f033cb4fe5837282b757ab4a2f4cd82a3117e62c4b86d56c7d47749c7f1345aa438ec6417dbf64a0ec74a292fe9eae44d languageName: node linkType: hard @@ -37458,6 +36146,17 @@ __metadata: languageName: node linkType: hard +"tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" + dependencies: + json5: ^2.2.2 + minimist: ^1.2.6 + strip-bom: ^3.0.0 + checksum: 28c5f7bbbcabc9dabd4117e8fdc61483f6872a1c6b02a4b1c4d68c5b79d06896c3cc9547610c4c3ba64658531caa2de13ead1ea1bf321c7b53e969c4752b98c7 + languageName: node + linkType: hard + "tsdav@npm:2.0.3": version: 2.0.3 resolution: "tsdav@npm:2.0.3" @@ -37484,27 +36183,27 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.4.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3": - version: 2.4.0 - resolution: "tslib@npm:2.4.0" - checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 - languageName: node - linkType: hard - -"tslib@npm:^1.0.0, tslib@npm:^1.10.0, tslib@npm:^1.11.1, tslib@npm:^1.8.1, tslib@npm:^1.9.3": +"tslib@npm:^1.0.0, tslib@npm:^1.10.0, tslib@npm:^1.11.1, tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd languageName: node linkType: hard -"tslib@npm:^2, tslib@npm:^2.4.1, tslib@npm:^2.6.1": +"tslib@npm:^2, tslib@npm:^2.4.1, tslib@npm:^2.6.1, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad languageName: node linkType: hard +"tslib@npm:^2.0.1, tslib@npm:^2.0.3": + version: 2.4.0 + resolution: "tslib@npm:2.4.0" + checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 + languageName: node + linkType: hard + "tslib@npm:^2.2.0": version: 2.4.1 resolution: "tslib@npm:2.4.1" @@ -37544,10 +36243,10 @@ __metadata: languageName: node linkType: hard -"tty-browserify@npm:0.0.0": - version: 0.0.0 - resolution: "tty-browserify@npm:0.0.0" - checksum: a06f746acc419cb2527ba19b6f3bd97b4a208c03823bfb37b2982629d2effe30ebd17eaed0d7e2fc741f3c4f2a0c43455bd5fb4194354b378e78cfb7ca687f59 +"tty-browserify@npm:^0.0.1": + version: 0.0.1 + resolution: "tty-browserify@npm:0.0.1" + checksum: 93b745d43fa5a7d2b948fa23be8d313576d1d884b48acd957c07710bac1c0d8ac34c0556ad4c57c73d36e11741763ef66b3fb4fb97b06b7e4d525315a3cd45f5 languageName: node linkType: hard @@ -37725,16 +36424,7 @@ __metadata: languageName: node linkType: hard -"type-check@npm:~0.3.2": - version: 0.3.2 - resolution: "type-check@npm:0.3.2" - dependencies: - prelude-ls: ~1.1.2 - checksum: dd3b1495642731bc0e1fc40abe5e977e0263005551ac83342ecb6f4f89551d106b368ec32ad3fb2da19b3bd7b2d1f64330da2ea9176d8ddbfe389fb286eb5124 - languageName: node - linkType: hard - -"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 @@ -37825,6 +36515,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^2.14.0, type-fest@npm:^2.19.0, type-fest@npm:~2.19": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: a4ef07ece297c9fba78fc1bd6d85dff4472fe043ede98bd4710d2615d15776902b595abf62bd78339ed6278f021235fb28a96361f8be86ed754f778973a0d278 + languageName: node + linkType: hard + "type-flag@npm:^2.1.0": version: 2.2.0 resolution: "type-flag@npm:2.2.0" @@ -37842,15 +36539,6 @@ __metadata: languageName: node linkType: hard -"typedarray-to-buffer@npm:^3.1.5": - version: 3.1.5 - resolution: "typedarray-to-buffer@npm:3.1.5" - dependencies: - is-typedarray: ^1.0.0 - checksum: 99c11aaa8f45189fcfba6b8a4825fd684a321caa9bd7a76a27cf0c7732c174d198b99f449c52c3818107430b5f41c0ccbbfb75cb2ee3ca4a9451710986d61a60 - languageName: node - linkType: hard - "typedarray-to-buffer@npm:~1.0.0": version: 1.0.4 resolution: "typedarray-to-buffer@npm:1.0.4" @@ -37975,9 +36663,9 @@ __metadata: linkType: hard "ua-parser-js@npm:^1.0.33, ua-parser-js@npm:^1.0.35": - version: 1.0.36 - resolution: "ua-parser-js@npm:1.0.36" - checksum: 5b2c8a5e3443dfbba7624421805de946457c26ae167cb2275781a2729d1518f7067c9d5c74c3b0acac4b9ff3278cae4eace08ca6eecb63848bc3b2f6a63cc975 + version: 1.0.37 + resolution: "ua-parser-js@npm:1.0.37" + checksum: 4d481c720d523366d7762dc8a46a1b58967d979aacf786f9ceceb1cd767de069f64a4bdffb63956294f1c0696eb465ddb950f28ba90571709e33521b4bd75e07 languageName: node linkType: hard @@ -38043,28 +36731,11 @@ __metadata: linkType: hard "undici@npm:^5.12.0": - version: 5.25.2 - resolution: "undici@npm:5.25.2" + version: 5.28.2 + resolution: "undici@npm:5.28.2" dependencies: - busboy: ^1.6.0 - checksum: 1177a9c4fc9a1ddb765508d0f69ae61c166559badce8d797aaa92beef70ec5ac8fdc420b643f5d8d40b9a37891ba5536e2070d86a9c54a128aec67ad0c862d06 - languageName: node - linkType: hard - -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 6a4b2557e1d921eaa80c4425ce27a404945ec26491ed06e62598f333996a91a44c7908cb26dc7c2746d735762b13276cf4aa41829b4c8f438dde63add3045d7a - languageName: node - linkType: hard - -"unherit@npm:^1.0.4": - version: 1.1.3 - resolution: "unherit@npm:1.1.3" - dependencies: - inherits: ^2.0.0 - xtend: ^4.0.0 - checksum: fd7922f84fc0bfb7c4df6d1f5a50b5b94a0218e3cda98a54dbbd209226ddd4072d742d3df44d0e295ab08d5ccfd304a1e193dfe31a86d2a91b7cb9fdac093194 + "@fastify/busboy": ^2.0.0 + checksum: f9e9335803f962fff07c3c11c6d50bbc76248bacf97035047155adb29c3622a65bd6bff23a22218189740133149d22e63b68131d8c40e78ac6cb4b6d686a6dfa languageName: node linkType: hard @@ -38109,20 +36780,6 @@ __metadata: languageName: node linkType: hard -"unified@npm:9.2.0": - version: 9.2.0 - resolution: "unified@npm:9.2.0" - dependencies: - bail: ^1.0.0 - extend: ^3.0.0 - is-buffer: ^2.0.0 - is-plain-obj: ^2.0.0 - trough: ^1.0.0 - vfile: ^4.0.0 - checksum: 0cac4ae119893fbd49d309b4db48595e4d4e9f0a2dc1dde4d0074059f9a46012a2905f37c1346715e583f30c970bc8078db8462675411d39ff5036ae18b4fb8a - languageName: node - linkType: hard - "unified@npm:^10.0.0": version: 10.1.2 resolution: "unified@npm:10.1.2" @@ -38138,24 +36795,18 @@ __metadata: languageName: node linkType: hard -"union-value@npm:^1.0.0": - version: 1.0.1 - resolution: "union-value@npm:1.0.1" +"unified@npm:^11.0.0": + version: 11.0.4 + resolution: "unified@npm:11.0.4" dependencies: - arr-union: ^3.1.0 - get-value: ^2.0.6 - is-extendable: ^0.1.1 - set-value: ^2.0.1 - checksum: a3464097d3f27f6aa90cf103ed9387541bccfc006517559381a10e0dffa62f465a9d9a09c9b9c3d26d0f4cbe61d4d010e2fbd710fd4bf1267a768ba8a774b0ba - languageName: node - linkType: hard - -"unique-filename@npm:^1.1.1": - version: 1.1.1 - resolution: "unique-filename@npm:1.1.1" - dependencies: - unique-slug: ^2.0.0 - checksum: cf4998c9228cc7647ba7814e255dec51be43673903897b1786eff2ac2d670f54d4d733357eb08dea969aa5e6875d0e1bd391d668fbdb5a179744e7c7551a6f80 + "@types/unist": ^3.0.0 + bail: ^2.0.0 + devlop: ^1.0.0 + extend: ^3.0.0 + is-plain-obj: ^4.0.0 + trough: ^2.0.0 + vfile: ^6.0.0 + checksum: cfb023913480ac2bd5e787ffb8c27782c43e6be4a55f8f1c288233fce46a7ebe7718ccc5adb80bf8d56b7ef85f5fc32239c7bfccda006f9f2382e0cc2e2a77e4 languageName: node linkType: hard @@ -38168,15 +36819,6 @@ __metadata: languageName: node linkType: hard -"unique-slug@npm:^2.0.0": - version: 2.0.2 - resolution: "unique-slug@npm:2.0.2" - dependencies: - imurmurhash: ^0.1.4 - checksum: 5b6876a645da08d505dedb970d1571f6cebdf87044cb6b740c8dbb24f0d6e1dc8bdbf46825fd09f994d7cf50760e6f6e063cfa197d51c5902c00a861702eb75a - languageName: node - linkType: hard - "unique-slug@npm:^3.0.0": version: 3.0.0 resolution: "unique-slug@npm:3.0.0" @@ -38195,13 +36837,6 @@ __metadata: languageName: node linkType: hard -"unist-builder@npm:2.0.3, unist-builder@npm:^2.0.0": - version: 2.0.3 - resolution: "unist-builder@npm:2.0.3" - checksum: e946fdf77dbfc320feaece137ce4959ae2da6614abd1623bd39512dc741a9d5f313eb2ba79f8887d941365dccddec7fef4e953827475e392bf49b45336f597f6 - languageName: node - linkType: hard - "unist-builder@npm:^3.0.0": version: 3.0.1 resolution: "unist-builder@npm:3.0.1" @@ -38211,13 +36846,6 @@ __metadata: languageName: node linkType: hard -"unist-util-generated@npm:^1.0.0": - version: 1.1.6 - resolution: "unist-util-generated@npm:1.1.6" - checksum: 86239ff88a08800d52198f2f0e15911f05bab2dad17cef95550f7c2728f15ebb0344694fcc3101d05762d88adaf86cb85aa7a3300fedabd0b6d7d00b41cdcb7f - languageName: node - linkType: hard - "unist-util-generated@npm:^2.0.0": version: 2.0.1 resolution: "unist-util-generated@npm:2.0.1" @@ -38241,10 +36869,12 @@ __metadata: languageName: node linkType: hard -"unist-util-position@npm:^3.0.0": - version: 3.1.0 - resolution: "unist-util-position@npm:3.1.0" - checksum: 10b3952e32a1ffabbecad41c3946237f7059f5bb6436796da05531a285f50b97e4f37cfc2f7164676d041063f40fe1ad92fbb8ca38d3ae8747328ebe738d738f +"unist-util-is@npm:^6.0.0": + version: 6.0.0 + resolution: "unist-util-is@npm:6.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f630a925126594af9993b091cf807b86811371e465b5049a6283e08537d3e6ba0f7e248e1e7dab52cfe33f9002606acef093441137181b327f6fe504884b20e2 languageName: node linkType: hard @@ -38257,30 +36887,22 @@ __metadata: languageName: node linkType: hard -"unist-util-remove-position@npm:^2.0.0": - version: 2.0.1 - resolution: "unist-util-remove-position@npm:2.0.1" +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" dependencies: - unist-util-visit: ^2.0.0 - checksum: 4149294969f1a78a367b5d03eb0a138aa8320a39e1b15686647a2bec5945af3df27f2936a1e9752ecbb4a82dc23bd86f7e5a0ee048e5eeaedc2deb9237872795 + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde languageName: node linkType: hard -"unist-util-remove@npm:^2.0.0": - version: 2.1.0 - resolution: "unist-util-remove@npm:2.1.0" +"unist-util-remove-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-remove-position@npm:5.0.0" dependencies: - unist-util-is: ^4.0.0 - checksum: 99e54f3ea0523f8cf957579a6e84e5b58427bffab929cc7f6aa5119581f929db683dd4691ea5483df0c272f486dda9dbd04f4ab74dca6cae1f3ebe8e4261a4d9 - languageName: node - linkType: hard - -"unist-util-stringify-position@npm:^2.0.0": - version: 2.0.3 - resolution: "unist-util-stringify-position@npm:2.0.3" - dependencies: - "@types/unist": ^2.0.2 - checksum: f755cadc959f9074fe999578a1a242761296705a7fe87f333a37c00044de74ab4b184b3812989a57d4cd12211f0b14ad397b327c3a594c7af84361b1c25a7f09 + "@types/unist": ^3.0.0 + unist-util-visit: ^5.0.0 + checksum: 8aabdb9d0e3e744141bc123d8f87b90835d521209ad3c6c4619d403b324537152f0b8f20dda839b40c3aa0abfbf1828b3635a7a8bb159c3ed469e743023510ee languageName: node linkType: hard @@ -38293,6 +36915,15 @@ __metadata: languageName: node linkType: hard +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 + languageName: node + linkType: hard + "unist-util-visit-parents@npm:^3.0.0": version: 3.1.1 resolution: "unist-util-visit-parents@npm:3.1.1" @@ -38313,7 +36944,17 @@ __metadata: languageName: node linkType: hard -"unist-util-visit@npm:2.0.3, unist-util-visit@npm:^2.0.0": +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-visit-parents@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: 08927647c579f63b91aafcbec9966dc4a7d0af1e5e26fc69f4e3e6a01215084835a2321b06f3cbe7bf7914a852830fc1439f0fc3d7153d8804ac3ef851ddfa20 + languageName: node + linkType: hard + +"unist-util-visit@npm:^2.0.0": version: 2.0.3 resolution: "unist-util-visit@npm:2.0.3" dependencies: @@ -38335,6 +36976,17 @@ __metadata: languageName: node linkType: hard +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 + languageName: node + linkType: hard + "universal-base64@npm:^2.1.0": version: 2.1.0 resolution: "universal-base64@npm:2.1.0" @@ -38379,29 +37031,22 @@ __metadata: languageName: node linkType: hard -"unset-value@npm:^1.0.0": - version: 1.0.0 - resolution: "unset-value@npm:1.0.0" +"unplugin@npm:^1.3.1": + version: 1.5.1 + resolution: "unplugin@npm:1.5.1" dependencies: - has-value: ^0.3.1 - isobject: ^3.0.0 - checksum: 5990ecf660672be2781fc9fb322543c4aa592b68ed9a3312fa4df0e9ba709d42e823af090fc8f95775b4cd2c9a5169f7388f0cec39238b6d0d55a69fc2ab6b29 + acorn: ^8.11.2 + chokidar: ^3.5.3 + webpack-sources: ^3.2.3 + webpack-virtual-modules: ^0.6.0 + checksum: c93cb8526026986ac34d79d7ee8a7c4f8371272bccc5f7a3f64158eb9fa2e296eb65345bd97f60be8f075188fcb681843b1c039eb2002f9a4b400f74e2bbae19 languageName: node linkType: hard -"untildify@npm:^2.0.0": - version: 2.1.0 - resolution: "untildify@npm:2.1.0" - dependencies: - os-homedir: ^1.0.0 - checksum: 071b394053fc94747d9df8c7f7ca50af41355c1207c8a0bf9f35f52b0d9ad5142a1920b018bc2b6ff04340a4f9c599ad50c9b8f4ff2c689ae52b1463ebbda94e - languageName: node - linkType: hard - -"upath@npm:^1.1.1": - version: 1.2.0 - resolution: "upath@npm:1.2.0" - checksum: 4c05c094797cb733193a0784774dbea5b1889d502fc9f0572164177e185e4a59ba7099bf0b0adf945b232e2ac60363f9bf18aac9b2206fb99cbef971a8455445 +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: 39ced9c418a74f73f0a56e1ba4634b4d959422dff61f4c72a8e39f60b99380c1b45ed776fbaa0a4101b157e4310d873ad7d114e8534ca02609b4916bb4187fb9 languageName: node linkType: hard @@ -38419,6 +37064,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.0.13": + version: 1.0.13 + resolution: "update-browserslist-db@npm:1.0.13" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 1e47d80182ab6e4ad35396ad8b61008ae2a1330221175d0abd37689658bdb61af9b705bfc41057fd16682474d79944fb2d86767c5ed5ae34b6276b9bed353322 + languageName: node + linkType: hard + "update-browserslist-db@npm:^1.0.4": version: 1.0.4 resolution: "update-browserslist-db@npm:1.0.4" @@ -38497,30 +37156,6 @@ __metadata: languageName: node linkType: hard -"urix@npm:^0.1.0": - version: 0.1.0 - resolution: "urix@npm:0.1.0" - checksum: 4c076ecfbf3411e888547fe844e52378ab5ada2d2f27625139011eada79925e77f7fbf0e4016d45e6a9e9adb6b7e64981bd49b22700c7c401c5fc15f423303b3 - languageName: node - linkType: hard - -"url-loader@npm:^4.1.1": - version: 4.1.1 - resolution: "url-loader@npm:4.1.1" - dependencies: - loader-utils: ^2.0.0 - mime-types: ^2.1.27 - schema-utils: ^3.0.0 - peerDependencies: - file-loader: "*" - webpack: ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - file-loader: - optional: true - checksum: c1122a992c6cff70a7e56dfc2b7474534d48eb40b2cc75467cde0c6972e7597faf8e43acb4f45f93c2473645dfd803bcbc20960b57544dd1e4c96e77f72ba6fd - languageName: node - linkType: hard - "url-parse@npm:^1.4.3, url-parse@npm:^1.5.3, url-parse@npm:^1.5.8, url-parse@npm:^1.5.9": version: 1.5.10 resolution: "url-parse@npm:1.5.10" @@ -38614,6 +37249,18 @@ __metadata: languageName: node linkType: hard +"use-resize-observer@npm:^9.1.0": + version: 9.1.0 + resolution: "use-resize-observer@npm:9.1.0" + dependencies: + "@juggle/resize-observer": ^3.3.1 + peerDependencies: + react: 16.8.0 - 18 + react-dom: 16.8.0 - 18 + checksum: 92be0ac34a3b3cf884cd55847c90792b5b44833dc258e96d650152815ad246afe45825aa223332203004d836535a927ab74f18dc0313229e2c7c69510eddf382 + languageName: node + linkType: hard + "use-sidecar@npm:^1.0.1": version: 1.0.5 resolution: "use-sidecar@npm:1.0.5" @@ -38651,13 +37298,6 @@ __metadata: languageName: node linkType: hard -"use@npm:^3.1.0": - version: 3.1.1 - resolution: "use@npm:3.1.1" - checksum: 08a130289f5238fcbf8f59a18951286a6e660d17acccc9d58d9b69dfa0ee19aa038e8f95721b00b432c36d1629a9e32a464bf2e7e0ae6a244c42ddb30bdd8b33 - languageName: node - linkType: hard - "utif@npm:^2.0.1": version: 2.0.1 resolution: "utif@npm:2.0.1" @@ -38674,31 +37314,16 @@ __metadata: languageName: node linkType: hard -"util.promisify@npm:1.0.0": - version: 1.0.0 - resolution: "util.promisify@npm:1.0.0" +"util@npm:^0.12.4, util@npm:^0.12.5": + version: 0.12.5 + resolution: "util@npm:0.12.5" dependencies: - define-properties: ^1.1.2 - object.getownpropertydescriptors: ^2.0.3 - checksum: 482e857d676adee506c5c3a10212fd6a06a51d827a9b6d5396a8e593db53b4bb7064f77c5071357d8cd76072542de5cc1c08bc6d7c10cf43fa22dc3bc67556f1 - languageName: node - linkType: hard - -"util@npm:0.10.3": - version: 0.10.3 - resolution: "util@npm:0.10.3" - dependencies: - inherits: 2.0.1 - checksum: bd800f5d237a82caddb61723a6cbe45297d25dd258651a31335a4d5d981fd033cb4771f82db3d5d59b582b187cb69cfe727dc6f4d8d7826f686ee6c07ce611e0 - languageName: node - linkType: hard - -"util@npm:^0.11.0": - version: 0.11.1 - resolution: "util@npm:0.11.1" - dependencies: - inherits: 2.0.3 - checksum: 80bee6a2edf5ab08dcb97bfe55ca62289b4e66f762ada201f2c5104cb5e46474c8b334f6504d055c0e6a8fda10999add9bcbd81ba765e7f37b17dc767331aa55 + inherits: ^2.0.3 + is-arguments: ^1.0.4 + is-generator-function: ^1.0.7 + is-typed-array: ^1.1.3 + which-typed-array: ^1.1.2 + checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a languageName: node linkType: hard @@ -38716,13 +37341,6 @@ __metadata: languageName: node linkType: hard -"uuid-browser@npm:^3.1.0": - version: 3.1.0 - resolution: "uuid-browser@npm:3.1.0" - checksum: 951ec47593865c7cc746df671f7b0f0ff48fcab583fcdaeab6c517a5222af0f5e144a6fcea5fa9620a5b3be047e2f9412a80267ea5c45050e07d51774197d49e - languageName: node - linkType: hard - "uuid@npm:9.0.0, uuid@npm:^9.0.0": version: 9.0.0 resolution: "uuid@npm:9.0.0" @@ -38771,13 +37389,6 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache@npm:^2.0.3": - version: 2.3.0 - resolution: "v8-compile-cache@npm:2.3.0" - checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e - languageName: node - linkType: hard - "v8-to-istanbul@npm:^9.0.0": version: 9.0.1 resolution: "v8-to-istanbul@npm:9.0.1" @@ -38831,13 +37442,6 @@ __metadata: languageName: node linkType: hard -"vfile-location@npm:^3.0.0, vfile-location@npm:^3.2.0": - version: 3.2.0 - resolution: "vfile-location@npm:3.2.0" - checksum: 9bb3df6d0be31b5dd2d8da0170c27b7045c64493a8ba7b6ff7af8596c524fc8896924b8dd85ae12d201eead2709217a0fbc44927b7264f4bbf0aa8027a78be9c - languageName: node - linkType: hard - "vfile-location@npm:^4.0.0": version: 4.1.0 resolution: "vfile-location@npm:4.1.0" @@ -38848,16 +37452,6 @@ __metadata: languageName: node linkType: hard -"vfile-message@npm:^2.0.0": - version: 2.0.4 - resolution: "vfile-message@npm:2.0.4" - dependencies: - "@types/unist": ^2.0.0 - unist-util-stringify-position: ^2.0.0 - checksum: 1bade499790f46ca5aba04bdce07a1e37c2636a8872e05cf32c26becc912826710b7eb063d30c5754fdfaeedc8a7658e78df10b3bc535c844890ec8a184f5643 - languageName: node - linkType: hard - "vfile-message@npm:^3.0.0": version: 3.1.4 resolution: "vfile-message@npm:3.1.4" @@ -38868,15 +37462,13 @@ __metadata: languageName: node linkType: hard -"vfile@npm:^4.0.0": - version: 4.2.1 - resolution: "vfile@npm:4.2.1" +"vfile-message@npm:^4.0.0": + version: 4.0.2 + resolution: "vfile-message@npm:4.0.2" dependencies: - "@types/unist": ^2.0.0 - is-buffer: ^2.0.0 - unist-util-stringify-position: ^2.0.0 - vfile-message: ^2.0.0 - checksum: ee5726e10d170472cde778fc22e0f7499caa096eb85babea5d0ce0941455b721037ee1c9e6ae506ca2803250acd313d0f464328ead0b55cfe7cb6315f1b462d6 + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 964e7e119f4c0e0270fc269119c41c96da20afa01acb7c9809a88365c8e0c64aa692fafbd952669382b978002ecd7ad31ef4446d85e8a22cdb62f6df20186c2d languageName: node linkType: hard @@ -38892,6 +37484,17 @@ __metadata: languageName: node linkType: hard +"vfile@npm:^6.0.0": + version: 6.0.1 + resolution: "vfile@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 05ccee73aeb00402bc8a5d0708af299e9f4a33f5132805449099295085e3ca3b0d018328bad9ff44cf2e6f4cd364f1d558d3fb9b394243a25b2739207edcb0ed + languageName: node + linkType: hard + "victory-vendor@npm:^36.6.8": version: 36.6.8 resolution: "victory-vendor@npm:36.6.8" @@ -38914,33 +37517,34 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:0.34.3": - version: 0.34.3 - resolution: "vite-node@npm:0.34.3" +"vite-node@npm:0.34.6": + version: 0.34.6 + resolution: "vite-node@npm:0.34.6" dependencies: cac: ^6.7.14 debug: ^4.3.4 mlly: ^1.4.0 pathe: ^1.1.1 picocolors: ^1.0.0 - vite: ^3.0.0 || ^4.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0-0 bin: vite-node: vite-node.mjs - checksum: 366c4f3fb7c038e2180abc6b18cfbac3b8684cd878eaf7ebf1ffb07d95d2ea325713fc575a7949a13bb00cfe264acbc28c02e2836b8647e1f443fe631c17805a + checksum: 46eba82bf8b69c7dfeed901502533b172cc6303212f0f49f82c2f64758fa4b60acd1b1e37cb96aff944e36b510b0d1beedb50d9cb25ef39e0159b2b9d1136b1f languageName: node linkType: hard -"vite@npm:^3.0.0 || ^4.0.0": - version: 4.3.8 - resolution: "vite@npm:4.3.8" +"vite@npm:^3.0.0 || ^4.0.0 || ^5.0.0-0, vite@npm:^3.1.0 || ^4.0.0 || ^5.0.0-0": + version: 5.0.6 + resolution: "vite@npm:5.0.6" dependencies: - esbuild: ^0.17.5 - fsevents: ~2.3.2 - postcss: ^8.4.23 - rollup: ^3.21.0 + esbuild: ^0.19.3 + fsevents: ~2.3.3 + postcss: ^8.4.32 + rollup: ^4.2.0 peerDependencies: - "@types/node": ">= 14" + "@types/node": ^18.0.0 || >=20.0.0 less: "*" + lightningcss: ^1.21.0 sass: "*" stylus: "*" sugarss: "*" @@ -38953,6 +37557,8 @@ __metadata: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true stylus: @@ -38963,7 +37569,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 454a7c0c1bd1fd5611c9df28c62e3adbe75f48e87fc787179c5af60c4ab9a87aa0eda44be446d898851a135766d36f65f8e7d56317556aa807d30e561de369c4 + checksum: 06d85f7d838d0cb2063c82c67974044f6b11e2914ac2cb395185509ecea85dcd5a9d3bd380b13f3c3494665b08b0793d1a03008372a10575f7619db003dbfc7e languageName: node linkType: hard @@ -39027,22 +37633,22 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^0.34.3": - version: 0.34.3 - resolution: "vitest@npm:0.34.3" +"vitest@npm:^0.34.6": + version: 0.34.6 + resolution: "vitest@npm:0.34.6" dependencies: "@types/chai": ^4.3.5 "@types/chai-subset": ^1.3.3 "@types/node": "*" - "@vitest/expect": 0.34.3 - "@vitest/runner": 0.34.3 - "@vitest/snapshot": 0.34.3 - "@vitest/spy": 0.34.3 - "@vitest/utils": 0.34.3 + "@vitest/expect": 0.34.6 + "@vitest/runner": 0.34.6 + "@vitest/snapshot": 0.34.6 + "@vitest/spy": 0.34.6 + "@vitest/utils": 0.34.6 acorn: ^8.9.0 acorn-walk: ^8.2.0 cac: ^6.7.14 - chai: ^4.3.7 + chai: ^4.3.10 debug: ^4.3.4 local-pkg: ^0.4.3 magic-string: ^0.30.1 @@ -39052,8 +37658,8 @@ __metadata: strip-literal: ^1.0.1 tinybench: ^2.5.0 tinypool: ^0.7.0 - vite: ^3.0.0 || ^4.0.0 - vite-node: 0.34.3 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 + vite-node: 0.34.6 why-is-node-running: ^2.2.2 peerDependencies: "@edge-runtime/vm": "*" @@ -39083,11 +37689,11 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 4535d080feede94db5015eb60c6ed5f7b0d8cd67f12072de5ae1faded133cc640043c0c2646ef51ab9b61c2f885589da57458a65e82cf91a25cf954470018a40 + checksum: 45f5c1987fa8c76dbaf5db379bbdb4f6e3713c484e850149af38247b627e70016c1863286fd7fcfab08a1d98430f66ba1f45af6f14f5c467ded4b1ea6f26afa3 languageName: node linkType: hard -"vm-browserify@npm:^1.0.1": +"vm-browserify@npm:^1.1.2": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" checksum: 10a1c50aab54ff8b4c9042c15fc64aefccce8d2fb90c0640403242db0ee7fb269f9b102bdb69cfb435d7ef3180d61fd4fb004a043a12709abaf9056cfd7e039d @@ -39111,21 +37717,21 @@ __metadata: linkType: hard "wait-on@npm:^7.0.1": - version: 7.0.1 - resolution: "wait-on@npm:7.0.1" + version: 7.2.0 + resolution: "wait-on@npm:7.2.0" dependencies: - axios: ^0.27.2 - joi: ^17.7.0 + axios: ^1.6.1 + joi: ^17.11.0 lodash: ^4.17.21 - minimist: ^1.2.7 - rxjs: ^7.8.0 + minimist: ^1.2.8 + rxjs: ^7.8.1 bin: wait-on: bin/wait-on - checksum: 1e8a17d8ee6436f71d3ab82781ce31267481fcd7bbccde49b0f8124871e6e40a1acac3401f04f775ba6203853a5813352fa131620fc139914351f3b2894d573f + checksum: 69ec1432bb4479363fdd71f2f3f501a98aa356a562781108a4a89ef8fdf1e3d5fd0c2fd56c4cc5902abbb662065f1f22d4e436a1e6fc9331ce8b575eb023325e languageName: node linkType: hard -"walker@npm:^1.0.7, walker@npm:~1.0.5": +"walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" dependencies: @@ -39143,16 +37749,7 @@ __metadata: languageName: node linkType: hard -"watchpack-chokidar2@npm:^2.0.1": - version: 2.0.1 - resolution: "watchpack-chokidar2@npm:2.0.1" - dependencies: - chokidar: ^2.1.8 - checksum: acf0f9ebca0c0b2fd1fe87ba557670477a6c0410bf1a653a726e68eb0620aa94fd9a43027a160a76bc793a21ea12e215e1e87dafe762682c13ef92ad4daf7b58 - languageName: node - linkType: hard - -"watchpack@npm:2.4.0, watchpack@npm:^2.2.0, watchpack@npm:^2.3.1, watchpack@npm:^2.4.0": +"watchpack@npm:2.4.0, watchpack@npm:^2.2.0, watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" dependencies: @@ -39162,23 +37759,6 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^1.7.4": - version: 1.7.5 - resolution: "watchpack@npm:1.7.5" - dependencies: - chokidar: ^3.4.1 - graceful-fs: ^4.1.2 - neo-async: ^2.5.0 - watchpack-chokidar2: ^2.0.1 - dependenciesMeta: - chokidar: - optional: true - watchpack-chokidar2: - optional: true - checksum: 8b7cb8c8df8f4dd0e8ac47693c0141c4f020a4b031411247d600eca31522fde6f1f9a3a6f6518b46e71f7971b0ed5734c08c60d7fdd2530e7262776286f69236 - languageName: node - linkType: hard - "wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -39195,13 +37775,6 @@ __metadata: languageName: node linkType: hard -"web-namespaces@npm:^1.0.0": - version: 1.1.4 - resolution: "web-namespaces@npm:1.1.4" - checksum: 5149842ccbfbc56fe4f8758957b3f8c8616a281874a5bb84aa1b305e4436a9bad853d21c629a7b8f174902449e1489c7a6c724fccf60965077c5636bd8aed42b - languageName: node - linkType: hard - "web-namespaces@npm:^2.0.0": version: 2.0.1 resolution: "web-namespaces@npm:2.0.1" @@ -39276,43 +37849,21 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:^3.7.3": - version: 3.7.3 - resolution: "webpack-dev-middleware@npm:3.7.3" +"webpack-dev-middleware@npm:^6.1.1": + version: 6.1.1 + resolution: "webpack-dev-middleware@npm:6.1.1" dependencies: - memory-fs: ^0.4.1 - mime: ^2.4.4 - mkdirp: ^0.5.1 + colorette: ^2.0.10 + memfs: ^3.4.12 + mime-types: ^2.1.31 range-parser: ^1.2.1 - webpack-log: ^2.0.0 + schema-utils: ^4.0.0 peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: faa3cdd7b82d23c35b8f45903556eadd92b0795c76f3e08e234d53f7bab3de13331096a71968e7e9905770ae5de7a4f75ddf09f66d1e0bbabfecbb30db0f71e3 - languageName: node - linkType: hard - -"webpack-dev-middleware@npm:^4.1.0": - version: 4.3.0 - resolution: "webpack-dev-middleware@npm:4.3.0" - dependencies: - colorette: ^1.2.2 - mem: ^8.1.1 - memfs: ^3.2.2 - mime-types: ^2.1.30 - range-parser: ^1.2.1 - schema-utils: ^3.0.0 - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - checksum: 113389f9aa488312758b329f9fdd34ff646a50822c197d0e1dc7ce171b1d826a607c92702a60439fead24e495d5b2c9959d90948fc272f7472a301d37cec1e8d - languageName: node - linkType: hard - -"webpack-filter-warnings-plugin@npm:^1.2.1": - version: 1.2.1 - resolution: "webpack-filter-warnings-plugin@npm:1.2.1" - peerDependencies: - webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 - checksum: 91d853596ddb81b6c4673e03f55ab18f7f652ef7a278533623910d53b59df1c661b7f2cb2ef859eabc5fd615daa5be3f9f4c00a59ab33192b93f1be7c8908ace + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 3bced6ef644b20f2e76df9db1c5209f4a73761d7d307fe29ae20bc00397d4f9727af2607f98794c6c7c57d5c1a48bfa12690168b08f5d46820b07aab2c63640c languageName: node linkType: hard @@ -39328,26 +37879,6 @@ __metadata: languageName: node linkType: hard -"webpack-log@npm:^2.0.0": - version: 2.0.0 - resolution: "webpack-log@npm:2.0.0" - dependencies: - ansi-colors: ^3.0.0 - uuid: ^3.3.2 - checksum: 4757179310995e20633ec2d77a8c1ac11e4135c84745f57148692f8195f1c0f8ec122c77d0dc16fc484b7d301df6674f36c9fc6b1ff06b5cf142abaaf5d24f4f - languageName: node - linkType: hard - -"webpack-sources@npm:^1.4.0, webpack-sources@npm:^1.4.1, webpack-sources@npm:^1.4.3": - version: 1.4.3 - resolution: "webpack-sources@npm:1.4.3" - dependencies: - source-list-map: ^2.0.0 - source-map: ~0.6.1 - checksum: 37463dad8d08114930f4bc4882a9602941f07c9f0efa9b6bc78738cd936275b990a596d801ef450d022bb005b109b9f451dd087db2f3c9baf53e8e22cf388f79 - languageName: node - linkType: hard - "webpack-sources@npm:^2.0.0 || ^3.0.0, webpack-sources@npm:^3.2.3": version: 3.2.3 resolution: "webpack-sources@npm:3.2.3" @@ -39355,112 +37886,35 @@ __metadata: languageName: node linkType: hard -"webpack-virtual-modules@npm:^0.2.2": - version: 0.2.2 - resolution: "webpack-virtual-modules@npm:0.2.2" - dependencies: - debug: ^3.0.0 - checksum: 38706eb5ffd7a5120a731c2d35d4de5714cb16dcc87076276d7b130e3221d2665f5c30696bfde5edfddc6b7ae40d772096a0019202260a9d4e19df43b7cf9c95 +"webpack-virtual-modules@npm:^0.5.0": + version: 0.5.0 + resolution: "webpack-virtual-modules@npm:0.5.0" + checksum: 22b59257b55c89d11ae295b588b683ee9fdf3aeb591bc7b6f88ac1d69cb63f4fcb507666ea986866dfae161a1fa534ad6fb4e2ea91bbcd0a6d454368d7d4c64b languageName: node linkType: hard -"webpack-virtual-modules@npm:^0.4.1": - version: 0.4.6 - resolution: "webpack-virtual-modules@npm:0.4.6" - checksum: cb056ba8c50b35436ae43149554b051b80065b0cf79f2d528ca692ddf344a422ac71c415adb9da83dc3acc6e7e58f518388cc1cd11cb4fa29dc04f2c4494afe3 +"webpack-virtual-modules@npm:^0.6.0": + version: 0.6.1 + resolution: "webpack-virtual-modules@npm:0.6.1" + checksum: 0cd993d7b00af0ed89eee96ed6dcb2307fa8dc38e37f34e78690088314976aa79a31cf146553c5e414cdc87222878c5e4979abeb0b00bf6dc9c6f018604a1310 languageName: node linkType: hard -"webpack@npm:4": - version: 4.46.0 - resolution: "webpack@npm:4.46.0" - dependencies: - "@webassemblyjs/ast": 1.9.0 - "@webassemblyjs/helper-module-context": 1.9.0 - "@webassemblyjs/wasm-edit": 1.9.0 - "@webassemblyjs/wasm-parser": 1.9.0 - acorn: ^6.4.1 - ajv: ^6.10.2 - ajv-keywords: ^3.4.1 - chrome-trace-event: ^1.0.2 - enhanced-resolve: ^4.5.0 - eslint-scope: ^4.0.3 - json-parse-better-errors: ^1.0.2 - loader-runner: ^2.4.0 - loader-utils: ^1.2.3 - memory-fs: ^0.4.1 - micromatch: ^3.1.10 - mkdirp: ^0.5.3 - neo-async: ^2.6.1 - node-libs-browser: ^2.2.1 - schema-utils: ^1.0.0 - tapable: ^1.1.3 - terser-webpack-plugin: ^1.4.3 - watchpack: ^1.7.4 - webpack-sources: ^1.4.1 - peerDependenciesMeta: - webpack-cli: - optional: true - webpack-command: - optional: true - bin: - webpack: bin/webpack.js - checksum: 013fa24c00d4261e16ebca60353fa6f848e417b5a44bdf28c16ebebd67fa61e960420bb314c8df05cfe2dad9b90efabcf38fd6875f2361922769a0384085ef1e - languageName: node - linkType: hard - -"webpack@npm:>=4.43.0 <6.0.0": - version: 5.73.0 - resolution: "webpack@npm:5.73.0" +"webpack@npm:5": + version: 5.89.0 + resolution: "webpack@npm:5.89.0" dependencies: "@types/eslint-scope": ^3.7.3 - "@types/estree": ^0.0.51 - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/wasm-edit": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - acorn: ^8.4.1 - acorn-import-assertions: ^1.7.6 - browserslist: ^4.14.5 - chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.9.3 - es-module-lexer: ^0.9.0 - eslint-scope: 5.1.1 - events: ^3.2.0 - glob-to-regexp: ^0.4.1 - graceful-fs: ^4.2.9 - json-parse-even-better-errors: ^2.3.1 - loader-runner: ^4.2.0 - mime-types: ^2.1.27 - neo-async: ^2.6.2 - schema-utils: ^3.1.0 - tapable: ^2.1.1 - terser-webpack-plugin: ^5.1.3 - watchpack: ^2.3.1 - webpack-sources: ^3.2.3 - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: aa434a241bad6176b68e1bf0feb1972da4dcbf27cb3d94ae24f6eb31acc37dceb9c4aae55e068edca75817bfe91f13cd20b023ac55d9b1b2f8b66a4037c9468f - languageName: node - linkType: hard - -"webpack@npm:^5.9.0": - version: 5.74.0 - resolution: "webpack@npm:5.74.0" - dependencies: - "@types/eslint-scope": ^3.7.3 - "@types/estree": ^0.0.51 - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/wasm-edit": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 + "@types/estree": ^1.0.0 + "@webassemblyjs/ast": ^1.11.5 + "@webassemblyjs/wasm-edit": ^1.11.5 + "@webassemblyjs/wasm-parser": ^1.11.5 acorn: ^8.7.1 - acorn-import-assertions: ^1.7.6 + acorn-import-assertions: ^1.9.0 browserslist: ^4.14.5 chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.10.0 - es-module-lexer: ^0.9.0 + enhanced-resolve: ^5.15.0 + es-module-lexer: ^1.2.1 eslint-scope: 5.1.1 events: ^3.2.0 glob-to-regexp: ^0.4.1 @@ -39469,9 +37923,9 @@ __metadata: loader-runner: ^4.2.0 mime-types: ^2.1.27 neo-async: ^2.6.2 - schema-utils: ^3.1.0 + schema-utils: ^3.2.0 tapable: ^2.1.1 - terser-webpack-plugin: ^5.1.3 + terser-webpack-plugin: ^5.3.7 watchpack: ^2.4.0 webpack-sources: ^3.2.3 peerDependenciesMeta: @@ -39479,7 +37933,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 320c41369a75051b19e18c63f408b3dcc481852e992f83d311771c5ec0f05f2946385e8ebef62030cf3587f0a3d2f12779ffdb191569a966847289ba7313f946 + checksum: 43fe0dbc30e168a685ef5a86759d5016a705f6563b39a240aa00826a80637d4a3deeb8062e709d6a4b05c63e796278244c84b04174704dc4a37bedb0f565c5ed languageName: node linkType: hard @@ -39567,6 +38021,18 @@ __metadata: languageName: node linkType: hard +"which-collection@npm:^1.0.1": + version: 1.0.1 + resolution: "which-collection@npm:1.0.1" + dependencies: + is-map: ^2.0.1 + is-set: ^2.0.1 + is-weakmap: ^2.0.1 + is-weakset: ^2.0.1 + checksum: c815bbd163107ef9cb84f135e6f34453eaf4cca994e7ba85ddb0d27cea724c623fae2a473ceccfd5549c53cc65a5d82692de418166df3f858e1e5dc60818581c + languageName: node + linkType: hard + "which-module@npm:^2.0.0": version: 2.0.0 resolution: "which-module@npm:2.0.0" @@ -39584,6 +38050,19 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.2": + version: 1.1.13 + resolution: "which-typed-array@npm:1.1.13" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.4 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.0 + checksum: 3828a0d5d72c800e369d447e54c7620742a4cc0c9baf1b5e8c17e9b6ff90d8d861a3a6dd4800f1953dbf80e5e5cec954a289e5b4a223e3bee4aeb1f8c5f33309 + languageName: node + linkType: hard + "which@npm:^1.2.9": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -39618,7 +38097,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: @@ -39636,7 +38115,7 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": +"word-wrap@npm:^1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f @@ -39650,24 +38129,6 @@ __metadata: languageName: node linkType: hard -"worker-farm@npm:^1.7.0": - version: 1.7.0 - resolution: "worker-farm@npm:1.7.0" - dependencies: - errno: ~0.1.7 - checksum: eab917530e1feddf157ec749e9c91b73a886142daa7fdf3490bccbf7b548b2576c43ab8d0a98e72ac755cbc101ca8647a7b1ff2485fddb9e8f53c40c77f5a719 - languageName: node - linkType: hard - -"worker-rpc@npm:^0.1.0": - version: 0.1.1 - resolution: "worker-rpc@npm:0.1.1" - dependencies: - microevent.ts: ~0.1.1 - checksum: 8f8607506172f44c05490f3ccf13e5c1f430eeb9b6116a405919c186b8b17add13bbb22467a0dbcd18ec7fcb080709a15738182e0003c5fbe2144721ea00f357 - languageName: node - linkType: hard - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -39708,30 +38169,33 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^3.0.0": - version: 3.0.3 - resolution: "write-file-atomic@npm:3.0.3" +"write-file-atomic@npm:^2.3.0": + version: 2.4.3 + resolution: "write-file-atomic@npm:2.4.3" dependencies: + graceful-fs: ^4.1.11 imurmurhash: ^0.1.4 - is-typedarray: ^1.0.0 signal-exit: ^3.0.2 - typedarray-to-buffer: ^3.1.5 - checksum: c55b24617cc61c3a4379f425fc62a386cc51916a9b9d993f39734d005a09d5a4bb748bc251f1304e7abd71d0a26d339996c275955f527a131b1dcded67878280 + checksum: 2db81f92ae974fd87ab4a5e7932feacaca626679a7c98fcc73ad8fcea5a1950eab32fa831f79e9391ac99b562ca091ad49be37a79045bd65f595efbb8f4596ae languageName: node linkType: hard -"ws@npm:8.14.1": - version: 8.14.1 - resolution: "ws@npm:8.14.1" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 9e310be2b0ff69e1f87d8c6d093ecd17a1ed4c37f281d17c35e8c30e2bd116401775b3d503249651374e6e0e1e9905db62fff096b694371c77561aee06bc3466 +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^3.0.7 + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c + languageName: node + linkType: hard + +"ws@npm:^6.1.0": + version: 6.2.2 + resolution: "ws@npm:6.2.2" + dependencies: + async-limiter: ~1.0.0 + checksum: aec3154ec51477c094ac2cb5946a156e17561a581fa27005cbf22c53ac57f8d4e5f791dd4bbba6a488602cb28778c8ab7df06251d590507c3c550fd8ebeee949 languageName: node linkType: hard @@ -39765,9 +38229,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.12.0": - version: 8.14.2 - resolution: "ws@npm:8.14.2" +"ws@npm:^8.12.0, ws@npm:^8.15.0": + version: 8.15.1 + resolution: "ws@npm:8.15.1" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -39776,7 +38240,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 3ca0dad26e8cc6515ff392b622a1467430814c463b3368b0258e33696b1d4bed7510bc7030f7b72838b9fdeb8dbd8839cbf808367d6aae2e1d668ce741d4308b + checksum: 8c67365f6e6134278ad635d558bfce466d7ef7543a043baea333aaa430429f0af8a130c0c36e7dd78f918d68167a659ba9b5067330b77c4b279e91533395952b languageName: node linkType: hard @@ -39810,20 +38274,6 @@ __metadata: languageName: node linkType: hard -"x-default-browser@npm:^0.4.0": - version: 0.4.0 - resolution: "x-default-browser@npm:0.4.0" - dependencies: - default-browser-id: ^1.0.4 - dependenciesMeta: - default-browser-id: - optional: true - bin: - x-default-browser: bin/x-default-browser.js - checksum: 9649fe6b4b91de93d5a48a5042b55a6e15c87d2514bc4f2e12582f8b25c1a6810fafc6f4c454fb531540e431e32a0a26ac130e418c0ce5c6ca892fb01945ea9e - languageName: node - linkType: hard - "xhr@npm:^2.0.1": version: 2.6.0 resolution: "xhr@npm:2.6.0" @@ -39960,7 +38410,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.1": +"xtend@npm:^4.0.0, xtend@npm:^4.0.2, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a @@ -40064,9 +38514,9 @@ __metadata: linkType: hard "yaml@npm:^2.3.1": - version: 2.3.2 - resolution: "yaml@npm:2.3.2" - checksum: acd80cc24df12c808c6dec8a0176d404ef9e6f08ad8786f746ecc9d8974968c53c6e8a67fdfabcc5f99f3dc59b6bb0994b95646ff03d18e9b1dcd59eccc02146 + version: 2.3.4 + resolution: "yaml@npm:2.3.4" + checksum: e6d1dae1c6383bcc8ba11796eef3b8c02d5082911c6723efeeb5ba50fc8e881df18d645e64de68e421b577296000bea9c75d6d9097c2f6699da3ae0406c030d8 languageName: node linkType: hard @@ -40190,6 +38640,16 @@ __metadata: languageName: node linkType: hard +"yauzl@npm:^2.10.0": + version: 2.10.0 + resolution: "yauzl@npm:2.10.0" + dependencies: + buffer-crc32: ~0.2.3 + fd-slicer: ~1.1.0 + checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" @@ -40330,13 +38790,6 @@ __metadata: languageName: node linkType: hard -"zwitch@npm:^1.0.0": - version: 1.0.5 - resolution: "zwitch@npm:1.0.5" - checksum: 28a1bebacab3bc60150b6b0a2ba1db2ad033f068e81f05e4892ec0ea13ae63f5d140a1d692062ac0657840c8da076f35b94433b5f1c329d7803b247de80f064a - languageName: node - linkType: hard - "zwitch@npm:^2.0.0, zwitch@npm:^2.0.4": version: 2.0.4 resolution: "zwitch@npm:2.0.4"