Files
pn-new-crm/src/app/(portal)/portal/my-yachts/page.tsx
Matt 0ab96d74a8 feat(deps): Tailwind 3 → 4 + swap tailwindcss-animate for tw-animate-css
Ran the official @tailwindcss/upgrade tool:
- tailwind.config.ts → @theme directive in globals.css
- @tailwind base/components/utilities → @import 'tailwindcss'
- postcss.config switched from tailwindcss + autoprefixer to
  @tailwindcss/postcss (autoprefixer baked in)
- focus-visible:outline-none → focus-visible:outline-hidden (the v3
  utility was a footgun — outline still showed in forced-colors mode)

Reverted the migration tool's over-zealous variant="outline" →
variant="outline-solid" rename on CVA prop values; that rename was
meant for the Tailwind `outline:` utility, not our Button/Badge
component variants.

Swapped tailwindcss-animate (v3-style JS plugin) for tw-animate-css
(v4-native @import). Same utility surface (animate-spin, animate-in,
etc.), one fewer JS plugin in the bundle.

Fixed the upgrade tool's malformed dark variant
(@custom-variant dark (&:is(class *)) — `class` was being parsed as
a tag) to canonical &:where(.dark, .dark *).

Verified: tsc 0 errors, eslint 0 errors (16 pre-existing warnings),
vitest 1315/1315, next build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:14:38 +02:00

78 lines
3.1 KiB
TypeScript

import { redirect } from 'next/navigation';
import { Sailboat } from 'lucide-react';
import type { Metadata } from 'next';
import { getPortalSession } from '@/lib/portal/auth';
import { getPortalUserYachts } from '@/lib/services/portal.service';
import { Badge } from '@/components/ui/badge';
export const metadata: Metadata = { title: 'My Yachts' };
const STATUS_COLORS: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
active: 'default',
retired: 'secondary',
sold_away: 'outline',
};
export default async function PortalMyYachtsPage() {
const session = await getPortalSession();
if (!session) redirect('/portal/login');
const yachts = await getPortalUserYachts(session.clientId, session.portId);
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">My Yachts</h1>
<p className="text-sm text-gray-500 mt-1">Vessels you own directly or through a company</p>
</div>
{yachts.length === 0 ? (
<div className="bg-white rounded-lg border p-12 text-center">
<Sailboat className="h-10 w-10 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500 font-medium">No yachts on file</p>
<p className="text-sm text-gray-400 mt-1">
Yachts owned by you or a company you are a member of will appear here.
</p>
</div>
) : (
<div className="space-y-3">
{yachts.map((y) => (
<div key={y.id} className="bg-white rounded-lg border p-5">
<div className="flex items-start gap-4">
<Sailboat className="h-5 w-5 text-gray-400 mt-0.5 shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div className="flex-1 min-w-0">
<p className="font-medium text-gray-900 truncate">{y.name}</p>
<p className="text-sm text-gray-500 mt-0.5">
{y.hullNumber ? `Hull ${y.hullNumber}` : 'No hull number'}
{y.flag ? ` · ${y.flag}` : ''}
{y.yearBuilt ? ` · ${y.yearBuilt}` : ''}
</p>
{y.ownerContext === 'company' && y.ownerCompanyName && (
<p className="text-xs text-[#1e2844] mt-2">Owned by {y.ownerCompanyName}</p>
)}
</div>
<Badge variant={STATUS_COLORS[y.status] ?? 'default'}>
{y.status.replace(/_/g, ' ')}
</Badge>
</div>
{(y.lengthFt || y.widthFt || y.registration) && (
<div className="flex flex-wrap gap-3 mt-3 text-xs text-gray-400">
{y.registration && <span>Reg: {y.registration}</span>}
{y.lengthFt && <span>Length: {y.lengthFt}ft</span>}
{y.widthFt && <span>Beam: {y.widthFt}ft</span>}
</div>
)}
</div>
</div>
</div>
))}
</div>
)}
</div>
);
}