Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled

Complete rewrite from Express to NestJS with enterprise-grade features:

## Backend Improvements
- Migrated from Express to NestJS 11.0.1 with TypeScript
- Implemented Prisma ORM 7.3.0 for type-safe database access
- Added CASL authorization system replacing role-based guards
- Created global exception filters with structured logging
- Implemented Auth0 JWT authentication with Passport.js
- Added vehicle management with conflict detection
- Enhanced event scheduling with driver/vehicle assignment
- Comprehensive error handling and logging

## Frontend Improvements
- Upgraded to React 19.2.0 with Vite 7.2.4
- Implemented CASL-based permission system
- Added AbilityContext for declarative permissions
- Created ErrorHandler utility for consistent error messages
- Enhanced API client with request/response logging
- Added War Room (Command Center) dashboard
- Created VIP Schedule view with complete itineraries
- Implemented Vehicle Management UI
- Added mock data generators for testing (288 events across 20 VIPs)

## New Features
- Vehicle fleet management (types, capacity, status tracking)
- Complete 3-day Jamboree schedule generation
- Individual VIP schedule pages with PDF export (planned)
- Real-time War Room dashboard with auto-refresh
- Permission-based navigation filtering
- First user auto-approval as administrator

## Documentation
- Created CASL_AUTHORIZATION.md (comprehensive guide)
- Created ERROR_HANDLING.md (error handling patterns)
- Updated CLAUDE.md with new architecture
- Added migration guides and best practices

## Technical Debt Resolved
- Removed custom authentication in favor of Auth0
- Replaced role checks with CASL abilities
- Standardized error responses across API
- Implemented proper TypeScript typing
- Added comprehensive logging

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 08:50:25 +01:00
parent 8ace1ab2c1
commit 868f7efc23
351 changed files with 44997 additions and 6276 deletions

View File

@@ -1,110 +1,159 @@
import React, { useState } from 'react';
interface DriverFormData {
name: string;
phone: string;
vehicleCapacity: number;
}
import { useState } from 'react';
import { X } from 'lucide-react';
interface DriverFormProps {
onSubmit: (driverData: DriverFormData) => void;
driver?: Driver | null;
onSubmit: (data: DriverFormData) => void;
onCancel: () => void;
isSubmitting: boolean;
}
const DriverForm: React.FC<DriverFormProps> = ({ onSubmit, onCancel }) => {
interface Driver {
id: string;
name: string;
phone: string;
department: string | null;
userId: string | null;
}
export interface DriverFormData {
name: string;
phone: string;
department?: string;
userId?: string;
}
export function DriverForm({ driver, onSubmit, onCancel, isSubmitting }: DriverFormProps) {
const [formData, setFormData] = useState<DriverFormData>({
name: '',
phone: '',
vehicleCapacity: 4
name: driver?.name || '',
phone: driver?.phone || '',
department: driver?.department || 'OFFICE_OF_DEVELOPMENT',
userId: driver?.userId || '',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(formData);
// Clean up the data - remove empty strings for optional fields
const cleanedData = {
...formData,
department: formData.department || undefined,
userId: formData.userId || undefined,
};
onSubmit(cleanedData);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
setFormData(prev => ({
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === 'number' || name === 'vehicleCapacity' ? parseInt(value) || 0 : value
[name]: value,
}));
};
return (
<div className="modal-overlay">
<div className="modal-content">
{/* Modal Header */}
<div className="modal-header">
<h2 className="text-2xl font-bold text-slate-800">Add New Driver</h2>
<p className="text-slate-600 mt-2">Enter driver contact information</p>
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b">
<h2 className="text-2xl font-bold text-gray-900">
{driver ? 'Edit Driver' : 'Add New Driver'}
</h2>
<button
onClick={onCancel}
className="text-gray-400 hover:text-gray-600"
>
<X className="h-6 w-6" />
</button>
</div>
{/* Modal Body */}
<div className="modal-body">
<form onSubmit={handleSubmit} className="space-y-6">
<div className="form-group">
<label htmlFor="name" className="form-label">Driver Name *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className="form-input"
placeholder="Enter driver's full name"
required
/>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Full Name *
</label>
<input
type="text"
name="name"
required
value={formData.name}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
<div className="form-group">
<label htmlFor="phone" className="form-label">Phone Number *</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className="form-input"
placeholder="Enter phone number"
required
/>
</div>
{/* Phone */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Phone Number *
</label>
<input
type="tel"
name="phone"
required
value={formData.phone}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
/>
</div>
<div className="form-group">
<label htmlFor="vehicleCapacity" className="form-label">Vehicle Capacity *</label>
<select
id="vehicleCapacity"
name="vehicleCapacity"
value={formData.vehicleCapacity}
onChange={handleChange}
className="form-input"
required
>
<option value={2}>2 passengers (Sedan/Coupe)</option>
<option value={4}>4 passengers (Standard Car)</option>
<option value={6}>6 passengers (SUV/Van)</option>
<option value={8}>8 passengers (Large Van)</option>
<option value={12}>12 passengers (Mini Bus)</option>
</select>
<p className="text-sm text-slate-600 mt-1">
🚗 Select the maximum number of passengers this vehicle can accommodate
</p>
</div>
{/* Department */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Department
</label>
<select
name="department"
value={formData.department}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="">Select Department</option>
<option value="OFFICE_OF_DEVELOPMENT">Office of Development</option>
<option value="ADMIN">Admin</option>
</select>
</div>
<div className="form-actions">
<button type="button" className="btn btn-secondary" onClick={onCancel}>
Cancel
</button>
<button type="submit" className="btn btn-primary">
Add Driver
</button>
</div>
</form>
</div>
{/* User ID (optional, for linking driver to user account) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
User Account ID (Optional)
</label>
<input
type="text"
name="userId"
value={formData.userId}
onChange={handleChange}
placeholder="Leave blank for standalone driver"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
/>
<p className="mt-1 text-xs text-gray-500">
Link this driver to a user account for login access
</p>
</div>
{/* Actions */}
<div className="flex gap-3 pt-4">
<button
type="submit"
disabled={isSubmitting}
className="flex-1 bg-primary text-white py-2 px-4 rounded-md hover:bg-primary/90 disabled:opacity-50"
>
{isSubmitting ? 'Saving...' : driver ? 'Update Driver' : 'Create Driver'}
</button>
<button
type="button"
onClick={onCancel}
className="flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300"
>
Cancel
</button>
</div>
</form>
</div>
</div>
);
};
export default DriverForm;
}