- Web3 authentication with MetaMask, WalletConnect, Coinbase wallet options - Demo mode for testing without wallet - Overview dashboard with KPI cards, asset allocation, positions, accounts, alerts - Transaction Builder module (full IDE-style drag-and-drop canvas with 28 gap fixes) - Accounts module with multi-account/subaccount hierarchical structures - Treasury Management module with positions table and 14-day cash forecast - Financial Reporting module with IPSAS, US GAAP, IFRS compliance - Compliance & Risk module with KYC/AML/Sanctions monitoring - Settlement & Clearing module with DVP/FOP/PVP operations - Settings with role-based permissions and enterprise controls - Dark theme professional UI with Solace Bank branding - HashRouter for static hosting compatibility Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
154 lines
5.5 KiB
TypeScript
154 lines
5.5 KiB
TypeScript
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';
|
|
import { BrowserProvider, formatEther } from 'ethers';
|
|
import type { AuthState, WalletInfo, PortalUser, UserRole, Permission } from '../types/portal';
|
|
|
|
interface AuthContextType extends AuthState {
|
|
connectWallet: (provider: 'metamask' | 'walletconnect' | 'coinbase') => Promise<void>;
|
|
disconnect: () => void;
|
|
error: string | null;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
|
|
admin: [
|
|
'accounts.view', 'accounts.manage', 'accounts.create',
|
|
'transactions.view', 'transactions.create', 'transactions.approve', 'transactions.execute',
|
|
'treasury.view', 'treasury.manage', 'treasury.rebalance',
|
|
'compliance.view', 'compliance.manage', 'compliance.override',
|
|
'reports.view', 'reports.generate', 'reports.export',
|
|
'settlements.view', 'settlements.approve',
|
|
'admin.users', 'admin.settings', 'admin.audit',
|
|
],
|
|
treasurer: [
|
|
'accounts.view', 'accounts.manage',
|
|
'transactions.view', 'transactions.create', 'transactions.approve',
|
|
'treasury.view', 'treasury.manage', 'treasury.rebalance',
|
|
'reports.view', 'reports.generate', 'reports.export',
|
|
'settlements.view', 'settlements.approve',
|
|
],
|
|
analyst: [
|
|
'accounts.view', 'transactions.view', 'treasury.view',
|
|
'reports.view', 'reports.generate', 'settlements.view',
|
|
],
|
|
compliance_officer: [
|
|
'accounts.view', 'transactions.view', 'treasury.view',
|
|
'compliance.view', 'compliance.manage',
|
|
'reports.view', 'reports.generate', 'reports.export',
|
|
'settlements.view',
|
|
],
|
|
auditor: [
|
|
'accounts.view', 'transactions.view', 'treasury.view',
|
|
'compliance.view', 'reports.view', 'reports.export',
|
|
'settlements.view', 'admin.audit',
|
|
],
|
|
viewer: ['accounts.view', 'transactions.view', 'treasury.view', 'reports.view', 'settlements.view'],
|
|
};
|
|
|
|
const AUTH_STORAGE_KEY = 'solace-auth';
|
|
|
|
function generateUser(address: string): PortalUser {
|
|
return {
|
|
id: `usr-${address.slice(2, 10)}`,
|
|
displayName: `${address.slice(0, 6)}...${address.slice(-4)}`,
|
|
role: 'admin',
|
|
permissions: ROLE_PERMISSIONS['admin'],
|
|
institution: 'Solace Bank Group PLC',
|
|
department: 'Treasury Operations',
|
|
lastLogin: new Date(),
|
|
walletAddress: address,
|
|
};
|
|
}
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [state, setState] = useState<AuthState>({
|
|
isAuthenticated: false,
|
|
wallet: null,
|
|
user: null,
|
|
loading: true,
|
|
});
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem(AUTH_STORAGE_KEY);
|
|
if (saved) {
|
|
try {
|
|
const parsed = JSON.parse(saved);
|
|
setState({
|
|
isAuthenticated: true,
|
|
wallet: parsed.wallet,
|
|
user: { ...parsed.user, lastLogin: new Date(parsed.user.lastLogin) },
|
|
loading: false,
|
|
});
|
|
return;
|
|
} catch { /* ignore */ }
|
|
}
|
|
setState(prev => ({ ...prev, loading: false }));
|
|
}, []);
|
|
|
|
const connectWallet = useCallback(async (providerType: 'metamask' | 'walletconnect' | 'coinbase') => {
|
|
setError(null);
|
|
setState(prev => ({ ...prev, loading: true }));
|
|
|
|
try {
|
|
let address: string;
|
|
let chainId: number;
|
|
let balance: string;
|
|
|
|
const ethereum = (window as unknown as Record<string, unknown>).ethereum as {
|
|
request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
|
|
isMetaMask?: boolean;
|
|
isCoinbaseWallet?: boolean;
|
|
chainId?: string;
|
|
} | undefined;
|
|
|
|
if (ethereum && (providerType === 'metamask' || providerType === 'coinbase')) {
|
|
const accounts = await ethereum.request({ method: 'eth_requestAccounts' }) as string[];
|
|
if (!accounts || accounts.length === 0) throw new Error('No accounts returned');
|
|
|
|
const provider = new BrowserProvider(ethereum as never);
|
|
const signer = await provider.getSigner();
|
|
address = await signer.getAddress();
|
|
const network = await provider.getNetwork();
|
|
chainId = Number(network.chainId);
|
|
const bal = await provider.getBalance(address);
|
|
balance = formatEther(bal);
|
|
} else {
|
|
// Demo mode — simulate wallet connection for environments without MetaMask
|
|
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
address = '0x' + Array.from({ length: 40 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
chainId = 1;
|
|
balance = (Math.random() * 100).toFixed(4);
|
|
}
|
|
|
|
const wallet: WalletInfo = { address, chainId, balance, provider: providerType };
|
|
const user = generateUser(address);
|
|
|
|
const newState: AuthState = { isAuthenticated: true, wallet, user, loading: false };
|
|
setState(newState);
|
|
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify({ wallet, user }));
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'Failed to connect wallet';
|
|
setError(msg);
|
|
setState(prev => ({ ...prev, loading: false }));
|
|
}
|
|
}, []);
|
|
|
|
const disconnect = useCallback(() => {
|
|
setState({ isAuthenticated: false, wallet: null, user: null, loading: false });
|
|
localStorage.removeItem(AUTH_STORAGE_KEY);
|
|
}, []);
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ ...state, connectWallet, disconnect, error }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
|
return ctx;
|
|
}
|