Skip to content

Compliance portal document pages#1491

Open
codenem wants to merge 5 commits into
mainfrom
compliance-portal-document-pages
Open

Compliance portal document pages#1491
codenem wants to merge 5 commits into
mainfrom
compliance-portal-document-pages

Conversation

@codenem

@codenem codenem commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Build the Trust Center Documents page with All/Public/Private tabs, grouped sections, and inline access actions. Adds a visibility filter to the trust v1 GraphQL API and ships v2 Tabs and Toaster (mounted app‑wide), plus follow‑ups for preload/tab sync, shared export helpers, and small UI polish.

Coredata +10 -0

  • Add WithTrustCenterVisibilities(...) to DocumentFilter and AuditFilter to request specific visibility slices.

GraphQL API +41 -11

  • Add TrustCenterVisibility enum and TrustCenterVisibilityFilter input.
  • Add optional filter to TrustCenter.documents, audits, and trustCenterFiles; default to public+private when omitted.

Service +14 -8

  • Allow Documents.ListForOrganizationId and Audits.ListForOrganizationId to accept an optional filter (defaulting to trust‑center filters when nil).
  • Update compliance_page_service call sites to pass nil for unchanged behavior.

Package: ui +527 -7

  • Add v2 Tabs on @base-ui/react/tabs (Tabs, TabsList, TabsTab, TabsIndicator, TabsSkeleton) and v2 Toaster on @base-ui/react/toast with per‑type icons and stories.
  • Polish: Toaster close gets focus ring and wraps content; TabsSkeleton fixes prop spread/aria handling.

Other +1228 -35

  • Add documents route (/documents) with loader and skeleton; localize page/UI strings (en/fr).
  • Build documents page: unify documents, audit reports, and uploaded files; group by type/category with section headers.
  • Toolbar: All/Public/Private tabs persisted in URL; reconcile against preload variables and refetch on change; dim results while loading; extract shared documentsLayout tv variant for page and skeleton.
  • Implement export‑and‑view flows via a shared useExportAndOpen hook; show “View”, “Access requested”, or inert “Get Access”.
  • Mount global toaster (Toast.Provider + <Toaster />) in the app root; add common.error i18n keys.
  • Header band: add flushBottomSpace to align tab bars; wire new documentRoutes and remove the old placeholder route.
  • Subprocessors: set a 64px min‑height on the toolbar and make the band flush; skeleton uses SelectSkeleton/TextFieldSkeleton; reuse shared groupBy helper.

Written for commit 99990d6. Summary will update on new commits.

Review in cubic

codenem added 3 commits July 15, 2026 19:26
Build the Trust Center documents page: a unified list of published
documents, uploaded files, and audit reports, grouped into category
sections. An All/Public/Private tab bar filters the list by trust
center visibility.

Expose that filter over the trust v1 API by adding a
TrustCenterVisibility enum and a shared TrustCenterVisibilityFilter
input, wiring it through the documents, audits, and trustCenterFiles
connections down to the existing coredata SQL filters. "All" keeps the
default public+private slice; the other tabs pin a single visibility.

Access controls are display-only for now (auth is handled separately):
authorized or public entries open their exported PDF via the export
mutations, requested entries show a pending state, and everything else
shows an inert Get Access affordance.

Add the v2 Tabs and Toaster kit components (Base UI headless) needed by
the page and mount a toast provider at the app root for mutation
feedback.

Signed-off-by: Émile Ré <emile@probo.com>
Replace the minimal placeholder Toaster with the richer implementation
already built on compliance-portal-auth (per-type icons plus warning and
info variants, and its stories). Both branches now share the exact same
component and app-root wiring, so they converge instead of colliding
when they merge. Align App.tsx provider ordering to match.

Signed-off-by: Émile Ré <emile@probo.com>
The results container hand-wrote its layout classes and toggled the
dimmed state with a string-interpolated conditional. Move both the page
shell and the busy/dimmed state into a documentsLayout tv variant, and
reuse it in the skeleton so the loading and loaded layouts share one
source of truth.

Signed-off-by: Émile Ré <emile@probo.com>
@codenem codenem force-pushed the compliance-portal-document-pages branch from 8d69040 to bce7535 Compare July 15, 2026 17:26
Give the toolbar a 64px min-height so its controls are vertically
centered, and make the header band flush at the bottom. This widens the
gap above the toolbar and trims the oversized space below it to match
the Figma hero. Mirror the header in the skeleton so it does not jump
when data loads.

Signed-off-by: Émile Ré <emile@probo.com>
@codenem codenem marked this pull request as ready for review July 15, 2026 17:31

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 46 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/compliance-portal/src/pages/documents/DocumentsPageLoader.tsx">

<violation number="1" location="apps/compliance-portal/src/pages/documents/DocumentsPageLoader.tsx:36">
P2: The first result can be displayed under the wrong tab when the URL changes while this preload is pending: `initialVariables` remains on the old tab, and `DocumentsPage` treats its first render as already synchronized and skips refetching. Passing the loaded tab through to the page or coordinating the initial refetch would keep the URL filter and displayed slice consistent.</violation>
</file>

<file name="apps/compliance-portal/src/pages/documents/_lib/openExportedFile.ts">

<violation number="1" location="apps/compliance-portal/src/pages/documents/_lib/openExportedFile.ts:28">
P1: Viewing a Trust Center file with an active MIME type can execute its uploaded HTML/SVG content because the returned MIME is used directly for a navigated Blob URL. Restrict inline navigation to an explicit safe allowlist such as PDF, or force untrusted/non-previewable types to download with `application/octet-stream`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

const commaIndex = dataUri.indexOf(",");
const base64 = commaIndex === -1 ? dataUri : dataUri.slice(commaIndex + 1);
const mimeMatch = dataUri.match(/^data:([^;]+);/);
const mimeType = mimeMatch?.[1] ?? "application/octet-stream";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Viewing a Trust Center file with an active MIME type can execute its uploaded HTML/SVG content because the returned MIME is used directly for a navigated Blob URL. Restrict inline navigation to an explicit safe allowlist such as PDF, or force untrusted/non-previewable types to download with application/octet-stream.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/compliance-portal/src/pages/documents/_lib/openExportedFile.ts, line 28:

<comment>Viewing a Trust Center file with an active MIME type can execute its uploaded HTML/SVG content because the returned MIME is used directly for a navigated Blob URL. Restrict inline navigation to an explicit safe allowlist such as PDF, or force untrusted/non-previewable types to download with `application/octet-stream`.</comment>

<file context>
@@ -0,0 +1,42 @@
+  const commaIndex = dataUri.indexOf(",");
+  const base64 = commaIndex === -1 ? dataUri : dataUri.slice(commaIndex + 1);
+  const mimeMatch = dataUri.match(/^data:([^;]+);/);
+  const mimeType = mimeMatch?.[1] ?? "application/octet-stream";
+
+  const binary = atob(base64);
</file context>

Comment thread packages/ui/src/v2/Toaster/variants.ts Outdated

// Seed the first fetch with the URL's tab; later changes are handled by the
// page's refetch.
const initialVariables = useRef(toQueryVariables(tab));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The first result can be displayed under the wrong tab when the URL changes while this preload is pending: initialVariables remains on the old tab, and DocumentsPage treats its first render as already synchronized and skips refetching. Passing the loaded tab through to the page or coordinating the initial refetch would keep the URL filter and displayed slice consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/compliance-portal/src/pages/documents/DocumentsPageLoader.tsx, line 36:

<comment>The first result can be displayed under the wrong tab when the URL changes while this preload is pending: `initialVariables` remains on the old tab, and `DocumentsPage` treats its first render as already synchronized and skips refetching. Passing the loaded tab through to the page or coordinating the initial refetch would keep the URL filter and displayed slice consistent.</comment>

<file context>
@@ -0,0 +1,47 @@
+
+  // Seed the first fetch with the URL's tab; later changes are handled by the
+  // page's refetch.
+  const initialVariables = useRef(toQueryVariables(tab));
+
+  useEffect(() => {
</file context>

Comment thread apps/compliance-portal/src/pages/documents/DocumentsPage.tsx
Comment thread apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageSkeleton.tsx Outdated
Comment thread apps/compliance-portal/src/pages/documents/_locales/fr-FR.json
Comment thread packages/ui/src/v2/Tabs/TabsSkeleton.tsx Outdated
Comment thread apps/compliance-portal/src/pages/documents/_lib/useDocumentTab.ts Outdated
Fold the review feedback from the documents page work into the shared
components and helpers:

- Toaster: give the close control the kit's focus-visible ring, and wrap
  the title/description in Toast.Content so stacked toasts get Base UI's
  height measurement and overflow handling.
- TabsSkeleton: spread rest before the fixed aria-hidden so the
  decorative subtree can't be exposed to assistive tech.
- DocumentsPage: reconcile the active tab against the tab the preloaded
  query actually loaded with, so a tab change during the initial preload
  no longer shows the wrong slice.
- Grouping: drop the duplicated bucketing helpers and reuse the shared
  groupBy from @probo/helpers, keeping field-specific sorting/labeling in
  the callers.
- Documents list items: extract a shared DocumentEntry row and a
  useExportAndOpen hook so the three item components stop repeating the
  export/access behavior while keeping their own fragments.
- Subprocessors skeleton: reuse SelectSkeleton/TextFieldSkeleton instead
  of hand-rolled placeholders.
- useDocumentTab: derive DocumentTab and URL validation from a single
  DOCUMENT_TABS source of truth.

Signed-off-by: Émile Ré <emile@probo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant