diff --git a/apps/api/pages/api/teams/[teamId]/_patch.ts b/apps/api/pages/api/teams/[teamId]/_patch.ts index cdc11dfc7d..9bfbea3a50 100644 --- a/apps/api/pages/api/teams/[teamId]/_patch.ts +++ b/apps/api/pages/api/teams/[teamId]/_patch.ts @@ -74,7 +74,7 @@ export async function patchHandler(req: NextApiRequest) { }, }, }); - + if (slugAlreadyExists && data.slug !== _team.slug) throw new HttpError({ statusCode: 409, message: "Team slug already exists" }); diff --git a/apps/web/app/not-found.tsx b/apps/web/app/not-found.tsx new file mode 100644 index 0000000000..3468198ca6 --- /dev/null +++ b/apps/web/app/not-found.tsx @@ -0,0 +1,17 @@ +import NotFoundPage from "@pages/404"; +import { WithLayout } from "app/layoutHOC"; +import type { GetStaticPropsContext } from "next"; + +import { ssgInit } from "@server/lib/ssg"; + +const getData = async (context: GetStaticPropsContext) => { + const ssg = await ssgInit(context); + + return { + dehydratedState: ssg.dehydrate(), + }; +}; + +export const dynamic = "force-static"; + +export default WithLayout({ getLayout: null, getData, Page: NotFoundPage }); diff --git a/apps/web/lib/app-providers-app-dir.tsx b/apps/web/lib/app-providers-app-dir.tsx index 0b6c8f009c..006145761a 100644 --- a/apps/web/lib/app-providers-app-dir.tsx +++ b/apps/web/lib/app-providers-app-dir.tsx @@ -62,8 +62,12 @@ const CustomI18nextProvider = (props: { children: React.ReactElement; i18n?: SSR // @TODO const session = useSession(); + + // window.document.documentElement.lang can be empty in some cases, for instance when we rendering GlobalError (not-found) page. const locale = - session?.data?.user.locale ?? typeof window !== "undefined" ? window.document.documentElement.lang : "en"; + session?.data?.user.locale ?? typeof window !== "undefined" + ? window.document.documentElement.lang || "en" + : "en"; useEffect(() => { try { diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx index 2410f6c4d4..a08b01b8af 100644 --- a/apps/web/pages/404.tsx +++ b/apps/web/pages/404.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetStaticPropsContext } from "next"; import Link from "next/link"; import { usePathname } from "next/navigation"; diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts index af0c69bca8..17aaa4caee 100644 --- a/apps/web/playwright/booking-seats.e2e.ts +++ b/apps/web/playwright/booking-seats.e2e.ts @@ -435,7 +435,7 @@ test.describe("Reschedule for booking with seats", () => { await page.locator('[data-testid="confirm_cancel"]').click(); - await page.waitForLoadState("networkidle"); + await page.waitForResponse((res) => res.url().includes("api/cancel") && res.status() === 200); const oldBooking = await prisma.booking.findFirst({ where: { uid: booking.uid }, diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index 2de5bca29f..1bccb632d9 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -360,5 +360,5 @@ export async function doOnOrgDomain( } // When App directory is there, this is the 404 page text. We should work on fixing the 404 page as it changed due to app directory. -export const NotFoundPageText = "This page could not be found"; +export const NotFoundPageTextAppDir = "This page does not exist."; // export const NotFoundPageText = "ERROR 404"; diff --git a/apps/web/playwright/teams.e2e.ts b/apps/web/playwright/teams.e2e.ts index dce93d42b9..558bfb4e20 100644 --- a/apps/web/playwright/teams.e2e.ts +++ b/apps/web/playwright/teams.e2e.ts @@ -6,7 +6,7 @@ import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { test } from "./lib/fixtures"; import { - NotFoundPageText, + NotFoundPageTextAppDir, bookTimeSlot, doOnOrgDomain, fillStripeTestCheckout, @@ -40,7 +40,7 @@ test.describe("Teams A/B tests", () => { expect(dataNextJsRouter).toEqual("app"); - const locator = page.getByRole("heading", { name: "teams" }); + const locator = page.getByRole("heading", { name: "Teams", exact: true }); await expect(locator).toBeVisible(); }); @@ -389,7 +389,7 @@ test.describe("Teams - Org", () => { await page.goto(`/team/${team.slug}/${teamEventSlug}`); - await expect(page.locator(`text=${NotFoundPageText}`)).toBeVisible(); + await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible(); await doOnOrgDomain( { orgSlug: org.slug, diff --git a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts index 7ee5e15737..24933bbc57 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -3,7 +3,7 @@ import { expect } from "@playwright/test"; import type { Fixtures } from "@calcom/web/playwright/lib/fixtures"; import { test } from "@calcom/web/playwright/lib/fixtures"; -import { NotFoundPageText, gotoRoutingLink } from "@calcom/web/playwright/lib/testUtils"; +import { NotFoundPageTextAppDir, gotoRoutingLink } from "@calcom/web/playwright/lib/testUtils"; import { addForm, @@ -36,7 +36,7 @@ test.describe("Routing Forms", () => { await page.goto(`apps/routing-forms/route-builder/${formId}`); await disableForm(page); await gotoRoutingLink({ page, formId }); - await expect(page.locator(`text=${NotFoundPageText}`)).toBeVisible(); + await expect(page.locator(`text=${NotFoundPageTextAppDir}`)).toBeVisible(); }); test("should be able to edit the form", async ({ page }) => { diff --git a/packages/ui/components/errorBoundary/ErrorBoundary.tsx b/packages/ui/components/errorBoundary/ErrorBoundary.tsx index a1523ae329..f6c7f8e652 100644 --- a/packages/ui/components/errorBoundary/ErrorBoundary.tsx +++ b/packages/ui/components/errorBoundary/ErrorBoundary.tsx @@ -17,6 +17,15 @@ class ErrorBoundary extends React.Component< } render() { + // do not intercept next-not-found error, allow displaying not-found.tsx page when notFound() is thrown on server side + if ( + this.state.error !== null && + "digest" in this.state.error && + this.state.error.digest === "NEXT_NOT_FOUND" + ) { + return this.props.children; + } + if (this.state.errorInfo) { // Error path return (