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

@@ -0,0 +1,59 @@
/**
* Input validation and sanitization utilities
*/
export const sanitizeString = (input: string): string => {
// Remove potential XSS vectors while preserving legitimate content
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
.replace(/javascript:/gi, '')
.trim();
};
export const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
export const validatePhone = (phone: string): boolean => {
// Accepts various phone formats
const phoneRegex = /^[\d\s\-\(\)\+]+$/;
return phoneRegex.test(phone) && phone.replace(/\D/g, '').length >= 10;
};
export const validateNumber = (value: string): number | null => {
const parsed = parseFloat(value);
if (isNaN(parsed)) return null;
return parsed;
};
export const validatePositiveNumber = (value: string): number | null => {
const num = validateNumber(value);
if (num === null || num < 0) return null;
return num;
};
export const validateRequired = (value: string): boolean => {
return value.trim().length > 0;
};
export const validateInvoiceNumber = (invoiceNumber: string, existingNumbers: string[]): boolean => {
if (!validateRequired(invoiceNumber)) return false;
// Check uniqueness
const sanitized = sanitizeString(invoiceNumber);
return !existingNumbers.some(num => num === sanitized);
};
export const generateInvoiceNumber = (existingNumbers: string[]): string => {
const year = new Date().getFullYear();
let counter = 1;
let invoiceNum: string;
do {
invoiceNum = `INV-${year}-${String(counter).padStart(3, '0')}`;
counter++;
} while (existingNumbers.includes(invoiceNum));
return invoiceNum;
};