Refactor and enhance frontend with security, validation, and performance improvements

## Summary
  Complete frontend overhaul implementing best practices, security hardening,
  full CRUD operations, and performance optimizations across the application.

  ## Key Changes

  ### Architecture & Performance
  - Implement code splitting with React.lazy() reducing main bundle from 795KB to 308KB (61% improvement)
  - Add error boundary component for graceful error handling
  - Create shared utility modules for formatters, validation, and calculations

  ### Security Enhancements
  - Add input sanitization to prevent XSS attacks
  - Implement comprehensive validation (email, phone, positive numbers, required fields)
  - Sanitize all user inputs before storage
  - Add confirmation dialogs for destructive operations

  ### Features & Functionality
  - Implement complete edit/delete operations for assets, liabilities, clients, and invoices
  - Add invoice status update functionality (draft/sent/paid/overdue/cancelled)
  - Connect "Record Snapshot" button with proper functionality
  - Fix hardcoded statistics with dynamic calculations (monthly change, YTD growth)
  - Make all list items clickable with hover effects and visual feedback

  ### Code Quality
  - Replace inline currency formatting with shared formatters
  - Add comprehensive input validation across all forms
  - Display inline error messages for validation failures
  - Implement loading states for lazy-loaded routes
  - Ensure type safety throughout with zero TypeScript errors

  ### UI/UX Improvements
  - Add hover states to all clickable items
  - Display validation errors inline with user-friendly messages
  - Add loading indicators during page transitions
  - Color-code financial metrics (green for positive, red for negative)
  - Improve form user experience with real-time validation

  ## Technical Details
  - Created src/lib/formatters.ts for currency and percentage formatting
  - Created src/lib/validation.ts for input validation and sanitization
  - Created src/lib/calculations.ts for financial calculations
  - Added EditAssetDialog, EditLiabilityDialog, EditClientDialog, InvoiceDetailsDialog
  - Wrapped app in ErrorBoundary for robust error handling
This commit is contained in:
2025-12-07 12:19:02 -05:00
parent 613e8fdb70
commit a62782a58f
14 changed files with 1050 additions and 50 deletions

View File

@@ -1,11 +1,15 @@
import {useState} from 'react';
import {Card, CardContent} from '@/components/ui/card';
import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store';
import {useAppSelector, type Client} from '@/store';
import AddClientDialog from '@/components/dialogs/AddClientDialog';
import EditClientDialog from '@/components/dialogs/EditClientDialog';
import {formatCurrency} from '@/lib/formatters';
export default function ClientsPage() {
const [dialogOpen, setDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [selectedClient, setSelectedClient] = useState<Client | null>(null);
const {clients, invoices} = useAppSelector(state => state.invoices);
const getClientStats = (clientId: string) => {
@@ -15,11 +19,14 @@ export default function ClientsPage() {
return {totalBilled, outstanding, count: clientInvoices.length};
};
const fmt = (value: number) => new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value);
const totalBilled = clients.reduce((sum, c) => sum + getClientStats(c.id).totalBilled, 0);
const totalOutstanding = clients.reduce((sum, c) => sum + getClientStats(c.id).outstanding, 0);
const handleEditClient = (client: Client) => {
setSelectedClient(client);
setEditDialogOpen(true);
};
return (
<div className="p-4">
{/* Header + Summary inline */}
@@ -28,8 +35,8 @@ export default function ClientsPage() {
<h1 className="text-lg font-semibold">Clients</h1>
<div className="flex gap-4 text-sm">
<div><span className="text-muted-foreground">Total Clients</span> <span className="font-medium">{clients.length}</span></div>
<div><span className="text-muted-foreground">Total Billed</span> <span className="font-medium">{fmt(totalBilled)}</span></div>
<div><span className="text-muted-foreground">Outstanding</span> <span className="font-medium">{fmt(totalOutstanding)}</span></div>
<div><span className="text-muted-foreground">Total Billed</span> <span className="font-medium">{formatCurrency(totalBilled)}</span></div>
<div><span className="text-muted-foreground">Outstanding</span> <span className="font-medium">{formatCurrency(totalOutstanding)}</span></div>
</div>
</div>
<Button size="sm" onClick={() => setDialogOpen(true)}>Add Client</Button>
@@ -40,7 +47,11 @@ export default function ClientsPage() {
{clients.map(client => {
const stats = getClientStats(client.id);
return (
<Card key={client.id} className="card-elevated">
<Card
key={client.id}
className="card-elevated cursor-pointer hover:bg-accent/30 transition-colors"
onClick={() => handleEditClient(client)}
>
<CardContent className="p-4">
<div className="flex justify-between items-start mb-2">
<div>
@@ -48,13 +59,13 @@ export default function ClientsPage() {
<p className="text-xs text-muted-foreground">{client.company || client.email}</p>
</div>
{stats.outstanding > 0 && (
<span className="text-xs text-yellow-400">{fmt(stats.outstanding)} due</span>
<span className="text-xs text-yellow-400">{formatCurrency(stats.outstanding)} due</span>
)}
</div>
<div className="flex gap-4 text-sm">
<div>
<p className="text-xs text-muted-foreground">Billed</p>
<p className="font-medium">{fmt(stats.totalBilled)}</p>
<p className="font-medium">{formatCurrency(stats.totalBilled)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Invoices</p>
@@ -75,6 +86,7 @@ export default function ClientsPage() {
</div>
<AddClientDialog open={dialogOpen} onOpenChange={setDialogOpen} />
<EditClientDialog open={editDialogOpen} onOpenChange={setEditDialogOpen} client={selectedClient} />
</div>
);
}