fix(F17 ext): apply DetailNotFound to clients/yachts/companies/berths
Refactored the interest-detail 404 pattern into a reusable `<DetailNotFound>` component and applied it to the four other entity detail pages. Pre-fix, navigating to a wrong-port or stale entity URL silently rendered the layout shell with empty tabs on: - /[portSlug]/clients/[id] - /[portSlug]/yachts/[id] - /[portSlug]/companies/[id] - /[portSlug]/berths/[id] All four now route a 404/403 response into an explicit "<Entity> not found" / "No access" EmptyState with a back-to-list CTA, and the TanStack Query retry policy short-circuits 404/403s so the empty state appears immediately. 1373/1373 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
src/components/shared/detail-not-found.tsx
Normal file
48
src/components/shared/detail-not-found.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { SearchX } from 'lucide-react';
|
||||
|
||||
import { EmptyState } from '@/components/shared/empty-state';
|
||||
|
||||
interface DetailNotFoundProps {
|
||||
/** "interest", "client", "yacht", etc. — used to build the copy. */
|
||||
entity: string;
|
||||
/** Plural list path back-link. e.g. "/port-x/clients". */
|
||||
backHref: string;
|
||||
/** Plural label for the back button, e.g. "Back to clients". */
|
||||
backLabel: string;
|
||||
/** HTTP status code from the failed fetch (404 vs 403 changes copy). */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an explicit "not found" / "no access" panel for entity detail
|
||||
* pages whose data fetch failed. Replaces the prior silent-list-shell
|
||||
* behaviour where a wrong-port URL paste rendered the navigation chrome
|
||||
* with empty tabs and no error message. Post-audit F17.
|
||||
*/
|
||||
export function DetailNotFound({ entity, backHref, backLabel, status }: DetailNotFoundProps) {
|
||||
const denied = status === 403;
|
||||
return (
|
||||
<EmptyState
|
||||
icon={SearchX}
|
||||
title={denied ? `No access to this ${entity}` : `${capitalize(entity)} not found`}
|
||||
description={
|
||||
denied
|
||||
? `You do not have permission to view this ${entity} in this port.`
|
||||
: `It may have been removed, archived, or it belongs to a different port. Use the back button or pick a different ${entity}.`
|
||||
}
|
||||
className="mt-16"
|
||||
action={{
|
||||
label: backLabel,
|
||||
onClick: () => {
|
||||
window.location.assign(backHref);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function capitalize(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
Reference in New Issue
Block a user