diff --git a/apps/trust/src/components/DocumentRow.tsx b/apps/trust/src/components/DocumentRow.tsx index edbf2c9bf..0a721499a 100644 --- a/apps/trust/src/components/DocumentRow.tsx +++ b/apps/trust/src/components/DocumentRow.tsx @@ -1,5 +1,6 @@ import { downloadFile, formatError } from "@probo/helpers"; import { useTranslate } from "@probo/i18n"; +import { UnAuthenticatedError } from "@probo/relay"; import { Button, IconArrowInbox, @@ -8,12 +9,12 @@ import { Spinner, useToast, } from "@probo/ui"; -import { use, useState } from "react"; +import { useState } from "react"; import { useFragment, useMutation } from "react-relay"; +import { useLocation, useNavigate } from "react-router"; import { graphql } from "relay-runtime"; import { useMutationWithToasts } from "#/hooks/useMutationWithToast"; -import { Viewer } from "#/providers/Viewer"; import type { DocumentRow_requestAccessMutation } from "./__generated__/DocumentRow_requestAccessMutation.graphql"; import type { DocumentRowDownloadMutation } from "./__generated__/DocumentRowDownloadMutation.graphql"; @@ -50,8 +51,9 @@ const documentRowFragment = graphql` export function DocumentRow(props: { document: DocumentRowFragment$key }) { const { __ } = useTranslate(); - const viewer = use(Viewer); const { toast } = useToast(); + const navigate = useNavigate(); + const location = useLocation(); const document = useFragment(documentRowFragment, props.document); const [hasRequested, setHasRequested] = useState( @@ -87,6 +89,13 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) { }); }, onError: (error) => { + if (error instanceof UnAuthenticatedError) { + const searchParams = new URLSearchParams([[ + "continue", window.location.origin + location.pathname + location.search, + ]]); + void navigate(`/connect?${searchParams.toString()}`); + } + toast({ title: __("Error"), description: error.message ?? __("Cannot request access"), @@ -127,28 +136,17 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) { {downloading ? __("Downloading") : __("Download")} ) - : viewer - ? ( - - ) - : ( - - )} + : ( + + )} ); } diff --git a/apps/trust/src/components/RootErrorBoundary.tsx b/apps/trust/src/components/RootErrorBoundary.tsx new file mode 100644 index 000000000..d1b2013a0 --- /dev/null +++ b/apps/trust/src/components/RootErrorBoundary.tsx @@ -0,0 +1,41 @@ +import { NDASignatureRequiredError, UnAuthenticatedError } from "@probo/relay"; +import { Navigate, useLocation, useRouteError } from "react-router"; + +import { getPathPrefix } from "#/utils/pathPrefix"; + +import { PageError } from "./PageError"; + +export function RootErrorBoundary() { + const error = useRouteError(); + const location = useLocation(); + + const search = new URLSearchParams(); + + if (location.pathname !== getPathPrefix() || location.search !== "") { + search.set("continue", window.location.href); + } + + const queryString = search.toString(); + + if (error instanceof UnAuthenticatedError) { + return ( + + ); + } + + if (error instanceof NDASignatureRequiredError) { + return ( + + ); + } + + return ; +} diff --git a/apps/trust/src/pages/NDAPage.tsx b/apps/trust/src/pages/NDAPage.tsx index 4bc0703be..a4b6c30e3 100644 --- a/apps/trust/src/pages/NDAPage.tsx +++ b/apps/trust/src/pages/NDAPage.tsx @@ -8,13 +8,14 @@ import { usePreloadedQuery, useRefetchableFragment, } from "react-relay"; -import { Navigate, useNavigate } from "react-router"; +import { Navigate, useSearchParams } from "react-router"; import { graphql } from "relay-runtime"; import { useWindowSize } from "usehooks-ts"; import { z } from "zod"; import { PDFPreview } from "#/components/PDFPreview"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; +import { getPathPrefix } from "#/utils/pathPrefix"; import type { NDAPageAcceptElectronicSignatureMutation } from "./__generated__/NDAPageAcceptElectronicSignatureMutation.graphql"; import type { NDAPageFragment$key } from "./__generated__/NDAPageFragment.graphql"; @@ -88,8 +89,11 @@ export function NDAPage(props: { queryRef: PreloadedQuery; }) { const { __ } = useTranslate(); - const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const documentViewedRef = useRef(false); + const { width } = useWindowSize(); + const isMobile = width < 1100; + const isDesktop = !isMobile; const queryData = usePreloadedQuery(ndaPageQuery, props.queryRef); const trustCenter = queryData.currentTrustCenter; @@ -101,19 +105,14 @@ export function NDAPage(props: { ); const ndaSignature = data.nonDisclosureAgreement.viewerSignature; - const { width } = useWindowSize(); - const isMobile = width < 1100; - const isDesktop = !isMobile; - - const { - handleSubmit: handleSubmitWrapper, - register, - formState, - } = useFormWithSchema(schema, { - defaultValues: { - fullName: viewer?.fullName, - }, - }); + const continueUrlParam = searchParams.get("continue"); + let safeContinueUrl: string; + if (continueUrlParam) { + const continueUrl = new URL(continueUrlParam); + safeContinueUrl = window.location.origin + continueUrl.pathname + continueUrl.search; + } else { + safeContinueUrl = window.location.origin + getPathPrefix(); + } const [acceptSignature, isAccepting] = useMutation( acceptElectronicSignatureMutation, @@ -130,11 +129,21 @@ export function NDAPage(props: { const isFailed = ndaSignature?.status === "FAILED"; const isCompleted = ndaSignature?.status === "COMPLETED"; + const { + handleSubmit: handleSubmitWrapper, + register, + formState, + } = useFormWithSchema(schema, { + defaultValues: { + fullName: viewer?.fullName, + }, + }); + useEffect(() => { if (isCompleted) { - void navigate("/overview", { replace: true }); + window.location.href = safeContinueUrl; } - }, [isCompleted, navigate]); + }, [isCompleted, safeContinueUrl]); useEffect(() => { if (!isProcessing) return; diff --git a/apps/trust/src/providers/RelayProviders.tsx b/apps/trust/src/providers/RelayProviders.tsx index 7283d8008..7ffae5d3c 100644 --- a/apps/trust/src/providers/RelayProviders.tsx +++ b/apps/trust/src/providers/RelayProviders.tsx @@ -70,7 +70,7 @@ const store = new Store(source, { }); export const consoleEnvironment = new Environment({ - configName: "trust", + configName: "compliance-page", network: Network.create(makeFetchQuery(buildEndpoint())), store, }); diff --git a/apps/trust/src/routes.tsx b/apps/trust/src/routes.tsx index 4595adf58..0401cec4d 100644 --- a/apps/trust/src/routes.tsx +++ b/apps/trust/src/routes.tsx @@ -7,7 +7,7 @@ import { } from "@probo/routes"; import { Fragment } from "react"; import { loadQuery } from "react-relay"; -import { createBrowserRouter, redirect, useRouteError } from "react-router"; +import { createBrowserRouter, redirect } from "react-router"; import { MainLayout } from "#/layouts/MainLayout"; import { DocumentsPage } from "#/pages/DocumentsPage"; @@ -20,19 +20,11 @@ import { } from "#/queries/TrustGraph"; import { PageError } from "./components/PageError"; +import { RootErrorBoundary } from "./components/RootErrorBoundary"; import { MainSkeleton } from "./components/Skeletons/MainSkeleton"; import { TabSkeleton } from "./components/Skeletons/TabSkeleton"; import { consoleEnvironment } from "./providers/RelayProviders"; -/** - * Top level error boundary - */ -function ErrorBoundary() { - const error = useRouteError(); - - return ; -} - const routes = [ { Component: lazy(() => import("#/pages/auth/AuthLayoutLoader")), @@ -54,12 +46,12 @@ const routes = [ throw redirect("/overview"); }, Component: Fragment, - ErrorBoundary: ErrorBoundary, + ErrorBoundary: RootErrorBoundary, }, { path: "/nda", Component: lazy(() => import("#/pages/NDAPageLoader")), - ErrorBoundary: ErrorBoundary, + ErrorBoundary: RootErrorBoundary, }, // Custom domain routes (subdomain-based) { @@ -69,7 +61,7 @@ const routes = [ ), Component: withQueryRef(MainLayout), Fallback: MainSkeleton, - ErrorBoundary: ErrorBoundary, + ErrorBoundary: RootErrorBoundary, children: [ { path: "", @@ -85,7 +77,7 @@ const routes = [ ), Component: withQueryRef(MainLayout), Fallback: MainSkeleton, - ErrorBoundary: ErrorBoundary, + ErrorBoundary: RootErrorBoundary, children: [ { path: "", @@ -104,7 +96,7 @@ const routes = [ ), Component: withQueryRef(MainLayout), Fallback: MainSkeleton, - ErrorBoundary: ErrorBoundary, + ErrorBoundary: RootErrorBoundary, children: [ { path: "", diff --git a/packages/relay/src/errors.ts b/packages/relay/src/errors.ts index dadf7b36a..ee15363a5 100644 --- a/packages/relay/src/errors.ts +++ b/packages/relay/src/errors.ts @@ -6,6 +6,14 @@ export class UnAuthenticatedError extends Error { } } +export class NDASignatureRequiredError extends Error { + constructor(message?: string) { + super(message || "NDA_SIGNATURE_REQUIRED"); + this.name = "NDASignatureRequiredError"; + Object.setPrototypeOf(this, NDASignatureRequiredError.prototype); + } +} + export class InternalServerError extends Error { constructor() { super("INTERNAL_SERVER_ERROR");