Add German, Spanish, Indonesian, Italian, Japanese, Korean, Polish,
Portuguese, Turkish, Ukrainian, and Simplified Chinese to
SUPPORTED_LANGUAGES, and map each one's browser language-tag prefix to
its canonical locale in resolveLanguage() so i18next can be asked to
load it. Catalog JSON for these locales lands in the following
commits.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The English and French compliance-portal strings referred to "trust
center" in a few spots (back-navigation link, empty states) while the
rest of the app already calls this surface the "compliance portal".
Standardize on that term everywhere it appears in copy, and rename the
backing i18n key (backToTrustCenter -> backToCompliancePortal) to
match. Also fixes two spots where the French translation had left
"Compliance Portal" and "trust center" untranslated in English.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The form wrapper broke DialogPopup's column gap, so the
textarea sat flush against the actions. Match SignInForm.
Signed-off-by: Émile Ré <emile@probo.com>
Documents and data requests duplicated the same card and
row chrome. A shared List primitive keeps those surfaces
consistent across the portal.
Signed-off-by: Émile Ré <emile@probo.com>
Require a verified viewer email before creating a rights request and
validate the free-text fields with the same SafeText bounds the console
uses, so this public portal mutation stays safe and bounded.
Move myRightsRequests onto the base Query, drop the now-dead count
loaders, and order the RECTIFICATION enum value before PORTABILITY so
the Postgres sort order matches RightsRequestTypes().
Harden the v2 kit primitives: SegmentedControl keeps equal-width cards
(auto-fill), preserves its selection when the active card is toggled,
and forwards an accessible name; Field associates its label and error
by id/aria instead of wrapping the control in a label. Give the type
group an accessible name, require the name field for non-complaint
types, use a timezone-stable reference year, drop the underreporting
header count, and neutralize the response-deadline copy.
Signed-off-by: Émile Ré <emile@probo.com>
Let trust-portal data subjects submit and track GDPR/CCPA rights
requests. The new Data Requests page lists the viewer's own requests
and a dialog submits new ones, scoped server-side to the verified
viewer email so former or inactive users can still exercise their
rights. Submission requires magic-link sign-in (reusing the existing
gate) but not the NDA gate.
Extend the shared rights_request enums with RECTIFICATION, OBJECTION
and COMPLAINT types plus a REJECTED state, and keep the console
GraphQL, @probo/helpers and the MCP specification in sync. Expose a
trust GraphQL surface (myRightsRequests query, createRightsRequest
mutation) backed by a trust service and contact-scoped coredata
loaders.
Add the missing v2 UI kit primitives the dialog needs on top of Base
UI: a SegmentedControl radio-cards group, a form Textarea, and a
Field wrapper.
Signed-off-by: Émile Ré <emile@probo.com>
Several risk views already used "Initial" while others still showed
"Inherent". Align user-facing labels across the console, shared UI
components, CLI help, n8n fields, generated documents, and MCP
descriptions. API and database field names are unchanged.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
resolveGateRedirect only supplied window.location.href to gateRedirectPath
from two call sites; drop the extra indirection and call gateRedirectPath
directly in the root and page error boundaries.
Signed-off-by: Émile Ré <emile@probo.com>
Route the full-name and NDA gates from the request-access flows to their
gate pages (deep-linking with the deferred continue URL) instead of a
dead-end toast, so signing or naming resumes the original request; the
shared gate-to-route mapping now lives in one helper reused by the route
boundaries and both request hooks.
Fix the NDA page redirecting to home while also redirecting to the
continue URL once the signature is sealed, surface consent/accept
failures so the sign button isn't silently inert, and build the
request-all continue URL before clearing its marker.
On the backend, return success from updateFullName when the identity has
no organization profile instead of dereferencing a nil profile, which
crashed external trust-center visitors completing the full-name gate.
Signed-off-by: Émile Ré <emile@probo.com>
Handle NDA_SIGNATURE_REQUIRED and FULL_NAME_REQUIRED the way the trust
app does: the Relay fetch throws the typed errors and the route error
boundaries redirect to /full-name or a new self-contained /nda page,
carrying a continue URL. The request-access hooks move both gates to
onError accordingly (NDA is toast-only, matching trust, since the query
boundary is its primary path).
The NDA page is styled like the document viewer: a header band with the
title, org subtitle, consent, and sign action, over the NDA PDF, with the
same page-navigation and zoom controls. It records the signing events,
accepts the electronic signature, polls until sealed, then returns to the
continue URL.
Also fall back to the email in the top-bar user menu when a member has no
full name yet, and hoist the shared PDF-viewer control labels into the
app common namespace (deduplicating the document viewer and NDA copies,
and collapsing a duplicated common block in the locales).
Signed-off-by: Émile Ré <emile@probo.com>
The document, report, and file "Get Access" buttons were inert. Wire
them to the per-resource access mutations, and gate unauthenticated
requests behind the sign-in dialog: a signed-out click defers the
request in the continue URL and resumes it after sign-in, mirroring the
top-bar request-all flow. Extend useResumeAccessRequest to complete the
deferred per-resource requests and route through the full-name gate.
Signed-off-by: Émile Ré <emile@probo.com>
The form and its Relay operation were named after the Figma "Login Form"
layer, but the rest of the feature speaks "sign in" (SignInDialog,
auth.signIn.*, the visible copy). Align the name so the dialog and its
form share one prefix.
Signed-off-by: Émile Ré <emile@probo.com>
The compliance portal's "Get Access" button was inert and the portal
had no way to authenticate or request trust-center access. Add a modal
sign-in flow (magic link + OIDC) that gates the requestAllAccesses
mutation, mirroring the trust app's flow but as a dialog instead of a
full /connect page.
Introduce the two v2 UI-kit primitives this depends on: a headless
Base UI Dialog and a styled Toaster (mutation toasts had no host yet).
Wire the top-bar button to open the dialog, resume the deferred access
request once authenticated, and add standalone routes for magic-link
verification and the full-name gate.
Signed-off-by: Émile Ré <emile@probo.com>
Groups with no cards render nothing, but the section still reserved its
eyebrow slot for the first group by index and kept its padded wrapper,
leaving a stray "Security Commitments" label misplacement and an empty
gap when every group was empty.
Filter out cardless groups before rendering so the eyebrow lands on the
first visible group and the whole section collapses when nothing renders.
Signed-off-by: Émile Ré <emile@probo.com>
Let admins reorder commitment groups and the cards within each group from
the console Commitments tab using up/down buttons, driven by the existing
rank-aware update mutations.
Make the (parent, rank) unique constraints on the commitment tables
DEFERRABLE INITIALLY DEFERRED. Reordering shifts several rows in one
UPDATE, which transiently duplicates a rank and tripped the immediately
enforced constraint with a 23505 error. This matches the other
rank-ordered tables (references, compliance frameworks).
Signed-off-by: Émile Ré <emile@probo.com>
The compliance portal home page rendered security-commitment cards from
a hardcoded placeholder POJO. Back them with real, per-organization data
that admins configure in the console and the portal loads over the trust
center GraphQL API.
Model two entities under the trust center: a commitment group (title,
description, rank) and a commitment card (icon, eyebrow, title,
description, rank). The card icon is a curated enum mapped to a Phosphor
icon in the portal. New entities adopt the compliance_portal_ prefix as
the start of the broader rename away from trust_center_ naming.
Expose the groups and cards read-only on the public trust API and with
full CRUD on the console API, add a Commitments tab to the compliance
page, and replace the placeholder section with a Relay-driven one.
Signed-off-by: Émile Ré <emile@probo.com>
Dispose the viewer query when the alias changes so navigating between
documents shows the skeleton instead of the previous document. Guard the
export completion against the current id so a slow export can't overwrite
a newer document's preview. Split the documents tab state into requested
and loaded refs so an out-of-order or failed refetch can't leave the list
showing a different tab than the toolbar.
Signed-off-by: Émile Ré <emile@probo.com>
Add a full-page viewer at /documents/:alias that resolves the aliased
node, exports its watermarked bytes, and renders them: PDFs via react-pdf
with page navigation and zoom, images inline, and a download fallback for
other file types. Unauthorized visitors see a locked state.
Wire the documents list "View" action to link into the viewer (fragments
now select alias) and drop the previous open-in-new-tab helpers, since the
viewer owns the export.
Bound MainLayout to the viewport so the top bar and footer stay fixed and
the page area scrolls on its own; the viewer then keeps its toolbar fixed
while the PDF body scrolls, matching the design.
Add the react-pdf dependency with the pdf.js worker bundled via Vite (for
CSP safety) and a headless v2 Separator kit component for the toolbar.
Signed-off-by: Émile Ré <emile@probo.com>
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>
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>
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>
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>
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>
The home page frames its content in labelled sections, so a bare inline
error floated as loose text. Wrap the home section error fallbacks
(Compliance, Trusted by, Recent updates) in a shared InlineErrorCard so a
failed section reads as intentional content on a soft card surface.
Standalone list pages (subprocessors, updates) have no such framing, so
ListErrorBoundary keeps a bare inline error, which looks cleaner there.
Signed-off-by: Émile Ré <emile@probo.com>
The compliance-framework tiles read an item fragment that lacked
@throwOnFieldError, so a tile field error slipped past the section
boundary and nulled silently. Mark it so the existing boundary catches
it.
Wrap the "Trusted by" section in its own boundary and mark its fragments
@throwOnFieldError so a references failure degrades to an inline error
instead of crashing to the page boundary. The hero contact row is left
as-is: its fields are optional and already hide on null/error.
Signed-off-by: Émile Ré <emile@probo.com>
Contain field errors on the subprocessors and updates lists to an inline
fallback instead of the whole page, and make the retry actually recover.
Introduce ListErrorBoundary, which keeps refetch above the boundary and
resets only after the network refetch settles (a bare boundary reset
re-reads the same errored record and throws again). Wire the subprocessors
and updates lists to refetch network-only on retry, and mark the item
fragments @throwOnFieldError so a row error lands below the boundary.
Fix the home sections, whose reset-only retry could not clear a field
error from the preloaded query, to reload the page instead. Generalize the
retry guidance in the error-handling guide (reset vs refetch vs reload).
Signed-off-by: Émile Ré <emile@probo.com>
React Router routes render and loader errors to the route-level
boundaries, so the App-level boundary above the router could only catch
provider render failures — which today are trivial — while true bootstrap
failures throw at module load before it mounts. Neither console nor trust
wraps App this way.
Rely on the root route boundary instead and remove the BootstrapError
fallback it used.
Signed-off-by: Émile Ré <emile@probo.com>
The recent-updates rows all come from a single connection query, so a
per-row boundary isolated a failure that the rows actually share and left
an unrecoverable row (its retry only re-read the same errored store).
Let a row's field error bubble to the section boundary instead, so the
whole list degrades and recovers as one unit.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce global, page, and section-level error handling for the
compliance portal so a failure is contained at the smallest possible
scope instead of blanking the whole page.
Add a portal-local Relay fetch that throws only request-level errors
(and always redirects on UNAUTHENTICATED) while leaving field-level
errors in the response, so Relay surfaces them at the reading component
through @throwOnFieldError and the nearest boundary. Add a NotFoundError
for node __typename mismatches mapped to a not-found page.
Ship reusable v2 kit primitives (ErrorBoundary, ErrorState, InlineError)
matching the Figma global/local/inline designs, wire the bootstrap and
route boundaries, and demonstrate section and row boundaries on the home
page. Update the error-handling and relay guides accordingly.
Signed-off-by: Émile Ré <emile@probo.com>
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:
- Convert every source-file header to the MIT text across all comment
styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
"MIT License" title line
- Switch the package.json license fields, Docker image label, and
cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
(Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
the comma-separated years to a hyphenated range
Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
The verification-code copy handler called navigator.clipboard.writeText
directly and relied on the promise rejection for the failure toast. In
an insecure context or an unsupported embedded browser navigator.clipboard
is undefined, so the call throws synchronously before .then and neither
toast fires, leaving the user without the manual-copy guidance. Guard the
access and wrap the call in try/catch, mirroring ScopeDiagram, so the
failure toast is always shown.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Two honest-feedback fixes in the API-key connect dialog. The
connection-failure toast told managed providers (Crisp) to check their
API key, but Crisp shows no key field; it now points them to the
settings and verification code instead. The verification-code copy
button showed a success toast before the clipboard write resolved; it
now confirms the write and reports a failure with a manual-copy hint,
matching how other copy buttons behave.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
AddAccessReviewSourceDialog had grown past a thousand lines, mixing
provider search, the API-key, client-credentials and OAuth connect
flows, and their shared mutation logic in one file.
Extract the per-protocol dialogs into _components, the shared
create-source flow into a _hooks hook, and the settings mappers and
OAuth redirect helper into _lib. Each connect dialog now owns its own
Relay operation. The API-key dialog carries the Crisp managed-connector
flow: it fetches the verification code for the entered Website ID and
distinguishes loading, ready and error states, so a failed fetch shows
an actionable error with a retry instead of a permanent spinner and a
superseded response cannot overwrite the current code.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Four API-key, single-tenant (Pattern 3) connectors:
- Scaleway: secret key in the X-Auth-Token header plus an Organization ID
setting; GET /iam/v1alpha1/users (owner/member, status, two-factor),
per-connection BuildProbeURL.
- Yousign: Bearer API key; GET /v3/users (admin/owner/member, is_active);
production host with a static probe.
- Railway: Bearer account token; GraphQL me{workspaces{members}} aggregated
and deduplicated across workspaces; custom probe, since Railway returns
HTTP 200 with an errors body on a rejected token.
- Crisp: plugin token as HTTP Basic (identifier:key) plus a Website ID
setting and the X-Crisp-Tier header; GET /v1/website/{id}/operators/list,
custom probe and name resolver.
Scaleway and Crisp carry a required extra setting, so the console add-source
dialog maps organizationId/websiteId onto their scalewayOrganizationId and
crispWebsiteId API-key inputs; without that mapping the value is silently
dropped and the create is rejected.
Cassette-backed driver tests plus unit tests for the cross-workspace
deduplication, the probe contracts and the role/MFA helpers.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Surface the organization risk assessments list alongside the risk register
by adding a shared Risks/Risk assessments tab bar to both list pages.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
After allow or deny succeeds, replace the consent form with a full-page
redirect message naming the application and a spinner while the browser
navigates to the OAuth callback URL.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Track a pending allow/deny action with Button loading spinners so
users cannot double-submit while the approveConsent mutation runs or
while the browser waits on the OAuth callback redirect.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Forward className on PaginationSkeleton to match the kit skeleton API,
and move the Next arrow to iconEnd per the forward-arrow convention.
Size the updates list skeleton to the page size so the placeholder no
longer jumps when the loaded page renders, and dispose the detail
query on updateId change to avoid a flash of the previous update.
Signed-off-by: Émile Ré <emile@probo.com>
Generalize the cursor Prev/Next pagination hook into a reusable
useCursorPagination in lib/relay, taking the page size as a parameter,
and keep the updates page size (25) as a feature constant.
Move the list card surface and its loading-dim state into tv variants
behind an UpdatesList component, and lift the detail article layout and
its gold metadata styling into shared variants, so the pages carry only
placement classes. Skeletons reuse the same variants.
Relocate the generic pager labels to the app-root namespace and expose
Intl.DateTimeFormat options on the formatDate helper.
Signed-off-by: Émile Ré <emile@probo.com>
Build the public Updates pages in the compliance portal: a
cursor-paginated list of sent mailing-list updates and a detail view
for a single update, replacing the previous stub page.
Add a MailingListUpdate case to the trust API node resolver, guarded so
only SENT updates belonging to the current trust center's mailing list
are exposed, so the detail page can load an update by URL.
Add a Prev/Next Pagination primitive to the v2 UI kit. Page numbers are
omitted because cursor pagination cannot derive an ordinal page index;
each arrow only shows when its page exists while keeping its slot
reserved so a visible arrow never shifts position.
Relocate the shared MailingListUpdateListItem to its own component
folder and wrap each row in a link to the detail page, so both the home
recent-updates section and the list navigate to detail.
Signed-off-by: Émile Ré <emile@probo.com>
Drop items-start from the category grid so its rows stretch again.
Each card is a block grid item that fills the row height, lining up
the bottom edges of cards on the same row regardless of how much text
each one carries.
Signed-off-by: Émile Ré <emile@probo.com>