diff --git a/apps/web/app/error.tsx b/apps/web/app/error.tsx index 0dd34475ec..b804d677ac 100644 --- a/apps/web/app/error.tsx +++ b/apps/web/app/error.tsx @@ -1,59 +1,64 @@ "use client"; -import { captureException } from "@sentry/nextjs"; +/** + * Typescript class based component for custom-error + * @link https://nextjs.org/docs/advanced-features/custom-error-page + */ +import type { NextPage } from "next"; +import type { ErrorProps } from "next/error"; import React from "react"; -import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { redactError } from "@calcom/lib/redactError"; import { ErrorPage } from "@components/error/error-page"; -const log = logger.getSubLogger({ prefix: ["[error]"] }); +type NextError = Error & { digest?: string }; -export type ErrorProps = { - error: Error; - reset?: () => void; +// Ref: https://nextjs.org/docs/app/api-reference/file-conventions/error#props +export type DefaultErrorProps = { + error: NextError; + reset: () => void; // A function to reset the error boundary }; -export default function Error({ error }: ErrorProps) { - React.useEffect(() => { - log.error(error); +type AugmentedError = NextError | HttpError | null; - // Log the error to Sentry - captureException(error); - }, [error]); +type CustomErrorProps = { + err?: AugmentedError; + statusCode?: number; + message?: string; +} & Omit; - const processedError = React.useMemo(() => { - const err = getErrorFromUnknown(error); +const log = logger.getSubLogger({ prefix: ["[error]"] }); - if (err instanceof HttpError) { - const redactedError = redactError(err); - return { - statusCode: err.statusCode, - title: redactedError.name, - name: redactedError.name, - message: redactedError.message, - url: err.url, - method: err.method, - cause: err.cause, - }; - } +const CustomError: NextPage = (props) => { + const { error } = props; + let errorObject: CustomErrorProps = { + message: error.message, + err: error, + }; - return { - statusCode: 500, - title: "Internal Server Error", - name: "Internal Server Error", - message: "An unexpected error occurred.", + if (error instanceof HttpError) { + const redactedError = redactError(error); + errorObject = { + statusCode: error.statusCode, + title: redactedError.name, + message: redactedError.message, + err: { + ...redactedError, + ...error, + }, }; - }, [error]); + } + + // `error.digest` property contains an automatically generated hash of the error that can be used to match the corresponding error in server-side logs + log.debug(`${error?.toString() ?? JSON.stringify(error)}`); + log.info("errorObject: ", errorObject); return ( - + ); -} +}; + +export default CustomError; diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx index df022ffbb1..ecf7247dbb 100644 --- a/apps/web/app/global-error.tsx +++ b/apps/web/app/global-error.tsx @@ -2,10 +2,9 @@ import { type NextPage } from "next"; -import CustomError from "./error"; -import type { ErrorProps } from "./error"; +import CustomError, { type DefaultErrorProps } from "./error"; -export const GlobalError: NextPage = (props) => { +export const GlobalError: NextPage = (props) => { return ( diff --git a/apps/web/app/not-found.tsx b/apps/web/app/not-found.tsx index 95a9a8c97e..a9a4bb5c92 100644 --- a/apps/web/app/not-found.tsx +++ b/apps/web/app/not-found.tsx @@ -1,246 +1,12 @@ -import { _generateMetadata, getTranslate } from "app/_utils"; -import { WithLayout } from "app/layoutHOC"; -import { headers } from "next/headers"; -import Link from "next/link"; - -import { - getOrgDomainConfigFromHostname, - subdomainSuffix, -} from "@calcom/features/ee/organizations/lib/orgDomains"; -import { DOCS_URL, IS_CALCOM, WEBSITE_URL } from "@calcom/lib/constants"; -import { Icon } from "@calcom/ui"; - -enum PageType { - ORG = "ORG", - TEAM = "TEAM", - USER = "USER", - OTHER = "OTHER", -} - -function getPageInfo(pathname: string, host: string) { - const { isValidOrgDomain, currentOrgDomain } = getOrgDomainConfigFromHostname({ hostname: host }); - const [routerUsername] = pathname?.replace("%20", "-").split(/[?#]/) ?? []; - if (!routerUsername || (isValidOrgDomain && currentOrgDomain)) { - return { - username: currentOrgDomain ?? "", - pageType: PageType.ORG, - url: `${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${ - currentOrgDomain?.replace("/", "") ?? "" - }`, - }; - } - - const splitPath = routerUsername.split("/"); - if (splitPath[1] === "team" && splitPath.length === 3) { - return { - username: splitPath[2], - pageType: PageType.TEAM, - url: `${WEBSITE_URL}/signup?callbackUrl=settings/teams/new%3Fslug%3D${splitPath[2].replace("/", "")}`, - }; - } - - return { - username: routerUsername, - pageType: PageType.USER, - url: `${WEBSITE_URL}/signup?username=${routerUsername.replace("/", "")}`, - }; -} - -async function NotFound() { - const t = await getTranslate(); - const headersList = headers(); - const host = headersList.get("x-forwarded-host") ?? ""; - const pathname = headersList.get("x-pathname") ?? ""; - - // This makes more sense after full migration to App Router - // if (!pathname) { - // redirect("/500"); - // } - - const { username, pageType, url } = getPageInfo(pathname, host); - const isBookingSuccessPage = pathname?.startsWith("/booking"); - const isSubpage = pathname?.includes("/", 2) || isBookingSuccessPage; - const isInsights = pathname?.startsWith("/insights"); - - const links = [ - { - title: t("enterprise"), - description: "Learn more about organizations and subdomains in our enterprise plan.", - icon: "shield" as const, - href: `${WEBSITE_URL}/enterprise`, - }, - { - title: t("documentation"), - description: t("documentation_description"), - icon: "file-text" as const, - href: DOCS_URL, - }, - { - title: t("blog"), - description: t("blog_description"), - icon: "book-open" as const, - href: `${WEBSITE_URL}/blog`, - }, - ]; - - /** - * If we're on 404 and the route is insights it means it is disabled - * TODO: Abstract this for all disabled features - **/ - if (isInsights) { - return ( -
-
-
-

{t("error_404")}

-

- {t("feature_currently_disabled") ?? "Feature is currently disabled"} -

-
-
-
- - {t("or_go_back_home")} - - -
-
-
-
- ); - } +import React from "react"; +const NotFound = () => { return ( -
-
-
-

{t("error_404")}

-

- {isBookingSuccessPage ? "Booking not found" : t("page_doesnt_exist")} -

- {isSubpage && pageType !== PageType.TEAM ? ( - {t("check_spelling_mistakes_or_go_back")} - ) : IS_CALCOM ? ( - - {t(`404_the_${pageType.toLowerCase()}`)} - - {username ? ( - <> - {username} {t("is_still_available")}{" "} - {t("register_now")}. - - ) : null} - - ) : ( - - {t(`404_the_${pageType.toLowerCase()}`)}{" "} - {username ? ( - <> - {username}{" "} - {t("is_still_available")} - - ) : null} - - )} -
-
- {((!isSubpage && IS_CALCOM) || pageType === PageType.ORG || pageType === PageType.TEAM) && ( - - )} -

{t("popular_pages")}

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

404 - Page Not Found

+

Sorry, the page you are looking for does not exist.

); -} - -export const generateMetadata = async () => { - const headersList = headers(); - const pathname = headersList.get("x-pathname") ?? ""; - const isInsights = pathname?.startsWith("/insights"); - - const metadata = await _generateMetadata( - (t) => - isInsights - ? t("feature_currently_disabled") ?? "Feature is currently disabled" - : t("404_page_not_found"), - (t) => t("404_page_not_found") - ); - return { - ...metadata, - robots: { - index: false, - follow: false, - }, - }; }; -export default WithLayout({ - ServerPage: NotFound, -}); +export default NotFound;