fix(web): add missing loading and error states to MfaPromptBanner, Step1Identity, MobileSummaryClient

- MfaPromptBanner: silently hide on query error (non-critical advisory banner)
- Step1Identity: show skeleton placeholders while blueprint list loads
- MobileSummaryClient: add error state with retry button for dashboard queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 23:22:18 +02:00
parent c9be7c9bbf
commit 74ed45ddfc
3 changed files with 47 additions and 14 deletions
@@ -8,7 +8,12 @@ import { MobileProjectCard } from "./MobileProjectCard.js";
import { EmptyState } from "~/components/ui/EmptyState.js";
export function MobileSummaryClient() {
const { data: overview, isLoading: overviewLoading } = trpc.dashboard.getOverview.useQuery(undefined, {
const {
data: overview,
isLoading: overviewLoading,
isError: overviewError,
refetch: refetchOverview,
} = trpc.dashboard.getOverview.useQuery(undefined, {
staleTime: 60_000,
});
@@ -16,18 +21,23 @@ export function MobileSummaryClient() {
const { data: projectsData, isLoading: projectsLoading } = (trpc.project.list.useQuery as any)(
{ limit: 5, status: "ACTIVE" },
{ staleTime: 60_000 },
) as { data: { projects: Array<{ id: string; shortCode: string; name: string; status: string }> } | undefined; isLoading: boolean };
) as {
data:
| { projects: Array<{ id: string; shortCode: string; name: string; status: string }> }
| undefined;
isLoading: boolean;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { data: demandData } = (trpc.dashboard.getDemand.useQuery as any)(
undefined,
{ staleTime: 60_000 },
) as { data: { openDemandCount?: number; openDemands?: unknown[] } | undefined };
const { data: demandData } = (trpc.dashboard.getDemand.useQuery as any)(undefined, {
staleTime: 60_000,
}) as { data: { openDemandCount?: number; openDemands?: unknown[] } | undefined };
const projects = projectsData?.projects ?? [];
const openDemandCount = demandData?.openDemandCount ?? demandData?.openDemands?.length ?? 0;
const isLoading = overviewLoading || projectsLoading;
const isError = overviewError;
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
@@ -40,7 +50,20 @@ export function MobileSummaryClient() {
</div>
<div className="max-w-[428px] mx-auto px-4 py-5 space-y-4">
{isLoading ? (
{isError ? (
<div className="rounded-2xl border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-950/30 p-6 text-center">
<p className="text-sm font-medium text-red-700 dark:text-red-300">
Failed to load dashboard data
</p>
<button
type="button"
onClick={() => void refetchOverview()}
className="mt-3 rounded-lg bg-red-600 px-4 py-2 text-xs font-medium text-white hover:bg-red-700"
>
Retry
</button>
</div>
) : isLoading ? (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-32 shimmer-skeleton rounded-2xl" />
@@ -64,7 +87,9 @@ export function MobileSummaryClient() {
className="flex items-center gap-3 rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-950/30 px-4 py-3"
>
<div className="h-8 w-8 shrink-0 rounded-full bg-amber-100 dark:bg-amber-900/50 flex items-center justify-center">
<span className="text-sm font-bold text-amber-700 dark:text-amber-300">{openDemandCount}</span>
<span className="text-sm font-bold text-amber-700 dark:text-amber-300">
{openDemandCount}
</span>
</div>
<div>
<div className="text-sm font-semibold text-amber-800 dark:text-amber-300">