Spaces:
Sleeping
Sleeping
Upload 11 files
Browse files- App.tsx +231 -0
- Dockerfile +27 -0
- README.md +20 -11
- index.html +119 -0
- index.tsx +16 -0
- metadata.json +7 -0
- package.json +32 -0
- server.js +225 -0
- tsconfig.json +29 -0
- types.ts +154 -0
- vite.config.ts +21 -0
App.tsx
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useState, useEffect } from 'react';
|
| 3 |
+
/* Fix: react-router-dom exports may be flaky in this environment, using standard v6 imports */
|
| 4 |
+
import { HashRouter, Routes, Route, Link, useLocation, Navigate } from 'react-router-dom';
|
| 5 |
+
import {
|
| 6 |
+
Leaf,
|
| 7 |
+
Bell,
|
| 8 |
+
LogOut,
|
| 9 |
+
Activity,
|
| 10 |
+
ChevronRight,
|
| 11 |
+
Cpu,
|
| 12 |
+
Settings as SettingsIcon,
|
| 13 |
+
Terminal,
|
| 14 |
+
Loader2
|
| 15 |
+
} from 'lucide-react';
|
| 16 |
+
/* Fix: removed .ts extensions from imports */
|
| 17 |
+
import { routes } from './views/routes';
|
| 18 |
+
import Login from './views/Login';
|
| 19 |
+
import Landing from './views/Landing';
|
| 20 |
+
import PrivacyPolicy from './views/PrivacyPolicy';
|
| 21 |
+
import Documentation from './views/Documentation';
|
| 22 |
+
import Airdrop from './views/Airdrop';
|
| 23 |
+
import { apiClient } from './services/api';
|
| 24 |
+
import { UserSession } from './types/index';
|
| 25 |
+
|
| 26 |
+
const SidebarItem: React.FC<{ icon: any, label: string, path: string, active: boolean }> = ({ icon: Icon, label, path, active }) => (
|
| 27 |
+
<Link
|
| 28 |
+
to={path}
|
| 29 |
+
className={`flex items-center justify-between px-6 py-4 rounded-2xl transition-all duration-500 group ${
|
| 30 |
+
active
|
| 31 |
+
? 'bg-white text-black shadow-2xl'
|
| 32 |
+
: 'text-zinc-500 hover:text-white hover:bg-zinc-900/50'
|
| 33 |
+
}`}
|
| 34 |
+
>
|
| 35 |
+
<div className="flex items-center space-x-4">
|
| 36 |
+
<Icon size={18} className={`${active ? 'text-black' : 'text-zinc-600 group-hover:text-blue-500'} transition-colors duration-500`} />
|
| 37 |
+
<span className="font-black text-[10px] uppercase tracking-[0.2em]">{label}</span>
|
| 38 |
+
</div>
|
| 39 |
+
{active && <ChevronRight size={14} />}
|
| 40 |
+
</Link>
|
| 41 |
+
);
|
| 42 |
+
|
| 43 |
+
const Header = ({ user, onLogout }: { user: UserSession, onLogout: () => void }) => {
|
| 44 |
+
const isEsgNeutral = localStorage.getItem('esg_neutral') === 'true';
|
| 45 |
+
const location = useLocation();
|
| 46 |
+
const currentRoute = routes.find(r => r.path === location.pathname);
|
| 47 |
+
const [typedTitle, setTypedTitle] = useState('');
|
| 48 |
+
|
| 49 |
+
useEffect(() => {
|
| 50 |
+
if (!currentRoute) return;
|
| 51 |
+
let i = 0;
|
| 52 |
+
const fullText = currentRoute.label;
|
| 53 |
+
setTypedTitle('');
|
| 54 |
+
const timer = setInterval(() => {
|
| 55 |
+
setTypedTitle(fullText.substring(0, i + 1));
|
| 56 |
+
i++;
|
| 57 |
+
if (i >= fullText.length) clearInterval(timer);
|
| 58 |
+
}, 40);
|
| 59 |
+
return () => clearInterval(timer);
|
| 60 |
+
}, [location.pathname, currentRoute]);
|
| 61 |
+
|
| 62 |
+
return (
|
| 63 |
+
<header className="h-24 flex items-center justify-between px-10 bg-transparent print:hidden relative z-50">
|
| 64 |
+
<div className="flex items-center space-x-6">
|
| 65 |
+
<div className="hidden md:flex flex-col">
|
| 66 |
+
<h1 className="text-xl font-black tracking-tighter text-white italic leading-none uppercase">
|
| 67 |
+
{typedTitle} <span className="text-blue-500 not-italic block md:inline">NODE</span>
|
| 68 |
+
</h1>
|
| 69 |
+
<p className="text-[10px] text-zinc-600 font-black uppercase tracking-[0.3em] mt-1 flex items-center gap-2">
|
| 70 |
+
<Terminal size={10} />
|
| 71 |
+
ROOT_IDENTIFIER: {user.name.toUpperCase().replace(' ', '_')}
|
| 72 |
+
</p>
|
| 73 |
+
</div>
|
| 74 |
+
|
| 75 |
+
{isEsgNeutral && (
|
| 76 |
+
<div className="px-4 py-1.5 bg-emerald-500/10 border border-emerald-500/20 rounded-full flex items-center gap-2 shadow-2xl shadow-emerald-500/5">
|
| 77 |
+
<Leaf size={12} className="text-emerald-500" />
|
| 78 |
+
<span className="text-[10px] font-black text-emerald-500 uppercase tracking-widest">ESG Offset Verified</span>
|
| 79 |
+
</div>
|
| 80 |
+
)}
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<div className="flex items-center space-x-8">
|
| 84 |
+
<div className="flex items-center space-x-6">
|
| 85 |
+
<button className="p-3 text-zinc-500 hover:text-white transition-all relative group bg-zinc-950 rounded-xl border border-zinc-900 shadow-xl">
|
| 86 |
+
<Bell size={18} />
|
| 87 |
+
<span className="absolute top-3 right-3 w-2 h-2 bg-blue-500 rounded-full shadow-[0_0_10px_rgba(59,130,246,0.8)]"></span>
|
| 88 |
+
</button>
|
| 89 |
+
<div className="h-8 w-px bg-zinc-900"></div>
|
| 90 |
+
<div className="flex items-center space-x-4 group cursor-pointer" onClick={onLogout}>
|
| 91 |
+
<div className="text-right">
|
| 92 |
+
<p className="text-xs font-black text-white uppercase italic tracking-tighter">{user.name}</p>
|
| 93 |
+
<p className="text-[9px] text-zinc-600 uppercase tracking-[0.2em] font-bold">{user.role}</p>
|
| 94 |
+
</div>
|
| 95 |
+
<div className="w-12 h-12 rounded-2xl bg-zinc-950 border border-zinc-900 flex items-center justify-center group-hover:border-rose-500/50 transition-all shadow-2xl">
|
| 96 |
+
<LogOut size={18} className="text-zinc-600 group-hover:text-rose-500 transition-colors" />
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</header>
|
| 102 |
+
);
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
const App: React.FC = () => {
|
| 106 |
+
const [currentUser, setCurrentUser] = useState<UserSession | null>(null);
|
| 107 |
+
const [isAuthChecked, setIsAuthChecked] = useState<boolean>(false);
|
| 108 |
+
|
| 109 |
+
const checkAuthStatus = async () => {
|
| 110 |
+
const { user } = await apiClient.auth.me();
|
| 111 |
+
setCurrentUser(user);
|
| 112 |
+
setIsAuthChecked(true);
|
| 113 |
+
};
|
| 114 |
+
|
| 115 |
+
useEffect(() => {
|
| 116 |
+
checkAuthStatus();
|
| 117 |
+
window.addEventListener('auth-update', checkAuthStatus);
|
| 118 |
+
return () => window.removeEventListener('auth-update', checkAuthStatus);
|
| 119 |
+
}, []);
|
| 120 |
+
|
| 121 |
+
const handleLogout = async () => {
|
| 122 |
+
await apiClient.auth.logout();
|
| 123 |
+
setCurrentUser(null);
|
| 124 |
+
window.dispatchEvent(new Event('auth-update'));
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
if (!isAuthChecked) {
|
| 128 |
+
return (
|
| 129 |
+
<div className="min-h-screen bg-[#020202] flex flex-col items-center justify-center space-y-8">
|
| 130 |
+
<Loader2 className="animate-spin text-blue-500" size={48} />
|
| 131 |
+
<p className="text-[10px] font-black text-zinc-600 uppercase tracking-[0.4em] animate-pulse">Initializing Neural Link...</p>
|
| 132 |
+
</div>
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
if (!currentUser) {
|
| 137 |
+
return (
|
| 138 |
+
<HashRouter>
|
| 139 |
+
<Routes>
|
| 140 |
+
<Route path="/" element={<Landing />} />
|
| 141 |
+
<Route path="/login" element={<Login />} />
|
| 142 |
+
<Route path="/airdrop" element={<Airdrop />} />
|
| 143 |
+
<Route path="/manifesto" element={<PrivacyPolicy />} />
|
| 144 |
+
<Route path="/documentation" element={<Documentation />} />
|
| 145 |
+
<Route path="*" element={<Navigate to="/" replace />} />
|
| 146 |
+
</Routes>
|
| 147 |
+
</HashRouter>
|
| 148 |
+
);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
return (
|
| 152 |
+
<HashRouter>
|
| 153 |
+
<div className="flex min-h-screen bg-[#020202] text-zinc-400 antialiased selection:bg-blue-500/30 selection:text-blue-200 font-sans">
|
| 154 |
+
<div className="fixed inset-0 pointer-events-none opacity-[0.03] z-0 overflow-hidden">
|
| 155 |
+
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:40px_40px]"></div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<aside className="w-80 fixed h-full bg-[#050505] border-r border-zinc-900 p-8 flex flex-col print:hidden z-50">
|
| 159 |
+
<div className="mb-14 px-4 flex items-center space-x-4">
|
| 160 |
+
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center shadow-2xl shadow-white/5">
|
| 161 |
+
<Cpu size={24} className="text-black" />
|
| 162 |
+
</div>
|
| 163 |
+
<div>
|
| 164 |
+
<h1 className="text-lg font-black italic tracking-tighter text-white uppercase leading-none">
|
| 165 |
+
Lumina <span className="text-blue-500 not-italic">Quantum</span>
|
| 166 |
+
</h1>
|
| 167 |
+
<p className="text-[9px] uppercase tracking-[0.4em] font-bold text-zinc-600 mt-1">Institutional Ledger</p>
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<nav className="flex-1 space-y-2 overflow-y-auto custom-scrollbar pr-2 pb-10">
|
| 172 |
+
<NavigationLinks />
|
| 173 |
+
</nav>
|
| 174 |
+
|
| 175 |
+
<div className="mt-auto pt-10 border-t border-zinc-900">
|
| 176 |
+
<SidebarItem icon={SettingsIcon} label="System Config" path="/settings" active={window.location.hash.includes('settings')} />
|
| 177 |
+
<div className="mt-8 p-6 bg-zinc-950 border border-zinc-900 rounded-[2rem] flex items-center justify-between group cursor-help shadow-2xl">
|
| 178 |
+
<div className="flex items-center gap-3">
|
| 179 |
+
<Activity size={16} className="text-emerald-500" />
|
| 180 |
+
<span className="text-[9px] font-black uppercase tracking-widest text-zinc-500">Node Reachable</span>
|
| 181 |
+
</div>
|
| 182 |
+
<div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse shadow-[0_0_8px_rgba(16,185,129,0.5)]"></div>
|
| 183 |
+
</div>
|
| 184 |
+
</div>
|
| 185 |
+
</aside>
|
| 186 |
+
|
| 187 |
+
<main className="flex-1 ml-80 min-h-screen flex flex-col relative z-10 print:ml-0 print:bg-white">
|
| 188 |
+
<Header user={currentUser} onLogout={handleLogout} />
|
| 189 |
+
<div className="px-10 pb-20 print:p-0">
|
| 190 |
+
<Routes>
|
| 191 |
+
{routes.map((route) => (
|
| 192 |
+
<Route key={route.path} path={route.path} element={<route.component />} />
|
| 193 |
+
))}
|
| 194 |
+
<Route path="*" element={<Navigate to="/" replace />} />
|
| 195 |
+
</Routes>
|
| 196 |
+
</div>
|
| 197 |
+
</main>
|
| 198 |
+
</div>
|
| 199 |
+
</HashRouter>
|
| 200 |
+
);
|
| 201 |
+
};
|
| 202 |
+
|
| 203 |
+
const NavigationLinks = () => {
|
| 204 |
+
const location = useLocation();
|
| 205 |
+
const categories = ['core', 'registry', 'finance', 'intelligence', 'system', 'admin'];
|
| 206 |
+
|
| 207 |
+
return (
|
| 208 |
+
<div className="space-y-10">
|
| 209 |
+
{categories.map(cat => {
|
| 210 |
+
const catRoutes = routes.filter(r => r.showInSidebar && r.category === cat);
|
| 211 |
+
if (catRoutes.length === 0) return null;
|
| 212 |
+
return (
|
| 213 |
+
<div key={cat} className="space-y-3">
|
| 214 |
+
<p className="px-6 text-[8px] font-black uppercase text-zinc-700 tracking-[0.5em] mb-4">{cat.toUpperCase()}_V6_SEGMENT</p>
|
| 215 |
+
{catRoutes.map((route) => (
|
| 216 |
+
<SidebarItem
|
| 217 |
+
key={route.path}
|
| 218 |
+
icon={route.icon}
|
| 219 |
+
label={route.label}
|
| 220 |
+
path={route.path}
|
| 221 |
+
active={location.pathname === route.path}
|
| 222 |
+
/>
|
| 223 |
+
))}
|
| 224 |
+
</div>
|
| 225 |
+
);
|
| 226 |
+
})}
|
| 227 |
+
</div>
|
| 228 |
+
);
|
| 229 |
+
};
|
| 230 |
+
|
| 231 |
+
export default App;
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Node.js runtime as the base image
|
| 2 |
+
FROM node:20
|
| 3 |
+
|
| 4 |
+
# Set the working directory in the container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy package.json and install all dependencies
|
| 8 |
+
COPY package.json ./
|
| 9 |
+
RUN npm install
|
| 10 |
+
|
| 11 |
+
# Copy the rest of the application code
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Build the React frontend into the 'dist' folder
|
| 15 |
+
# This is the folder server.js will serve
|
| 16 |
+
RUN npm run build
|
| 17 |
+
|
| 18 |
+
# Create an empty SQLite database file and set permissions
|
| 19 |
+
# This ensures the application has write access in the container
|
| 20 |
+
RUN touch database.db && chmod 777 database.db
|
| 21 |
+
|
| 22 |
+
# Hugging Face Spaces use port 7860 by default
|
| 23 |
+
ENV PORT=7860
|
| 24 |
+
EXPOSE 7860
|
| 25 |
+
|
| 26 |
+
# Start the application using the start script defined in package.json
|
| 27 |
+
CMD ["npm", "start"]
|
README.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
| 3 |
+
</div>
|
| 4 |
+
|
| 5 |
+
# Run and deploy your AI Studio app
|
| 6 |
+
|
| 7 |
+
This contains everything you need to run your app locally.
|
| 8 |
+
|
| 9 |
+
View your app in AI Studio: https://ai.studio/apps/drive/1lIFV2uT_kj0gG9PM3NWzaKh059u7b3nt
|
| 10 |
+
|
| 11 |
+
## Run Locally
|
| 12 |
+
|
| 13 |
+
**Prerequisites:** Node.js
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
1. Install dependencies:
|
| 17 |
+
`npm install`
|
| 18 |
+
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
| 19 |
+
3. Run the app:
|
| 20 |
+
`npm run dev`
|
index.html
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Lumina Quantum Ledger</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
body {
|
| 11 |
+
font-family: 'Inter', sans-serif;
|
| 12 |
+
background-color: #050505;
|
| 13 |
+
color: #e5e5e5;
|
| 14 |
+
}
|
| 15 |
+
.mono {
|
| 16 |
+
font-family: 'JetBrains Mono', monospace;
|
| 17 |
+
}
|
| 18 |
+
::-webkit-scrollbar {
|
| 19 |
+
width: 6px;
|
| 20 |
+
}
|
| 21 |
+
::-webkit-scrollbar-track {
|
| 22 |
+
background: #111;
|
| 23 |
+
}
|
| 24 |
+
::-webkit-scrollbar-thumb {
|
| 25 |
+
background: #333;
|
| 26 |
+
border-radius: 10px;
|
| 27 |
+
}
|
| 28 |
+
::-webkit-scrollbar-thumb:hover {
|
| 29 |
+
background: #444;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@keyframes matrix-scan {
|
| 33 |
+
0% { top: -10%; }
|
| 34 |
+
100% { top: 110%; }
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.matrix-line {
|
| 38 |
+
position: absolute;
|
| 39 |
+
width: 100%;
|
| 40 |
+
height: 2px;
|
| 41 |
+
background: linear-gradient(90deg, transparent, #f43f5e, transparent);
|
| 42 |
+
opacity: 0.3;
|
| 43 |
+
animation: matrix-scan 3s linear infinite;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
@media print {
|
| 47 |
+
@page {
|
| 48 |
+
margin: 0;
|
| 49 |
+
size: auto;
|
| 50 |
+
}
|
| 51 |
+
body {
|
| 52 |
+
background: white !important;
|
| 53 |
+
color: black !important;
|
| 54 |
+
margin: 0 !important;
|
| 55 |
+
padding: 0 !important;
|
| 56 |
+
}
|
| 57 |
+
#root {
|
| 58 |
+
padding: 0 !important;
|
| 59 |
+
}
|
| 60 |
+
.print-hidden {
|
| 61 |
+
display: none !important;
|
| 62 |
+
}
|
| 63 |
+
aside, header, nav, button {
|
| 64 |
+
display: none !important;
|
| 65 |
+
}
|
| 66 |
+
main {
|
| 67 |
+
margin-left: 0 !important;
|
| 68 |
+
padding: 0 !important;
|
| 69 |
+
width: 100% !important;
|
| 70 |
+
}
|
| 71 |
+
.print\:p-0 {
|
| 72 |
+
padding: 0 !important;
|
| 73 |
+
}
|
| 74 |
+
.print\:m-0 {
|
| 75 |
+
margin: 0 !important;
|
| 76 |
+
}
|
| 77 |
+
.print\:shadow-none {
|
| 78 |
+
box-shadow: none !important;
|
| 79 |
+
}
|
| 80 |
+
.print\:border-none {
|
| 81 |
+
border: none !important;
|
| 82 |
+
}
|
| 83 |
+
.print\:rounded-none {
|
| 84 |
+
border-radius: 0 !important;
|
| 85 |
+
}
|
| 86 |
+
* {
|
| 87 |
+
-webkit-print-color-adjust: exact !important;
|
| 88 |
+
print-color-adjust: exact !important;
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
<script type="importmap">
|
| 93 |
+
{
|
| 94 |
+
"imports": {
|
| 95 |
+
"sqlite3": "https://esm.sh/sqlite3@^5.1.7",
|
| 96 |
+
"lucide-react": "https://esm.sh/lucide-react@^0.563.0",
|
| 97 |
+
"react-dom/": "https://esm.sh/react-dom@^19.2.4/",
|
| 98 |
+
"react-router-dom": "https://esm.sh/react-router-dom@^7.13.0",
|
| 99 |
+
"@google/genai": "https://esm.sh/@google/genai@^1.41.0",
|
| 100 |
+
"@vitejs/plugin-react": "https://esm.sh/@vitejs/plugin-react@^5.1.4",
|
| 101 |
+
"react/": "https://esm.sh/react@^19.2.4/",
|
| 102 |
+
"react": "https://esm.sh/react@^19.2.4",
|
| 103 |
+
"path": "https://esm.sh/path@^0.12.7",
|
| 104 |
+
"vite": "https://esm.sh/vite@^7.3.1",
|
| 105 |
+
"recharts": "https://esm.sh/recharts@^3.7.0",
|
| 106 |
+
"bcryptjs": "https://esm.sh/bcryptjs@^3.0.3",
|
| 107 |
+
"url": "https://esm.sh/url@^0.11.4",
|
| 108 |
+
"express": "https://esm.sh/express@^5.2.1",
|
| 109 |
+
"cors": "https://esm.sh/cors@^2.8.6"
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
</script>
|
| 113 |
+
<link rel="stylesheet" href="/index.css">
|
| 114 |
+
</head>
|
| 115 |
+
<body>
|
| 116 |
+
<div id="root"></div>
|
| 117 |
+
<script type="module" src="/index.tsx"></script>
|
| 118 |
+
</body>
|
| 119 |
+
</html>
|
index.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React from 'react';
|
| 3 |
+
import ReactDOM from 'react-dom/client';
|
| 4 |
+
import App from './App';
|
| 5 |
+
|
| 6 |
+
const rootElement = document.getElementById('root');
|
| 7 |
+
if (!rootElement) {
|
| 8 |
+
throw new Error("Could not find root element to mount to");
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const root = ReactDOM.createRoot(rootElement);
|
| 12 |
+
root.render(
|
| 13 |
+
<React.StrictMode>
|
| 14 |
+
<App />
|
| 15 |
+
</React.StrictMode>
|
| 16 |
+
);
|
metadata.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Quantum Ledger Intelligence",
|
| 3 |
+
"description": "A high-performance financial dashboard integrating AI-driven forecasting, sustainability metrics, and corporate asset management using the Gemini API.",
|
| 4 |
+
"requestFramePermissions": [
|
| 5 |
+
"microphone"
|
| 6 |
+
]
|
| 7 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "quantum-ledger-intelligence",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "1.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview",
|
| 10 |
+
"start": "node server.js"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@google/genai": "^1.41.0",
|
| 14 |
+
"bcryptjs": "^3.0.3",
|
| 15 |
+
"cors": "^2.8.6",
|
| 16 |
+
"express": "^5.2.1",
|
| 17 |
+
"lucide-react": "^0.563.0",
|
| 18 |
+
"path": "^0.12.7",
|
| 19 |
+
"react": "^19.2.4",
|
| 20 |
+
"react-dom": "^19.2.4",
|
| 21 |
+
"react-router-dom": "^7.13.0",
|
| 22 |
+
"recharts": "^3.7.0",
|
| 23 |
+
"sqlite3": "^5.1.7"
|
| 24 |
+
},
|
| 25 |
+
"devDependencies": {
|
| 26 |
+
"@types/react": "^19.2.4",
|
| 27 |
+
"@types/react-dom": "^19.2.4",
|
| 28 |
+
"@vitejs/plugin-react": "^5.1.4",
|
| 29 |
+
"typescript": "^5.7.3",
|
| 30 |
+
"vite": "^7.3.1"
|
| 31 |
+
}
|
| 32 |
+
}
|
server.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import express from 'express';
|
| 2 |
+
import cors from 'cors';
|
| 3 |
+
import { GoogleGenAI } from '@google/genai';
|
| 4 |
+
import path from 'path';
|
| 5 |
+
import { fileURLToPath } from 'url';
|
| 6 |
+
import sqlite3 from 'sqlite3';
|
| 7 |
+
import bcrypt from 'bcryptjs';
|
| 8 |
+
|
| 9 |
+
const app = express();
|
| 10 |
+
// Hugging Face Spaces require port 7860
|
| 11 |
+
const port = process.env.PORT || 7860;
|
| 12 |
+
|
| 13 |
+
// --- Database Initialization ---
|
| 14 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 15 |
+
const __dirname = path.dirname(__filename);
|
| 16 |
+
const dbPath = path.join(__dirname, 'database.db');
|
| 17 |
+
const db = new sqlite3.Database(dbPath);
|
| 18 |
+
|
| 19 |
+
db.serialize(() => {
|
| 20 |
+
// Users table
|
| 21 |
+
db.run(`
|
| 22 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 23 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 24 |
+
username TEXT UNIQUE NOT NULL,
|
| 25 |
+
password_hash TEXT NOT NULL,
|
| 26 |
+
role TEXT DEFAULT 'Standard Node',
|
| 27 |
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 28 |
+
)
|
| 29 |
+
`);
|
| 30 |
+
|
| 31 |
+
// Messages table for Neural Memory
|
| 32 |
+
db.run(`
|
| 33 |
+
CREATE TABLE IF NOT EXISTS messages (
|
| 34 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 35 |
+
user_id INTEGER,
|
| 36 |
+
role TEXT NOT NULL,
|
| 37 |
+
content TEXT NOT NULL,
|
| 38 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 39 |
+
FOREIGN KEY(user_id) REFERENCES users(id)
|
| 40 |
+
)
|
| 41 |
+
`);
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
// --- Simulated In-Memory Session Storage ---
|
| 45 |
+
let activeSession = null;
|
| 46 |
+
|
| 47 |
+
// --- Mock Citi Financial Data ---
|
| 48 |
+
const CITI_ACCOUNTS = {
|
| 49 |
+
accountGroupDetails: [
|
| 50 |
+
{
|
| 51 |
+
accountGroup: "CHECKING",
|
| 52 |
+
checkingAccountsDetails: [
|
| 53 |
+
{
|
| 54 |
+
productName: "Corporate Mastery Checking",
|
| 55 |
+
accountNickname: "Main Ops Node",
|
| 56 |
+
accountDescription: "Corporate Mastery Checking - 9594",
|
| 57 |
+
balanceType: "ASSET",
|
| 58 |
+
displayAccountNumber: "XXXXXX9594",
|
| 59 |
+
accountId: "citi_acc_99201",
|
| 60 |
+
currencyCode: "USD",
|
| 61 |
+
accountStatus: "ACTIVE",
|
| 62 |
+
currentBalance: 1245000.50,
|
| 63 |
+
availableBalance: 1240000.00
|
| 64 |
+
}
|
| 65 |
+
],
|
| 66 |
+
totalCurrentBalance: { localCurrencyCode: "USD", localCurrencyBalanceAmount: 1245000.50 },
|
| 67 |
+
totalAvailableBalance: { localCurrencyCode: "USD", localCurrencyBalanceAmount: 1240000.00 }
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
customer: {
|
| 71 |
+
customerId: "citi_cust_884102"
|
| 72 |
+
}
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
const CITI_TRANSACTIONS = {
|
| 76 |
+
"citi_acc_99201": {
|
| 77 |
+
checkingAccountTransactions: [
|
| 78 |
+
{
|
| 79 |
+
accountId: "citi_acc_99201",
|
| 80 |
+
currencyCode: "USD",
|
| 81 |
+
transactionAmount: -25000.00,
|
| 82 |
+
transactionDate: "2024-03-31",
|
| 83 |
+
transactionDescription: "NEURAL_NETWORK_COMPUTE_Q1_ALLOCATION",
|
| 84 |
+
transactionId: "TXN_C_884102",
|
| 85 |
+
transactionStatus: "POSTED",
|
| 86 |
+
transactionType: "PAYMENT",
|
| 87 |
+
displayAccountNumber: "XXXXXX9594"
|
| 88 |
+
}
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
};
|
| 92 |
+
|
| 93 |
+
app.use(cors());
|
| 94 |
+
app.use(express.json());
|
| 95 |
+
app.use(express.urlencoded({ extended: true }));
|
| 96 |
+
|
| 97 |
+
// --- Authentication Endpoints ---
|
| 98 |
+
|
| 99 |
+
app.get('/api/auth/me', (req, res) => {
|
| 100 |
+
if (activeSession) {
|
| 101 |
+
res.json({ isAuthenticated: true, user: activeSession });
|
| 102 |
+
} else {
|
| 103 |
+
res.status(401).json({ isAuthenticated: false, user: null });
|
| 104 |
+
}
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
app.post('/api/auth/register', async (req, res) => {
|
| 108 |
+
const { username, password } = req.body;
|
| 109 |
+
if (!username || !password) return res.status(400).json({ error: 'Identity credentials missing.' });
|
| 110 |
+
|
| 111 |
+
try {
|
| 112 |
+
const hash = await bcrypt.hash(password, 10);
|
| 113 |
+
db.run(
|
| 114 |
+
'INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)',
|
| 115 |
+
[username, hash, 'Root Admin'],
|
| 116 |
+
function(err) {
|
| 117 |
+
if (err) {
|
| 118 |
+
if (err.message.includes('UNIQUE')) {
|
| 119 |
+
return res.status(409).json({ error: 'Identity node already registered.' });
|
| 120 |
+
}
|
| 121 |
+
return res.status(500).json({ error: 'Registry write failure.' });
|
| 122 |
+
}
|
| 123 |
+
res.status(201).json({ success: true, userId: this.lastID });
|
| 124 |
+
}
|
| 125 |
+
);
|
| 126 |
+
} catch (error) {
|
| 127 |
+
res.status(500).json({ error: 'Encryption engine failure.' });
|
| 128 |
+
}
|
| 129 |
+
});
|
| 130 |
+
|
| 131 |
+
app.post('/api/auth/login', (req, res) => {
|
| 132 |
+
const { username, password } = req.body;
|
| 133 |
+
|
| 134 |
+
db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => {
|
| 135 |
+
if (err || !user) {
|
| 136 |
+
return res.status(401).json({ success: false, message: 'Identity node rejected credentials.' });
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
const match = await bcrypt.compare(password, user.password_hash);
|
| 140 |
+
if (match) {
|
| 141 |
+
activeSession = {
|
| 142 |
+
id: `USR-${user.id}`,
|
| 143 |
+
dbId: user.id, // Internal database ID for relations
|
| 144 |
+
name: user.username,
|
| 145 |
+
role: user.role,
|
| 146 |
+
lastLogin: new Date().toISOString()
|
| 147 |
+
};
|
| 148 |
+
res.json({ success: true, user: activeSession });
|
| 149 |
+
} else {
|
| 150 |
+
res.status(401).json({ success: false, message: 'Identity node rejected credentials.' });
|
| 151 |
+
}
|
| 152 |
+
});
|
| 153 |
+
});
|
| 154 |
+
|
| 155 |
+
app.post('/api/auth/logout', (req, res) => {
|
| 156 |
+
activeSession = null;
|
| 157 |
+
res.json({ success: true });
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
// --- Chat History Endpoints ---
|
| 161 |
+
|
| 162 |
+
app.get('/api/chat/history', (req, res) => {
|
| 163 |
+
if (!activeSession) return res.status(401).json({ error: 'Session parity lost.' });
|
| 164 |
+
|
| 165 |
+
db.all(
|
| 166 |
+
'SELECT * FROM messages WHERE user_id = ? ORDER BY timestamp ASC',
|
| 167 |
+
[activeSession.dbId],
|
| 168 |
+
(err, rows) => {
|
| 169 |
+
if (err) return res.status(500).json({ error: 'Neural memory retrieval failure.' });
|
| 170 |
+
res.json(rows);
|
| 171 |
+
}
|
| 172 |
+
);
|
| 173 |
+
});
|
| 174 |
+
|
| 175 |
+
// --- Gemini Proxy ---
|
| 176 |
+
app.post('/api/gemini/generate', async (req, res) => {
|
| 177 |
+
try {
|
| 178 |
+
const apiKey = process.env.API_KEY;
|
| 179 |
+
if (!apiKey) return res.status(500).json({ error: 'API Key missing' });
|
| 180 |
+
|
| 181 |
+
const { model, contents, config, saveToMemory } = req.body;
|
| 182 |
+
const ai = new GoogleGenAI({ apiKey });
|
| 183 |
+
const response = await ai.models.generateContent({
|
| 184 |
+
model: model || 'gemini-3-flash-preview',
|
| 185 |
+
contents,
|
| 186 |
+
config: config || {}
|
| 187 |
+
});
|
| 188 |
+
|
| 189 |
+
const aiText = response.text;
|
| 190 |
+
|
| 191 |
+
// Persist to memory if requested and authenticated
|
| 192 |
+
if (activeSession && saveToMemory && contents[0]?.parts[0]?.text) {
|
| 193 |
+
const userPrompt = contents[0].parts[0].text;
|
| 194 |
+
|
| 195 |
+
// Save user prompt
|
| 196 |
+
db.run('INSERT INTO messages (user_id, role, content) VALUES (?, ?, ?)', [activeSession.dbId, 'user', userPrompt]);
|
| 197 |
+
// Save AI response
|
| 198 |
+
db.run('INSERT INTO messages (user_id, role, content) VALUES (?, ?, ?)', [activeSession.dbId, 'assistant', aiText]);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
res.json({ text: aiText, candidates: response.candidates });
|
| 202 |
+
} catch (error) {
|
| 203 |
+
res.status(500).json({ error: 'AI communication failure', details: error.message });
|
| 204 |
+
}
|
| 205 |
+
});
|
| 206 |
+
|
| 207 |
+
const CITI_BASE = '/api/accounts/account-transactions/partner/v1';
|
| 208 |
+
app.get(`${CITI_BASE}/accounts/details`, (req, res) => res.json(CITI_ACCOUNTS));
|
| 209 |
+
app.get(`${CITI_BASE}/accounts/:accountId/transactions`, (req, res) => {
|
| 210 |
+
const { accountId } = req.params;
|
| 211 |
+
res.json(CITI_TRANSACTIONS[accountId] || { checkingAccountTransactions: [] });
|
| 212 |
+
});
|
| 213 |
+
|
| 214 |
+
// Serve frontend files from the dist directory
|
| 215 |
+
app.use(express.static(path.join(__dirname, 'dist')));
|
| 216 |
+
|
| 217 |
+
// SPA Fallback: Redirect all non-API requests to index.html
|
| 218 |
+
app.get('*', (req, res) => {
|
| 219 |
+
if (req.path.startsWith('/api')) {
|
| 220 |
+
return res.status(404).json({ error: 'API endpoint not found' });
|
| 221 |
+
}
|
| 222 |
+
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
| 223 |
+
});
|
| 224 |
+
|
| 225 |
+
app.listen(port, '0.0.0.0', () => console.log(`Financial Node Proxy Operational on port ${port}`));
|
tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"experimentalDecorators": true,
|
| 5 |
+
"useDefineForClassFields": false,
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"lib": [
|
| 8 |
+
"ES2022",
|
| 9 |
+
"DOM",
|
| 10 |
+
"DOM.Iterable"
|
| 11 |
+
],
|
| 12 |
+
"skipLibCheck": true,
|
| 13 |
+
"types": [
|
| 14 |
+
"node"
|
| 15 |
+
],
|
| 16 |
+
"moduleResolution": "bundler",
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"moduleDetection": "force",
|
| 19 |
+
"allowJs": true,
|
| 20 |
+
"jsx": "react-jsx",
|
| 21 |
+
"paths": {
|
| 22 |
+
"@/*": [
|
| 23 |
+
"./*"
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
"allowImportingTsExtensions": true,
|
| 27 |
+
"noEmit": true
|
| 28 |
+
}
|
| 29 |
+
}
|
types.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
export interface Account {
|
| 3 |
+
id: string;
|
| 4 |
+
name: string;
|
| 5 |
+
type: 'quantum_checking' | 'elite_savings' | 'high_yield_vault';
|
| 6 |
+
balance: number;
|
| 7 |
+
currency: string;
|
| 8 |
+
institution: string;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export interface Transaction {
|
| 12 |
+
id: string;
|
| 13 |
+
amount: number;
|
| 14 |
+
currency: string;
|
| 15 |
+
date: string;
|
| 16 |
+
description: string;
|
| 17 |
+
category: string;
|
| 18 |
+
status: 'POSTED' | 'PENDING';
|
| 19 |
+
type: string;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export interface InternalAccount {
|
| 23 |
+
id: string;
|
| 24 |
+
productName: string;
|
| 25 |
+
displayAccountNumber: string;
|
| 26 |
+
currency: string;
|
| 27 |
+
status: 'ACTIVE' | 'INACTIVE' | 'CLOSED';
|
| 28 |
+
currentBalance: number;
|
| 29 |
+
availableBalance: number;
|
| 30 |
+
institutionName: string;
|
| 31 |
+
connectionId: string;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export interface Counterparty {
|
| 35 |
+
id: string;
|
| 36 |
+
name: string;
|
| 37 |
+
email: string;
|
| 38 |
+
status: 'ACTIVE' | 'PENDING' | 'INACTIVE';
|
| 39 |
+
createdAt: string;
|
| 40 |
+
metadata?: Record<string, any>;
|
| 41 |
+
accounts?: Array<{
|
| 42 |
+
id: string;
|
| 43 |
+
accountType: string;
|
| 44 |
+
accountNumber: string;
|
| 45 |
+
}>;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
export interface Connection {
|
| 49 |
+
id: string;
|
| 50 |
+
vendorCustomerId: string;
|
| 51 |
+
entity: string;
|
| 52 |
+
status: 'CONNECTED' | 'DISCONNECTED' | 'ERROR';
|
| 53 |
+
lastSyncedAt: string;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
export interface Document {
|
| 57 |
+
id: string;
|
| 58 |
+
documentableId: string;
|
| 59 |
+
documentableType: string;
|
| 60 |
+
documentType: string;
|
| 61 |
+
fileName: string;
|
| 62 |
+
size: number;
|
| 63 |
+
createdAt: string;
|
| 64 |
+
format: string;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export interface SimulationResult {
|
| 68 |
+
outcomeNarrative: string;
|
| 69 |
+
projectedValue: number;
|
| 70 |
+
confidenceScore: number;
|
| 71 |
+
status: string;
|
| 72 |
+
simulationId: string;
|
| 73 |
+
keyRisks?: string[];
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
export interface Payee {
|
| 77 |
+
payeeId: string;
|
| 78 |
+
displayName: string;
|
| 79 |
+
merchantName: string;
|
| 80 |
+
status: string;
|
| 81 |
+
address?: {
|
| 82 |
+
line1: string;
|
| 83 |
+
city: string;
|
| 84 |
+
region: string;
|
| 85 |
+
postalCode: string;
|
| 86 |
+
};
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
export interface Payment {
|
| 90 |
+
paymentId: string;
|
| 91 |
+
amount: number;
|
| 92 |
+
status: string;
|
| 93 |
+
dueDate: string;
|
| 94 |
+
toPayeeId: string;
|
| 95 |
+
fromAccountId: string;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
export interface CustomerProfileResponse {
|
| 99 |
+
firstName: string;
|
| 100 |
+
lastName: string;
|
| 101 |
+
middleName: string;
|
| 102 |
+
fullName: string;
|
| 103 |
+
companyName: string;
|
| 104 |
+
emails: Array<{
|
| 105 |
+
emailAddress: string;
|
| 106 |
+
preferenceType: string;
|
| 107 |
+
}>;
|
| 108 |
+
addressList: Array<{
|
| 109 |
+
addressLine1: string;
|
| 110 |
+
city: string;
|
| 111 |
+
countryCode: string;
|
| 112 |
+
postalCode: string;
|
| 113 |
+
addressType: string;
|
| 114 |
+
}>;
|
| 115 |
+
phones: Array<{
|
| 116 |
+
phoneType: string;
|
| 117 |
+
fullPhoneNumber: string;
|
| 118 |
+
preferenceType: string;
|
| 119 |
+
}>;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
export interface ClientRegisterResponse {
|
| 123 |
+
client_id: string;
|
| 124 |
+
client_secret: string;
|
| 125 |
+
client_name: string;
|
| 126 |
+
appId?: string;
|
| 127 |
+
redirect_uris: string[];
|
| 128 |
+
scope: string[];
|
| 129 |
+
token_endpoint_auth_method?: string;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
export interface VirtualAccount {
|
| 133 |
+
id: string;
|
| 134 |
+
name: string;
|
| 135 |
+
description: string | null;
|
| 136 |
+
status: 'ACTIVE' | 'PENDING' | 'CLOSED';
|
| 137 |
+
internal_account_id: string;
|
| 138 |
+
counterparty_id: string | null;
|
| 139 |
+
created_at: string;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
export interface LineItem {
|
| 143 |
+
id: string;
|
| 144 |
+
amount: number;
|
| 145 |
+
currency: string;
|
| 146 |
+
description: string;
|
| 147 |
+
ledger_account_id: string;
|
| 148 |
+
createdAt: string;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
export interface LineItemUpdateRequest {
|
| 152 |
+
description?: string;
|
| 153 |
+
metadata?: Record<string, any>;
|
| 154 |
+
}
|
vite.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import { defineConfig } from 'vite';
|
| 3 |
+
import react from '@vitejs/plugin-react';
|
| 4 |
+
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
server: {
|
| 8 |
+
proxy: {
|
| 9 |
+
'/api': {
|
| 10 |
+
target: 'http://localhost:3001',
|
| 11 |
+
changeOrigin: true,
|
| 12 |
+
secure: false,
|
| 13 |
+
},
|
| 14 |
+
'/openapi': {
|
| 15 |
+
target: 'http://localhost:3001',
|
| 16 |
+
changeOrigin: true,
|
| 17 |
+
secure: false,
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
},
|
| 21 |
+
});
|