diff --git a/.github/actions/cache-build-key/action.yml b/.github/actions/cache-build-key/action.yml new file mode 100644 index 00000000000000..847989e46a70b4 --- /dev/null +++ b/.github/actions/cache-build-key/action.yml @@ -0,0 +1,23 @@ +name: Generate cache-build key +description: "Generate the cache key for production build caching" +inputs: + branch_key: + required: true + description: "Branch key for cache scoping (e.g., github.head_ref for PRs)" +outputs: + key: + description: "The generated cache key" + value: ${{ steps.generate-key.outputs.key }} +runs: + using: "composite" + steps: + - name: Generate cache key + id: generate-key + shell: bash + env: + CACHE_NAME: prod-build + BRANCH_KEY: ${{ inputs.branch_key }} + LOCKFILE_HASH: ${{ hashFiles('yarn.lock') }} + SOURCE_HASH: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'apps/**/*.json', 'apps/**/*.css', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', 'packages/prisma/schema.prisma', 'packages/prisma/migrations/**/*.sql', '!**/node_modules/**', '!packages/prisma/generated/**', '!packages/prisma/client/**', '!packages/prisma/zod/**', '!packages/kysely/**') }} + run: | + echo "key=${CACHE_NAME}-${BRANCH_KEY}-${LOCKFILE_HASH}-${SOURCE_HASH}" >> $GITHUB_OUTPUT diff --git a/.github/actions/cache-build/action.yml b/.github/actions/cache-build/action.yml index d3445a489ac7bd..2197e923705a9e 100644 --- a/.github/actions/cache-build/action.yml +++ b/.github/actions/cache-build/action.yml @@ -1,31 +1,23 @@ name: Cache production build binaries description: "Cache or restore if necessary" -inputs: - node_version: - required: false - default: v18.x runs: using: "composite" steps: + - name: Generate cache key + id: cache-key + uses: ./.github/actions/cache-build-key + with: + branch_key: ${{ github.head_ref || github.ref_name }} - name: Cache production build uses: actions/cache@v4 id: cache-build - env: - cache-name: prod-build - # Use branch name for cache scoping (head_ref for PRs, ref_name for pushes) - branch-key: ${{ github.head_ref || github.ref_name }} - # Hash yarn.lock for dependency changes - lockfile-hash: ${{ hashFiles('yarn.lock') }} - # Hash source files that affect the web build, excluding generated directories - # Excludes packages/prisma/{generated,client,zod}/* and packages/kysely/* which are generated by prisma generate - source-hash: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'apps/**/*.json', 'apps/**/*.css', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', 'packages/prisma/schema.prisma', 'packages/prisma/migrations/**/*.sql', '!**/node_modules/**', '!packages/prisma/generated/**', '!packages/prisma/client/**', '!packages/prisma/zod/**', '!packages/kysely/**') }} with: path: | ${{ github.workspace }}/apps/web/.next ${{ github.workspace }}/apps/web/public/embed **/.turbo/** **/dist/** - key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.node_version }}-${{ env.branch-key }}-${{ env.lockfile-hash }}-${{ env.source-hash }} + key: ${{ steps.cache-key.outputs.key }} - run: | export NODE_OPTIONS="--max_old_space_size=8192" yarn build diff --git a/.github/actions/yarn-install/action.yml b/.github/actions/yarn-install/action.yml index c48a484f0f0a87..a930ce326d37ef 100644 --- a/.github/actions/yarn-install/action.yml +++ b/.github/actions/yarn-install/action.yml @@ -15,6 +15,10 @@ inputs: node_version: required: false default: v20.x + skip-install-if-cache-hit: + description: "Skip yarn install if node_modules cache is hit. Use this for jobs that only need to check that cache exists." + required: false + default: "false" runs: using: "composite" @@ -29,9 +33,56 @@ runs: run: | echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + # When skip-install-if-cache-hit is true, first check if all caches exist without downloading (lookup-only mode) + # This avoids downloading ~1.2GB of cache data when we just want to verify caches exist + - name: Check yarn cache (lookup-only) + if: ${{ inputs.skip-install-if-cache-hit == 'true' }} + uses: actions/cache/restore@v4 + id: yarn-download-cache-check + with: + path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }} + key: yarn-download-cache-${{ hashFiles('yarn.lock') }} + lookup-only: true + + - name: Check node_modules cache (lookup-only) + if: ${{ inputs.skip-install-if-cache-hit == 'true' }} + uses: actions/cache/restore@v4 + id: yarn-nm-cache-check + with: + path: | + **/node_modules/ + !**/.next/node_modules/ + key: ${{ runner.os }}-yarn-nm-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + lookup-only: true + + - name: Check yarn install state cache (lookup-only) + if: ${{ inputs.skip-install-if-cache-hit == 'true' }} + uses: actions/cache/restore@v4 + id: yarn-install-state-cache-check + with: + path: .yarn/ci-cache/ + key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + lookup-only: true + + # Compute whether all caches hit (only in skip mode) + - name: Check if all caches hit + if: ${{ inputs.skip-install-if-cache-hit == 'true' }} + id: all-caches-check + shell: bash + run: | + if [[ "${{ steps.yarn-download-cache-check.outputs.cache-hit }}" == "true" && \ + "${{ steps.yarn-nm-cache-check.outputs.cache-hit }}" == "true" && \ + "${{ steps.yarn-install-state-cache-check.outputs.cache-hit }}" == "true" ]]; then + echo "all-hit=true" >> $GITHUB_OUTPUT + else + echo "all-hit=false" >> $GITHUB_OUTPUT + fi + # Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325 # Yarn cache is also reusable between arch and os. + # Only restore if not in skip mode, or if skip mode but any cache check missed - name: Restore yarn cache + if: ${{ inputs.skip-install-if-cache-hit != 'true' || steps.all-caches-check.outputs.all-hit != 'true' }} uses: actions/cache@v4 id: yarn-download-cache with: @@ -40,6 +91,7 @@ runs: # Invalidated on yarn.lock changes - name: Restore node_modules + if: ${{ inputs.skip-install-if-cache-hit != 'true' || steps.all-caches-check.outputs.all-hit != 'true' }} id: yarn-nm-cache uses: actions/cache@v4 with: @@ -50,6 +102,7 @@ runs: # Invalidated on yarn.lock changes - name: Restore yarn install state + if: ${{ inputs.skip-install-if-cache-hit != 'true' || steps.all-caches-check.outputs.all-hit != 'true' }} id: yarn-install-state-cache uses: actions/cache@v4 with: @@ -57,6 +110,7 @@ runs: key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} - name: Install dependencies + if: ${{ inputs.skip-install-if-cache-hit != 'true' || steps.all-caches-check.outputs.all-hit != 'true' }} shell: bash run: | yarn install --inline-builds diff --git a/.github/actions/yarn-playwright-install/action.yml b/.github/actions/yarn-playwright-install/action.yml index 7d444283c1f210..ea3ae0469b8d0d 100644 --- a/.github/actions/yarn-playwright-install/action.yml +++ b/.github/actions/yarn-playwright-install/action.yml @@ -1,5 +1,10 @@ name: Install playwright binaries description: "Install playwright, cache and restore if necessary" +inputs: + skip-install-if-cache-hit: + description: "Skip playwright install if cache is hit. Use this for jobs that only need to check that cache exists." + required: false + default: "false" runs: using: "composite" steps: @@ -7,7 +12,23 @@ runs: shell: bash id: playwright-version run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV + + # When skip-install-if-cache-hit is true, first check if cache exists without downloading (lookup-only mode) + - name: Check playwright cache (lookup-only) + if: ${{ inputs.skip-install-if-cache-hit == 'true' }} + id: playwright-cache-check + uses: actions/cache/restore@v4 + with: + path: | + ~/Library/Caches/ms-playwright + ~/.cache/ms-playwright + ${{ github.workspace }}/node_modules/playwright + key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} + lookup-only: true + + # Only restore cache if not in skip mode, or if skip mode but cache check missed - name: Cache playwright binaries + if: ${{ inputs.skip-install-if-cache-hit != 'true' || steps.playwright-cache-check.outputs.cache-hit != 'true' }} id: playwright-cache uses: actions/cache@v4 with: @@ -16,7 +37,10 @@ runs: ~/.cache/ms-playwright ${{ github.workspace }}/node_modules/playwright key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} + + # In default mode: run if playwright-cache missed + # In skip mode: run if playwright-cache-check missed (but cache restore step was also skipped) - name: Yarn playwright install - if: steps.playwright-cache.outputs.cache-hit != 'true' + if: ${{ (inputs.skip-install-if-cache-hit != 'true' && steps.playwright-cache.outputs.cache-hit != 'true') || (inputs.skip-install-if-cache-hit == 'true' && steps.playwright-cache-check.outputs.cache-hit != 'true') }} shell: bash run: yarn playwright install --with-deps diff --git a/.github/workflows/delete-blacksmith-cache.yml b/.github/workflows/delete-blacksmith-cache.yml index aad432deb0b9ad..26addc991fecc6 100644 --- a/.github/workflows/delete-blacksmith-cache.yml +++ b/.github/workflows/delete-blacksmith-cache.yml @@ -1,4 +1,4 @@ -name: Manually Delete Blacksmith Cache +name: Delete Blacksmith Cache on: workflow_dispatch: inputs: @@ -6,12 +6,28 @@ on: description: "Blacksmith Cache Key to Delete" required: true type: string + pull_request: + types: [closed] + jobs: manually-delete-blacksmith-cache: + if: github.event_name == 'workflow_dispatch' runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - name: Checkout uses: actions/checkout@v4 - uses: useblacksmith/cache-delete@v1 with: - cache_key: ${{ inputs.cache_key }} + key: ${{ inputs.cache_key }} + + delete-cache-build-on-pr-close: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: blacksmith-2vcpu-ubuntu-2404 + env: + CACHE_NAME: prod-build + steps: + - name: Delete cache-build cache + uses: useblacksmith/cache-delete@v1 + with: + key: ${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.ref }} + prefix: "true" diff --git a/.github/workflows/yarn-install.yml b/.github/workflows/yarn-install.yml index 81eb75d63048e9..e9e4c6ca5935eb 100644 --- a/.github/workflows/yarn-install.yml +++ b/.github/workflows/yarn-install.yml @@ -15,4 +15,8 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/dangerous-git-checkout - uses: ./.github/actions/yarn-install + with: + skip-install-if-cache-hit: "true" - uses: ./.github/actions/yarn-playwright-install + with: + skip-install-if-cache-hit: "true" diff --git a/apps/api/v1/test/lib/bookings/_post.test.ts b/apps/api/v1/test/lib/bookings/_post.test.ts index 3e397eefc28019..de13aeb627b71e 100644 --- a/apps/api/v1/test/lib/bookings/_post.test.ts +++ b/apps/api/v1/test/lib/bookings/_post.test.ts @@ -245,7 +245,9 @@ describe("POST /api/bookings", () => { const { req, res } = createMocks({ method: "POST", - body: {}, + body: { + eventTypeId: 2, + }, }); await handler(req, res); diff --git a/apps/web/app/api/defaultResponderForAppDir.test.ts b/apps/web/app/api/defaultResponderForAppDir.test.ts index 1173fe7ccd7ae2..dc8e7c48c873c7 100644 --- a/apps/web/app/api/defaultResponderForAppDir.test.ts +++ b/apps/web/app/api/defaultResponderForAppDir.test.ts @@ -8,15 +8,18 @@ import { TRPCError } from "@trpc/server"; import { defaultResponderForAppDir } from "./defaultResponderForAppDir"; -vi.mock("next/server", () => ({ - NextRequest: class MockNextRequest {}, - NextResponse: { - json: vi.fn((body, init) => ({ - json: vi.fn().mockResolvedValue(body), - status: init?.status || 200, - })), - }, -})); +vi.mock("next/server", () => { + class MockNextRequest extends Request {} + return { + NextRequest: MockNextRequest, + NextResponse: { + json: vi.fn((body, init) => ({ + json: vi.fn().mockResolvedValue(body), + status: init?.status || 200, + })), + }, + }; +}); describe("defaultResponderForAppDir", () => { it("should return a JSON response when handler resolves with a result", async () => { @@ -44,7 +47,9 @@ describe("defaultResponderForAppDir", () => { }); it("should respond with status code 409 for NoAvailableUsersFound", async () => { - const f = vi.fn().mockRejectedValue(new Error(ErrorCode.NoAvailableUsersFound)); + const f = vi + .fn() + .mockRejectedValue(new Error(ErrorCode.NoAvailableUsersFound)); const req = { method: "GET", url: "/api/test" } as unknown as NextRequest; const params = Promise.resolve({}); @@ -60,7 +65,9 @@ describe("defaultResponderForAppDir", () => { }); it("should respond with a 429 status code for rate limit errors", async () => { - const f = vi.fn().mockRejectedValue(new TRPCError({ code: "TOO_MANY_REQUESTS" })); + const f = vi + .fn() + .mockRejectedValue(new TRPCError({ code: "TOO_MANY_REQUESTS" })); const req = { method: "POST", url: "/api/test" } as unknown as NextRequest; const params = Promise.resolve({}); diff --git a/apps/web/modules/shell/UpgradeTip.tsx b/apps/web/modules/shell/UpgradeTip.tsx index 87a34353f9753f..8545182bdc51e9 100644 --- a/apps/web/modules/shell/UpgradeTip.tsx +++ b/apps/web/modules/shell/UpgradeTip.tsx @@ -45,18 +45,18 @@ export function UpgradeTip({ return ( <>
- - + + {" "} + {title} -
-

{title}

-

{description}

+
+

{title}

+

{description}

{buttons}
diff --git a/apps/web/modules/users/components/UserTable/UserListTable.tsx b/apps/web/modules/users/components/UserTable/UserListTable.tsx index 08365e2247ac3e..c70b2c6e25ecf2 100644 --- a/apps/web/modules/users/components/UserTable/UserListTable.tsx +++ b/apps/web/modules/users/components/UserTable/UserListTable.tsx @@ -79,6 +79,7 @@ const initalColumnVisibility = { teams: true, createdAt: false, updatedAt: false, + completedOnboarding: false, twoFactorEnabled: false, actions: true, }; @@ -418,6 +419,22 @@ function UserListTableContent({ }, cell: ({ row }) =>
{row.original.updatedAt || ""}
, }, + { + id: "completedOnboarding", + accessorKey: "completedOnboarding", + header: t("completed_onboarding"), + enableSorting: false, + enableColumnFilter: false, + size: 80, + cell: ({ row }) => { + const { completedOnboarding } = row.original; + return ( + + {completedOnboarding ? t("yes") : t("no")} + + ); + }, + }, { id: "twoFactorEnabled", accessorKey: "twoFactorEnabled", diff --git a/apps/web/package.json b/apps/web/package.json index f35468f7bf9b91..5ff666a3317e9d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -151,6 +151,7 @@ }, "devDependencies": { "@babel/core": "7.26.10", + "@biomejs/biome": "2.3.10", "@calcom/config": "workspace:*", "@calcom/types": "workspace:*", "@microsoft/microsoft-graph-types-beta": "0.15.0-preview", diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index c0f598bd8475ca..f38e672c5020ca 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -1,12 +1,3 @@ -import { expect } from "@playwright/test"; -import { JSDOM } from "jsdom"; - -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { generateHashedLink } from "@calcom/lib/generateHashedLink"; -import { randomString } from "@calcom/lib/random"; -import { SchedulingType } from "@calcom/prisma/enums"; -import type { Schedule, TimeRange } from "@calcom/types/schedule"; - import { test, todo } from "./lib/fixtures"; import { bookFirstEvent, @@ -20,6 +11,13 @@ import { testName, cancelBookingFromBookingsList, } from "./lib/testUtils"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { generateHashedLink } from "@calcom/lib/generateHashedLink"; +import { randomString } from "@calcom/lib/random"; +import { SchedulingType } from "@calcom/prisma/enums"; +import type { Schedule, TimeRange } from "@calcom/types/schedule"; +import { expect } from "@playwright/test"; +import { JSDOM } from "jsdom"; const freeUserObj = { name: `Free-user-${randomString(3)}` }; test.describe.configure({ mode: "parallel" }); @@ -33,7 +31,9 @@ test("check SSR and OG - User Event Type", async ({ page, users }) => { name, }); const responsePromise = page.waitForResponse( - (response) => response.url().includes(`/${user.username}/30-min`) && response.status() === 200 + (response) => + response.url().includes(`/${user.username}/30-min`) && + response.status() === 200 ); await page.goto(`/${user.username}/30-min`); await page.content(); @@ -42,17 +42,27 @@ test("check SSR and OG - User Event Type", async ({ page, users }) => { const document = new JSDOM(ssrResponse).window.document; const titleText = document.querySelector("title")?.textContent; - const ogImage = document.querySelector('meta[property="og:image"]')?.getAttribute("content"); - const ogUrl = document.querySelector('meta[property="og:url"]')?.getAttribute("content"); - const canonicalLink = document.querySelector('link[rel="canonical"]')?.getAttribute("href"); + const ogImage = document + .querySelector('meta[property="og:image"]') + ?.getAttribute("content"); + const ogUrl = document + .querySelector('meta[property="og:url"]') + ?.getAttribute("content"); + const canonicalLink = document + .querySelector('link[rel="canonical"]') + ?.getAttribute("href"); expect(titleText).toContain(name); expect(ogUrl).toEqual(`${WEBAPP_URL}/${user.username}/30-min`); await page.waitForSelector('[data-testid="avatar-href"]'); - const avatarLocators = await page.locator('[data-testid="avatar-href"]').all(); + const avatarLocators = await page + .locator('[data-testid="avatar-href"]') + .all(); expect(avatarLocators.length).toBe(1); for (const avatarLocator of avatarLocators) { - expect(await avatarLocator.getAttribute("href")).toEqual(`${WEBAPP_URL}/${user.username}?redirect=false`); + expect(await avatarLocator.getAttribute("href")).toEqual( + `${WEBAPP_URL}/${user.username}?redirect=false` + ); } expect(canonicalLink).toEqual(`${WEBAPP_URL}/${user.username}/30-min`); @@ -157,7 +167,10 @@ test.describe("pro user", () => { await page.goto("/bookings/upcoming"); await page.waitForSelector('[data-testid="bookings"]'); // Click the ellipsis menu button to open the dropdown - await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page + .locator('[data-testid="booking-actions-dropdown"]') + .nth(0) + .click(); await page.locator('[data-testid="reschedule"]').click(); await page.waitForURL((url) => { const bookingId = url.searchParams.get("rescheduleUid"); @@ -178,17 +191,31 @@ test.describe("pro user", () => { }) => { const [pro] = users.get(); const [eventType] = pro.eventTypes; - const bookingFixture = await bookings.create(pro.id, pro.username, eventType.id); + const bookingFixture = await bookings.create( + pro.id, + pro.username, + eventType.id + ); // open the wrong eventType (rescheduleUid created for /30min event) - await page.goto(`${pro.username}/${pro.eventTypes[1].slug}?rescheduleUid=${bookingFixture.uid}`); + await page.goto( + `${pro.username}/${pro.eventTypes[1].slug}?rescheduleUid=${bookingFixture.uid}` + ); - await expect(page).toHaveURL(new RegExp(`${pro.username}/${eventType.slug}`)); + await expect(page).toHaveURL( + new RegExp(`${pro.username}/${eventType.slug}`) + ); }); - test("it returns a 404 when a requested event type does not exist", async ({ page, users }) => { + test("it returns a 404 when a requested event type does not exist", async ({ + page, + users, + }) => { const [pro] = users.get(); - const unexistingPageUrl = new URL(`${pro.username}/invalid-event-type`, WEBAPP_URL); + const unexistingPageUrl = new URL( + `${pro.username}/invalid-event-type`, + WEBAPP_URL + ); const response = await page.goto(unexistingPageUrl.href); expect(response?.status()).toBe(404); }); @@ -200,8 +227,12 @@ test.describe("pro user", () => { // 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); + 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(); @@ -224,8 +255,12 @@ test.describe("pro user", () => { // 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); + 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(); @@ -242,7 +277,9 @@ test.describe("pro user", () => { await page.goto(`/reschedule/${bookingUid}`); expect(page.url()).not.toContain("rescheduleUid"); - const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]'); + const cancelledHeadline = page.locator( + '[data-testid="cancelled-headline"]' + ); await expect(cancelledHeadline).toBeVisible(); }); @@ -257,14 +294,18 @@ test.describe("pro user", () => { await page.goto("/bookings/unconfirmed"); await Promise.all([ page.click('[data-testid="confirm"]'), - page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm")), + page.waitForResponse((response) => + response.url().includes("/api/trpc/bookings/confirm") + ), ]); // This is the only booking in there that needed confirmation and now it should be empty screen await expect(page.locator('[data-testid="empty-screen"]')).toBeVisible(); }); test("can book an unconfirmed event multiple times", async ({ page }) => { - await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click(); + await page + .locator('[data-testid="event-type-link"]:has-text("Opt in")') + .click(); await selectFirstAvailableTimeSlotNextMonth(page); const pageUrl = page.url(); @@ -281,7 +322,9 @@ test.describe("pro user", () => { test("booking an unconfirmed event with the same email brings you to the original request", async ({ page, }) => { - await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click(); + await page + .locator('[data-testid="event-type-link"]:has-text("Opt in")') + .click(); await selectFirstAvailableTimeSlotNextMonth(page); const pageUrl = page.url(); @@ -312,12 +355,17 @@ test.describe("pro user", () => { await expect(page.locator("[data-testid=success-page]")).toBeVisible(); const promises = additionalGuests.map(async (email) => { - await expect(page.locator(`[data-testid="attendee-email-${email}"]`)).toHaveText(email); + await expect( + page.locator(`[data-testid="attendee-email-${email}"]`) + ).toHaveText(email); }); await Promise.all(promises); }); - test("Time slots should be reserved when selected", async ({ page, browser }) => { + test("Time slots should be reserved when selected", async ({ + page, + browser, + }) => { const initialUrl = page.url(); await page.locator('[data-testid="event-type-link"]').first().click(); await selectFirstAvailableTimeSlotNextMonth(page); @@ -325,16 +373,29 @@ test.describe("pro user", () => { const pageTwoInNewContext = await newContext.newPage(); await pageTwoInNewContext.goto(initialUrl); await pageTwoInNewContext.waitForURL(initialUrl); - await pageTwoInNewContext.locator('[data-testid="event-type-link"]').first().click(); - - await pageTwoInNewContext.locator('[data-testid="incrementMonth"]').waitFor(); + await pageTwoInNewContext + .locator('[data-testid="event-type-link"]') + .first() + .click(); + + await pageTwoInNewContext + .locator('[data-testid="incrementMonth"]') + .waitFor(); await pageTwoInNewContext.click('[data-testid="incrementMonth"]'); - await pageTwoInNewContext.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor(); - await pageTwoInNewContext.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); + await pageTwoInNewContext + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .waitFor(); + await pageTwoInNewContext + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .click(); // 9:30 should be the first available time slot await pageTwoInNewContext.locator('[data-testid="time"]').nth(0).waitFor(); - const firstSlotAvailable = pageTwoInNewContext.locator('[data-testid="time"]').nth(0); + const firstSlotAvailable = pageTwoInNewContext + .locator('[data-testid="time"]') + .nth(0); // Find text inside the element const firstSlotAvailableText = await firstSlotAvailable.innerText(); expect(firstSlotAvailableText).toContain("9:30"); @@ -346,7 +407,9 @@ test.describe("pro user", () => { }) => { const initialUrl = page.url(); await page.waitForSelector('[data-testid="event-type-link"]'); - const eventTypeLink = page.locator('[data-testid="event-type-link"]').first(); + const eventTypeLink = page + .locator('[data-testid="event-type-link"]') + .first(); await eventTypeLink.click(); await selectFirstAvailableTimeSlotNextMonth(page); @@ -355,7 +418,9 @@ test.describe("pro user", () => { await pageTwo.waitForURL(initialUrl); await pageTwo.waitForSelector('[data-testid="event-type-link"]'); - const eventTypeLinkTwo = pageTwo.locator('[data-testid="event-type-link"]').first(); + const eventTypeLinkTwo = pageTwo + .locator('[data-testid="event-type-link"]') + .first(); await eventTypeLinkTwo.waitFor(); await eventTypeLinkTwo.click(); @@ -364,8 +429,14 @@ test.describe("pro user", () => { await pageTwo.locator('[data-testid="incrementMonth"]').waitFor(); await pageTwo.click('[data-testid="incrementMonth"]'); - await pageTwo.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor(); - await pageTwo.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); + await pageTwo + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .waitFor(); + await pageTwo + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .click(); await pageTwo.locator('[data-testid="time"]').nth(0).waitFor(); const firstSlotAvailable = pageTwo.locator('[data-testid="time"]').nth(0); @@ -384,7 +455,9 @@ test.describe("prefill", () => { await test.step("from session", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await expect(page.locator('[name="name"]')).toHaveValue(prefill.name || ""); + await expect(page.locator('[name="name"]')).toHaveValue( + prefill.name || "" + ); await expect(page.locator('[name="email"]')).toHaveValue(prefill.email); }); @@ -399,7 +472,9 @@ test.describe("prefill", () => { }); }); - test("Persist the field values when going back and coming back to the booking form", async ({ page }) => { + test("Persist the field values when going back and coming back to the booking form", async ({ + page, + }) => { await page.goto("/pro/30min"); await selectFirstAvailableTimeSlotNextMonth(page); await page.fill('[name="name"]', "John Doe"); @@ -409,7 +484,9 @@ test.describe("prefill", () => { await selectFirstAvailableTimeSlotNextMonth(page); await expect(page.locator('[name="name"]')).toHaveValue("John Doe"); - await expect(page.locator('[name="email"]')).toHaveValue("john@example.com"); + await expect(page.locator('[name="email"]')).toHaveValue( + "john@example.com" + ); await expect(page.locator('[name="notes"]')).toHaveValue("Test notes"); }); @@ -429,7 +506,9 @@ test.describe("prefill", () => { }); }); - test("skip confirm step if all fields are prefilled from query params", async ({ page }) => { + test("skip confirm step if all fields are prefilled from query params", async ({ + page, + }) => { await page.goto("/pro/30min"); const url = new URL(page.url()); url.searchParams.set("name", testName); @@ -440,7 +519,9 @@ test.describe("prefill", () => { await page.goto(url.toString()); await selectFirstAvailableTimeSlotNextMonth(page); - await expect(page.locator('[data-testid="skip-confirm-book-button"]')).toBeVisible(); + await expect( + page.locator('[data-testid="skip-confirm-book-button"]') + ).toBeVisible(); await page.click('[data-testid="skip-confirm-book-button"]'); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); @@ -455,7 +536,15 @@ test.describe("Booking on different layouts", () => { start: new Date(new Date().setUTCHours(9, 0, 0, 0)), end: new Date(new Date().setUTCHours(17, 0, 0, 0)), }; - const schedule: Schedule = [[], [dateRanges], [dateRanges], [dateRanges], [dateRanges], [dateRanges], []]; + const schedule: Schedule = [ + [], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [], + ]; const user = await users.create({ schedule }); await page.goto(`/${user.username}`); @@ -490,6 +579,13 @@ test.describe("Booking on different layouts", () => { await page.click('[data-testid="incrementMonth"]'); + await page.waitForURL((url) => { + return url.searchParams.has("month"); + }) + + await page.reload(); + await page.waitForLoadState("networkidle"); + await page.locator('[data-testid="time"]').nth(1).click(); // Fill what is this meeting about? name email and notes @@ -513,7 +609,15 @@ test.describe("Booking round robin event", () => { end: new Date(new Date().setUTCHours(17, 0, 0, 0)), }; - const schedule: Schedule = [[], [dateRanges], [dateRanges], [dateRanges], [dateRanges], [dateRanges], []]; + const schedule: Schedule = [ + [], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [], + ]; const testUser = await users.create( { schedule }, @@ -621,12 +725,16 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { bookingId = pathSegments[pathSegments.length - 1]; }); - test("Reschedule and cancel buttons should be hidden on success page", async ({ page }) => { + test("Reschedule and cancel buttons should be hidden on success page", async ({ + page, + }) => { await expect(page.locator('[data-testid="reschedule-link"]')).toBeHidden(); await expect(page.locator('[data-testid="cancel"]')).toBeHidden(); }); - test("Direct access to reschedule/{bookingId} should redirect to success page", async ({ page }) => { + test("Direct access to reschedule/{bookingId} should redirect to success page", async ({ + page, + }) => { await page.goto(`/reschedule/${bookingId}`); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); @@ -634,15 +742,21 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { await page.waitForURL((url) => url.pathname === `/booking/${bookingId}`); }); - test("Using rescheduleUid query parameter should redirect to success page", async ({ page }) => { - await page.goto(`/${user.username}/no-cancel-no-reschedule?rescheduleUid=${bookingId}`); + test("Using rescheduleUid query parameter should redirect to success page", async ({ + page, + }) => { + await page.goto( + `/${user.username}/no-cancel-no-reschedule?rescheduleUid=${bookingId}` + ); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); await page.waitForURL((url) => url.pathname === `/booking/${bookingId}`); }); - test("Should prevent cancellation and show an error message", async ({ page }) => { + test("Should prevent cancellation and show an error message", async ({ + page, + }) => { const csrfTokenResponse = await page.request.get("/api/csrf"); const { csrfToken } = await csrfTokenResponse.json(); const response = await page.request.post("/api/cancel", { @@ -657,10 +771,15 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { expect(response.status()).toBe(400); const responseBody = await response.json(); - expect(responseBody.message).toBe("This event type does not allow cancellations"); + expect(responseBody.message).toBe( + "This event type does not allow cancellations" + ); }); }); -test("Should throw error when both seatsPerTimeSlot and recurringEvent are set", async ({ page, users }) => { +test("Should throw error when both seatsPerTimeSlot and recurringEvent are set", async ({ + page, + users, +}) => { const user = await users.create({ name: `Test-user-${randomString(4)}`, eventTypes: [ @@ -699,7 +818,11 @@ test.describe("GTM container", () => { await users.create(); }); - test("global GTM should not be loaded on private booking link", async ({ page, users, prisma }) => { + test("global GTM should not be loaded on private booking link", async ({ + page, + users, + prisma, + }) => { const [user] = users.get(); const eventType = await user.getFirstEventAsOwner(); @@ -722,9 +845,12 @@ test.describe("GTM container", () => { }); const getScheduleRespPromise = page.waitForResponse( - (response) => response.url().includes("getSchedule") && response.status() === 200 + (response) => + response.url().includes("getSchedule") && response.status() === 200 + ); + await page.goto( + `/d/${eventWithPrivateLink.hashedLink[0]?.link}/${eventWithPrivateLink.slug}` ); - await page.goto(`/d/${eventWithPrivateLink.hashedLink[0]?.link}/${eventWithPrivateLink.slug}`); await page.waitForLoadState("domcontentloaded"); await getScheduleRespPromise; @@ -732,15 +858,23 @@ test.describe("GTM container", () => { await expect(injectedScript).not.toBeAttached(); }); - test("global GTM should be loaded on non-booking pages", async ({ page, users }) => { - test.skip(!process.env.NEXT_PUBLIC_BODY_SCRIPTS, "Skipping test as NEXT_PUBLIC_BODY_SCRIPTS is not set"); + test("global GTM should be loaded on non-booking pages", async ({ + page, + users, + }) => { + test.skip( + !process.env.NEXT_PUBLIC_BODY_SCRIPTS, + "Skipping test as NEXT_PUBLIC_BODY_SCRIPTS is not set" + ); const [user] = users.get(); await user.apiLogin(); // Go to /insights page and wait for one of the common API call to complete const eventsByStatusRespPromise = page.waitForResponse( - (response) => response.url().includes("getEventTypesFromGroup") && response.status() === 200 + (response) => + response.url().includes("getEventTypesFromGroup") && + response.status() === 200 ); await page.goto(`/insights`); await page.waitForLoadState("domcontentloaded"); @@ -755,7 +889,11 @@ test.describe("GTM container", () => { }); test.describe("Past booking cancellation", () => { - test("Cancel button should be hidden for past bookings", async ({ page, users, bookings }) => { + test("Cancel button should be hidden for past bookings", async ({ + page, + users, + bookings, + }) => { const user = await users.create({ name: "Test User", }); @@ -766,15 +904,23 @@ test.describe("Past booking cancellation", () => { pastDate.setDate(pastDate.getDate() - 1); const endDate = new Date(pastDate.getTime() + 30 * 60 * 1000); - const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id, { - title: "Past Meeting", - startTime: pastDate, - endTime: endDate, - status: "ACCEPTED", - }); + const booking = await bookings.create( + user.id, + user.username, + user.eventTypes[0].id, + { + title: "Past Meeting", + startTime: pastDate, + endTime: endDate, + status: "ACCEPTED", + } + ); await page.goto("/bookings/past"); - await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page + .locator('[data-testid="booking-actions-dropdown"]') + .nth(0) + .click(); await expect(page.locator('[data-testid="cancel"]')).toBeDisabled(); await page.goto(`/booking/${booking.uid}`); diff --git a/apps/web/playwright/fixtures/apps.ts b/apps/web/playwright/fixtures/apps.ts index e2c359b10522c1..e44f64909c0678 100644 --- a/apps/web/playwright/fixtures/apps.ts +++ b/apps/web/playwright/fixtures/apps.ts @@ -117,7 +117,7 @@ export function createAppsFixture(page: Page) { }, goToAppsTab: async () => { await page.getByTestId("vertical-tab-apps").click(); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); }, activeApp: async (app: string) => { await page.locator(`[data-testid='${app}-app-switch']`).click(); @@ -127,7 +127,7 @@ export function createAppsFixture(page: Page) { }, verifyAppsInfoNew: async (app: string, eventTypeId: number) => { await page.goto(`event-types/${eventTypeId}?tabName=apps`); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); // fix the race condition + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); // fix the race condition await expect(page.locator(`[data-testid='${app}-app-switch'][data-state="checked"]`)).toBeVisible(); }, }; diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index a9ee8b89121fd8..2e6898dabd02a8 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -34,7 +34,7 @@ test.describe("Managed Event Types", () => { // Let's create a team // Going to create an event type await page.goto("/event-types"); - const tabItem = page.getByTestId(`horizontal-tab-Owner`); + const tabItem = page.getByTestId("horizontal-tab-Owner").first(); await expect(tabItem).toBeVisible(); // We wait until loading is finished await page.waitForSelector('[data-testid="event-types"]'); diff --git a/apps/web/playwright/payment-apps.e2e.ts b/apps/web/playwright/payment-apps.e2e.ts index 2b5cf89b1e38ef..866a0a24b2d901 100644 --- a/apps/web/playwright/payment-apps.e2e.ts +++ b/apps/web/playwright/payment-apps.e2e.ts @@ -11,7 +11,7 @@ test.afterEach(({ users }) => users.deleteAll()); async function goToAppsTab(page: Page, eventTypeId?: number) { await page.goto(`event-types/${eventTypeId}?tabName=apps`); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); } test.describe("Payment app", () => { diff --git a/apps/web/public/static/locales/ar/common.json b/apps/web/public/static/locales/ar/common.json index cf21eea11b5a7a..6689db8b6dd5ba 100644 --- a/apps/web/public/static/locales/ar/common.json +++ b/apps/web/public/static/locales/ar/common.json @@ -3493,6 +3493,7 @@ "last_active": "آخر نشاط", "member_since": "عضو منذ", "last_updated": "آخر تحديث", + "completed_onboarding": "اكتمل الإعداد", "salesforce_on_cancel_write_to_event": "عند إلغاء الحجز، اكتب في سجل الحدث بدلاً من حذف الحدث", "salesforce_on_every_cancellation": "عند كل إلغاء", "report_issue": "الإبلاغ عن مشكلة", diff --git a/apps/web/public/static/locales/az/common.json b/apps/web/public/static/locales/az/common.json index 581ff6b3a90c66..001afb8e2afbe2 100644 --- a/apps/web/public/static/locales/az/common.json +++ b/apps/web/public/static/locales/az/common.json @@ -3493,6 +3493,7 @@ "last_active": "Son aktivlik", "member_since": "Üzvlük tarixi", "last_updated": "Son yenilənmə", + "completed_onboarding": "Tanışlıq tamamlandı", "salesforce_on_cancel_write_to_event": "Rezervasiya ləğv edildikdə, tədbiri silmək əvəzinə tədbir qeydinə yazın", "salesforce_on_every_cancellation": "Hər ləğv zamanı", "report_issue": "Problemi bildirin", diff --git a/apps/web/public/static/locales/bg/common.json b/apps/web/public/static/locales/bg/common.json index ba87c0891a8916..dfa976315c4ecc 100644 --- a/apps/web/public/static/locales/bg/common.json +++ b/apps/web/public/static/locales/bg/common.json @@ -3493,6 +3493,7 @@ "last_active": "Последна активност", "member_since": "Член от", "last_updated": "Последно обновено", + "completed_onboarding": "Завършено въвеждане", "salesforce_on_cancel_write_to_event": "При отменена резервация, запиши в записа на събитието вместо да изтриеш събитието", "salesforce_on_every_cancellation": "При всяка отмяна", "report_issue": "Докладвай проблем", diff --git a/apps/web/public/static/locales/bn/common.json b/apps/web/public/static/locales/bn/common.json index 2c357da03bfd67..90ea99fbfbda9d 100644 --- a/apps/web/public/static/locales/bn/common.json +++ b/apps/web/public/static/locales/bn/common.json @@ -3493,6 +3493,7 @@ "last_active": "সর্বশেষ সক্রিয়", "member_since": "সদস্য হওয়ার তারিখ", "last_updated": "সর্বশেষ আপডেট", + "completed_onboarding": "অনবোর্ডিং সম্পন্ন হয়েছে", "salesforce_on_cancel_write_to_event": "বাতিল করা বুকিংয়ের ক্ষেত্রে, ইভেন্ট মুছে ফেলার পরিবর্তে ইভেন্ট রেকর্ডে লিখুন", "salesforce_on_every_cancellation": "প্রতিটি বাতিলকরণের সময়", "report_issue": "সমস্যা রিপোর্ট করুন", diff --git a/apps/web/public/static/locales/ca/common.json b/apps/web/public/static/locales/ca/common.json index de325f2bb62d91..5dfee2581cd540 100644 --- a/apps/web/public/static/locales/ca/common.json +++ b/apps/web/public/static/locales/ca/common.json @@ -3493,6 +3493,7 @@ "last_active": "Última activitat", "member_since": "Membre des de", "last_updated": "Última actualització", + "completed_onboarding": "Incorporació completada", "salesforce_on_cancel_write_to_event": "En cancel·lar la reserva, escriu al registre d'esdeveniments en lloc d'eliminar l'esdeveniment", "salesforce_on_every_cancellation": "En cada cancel·lació", "report_issue": "Informar d'un problema", diff --git a/apps/web/public/static/locales/cs/common.json b/apps/web/public/static/locales/cs/common.json index a33e9e255f2f1e..f5d6384ca4a752 100644 --- a/apps/web/public/static/locales/cs/common.json +++ b/apps/web/public/static/locales/cs/common.json @@ -3493,6 +3493,7 @@ "last_active": "Naposledy aktivní", "member_since": "Členem od", "last_updated": "Naposledy aktualizováno", + "completed_onboarding": "Dokončené úvodní nastavení", "salesforce_on_cancel_write_to_event": "Při zrušení rezervace zapsat do záznamu události místo smazání události", "salesforce_on_every_cancellation": "Při každém zrušení", "report_issue": "Nahlásit problém", diff --git a/apps/web/public/static/locales/da/common.json b/apps/web/public/static/locales/da/common.json index 2601d251db0ec2..ae90aeccce9edf 100644 --- a/apps/web/public/static/locales/da/common.json +++ b/apps/web/public/static/locales/da/common.json @@ -3493,6 +3493,7 @@ "last_active": "Sidst aktiv", "member_since": "Medlem siden", "last_updated": "Sidst opdateret", + "completed_onboarding": "Onboarding gennemført", "salesforce_on_cancel_write_to_event": "Ved aflyst booking, skriv til begivenhedsoptegnelsen i stedet for at slette begivenheden", "salesforce_on_every_cancellation": "Ved hver aflysning", "report_issue": "Rapportér problem", diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index d0cb2b75fb76e3..5091b98f96b388 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -3493,6 +3493,7 @@ "last_active": "Zuletzt aktiv", "member_since": "Mitglied seit", "last_updated": "Zuletzt aktualisiert", + "completed_onboarding": "Onboarding abgeschlossen", "salesforce_on_cancel_write_to_event": "Bei stornierter Buchung in den Ereignisdatensatz schreiben, anstatt das Ereignis zu löschen", "salesforce_on_every_cancellation": "Bei jeder Stornierung", "report_issue": "Problem melden", diff --git a/apps/web/public/static/locales/el/common.json b/apps/web/public/static/locales/el/common.json index abc3fcd1449a4e..b737fb63e8259d 100644 --- a/apps/web/public/static/locales/el/common.json +++ b/apps/web/public/static/locales/el/common.json @@ -3493,6 +3493,7 @@ "last_active": "Τελευταία ενεργός", "member_since": "Μέλος από", "last_updated": "Τελευταία ενημέρωση", + "completed_onboarding": "Ολοκληρωμένη εισαγωγή", "salesforce_on_cancel_write_to_event": "Σε ακύρωση κράτησης, εγγραφή στο αρχείο συμβάντος αντί για διαγραφή συμβάντος", "salesforce_on_every_cancellation": "Σε κάθε ακύρωση", "report_issue": "Αναφορά προβλήματος", diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index d3a9eb82388c5f..b38ffb7ae53af1 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3494,6 +3494,7 @@ "last_active": "Last Active", "member_since": "Member Since", "last_updated": "Last Updated", + "completed_onboarding": "Completed Onboarding", "salesforce_on_cancel_write_to_event": "On cancelled booking, write to event record instead of deleting event", "salesforce_on_every_cancellation": "On every cancellation", "report_issue": "Report issue", diff --git a/apps/web/public/static/locales/es-419/common.json b/apps/web/public/static/locales/es-419/common.json index 07458444408040..d3d650f5b171ce 100644 --- a/apps/web/public/static/locales/es-419/common.json +++ b/apps/web/public/static/locales/es-419/common.json @@ -3493,6 +3493,7 @@ "last_active": "Última actividad", "member_since": "Miembro desde", "last_updated": "Última actualización", + "completed_onboarding": "Configuración inicial completada", "salesforce_on_cancel_write_to_event": "Al cancelar una reserva, escribir en el registro del evento en lugar de eliminarlo", "salesforce_on_every_cancellation": "En cada cancelación", "report_issue": "Reportar problema", diff --git a/apps/web/public/static/locales/es/common.json b/apps/web/public/static/locales/es/common.json index 134e214209bc4e..78a0a63b8e746b 100644 --- a/apps/web/public/static/locales/es/common.json +++ b/apps/web/public/static/locales/es/common.json @@ -3493,6 +3493,7 @@ "last_active": "Última actividad", "member_since": "Miembro desde", "last_updated": "Última actualización", + "completed_onboarding": "Configuración inicial completada", "salesforce_on_cancel_write_to_event": "Al cancelar la reserva, escribir en el registro del evento en lugar de eliminarlo", "salesforce_on_every_cancellation": "En cada cancelación", "report_issue": "Informar de un problema", diff --git a/apps/web/public/static/locales/et/common.json b/apps/web/public/static/locales/et/common.json index c552b3f38e78f8..c98fddb8dcfff4 100644 --- a/apps/web/public/static/locales/et/common.json +++ b/apps/web/public/static/locales/et/common.json @@ -3493,6 +3493,7 @@ "last_active": "Viimati aktiivne", "member_since": "Liige alates", "last_updated": "Viimati uuendatud", + "completed_onboarding": "Sisseelamine lõpetatud", "salesforce_on_cancel_write_to_event": "Tühistatud broneeringu korral kirjutage sündmuse kirjesse selle asemel, et sündmus kustutada", "salesforce_on_every_cancellation": "Iga tühistamise korral", "report_issue": "Teata probleemist", diff --git a/apps/web/public/static/locales/eu/common.json b/apps/web/public/static/locales/eu/common.json index 2c5ca3783b5685..f0549773616584 100644 --- a/apps/web/public/static/locales/eu/common.json +++ b/apps/web/public/static/locales/eu/common.json @@ -3493,6 +3493,7 @@ "last_active": "Azken aldiz aktibo", "member_since": "Kide noiztik", "last_updated": "Azken eguneratzea", + "completed_onboarding": "Hasierako prozesua amaituta", "salesforce_on_cancel_write_to_event": "Erreserba bertan behera uztean, idatzi gertaera erregistroan gertaera ezabatu beharrean", "salesforce_on_every_cancellation": "Bertan behera uzte bakoitzean", "report_issue": "Arazoa jakinarazi", diff --git a/apps/web/public/static/locales/fi/common.json b/apps/web/public/static/locales/fi/common.json index 59bc6d8147273c..39d27d3f79c4d1 100644 --- a/apps/web/public/static/locales/fi/common.json +++ b/apps/web/public/static/locales/fi/common.json @@ -3493,6 +3493,7 @@ "last_active": "Viimeksi aktiivinen", "member_since": "Jäsen alkaen", "last_updated": "Viimeksi päivitetty", + "completed_onboarding": "Käyttöönotto suoritettu", "salesforce_on_cancel_write_to_event": "Peruutetun varauksen yhteydessä kirjoita tapahtuman tietueeseen tapahtuman poistamisen sijaan", "salesforce_on_every_cancellation": "Jokaisessa peruutuksessa", "report_issue": "Ilmoita ongelmasta", diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index 895b895aa4e338..c836e702288093 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -3493,6 +3493,7 @@ "last_active": "Dernière activité", "member_since": "Membre depuis", "last_updated": "Dernière mise à jour", + "completed_onboarding": "Intégration terminée", "salesforce_on_cancel_write_to_event": "Lors de l'annulation d'une réservation, écrire dans l'enregistrement de l'événement au lieu de le supprimer", "salesforce_on_every_cancellation": "À chaque annulation", "report_issue": "Signaler un problème", diff --git a/apps/web/public/static/locales/he/common.json b/apps/web/public/static/locales/he/common.json index a280f3b876ddc4..734a8a9aa2be22 100644 --- a/apps/web/public/static/locales/he/common.json +++ b/apps/web/public/static/locales/he/common.json @@ -3493,6 +3493,7 @@ "last_active": "פעיל לאחרונה", "member_since": "חבר מאז", "last_updated": "עודכן לאחרונה", + "completed_onboarding": "תהליך ההכנה הושלם", "salesforce_on_cancel_write_to_event": "בעת ביטול הזמנה, כתוב לרשומת האירוע במקום למחוק את האירוע", "salesforce_on_every_cancellation": "בכל ביטול", "report_issue": "דווח על בעיה", diff --git a/apps/web/public/static/locales/hu/common.json b/apps/web/public/static/locales/hu/common.json index ff6780f410cb10..80e4d1d8fbdd1d 100644 --- a/apps/web/public/static/locales/hu/common.json +++ b/apps/web/public/static/locales/hu/common.json @@ -3493,6 +3493,7 @@ "last_active": "Utoljára aktív", "member_since": "Tag mióta", "last_updated": "Utoljára frissítve", + "completed_onboarding": "Beállítás befejezve", "salesforce_on_cancel_write_to_event": "Lemondott foglalás esetén írjon az esemény rekordba az esemény törlése helyett", "salesforce_on_every_cancellation": "Minden lemondás esetén", "report_issue": "Probléma jelentése", diff --git a/apps/web/public/static/locales/it/common.json b/apps/web/public/static/locales/it/common.json index c9db26220955de..f319a50ab4f7a1 100644 --- a/apps/web/public/static/locales/it/common.json +++ b/apps/web/public/static/locales/it/common.json @@ -3493,6 +3493,7 @@ "last_active": "Ultima attività", "member_since": "Membro dal", "last_updated": "Ultimo aggiornamento", + "completed_onboarding": "Onboarding completato", "salesforce_on_cancel_write_to_event": "In caso di prenotazione annullata, scrivi nel record dell'evento invece di eliminarlo", "salesforce_on_every_cancellation": "Ad ogni cancellazione", "report_issue": "Segnala un problema", diff --git a/apps/web/public/static/locales/ja/common.json b/apps/web/public/static/locales/ja/common.json index aa0fd6be219bca..dc762198451d04 100644 --- a/apps/web/public/static/locales/ja/common.json +++ b/apps/web/public/static/locales/ja/common.json @@ -3493,6 +3493,7 @@ "last_active": "最終アクティブ", "member_since": "登録日", "last_updated": "最終更新日", + "completed_onboarding": "オンボーディング完了", "salesforce_on_cancel_write_to_event": "予約キャンセル時、イベントを削除せずにイベントレコードに書き込む", "salesforce_on_every_cancellation": "すべてのキャンセル時", "report_issue": "問題を報告", diff --git a/apps/web/public/static/locales/km/common.json b/apps/web/public/static/locales/km/common.json index 352eae15aebaec..27cad0f9b03529 100644 --- a/apps/web/public/static/locales/km/common.json +++ b/apps/web/public/static/locales/km/common.json @@ -3493,6 +3493,7 @@ "last_active": "សកម្មភាពចុងក្រោយ", "member_since": "ជាសមាជិកតាំងពី", "last_updated": "បានធ្វើបច្ចុប្បន្នភាពចុងក្រោយ", + "completed_onboarding": "បានបញ្ចប់ការណែនាំ", "salesforce_on_cancel_write_to_event": "នៅពេលលុបចោលការកក់ សូមសរសេរទៅកាន់កំណត់ត្រាព្រឹត្តិការណ៍ជំនួសឱ្យការលុបព្រឹត្តិការណ៍", "salesforce_on_every_cancellation": "នៅរាល់ពេលលុបចោល", "report_issue": "រាយការណ៍បញ្ហា", diff --git a/apps/web/public/static/locales/ko/common.json b/apps/web/public/static/locales/ko/common.json index dbc97a058bd081..4d8b1b154890d8 100644 --- a/apps/web/public/static/locales/ko/common.json +++ b/apps/web/public/static/locales/ko/common.json @@ -3493,6 +3493,7 @@ "last_active": "마지막 활동", "member_since": "가입일", "last_updated": "마지막 업데이트", + "completed_onboarding": "온보딩 완료됨", "salesforce_on_cancel_write_to_event": "예약 취소 시 이벤트 삭제 대신 이벤트 기록에 작성", "salesforce_on_every_cancellation": "모든 취소 시", "report_issue": "문제 신고", diff --git a/apps/web/public/static/locales/nl/common.json b/apps/web/public/static/locales/nl/common.json index e1f803f2266dd3..cf4c51ceb510b1 100644 --- a/apps/web/public/static/locales/nl/common.json +++ b/apps/web/public/static/locales/nl/common.json @@ -3493,6 +3493,7 @@ "last_active": "Laatst actief", "member_since": "Lid sinds", "last_updated": "Laatst bijgewerkt", + "completed_onboarding": "Onboarding voltooid", "salesforce_on_cancel_write_to_event": "Bij geannuleerde boeking, schrijf naar evenementrecord in plaats van evenement verwijderen", "salesforce_on_every_cancellation": "Bij elke annulering", "report_issue": "Probleem melden", diff --git a/apps/web/public/static/locales/no/common.json b/apps/web/public/static/locales/no/common.json index d77f55a8733996..4cbbda06c98e60 100644 --- a/apps/web/public/static/locales/no/common.json +++ b/apps/web/public/static/locales/no/common.json @@ -3493,6 +3493,7 @@ "last_active": "Sist aktiv", "member_since": "Medlem siden", "last_updated": "Sist oppdatert", + "completed_onboarding": "Fullført onboarding", "salesforce_on_cancel_write_to_event": "Ved kansellert booking, skriv til hendelsesposten i stedet for å slette hendelsen", "salesforce_on_every_cancellation": "Ved hver kansellering", "report_issue": "Rapporter problem", diff --git a/apps/web/public/static/locales/pl/common.json b/apps/web/public/static/locales/pl/common.json index 2a66b6bcd4bae5..d23094810abb9a 100644 --- a/apps/web/public/static/locales/pl/common.json +++ b/apps/web/public/static/locales/pl/common.json @@ -3493,6 +3493,7 @@ "last_active": "Ostatnia aktywność", "member_since": "Członek od", "last_updated": "Ostatnia aktualizacja", + "completed_onboarding": "Wdrażanie zakończone", "salesforce_on_cancel_write_to_event": "W przypadku odwołanej rezerwacji zapisz w rekordzie wydarzenia zamiast usuwać wydarzenie", "salesforce_on_every_cancellation": "Przy każdej anulacji", "report_issue": "Zgłoś problem", diff --git a/apps/web/public/static/locales/pt-BR/common.json b/apps/web/public/static/locales/pt-BR/common.json index 690318231c1301..3d6f64408d75a2 100644 --- a/apps/web/public/static/locales/pt-BR/common.json +++ b/apps/web/public/static/locales/pt-BR/common.json @@ -3493,6 +3493,7 @@ "last_active": "Último ativo", "member_since": "Membro desde", "last_updated": "Última atualização", + "completed_onboarding": "Integração concluída", "salesforce_on_cancel_write_to_event": "Ao cancelar a reserva, escrever no registro do evento em vez de excluí-lo", "salesforce_on_every_cancellation": "Em cada cancelamento", "report_issue": "Reportar problema", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index bd5f4e49657b2a..961a9d07cf1503 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -3493,6 +3493,7 @@ "last_active": "Última atividade", "member_since": "Membro desde", "last_updated": "Última atualização", + "completed_onboarding": "Integração concluída", "salesforce_on_cancel_write_to_event": "Ao cancelar a reserva, escrever no registro do evento em vez de excluí-lo", "salesforce_on_every_cancellation": "Em cada cancelamento", "report_issue": "Reportar problema", diff --git a/apps/web/public/static/locales/ro/common.json b/apps/web/public/static/locales/ro/common.json index da1c2250da3f04..d04fa56c70ce3d 100644 --- a/apps/web/public/static/locales/ro/common.json +++ b/apps/web/public/static/locales/ro/common.json @@ -3493,6 +3493,7 @@ "last_active": "Ultima activitate", "member_since": "Membru din", "last_updated": "Ultima actualizare", + "completed_onboarding": "Onboarding finalizat", "salesforce_on_cancel_write_to_event": "La anularea rezervării, scrie în înregistrarea evenimentului în loc să ștergi evenimentul", "salesforce_on_every_cancellation": "La fiecare anulare", "report_issue": "Raportează o problemă", diff --git a/apps/web/public/static/locales/ru/common.json b/apps/web/public/static/locales/ru/common.json index b339556bad4f14..435703b615bc6a 100644 --- a/apps/web/public/static/locales/ru/common.json +++ b/apps/web/public/static/locales/ru/common.json @@ -3493,6 +3493,7 @@ "last_active": "Последняя активность", "member_since": "Участник с", "last_updated": "Последнее обновление", + "completed_onboarding": "Адаптация завершена", "salesforce_on_cancel_write_to_event": "При отмене бронирования записывать в запись события вместо удаления события", "salesforce_on_every_cancellation": "При каждой отмене", "report_issue": "Сообщить о проблеме", diff --git a/apps/web/public/static/locales/sk-SK/common.json b/apps/web/public/static/locales/sk-SK/common.json index c07373c3af7878..0a986f842cf7c8 100644 --- a/apps/web/public/static/locales/sk-SK/common.json +++ b/apps/web/public/static/locales/sk-SK/common.json @@ -3493,6 +3493,7 @@ "last_active": "Naposledy aktívny", "member_since": "Členom od", "last_updated": "Naposledy aktualizované", + "completed_onboarding": "Dokončené úvodné nastavenie", "salesforce_on_cancel_write_to_event": "Pri zrušenej rezervácii zapísať do záznamu udalosti namiesto vymazania udalosti", "salesforce_on_every_cancellation": "Pri každom zrušení", "report_issue": "Nahlásiť problém", diff --git a/apps/web/public/static/locales/sr/common.json b/apps/web/public/static/locales/sr/common.json index c5c100f0d80a68..db54bb4d601706 100644 --- a/apps/web/public/static/locales/sr/common.json +++ b/apps/web/public/static/locales/sr/common.json @@ -3493,6 +3493,7 @@ "last_active": "Poslednja aktivnost", "member_since": "Član od", "last_updated": "Poslednje ažuriranje", + "completed_onboarding": "Završeno uvođenje", "salesforce_on_cancel_write_to_event": "Pri otkazivanju rezervacije, upiši u evidenciju događaja umesto brisanja događaja", "salesforce_on_every_cancellation": "Pri svakom otkazivanju", "report_issue": "Prijavi problem", diff --git a/apps/web/public/static/locales/sv/common.json b/apps/web/public/static/locales/sv/common.json index 099597bb5e626b..05593d26bc0966 100644 --- a/apps/web/public/static/locales/sv/common.json +++ b/apps/web/public/static/locales/sv/common.json @@ -3493,6 +3493,7 @@ "last_active": "Senast aktiv", "member_since": "Medlem sedan", "last_updated": "Senast uppdaterad", + "completed_onboarding": "Onboarding slutförd", "salesforce_on_cancel_write_to_event": "Vid avbokning, skriv till händelseregistret istället för att ta bort händelsen", "salesforce_on_every_cancellation": "Vid varje avbokning", "report_issue": "Rapportera problem", diff --git a/apps/web/public/static/locales/tr/common.json b/apps/web/public/static/locales/tr/common.json index bbd961331c2c5c..b167ba03eea1f2 100644 --- a/apps/web/public/static/locales/tr/common.json +++ b/apps/web/public/static/locales/tr/common.json @@ -3493,6 +3493,7 @@ "last_active": "Son Aktif", "member_since": "Üyelik Başlangıcı", "last_updated": "Son Güncelleme", + "completed_onboarding": "Oryantasyon tamamlandı", "salesforce_on_cancel_write_to_event": "İptal edilen rezervasyonda, etkinliği silmek yerine etkinlik kaydına yaz", "salesforce_on_every_cancellation": "Her iptal işleminde", "report_issue": "Sorun bildir", diff --git a/apps/web/public/static/locales/uk/common.json b/apps/web/public/static/locales/uk/common.json index 0abebf8c646c3a..502bede445b188 100644 --- a/apps/web/public/static/locales/uk/common.json +++ b/apps/web/public/static/locales/uk/common.json @@ -3493,6 +3493,7 @@ "last_active": "Остання активність", "member_since": "Учасник з", "last_updated": "Останнє оновлення", + "completed_onboarding": "Онбординг завершено", "salesforce_on_cancel_write_to_event": "При скасуванні бронювання, записувати в запис події замість видалення події", "salesforce_on_every_cancellation": "При кожному скасуванні", "report_issue": "Повідомити про проблему", diff --git a/apps/web/public/static/locales/vi/common.json b/apps/web/public/static/locales/vi/common.json index 4d01d58ec10718..820a46832011c5 100644 --- a/apps/web/public/static/locales/vi/common.json +++ b/apps/web/public/static/locales/vi/common.json @@ -3493,6 +3493,7 @@ "last_active": "Hoạt động lần cuối", "member_since": "Thành viên từ", "last_updated": "Cập nhật lần cuối", + "completed_onboarding": "Đã hoàn thành thiết lập", "salesforce_on_cancel_write_to_event": "Khi hủy đặt lịch, ghi vào bản ghi sự kiện thay vì xóa sự kiện", "salesforce_on_every_cancellation": "Với mỗi lần hủy", "report_issue": "Báo cáo vấn đề", diff --git a/apps/web/public/static/locales/zh-CN/common.json b/apps/web/public/static/locales/zh-CN/common.json index d6b5692981f23c..6185505c0a4f56 100644 --- a/apps/web/public/static/locales/zh-CN/common.json +++ b/apps/web/public/static/locales/zh-CN/common.json @@ -3493,6 +3493,7 @@ "last_active": "上次活跃", "member_since": "会员自", "last_updated": "最后更新", + "completed_onboarding": "引导已完成", "salesforce_on_cancel_write_to_event": "在取消预订时,写入事件记录而不是删除事件", "salesforce_on_every_cancellation": "每次取消时", "report_issue": "报告问题", diff --git a/apps/web/public/static/locales/zh-TW/common.json b/apps/web/public/static/locales/zh-TW/common.json index 5db09d83bdd1cb..c9f41537875122 100644 --- a/apps/web/public/static/locales/zh-TW/common.json +++ b/apps/web/public/static/locales/zh-TW/common.json @@ -3493,6 +3493,7 @@ "last_active": "最近活動", "member_since": "成員自", "last_updated": "最後更新", + "completed_onboarding": "已完成入門設置", "salesforce_on_cancel_write_to_event": "在取消預約時,寫入事件記錄而不是刪除事件", "salesforce_on_every_cancellation": "每次取消時", "report_issue": "回報問題", diff --git a/biome.json b/biome.json index 0eb530165696f8..055095ed7786f7 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json", "root": true, "css": { "parser": { @@ -21,26 +21,29 @@ "files": { "includes": [ "**/*", - "!**/node_modules", - "!**/.next", - "!**/.turbo", - "!**/dist", - "!**/build", - "!**/public", - "!public", - "!apps/web/public/embed", - "!packages/prisma/zod", - "!packages/prisma/enums", - "!*.lock", - "!*.log", - "!.gitignore", - "!.npmignore", - "!.prettierignore", - "!.eslintignore", - "!.DS_Store", - "!coverage", - "!lint-results", - "!test-results" + "!!**/node_modules", + "!!**/.next", + "!!**/.turbo", + "!!**/dist", + "!!**/build", + "!!**/public", + "!!public", + "!!apps/web/public/embed", + "!!packages/prisma/zod", + "!!packages/prisma/enums", + "!!*.lock", + "!!*.log", + "!!.gitignore", + "!!.npmignore", + "!!.prettierignore", + "!!.eslintignore", + "!!.DS_Store", + "!!coverage", + "!!lint-results", + "!!test-results", + "!!packages/lib/raqb/resolveQueryValue.test.ts", + "!!packages/ui/components/editor/plugins/ToolbarPlugin.tsx", + "!!packages/embeds/embed-core/src/lib/domUtils.ts" ] }, "javascript": { @@ -58,17 +61,14 @@ "enabled": true, "rules": { "recommended": true, - "style": { - "useNodejsImportProtocol": "warn" - }, - "correctness": { - "noUnusedVariables": { - "level": "warn", - "options": { - "ignoreRestSiblings": true - } - } - } + "correctness": "warn", + "suspicious": "warn", + "complexity": "warn", + "performance": "warn", + "nursery": "warn", + "a11y": "warn", + "style": "warn", + "security": "warn" } }, "overrides": [ @@ -78,7 +78,7 @@ "rules": { "style": { "noRestrictedImports": { - "level": "error", + "level": "warn", "options": { "patterns": [ { @@ -110,7 +110,7 @@ "rules": { "style": { "noRestrictedImports": { - "level": "error", + "level": "warn", "options": { "patterns": [ { @@ -134,7 +134,7 @@ "rules": { "style": { "noRestrictedImports": { - "level": "error", + "level": "warn", "options": { "patterns": [ { @@ -154,7 +154,7 @@ "rules": { "style": { "noRestrictedImports": { - "level": "error", + "level": "warn", "options": { "patterns": [ { diff --git a/companion/biome.json b/companion/biome.json index 98cc685dec8b6a..75163f8b39115f 100644 --- a/companion/biome.json +++ b/companion/biome.json @@ -1,5 +1,6 @@ { "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json", + "root": false, "vcs": { "enabled": true, "clientKind": "git", diff --git a/example-apps/credential-sync/package.json b/example-apps/credential-sync/package.json index fd455c040994df..cadaad94f2ffdb 100644 --- a/example-apps/credential-sync/package.json +++ b/example-apps/credential-sync/package.json @@ -5,8 +5,7 @@ "scripts": { "dev": "PORT=5100 next dev", "build": "next build", - "start": "next start", - "lint": "biome lint ." + "start": "next start" }, "dependencies": { "@calcom/atoms": "workspace:*", diff --git a/i18n.lock b/i18n.lock index f943900380cebf..0e8a3582a82715 100644 --- a/i18n.lock +++ b/i18n.lock @@ -3490,6 +3490,7 @@ checksums: last_active: f56f696ea3c36a1084fe7fab1c422b74 member_since: 68f13de9a1bc207db0d8b62c6b433052 last_updated: 956353f0af47fe60f0f45ec25d129a01 + completed_onboarding: 264710464ed3a726be025bdbff860db5 salesforce_on_cancel_write_to_event: 5d82dd4959cc2ae009a750f096ad74e0 salesforce_on_every_cancellation: a54453f804195b3747564b1ceb0d60b4 report_issue: 5abe40470cb5476d6e4f49fe422bd438 diff --git a/package.json b/package.json index 731198f72395b1..039bc9cf0e9e98 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "lint-staged": "lint-staged" }, "devDependencies": { - "@biomejs/biome": "^2.3.8", + "@biomejs/biome": "2.3.10", "@changesets/changelog-github": "0.5.1", "@changesets/cli": "2.29.4", "@faker-js/faker": "9.2.0", diff --git a/packages/coss-ui/package.json b/packages/coss-ui/package.json index 10383fece70825..dde1846e31847b 100644 --- a/packages/coss-ui/package.json +++ b/packages/coss-ui/package.json @@ -9,8 +9,8 @@ "./hooks/*": "./src/hooks/*.ts" }, "scripts": { - "lint": "biome lint .", - "lint:fix": "biome lint --write .", + "lint": "biome lint src", + "lint:fix": "biome lint --write src", "type-check": "tsc --pretty --noEmit", "type-check:ci": "tsc-absolute --pretty --noEmit", "registry:pull": "ts-node ../../scripts/pull-coss-ui-components.ts" @@ -27,6 +27,7 @@ "react-dom": "^18.0.0 || ^19.0.0" }, "devDependencies": { + "@biomejs/biome": "2.3.10", "ts-node": "10.9.2", "tsc-absolute": "1.0.0", "typescript": "5.9.0-beta" diff --git a/packages/embeds/embed-core/package.json b/packages/embeds/embed-core/package.json index eb095e31b20679..7bf5cb155004fa 100644 --- a/packages/embeds/embed-core/package.json +++ b/packages/embeds/embed-core/package.json @@ -41,6 +41,7 @@ "dist" ], "devDependencies": { + "@biomejs/biome": "2.3.10", "@playwright/test": "1.57.0", "@tailwindcss/cli": "4.1.16", "@tailwindcss/postcss": "4.1.15", diff --git a/packages/features/bookings/lib/handleNewBooking/getEventType.ts b/packages/features/bookings/lib/handleNewBooking/getEventType.ts index bb5313375b3994..ba0fa32b8c9df1 100644 --- a/packages/features/bookings/lib/handleNewBooking/getEventType.ts +++ b/packages/features/bookings/lib/handleNewBooking/getEventType.ts @@ -1,4 +1,5 @@ import { getDefaultEvent } from "@calcom/features/eventtypes/lib/defaultEvents"; +import { HttpError } from "@calcom/lib/http-error"; import { withReporting } from "@calcom/lib/sentryWrapper"; import { getBookingFieldsWithSystemFields } from "../getBookingFields"; @@ -11,6 +12,10 @@ const _getEventType = async ({ eventTypeId: number; eventTypeSlug?: string; }) => { + if (!eventTypeId && !eventTypeSlug) { + throw new HttpError({ statusCode: 400, message: "Either eventTypeId or eventTypeSlug must be provided" }); + } + // handle dynamic user const eventType = !eventTypeId && !!eventTypeSlug ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId); diff --git a/packages/features/eventtypes/lib/schemas.ts b/packages/features/eventtypes/lib/schemas.ts index 30b467797d5fa2..6aa18a8d59ac26 100644 --- a/packages/features/eventtypes/lib/schemas.ts +++ b/packages/features/eventtypes/lib/schemas.ts @@ -1,7 +1,8 @@ import { z } from "zod"; +import { eventTypeLocations, eventTypeSlug } from "@calcom/lib/zod/eventType"; import { SchedulingType } from "@calcom/prisma/enums"; -import { eventTypeLocations, EventTypeMetaDataSchema, eventTypeSlug } from "@calcom/prisma/zod-utils"; +import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; type CalVideoSettings = | { diff --git a/packages/lib/package.json b/packages/lib/package.json index 5dd524dd0b821f..231b91cfbdd81a 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -30,6 +30,7 @@ "uuid": "8.3.2" }, "devDependencies": { + "@biomejs/biome": "2.3.10", "@calcom/tsconfig": "workspace:*", "@calcom/types": "workspace:*", "@faker-js/faker": "7.6.0", diff --git a/packages/lib/zod/eventType.ts b/packages/lib/zod/eventType.ts new file mode 100644 index 00000000000000..5d6c134c256276 --- /dev/null +++ b/packages/lib/zod/eventType.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; + +import slugify from "../slugify"; + +/** + * Type definition for event type location + * Moved from @calcom/prisma/zod-utils to avoid prisma imports in non-repository code + */ +export type EventTypeLocation = { + type: string; + address?: string; + link?: string; + displayLocationPublicly?: boolean; + hostPhoneNumber?: string; + credentialId?: number; + teamName?: string; + customLabel?: string; +}; + +/** + * Schema for event type locations + * Validates an array of location objects for event types + */ +export const eventTypeLocations: z.ZodType = z.array( + z.object({ + // TODO: Couldn't find a way to make it a union of types from App Store locations + // Creating a dynamic union by iterating over the object doesn't seem to make TS happy + type: z.string(), + address: z.string().optional(), + link: z.string().url().optional(), + displayLocationPublicly: z.boolean().optional(), + hostPhoneNumber: z.string().optional(), + credentialId: z.number().optional(), + teamName: z.string().optional(), + customLabel: z.string().optional(), + }) +); + +/** + * Schema for event type slug + * Transforms and validates slugs for event types + */ +export const eventTypeSlug = z + .string() + .trim() + .transform((val) => slugify(val)) + .refine((val) => val.length >= 1, { + message: "Please enter at least one character", + }); diff --git a/packages/platform/examples/base/package.json b/packages/platform/examples/base/package.json index 7df89d7abdd7de..1ebb7e027f97ec 100644 --- a/packages/platform/examples/base/package.json +++ b/packages/platform/examples/base/package.json @@ -7,7 +7,6 @@ "dev": "PORT=4321 next dev", "build": "next build", "start": "next start", - "lint": "biome lint .", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui" }, diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index d245a4304a865f..9dd03b80d581d3 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -32,35 +32,6 @@ const emailRegexSchema = z .max(MAX_EMAIL_LENGTH, { message: "Email address is too long" }) .regex(emailRegex); -const slugify = (str: string, forDisplayingInput?: boolean) => { - if (!str) { - return ""; - } - - const s = str - .toLowerCase() // Convert to lowercase - .trim() // Remove whitespace from both sides - .normalize("NFD") // Normalize to decomposed form for handling accents - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .replace(/\p{Diacritic}/gu, "") // Remove any diacritics (accents) from characters - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .replace(/[^.\p{L}\p{N}\p{Zs}\p{Emoji}]+/gu, "-") // Replace any non-alphanumeric characters (including Unicode and except "." period) with a dash - .replace(/[\s_#]+/g, "-") // Replace whitespace, # and underscores with a single dash - .replace(/^-+/, "") // Remove dashes from start - .replace(/\.{2,}/g, ".") // Replace consecutive periods with a single period - .replace(/^\.+/, "") // Remove periods from the start - .replace( - /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, - "" - ) // Removes emojis - .replace(/\s+/g, " ") - .replace(/-+/g, "-"); // Replace consecutive dashes with a single dash - - return forDisplayingInput ? s : s.replace(/-+$/, "").replace(/\.*$/, ""); // Remove dashes and period from end -}; - const getValidRhfFieldName = (fieldName: string) => { // Remember that any transformation that you do here would run on System Field names as well. So, be careful and avoiding doing anything here that would modify the SystemField names. // e.g. SystemField name currently have uppercases in them. So, no need to lowercase unless absolutely needed. @@ -326,31 +297,8 @@ export const bookingResponses = z export type BookingResponses = z.infer; -export type EventTypeLocation = { - type: string; - address?: string; - link?: string; - displayLocationPublicly?: boolean; - hostPhoneNumber?: string; - credentialId?: number; - teamName?: string; - customLabel?: string; -}; - -export const eventTypeLocations: z.ZodType = z.array( - z.object({ - // TODO: Couldn't find a way to make it a union of types from App Store locations - // Creating a dynamic union by iterating over the object doesn't seem to make TS happy - type: z.string(), - address: z.string().optional(), - link: z.string().url().optional(), - displayLocationPublicly: z.boolean().optional(), - hostPhoneNumber: z.string().optional(), - credentialId: z.number().optional(), - teamName: z.string().optional(), - customLabel: z.string().optional(), - }) -); +// Re-exported from @calcom/lib/zod/eventType for backwards compatibility +export { eventTypeLocations, type EventTypeLocation } from "@calcom/lib/zod/eventType"; // Matching RRule.Options: rrule/dist/esm/src/types.d.ts export const recurringEventType = z @@ -387,13 +335,8 @@ export const eventTypeColor = z export type IntervalLimitsType = IntervalLimit | null; -export const eventTypeSlug = z - .string() - .trim() - .transform((val) => slugify(val)) - .refine((val) => val.length >= 1, { - message: "Please enter at least one character", - }); +// Re-exported from @calcom/lib/zod/eventType for backwards compatibility +export { eventTypeSlug } from "@calcom/lib/zod/eventType"; export const stringToDate = z.string().transform((a) => new Date(a)); diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 1c0f8c7277675c..8d1aa7664fea10 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -30,6 +30,7 @@ "react-dom": "^18.0.0 || ^19.0.0" }, "devDependencies": { + "@biomejs/biome": "2.3.10", "@tanstack/react-query": "5.17.19", "@types/cookie": "0.6.0", "@types/uuid": "8.3.4", diff --git a/packages/trpc/server/routers/viewer/organizations/intentToCreateOrg.handler.test.ts b/packages/trpc/server/routers/viewer/organizations/intentToCreateOrg.handler.test.ts index f1d5834c5a319c..6e2621f7c6cb40 100644 --- a/packages/trpc/server/routers/viewer/organizations/intentToCreateOrg.handler.test.ts +++ b/packages/trpc/server/routers/viewer/organizations/intentToCreateOrg.handler.test.ts @@ -1,14 +1,14 @@ import prismock from "../../../../../../tests/libs/__mocks__/prisma"; - -import { describe, expect, it, vi, beforeEach } from "vitest"; - +import { intentToCreateOrgHandler } from "./intentToCreateOrg.handler"; import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService"; import { OrganizationPaymentService } from "@calcom/features/ee/organizations/lib/OrganizationPaymentService"; -import { BillingPeriod, UserPermissionRole, CreationSource } from "@calcom/prisma/enums"; - +import { + BillingPeriod, + UserPermissionRole, + CreationSource, +} from "@calcom/prisma/enums"; import { TRPCError } from "@trpc/server"; - -import { intentToCreateOrgHandler } from "./intentToCreateOrg.handler"; +import { describe, expect, it, vi, beforeEach } from "vitest"; vi.mock("@calcom/ee/common/server/LicenseKeyService", () => ({ LicenseKeySingleton: { @@ -18,6 +18,65 @@ vi.mock("@calcom/ee/common/server/LicenseKeyService", () => ({ vi.mock("@calcom/features/ee/organizations/lib/OrganizationPaymentService"); +vi.mock("@calcom/features/ee/teams/repositories/TeamRepository", () => ({ + TeamRepository: class { + constructor() {} + findOwnedTeamsByUserId() { + return Promise.resolve([]); + } + findById() { + return Promise.resolve({ + id: 1, + name: "Test Org", + slug: "test-org", + logoUrl: null, + parentId: null, + metadata: {}, + isOrganization: true, + organizationSettings: null, + isPlatform: false, + }); + } + }, +})); + +vi.mock( + "@calcom/trpc/server/routers/viewer/organizations/createTeams.handler", + () => ({ + createTeamsHandler: vi.fn().mockResolvedValue({}), + }) +); + +vi.mock( + "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler", + () => ({ + inviteMembersWithNoInviterPermissionCheck: vi.fn().mockResolvedValue({}), + }) +); + +vi.mock("@calcom/lib/server/i18n", () => ({ + getTranslation: vi + .fn() + .mockImplementation(async (locale: string, namespace: string) => { + const t = (key: string) => key; + t.locale = locale; + t.namespace = namespace; + return t; + }), +})); + +vi.mock("@calcom/lib/domainManager/organization", () => ({ + createDomain: vi.fn().mockResolvedValue({}), +})); + +vi.mock("@calcom/emails/organization-email-service", () => ({ + sendOrganizationCreationEmail: vi.fn().mockResolvedValue({}), +})); + +vi.mock("@calcom/features/auth/lib/verifyEmail", () => ({ + sendEmailVerification: vi.fn().mockResolvedValue({}), +})); + const mockInput = { name: "Test Org", slug: "test-org", @@ -57,25 +116,27 @@ describe("intentToCreateOrgHandler", () => { vi.mocked(OrganizationPaymentService).mockImplementation(() => { return { - createOrganizationOnboarding: vi.fn().mockImplementation(async (data: any) => { - return await prismock.organizationOnboarding.create({ - data: { - id: "onboarding-123", - name: data.name, - slug: data.slug, - orgOwnerEmail: data.orgOwnerEmail, - seats: data.seats ?? 10, - pricePerSeat: data.pricePerSeat ?? 15, - billingPeriod: data.billingPeriod ?? BillingPeriod.MONTHLY, - isComplete: false, - stripeCustomerId: null, - createdById: data.createdByUserId, - teams: data.teams ?? [], - invitedMembers: data.invitedMembers ?? [], - isPlatform: data.isPlatform ?? false, - }, - }); - }), + createOrganizationOnboarding: vi + .fn() + .mockImplementation(async (data: any) => { + return await prismock.organizationOnboarding.create({ + data: { + id: "onboarding-123", + name: data.name, + slug: data.slug, + orgOwnerEmail: data.orgOwnerEmail, + seats: data.seats ?? 10, + pricePerSeat: data.pricePerSeat ?? 15, + billingPeriod: data.billingPeriod ?? BillingPeriod.MONTHLY, + isComplete: false, + stripeCustomerId: null, + createdById: data.createdByUserId, + teams: data.teams ?? [], + invitedMembers: data.invitedMembers ?? [], + isPlatform: data.isPlatform ?? false, + }, + }); + }), createPaymentIntent: vi.fn().mockResolvedValue({ checkoutUrl: "https://stripe.com/checkout/session", organizationOnboarding: {}, @@ -137,20 +198,25 @@ describe("intentToCreateOrgHandler", () => { organizationOnboardingId: expect.any(String), checkoutUrl: null, organizationId: null, // Not created yet - handover flow - handoverUrl: expect.stringContaining("/settings/organizations/new/resume?onboardingId="), + handoverUrl: expect.stringContaining( + "/settings/organizations/new/resume?onboardingId=" + ), }); // Verify organization onboarding was created - const organizationOnboarding = await prismock.organizationOnboarding.findFirst({ - where: { - slug: mockInput.slug, - }, - }); + const organizationOnboarding = + await prismock.organizationOnboarding.findFirst({ + where: { + slug: mockInput.slug, + }, + }); expect(organizationOnboarding).toBeDefined(); expect(organizationOnboarding?.name).toBe(mockInput.name); expect(organizationOnboarding?.slug).toBe(mockInput.slug); - expect(organizationOnboarding?.orgOwnerEmail).toBe(mockInput.orgOwnerEmail); + expect(organizationOnboarding?.orgOwnerEmail).toBe( + mockInput.orgOwnerEmail + ); }); it("should allow user to create org for themselves", async () => { @@ -185,7 +251,12 @@ describe("intentToCreateOrgHandler", () => { user: null as any, }, }) - ).rejects.toThrow(new TRPCError({ code: "UNAUTHORIZED", message: "You are not authorized." })); + ).rejects.toThrow( + new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized.", + }) + ); }); it("should throw forbidden error when non-admin tries to create org for another user", async () => { @@ -344,20 +415,25 @@ describe("intentToCreateOrgHandler", () => { organizationOnboardingId: expect.any(String), checkoutUrl: null, organizationId: null, // Not created yet - handover flow - handoverUrl: expect.stringContaining("/settings/organizations/new/resume?onboardingId="), + handoverUrl: expect.stringContaining( + "/settings/organizations/new/resume?onboardingId=" + ), }); // Verify organization onboarding was created - const organizationOnboarding = await prismock.organizationOnboarding.findFirst({ - where: { - slug: mockInput.slug, - }, - }); + const organizationOnboarding = + await prismock.organizationOnboarding.findFirst({ + where: { + slug: mockInput.slug, + }, + }); expect(organizationOnboarding).toBeDefined(); expect(organizationOnboarding?.name).toBe(mockInput.name); expect(organizationOnboarding?.slug).toBe(mockInput.slug); - expect(organizationOnboarding?.orgOwnerEmail).toBe(mockInput.orgOwnerEmail); + expect(organizationOnboarding?.orgOwnerEmail).toBe( + mockInput.orgOwnerEmail + ); }); it("should handle teams and invites in the request", async () => { @@ -397,15 +473,20 @@ describe("intentToCreateOrgHandler", () => { expect(result.organizationOnboardingId).toBeDefined(); - const organizationOnboarding = await prismock.organizationOnboarding.findFirst({ - where: { - slug: mockInput.slug, - }, - }); + const organizationOnboarding = + await prismock.organizationOnboarding.findFirst({ + where: { + slug: mockInput.slug, + }, + }); expect(organizationOnboarding).toBeDefined(); - expect(organizationOnboarding?.teams).toEqual(inputWithTeamsAndInvites.teams); - expect(organizationOnboarding?.invitedMembers).toEqual(inputWithTeamsAndInvites.invitedMembers); + expect(organizationOnboarding?.teams).toEqual( + inputWithTeamsAndInvites.teams + ); + expect(organizationOnboarding?.invitedMembers).toEqual( + inputWithTeamsAndInvites.invitedMembers + ); }); it("should preserve teamName, teamId, and role in invites payload", async () => { @@ -433,7 +514,12 @@ describe("intentToCreateOrgHandler", () => { ], invitedMembers: [ { email: "new@new.com", teamName: "new", teamId: -1, role: "ADMIN" }, - { email: "team@new.com", teamName: "team", teamId: -1, role: "ADMIN" }, + { + email: "team@new.com", + teamName: "team", + teamId: -1, + role: "ADMIN", + }, ], }; @@ -446,14 +532,17 @@ describe("intentToCreateOrgHandler", () => { expect(result.organizationOnboardingId).toBeDefined(); - const organizationOnboarding = await prismock.organizationOnboarding.findFirst({ - where: { - slug: mockInput.slug, - }, - }); + const organizationOnboarding = + await prismock.organizationOnboarding.findFirst({ + where: { + slug: mockInput.slug, + }, + }); expect(organizationOnboarding).toBeDefined(); - expect(organizationOnboarding?.teams).toEqual(inputWithTeamsAndInvites.teams); + expect(organizationOnboarding?.teams).toEqual( + inputWithTeamsAndInvites.teams + ); // Verify invitedMembers are stored with all fields including teamName, teamId, and role expect(organizationOnboarding?.invitedMembers).toBeDefined(); diff --git a/packages/ui/components/icon/icon-names.ts b/packages/ui/components/icon/icon-names.ts index c6a977ea4e9cca..d4798ce3bdeb8a 100644 --- a/packages/ui/components/icon/icon-names.ts +++ b/packages/ui/components/icon/icon-names.ts @@ -2,158 +2,158 @@ export type IconName = | "activity" - | "arrow-down" - | "arrow-left" - | "arrow-right" - | "arrow-up-right" - | "arrow-up" - | "asterisk" - | "at-sign" - | "atom" - | "badge-check" - | "ban" - | "bell" - | "binary" - | "blocks" - | "bold" - | "book-open-check" - | "book-open" - | "book-user" - | "book" - | "bookmark" - | "building" - | "calendar-check-2" - | "calendar-days" - | "calendar-heart" - | "calendar-range" - | "calendar-search" - | "calendar-x-2" - | "calendar" - | "chart-bar" - | "chart-line" - | "check-check" - | "check" - | "chevron-down" - | "chevron-left" - | "chevron-right" - | "chevron-up" - | "chevrons-down-up" - | "chevrons-left" - | "chevrons-right" - | "chevrons-up-down" - | "circle-alert" - | "circle-arrow-up" - | "circle-check-big" - | "circle-check" - | "circle-help" - | "circle-plus" - | "circle-x" - | "circle" - | "clipboard-check" - | "clipboard" - | "clock" - | "code" - | "columns-3" - | "command" - | "contact" - | "copy" - | "corner-down-left" - | "corner-down-right" - | "credit-card" - | "disc" - | "dot" - | "download" - | "ellipsis-vertical" - | "ellipsis" - | "external-link" - | "eye-off" - | "eye" - | "file-down" - | "file-text" - | "file" - | "filter" - | "fingerprint" - | "flag" - | "folder" - | "gift" - | "git-merge" - | "github" - | "globe" - | "grid-3x3" - | "handshake" - | "info" - | "italic" - | "key" - | "layers" - | "layout-dashboard" - | "link-2" - | "link" - | "list-filter" - | "loader" - | "lock-open" - | "lock" - | "log-out" - | "mail-open" - | "mail" - | "map-pin" - | "map" - | "menu" - | "message-circle" - | "messages-square" - | "mic-off" - | "mic" - | "monitor" - | "moon" - | "paintbrush" - | "paperclip" - | "pause" - | "pencil" - | "phone-call" - | "phone-incoming" - | "phone-off" - | "phone-outgoing" - | "phone" - | "play" - | "plus" - | "refresh-ccw" - | "refresh-cw" - | "repeat" - | "rocket" - | "rotate-ccw" - | "rotate-cw" - | "search" - | "send" - | "settings" - | "share-2" - | "shield-check" - | "shield" - | "shuffle" - | "sliders-horizontal" - | "sliders-vertical" - | "smartphone" - | "sparkles" - | "split" - | "square-check" - | "square-pen" - | "star" - | "sun" - | "sunrise" - | "sunset" - | "tags" - | "terminal" - | "trash-2" - | "trash" - | "trello" - | "triangle-alert" - | "upload" - | "user-check" - | "user-plus" - | "user-x" - | "user" - | "users" - | "venetian-mask" - | "video" - | "waypoints" - | "webhook" - | "x" - | "zap"; + | "arrow-down" + | "arrow-left" + | "arrow-right" + | "arrow-up-right" + | "arrow-up" + | "asterisk" + | "at-sign" + | "atom" + | "badge-check" + | "ban" + | "bell" + | "binary" + | "blocks" + | "bold" + | "book-open-check" + | "book-open" + | "book-user" + | "book" + | "bookmark" + | "building" + | "calendar-check-2" + | "calendar-days" + | "calendar-heart" + | "calendar-range" + | "calendar-search" + | "calendar-x-2" + | "calendar" + | "chart-bar" + | "chart-line" + | "check-check" + | "check" + | "chevron-down" + | "chevron-left" + | "chevron-right" + | "chevron-up" + | "chevrons-down-up" + | "chevrons-left" + | "chevrons-right" + | "chevrons-up-down" + | "circle-alert" + | "circle-arrow-up" + | "circle-check-big" + | "circle-check" + | "circle-help" + | "circle-plus" + | "circle-x" + | "circle" + | "clipboard-check" + | "clipboard" + | "clock" + | "code" + | "columns-3" + | "command" + | "contact" + | "copy" + | "corner-down-left" + | "corner-down-right" + | "credit-card" + | "disc" + | "dot" + | "download" + | "ellipsis-vertical" + | "ellipsis" + | "external-link" + | "eye-off" + | "eye" + | "file-down" + | "file-text" + | "file" + | "filter" + | "fingerprint" + | "flag" + | "folder" + | "gift" + | "git-merge" + | "github" + | "globe" + | "grid-3x3" + | "handshake" + | "info" + | "italic" + | "key" + | "layers" + | "layout-dashboard" + | "link-2" + | "link" + | "list-filter" + | "loader" + | "lock-open" + | "lock" + | "log-out" + | "mail-open" + | "mail" + | "map-pin" + | "map" + | "menu" + | "message-circle" + | "messages-square" + | "mic-off" + | "mic" + | "monitor" + | "moon" + | "paintbrush" + | "paperclip" + | "pause" + | "pencil" + | "phone-call" + | "phone-incoming" + | "phone-off" + | "phone-outgoing" + | "phone" + | "play" + | "plus" + | "refresh-ccw" + | "refresh-cw" + | "repeat" + | "rocket" + | "rotate-ccw" + | "rotate-cw" + | "search" + | "send" + | "settings" + | "share-2" + | "shield-check" + | "shield" + | "shuffle" + | "sliders-horizontal" + | "sliders-vertical" + | "smartphone" + | "sparkles" + | "split" + | "square-check" + | "square-pen" + | "star" + | "sun" + | "sunrise" + | "sunset" + | "tags" + | "terminal" + | "trash-2" + | "trash" + | "trello" + | "triangle-alert" + | "upload" + | "user-check" + | "user-plus" + | "user-x" + | "user" + | "users" + | "venetian-mask" + | "video" + | "waypoints" + | "webhook" + | "x" + | "zap"; diff --git a/packages/ui/scripts/build-icons.mjs b/packages/ui/scripts/build-icons.mjs index 3142fd9427b98d..19f07730747fb4 100644 --- a/packages/ui/scripts/build-icons.mjs +++ b/packages/ui/scripts/build-icons.mjs @@ -32,13 +32,21 @@ async function generateIconFiles() { const spriteFilepath = path.join(outputDir, "sprite.svg"); const typeOutputFilepath = path.join(typesDir, "icon-names.ts"); - const currentSprite = await fsExtra.readFile(spriteFilepath, "utf8").catch(() => ""); - const currentTypes = await fsExtra.readFile(typeOutputFilepath, "utf8").catch(() => ""); + const currentSprite = await fsExtra + .readFile(spriteFilepath, "utf8") + .catch(() => ""); + const currentTypes = await fsExtra + .readFile(typeOutputFilepath, "utf8") + .catch(() => ""); const iconNames = files.map((file) => iconName(file)); - const spriteUpToDate = iconNames.every((name) => currentSprite.includes(`id=${name}`)); - const typesUpToDate = iconNames.every((name) => currentTypes.includes(`"${name}"`)); + const spriteUpToDate = iconNames.every((name) => + currentSprite.includes(`id=${name}`) + ); + const typesUpToDate = iconNames.every((name) => + currentTypes.includes(`"${name}"`) + ); if (spriteUpToDate && typesUpToDate) { logVerbose(`Icons are up to date`); @@ -63,9 +71,12 @@ async function generateIconFiles() { const typeOutputContent = `// This file is generated by yarn run build:icons export type IconName = -\t| ${stringifiedIconNames.join("\n\t| ")}; +\t| ${stringifiedIconNames.join("\n | ")}; `; - const typesChanged = await writeIfChanged(typeOutputFilepath, typeOutputContent); + const typesChanged = await writeIfChanged( + typeOutputFilepath, + typeOutputContent + ); logVerbose(`Manifest saved to ${path.relative(cwd, typeOutputFilepath)}`); @@ -119,7 +130,9 @@ async function generateSvgSprite({ files, inputDir, outputPath }) { } async function writeIfChanged(filepath, newContent) { - const currentContent = await fsExtra.readFile(filepath, "utf8").catch(() => ""); + const currentContent = await fsExtra + .readFile(filepath, "utf8") + .catch(() => ""); if (currentContent === newContent) return false; await fsExtra.writeFile(filepath, newContent, "utf8"); await $`node ${biomeBin} format --write ${filepath}`; diff --git a/yarn.lock b/yarn.lock index be3a74d4af88e1..268751c61d3124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1605,7 +1605,7 @@ __metadata: languageName: node linkType: hard -"@biomejs/biome@npm:^2.3.8": +"@biomejs/biome@npm:2.3.10, @biomejs/biome@npm:^2.3.8": version: 2.3.10 resolution: "@biomejs/biome@npm:2.3.10" dependencies: @@ -2376,6 +2376,7 @@ __metadata: version: 0.0.0-use.local resolution: "@calcom/embed-core@workspace:packages/embeds/embed-core" dependencies: + "@biomejs/biome": "npm:2.3.10" "@playwright/test": "npm:1.57.0" "@tailwindcss/cli": "npm:4.1.16" "@tailwindcss/postcss": "npm:4.1.15" @@ -2767,6 +2768,7 @@ __metadata: version: 0.0.0-use.local resolution: "@calcom/lib@workspace:packages/lib" dependencies: + "@biomejs/biome": "npm:2.3.10" "@calcom/config": "workspace:*" "@calcom/dayjs": "workspace:*" "@calcom/tsconfig": "workspace:*" @@ -3251,6 +3253,7 @@ __metadata: version: 0.0.0-use.local resolution: "@calcom/trpc@workspace:packages/trpc" dependencies: + "@biomejs/biome": "npm:2.3.10" "@tanstack/react-query": "npm:5.17.19" "@trpc/client": "npm:11.0.0-next-beta.222" "@trpc/next": "npm:11.0.0-next-beta.222" @@ -3403,6 +3406,7 @@ __metadata: resolution: "@calcom/web@workspace:apps/web" dependencies: "@babel/core": "npm:7.26.10" + "@biomejs/biome": "npm:2.3.10" "@boxyhq/saml-jackson": "npm:1.52.2" "@calcom/app-store": "workspace:*" "@calcom/app-store-cli": "workspace:*" @@ -3980,6 +3984,7 @@ __metadata: resolution: "@coss/ui@workspace:packages/coss-ui" dependencies: "@base-ui/react": "npm:1.0.0" + "@biomejs/biome": "npm:2.3.10" class-variance-authority: "npm:0.7.1" clsx: "npm:2.1.1" lucide-react: "npm:0.555.0" @@ -18758,7 +18763,7 @@ __metadata: version: 0.0.0-use.local resolution: "calcom-monorepo@workspace:." dependencies: - "@biomejs/biome": "npm:^2.3.8" + "@biomejs/biome": "npm:2.3.10" "@changesets/changelog-github": "npm:0.5.1" "@changesets/cli": "npm:2.29.4" "@faker-js/faker": "npm:9.2.0"