Put home section errors on a card surface
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>
This commit is contained in:
@@ -19,10 +19,10 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
|
||||
import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
|
||||
import type { ComplianceFrameworksSection_trustCenter$key } from "./__generated__/ComplianceFrameworksSection_trustCenter.graphql";
|
||||
@@ -60,11 +60,7 @@ export function ComplianceFrameworksSection({ trustCenterKey }: ComplianceFramew
|
||||
// The data comes from the preloaded HomePageQuery, so there is no local
|
||||
// refetch to clear a field error — reload the page to recover.
|
||||
<HomeSection title={t("home.sections.compliance")}>
|
||||
<InlineError
|
||||
message={t("errors.inline.message")}
|
||||
retryLabel={t("errors.inline.retry")}
|
||||
onRetry={() => window.location.reload()}
|
||||
/>
|
||||
<InlineErrorCard onRetry={() => window.location.reload()} />
|
||||
</HomeSection>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
|
||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
||||
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
|
||||
import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
import { MailingListUpdateListItem } from "#/components/MailingListUpdateListItem/MailingListUpdateListItem";
|
||||
import { dotPatternStyle } from "#/components/MediaTile/variants";
|
||||
@@ -60,11 +60,7 @@ export function RecentUpdatesSection({ trustCenterKey }: RecentUpdatesSectionPro
|
||||
// The data comes from the preloaded HomePageQuery, so there is no local
|
||||
// refetch to clear a field error — reload the page to recover.
|
||||
<HomeSection title={t("home.sections.recentUpdates")}>
|
||||
<InlineError
|
||||
message={t("errors.inline.message")}
|
||||
retryLabel={t("errors.inline.retry")}
|
||||
onRetry={() => window.location.reload()}
|
||||
/>
|
||||
<InlineErrorCard onRetry={() => window.location.reload()} />
|
||||
</HomeSection>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { ErrorBoundary } from "@probo/ui/src/v2/ErrorBoundary/ErrorBoundary";
|
||||
import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { InlineErrorCard } from "#/components/errors/InlineErrorCard";
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
|
||||
import type { TrustedBySection_trustCenter$key } from "./__generated__/TrustedBySection_trustCenter.graphql";
|
||||
@@ -58,11 +58,7 @@ export function TrustedBySection({ trustCenterKey }: TrustedBySectionProps) {
|
||||
// The data comes from the preloaded HomePageQuery, so there is no local
|
||||
// refetch to clear a field error — reload the page to recover.
|
||||
<HomeSection title={t("home.sections.trustedBy")}>
|
||||
<InlineError
|
||||
message={t("errors.inline.message")}
|
||||
retryLabel={t("errors.inline.retry")}
|
||||
onRetry={() => window.location.reload()}
|
||||
/>
|
||||
<InlineErrorCard onRetry={() => window.location.reload()} />
|
||||
</HomeSection>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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 { Card } from "@probo/ui/src/v2/Card/Card";
|
||||
import { InlineError } from "@probo/ui/src/v2/InlineError/InlineError";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface InlineErrorCardProps {
|
||||
// Retry handler. When omitted, the retry action is hidden.
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
// The standard section/list error fallback: the vertical InlineError on a soft
|
||||
// card surface, so a failed region reads as intentional content rather than
|
||||
// floating text. Row-level failures use the bare horizontal InlineError.
|
||||
export function InlineErrorCard({ onRetry }: InlineErrorCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card variant="soft" padding={4}>
|
||||
<InlineError
|
||||
message={t("errors.inline.message")}
|
||||
retryLabel={t("errors.inline.retry")}
|
||||
onRetry={onRetry}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -25,11 +25,12 @@ interface ListErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// Contains a list/section field error to an inline fallback with a working
|
||||
// retry. The boundary only resets *after* the caller's refetch settles (via the
|
||||
// `done` callback bumping its key), so remounting reads the refreshed store
|
||||
// instead of racing the in-flight request back into the same error. See
|
||||
// contrib/claude/error-handling.md.
|
||||
// Contains a list field error to an inline fallback with a working retry. A
|
||||
// standalone list page has no section framing, so the fallback is a bare
|
||||
// InlineError (no card). The boundary only resets *after* the caller's refetch
|
||||
// settles (via the `done` callback bumping its key), so remounting reads the
|
||||
// refreshed store instead of racing the in-flight request back into the same
|
||||
// error. See contrib/claude/error-handling.md.
|
||||
export function ListErrorBoundary({ onRetry, children }: ListErrorBoundaryProps) {
|
||||
const { t } = useTranslation();
|
||||
const [resetToken, setResetToken] = useState(0);
|
||||
|
||||
@@ -26,6 +26,8 @@ import { type FetchFunction, type GraphQLResponse } from "relay-runtime";
|
||||
const isRequestLevel = (error: GraphQLError) =>
|
||||
error.path === undefined || error.path === null || error.path.length === 0;
|
||||
|
||||
type ErrorResponse = GraphQLResponse & { errors?: GraphQLError[] };
|
||||
|
||||
// The portal fetch only throws for request-level failures. Everything else
|
||||
// (field-level errors) flows through to Relay untouched.
|
||||
export const makeFetchQuery = (endpoint: string): FetchFunction => {
|
||||
@@ -82,9 +84,7 @@ export const makeFetchQuery = (endpoint: string): FetchFunction => {
|
||||
throw new InternalServerError();
|
||||
}
|
||||
|
||||
const json = (await response.json()) as GraphQLResponse & {
|
||||
errors?: GraphQLError[];
|
||||
};
|
||||
const json = (await response.json()) as ErrorResponse;
|
||||
|
||||
if (json.errors) {
|
||||
// An unauthenticated session is always a global concern: the backend
|
||||
|
||||
Reference in New Issue
Block a user