fix(F17 ext): apply DetailNotFound to clients/yachts/companies/berths
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m12s
Build & Push Docker Images / build-and-push (push) Successful in 4m44s

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:
2026-05-14 23:06:36 +02:00
parent 608641c23b
commit 3e78c2d4ab
6 changed files with 142 additions and 27 deletions

View File

@@ -3,10 +3,9 @@
import { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useParams } from 'next/navigation';
import { SearchX } from 'lucide-react';
import { DetailLayout } from '@/components/shared/detail-layout';
import { EmptyState } from '@/components/shared/empty-state';
import { DetailNotFound } from '@/components/shared/detail-not-found';
import { InterestDetailHeader } from '@/components/interests/interest-detail-header';
import { getInterestTabs } from '@/components/interests/interest-tabs';
import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider';
@@ -129,29 +128,14 @@ export function InterestDetail({ interestId, currentUserId }: InterestDetailProp
);
// F17: explicit "not found" state when the API 404'd or 403'd.
// Pre-fix the page silently rendered the layout shell with empty tabs,
// leaving users unsure whether the interest existed or just hadn't
// loaded. Cross-port URL pastes now land here with a clear message.
if (error && !isLoading) {
const status = (error as { status?: number } | null | undefined)?.status;
return (
<EmptyState
icon={SearchX}
title={status === 403 ? 'No access to this interest' : 'Interest not found'}
description={
status === 403
? 'You do not have permission to view this interest in this port.'
: 'It may have been removed, archived, or it belongs to a different port. Use the back button or pick a different interest.'
}
className="mt-16"
action={{
label: 'Back to interests',
// EmptyState only knows about onClick; render a Link-styled
// button below so back-nav works without JS-routing surprises.
onClick: () => {
window.location.assign(`/${portSlug}/interests`);
},
}}
<DetailNotFound
entity="interest"
backHref={`/${portSlug}/interests`}
backLabel="Back to interests"
status={status}
/>
);
}