Address compliance-portal review feedback
Fix the valid issues raised in the scaffold review. UI kit: the Button loading state now replaces only the leading icon instead of dropping the label, Button consumes the `active` variant so it no longer leaks onto the DOM, and every v2 skeleton sets aria-hidden after the prop spread so a consumer cannot override it. @probo/relay: guard the caller-supplied onCompleted/onError callbacks so a throwing callback still settles the awaitable mutation promise instead of leaving it pending. compliance-portal: normalize external website hrefs and read hostname via URL.hostname, add a localized catch-all not-found route, and widen the .gitattributes glob so colocated __generated__ artifacts at any depth are marked generated. Docs: correct the forms guide (Base UI passes plain values, Zod v3 flatten API), spread the child fragment in the permissions example, and drop references to v2 components that do not exist in the ui guide. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
3
apps/compliance-portal/.gitattributes
vendored
3
apps/compliance-portal/.gitattributes
vendored
@@ -1,2 +1 @@
|
||||
__generated__/*.graphql linguist-generated
|
||||
__generated__/*.js linguist-generated
|
||||
**/__generated__/** linguist-generated
|
||||
|
||||
@@ -28,5 +28,10 @@
|
||||
"requests": {
|
||||
"title": "Data Requests",
|
||||
"newRequest": "New Request"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page not found",
|
||||
"description": "The page you are looking for does not exist or has moved.",
|
||||
"backHome": "Back to home"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,10 @@
|
||||
"requests": {
|
||||
"title": "Demandes de données",
|
||||
"newRequest": "Nouvelle demande"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page introuvable",
|
||||
"description": "La page que vous recherchez n'existe pas ou a été déplacée.",
|
||||
"backHome": "Retour à l'accueil"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { EnvelopeIcon, GlobeSimpleIcon, MapPinSimpleIcon } from "@phosphor-icons
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { hostnameOf } from "#/lib/url/hostname";
|
||||
import { externalHref, hostnameOf } from "#/lib/url/hostname";
|
||||
|
||||
import type { OrganizationContactInfo_organization$key } from "./__generated__/OrganizationContactInfo_organization.graphql";
|
||||
import { organizationContactInfo } from "./variants";
|
||||
@@ -54,7 +54,7 @@ export function OrganizationContactInfo({ organizationKey }: OrganizationContact
|
||||
{hasWebsite && (
|
||||
<a
|
||||
className={link()}
|
||||
href={organization.websiteUrl}
|
||||
href={externalHref(organization.websiteUrl)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -12,12 +12,25 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Prepend https:// when a URL carries no http(s) scheme, so a protocol-less
|
||||
// value (e.g. "blaxel.ai") parses as absolute instead of being treated as a
|
||||
// relative path.
|
||||
function withHttpScheme(url: string): string {
|
||||
return /^https?:\/\//i.test(url) ? url : `https://${url}`;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return new URL(withHttpScheme(url)).hostname;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// Build a safe absolute href for an external link, normalizing the scheme so a
|
||||
// protocol-less value does not resolve as a relative link.
|
||||
export function externalHref(url: string): string {
|
||||
return withHttpScheme(url);
|
||||
}
|
||||
|
||||
42
apps/compliance-portal/src/pages/NotFoundPage.tsx
Normal file
42
apps/compliance-portal/src/pages/NotFoundPage.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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 { Link } from "@probo/ui/src/v2/Button/Link";
|
||||
import { Heading } from "@probo/ui/src/v2/typography/Heading";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { HeaderBand } from "#/components/HeaderBand/HeaderBand";
|
||||
|
||||
// Catch-all page for portal paths that match no route, so an unknown URL renders
|
||||
// an explicit not-found state inside the layout instead of an empty body.
|
||||
export default function NotFoundPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderBand>
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<Heading level={1} size={7} weight="medium" highContrast>
|
||||
{t("notFound.title")}
|
||||
</Heading>
|
||||
<Text size={2} color="neutral">
|
||||
{t("notFound.description")}
|
||||
</Text>
|
||||
<Link to="/" variant="soft" color="neutral" highContrast size={2}>
|
||||
{t("notFound.backHome")}
|
||||
</Link>
|
||||
</div>
|
||||
</HeaderBand>
|
||||
);
|
||||
}
|
||||
@@ -47,6 +47,10 @@ const routes = [
|
||||
path: "requests",
|
||||
Component: lazy(() => import("#/pages/RequestsPage")),
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
Component: lazy(() => import("#/pages/NotFoundPage")),
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
Reference in New Issue
Block a user