Initial monorepo: shared package and DBIS, ICCC, OMNL, XOM portals
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
18
.eslintrc.cjs
Normal file
18
.eslintrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/** @type { import("eslint").Linter.Config } */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { node: true, es2022: true },
|
||||||
|
extends: ["eslint:recommended", "prettier"],
|
||||||
|
ignorePatterns: [
|
||||||
|
"node_modules",
|
||||||
|
".next",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
"coverage",
|
||||||
|
"*.config.js",
|
||||||
|
"*.config.cjs",
|
||||||
|
"*.config.mjs",
|
||||||
|
"*.config.ts",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# IDE / OS
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Debug / logs
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
*.pem
|
||||||
8
.prettierignore
Normal file
8
.prettierignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
*.min.js
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
9
.prettierrc.json
Normal file
9
.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
8
DBIS/.env.example
Normal file
8
DBIS/.env.example
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Next.js
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# OIDC / OAuth 2.0 (placeholder until identity provider is configured)
|
||||||
|
# OIDC_ISSUER=
|
||||||
|
# OIDC_CLIENT_ID=
|
||||||
|
# OIDC_CLIENT_SECRET=
|
||||||
|
# OIDC_REDIRECT_URI=
|
||||||
5
DBIS/.eslintrc.cjs
Normal file
5
DBIS/.eslintrc.cjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type { import("eslint").Linter.Config } */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ["../.eslintrc.cjs", "next/core-web-vitals"],
|
||||||
|
};
|
||||||
29
DBIS/README.md
Normal file
29
DBIS/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# DBIS Portal
|
||||||
|
|
||||||
|
Portal for the **Digital Bank of International Settlements**.
|
||||||
|
|
||||||
|
**Gitea repository:** https://gitea.d-bis.org/Gov_Web_Portals/DBIS.git Next-generation web application following the shared tech stack and policies.
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env.local` and set values. No secrets in repo.
|
||||||
|
|
||||||
|
## Standards
|
||||||
|
|
||||||
|
- [TECH_STACK.md](../TECH_STACK.md)
|
||||||
|
- [TECH_POLICIES.md](../TECH_POLICIES.md)
|
||||||
|
- [PORTAL_NAVIGATION.md](../PORTAL_NAVIGATION.md)
|
||||||
|
- [ORG_STRUCTURE.md](../ORG_STRUCTURE.md)
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
Same app structure as other portals: public routes, transparency, workflow (submit/apply/report/request), dashboard, auth. Entity-specific routes: Financial Bulletins, Monetary Operations.
|
||||||
55
DBIS/app/(auth)/login/page.tsx
Normal file
55
DBIS/app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
function LoginForm() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const returnUrl = searchParams.get("returnUrl") ?? "/dashboard";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12 max-w-md">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Sign in</h1>
|
||||||
|
<p className="mt-2 text-neutral-600">
|
||||||
|
Placeholder login. Replace with OIDC/OAuth 2.0 when identity provider is configured.
|
||||||
|
</p>
|
||||||
|
<form action="/api/auth/mock-login" method="POST" className="mt-6 space-y-4">
|
||||||
|
<input type="hidden" name="returnUrl" value={returnUrl} />
|
||||||
|
<div>
|
||||||
|
<label htmlFor="role" className="block text-sm font-medium text-neutral-700">
|
||||||
|
Role (dev placeholder)
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="role"
|
||||||
|
name="role"
|
||||||
|
className="mt-1 block w-full rounded border border-neutral-300 px-3 py-2 focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="member">Member</option>
|
||||||
|
<option value="staff">Staff</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<p className="mt-4 text-sm text-neutral-500">
|
||||||
|
<Link href={returnUrl} className="text-primary-600 hover:underline">
|
||||||
|
Return to previous page
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="container mx-auto px-4 py-12 max-w-md">Loading...</div>}>
|
||||||
|
<LoginForm />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
DBIS/app/(auth)/logout/page.tsx
Normal file
20
DBIS/app/(auth)/logout/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function LogoutPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
document.cookie = "portal-role=; path=/; max-age=0";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12 max-w-md">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Signed out</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">You have been signed out.</p>
|
||||||
|
<Link href="/" className="mt-6 inline-block text-primary-600 hover:underline">
|
||||||
|
Return to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/about/page.tsx
Normal file
8
DBIS/app/(public)/about/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AboutPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Mandate</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">About the Digital Bank of International Settlements.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/accreditation/page.tsx
Normal file
8
DBIS/app/(public)/accreditation/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AccreditationPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Accreditation</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Accreditation and credentials.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/contact/page.tsx
Normal file
8
DBIS/app/(public)/contact/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ContactPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Contact</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">General contact and enquiries.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/departments/page.tsx
Normal file
8
DBIS/app/(public)/departments/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DepartmentsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Departments</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Mission departments and programs.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/documents/page.tsx
Normal file
8
DBIS/app/(public)/documents/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DocumentsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Documents</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Library, standards, and directives.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/governance/page.tsx
Normal file
8
DBIS/app/(public)/governance/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function GovernancePage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Governance</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Charters, instruments, and governance.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/leadership/page.tsx
Normal file
8
DBIS/app/(public)/leadership/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function LeadershipPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Leadership</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Executive leadership and secretariat.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/membership/page.tsx
Normal file
8
DBIS/app/(public)/membership/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function MembershipPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Membership</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Member directory and accreditation.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/news/page.tsx
Normal file
8
DBIS/app/(public)/news/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function NewsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">News</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">News and announcements.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/org-chart/page.tsx
Normal file
8
DBIS/app/(public)/org-chart/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function OrgChartPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Org Chart</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Organizational structure.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/programs/page.tsx
Normal file
8
DBIS/app/(public)/programs/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ProgramsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Programs</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Programs and initiatives.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/publications/page.tsx
Normal file
8
DBIS/app/(public)/publications/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function PublicationsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Publications</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Publications and reports.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/regions/page.tsx
Normal file
8
DBIS/app/(public)/regions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RegionsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Regional Offices</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Americas, EMEA, APAC delegations and field missions.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/(public)/standards/page.tsx
Normal file
8
DBIS/app/(public)/standards/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function StandardsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Standards</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Standards and codes.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
DBIS/app/api/auth/mock-login/route.ts
Normal file
10
DBIS/app/api/auth/mock-login/route.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const form = await request.formData();
|
||||||
|
const returnUrl = (form.get("returnUrl") as string) ?? "/dashboard";
|
||||||
|
const role = (form.get("role") as string) ?? "member";
|
||||||
|
const res = NextResponse.redirect(new URL(returnUrl, request.url));
|
||||||
|
res.cookies.set("portal-role", role, { path: "/", httpOnly: true, maxAge: 60 * 60 * 24 });
|
||||||
|
return res;
|
||||||
|
}
|
||||||
8
DBIS/app/apply/page.tsx
Normal file
8
DBIS/app/apply/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ApplyPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Apply</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Apply for membership or accreditation.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
DBIS/app/dashboard/layout.tsx
Normal file
17
DBIS/app/dashboard/layout.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[60vh]">
|
||||||
|
<aside className="w-56 border-r border-neutral-200 bg-white p-4">
|
||||||
|
<nav className="space-y-1" aria-label="Dashboard navigation">
|
||||||
|
<Link href="/dashboard" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">Home</Link>
|
||||||
|
<Link href="/dashboard/submissions" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">My submissions</Link>
|
||||||
|
<Link href="/dashboard/requests" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">My requests</Link>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<div className="flex-1 p-6">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/dashboard/page.tsx
Normal file
8
DBIS/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DashboardPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Dashboard</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Role-based dashboard.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/dashboard/requests/page.tsx
Normal file
8
DBIS/app/dashboard/requests/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RequestsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">My requests</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Track your requests.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/dashboard/submissions/page.tsx
Normal file
8
DBIS/app/dashboard/submissions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SubmissionsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">My submissions</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Track your submissions and status.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/departments/monetary-operations/page.tsx
Normal file
8
DBIS/app/departments/monetary-operations/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function MonetaryOperationsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Monetary Operations</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Digital reserves and monetary operations.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/documents/bulletins/page.tsx
Normal file
8
DBIS/app/documents/bulletins/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function BulletinsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Financial Bulletins</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">DBIS financial bulletins and reports.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
DBIS/app/globals.css
Normal file
3
DBIS/app/globals.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
34
DBIS/app/layout.tsx
Normal file
34
DBIS/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import "./globals.css";
|
||||||
|
import { Header } from "@/components/layout/Header";
|
||||||
|
import { Footer } from "@/components/layout/Footer";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Digital Bank of International Settlements",
|
||||||
|
description: "Official portal of the Digital Bank of International Settlements",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className="min-h-screen flex flex-col bg-neutral-50 text-neutral-900 antialiased">
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary-600 focus:text-white focus:rounded"
|
||||||
|
>
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
<Header />
|
||||||
|
<main id="main-content" className="flex-1">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/membership/renewals/page.tsx
Normal file
8
DBIS/app/membership/renewals/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RenewalsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Renewals and Attestations</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Annual renewals for members.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
DBIS/app/page.tsx
Normal file
24
DBIS/app/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-4xl font-bold text-neutral-900">Digital Bank of International Settlements</h1>
|
||||||
|
<p className="mt-4 text-lg text-neutral-600">
|
||||||
|
Welcome to the official portal. Explore mandate, governance, documents, and services.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-wrap gap-4">
|
||||||
|
<a href="/about" className="inline-flex items-center rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
<a href="/documents" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Documents
|
||||||
|
</a>
|
||||||
|
<a href="/transparency" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Transparency
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/report/page.tsx
Normal file
8
DBIS/app/report/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ReportPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Report</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Report incidents or compliance matters.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/request/page.tsx
Normal file
8
DBIS/app/request/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RequestPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Request</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Request documents or support.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/submit/page.tsx
Normal file
8
DBIS/app/submit/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SubmitPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Submit</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Submission portal.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/audit/page.tsx
Normal file
8
DBIS/app/transparency/audit/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AuditPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Audit Reports</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Audit reports and findings tracker.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/data-protection/page.tsx
Normal file
8
DBIS/app/transparency/data-protection/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DataProtectionPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Data Protection and Records</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Records retention and data protection.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/ethics/page.tsx
Normal file
8
DBIS/app/transparency/ethics/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function EthicsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Ethics and Conduct</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Ethics and conduct policies.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/page.tsx
Normal file
8
DBIS/app/transparency/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function TransparencyPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Transparency</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Audit, ethics, whistleblower, sanctions, and data protection.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/sanctions/page.tsx
Normal file
8
DBIS/app/transparency/sanctions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SanctionsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Sanctions and Restricted Parties</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Eligibility and restricted parties.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
DBIS/app/transparency/whistleblower/page.tsx
Normal file
8
DBIS/app/transparency/whistleblower/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function WhistleblowerPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Whistleblower Intake</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Secure anonymous reporting.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
DBIS/components/layout/Footer.tsx
Normal file
48
DBIS/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { navItems } from "@/lib/nav-config";
|
||||||
|
|
||||||
|
const footerLinks = [
|
||||||
|
{ label: "About", href: "/about" },
|
||||||
|
{ label: "Documents", href: "/documents" },
|
||||||
|
{ label: "Transparency", href: "/transparency" },
|
||||||
|
{ label: "Contact", href: "/contact" },
|
||||||
|
{ label: "Regional Offices", href: "/regions" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function flatNavItems(items: typeof navItems): { label: string; href: string }[] {
|
||||||
|
const out: { label: string; href: string }[] = [];
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.children) {
|
||||||
|
for (const c of item.children) out.push({ label: c.label, href: c.href });
|
||||||
|
} else {
|
||||||
|
out.push({ label: item.label, href: item.href });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
const flat = flatNavItems(navItems);
|
||||||
|
const keyLinks = footerLinks.length ? footerLinks : flat.slice(0, 6);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-neutral-200 bg-white">
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<div className="flex flex-wrap gap-6">
|
||||||
|
{keyLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="text-sm text-neutral-600 hover:text-neutral-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-6 text-sm text-neutral-500">
|
||||||
|
Digital Bank of International Settlements. All portals follow the same tech stack and policies.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
58
DBIS/components/layout/Header.tsx
Normal file
58
DBIS/components/layout/Header.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { navItems } from "@/lib/nav-config";
|
||||||
|
import { NavDropdown } from "./NavDropdown";
|
||||||
|
import { MobileNav } from "./MobileNav";
|
||||||
|
|
||||||
|
export function Header() {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="sticky top-0 z-50 border-b border-neutral-200 bg-white">
|
||||||
|
<div className="container mx-auto flex h-16 items-center justify-between px-4">
|
||||||
|
<Link href="/" className="text-xl font-semibold text-neutral-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded">
|
||||||
|
DBIS
|
||||||
|
</Link>
|
||||||
|
<nav className="hidden md:flex md:items-center md:gap-1" aria-label="Main navigation">
|
||||||
|
{navItems.map((item) =>
|
||||||
|
item.children ? (
|
||||||
|
<NavDropdown key={item.href} item={item} />
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Link href="/login" className="hidden md:inline-flex rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset">
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="md:hidden rounded p-2 text-neutral-600 hover:bg-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||||
|
aria-expanded={mobileOpen}
|
||||||
|
aria-controls="mobile-nav"
|
||||||
|
onClick={() => setMobileOpen(!mobileOpen)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">{mobileOpen ? "Close menu" : "Open menu"}</span>
|
||||||
|
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
||||||
|
{mobileOpen ? (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
) : (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MobileNav items={navItems} open={mobileOpen} onClose={() => setMobileOpen(false)} />
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
DBIS/components/layout/MobileNav.tsx
Normal file
57
DBIS/components/layout/MobileNav.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import type { NavItem } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
export function MobileNav({
|
||||||
|
items,
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
items: NavItem[];
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
id="mobile-nav"
|
||||||
|
className="md:hidden border-t border-neutral-200 bg-white px-4 py-4"
|
||||||
|
aria-label="Mobile navigation"
|
||||||
|
>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{items.map((item) => (
|
||||||
|
<li key={item.href}>
|
||||||
|
{item.children ? (
|
||||||
|
<>
|
||||||
|
<span className="block px-3 py-2 text-sm font-semibold text-neutral-900">{item.label}</span>
|
||||||
|
<ul className="ml-4 space-y-1 border-l border-neutral-200 pl-4">
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<li key={child.href}>
|
||||||
|
<Link
|
||||||
|
href={child.href}
|
||||||
|
className="block px-3 py-2 text-sm text-neutral-600 hover:text-neutral-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset rounded"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{child.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={item.href}
|
||||||
|
className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
DBIS/components/layout/NavDropdown.tsx
Normal file
57
DBIS/components/layout/NavDropdown.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import type { NavItem } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
export function NavDropdown({ item }: { item: NavItem }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false);
|
||||||
|
}
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" ref={ref}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-1 rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset"
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-controls={`nav-menu-${item.label.replace(/\s/g, "-")}`}
|
||||||
|
id={`nav-button-${item.label.replace(/\s/g, "-")}`}
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<ul
|
||||||
|
id={`nav-menu-${item.label.replace(/\s/g, "-")}`}
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby={`nav-button-${item.label.replace(/\s/g, "-")}`}
|
||||||
|
className={`absolute left-0 top-full z-10 mt-1 min-w-[12rem] rounded-md border border-neutral-200 bg-white py-1 shadow-lg ${open ? "block" : "hidden"}`}
|
||||||
|
>
|
||||||
|
{item.children?.map((child) => (
|
||||||
|
<li key={child.href} role="none">
|
||||||
|
<Link
|
||||||
|
href={child.href}
|
||||||
|
role="menuitem"
|
||||||
|
className="block px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 focus:bg-neutral-100 focus:outline-none"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
{child.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
DBIS/lib/auth.ts
Normal file
10
DBIS/lib/auth.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { SessionOrNull } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder session. Replace with OIDC/OAuth 2.0 when identity provider is configured.
|
||||||
|
* TECH_POLICIES: use httpOnly cookies / secure token storage.
|
||||||
|
*/
|
||||||
|
export async function getSession(): Promise<SessionOrNull> {
|
||||||
|
// TODO: read session from cookie or header (e.g. NextAuth, OIDC)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
4
DBIS/lib/nav-config.ts
Normal file
4
DBIS/lib/nav-config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { getNavForPortal } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
export const portalId = "DBIS" as const;
|
||||||
|
export const navItems = getNavForPortal(portalId);
|
||||||
66
DBIS/lib/rbac.ts
Normal file
66
DBIS/lib/rbac.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import type { Role } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
/** Route prefixes that require any authenticated role */
|
||||||
|
const protectedPrefixes = [
|
||||||
|
"/dashboard",
|
||||||
|
"/submit",
|
||||||
|
"/apply",
|
||||||
|
"/report",
|
||||||
|
"/request",
|
||||||
|
"/membership/renewals",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Route prefixes that require admin role */
|
||||||
|
const adminOnlyPrefixes = ["/dashboard/admin"];
|
||||||
|
|
||||||
|
/** Routes that are always public (no auth) */
|
||||||
|
const publicPaths = [
|
||||||
|
"/",
|
||||||
|
"/about",
|
||||||
|
"/governance",
|
||||||
|
"/leadership",
|
||||||
|
"/org-chart",
|
||||||
|
"/departments",
|
||||||
|
"/programs",
|
||||||
|
"/membership",
|
||||||
|
"/accreditation",
|
||||||
|
"/documents",
|
||||||
|
"/standards",
|
||||||
|
"/news",
|
||||||
|
"/publications",
|
||||||
|
"/contact",
|
||||||
|
"/regions",
|
||||||
|
"/transparency",
|
||||||
|
"/login",
|
||||||
|
"/logout",
|
||||||
|
];
|
||||||
|
|
||||||
|
function pathIsPublic(pathname: string): boolean {
|
||||||
|
if (publicPaths.includes(pathname)) return true;
|
||||||
|
if (pathname.startsWith("/transparency/")) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathIsProtected(pathname: string): boolean {
|
||||||
|
return protectedPrefixes.some((p) => pathname === p || pathname.startsWith(p + "/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathIsAdminOnly(pathname: string): boolean {
|
||||||
|
return adminOnlyPrefixes.some((p) => pathname === p || pathname.startsWith(p + "/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canAccess(pathname: string, role: Role | null): boolean {
|
||||||
|
if (pathIsPublic(pathname)) return true;
|
||||||
|
if (role == null) return false;
|
||||||
|
if (pathIsAdminOnly(pathname)) return role === "admin";
|
||||||
|
if (pathIsProtected(pathname)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requiresAuth(pathname: string): boolean {
|
||||||
|
return pathIsProtected(pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requiresAdmin(pathname: string): boolean {
|
||||||
|
return pathIsAdminOnly(pathname);
|
||||||
|
}
|
||||||
39
DBIS/middleware.ts
Normal file
39
DBIS/middleware.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
import { canAccess, requiresAuth, requiresAdmin } from "./lib/rbac";
|
||||||
|
import type { Role } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
const LOGIN = "/login";
|
||||||
|
|
||||||
|
function getRoleFromCookie(request: NextRequest): Role | null {
|
||||||
|
const role = request.cookies.get("portal-role")?.value;
|
||||||
|
if (!role) return null;
|
||||||
|
const valid: Role[] = ["member", "judge", "clerk", "diplomat", "donor", "staff", "admin"];
|
||||||
|
return valid.includes(role as Role) ? (role as Role) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const pathname = request.nextUrl.pathname;
|
||||||
|
const role = getRoleFromCookie(request);
|
||||||
|
|
||||||
|
if (requiresAdmin(pathname) && role !== "admin") {
|
||||||
|
return NextResponse.redirect(new URL("/dashboard", request.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiresAuth(pathname)) {
|
||||||
|
if (role == null) {
|
||||||
|
const url = new URL(LOGIN, request.url);
|
||||||
|
url.searchParams.set("returnUrl", pathname);
|
||||||
|
return NextResponse.redirect(url);
|
||||||
|
}
|
||||||
|
if (!canAccess(pathname, role)) {
|
||||||
|
return NextResponse.redirect(new URL("/dashboard", request.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
|
||||||
|
};
|
||||||
5
DBIS/next-env.d.ts
vendored
Normal file
5
DBIS/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||||
32
DBIS/next.config.js
Normal file
32
DBIS/next.config.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/** @type { import('next').NextConfig } */
|
||||||
|
const nextConfig = {
|
||||||
|
transpilePackages: ["@public-web-portals/shared"],
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/:path*",
|
||||||
|
headers: [
|
||||||
|
{ key: "X-Frame-Options", value: "DENY" },
|
||||||
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
||||||
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||||
|
{
|
||||||
|
key: "Content-Security-Policy",
|
||||||
|
value: [
|
||||||
|
"default-src 'self'",
|
||||||
|
"script-src 'self'",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
"img-src 'self' data: https:",
|
||||||
|
"font-src 'self'",
|
||||||
|
"connect-src 'self'",
|
||||||
|
"frame-ancestors 'none'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"form-action 'self'",
|
||||||
|
].join("; "),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
32
DBIS/package.json
Normal file
32
DBIS/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "portal-dbis",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --port 3000",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start --port 3000",
|
||||||
|
"lint": "eslint . --max-warnings 0 --config ../.eslintrc.cjs",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@public-web-portals/shared": "workspace:*",
|
||||||
|
"next": "^14.2.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.10.0",
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
|
"autoprefixer": "^10.4.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-next": "^14.2.0",
|
||||||
|
"postcss": "^8.4.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
DBIS/postcss.config.js
Normal file
6
DBIS/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
15
DBIS/tailwind.config.ts
Normal file
15
DBIS/tailwind.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
import { designTokens } from "@public-web-portals/shared";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: designTokens as Record<string, unknown>,
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
30
DBIS/tsconfig.json
Normal file
30
DBIS/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"incremental": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
8
ICCC/.env.example
Normal file
8
ICCC/.env.example
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Next.js
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# OIDC / OAuth 2.0 (placeholder until identity provider is configured)
|
||||||
|
# OIDC_ISSUER=
|
||||||
|
# OIDC_CLIENT_ID=
|
||||||
|
# OIDC_CLIENT_SECRET=
|
||||||
|
# OIDC_REDIRECT_URI=
|
||||||
5
ICCC/.eslintrc.cjs
Normal file
5
ICCC/.eslintrc.cjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type { import("eslint").Linter.Config } */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ["../.eslintrc.cjs", "next/core-web-vitals"],
|
||||||
|
};
|
||||||
29
ICCC/README.md
Normal file
29
ICCC/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ICCC Portal
|
||||||
|
|
||||||
|
Portal for the **International Criminal Court of Commerce**.
|
||||||
|
|
||||||
|
**Gitea repository:** https://gitea.d-bis.org/Gov_Web_Portals/ICCC.git Next-generation web application following the shared tech stack and policies.
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env.local` and set values. No secrets in repo.
|
||||||
|
|
||||||
|
## Standards
|
||||||
|
|
||||||
|
- [TECH_STACK.md](../TECH_STACK.md)
|
||||||
|
- [TECH_POLICIES.md](../TECH_POLICIES.md)
|
||||||
|
- [PORTAL_NAVIGATION.md](../PORTAL_NAVIGATION.md)
|
||||||
|
- [ORG_STRUCTURE.md](../ORG_STRUCTURE.md)
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
Same app structure as other portals: public routes, transparency, workflow (submit/apply/report/request), dashboard, auth. Entity-specific routes: Case Law, Registry, Chambers, Judgments.
|
||||||
55
ICCC/app/(auth)/login/page.tsx
Normal file
55
ICCC/app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
function LoginForm() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const returnUrl = searchParams.get("returnUrl") ?? "/dashboard";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12 max-w-md">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Sign in</h1>
|
||||||
|
<p className="mt-2 text-neutral-600">
|
||||||
|
Placeholder login. Replace with OIDC/OAuth 2.0 when identity provider is configured.
|
||||||
|
</p>
|
||||||
|
<form action="/api/auth/mock-login" method="POST" className="mt-6 space-y-4">
|
||||||
|
<input type="hidden" name="returnUrl" value={returnUrl} />
|
||||||
|
<div>
|
||||||
|
<label htmlFor="role" className="block text-sm font-medium text-neutral-700">
|
||||||
|
Role (dev placeholder)
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="role"
|
||||||
|
name="role"
|
||||||
|
className="mt-1 block w-full rounded border border-neutral-300 px-3 py-2 focus:border-primary-500 focus:ring-1 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="member">Member</option>
|
||||||
|
<option value="staff">Staff</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<p className="mt-4 text-sm text-neutral-500">
|
||||||
|
<Link href={returnUrl} className="text-primary-600 hover:underline">
|
||||||
|
Return to previous page
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div className="container mx-auto px-4 py-12 max-w-md">Loading...</div>}>
|
||||||
|
<LoginForm />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
ICCC/app/(auth)/logout/page.tsx
Normal file
20
ICCC/app/(auth)/logout/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function LogoutPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
document.cookie = "portal-role=; path=/; max-age=0";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12 max-w-md">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Signed out</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">You have been signed out.</p>
|
||||||
|
<Link href="/" className="mt-6 inline-block text-primary-600 hover:underline">
|
||||||
|
Return to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/about/page.tsx
Normal file
8
ICCC/app/(public)/about/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AboutPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Mandate</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">About the Digital Bank of International Settlements.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/accreditation/page.tsx
Normal file
8
ICCC/app/(public)/accreditation/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AccreditationPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Accreditation</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Accreditation and credentials.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/contact/page.tsx
Normal file
8
ICCC/app/(public)/contact/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ContactPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Contact</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">General contact and enquiries.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/departments/page.tsx
Normal file
8
ICCC/app/(public)/departments/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DepartmentsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Departments</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Mission departments and programs.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/documents/page.tsx
Normal file
8
ICCC/app/(public)/documents/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DocumentsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Documents</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Library, standards, and directives.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/governance/page.tsx
Normal file
8
ICCC/app/(public)/governance/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function GovernancePage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Governance</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Charters, instruments, and governance.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/leadership/page.tsx
Normal file
8
ICCC/app/(public)/leadership/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function LeadershipPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Leadership</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Executive leadership and secretariat.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/membership/page.tsx
Normal file
8
ICCC/app/(public)/membership/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function MembershipPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Membership</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Member directory and accreditation.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/news/page.tsx
Normal file
8
ICCC/app/(public)/news/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function NewsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">News</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">News and announcements.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/org-chart/page.tsx
Normal file
8
ICCC/app/(public)/org-chart/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function OrgChartPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Org Chart</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Organizational structure.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/programs/page.tsx
Normal file
8
ICCC/app/(public)/programs/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ProgramsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Programs</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Programs and initiatives.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/publications/page.tsx
Normal file
8
ICCC/app/(public)/publications/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function PublicationsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Publications</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Publications and reports.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/regions/page.tsx
Normal file
8
ICCC/app/(public)/regions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RegionsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Regional Offices</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Americas, EMEA, APAC delegations and field missions.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/(public)/standards/page.tsx
Normal file
8
ICCC/app/(public)/standards/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function StandardsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Standards</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Standards and codes.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
ICCC/app/api/auth/mock-login/route.ts
Normal file
10
ICCC/app/api/auth/mock-login/route.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const form = await request.formData();
|
||||||
|
const returnUrl = (form.get("returnUrl") as string) ?? "/dashboard";
|
||||||
|
const role = (form.get("role") as string) ?? "member";
|
||||||
|
const res = NextResponse.redirect(new URL(returnUrl, request.url));
|
||||||
|
res.cookies.set("portal-role", role, { path: "/", httpOnly: true, maxAge: 60 * 60 * 24 });
|
||||||
|
return res;
|
||||||
|
}
|
||||||
8
ICCC/app/apply/page.tsx
Normal file
8
ICCC/app/apply/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ApplyPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Apply</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Apply for membership or accreditation.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/case-law/page.tsx
Normal file
8
ICCC/app/case-law/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function CaseLawPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Case Law</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Case law and decisions.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/chambers/page.tsx
Normal file
8
ICCC/app/chambers/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ChambersPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Chambers</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Trial and appeals chambers.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
ICCC/app/dashboard/layout.tsx
Normal file
17
ICCC/app/dashboard/layout.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[60vh]">
|
||||||
|
<aside className="w-56 border-r border-neutral-200 bg-white p-4">
|
||||||
|
<nav className="space-y-1" aria-label="Dashboard navigation">
|
||||||
|
<Link href="/dashboard" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">Home</Link>
|
||||||
|
<Link href="/dashboard/submissions" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">My submissions</Link>
|
||||||
|
<Link href="/dashboard/requests" className="block rounded px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-100">My requests</Link>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<div className="flex-1 p-6">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/dashboard/page.tsx
Normal file
8
ICCC/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DashboardPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Dashboard</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Role-based dashboard.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/dashboard/requests/page.tsx
Normal file
8
ICCC/app/dashboard/requests/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RequestsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">My requests</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Track your requests.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/dashboard/submissions/page.tsx
Normal file
8
ICCC/app/dashboard/submissions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SubmissionsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">My submissions</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Track your submissions and status.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
ICCC/app/globals.css
Normal file
3
ICCC/app/globals.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
8
ICCC/app/judgments/page.tsx
Normal file
8
ICCC/app/judgments/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function JudgmentsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Judgments</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Judgments, orders, and remedies.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
ICCC/app/layout.tsx
Normal file
34
ICCC/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import "./globals.css";
|
||||||
|
import { Header } from "@/components/layout/Header";
|
||||||
|
import { Footer } from "@/components/layout/Footer";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "International Criminal Court of Commerce",
|
||||||
|
description: "Official portal of the International Criminal Court of Commerce",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className="min-h-screen flex flex-col bg-neutral-50 text-neutral-900 antialiased">
|
||||||
|
<a
|
||||||
|
href="#main-content"
|
||||||
|
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary-600 focus:text-white focus:rounded"
|
||||||
|
>
|
||||||
|
Skip to main content
|
||||||
|
</a>
|
||||||
|
<Header />
|
||||||
|
<main id="main-content" className="flex-1">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/membership/renewals/page.tsx
Normal file
8
ICCC/app/membership/renewals/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RenewalsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Renewals and Attestations</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Annual renewals for members.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
ICCC/app/page.tsx
Normal file
24
ICCC/app/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-4xl font-bold text-neutral-900">International Criminal Court of Commerce</h1>
|
||||||
|
<p className="mt-4 text-lg text-neutral-600">
|
||||||
|
Welcome to the official portal. Explore registry, chambers, case law, and judgments.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-wrap gap-4">
|
||||||
|
<a href="/about" className="inline-flex items-center rounded-md bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
<a href="/documents" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Documents
|
||||||
|
</a>
|
||||||
|
<a href="/transparency" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Transparency
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard" className="inline-flex items-center rounded-md border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/registry/page.tsx
Normal file
8
ICCC/app/registry/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RegistryPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Registry</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Clerk of Court and registry.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/report/page.tsx
Normal file
8
ICCC/app/report/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function ReportPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Report</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Report incidents or compliance matters.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/request/page.tsx
Normal file
8
ICCC/app/request/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function RequestPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Request</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Request documents or support.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/submit/page.tsx
Normal file
8
ICCC/app/submit/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SubmitPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Submit</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Submission portal.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/transparency/audit/page.tsx
Normal file
8
ICCC/app/transparency/audit/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function AuditPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Audit Reports</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Audit reports and findings tracker.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/transparency/data-protection/page.tsx
Normal file
8
ICCC/app/transparency/data-protection/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function DataProtectionPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Data Protection and Records</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Records retention and data protection.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/transparency/ethics/page.tsx
Normal file
8
ICCC/app/transparency/ethics/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function EthicsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Ethics and Conduct</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Ethics and conduct policies.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/transparency/page.tsx
Normal file
8
ICCC/app/transparency/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function TransparencyPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Transparency</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Audit, ethics, whistleblower, sanctions, and data protection.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
ICCC/app/transparency/sanctions/page.tsx
Normal file
8
ICCC/app/transparency/sanctions/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default function SanctionsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<h1 className="text-3xl font-bold text-neutral-900">Sanctions and Restricted Parties</h1>
|
||||||
|
<p className="mt-4 text-neutral-600">Eligibility and restricted parties.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user