Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { redirect } from 'next/navigation';
|
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate
R2-M11: mobile More-sheet missing 4 destinations. Added Reservations,
Notifications, Residential, Website analytics — anyone using mobile
chrome to triage on the go can now reach those domains.
R2-M12: portal had no profile / change-password surface. New
/portal/profile page with read-only contact details + a
ChangePasswordForm component, backed by a new POST
/api/portal/auth/change-password endpoint and
changePortalPassword() service function. Audits both ok and failure
cases at warning severity. Added Profile to PortalNav.
R2-M1: portal dashboard "My Memberships" tile had no href and no
/portal/memberships route — dead-end on tap. Hidden until a
memberships page ships; the count remains in the underlying data.
R2-M7: InlineStagePicker never sent override:true so users with
interests.override_stage couldn't actually use the perm from the
inline chip — they had to fall back to the modal picker. Now the
picker auto-detects when a transition isn't legal AND the user has
override_stage, sets override:true, and supplies a default reason.
Frontend M2: hard-delete-dialog confirm stage now has a "Send a new
code" link in case the original expired before the user could enter
it. Avoids forcing a full Cancel + reopen.
Frontend M4: audit-log-list date-range validation. From > To now
shows an inline error and skips the request rather than firing an
empty-range query that surfaces "no entries found".
R2-M6: external-EOI route now requires interests.edit AND
documents.upload_signed (defense-in-depth) — uploading a signed EOI
mutates interest state, so the upload-signed perm alone shouldn't
let a custom role flip an interest.
1175/1175 vitest passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
|
|
|
import { Anchor, FileText, Receipt, Sailboat, CalendarCheck } from 'lucide-react';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import type { Metadata } from 'next';
|
|
|
|
|
|
|
|
|
|
import { getPortalSession } from '@/lib/portal/auth';
|
|
|
|
|
import { getPortalDashboard } from '@/lib/services/portal.service';
|
|
|
|
|
import { PortalCard } from '@/components/portal/portal-card';
|
|
|
|
|
|
|
|
|
|
export const metadata: Metadata = { title: 'Dashboard' };
|
|
|
|
|
|
|
|
|
|
export default async function PortalDashboardPage() {
|
|
|
|
|
const session = await getPortalSession();
|
|
|
|
|
if (!session) redirect('/portal/login');
|
|
|
|
|
|
|
|
|
|
const dashboard = await getPortalDashboard(session.clientId, session.portId);
|
|
|
|
|
if (!dashboard) redirect('/portal/login');
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-2xl font-semibold text-gray-900">
|
|
|
|
|
Welcome back, {dashboard.client.fullName.split(' ')[0]}
|
|
|
|
|
</h1>
|
2026-04-24 14:43:12 +02:00
|
|
|
{dashboard.client.nationality && (
|
|
|
|
|
<p className="text-sm text-gray-400 mt-0.5">{dashboard.client.nationality}</p>
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-24 14:43:12 +02:00
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
<PortalCard
|
|
|
|
|
title="Berth Interests"
|
|
|
|
|
value={dashboard.counts.interests}
|
|
|
|
|
description="Your berth enquiries and applications"
|
|
|
|
|
icon={Anchor}
|
|
|
|
|
href="/portal/interests"
|
|
|
|
|
/>
|
|
|
|
|
<PortalCard
|
|
|
|
|
title="Documents"
|
|
|
|
|
value={dashboard.counts.documents}
|
|
|
|
|
description="Contracts, EOIs and signed agreements"
|
|
|
|
|
icon={FileText}
|
|
|
|
|
href="/portal/documents"
|
|
|
|
|
/>
|
|
|
|
|
<PortalCard
|
|
|
|
|
title="Invoices"
|
|
|
|
|
value={dashboard.counts.invoices}
|
|
|
|
|
description="Billing statements and payment history"
|
|
|
|
|
icon={Receipt}
|
|
|
|
|
href="/portal/invoices"
|
|
|
|
|
/>
|
2026-04-24 14:43:12 +02:00
|
|
|
<PortalCard
|
|
|
|
|
title="My Yachts"
|
|
|
|
|
value={dashboard.counts.yachts}
|
|
|
|
|
description="Vessels you own directly or through a company"
|
|
|
|
|
icon={Sailboat}
|
|
|
|
|
href="/portal/my-yachts"
|
|
|
|
|
/>
|
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate
R2-M11: mobile More-sheet missing 4 destinations. Added Reservations,
Notifications, Residential, Website analytics — anyone using mobile
chrome to triage on the go can now reach those domains.
R2-M12: portal had no profile / change-password surface. New
/portal/profile page with read-only contact details + a
ChangePasswordForm component, backed by a new POST
/api/portal/auth/change-password endpoint and
changePortalPassword() service function. Audits both ok and failure
cases at warning severity. Added Profile to PortalNav.
R2-M1: portal dashboard "My Memberships" tile had no href and no
/portal/memberships route — dead-end on tap. Hidden until a
memberships page ships; the count remains in the underlying data.
R2-M7: InlineStagePicker never sent override:true so users with
interests.override_stage couldn't actually use the perm from the
inline chip — they had to fall back to the modal picker. Now the
picker auto-detects when a transition isn't legal AND the user has
override_stage, sets override:true, and supplies a default reason.
Frontend M2: hard-delete-dialog confirm stage now has a "Send a new
code" link in case the original expired before the user could enter
it. Avoids forcing a full Cancel + reopen.
Frontend M4: audit-log-list date-range validation. From > To now
shows an inline error and skips the request rather than firing an
empty-range query that surfaces "no entries found".
R2-M6: external-EOI route now requires interests.edit AND
documents.upload_signed (defense-in-depth) — uploading a signed EOI
mutates interest state, so the upload-signed perm alone shouldn't
let a custom role flip an interest.
1175/1175 vitest passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
|
|
|
{/* My Memberships tile was a dead-end (no href, no /portal/memberships
|
|
|
|
|
route). Hidden until a memberships page ships. The count is still
|
|
|
|
|
available in the underlying dashboard data when needed. */}
|
2026-04-24 14:43:12 +02:00
|
|
|
<PortalCard
|
|
|
|
|
title="My Active Reservations"
|
|
|
|
|
value={dashboard.counts.activeReservations}
|
|
|
|
|
description="Current and pending berth reservations"
|
|
|
|
|
icon={CalendarCheck}
|
|
|
|
|
href="/portal/my-reservations"
|
|
|
|
|
/>
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="bg-white rounded-lg border p-6">
|
|
|
|
|
<h2 className="text-sm font-medium text-gray-700 mb-1">Need assistance?</h2>
|
|
|
|
|
<p className="text-sm text-gray-500">
|
2026-04-24 14:43:12 +02:00
|
|
|
Contact the {dashboard.port.name} team directly. This portal provides a read-only view of
|
|
|
|
|
your account. All changes must be made through your port contact.
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|