diff --git a/apps/web/package.json b/apps/web/package.json
index a40e710..02c27c8 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -10,15 +10,16 @@
"clean": "rm -rf dist"
},
"dependencies": {
+ "@brazil-swift-ops/audit": "workspace:*",
+ "@brazil-swift-ops/iso20022": "workspace:*",
+ "@brazil-swift-ops/risk-models": "workspace:*",
+ "@brazil-swift-ops/rules-engine": "workspace:*",
+ "@brazil-swift-ops/treasury": "workspace:*",
"@brazil-swift-ops/types": "workspace:*",
"@brazil-swift-ops/utils": "workspace:*",
- "@brazil-swift-ops/rules-engine": "workspace:*",
- "@brazil-swift-ops/iso20022": "workspace:*",
- "@brazil-swift-ops/treasury": "workspace:*",
- "@brazil-swift-ops/risk-models": "workspace:*",
- "@brazil-swift-ops/audit": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.20.0",
"zustand": "^4.4.7"
},
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 46b3df8..14ced66 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,65 +1,88 @@
-import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
+import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';
import { ErrorBoundary } from './components/ErrorBoundary';
+import { ToastProvider } from './components/ToastProvider';
+import UserMenu from './components/UserMenu';
import DashboardPage from './pages/DashboardPage';
import TransactionsPage from './pages/TransactionsPage';
import TreasuryPage from './pages/TreasuryPage';
import ReportsPage from './pages/ReportsPage';
+import { FiHome, FiFileText, FiDollarSign, FiBarChart2, FiBell } from 'react-icons/fi';
+
+function Navigation() {
+ const location = useLocation();
+
+ const isActive = (path: string) => location.pathname === path;
+
+ const navItems = [
+ { path: '/', label: 'Dashboard', icon: FiHome },
+ { path: '/transactions', label: 'Transactions', icon: FiFileText },
+ { path: '/treasury', label: 'Treasury', icon: FiDollarSign },
+ { path: '/reports', label: 'Reports', icon: FiBarChart2 },
+ ];
+
+ return (
+
+ );
+}
function App() {
return (
-
-
-
+
+
+
+
-
-
- } />
- } />
- } />
- } />
-
-
-
-
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
);
}
diff --git a/apps/web/src/components/Toast.tsx b/apps/web/src/components/Toast.tsx
new file mode 100644
index 0000000..e2a9ec6
--- /dev/null
+++ b/apps/web/src/components/Toast.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { FiCheckCircle, FiXCircle, FiAlertCircle, FiInfo } from 'react-icons/fi';
+
+export type ToastType = 'success' | 'error' | 'warning' | 'info';
+
+interface ToastProps {
+ type: ToastType;
+ message: string;
+ onClose: () => void;
+}
+
+export default function Toast({ type, message, onClose }: ToastProps) {
+ const icons = {
+ success: ,
+ error: ,
+ warning: ,
+ info: ,
+ };
+
+ const colors = {
+ success: 'bg-green-50 border-green-200 text-green-800',
+ error: 'bg-red-50 border-red-200 text-red-800',
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
+ info: 'bg-blue-50 border-blue-200 text-blue-800',
+ };
+
+ const iconColors = {
+ success: 'text-green-600',
+ error: 'text-red-600',
+ warning: 'text-yellow-600',
+ info: 'text-blue-600',
+ };
+
+ return (
+
+
{icons[type]}
+
{message}
+
+
+ );
+}
diff --git a/apps/web/src/pages/ReportsPage.tsx b/apps/web/src/pages/ReportsPage.tsx
index e87ce40..6c6152f 100644
--- a/apps/web/src/pages/ReportsPage.tsx
+++ b/apps/web/src/pages/ReportsPage.tsx
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
import { useTransactionStore } from '../stores/transactionStore';
+import { useToast } from '../components/ToastProvider';
import { generateBCBReport, exportBCBReportToCSV } from '@brazil-swift-ops/audit';
import type { BCBReport } from '@brazil-swift-ops/types';
export default function ReportsPage() {
const { transactions, results } = useTransactionStore();
+ const { showToast } = useToast();
const [report, setReport] = useState(null);
const [dateRange, setDateRange] = useState({
start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
@@ -33,34 +35,52 @@ export default function ReportsPage() {
);
setReport(generatedReport);
+ showToast('success', `Report generated successfully with ${generatedReport.summary.totalTransactions} transactions`);
} catch (error) {
- console.error('Error generating report:', error);
+ const errorMessage = error instanceof Error ? error.message : 'Failed to generate report';
+ showToast('error', errorMessage);
} finally {
setIsGenerating(false);
}
};
const handleExportJSON = () => {
- if (!report) return;
- const blob = new Blob([report.data], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `BCB_Report_${report.reportId}.json`;
- a.click();
- URL.revokeObjectURL(url);
+ if (!report) {
+ showToast('warning', 'Please generate a report first');
+ return;
+ }
+ try {
+ const blob = new Blob([report.data], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `BCB_Report_${report.reportId}.json`;
+ a.click();
+ URL.revokeObjectURL(url);
+ showToast('success', 'Report exported as JSON');
+ } catch (error) {
+ showToast('error', 'Failed to export report');
+ }
};
const handleExportCSV = () => {
- if (!report) return;
- const csv = exportBCBReportToCSV(report);
- const blob = new Blob([csv], { type: 'text/csv' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `BCB_Report_${report.reportId}.csv`;
- a.click();
- URL.revokeObjectURL(url);
+ if (!report) {
+ showToast('warning', 'Please generate a report first');
+ return;
+ }
+ try {
+ const csv = exportBCBReportToCSV(report);
+ const blob = new Blob([csv], { type: 'text/csv' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `BCB_Report_${report.reportId}.csv`;
+ a.click();
+ URL.revokeObjectURL(url);
+ showToast('success', 'Report exported as CSV');
+ } catch (error) {
+ showToast('error', 'Failed to export report');
+ }
};
return (
diff --git a/apps/web/src/pages/TransactionsPage.tsx b/apps/web/src/pages/TransactionsPage.tsx
index 5bce538..27d9a29 100644
--- a/apps/web/src/pages/TransactionsPage.tsx
+++ b/apps/web/src/pages/TransactionsPage.tsx
@@ -1,5 +1,6 @@
import React, { useState, useMemo, useCallback } from 'react';
import { useTransactionStore } from '../stores/transactionStore';
+import { useToast } from '../components/ToastProvider';
import type { Transaction } from '@brazil-swift-ops/types';
import { calculateTransactionEOUplift, getDefaultConverter, getAllBanks, getBankBySwiftCode } from '@brazil-swift-ops/utils';
import {
@@ -16,6 +17,7 @@ import LoadingSpinner from '../components/LoadingSpinner';
export default function TransactionsPage() {
const { transactions, results, addTransaction, evaluateTransaction } = useTransactionStore();
+ const { showToast } = useToast();
const [isProcessing, setIsProcessing] = useState(false);
const [formData, setFormData] = useState>({
direction: 'outbound',
@@ -170,13 +172,15 @@ export default function TransactionsPage() {
country: 'BR',
},
});
+ setErrors({});
} catch (error) {
- console.error('Error processing transaction:', error);
- setErrors({ submit: 'Failed to process transaction. Please try again.' });
+ const errorMessage = error instanceof Error ? error.message : 'Failed to process transaction. Please try again.';
+ showToast('error', errorMessage);
+ setErrors({ submit: errorMessage });
} finally {
setIsProcessing(false);
}
- }, [formData, addTransaction, converter]);
+ }, [formData, addTransaction, converter, showToast]);
return (
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c03e0a..9b44439 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -81,6 +81,9 @@ importers:
react-dom:
specifier: ^18.2.0
version: 18.3.1(react@18.3.1)
+ react-icons:
+ specifier: ^5.5.0
+ version: 5.5.0(react@18.3.1)
react-router-dom:
specifier: ^6.20.0
version: 6.30.3(react-dom@18.3.1)(react@18.3.1)
@@ -2453,6 +2456,14 @@ packages:
scheduler: 0.23.2
dev: false
+ /react-icons@5.5.0(react@18.3.1):
+ resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
+ peerDependencies:
+ react: '*'
+ dependencies:
+ react: 18.3.1
+ dev: false
+
/react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
dev: true