diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 15bf0f0..f753bfb 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -4,7 +4,11 @@
"Bash(timeout:*)",
"Bash(timeout /t 2)",
"Bash(if exist .nextdevlock del /F .nextdevlock)",
- "Bash(if exist .nextdev rd /S /Q .nextdev)"
+ "Bash(if exist .nextdev rd /S /Q .nextdev)",
+ "mcp__serena__initial_instructions",
+ "mcp__serena__get_current_config",
+ "mcp__playwright__browser_fill_form",
+ "WebSearch"
],
"deny": [],
"ask": []
diff --git a/app/admin/AdminLayoutClient.tsx b/app/admin/AdminLayoutClient.tsx
new file mode 100644
index 0000000..8ca456f
--- /dev/null
+++ b/app/admin/AdminLayoutClient.tsx
@@ -0,0 +1,50 @@
+'use client';
+
+import { useEffect } from 'react';
+import { useRouter, usePathname } from 'next/navigation';
+import { AdminSidebar } from '@/components/admin/AdminSidebar';
+
+export default function AdminLayoutClient({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const router = useRouter();
+ const pathname = usePathname();
+
+ useEffect(() => {
+ // Skip auth check for login page
+ if (pathname === '/admin/login') {
+ return;
+ }
+
+ // Check authentication
+ const checkAuth = async () => {
+ try {
+ const response = await fetch('/api/admin/auth/verify');
+ if (!response.ok) {
+ router.push('/admin/login');
+ }
+ } catch (error) {
+ router.push('/admin/login');
+ }
+ };
+
+ checkAuth();
+ }, [pathname, router]);
+
+ // If on login page, render full-screen without sidebar
+ if (pathname === '/admin/login') {
+ return <>{children}>;
+ }
+
+ // Dashboard/orders pages with sidebar
+ return (
+
+ );
+}
diff --git a/app/admin/dashboard/page.tsx b/app/admin/dashboard/page.tsx
new file mode 100644
index 0000000..2a7d4f1
--- /dev/null
+++ b/app/admin/dashboard/page.tsx
@@ -0,0 +1,119 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { DollarSign, Package, Leaf, TrendingUp } from 'lucide-react';
+
+export default function AdminDashboard() {
+ // Placeholder data - will be replaced with real API data
+ const stats = [
+ {
+ title: 'Total Orders',
+ value: '0',
+ icon: ,
+ trend: { value: 0, isPositive: true },
+ gradient: 'bg-gradient-to-br from-royal-purple to-purple-600',
+ bgColor: 'bg-royal-purple',
+ },
+ {
+ title: 'Total CO₂ Offset',
+ value: '0 tons',
+ icon: ,
+ trend: { value: 0, isPositive: true },
+ gradient: 'bg-gradient-to-br from-sea-green to-green-600',
+ bgColor: 'bg-sea-green',
+ },
+ {
+ title: 'Total Revenue',
+ value: '$0',
+ icon: ,
+ trend: { value: 0, isPositive: true },
+ gradient: 'bg-gradient-to-br from-muted-gold to-orange-600',
+ bgColor: 'bg-muted-gold',
+ },
+ {
+ title: 'Fulfillment Rate',
+ value: '0%',
+ icon: ,
+ trend: { value: 0, isPositive: true },
+ gradient: 'bg-gradient-to-br from-maritime-teal to-teal-600',
+ bgColor: 'bg-maritime-teal',
+ },
+ ];
+
+ return (
+
+ {/* Header */}
+
+
Dashboard
+
Welcome to the Puffin Offset Admin Portal
+
+
+ {/* Stats Grid */}
+
+ {stats.map((stat, index) => (
+
+
+
+ {stat.icon}
+
+ {stat.trend && (
+
+ {stat.trend.isPositive ? '+' : '-'}{stat.trend.value}%
+
+ )}
+
+ {stat.title}
+ {stat.value}
+
+ ))}
+
+
+ {/* Placeholder for Charts */}
+
+
+ Orders Timeline
+
+ Chart will be implemented in Phase 3
+
+
+
+
+ Status Distribution
+
+ Chart will be implemented in Phase 3
+
+
+
+
+ {/* Info Card */}
+
+ 🎉 Admin Center Active!
+
+ Phase 1 (Foundation) is complete. Dashboard, authentication, and navigation are now functional.
+ Phase 2 will add backend APIs and real data integration.
+
+
+
+ );
+}
diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx
new file mode 100644
index 0000000..c2fd11c
--- /dev/null
+++ b/app/admin/layout.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from 'next';
+import AdminLayoutClient from './AdminLayoutClient';
+
+export const metadata: Metadata = {
+ title: {
+ default: 'Admin Portal | Puffin Offset',
+ template: '%s | Admin Portal',
+ },
+ description: 'Admin management portal for Puffin Offset',
+ robots: {
+ index: false,
+ follow: false,
+ },
+};
+
+export default function AdminLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx
new file mode 100644
index 0000000..849c2f1
--- /dev/null
+++ b/app/admin/login/page.tsx
@@ -0,0 +1,201 @@
+'use client';
+
+import { useState, FormEvent } from 'react';
+import { useRouter } from 'next/navigation';
+import { Lock, User, Loader2 } from 'lucide-react';
+import { motion } from 'framer-motion';
+import Image from 'next/image';
+
+export default function AdminLogin() {
+ const router = useRouter();
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setError('');
+ setIsLoading(true);
+
+ try {
+ const response = await fetch('/api/admin/auth/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ username, password }),
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ setError(data.error || 'Login failed');
+ setIsLoading(false);
+ return;
+ }
+
+ // Successful login - redirect to dashboard
+ router.push('/admin/dashboard');
+ } catch (err) {
+ setError('Network error. Please try again.');
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ {/* Monaco Background Image */}
+
+
+ {/* Overlay gradient for better readability */}
+
+
+
+ {/* Logo and Title */}
+
+
+
+
+
+
+
+ Admin Portal
+
+
+ Puffin Offset Management
+
+
+
+ {/* Login Card */}
+
+
+
+
+ {/* Footer */}
+
+ © 2024 Puffin Offset. Secure admin access.
+
+
+
+ );
+}
diff --git a/app/admin/orders/page.tsx b/app/admin/orders/page.tsx
new file mode 100644
index 0000000..a7f7874
--- /dev/null
+++ b/app/admin/orders/page.tsx
@@ -0,0 +1,32 @@
+'use client';
+
+import { motion } from 'framer-motion';
+import { Package } from 'lucide-react';
+
+export default function AdminOrders() {
+ return (
+
+ {/* Header */}
+
+
Orders
+
View and manage all carbon offset orders
+
+
+ {/* Placeholder */}
+
+
+ Orders Management
+
+ Orders table with filtering, search, and export will be implemented in Phase 4.
+
+
+ Backend API integration coming in Phase 2
+
+
+
+ );
+}
diff --git a/app/api/admin/auth/login/route.ts b/app/api/admin/auth/login/route.ts
new file mode 100644
index 0000000..9c5531b
--- /dev/null
+++ b/app/api/admin/auth/login/route.ts
@@ -0,0 +1,59 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { verifyCredentials, generateToken } from '@/lib/admin/auth';
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { username, password } = body;
+
+ // Validate input
+ if (!username || !password) {
+ return NextResponse.json(
+ { error: 'Username and password are required' },
+ { status: 400 }
+ );
+ }
+
+ // Verify credentials
+ const isValid = verifyCredentials(username, password);
+
+ if (!isValid) {
+ return NextResponse.json(
+ { error: 'Invalid credentials' },
+ { status: 401 }
+ );
+ }
+
+ // Generate JWT token
+ const token = generateToken({
+ username,
+ isAdmin: true,
+ });
+
+ // Create response with token in cookie
+ const response = NextResponse.json({
+ success: true,
+ user: {
+ username,
+ isAdmin: true,
+ },
+ });
+
+ // Set HTTP-only cookie with JWT token
+ response.cookies.set('admin-token', token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ maxAge: 60 * 60 * 24, // 24 hours
+ path: '/',
+ });
+
+ return response;
+ } catch (error) {
+ console.error('Login error:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/admin/auth/logout/route.ts b/app/api/admin/auth/logout/route.ts
new file mode 100644
index 0000000..49e5148
--- /dev/null
+++ b/app/api/admin/auth/logout/route.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from 'next/server';
+
+export async function POST() {
+ const response = NextResponse.json({ success: true });
+
+ // Clear the admin token cookie
+ response.cookies.set('admin-token', '', {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ maxAge: 0,
+ path: '/',
+ });
+
+ return response;
+}
diff --git a/app/api/admin/auth/verify/route.ts b/app/api/admin/auth/verify/route.ts
new file mode 100644
index 0000000..05e90f1
--- /dev/null
+++ b/app/api/admin/auth/verify/route.ts
@@ -0,0 +1,18 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getAdminFromRequest } from '@/lib/admin/middleware';
+
+export async function GET(request: NextRequest) {
+ const admin = getAdminFromRequest(request);
+
+ if (!admin) {
+ return NextResponse.json(
+ { error: 'Unauthorized' },
+ { status: 401 }
+ );
+ }
+
+ return NextResponse.json({
+ success: true,
+ user: admin,
+ });
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 0b5a47c..78ece89 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,8 +1,7 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import Script from 'next/script';
-import { Header } from '../components/Header';
-import { Footer } from '../components/Footer';
+import { RootLayoutClient } from '../components/RootLayoutClient';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
@@ -100,11 +99,7 @@ export default function RootLayout({
>
)}
-
-
- {children}
-
-
+ {children}