feat: implement naming convention, deployment automation, and infrastructure updates

- Add comprehensive naming convention (provider-region-resource-env-purpose)
- Implement Terraform locals for centralized naming
- Update all Terraform resources to use new naming convention
- Create deployment automation framework (18 phase scripts)
- Add Azure setup scripts (provider registration, quota checks)
- Update deployment scripts config with naming functions
- Create complete deployment documentation (guide, steps, quick reference)
- Add frontend portal implementations (public and internal)
- Add UI component library (18 components)
- Enhance Entra VerifiedID integration with file utilities
- Add API client package for all services
- Create comprehensive documentation (naming, deployment, next steps)

Infrastructure:
- Resource groups, storage accounts with new naming
- Terraform configuration updates
- Outputs with naming convention examples

Deployment:
- Automated deployment scripts for all 15 phases
- State management and logging
- Error handling and validation

Documentation:
- Naming convention guide and implementation summary
- Complete deployment guide (296 steps)
- Next steps and quick start guides
- Azure prerequisites and setup completion docs

Note: ESLint warnings present - will be addressed in follow-up commit
This commit is contained in:
defiQUG
2025-11-12 08:22:51 -08:00
parent 9e46f3f316
commit 8649ad4124
136 changed files with 17251 additions and 147 deletions

View File

@@ -0,0 +1,116 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function AboutPage() {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-4xl">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">About The Order</h1>
<p className="text-xl text-gray-600">
The Order of Military Hospitallers - A constitutional sovereign structure
</p>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Our Mission</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
The Order of Military Hospitallers is a constitutional sovereign structure dedicated to
providing digital identity, legal services, and governance frameworks for the modern era.
</p>
<p>
We operate as a decentralized sovereign body (DSB), offering eResidency and eCitizenship
programs that provide individuals with verifiable digital credentials and access to our
services.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>What We Offer</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-3 text-gray-700">
<li className="flex items-start">
<span className="mr-2"></span>
<span>
<strong>eResidency:</strong> Digital residency credentials for individuals seeking
to participate in The Order's ecosystem
</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>
<strong>eCitizenship:</strong> Full citizenship credentials with governance rights
and responsibilities
</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>
<strong>Verifiable Credentials:</strong> Secure, tamper-proof digital credentials
based on W3C standards
</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>
<strong>Legal Services:</strong> Access to the International Criminal Court of
Commerce (ICCC)
</span>
</li>
<li className="flex items-start">
<span className="mr-2"></span>
<span>
<strong>Financial Services:</strong> Digital Bank of International Settlements
(DBIS) infrastructure
</span>
</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Governance</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
The Order operates under a constitutional framework with:
</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Founding Council for strategic decisions</li>
<li>Judicial arm (ICCC) for legal matters</li>
<li>Administrative structures for day-to-day operations</li>
<li>Transparent governance and audit processes</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Technology</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
Our platform is built on modern, secure technologies:
</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Decentralized Identifiers (DIDs) for identity management</li>
<li>Verifiable Credentials (VCs) following W3C standards</li>
<li>eIDAS-compliant qualified signatures</li>
<li>Blockchain and cryptographic security</li>
<li>Open-source and transparent systems</li>
</ul>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,235 @@
'use client';
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Button } from '@the-order/ui';
import { getApiClient } from '@the-order/api-client';
import { useToast } from '@the-order/ui';
export default function ApplyPage() {
const router = useRouter();
const apiClient = getApiClient();
const { success, error: showError } = useToast();
const [formData, setFormData] = useState({
email: '',
givenName: '',
familyName: '',
dateOfBirth: '',
nationality: '',
phone: '',
street: '',
city: '',
region: '',
postalCode: '',
country: '',
});
const mutation = useMutation({
mutationFn: async (data: typeof formData) => {
return apiClient.eresidency.submitApplication({
email: data.email,
givenName: data.givenName,
familyName: data.familyName,
dateOfBirth: data.dateOfBirth || undefined,
nationality: data.nationality || undefined,
phone: data.phone || undefined,
address: data.street || data.city || data.region || data.postalCode || data.country
? {
street: data.street || undefined,
city: data.city || undefined,
region: data.region || undefined,
postalCode: data.postalCode || undefined,
country: data.country || undefined,
}
: undefined,
});
},
onSuccess: (data) => {
success('Application submitted successfully!', 'Your eResidency application has been received.');
router.push(`/status?id=${data.id}`);
},
onError: (error) => {
showError(
error instanceof Error ? error.message : 'Failed to submit application',
'Submission Error'
);
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutation.mutate(formData);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Apply for eResidency</CardTitle>
<CardDescription>
Complete the form below to apply for eResidency with The Order
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div>
<Label htmlFor="email">Email Address *</Label>
<Input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleChange}
placeholder="your.email@example.com"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="givenName">Given Name *</Label>
<Input
id="givenName"
name="givenName"
required
value={formData.givenName}
onChange={handleChange}
placeholder="John"
/>
</div>
<div>
<Label htmlFor="familyName">Family Name *</Label>
<Input
id="familyName"
name="familyName"
required
value={formData.familyName}
onChange={handleChange}
placeholder="Doe"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="dateOfBirth">Date of Birth</Label>
<Input
id="dateOfBirth"
name="dateOfBirth"
type="date"
value={formData.dateOfBirth}
onChange={handleChange}
/>
</div>
<div>
<Label htmlFor="nationality">Nationality</Label>
<Input
id="nationality"
name="nationality"
value={formData.nationality}
onChange={handleChange}
placeholder="US"
/>
</div>
</div>
<div>
<Label htmlFor="phone">Phone Number</Label>
<Input
id="phone"
name="phone"
type="tel"
value={formData.phone}
onChange={handleChange}
placeholder="+1 (555) 123-4567"
/>
</div>
<div className="border-t pt-4">
<h3 className="text-lg font-semibold mb-4">Address (Optional)</h3>
<div className="space-y-4">
<div>
<Label htmlFor="street">Street Address</Label>
<Input
id="street"
name="street"
value={formData.street}
onChange={handleChange}
placeholder="123 Main St"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="city">City</Label>
<Input
id="city"
name="city"
value={formData.city}
onChange={handleChange}
placeholder="New York"
/>
</div>
<div>
<Label htmlFor="region">Region/State</Label>
<Input
id="region"
name="region"
value={formData.region}
onChange={handleChange}
placeholder="NY"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="postalCode">Postal Code</Label>
<Input
id="postalCode"
name="postalCode"
value={formData.postalCode}
onChange={handleChange}
placeholder="10001"
/>
</div>
<div>
<Label htmlFor="country">Country</Label>
<Input
id="country"
name="country"
value={formData.country}
onChange={handleChange}
placeholder="United States"
/>
</div>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-4">
<Button type="button" variant="secondary" onClick={() => router.back()}>
Cancel
</Button>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Submitting...' : 'Submit Application'}
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,121 @@
'use client';
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Textarea, Button, useToast } from '@the-order/ui';
export default function ContactPage() {
const { success, error: showError } = useToast();
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: '',
});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
success('Message sent successfully!', 'We will get back to you soon.');
setFormData({ name: '', email: '', subject: '', message: '' });
} catch (err) {
showError('Failed to send message', 'Please try again later.');
} finally {
setIsSubmitting(false);
}
};
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">Contact Us</h1>
<p className="text-xl text-gray-600">
Have questions? We'd love to hear from you.
</p>
</div>
<Card>
<CardHeader>
<CardTitle>Send us a message</CardTitle>
<CardDescription>Fill out the form below and we'll get back to you as soon as possible.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<Label htmlFor="name">Name</Label>
<Input
id="name"
required
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
required
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="subject">Subject</Label>
<Input
id="subject"
required
value={formData.subject}
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
className="mt-2"
/>
</div>
<div>
<Label htmlFor="message">Message</Label>
<Textarea
id="message"
required
rows={6}
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
className="mt-2"
/>
</div>
<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? 'Sending...' : 'Send Message'}
</Button>
</form>
</CardContent>
</Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>Other ways to reach us</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<div>
<h3 className="font-semibold mb-1">Email</h3>
<p>support@theorder.org</p>
</div>
<div>
<h3 className="font-semibold mb-1">Response Time</h3>
<p>We typically respond within 24-48 hours.</p>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,128 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
import Link from 'next/link';
export default function DocsPage() {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-4xl">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">Documentation</h1>
<p className="text-xl text-gray-600">
Learn how to use The Order's services and APIs
</p>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Getting Started</CardTitle>
<CardDescription>Quick start guide for new users</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">1. Apply for eResidency</h3>
<p className="text-gray-700">
Start by submitting an eResidency application through our{' '}
<Link href="/apply" className="text-primary hover:underline">
application form
</Link>
. You'll need to provide personal information and identity documents.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">2. Wait for Review</h3>
<p className="text-gray-700">
Our team will review your application. You can check the status using your
application ID on the{' '}
<Link href="/status" className="text-primary hover:underline">
status page
</Link>
.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">3. Receive Credentials</h3>
<p className="text-gray-700">
Once approved, you'll receive verifiable credentials that you can use to access
services and prove your identity.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>API Documentation</CardTitle>
<CardDescription>RESTful API endpoints and integration guides</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Identity Service</h3>
<p className="text-gray-700 mb-2">
Manage verifiable credentials, issue new credentials, and verify existing ones.
</p>
<ul className="list-disc list-inside space-y-1 text-gray-600 ml-4">
<li>POST /api/v1/credentials/issue - Issue a new credential</li>
<li>POST /api/v1/credentials/verify - Verify a credential</li>
<li>GET /api/v1/credentials/{'{id}'} - Get credential details</li>
<li>POST /api/v1/credentials/{'{id}'}/revoke - Revoke a credential</li>
</ul>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">eResidency Service</h3>
<p className="text-gray-700 mb-2">
Submit and manage eResidency applications.
</p>
<ul className="list-disc list-inside space-y-1 text-gray-600 ml-4">
<li>POST /api/v1/applications - Submit an application</li>
<li>GET /api/v1/applications/{'{id}'} - Get application status</li>
<li>GET /api/v1/applications - List applications (admin)</li>
</ul>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Verifiable Credentials</CardTitle>
<CardDescription>Understanding digital credentials</CardDescription>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
Verifiable Credentials are tamper-proof digital documents that prove your identity
or qualifications. They follow W3C standards and can be verified by anyone with
access to the public key.
</p>
<div>
<h3 className="font-semibold mb-2">Features:</h3>
<ul className="list-disc list-inside space-y-1 ml-4">
<li>Cryptographically signed and tamper-proof</li>
<li>Privacy-preserving - you control what information to share</li>
<li>Interoperable - works with standard verifiers</li>
<li>Revocable - can be revoked if compromised</li>
</ul>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Support</CardTitle>
<CardDescription>Need help? We're here for you</CardDescription>
</CardHeader>
<CardContent>
<p className="text-gray-700 mb-4">
If you have questions or need assistance, please{' '}
<Link href="/contact" className="text-primary hover:underline">
contact our support team
</Link>
.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,43 @@
'use client';
import { useEffect } from 'react';
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<Card className="max-w-md w-full text-center">
<CardHeader>
<CardTitle className="text-6xl font-bold text-red-600 mb-4">500</CardTitle>
<CardDescription className="text-xl">Something went wrong</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-gray-600">
An unexpected error occurred. Please try again or contact support if the problem
persists.
</p>
{error.digest && (
<p className="text-sm text-gray-500 font-mono">Error ID: {error.digest}</p>
)}
<div className="flex gap-4 justify-center">
<Button onClick={reset}>Try Again</Button>
<Button variant="outline" onClick={() => (window.location.href = '/')}>
Go Home
</Button>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,60 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -1,5 +1,9 @@
import type { Metadata } from 'next';
import { ReactNode } from 'react';
import './globals.css';
import { Providers } from '../lib/providers';
import { Header } from '../components/Header';
import { Footer } from '../components/Footer';
export const metadata: Metadata = {
title: 'The Order - Public Portal',
@@ -13,7 +17,13 @@ export default function RootLayout({
}) {
return (
<html lang="en">
<body>{children}</body>
<body className="flex flex-col min-h-screen">
<Providers>
<Header />
<main className="flex-1">{children}</main>
<Footer />
</Providers>
</body>
</html>
);
}

View File

@@ -0,0 +1,131 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Button, Alert, AlertDescription } from '@the-order/ui';
import { useAuth } from '../../lib/auth';
import { useToast } from '@the-order/ui';
export default function LoginPage() {
const router = useRouter();
const { login } = useAuth();
const { success, error: showError } = useToast();
const [formData, setFormData] = useState({
email: '',
password: '',
});
const [isLoading, setIsLoading] = useState(false);
const [loginError, setLoginError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setLoginError(null);
try {
// In production, this would call an authentication API
// For now, we'll simulate a login
if (formData.email && formData.password) {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
// Mock authentication - in production, this would be a real API call
const mockUser = {
id: 'user-123',
email: formData.email,
name: formData.email.split('@')[0],
accessToken: 'mock-access-token-' + Date.now(),
roles: ['user'],
};
login(mockUser);
success('Login successful', 'Welcome back!');
router.push('/');
} else {
throw new Error('Please enter email and password');
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Login failed';
setLoginError(errorMessage);
showError(errorMessage, 'Login Error');
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center py-12 px-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Login</CardTitle>
<CardDescription>Sign in to your account</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
{loginError && (
<Alert variant="destructive">
<AlertDescription>{loginError}</AlertDescription>
</Alert>
)}
<div>
<Label htmlFor="email">Email Address</Label>
<Input
id="email"
type="email"
required
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="your.email@example.com"
className="mt-2"
/>
</div>
<div>
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
required
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
placeholder="Enter your password"
className="mt-2"
/>
</div>
<div className="flex items-center justify-between">
<a href="/forgot-password" className="text-sm text-primary hover:underline">
Forgot password?
</a>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In'}
</Button>
<div className="text-center text-sm text-gray-600">
Don't have an account?{' '}
<a href="/register" className="text-primary hover:underline">
Sign up
</a>
</div>
</form>
<div className="mt-6 pt-6 border-t">
<p className="text-sm text-gray-600 text-center mb-4">Or continue with</p>
<div className="space-y-2">
<Button variant="outline" className="w-full" type="button">
OIDC / eIDAS
</Button>
<Button variant="outline" className="w-full" type="button">
DID Wallet
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,29 @@
import Link from 'next/link';
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function NotFound() {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<Card className="max-w-md w-full text-center">
<CardHeader>
<CardTitle className="text-6xl font-bold text-gray-900 mb-4">404</CardTitle>
<CardDescription className="text-xl">Page Not Found</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-gray-600">
The page you're looking for doesn't exist or has been moved.
</p>
<div className="flex gap-4 justify-center">
<Link href="/">
<Button>Go Home</Button>
</Link>
<Button variant="outline" onClick={() => window.history.back()}>
Go Back
</Button>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,9 +1,94 @@
import Link from 'next/link';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function Home() {
return (
<main>
<h1>The Order - Public Portal</h1>
<p>Welcome to The Order public portal.</p>
</main>
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
<div className="container mx-auto px-4 py-16">
<div className="text-center mb-12">
<h1 className="text-5xl font-bold text-gray-900 mb-4">The Order</h1>
<p className="text-xl text-gray-600 max-w-2xl mx-auto mb-6">
Order of Military Hospitallers - Digital Identity & Governance Platform
</p>
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
Apply for eResidency, verify credentials, and access our services
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
<Card>
<CardHeader>
<CardTitle>Apply for eResidency</CardTitle>
<CardDescription>Start your application for eResidency with The Order</CardDescription>
</CardHeader>
<CardContent>
<Link href="/apply" className="text-primary hover:underline font-medium">
Begin Application
</Link>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Check Application Status</CardTitle>
<CardDescription>View the status of your eResidency application</CardDescription>
</CardHeader>
<CardContent>
<Link href="/status" className="text-primary hover:underline font-medium">
Check Status
</Link>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Verify Credential</CardTitle>
<CardDescription>Verify a verifiable credential issued by The Order</CardDescription>
</CardHeader>
<CardContent>
<Link href="/verify" className="text-primary hover:underline font-medium">
Verify Credential
</Link>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>About The Order</CardTitle>
<CardDescription>Learn about The Order and our mission</CardDescription>
</CardHeader>
<CardContent>
<Link href="/about" className="text-primary hover:underline font-medium">
Learn More
</Link>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Documentation</CardTitle>
<CardDescription>Access documentation and help resources</CardDescription>
</CardHeader>
<CardContent>
<Link href="/docs" className="text-primary hover:underline font-medium">
View Docs
</Link>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Contact</CardTitle>
<CardDescription>Get in touch with our support team</CardDescription>
</CardHeader>
<CardContent>
<Link href="/contact" className="text-primary hover:underline font-medium">
Contact Us
</Link>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,94 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function PrivacyPage() {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-4xl">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">Privacy Policy</h1>
<p className="text-lg text-gray-600">Last updated: {new Date().toLocaleDateString()}</p>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>1. Information We Collect</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
We collect information that you provide directly to us, including:
</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Personal identification information (name, email, date of birth)</li>
<li>Identity document information (for verification purposes)</li>
<li>Application and credential data</li>
<li>Usage data and analytics</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>2. How We Use Your Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>We use the information we collect to:</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Process and review your eResidency applications</li>
<li>Issue and manage verifiable credentials</li>
<li>Provide and improve our services</li>
<li>Comply with legal obligations</li>
<li>Prevent fraud and ensure security</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>3. Data Security</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
We implement appropriate technical and organizational measures to protect your
personal information against unauthorized access, alteration, disclosure, or
destruction.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>4. Your Rights</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>You have the right to:</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Access your personal information</li>
<li>Request correction of inaccurate data</li>
<li>Request deletion of your data</li>
<li>Object to processing of your data</li>
<li>Request data portability</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>5. Contact Us</CardTitle>
</CardHeader>
<CardContent className="text-gray-700">
<p>
If you have questions about this Privacy Policy, please contact us at{' '}
<a href="mailto:privacy@theorder.org" className="text-primary hover:underline">
privacy@theorder.org
</a>
.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,168 @@
'use client';
import { useSearchParams } from 'next/navigation';
import { useQuery } from '@tanstack/react-query';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Label } from '@the-order/ui';
import { getApiClient } from '@the-order/api-client';
export default function StatusPage() {
const searchParams = useSearchParams();
const applicationId = searchParams.get('id');
const apiClient = getApiClient();
const { data: application, isLoading, error } = useQuery({
queryKey: ['application', applicationId],
queryFn: () => apiClient.eresidency.getApplication(applicationId!),
enabled: !!applicationId,
});
if (!applicationId) {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Application Status</CardTitle>
<CardDescription>Please provide an application ID to check status</CardDescription>
</CardHeader>
</Card>
</div>
</div>
);
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardContent className="py-12 text-center">
<p className="text-gray-600">Loading application status...</p>
</CardContent>
</Card>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Error</CardTitle>
<CardDescription>Failed to load application status</CardDescription>
</CardHeader>
<CardContent>
<p className="text-red-600">{error instanceof Error ? error.message : 'Unknown error'}</p>
</CardContent>
</Card>
</div>
</div>
);
}
if (!application) {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Application Not Found</CardTitle>
<CardDescription>No application found with the provided ID</CardDescription>
</CardHeader>
</Card>
</div>
</div>
);
}
const getStatusColor = (status: string) => {
switch (status) {
case 'approved':
return 'text-green-600 bg-green-50';
case 'rejected':
return 'text-red-600 bg-red-50';
case 'under_review':
return 'text-blue-600 bg-blue-50';
case 'kyc_pending':
return 'text-yellow-600 bg-yellow-50';
default:
return 'text-gray-600 bg-gray-50';
}
};
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Application Status</CardTitle>
<CardDescription>Application ID: {application.id}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div>
<Label className="text-sm font-medium text-gray-500">Status</Label>
<div className={`mt-1 px-3 py-2 rounded-md inline-block ${getStatusColor(application.status)}`}>
{application.status.toUpperCase().replace('_', ' ')}
</div>
</div>
<div>
<Label className="text-sm font-medium text-gray-500">Applicant</Label>
<p className="mt-1 text-gray-900">
{application.givenName} {application.familyName}
</p>
<p className="text-sm text-gray-600">{application.email}</p>
</div>
{application.submittedAt && (
<div>
<Label className="text-sm font-medium text-gray-500">Submitted At</Label>
<p className="mt-1 text-gray-900">{new Date(application.submittedAt).toLocaleString()}</p>
</div>
)}
{application.reviewedAt && (
<div>
<Label className="text-sm font-medium text-gray-500">Reviewed At</Label>
<p className="mt-1 text-gray-900">{new Date(application.reviewedAt).toLocaleString()}</p>
</div>
)}
{application.reviewedBy && (
<div>
<Label className="text-sm font-medium text-gray-500">Reviewed By</Label>
<p className="mt-1 text-gray-900">{application.reviewedBy}</p>
</div>
)}
{application.rejectionReason && (
<div>
<Label className="text-sm font-medium text-gray-500">Rejection Reason</Label>
<p className="mt-1 text-red-600">{application.rejectionReason}</p>
</div>
)}
{application.kycStatus && (
<div>
<Label className="text-sm font-medium text-gray-500">KYC Status</Label>
<p className="mt-1 text-gray-900">{application.kycStatus}</p>
</div>
)}
{application.sanctionsStatus && (
<div>
<Label className="text-sm font-medium text-gray-500">Sanctions Status</Label>
<p className="mt-1 text-gray-900">{application.sanctionsStatus}</p>
</div>
)}
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,102 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@the-order/ui';
export default function TermsPage() {
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-4xl">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4">Terms of Service</h1>
<p className="text-lg text-gray-600">Last updated: {new Date().toLocaleDateString()}</p>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>1. Acceptance of Terms</CardTitle>
</CardHeader>
<CardContent className="text-gray-700">
<p>
By accessing and using The Order's services, you accept and agree to be bound by
these Terms of Service. If you do not agree, you may not use our services.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>2. Description of Services</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>
The Order provides digital identity and credential management services, including:
</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>eResidency and eCitizenship applications</li>
<li>Verifiable credential issuance and management</li>
<li>Identity verification services</li>
<li>Access to governance and legal services</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>3. User Responsibilities</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-gray-700">
<p>You agree to:</p>
<ul className="list-disc list-inside space-y-2 ml-4">
<li>Provide accurate and truthful information</li>
<li>Maintain the security of your credentials</li>
<li>Use services only for lawful purposes</li>
<li>Not attempt to circumvent security measures</li>
<li>Comply with all applicable laws and regulations</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>4. Intellectual Property</CardTitle>
</CardHeader>
<CardContent className="text-gray-700">
<p>
All content, features, and functionality of our services are owned by The Order and
are protected by international copyright, trademark, and other intellectual property
laws.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>5. Limitation of Liability</CardTitle>
</CardHeader>
<CardContent className="text-gray-700">
<p>
The Order shall not be liable for any indirect, incidental, special, consequential,
or punitive damages resulting from your use of our services.
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>6. Contact Information</CardTitle>
</CardHeader>
<CardContent className="text-gray-700">
<p>
For questions about these Terms, contact us at{' '}
<a href="mailto:legal@theorder.org" className="text-primary hover:underline">
legal@theorder.org
</a>
.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,129 @@
'use client';
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Button, Alert, AlertDescription, useToast } from '@the-order/ui';
import { getApiClient } from '@the-order/api-client';
export default function VerifyPage() {
const apiClient = getApiClient();
const { success, error: showError } = useToast();
const [credentialId, setCredentialId] = useState('');
const [verificationResult, setVerificationResult] = useState<{ valid: boolean; error?: string } | null>(null);
const mutation = useMutation({
mutationFn: async (id: string) => {
return apiClient.identity.verifyCredential({
credential: {
id,
},
});
},
onSuccess: (data) => {
setVerificationResult({ valid: data.valid });
if (data.valid) {
success('Credential verified successfully', 'This credential is valid and authentic.');
} else {
showError('Credential verification failed', 'This credential could not be verified.');
}
},
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : 'Verification failed';
setVerificationResult({
valid: false,
error: errorMessage,
});
showError(errorMessage, 'Verification Error');
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!credentialId.trim()) {
setVerificationResult({ valid: false, error: 'Please enter a credential ID' });
return;
}
mutation.mutate(credentialId.trim());
};
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>Verify Credential</CardTitle>
<CardDescription>
Enter a credential ID to verify its validity and authenticity
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<Label htmlFor="credentialId">Credential ID</Label>
<Input
id="credentialId"
type="text"
placeholder="Enter credential ID"
value={credentialId}
onChange={(e) => setCredentialId(e.target.value)}
className="mt-2"
/>
<p className="text-sm text-gray-500 mt-1">
The credential ID is typically a UUID or DID identifier
</p>
</div>
{verificationResult && (
<Alert variant={verificationResult.valid ? 'success' : 'destructive'}>
<AlertDescription>
{verificationResult.valid ? (
<div>
<p className="font-semibold"> Credential Verified</p>
<p className="text-sm mt-1">This credential is valid and authentic.</p>
</div>
) : (
<div>
<p className="font-semibold"> Verification Failed</p>
<p className="text-sm mt-1">
{verificationResult.error || 'The credential could not be verified.'}
</p>
</div>
)}
</AlertDescription>
</Alert>
)}
<div className="flex justify-end">
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Verifying...' : 'Verify Credential'}
</Button>
</div>
</form>
</CardContent>
</Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>About Credential Verification</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-gray-600">
<p>
Credential verification checks the authenticity and validity of verifiable credentials
issued by The Order.
</p>
<p>
The verification process validates:
</p>
<ul className="list-disc list-inside space-y-1 ml-4">
<li>Credential signature and proof</li>
<li>Credential expiration status</li>
<li>Revocation status</li>
<li>Issuer authenticity</li>
</ul>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,31 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '../lib/auth';
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.push('/login');
}
}, [isAuthenticated, isLoading, router]);
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<p className="text-gray-600">Loading...</p>
</div>
);
}
if (!isAuthenticated) {
return null;
}
return <>{children}</>;
}

View File

@@ -0,0 +1,77 @@
import Link from 'next/link';
export function Footer() {
return (
<footer className="border-t bg-gray-50 mt-auto">
<div className="container mx-auto px-4 py-8">
<div className="grid md:grid-cols-4 gap-8">
<div>
<h3 className="font-semibold text-gray-900 mb-4">The Order</h3>
<p className="text-sm text-gray-600">
Digital identity and credential management for the Order of Military Hospitallers.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-4">Services</h4>
<ul className="space-y-2 text-sm">
<li>
<Link href="/apply" className="text-gray-600 hover:text-gray-900">
Apply for eResidency
</Link>
</li>
<li>
<Link href="/verify" className="text-gray-600 hover:text-gray-900">
Verify Credentials
</Link>
</li>
<li>
<Link href="/status" className="text-gray-600 hover:text-gray-900">
Check Status
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-4">Resources</h4>
<ul className="space-y-2 text-sm">
<li>
<Link href="/about" className="text-gray-600 hover:text-gray-900">
About
</Link>
</li>
<li>
<Link href="/docs" className="text-gray-600 hover:text-gray-900">
Documentation
</Link>
</li>
<li>
<Link href="/contact" className="text-gray-600 hover:text-gray-900">
Contact
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-4">Legal</h4>
<ul className="space-y-2 text-sm">
<li>
<Link href="/privacy" className="text-gray-600 hover:text-gray-900">
Privacy Policy
</Link>
</li>
<li>
<Link href="/terms" className="text-gray-600 hover:text-gray-900">
Terms of Service
</Link>
</li>
</ul>
</div>
</div>
<div className="mt-8 pt-8 border-t text-center text-sm text-gray-600">
<p>&copy; {new Date().getFullYear()} The Order. All rights reserved.</p>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,54 @@
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { Button } from '@the-order/ui';
import { useAuth } from '../lib/auth';
export function Header() {
const router = useRouter();
const { isAuthenticated, user, logout } = useAuth();
const handleLogout = () => {
logout();
router.push('/');
};
return (
<header className="border-b bg-white">
<div className="container mx-auto px-4 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-gray-900">
The Order
</Link>
<nav className="flex items-center gap-4">
<Link href="/apply" className="text-gray-600 hover:text-gray-900">
Apply
</Link>
<Link href="/status" className="text-gray-600 hover:text-gray-900">
Status
</Link>
<Link href="/verify" className="text-gray-600 hover:text-gray-900">
Verify
</Link>
<Link href="/about" className="text-gray-600 hover:text-gray-900">
About
</Link>
{isAuthenticated ? (
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">{user?.email || user?.name}</span>
<Button variant="outline" size="sm" onClick={handleLogout}>
Logout
</Button>
</div>
) : (
<Link href="/login">
<Button variant="outline" size="sm">Login</Button>
</Link>
)}
</nav>
</div>
</div>
</header>
);
}

View File

@@ -0,0 +1,126 @@
'use client';
import React from 'react';
// Simple auth store without external dependencies
// In production, this would use Zustand or a proper auth library
interface AuthUser {
id: string;
email?: string;
name?: string;
did?: string;
roles?: string[];
accessToken?: string;
refreshToken?: string;
}
interface AuthState {
user: AuthUser | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (user: AuthUser) => void;
logout: () => void;
setUser: (user: AuthUser | null) => void;
}
// Simple in-memory store with localStorage persistence
class AuthStore {
private state: AuthState = {
user: null,
isAuthenticated: false,
isLoading: false,
login: () => {},
logout: () => {},
setUser: () => {},
};
private listeners: Set<(state: AuthState) => void> = new Set();
constructor() {
this.loadFromStorage();
}
private loadFromStorage() {
if (typeof window === 'undefined') return;
try {
const stored = localStorage.getItem('auth-storage');
if (stored) {
const parsed = JSON.parse(stored);
this.state.user = parsed.user || null;
this.state.isAuthenticated = !!this.state.user;
}
} catch {
// Ignore parse errors
}
}
private saveToStorage() {
if (typeof window === 'undefined') return;
try {
localStorage.setItem('auth-storage', JSON.stringify({ user: this.state.user }));
} catch {
// Ignore storage errors
}
}
private notify() {
this.listeners.forEach((listener) => listener(this.state));
}
subscribe(listener: (state: AuthState) => void) {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
getState() {
return this.state;
}
setState(updates: Partial<AuthState>) {
this.state = { ...this.state, ...updates };
this.saveToStorage();
this.notify();
}
}
const authStore = typeof window !== 'undefined' ? new AuthStore() : null;
// React hook to use auth state
export function useAuth(): AuthState {
const [state, setState] = React.useState<AuthState>(
authStore?.getState() || {
user: null,
isAuthenticated: false,
isLoading: false,
login: () => {},
logout: () => {},
setUser: () => {},
}
);
React.useEffect(() => {
if (!authStore) return;
const unsubscribe = authStore.subscribe(setState);
return unsubscribe;
}, []);
return {
...state,
login: (user: AuthUser) => {
authStore?.setState({ user, isAuthenticated: true });
if (user.accessToken) {
localStorage.setItem('auth_token', user.accessToken);
}
},
logout: () => {
authStore?.setState({ user: null, isAuthenticated: false });
localStorage.removeItem('auth_token');
},
setUser: (user: AuthUser | null) => {
authStore?.setState({ user, isAuthenticated: !!user });
},
};
}

View File

@@ -0,0 +1,27 @@
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode, useState } from 'react';
import { ToastProvider } from '@the-order/ui';
export function Providers({ children }: { children: ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
refetchOnWindowFocus: false,
retry: 1,
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
);
}

View File

@@ -0,0 +1,27 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Public portal routes are generally open
// Only protect specific routes if needed
const protectedRoutes: string[] = []; // Add protected routes here if needed
const isProtectedRoute = protectedRoutes.some((route) => request.nextUrl.pathname.startsWith(route));
if (isProtectedRoute) {
const token = request.cookies.get('auth_token') || request.headers.get('authorization');
if (!token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
}
return NextResponse.next();
}
export const config = {
matcher: [], // Add protected routes here if needed
};