Add Trust Center top bar to compliance portal
Build the Trust Center top navigation in a Relay-wired layout route. The
portal now mounts a Relay environment and provider, and the root layout
loads a query whose fragment feeds the TopBar: brand, ghost-pill nav with
an active state from the router, and a guest "Get Access" button versus an
authenticated user menu. Placeholder section routes keep the nav links and
active state functional until real pages land.
Wire the Relay tagged-template transform via @rolldown/plugin-babel and
set the router basename to the /trust/{slug} path prefix so the app
resolves under its served path.
Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -25,6 +25,8 @@
|
||||
"relay-runtime": "^21.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@types/node": "^25.9.3",
|
||||
"@types/react": "^19.2.17",
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
|
||||
import { RouterProvider } from "react-router";
|
||||
|
||||
import { RelayProvider } from "#/lib/relay/RelayProvider";
|
||||
import { router } from "#/routes";
|
||||
|
||||
export function App() {
|
||||
return <RouterProvider router={router} />;
|
||||
return (
|
||||
<RelayProvider>
|
||||
<RouterProvider router={router} />
|
||||
</RelayProvider>
|
||||
);
|
||||
}
|
||||
|
||||
116
apps/compliance-portal/src/components/TopBar/TopBar.tsx
Normal file
116
apps/compliance-portal/src/components/TopBar/TopBar.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 { LockSimpleIcon } from "@phosphor-icons/react";
|
||||
import { Avatar } from "@probo/ui/src/v2/Avatar/Avatar";
|
||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
import { Link as RouterLink, useLocation } from "react-router";
|
||||
|
||||
import type { TopBar_query$key } from "./__generated__/TopBar_query.graphql";
|
||||
import { TopBarUserMenu } from "./TopBarUserMenu";
|
||||
import { topBar } from "./variants";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ to: "/documents", label: "Documents" },
|
||||
{ to: "/subprocessors", label: "Subprocessors" },
|
||||
{ to: "/updates", label: "Updates" },
|
||||
{ to: "/requests", label: "Requests" },
|
||||
] as const;
|
||||
|
||||
const topBarFragment = graphql`
|
||||
fragment TopBar_query on Query {
|
||||
viewer {
|
||||
id
|
||||
...TopBarUserMenu_identity
|
||||
}
|
||||
currentTrustCenter @required(action: THROW) {
|
||||
organization {
|
||||
name
|
||||
logo {
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface TopBarProps {
|
||||
queryKey: TopBar_query$key;
|
||||
}
|
||||
|
||||
function isActive(pathname: string, to: string): boolean {
|
||||
return pathname === to || pathname.startsWith(`${to}/`);
|
||||
}
|
||||
|
||||
export function TopBar({ queryKey }: TopBarProps) {
|
||||
const data = useFragment(topBarFragment, queryKey);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { organization } = data.currentTrustCenter;
|
||||
const organizationName = organization.name;
|
||||
const logoUrl = organization.logo?.downloadUrl ?? undefined;
|
||||
|
||||
const slots = topBar();
|
||||
|
||||
return (
|
||||
<header className={slots.bar()}>
|
||||
<div className={slots.inner()}>
|
||||
<RouterLink to="/" className={slots.brand()}>
|
||||
<Avatar
|
||||
size={1}
|
||||
variant="soft"
|
||||
color="neutral"
|
||||
radius="small"
|
||||
src={logoUrl}
|
||||
fallback={organizationName.charAt(0) || "?"}
|
||||
className={slots.logo()}
|
||||
/>
|
||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||
{organizationName}
|
||||
</Text>
|
||||
<Text size={2} color="neutral">
|
||||
Compliance Portal
|
||||
</Text>
|
||||
</RouterLink>
|
||||
|
||||
<div className={slots.spacer()} />
|
||||
|
||||
<nav className={slots.nav()}>
|
||||
{NAV_ITEMS.map(item => (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
size={2}
|
||||
active={isActive(pathname, item.to)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
{data.viewer == null
|
||||
? (
|
||||
<Button variant="solid" color="neutral" highContrast iconStart={<LockSimpleIcon />}>
|
||||
Get Access
|
||||
</Button>
|
||||
)
|
||||
: <TopBarUserMenu identityKey={data.viewer} />}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -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 { AvatarSkeleton } from "@probo/ui/src/v2/Avatar/AvatarSkeleton";
|
||||
import { ButtonSkeleton } from "@probo/ui/src/v2/Button/ButtonSkeleton";
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { topBar } from "./variants";
|
||||
|
||||
const NAV_ITEM_KEYS = ["documents", "subprocessors", "updates", "requests"] as const;
|
||||
|
||||
// Loading placeholder paired with TopBar: reuses the same layout slots with
|
||||
// skeleton primitives. Imports no Relay / Base UI, so it renders instantly.
|
||||
export function TopBarSkeleton() {
|
||||
const slots = topBar();
|
||||
|
||||
return (
|
||||
<div className={slots.bar()}>
|
||||
<div className={slots.inner()}>
|
||||
<div className={slots.brand()}>
|
||||
<AvatarSkeleton size={1} radius="small" />
|
||||
<TextSkeleton size={2} className="w-20" />
|
||||
</div>
|
||||
|
||||
<div className={slots.spacer()} />
|
||||
|
||||
<nav className={slots.nav()}>
|
||||
{NAV_ITEM_KEYS.map(key => (
|
||||
<TextSkeleton key={key} size={2} className="mx-3 w-16" />
|
||||
))}
|
||||
<ButtonSkeleton size={2} />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 { CaretDownIcon, SignOutIcon, UserIcon } from "@phosphor-icons/react";
|
||||
import { Avatar } from "@probo/ui/src/v2/Avatar/Avatar";
|
||||
import { Dropdown } from "@probo/ui/src/v2/Dropdown/Dropdown";
|
||||
import { DropdownGroupLabel } from "@probo/ui/src/v2/Dropdown/DropdownGroupLabel";
|
||||
import { DropdownItem } from "@probo/ui/src/v2/Dropdown/DropdownItem";
|
||||
import { DropdownPopup } from "@probo/ui/src/v2/Dropdown/DropdownPopup";
|
||||
import { DropdownSeparator } from "@probo/ui/src/v2/Dropdown/DropdownSeparator";
|
||||
import { DropdownTrigger } from "@probo/ui/src/v2/Dropdown/DropdownTrigger";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import type { TopBarUserMenu_identity$key } from "./__generated__/TopBarUserMenu_identity.graphql";
|
||||
import { topBarUserMenuTrigger } from "./variants";
|
||||
|
||||
const topBarUserMenuFragment = graphql`
|
||||
fragment TopBarUserMenu_identity on Identity {
|
||||
fullName
|
||||
email
|
||||
}
|
||||
`;
|
||||
|
||||
interface TopBarUserMenuProps {
|
||||
identityKey: TopBarUserMenu_identity$key;
|
||||
}
|
||||
|
||||
export function TopBarUserMenu({ identityKey }: TopBarUserMenuProps) {
|
||||
const identity = useFragment(topBarUserMenuFragment, identityKey);
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownTrigger
|
||||
render={(
|
||||
<button type="button" className={topBarUserMenuTrigger()} aria-label={identity.fullName}>
|
||||
<Avatar
|
||||
size={1}
|
||||
variant="soft"
|
||||
color="gold"
|
||||
radius="small"
|
||||
fallback={<UserIcon />}
|
||||
/>
|
||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||
{identity.fullName}
|
||||
</Text>
|
||||
<CaretDownIcon className="size-4 text-sand-11" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<DropdownPopup align="end">
|
||||
<DropdownGroupLabel>{identity.email}</DropdownGroupLabel>
|
||||
<DropdownSeparator />
|
||||
<DropdownItem color="error" iconStart={<SignOutIcon />}>
|
||||
Sign out
|
||||
</DropdownItem>
|
||||
</DropdownPopup>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
38
apps/compliance-portal/src/components/TopBar/variants.ts
Normal file
38
apps/compliance-portal/src/components/TopBar/variants.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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";
|
||||
|
||||
// Trust Center top navigation bar. Slots are shared by the live TopBar and its
|
||||
// skeleton so the loading placeholder is structurally identical.
|
||||
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",
|
||||
brand: "flex items-center gap-2",
|
||||
logo: "shrink-0",
|
||||
spacer: "h-px flex-1",
|
||||
nav: "flex items-center gap-1",
|
||||
},
|
||||
});
|
||||
|
||||
// Rounded pill that opens the authenticated user menu.
|
||||
export const topBarUserMenuTrigger = tv({
|
||||
base: [
|
||||
"flex h-8 items-center gap-2 rounded-full py-1 pr-2.5 pl-1",
|
||||
"cursor-pointer outline-none transition-colors select-none",
|
||||
"hover:bg-sand-3 data-[popup-open]:bg-sand-3",
|
||||
"focus-visible:ring-2 focus-visible:ring-sand-8 focus-visible:ring-offset-1 focus-visible:ring-offset-sand-1",
|
||||
],
|
||||
});
|
||||
26
apps/compliance-portal/src/lib/relay/RelayProvider.tsx
Normal file
26
apps/compliance-portal/src/lib/relay/RelayProvider.tsx
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 type { PropsWithChildren } from "react";
|
||||
import { RelayEnvironmentProvider } from "react-relay";
|
||||
|
||||
import { environment } from "#/lib/relay/environment";
|
||||
|
||||
export function RelayProvider({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<RelayEnvironmentProvider environment={environment}>
|
||||
{children}
|
||||
</RelayEnvironmentProvider>
|
||||
);
|
||||
}
|
||||
29
apps/compliance-portal/src/lib/relay/environment.ts
Normal file
29
apps/compliance-portal/src/lib/relay/environment.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 { makeFetchQuery } from "@probo/relay";
|
||||
import { Environment, Network, RecordSource, Store } from "relay-runtime";
|
||||
|
||||
import { buildEndpoint } from "#/lib/http/endpoint";
|
||||
|
||||
const store = new Store(new RecordSource(), {
|
||||
queryCacheExpirationTime: 1 * 60 * 1000,
|
||||
gcReleaseBufferSize: 20,
|
||||
});
|
||||
|
||||
export const environment = new Environment({
|
||||
configName: "complianceportal",
|
||||
network: Network.create(makeFetchQuery(buildEndpoint())),
|
||||
store,
|
||||
});
|
||||
@@ -12,11 +12,30 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import { graphql, usePreloadedQuery } from "react-relay";
|
||||
import { Outlet } from "react-router";
|
||||
|
||||
export default function MainLayout() {
|
||||
import { TopBar } from "#/components/TopBar/TopBar";
|
||||
|
||||
import type { MainLayoutQuery } from "./__generated__/MainLayoutQuery.graphql";
|
||||
|
||||
export const mainLayoutQuery = graphql`
|
||||
query MainLayoutQuery {
|
||||
...TopBar_query
|
||||
}
|
||||
`;
|
||||
|
||||
interface MainLayoutProps {
|
||||
queryRef: PreloadedQuery<MainLayoutQuery>;
|
||||
}
|
||||
|
||||
export function MainLayout({ queryRef }: MainLayoutProps) {
|
||||
const data = usePreloadedQuery<MainLayoutQuery>(mainLayoutQuery, queryRef);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-sand-1">
|
||||
<TopBar queryKey={data} />
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
|
||||
34
apps/compliance-portal/src/pages/MainLayoutLoader.tsx
Normal file
34
apps/compliance-portal/src/pages/MainLayoutLoader.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 { MainLayoutQuery } from "./__generated__/MainLayoutQuery.graphql";
|
||||
import { MainLayout, mainLayoutQuery } from "./MainLayout";
|
||||
import { MainLayoutSkeleton } from "./MainLayoutSkeleton";
|
||||
|
||||
export default function MainLayoutLoader() {
|
||||
const [queryRef, loadQuery] = useQueryLoader<MainLayoutQuery>(mainLayoutQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <MainLayoutSkeleton />;
|
||||
}
|
||||
|
||||
return <MainLayout queryRef={queryRef} />;
|
||||
}
|
||||
23
apps/compliance-portal/src/pages/MainLayoutSkeleton.tsx
Normal file
23
apps/compliance-portal/src/pages/MainLayoutSkeleton.tsx
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.
|
||||
|
||||
import { TopBarSkeleton } from "#/components/TopBar/TopBarSkeleton";
|
||||
|
||||
export function MainLayoutSkeleton() {
|
||||
return (
|
||||
<div className="min-h-screen bg-sand-1">
|
||||
<TopBarSkeleton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
apps/compliance-portal/src/pages/PlaceholderPage.tsx
Normal file
30
apps/compliance-portal/src/pages/PlaceholderPage.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 { useLocation } from "react-router";
|
||||
|
||||
// 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);
|
||||
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-[1024px] px-8 py-10">
|
||||
<Heading>{title}</Heading>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -17,19 +17,40 @@ import { type AppRoute, routeFromAppRoute } from "@probo/routes";
|
||||
import { createBrowserRouter } from "react-router";
|
||||
|
||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||
import { getPathPrefix } from "#/lib/http/pathPrefix";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
Fallback: PageSkeleton,
|
||||
Component: lazy(() => import("#/pages/MainLayout")),
|
||||
Component: lazy(() => import("#/pages/MainLayoutLoader")),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
Component: lazy(() => import("#/pages/HomePage")),
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
},
|
||||
{
|
||||
path: "subprocessors",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
},
|
||||
{
|
||||
path: "updates",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
},
|
||||
{
|
||||
path: "requests",
|
||||
Component: lazy(() => import("#/pages/PlaceholderPage")),
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
export const router = createBrowserRouter(routes.map(routeFromAppRoute));
|
||||
// The portal is served under a /trust/{slug} path prefix (or a bare custom
|
||||
// domain). Match the router basename to that prefix so the routes resolve.
|
||||
export const router = createBrowserRouter(routes.map(routeFromAppRoute), {
|
||||
basename: getPathPrefix() || "/",
|
||||
});
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import babel from "@rolldown/plugin-babel";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
// @vitejs/plugin-react@6 (Vite 8) no longer runs Babel, so the Relay tagged
|
||||
// template transform is applied via @rolldown/plugin-babel instead.
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [react(), babel({ plugins: ["relay"] }), tailwindcss()],
|
||||
build: {
|
||||
assetsDir: "assets",
|
||||
},
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -44,6 +44,8 @@
|
||||
"relay-runtime": "^21.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@types/node": "^25.9.3",
|
||||
"@types/react": "^19.2.17",
|
||||
|
||||
Reference in New Issue
Block a user