Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5c8391d
feat: add NSW stakeholder mandates
devin-ai-integration[bot] Aug 24, 2026
a3e7e47
feat(nsw): complete registration tracking and cargo parity
devin-ai-integration[bot] Aug 24, 2026
17a5d7a
feat: complete NSW parity registration and cargo surfaces
devin-ai-integration[bot] Aug 24, 2026
c67d716
feat: link mandate and stakeholder services
devin-ai-integration[bot] Aug 24, 2026
ed39654
docs(nsw): add nsw.gov.ng parity analysis
devin-ai-integration[bot] Aug 24, 2026
d93059d
feat: restore declaration shipment tracking
devin-ai-integration[bot] Aug 24, 2026
ba7c8ba
fix(cargo): report an ambiguous bill of lading distinctly from a miss…
devin-ai-integration[bot] Aug 24, 2026
2ade824
feat: add excise digital tax stamps domain
devin-ai-integration[bot] Aug 24, 2026
80b065b
fix: coarsen anonymous excise scan locations
devin-ai-integration[bot] Aug 24, 2026
eb37f6d
fix: harden excise lifecycle and settlement
devin-ai-integration[bot] Aug 24, 2026
e9faa7d
fix: fail closed without excise coordination
devin-ai-integration[bot] Aug 24, 2026
210eb5b
test: cover excise lifecycle safety
devin-ai-integration[bot] Aug 24, 2026
5e8ed25
Merge pull request #36 from munisp/devin/excise-tax-stamps
munisp Aug 24, 2026
3c0d358
feat: add regulatory obligation layer
devin-ai-integration[bot] Aug 24, 2026
d45d003
fix: harden regulatory clearance controls
devin-ai-integration[bot] Aug 24, 2026
8031591
fix: wire quota ledger accounts
devin-ai-integration[bot] Aug 24, 2026
f837ac6
fix: version quota allocation retries
devin-ai-integration[bot] Aug 24, 2026
6465a1b
fix: enforce regulatory clearance obligations
devin-ai-integration[bot] Aug 24, 2026
bf65523
Merge pull request #37 from munisp/devin/single-window-regulatory-layer
munisp Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import NotFound from "@/pages/NotFound";
import { Route, Switch, Redirect } from "wouter";
import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext";
import { AdminGuard, CustomsGuard, OGAGuard, FinanceGuard, SecurityGuard, ExecutiveGuard } from "./components/RoleGuard";
import RoleGuard, { AdminGuard, CustomsGuard, OGAGuard, FinanceGuard, SecurityGuard, ExecutiveGuard } from "./components/RoleGuard";
import Home from "./pages/Home";
import TraderDashboard from "./pages/app/TraderDashboard";
import NewDeclaration from "./pages/app/NewDeclaration";
Expand Down Expand Up @@ -100,6 +100,12 @@ const AdminProductionChecklist = lazy(() => import('./pages/app/AdminProductionC
const ServiceHealth = lazy(() => import('./pages/app/ServiceHealth'));
const AuditLog = lazy(() => import('./pages/app/AuditLog'));
const CertVerify = lazy(() => import('./pages/public/CertVerify'));
const ApplicationTracker = lazy(() => import('./pages/public/ApplicationTracker'));
const ShipmentTracker = lazy(() => import('./pages/public/ShipmentTracker'));
const PermitValidation = lazy(() => import('./pages/public/PermitValidation'));
const StakeholderRegistration = lazy(() => import('./pages/app/StakeholderRegistration'));
const StakeholderRegistrationReview = lazy(() => import('./pages/app/StakeholderRegistrationReview'));
const MandateManagement = lazy(() => import('./pages/app/MandateManagement'));
const SystemStatus = lazy(() => import('./pages/SystemStatus'));
const DemoLogin = lazy(() => import('./pages/DemoLogin'));
const NLFinancialQuery = lazy(() => import('./pages/app/NLFinancialQuery'));
Expand Down Expand Up @@ -167,9 +173,27 @@ function Router() {
<Route path="/" component={Home} />

{/* Sprint 80 — Public certificate verification (no auth required, QR-scannable) */}
<Route path="/verify/permit/:permitNumber">
<Suspense fallback={<LazyFallback />}><PermitValidation /></Suspense>
</Route>
<Route path="/verify/permit">
<Suspense fallback={<LazyFallback />}><PermitValidation /></Suspense>
</Route>
<Route path="/verify/:certNumber">
<Suspense fallback={<LazyFallback />}><CertVerify /></Suspense>
</Route>
<Route path="/track-application/:referenceNumber">
<Suspense fallback={<LazyFallback />}><ApplicationTracker /></Suspense>
</Route>
<Route path="/track-application">
<Suspense fallback={<LazyFallback />}><ApplicationTracker /></Suspense>
</Route>
<Route path="/track-shipment/:declarationRef">
<Suspense fallback={<LazyFallback />}><ShipmentTracker /></Suspense>
</Route>
<Route path="/track-shipment">
<Suspense fallback={<LazyFallback />}><ShipmentTracker /></Suspense>
</Route>

{/* Public system status page — no authentication required */}
<Route path="/status">
Expand Down Expand Up @@ -430,6 +454,17 @@ function Router() {
<Route path="/app/onboarding">
<Suspense fallback={<LazyFallback />}><TraderOnboarding /></Suspense>
</Route>
<Route path="/app/stakeholder-registration">
<Suspense fallback={<LazyFallback />}><StakeholderRegistration /></Suspense>
</Route>
<Route path="/app/mandates">
<Suspense fallback={<LazyFallback />}><MandateManagement /></Suspense>
</Route>
<Route path="/app/admin/stakeholder-registrations">
<RoleGuard allowedRoles={["admin", "customs_officer", "oga_officer"]}>
<Suspense fallback={<LazyFallback />}><StakeholderRegistrationReview /></Suspense>
</RoleGuard>
</Route>

{/* Sprint 68 — OpenAPI Explorer */}
<Route path="/app/developer/api-explorer">
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ function getNavGroups(role: string): NavGroup[] {
{ icon: ShieldCheck, label: "Trusted Trader Programme", path: "/app/admin/aeo" },
{ icon: Award, label: "AEO Applications", path: "/app/aeo/applications" },
{ icon: UserCheck, label: "Trader Verification", path: "/app/admin/kyc-review" },
{ icon: Users, label: "Stakeholder Registration Review", path: "/app/admin/stakeholder-registrations" },
{ icon: BarChart3, label: "Performance Reports", path: "/app/admin/analytics" },
],
},
Expand Down Expand Up @@ -270,6 +271,7 @@ function getNavGroups(role: string): NavGroup[] {
{ icon: Camera, label: "Cargo Inspection", path: "/app/customs/vision" },
{ icon: ScanLine, label: "Vision Batch Analysis", path: "/app/customs/vision-batch" },
{ icon: Package, label: "Agency Permits", path: "/app/oga" },
...(role === "customs_officer" ? [{ icon: Users, label: "Stakeholder Registration Review", path: "/app/admin/stakeholder-registrations" }] : []),
{ icon: ClipboardCheck, label: "Post-Clearance Review", path: "/app/customs/audit" },
],
},
Expand Down Expand Up @@ -310,6 +312,7 @@ function getNavGroups(role: string): NavGroup[] {
{ icon: CalendarClock, label: "Expiry Calendar", path: "/app/oga/expiry-calendar" },
{ icon: FileText, label: "Related Declarations", path: "/app/customs" },
{ icon: Globe, label: "Rules of Origin (AfCFTA)", path: "/app/oga/rules-of-origin" },
{ icon: Users, label: "Stakeholder Registration Review", path: "/app/admin/stakeholder-registrations" },
],
},
{
Expand Down Expand Up @@ -366,6 +369,8 @@ function getNavGroups(role: string): NavGroup[] {
{ icon: Layers, label: "Payment Queue", path: "/app/trader/payment-queue" },
{ icon: Fingerprint, label: "Identity Verification", path: "/app/trader/kyc" },
{ icon: Anchor, label: "Cargo Tracking Map", path: "/app/geo/cargo-tracking" },
{ icon: UserCheck, label: "Stakeholder Registration", path: "/app/stakeholder-registration" },
{ icon: Users, label: "Mandate Management", path: "/app/mandates" },
{ icon: CheckCircle, label: "Account Setup Wizard", path: "/app/onboarding" },
],
},
Expand Down
10 changes: 5 additions & 5 deletions client/src/hooks/useVesselWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import { useEffect, useRef, useState, useCallback } from "react";

export type VesselPosition = {
mmsi: string;
vesselName: string;
vesselName: string | null;
lat: number;
lon: number;
speed: number;
heading: number;
status: string;
riskFlag: "green" | "amber" | "red";
speed: number | null;
heading: number | null;
status: string | null;
riskFlag: "green" | "amber" | "red" | null;
lastUpdate: string;
};

Expand Down
1 change: 1 addition & 0 deletions client/src/lib/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "../../../server/routers";

export const trpc = createTRPCReact<AppRouter>();
export type { AppRouter };
60 changes: 60 additions & 0 deletions client/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ const PORTALS = [
},
];

const PUBLIC_UTILITIES = [
{
title: "Track your Application",
description: "Check the status and latest update for a submitted application.",
href: "/track-application",
icon: Clock,
},
{
title: "Permit / COO Validation",
description: "Verify a government permit or certificate of origin before relying on it.",
href: "/verify/permit",
icon: ShieldCheck,
},
{
title: "Track your Shipment",
description: "Find the latest available vessel position for a declaration or UCR.",
href: "/track-shipment",
icon: MapPin,
},
];

const CAPABILITIES = [
{
icon: Clock,
Expand Down Expand Up @@ -281,6 +302,45 @@ export default function Home() {
</div>
</section>

{/* ── Public eServices ── */}
<section className="py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-10">
<Badge className="mb-4 text-xs tracking-wider uppercase" variant="outline">Public eServices</Badge>
<h2
className="text-3xl lg:text-4xl font-black text-foreground mb-3"
style={{ fontFamily: "var(--font-display)" }}
>
Verify and track with confidence
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
Use official registry services to check an application or validate a trade document.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-4xl mx-auto">
{PUBLIC_UTILITIES.map((utility) => {
const Icon = utility.icon;
return (
<Link key={utility.href} href={utility.href}>
<div className="group flex h-full items-start gap-4 rounded-2xl border border-border/60 bg-card p-6 transition-all hover:border-[var(--color-gold)] hover:shadow-lg cursor-pointer">
<div className="rounded-xl bg-[var(--color-gold)]/10 p-3 text-[var(--color-gold)]">
<Icon className="h-5 w-5" />
</div>
<div>
<h3 className="font-bold text-foreground group-hover:text-[var(--color-gold)] transition-colors">
{utility.title}
</h3>
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">{utility.description}</p>
</div>
<ChevronRight className="ml-auto mt-1 h-4 w-4 shrink-0 text-muted-foreground group-hover:text-[var(--color-gold)]" />
</div>
</Link>
);
})}
</div>
</div>
</section>

{/* ── Portal Access ── */}
<section className="py-20 bg-muted/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
Expand Down
Loading