Add Trust Center hero and page headers
Build the Trust Center landing hero and the nav page headers on a shared HeaderBand shell. The home page loads its own query and feeds the Hero (org headline, description, and contact info) via a colocated fragment; the Documents, Subprocessors, Updates, and Data Requests pages use a PageHeader (size-7 title, optional count, inline actions). Switch the layout body to the grey sand-2 canvas so the white header bands read as surfaces on top. Split the header UI into focused components rather than one over-configurable Hero: HeaderBand owns the band, Hero the landing content, PageHeader the nav-page content. Add a hostname helper under lib/url and standardize the 1024px container on the max-w-5xl token. The Documents/Subprocessors filter/search/tabs toolbars and item counts are deferred until the matching v2 components and queries exist. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -12,19 +12,20 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
||||
import { useLocation } from "react-router";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
// Temporary stub for the Trust Center sections so the top-bar nav links resolve
|
||||
// and the active state renders. Real pages replace these per section.
|
||||
export default function PlaceholderPage() {
|
||||
const { pathname } = useLocation();
|
||||
const segment = pathname.replace(/^\//, "").split("/")[0] ?? "";
|
||||
const title = segment.charAt(0).toUpperCase() + segment.slice(1);
|
||||
import { headerBand } from "./variants";
|
||||
|
||||
export type HeaderBandProps = PropsWithChildren;
|
||||
|
||||
// Pure layout shell: the white band + centered content column. Consumers (Hero,
|
||||
// PageHeader) supply a single content column and own its internal spacing.
|
||||
export function HeaderBand({ children }: HeaderBandProps) {
|
||||
const { band, inner } = headerBand();
|
||||
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-[1024px] px-8 py-10">
|
||||
<Heading>{title}</Heading>
|
||||
</main>
|
||||
<header className={band()}>
|
||||
<div className={inner()}>{children}</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
25
apps/compliance-portal/src/components/HeaderBand/variants.ts
Normal file
25
apps/compliance-portal/src/components/HeaderBand/variants.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// The white header band that sits over the grey body: full-width surface with a
|
||||
// bottom border and a centered max-w-5xl content column. Shared by the landing
|
||||
// Hero and the page headers so the band is defined once.
|
||||
export const headerBand = tv({
|
||||
slots: {
|
||||
band: "flex w-full flex-col items-center border-b border-sand-6 bg-sand-1 px-8 py-8",
|
||||
inner: "w-full max-w-5xl",
|
||||
},
|
||||
});
|
||||
53
apps/compliance-portal/src/components/Hero/Hero.tsx
Normal file
53
apps/compliance-portal/src/components/Hero/Hero.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { HeaderBand } from "#/components/HeaderBand/HeaderBand";
|
||||
|
||||
import { hero } from "./variants";
|
||||
|
||||
export interface HeroProps {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// Landing hero (home): a size-8 title (+ optional description) in the shared
|
||||
// white band, plus an optional bottom slot for page-specific content (the org
|
||||
// contact row). The slot content owns its own divider/spacing so it disappears
|
||||
// cleanly when empty.
|
||||
export function Hero({ title, description, children }: HeroProps) {
|
||||
const { content, section } = hero();
|
||||
|
||||
return (
|
||||
<HeaderBand>
|
||||
<div className={content()}>
|
||||
<div className={section()}>
|
||||
<Heading level={1} size={8} weight="medium" highContrast>
|
||||
{title}
|
||||
</Heading>
|
||||
{description != null && description !== "" && (
|
||||
<Text size={2} color="neutral">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</HeaderBand>
|
||||
);
|
||||
}
|
||||
47
apps/compliance-portal/src/components/Hero/HeroSkeleton.tsx
Normal file
47
apps/compliance-portal/src/components/Hero/HeroSkeleton.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { HeadingSkeleton } from "@probo/ui/src/v2/typography/HeadingSkeleton";
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { HeaderBand } from "#/components/HeaderBand/HeaderBand";
|
||||
|
||||
import { hero, organizationContactInfo } from "./variants";
|
||||
|
||||
const CONTACT_ITEM_KEYS = ["website", "email", "location"] as const;
|
||||
|
||||
// Loading placeholder paired with Hero: reuses the same layout slots with
|
||||
// skeleton primitives. Imports no Relay, so it renders instantly.
|
||||
export function HeroSkeleton() {
|
||||
const { content, section } = hero();
|
||||
const { root, item } = organizationContactInfo();
|
||||
|
||||
return (
|
||||
<HeaderBand>
|
||||
<div className={content()}>
|
||||
<div className={section()}>
|
||||
<HeadingSkeleton size={8} className="w-96" />
|
||||
<TextSkeleton size={2} className="w-full max-w-2xl" />
|
||||
</div>
|
||||
<div className={root()}>
|
||||
{CONTACT_ITEM_KEYS.map(key => (
|
||||
<div key={key} className={item()}>
|
||||
<TextSkeleton size={2} className="w-28" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</HeaderBand>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { EnvelopeIcon, GlobeSimpleIcon, MapPinSimpleIcon } from "@phosphor-icons/react";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { hostnameOf } from "#/lib/url/hostname";
|
||||
|
||||
import type { OrganizationContactInfo_organization$key } from "./__generated__/OrganizationContactInfo_organization.graphql";
|
||||
import { organizationContactInfo } from "./variants";
|
||||
|
||||
const organizationContactInfoFragment = graphql`
|
||||
fragment OrganizationContactInfo_organization on Organization {
|
||||
websiteUrl
|
||||
email
|
||||
headquarterAddress
|
||||
}
|
||||
`;
|
||||
|
||||
interface OrganizationContactInfoProps {
|
||||
organizationKey: OrganizationContactInfo_organization$key;
|
||||
}
|
||||
|
||||
// Organization contact details (website, email, HQ) rendered as an icon + label
|
||||
// row. Owns its fragment so it can be reused wherever the org is in scope.
|
||||
export function OrganizationContactInfo({ organizationKey }: OrganizationContactInfoProps) {
|
||||
const organization = useFragment(organizationContactInfoFragment, organizationKey);
|
||||
|
||||
const hasWebsite = organization.websiteUrl != null && organization.websiteUrl !== "";
|
||||
const hasEmail = organization.email != null && organization.email !== "";
|
||||
const hasAddress = organization.headquarterAddress != null && organization.headquarterAddress !== "";
|
||||
|
||||
// Nothing to show — render no row (and therefore no divider) at all.
|
||||
if (!hasWebsite && !hasEmail && !hasAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { root, item, link } = organizationContactInfo();
|
||||
|
||||
return (
|
||||
<div className={root()}>
|
||||
{hasWebsite && (
|
||||
<a
|
||||
className={link()}
|
||||
href={organization.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<GlobeSimpleIcon />
|
||||
<Text size={2} color="neutral">
|
||||
{hostnameOf(organization.websiteUrl)}
|
||||
</Text>
|
||||
</a>
|
||||
)}
|
||||
{hasEmail && (
|
||||
<a className={link()} href={`mailto:${organization.email}`}>
|
||||
<EnvelopeIcon />
|
||||
<Text size={2} color="neutral">
|
||||
{organization.email}
|
||||
</Text>
|
||||
</a>
|
||||
)}
|
||||
{hasAddress && (
|
||||
<div className={item()}>
|
||||
<MapPinSimpleIcon />
|
||||
<Text size={2} color="neutral">
|
||||
{organization.headquarterAddress}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
apps/compliance-portal/src/components/Hero/variants.ts
Normal file
37
apps/compliance-portal/src/components/Hero/variants.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Landing hero content rendered inside the shared HeaderBand: a centered
|
||||
// title/description section above an optional bottom slot (the contact row).
|
||||
// Slots are shared by the live Hero and its skeleton.
|
||||
export const hero = tv({
|
||||
slots: {
|
||||
content: "flex w-full flex-col gap-10",
|
||||
section: "flex w-full flex-col gap-4",
|
||||
},
|
||||
});
|
||||
|
||||
// Organization contact block: a horizontal row of icon + label items, with a
|
||||
// top divider so it reads as the hero's bottom section. Self-contained so the
|
||||
// divider only appears when there is contact info to show.
|
||||
export const organizationContactInfo = tv({
|
||||
slots: {
|
||||
// Only a top divider gap; the band's py-8 provides the bottom spacing.
|
||||
root: "flex w-full items-center gap-6 border-t border-sand-6 pt-4",
|
||||
item: "flex items-center gap-2 text-sand-11 [&_svg]:size-5",
|
||||
link: "flex items-center gap-2 text-sand-11 hover:underline [&_svg]:size-5",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { HeaderBand } from "#/components/HeaderBand/HeaderBand";
|
||||
|
||||
import { pageHeader } from "./variants";
|
||||
|
||||
export interface PageHeaderProps {
|
||||
title: ReactNode;
|
||||
// Optional muted item count appended to the title (e.g. "Documents (3)").
|
||||
count?: number;
|
||||
// Right-aligned controls on the title line (e.g. a "New Request" button).
|
||||
actions?: ReactNode;
|
||||
// Optional toolbar (tabs / filters / search) rendered below the title.
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// Page header for the Trust Center nav pages: a size-7 title in the shared white
|
||||
// band, with an optional count, inline actions, and a toolbar slot below.
|
||||
export function PageHeader({ title, count, actions, children }: PageHeaderProps) {
|
||||
const { content, titleRow, count: countSlot } = pageHeader();
|
||||
|
||||
return (
|
||||
<HeaderBand>
|
||||
<div className={content()}>
|
||||
<div className={titleRow()}>
|
||||
<Heading level={1} size={7} weight="medium" highContrast>
|
||||
{title}
|
||||
{count != null && <span className={countSlot()}>{` (${count})`}</span>}
|
||||
</Heading>
|
||||
{actions}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</HeaderBand>
|
||||
);
|
||||
}
|
||||
26
apps/compliance-portal/src/components/PageHeader/variants.ts
Normal file
26
apps/compliance-portal/src/components/PageHeader/variants.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Page header content rendered inside the shared HeaderBand: a size-7 title row
|
||||
// (with optional count and right-aligned actions) above an optional toolbar.
|
||||
export const pageHeader = tv({
|
||||
slots: {
|
||||
content: "flex w-full flex-col gap-2",
|
||||
titleRow: "flex w-full items-center justify-between gap-4",
|
||||
// Muted item count appended to the title (e.g. "Documents (3)").
|
||||
count: "font-light text-sand-8",
|
||||
},
|
||||
});
|
||||
@@ -19,7 +19,7 @@ import { tv } from "tailwind-variants/lite";
|
||||
export const topBar = tv({
|
||||
slots: {
|
||||
bar: "flex h-14 items-center bg-sand-1 px-8",
|
||||
inner: "mx-auto flex w-full max-w-[1024px] items-center gap-10",
|
||||
inner: "mx-auto flex w-full max-w-5xl items-center gap-10",
|
||||
brand: "flex items-center gap-2",
|
||||
logo: "shrink-0",
|
||||
spacer: "h-px flex-1",
|
||||
|
||||
23
apps/compliance-portal/src/lib/url/hostname.ts
Normal file
23
apps/compliance-portal/src/lib/url/hostname.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Show only the hostname for a URL (e.g. "https://blaxel.ai/x" -> "blaxel.ai"),
|
||||
// falling back to the raw value when it cannot be parsed.
|
||||
export function hostnameOf(url: string): string {
|
||||
try {
|
||||
return new URL(url).host;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
21
apps/compliance-portal/src/pages/DocumentsPage.tsx
Normal file
21
apps/compliance-portal/src/pages/DocumentsPage.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { PageHeader } from "#/components/PageHeader/PageHeader";
|
||||
|
||||
// Toolbar (All/Public/Private tabs, framework filter, search) and the document
|
||||
// count are deferred until the v2 Tabs/Select/TextField components exist.
|
||||
export default function DocumentsPage() {
|
||||
return <PageHeader title="Documents" />;
|
||||
}
|
||||
@@ -12,10 +12,37 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
export default function HomePage() {
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import { graphql, usePreloadedQuery } from "react-relay";
|
||||
|
||||
import { Hero } from "#/components/Hero/Hero";
|
||||
import { OrganizationContactInfo } from "#/components/Hero/OrganizationContactInfo";
|
||||
|
||||
import type { HomePageQuery } from "./__generated__/HomePageQuery.graphql";
|
||||
|
||||
export const homePageQuery = graphql`
|
||||
query HomePageQuery {
|
||||
currentTrustCenter @required(action: THROW) {
|
||||
organization {
|
||||
name
|
||||
description
|
||||
...OrganizationContactInfo_organization
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface HomePageProps {
|
||||
queryRef: PreloadedQuery<HomePageQuery>;
|
||||
}
|
||||
|
||||
export function HomePage({ queryRef }: HomePageProps) {
|
||||
const data = usePreloadedQuery<HomePageQuery>(homePageQuery, queryRef);
|
||||
const { organization } = data.currentTrustCenter;
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center">
|
||||
<h1 className="text-6 font-bold text-sand-12">Compliance Portal</h1>
|
||||
</main>
|
||||
<Hero title={`Trust at ${organization.name}.`} description={organization.description}>
|
||||
<OrganizationContactInfo organizationKey={organization} />
|
||||
</Hero>
|
||||
);
|
||||
}
|
||||
|
||||
34
apps/compliance-portal/src/pages/HomePageLoader.tsx
Normal file
34
apps/compliance-portal/src/pages/HomePageLoader.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
|
||||
import type { HomePageQuery } from "./__generated__/HomePageQuery.graphql";
|
||||
import { HomePage, homePageQuery } from "./HomePage";
|
||||
import { HomePageSkeleton } from "./HomePageSkeleton";
|
||||
|
||||
export default function HomePageLoader() {
|
||||
const [queryRef, loadQuery] = useQueryLoader<HomePageQuery>(homePageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <HomePageSkeleton />;
|
||||
}
|
||||
|
||||
return <HomePage queryRef={queryRef} />;
|
||||
}
|
||||
19
apps/compliance-portal/src/pages/HomePageSkeleton.tsx
Normal file
19
apps/compliance-portal/src/pages/HomePageSkeleton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { HeroSkeleton } from "#/components/Hero/HeroSkeleton";
|
||||
|
||||
export function HomePageSkeleton() {
|
||||
return <HeroSkeleton />;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export function MainLayout({ queryRef }: MainLayoutProps) {
|
||||
const data = usePreloadedQuery<MainLayoutQuery>(mainLayoutQuery, queryRef);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-sand-1">
|
||||
<div className="min-h-screen bg-sand-2">
|
||||
<TopBar queryKey={data} />
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TopBarSkeleton } from "#/components/TopBar/TopBarSkeleton";
|
||||
|
||||
export function MainLayoutSkeleton() {
|
||||
return (
|
||||
<div className="min-h-screen bg-sand-1">
|
||||
<div className="min-h-screen bg-sand-2">
|
||||
<TopBarSkeleton />
|
||||
</div>
|
||||
);
|
||||
|
||||
31
apps/compliance-portal/src/pages/RequestsPage.tsx
Normal file
31
apps/compliance-portal/src/pages/RequestsPage.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { PlusIcon } from "@phosphor-icons/react";
|
||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
||||
|
||||
import { PageHeader } from "#/components/PageHeader/PageHeader";
|
||||
|
||||
export default function RequestsPage() {
|
||||
return (
|
||||
<PageHeader
|
||||
title="Data Requests"
|
||||
actions={(
|
||||
<Button variant="soft" color="neutral" highContrast iconStart={<PlusIcon />}>
|
||||
New Request
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
21
apps/compliance-portal/src/pages/SubprocessorsPage.tsx
Normal file
21
apps/compliance-portal/src/pages/SubprocessorsPage.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { PageHeader } from "#/components/PageHeader/PageHeader";
|
||||
|
||||
// Toolbar (category/region filters, search, Download CSV) and the subprocessor
|
||||
// count are deferred until the v2 Select/TextField components exist.
|
||||
export default function SubprocessorsPage() {
|
||||
return <PageHeader title="Subprocessors" />;
|
||||
}
|
||||
31
apps/compliance-portal/src/pages/UpdatesPage.tsx
Normal file
31
apps/compliance-portal/src/pages/UpdatesPage.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { BellIcon } from "@phosphor-icons/react";
|
||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
||||
|
||||
import { PageHeader } from "#/components/PageHeader/PageHeader";
|
||||
|
||||
export default function UpdatesPage() {
|
||||
return (
|
||||
<PageHeader
|
||||
title="Updates"
|
||||
actions={(
|
||||
<Button variant="soft" color="neutral" highContrast iconStart={<BellIcon />}>
|
||||
Subscribe to updates
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { createBrowserRouter } from "react-router";
|
||||
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
import { getPathPrefix } from "#/lib/http/pathPrefix";
|
||||
import { HomePageSkeleton } from "#/pages/HomePageSkeleton";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -27,23 +28,24 @@ const routes = [
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
Component: lazy(() => import("#/pages/HomePage")),
|
||||
Fallback: HomePageSkeleton,
|
||||
Component: lazy(() => import("#/pages/HomePageLoader")),
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
Component: lazy(() => import("#/pages/DocumentsPage")),
|
||||
},
|
||||
{
|
||||
path: "subprocessors",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
Component: lazy(() => import("#/pages/SubprocessorsPage")),
|
||||
},
|
||||
{
|
||||
path: "updates",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
Component: lazy(() => import("#/pages/UpdatesPage")),
|
||||
},
|
||||
{
|
||||
path: "requests",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
Component: lazy(() => import("#/pages/RequestsPage")),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user