Update trust webapp to work in both mode

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-10-08 20:37:21 +02:00
parent 65b7d90d61
commit a3e3c0bdda
11 changed files with 1139 additions and 62 deletions

View File

@@ -1,6 +1,6 @@
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import type { TrustGraphQuery } from "/queries/__generated__/TrustGraphQuery.graphql.ts";
import { trustGraphQuery } from "/queries/TrustGraph.ts";
import type { TrustGraphCurrentQuery } from "/queries/__generated__/TrustGraphCurrentQuery.graphql.ts";
import { currentTrustGraphQuery } from "/queries/TrustGraph.ts";
import { Logo, TabLink, Tabs } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { OrganizationSidebar } from "/components/OrganizationSidebar";
@@ -10,21 +10,17 @@ import { AuthProvider } from "/providers/AuthProvider";
import { TrustCenterProvider } from "/providers/TrustCenterProvider";
type Props = {
queryRef: PreloadedQuery<TrustGraphQuery>;
queryRef: PreloadedQuery<TrustGraphCurrentQuery>;
};
export function MainLayout(props: Props) {
const { __ } = useTranslate();
const trustCenter = usePreloadedQuery(
trustGraphQuery,
props.queryRef,
).trustCenterBySlug;
const data = usePreloadedQuery(currentTrustGraphQuery, props.queryRef);
const trustCenter = data.currentTrustCenter;
if (!trustCenter) {
return null;
}
const baseTabUrl = `/trust/${trustCenter.slug}`;
const showNDADialog =
trustCenter.isUserAuthenticated &&
!trustCenter.hasAcceptedNonDisclosureAgreement &&
@@ -44,11 +40,11 @@ export function MainLayout(props: Props) {
<OrganizationSidebar trustCenter={trustCenter} />
<main>
<Tabs className="mb-8">
<TabLink to={`${baseTabUrl}/overview`}>{__("Overview")}</TabLink>
<TabLink to={`${baseTabUrl}/documents`}>
<TabLink to="/overview">{__("Overview")}</TabLink>
<TabLink to="/documents">
{__("Documents")}
</TabLink>
<TabLink to={`${baseTabUrl}/subprocessors`}>
<TabLink to="/subprocessors">
{__("Subprocessors")}
</TabLink>
</Tabs>

View File

@@ -1,6 +1,6 @@
import { useTranslate } from "@probo/i18n";
import { useEffect, useState } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router";
import { useNavigate, useSearchParams } from "react-router";
import { buildEndpoint } from "/providers/RelayProviders";
import { PageError } from "/components/PageError";
import { Spinner } from "@probo/ui";
@@ -10,16 +10,12 @@ import { Spinner } from "@probo/ui";
*/
export function AccessPage() {
const { __ } = useTranslate();
const { slug } = useParams<{ slug: string }>();
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
const navigate = useNavigate();
const isValidRequest = !!(slug && token);
const isValidRequest = !!token;
const [error, setError] = useState<string | null>(() => {
if (!slug) {
return __("Invalid trust center");
}
if (!token) {
return __("Invalid access token");
}
@@ -57,7 +53,7 @@ export function AccessPage() {
})
.then((data) => {
if (data.success) {
navigate(`/trust/${slug}`);
navigate("/overview");
return;
}
throw new Error(data.message ?? __("Authentication failed"));
@@ -65,7 +61,7 @@ export function AccessPage() {
.catch((error) => {
setError(error.message);
});
}, [isValidRequest, slug, token, __, navigate]);
}, [isValidRequest, token, __, navigate]);
if (error) {
return <PageError error={error} />;

View File

@@ -1,6 +1,6 @@
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { trustDocumentsQuery } from "/queries/TrustGraph";
import type { TrustGraphDocumentsQuery } from "/queries/__generated__/TrustGraphDocumentsQuery.graphql.ts";
import { currentTrustDocumentsQuery } from "/queries/TrustGraph";
import type { TrustGraphCurrentDocumentsQuery } from "/queries/__generated__/TrustGraphCurrentDocumentsQuery.graphql.ts";
import { groupBy, objectEntries } from "@probo/helpers";
import { documentTypeLabel } from "/helpers/documents";
import { useTranslate } from "@probo/i18n";
@@ -10,16 +10,19 @@ import { Rows } from "/components/Rows.tsx";
import { RowHeader } from "/components/RowHeader.tsx";
type Props = {
queryRef: PreloadedQuery<TrustGraphDocumentsQuery>;
queryRef: PreloadedQuery<TrustGraphCurrentDocumentsQuery>;
};
export function DocumentsPage({ queryRef }: Props) {
const { __ } = useTranslate();
const data = usePreloadedQuery(trustDocumentsQuery, queryRef);
const data = usePreloadedQuery<TrustGraphCurrentDocumentsQuery>(
currentTrustDocumentsQuery,
queryRef
);
const documents =
data.trustCenterBySlug?.documents.edges.map((edge) => edge.node) ?? [];
data.currentTrustCenter?.documents.edges.map((edge) => edge.node) ?? [];
const documentsPerType = groupBy(documents, (document) =>
documentTypeLabel(document.documentType, __),
documentTypeLabel(document.documentType, __)
);
return (
<div>

View File

@@ -1,27 +1,27 @@
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { trustVendorsQuery } from "/queries/TrustGraph";
import { currentTrustVendorsQuery } from "/queries/TrustGraph";
import { sprintf } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import type { TrustGraphVendorsQuery } from "/queries/__generated__/TrustGraphVendorsQuery.graphql";
import type { TrustGraphCurrentVendorsQuery } from "/queries/__generated__/TrustGraphCurrentVendorsQuery.graphql";
import { VendorRow } from "/components/VendorRow";
import { Rows } from "/components/Rows.tsx";
type Props = {
queryRef: PreloadedQuery<TrustGraphVendorsQuery>;
queryRef: PreloadedQuery<TrustGraphCurrentVendorsQuery>;
};
export function SubprocessorsPage({ queryRef }: Props) {
const { __ } = useTranslate();
const data = usePreloadedQuery(trustVendorsQuery, queryRef);
const data = usePreloadedQuery(currentTrustVendorsQuery, queryRef);
const vendors =
data.trustCenterBySlug?.vendors.edges.map((edge) => edge.node) ?? [];
data.currentTrustCenter?.vendors.edges.map((edge) => edge.node) ?? [];
return (
<div>
<h2 className="font-medium mb-1">{__("Subprocessors")}</h2>
<p className="text-sm text-txt-secondary mb-4">
{sprintf(
__("Third-party subprocessors %s work with:"),
data.trustCenterBySlug?.organization.name ?? "",
data.currentTrustCenter?.organization.name ?? ""
)}
</p>
<Rows>

View File

@@ -86,10 +86,15 @@ const fetchRelay: FetchFunction = async (
requestInit.body = formData;
} else {
// Extract slug from URL if present for slug-based routing
const slugMatch = window.location.pathname.match(/^\/trust\/([^/]+)/);
const slug = slugMatch ? slugMatch[1] : null;
requestInit.headers = {
Accept:
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
"Content-Type": "application/json",
...(slug ? { "X-Trust-Slug": slug } : {}),
};
requestInit.body = JSON.stringify({
@@ -99,8 +104,11 @@ const fetchRelay: FetchFunction = async (
});
}
// Use relative API path to ensure it goes through the same routing context
// For /trust/slug/overview, this resolves to /trust/slug/api/trust/v1/graphql
// For custom domains at /overview, this resolves to /api/trust/v1/graphql
const response = await fetch(
buildEndpoint("/api/trust/v1/graphql"),
buildEndpoint("./api/trust/v1/graphql"),
requestInit,
);

View File

@@ -68,3 +68,73 @@ export const trustVendorsQuery = graphql`
}
}
`;
// Queries for custom domain (subdomain) approach
export const currentTrustGraphQuery = graphql`
query TrustGraphCurrentQuery {
currentTrustCenter {
id
slug
isUserAuthenticated
hasAcceptedNonDisclosureAgreement
ndaFileName
ndaFileUrl
organization {
name
description
websiteUrl
logoUrl
email
headquarterAddress
}
...OverviewPageFragment
audits(first: 50) {
edges {
node {
id
...AuditRowFragment
}
}
}
}
}
`;
export const currentTrustDocumentsQuery = graphql`
query TrustGraphCurrentDocumentsQuery {
currentTrustCenter {
id
organization {
name
}
documents(first: 50) {
edges {
node {
id
documentType
...DocumentRowFragment
}
}
}
}
}
`;
export const currentTrustVendorsQuery = graphql`
query TrustGraphCurrentVendorsQuery {
currentTrustCenter {
id
organization {
name
}
vendors(first: 50) {
edges {
node {
id
...VendorRowFragment
}
}
}
}
}
`;

View File

@@ -0,0 +1,229 @@
/**
* @generated SignedSource<<dcb43113e97ddd2db2834d5f81c3cd3d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
export type TrustGraphCurrentDocumentsQuery$variables = Record<PropertyKey, never>;
export type TrustGraphCurrentDocumentsQuery$data = {
readonly currentTrustCenter: {
readonly documents: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly documentType: DocumentType;
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"DocumentRowFragment">;
};
}>;
};
readonly id: string;
readonly organization: {
readonly name: string;
};
} | null | undefined;
};
export type TrustGraphCurrentDocumentsQuery = {
response: TrustGraphCurrentDocumentsQuery$data;
variables: TrustGraphCurrentDocumentsQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v2 = [
{
"kind": "Literal",
"name": "first",
"value": 50
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "TrustGraphCurrentDocumentsQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v3/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "DocumentRowFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documents(first:50)"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "TrustGraphCurrentDocumentsQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documents(first:50)"
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "f04b485bb7a2ff20ac409e123404a954",
"id": null,
"metadata": {},
"name": "TrustGraphCurrentDocumentsQuery",
"operationKind": "query",
"text": "query TrustGraphCurrentDocumentsQuery {\n currentTrustCenter {\n id\n organization {\n name\n id\n }\n documents(first: 50) {\n edges {\n node {\n id\n documentType\n ...DocumentRowFragment\n }\n }\n }\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n}\n"
}
};
})();
(node as any).hash = "73636da5c06a16813a57b92f30ab93d2";
export default node;

View File

@@ -0,0 +1,509 @@
/**
* @generated SignedSource<<1f034e5f6819d3db77f6aa02ec4e92c5>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type TrustGraphCurrentQuery$variables = Record<PropertyKey, never>;
export type TrustGraphCurrentQuery$data = {
readonly currentTrustCenter: {
readonly audits: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"AuditRowFragment">;
};
}>;
};
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly isUserAuthenticated: boolean;
readonly ndaFileName: string | null | undefined;
readonly ndaFileUrl: string | null | undefined;
readonly organization: {
readonly description: string | null | undefined;
readonly email: string | null | undefined;
readonly headquarterAddress: string | null | undefined;
readonly logoUrl: string | null | undefined;
readonly name: string;
readonly websiteUrl: string | null | undefined;
};
readonly slug: string;
readonly " $fragmentSpreads": FragmentRefs<"OverviewPageFragment">;
} | null | undefined;
};
export type TrustGraphCurrentQuery = {
response: TrustGraphCurrentQuery$data;
variables: TrustGraphCurrentQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "slug",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "isUserAuthenticated",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileName",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileUrl",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "websiteUrl",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "headquarterAddress",
"storageKey": null
},
v12 = [
{
"kind": "Literal",
"name": "first",
"value": 50
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "TrustGraphCurrentQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/)
],
"storageKey": null
},
{
"args": null,
"kind": "FragmentSpread",
"name": "OverviewPageFragment"
},
{
"alias": null,
"args": (v12/*: any*/),
"concreteType": "AuditConnection",
"kind": "LinkedField",
"name": "audits",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "AuditEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "AuditRowFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "audits(first:50)"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "TrustGraphCurrentQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v0/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 14
}
],
"concreteType": "TrustCenterReferenceConnection",
"kind": "LinkedField",
"name": "references",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterReferenceEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterReference",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v6/*: any*/),
(v9/*: any*/),
(v8/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "references(first:14)"
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 3
}
],
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Vendor",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "privacyPolicyUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "countries",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "vendors(first:3)"
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 5
}
],
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documents(first:5)"
},
{
"alias": null,
"args": (v12/*: any*/),
"concreteType": "AuditConnection",
"kind": "LinkedField",
"name": "audits",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "AuditEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v0/*: any*/),
(v6/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "audits(first:50)"
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "a12f51feed01678d5a39df58ef95ad29",
"id": null,
"metadata": {},
"name": "TrustGraphCurrentQuery",
"operationKind": "query",
"text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n }\n framework {\n id\n name\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
}
};
})();
(node as any).hash = "2fc7c45c2636a551c13d806f89e53c4a";
export default node;

View File

@@ -0,0 +1,240 @@
/**
* @generated SignedSource<<66c7f989b05136848efd4ec2d918a0bd>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type TrustGraphCurrentVendorsQuery$variables = Record<PropertyKey, never>;
export type TrustGraphCurrentVendorsQuery$data = {
readonly currentTrustCenter: {
readonly id: string;
readonly organization: {
readonly name: string;
};
readonly vendors: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"VendorRowFragment">;
};
}>;
};
} | null | undefined;
};
export type TrustGraphCurrentVendorsQuery = {
response: TrustGraphCurrentVendorsQuery$data;
variables: TrustGraphCurrentVendorsQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v2 = [
{
"kind": "Literal",
"name": "first",
"value": 50
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "TrustGraphCurrentVendorsQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Vendor",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "VendorRowFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "vendors(first:50)"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "TrustGraphCurrentVendorsQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "currentTrustCenter",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Vendor",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "websiteUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "privacyPolicyUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "countries",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "vendors(first:50)"
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "1ae723b3588f13e1233ab6eb4c18215e",
"id": null,
"metadata": {},
"name": "TrustGraphCurrentVendorsQuery",
"operationKind": "query",
"text": "query TrustGraphCurrentVendorsQuery {\n currentTrustCenter {\n id\n organization {\n name\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n ...VendorRowFragment\n }\n }\n }\n }\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
}
};
})();
(node as any).hash = "a02b52855582163623c28763fbaeb568";
export default node;

View File

@@ -16,9 +16,9 @@ import { useCleanup } from "./hooks/useDelayedEffect";
import { PageError } from "./components/PageError";
import { MainLayout } from "/layouts/MainLayout";
import {
trustGraphQuery,
trustDocumentsQuery,
trustVendorsQuery,
currentTrustGraphQuery,
currentTrustDocumentsQuery,
currentTrustVendorsQuery,
} from "/queries/TrustGraph";
import { OverviewPage } from "/pages/OverviewPage";
import { DocumentsPage } from "/pages/DocumentsPage";
@@ -50,49 +50,64 @@ function ErrorBoundary({ error: propsError }: { error?: string }) {
const routes = [
{
path: "/",
loader: async () => {
throw redirect("/overview");
},
Component: Fragment,
ErrorBoundary: ErrorBoundary,
},
// Custom domain routes (subdomain-based)
{
path: "/trust/:slug/access",
Component: AccessPage,
ErrorBoundary: ErrorBoundary,
},
{
path: "/trust/:slug",
queryLoader: ({ slug }) =>
loadQuery(relayEnvironment, trustGraphQuery, { slug: slug }),
path: "/overview",
queryLoader: () => loadQuery(relayEnvironment, currentTrustGraphQuery, {}),
Component: MainLayout,
fallback: MainSkeleton,
ErrorBoundary: ErrorBoundary,
children: [
{
path: "",
loader: ({ params }) => {
throw redirect(`/trust/${params.slug}/overview`);
},
},
{
path: "overview",
fallback: TabSkeleton,
Component: OverviewPage,
},
],
},
{
path: "/documents",
queryLoader: () => loadQuery(relayEnvironment, currentTrustGraphQuery, {}),
Component: MainLayout,
fallback: MainSkeleton,
ErrorBoundary: ErrorBoundary,
children: [
{
path: "documents",
path: "",
queryLoader: () =>
loadQuery(relayEnvironment, currentTrustDocumentsQuery, {}),
fallback: TabSkeleton,
Component: DocumentsPage,
queryLoader: ({ slug }) =>
loadQuery(relayEnvironment, trustDocumentsQuery, { slug: slug }),
},
{
path: "subprocessors",
fallback: TabSkeleton,
Component: SubprocessorsPage,
queryLoader: ({ slug }) =>
loadQuery(relayEnvironment, trustVendorsQuery, { slug: slug }),
},
],
},
{
path: "/subprocessors",
queryLoader: () => loadQuery(relayEnvironment, currentTrustGraphQuery, {}),
Component: MainLayout,
fallback: MainSkeleton,
ErrorBoundary: ErrorBoundary,
children: [
{
path: "",
queryLoader: () =>
loadQuery(relayEnvironment, currentTrustVendorsQuery, {}),
fallback: TabSkeleton,
Component: SubprocessorsPage,
},
],
},
{
path: "/access",
Component: AccessPage,
ErrorBoundary: ErrorBoundary,
},
// Fallback URL to the NotFound Page
{
path: "*",
@@ -150,4 +165,15 @@ function routeTransformer({
} as RouteObject;
}
export const router = createBrowserRouter(routes.map(routeTransformer));
// Detect basename from current URL path
// If URL starts with /trust/{slug}, extract that as the basename
// Otherwise, use "/" for custom domains
function getBasename(): string {
const path = window.location.pathname;
const trustMatch = path.match(/^\/trust\/[^/]+/);
return trustMatch ? trustMatch[0] : "/";
}
export const router = createBrowserRouter(routes.map(routeTransformer), {
basename: getBasename(),
});

View File

@@ -9,7 +9,7 @@ export default defineConfig({
build: {
assetsDir: "assets",
},
base: "/trust/",
base: "./",
server: {
port: 5174,
proxy: {