dev experience
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import AlphaFooter from '@/components/alpha/AlphaFooter';
|
||||
import { AlphaNav } from '@/components/alpha/AlphaNav';
|
||||
import DevToolbar from '@/components/dev/DevToolbar';
|
||||
import { DebugModeToggle } from '@/components/dev/DebugModeToggle';
|
||||
import { ApiErrorBoundary } from '@/components/errors/ApiErrorBoundary';
|
||||
import { EnhancedErrorBoundary } from '@/components/errors/EnhancedErrorBoundary';
|
||||
import { NotificationIntegration } from '@/components/errors/NotificationIntegration';
|
||||
import { ErrorAnalyticsDashboard } from '@/components/errors/ErrorAnalyticsDashboard';
|
||||
import NotificationProvider from '@/components/notifications/NotificationProvider';
|
||||
import { AuthProvider } from '@/lib/auth/AuthContext';
|
||||
import { getAppMode } from '@/lib/mode';
|
||||
@@ -100,8 +98,6 @@ export default async function RootLayout({
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<>
|
||||
<DevToolbar />
|
||||
<DebugModeToggle />
|
||||
<ErrorAnalyticsDashboard refreshInterval={5000} />
|
||||
</>
|
||||
)}
|
||||
</EnhancedErrorBoundary>
|
||||
@@ -149,8 +145,7 @@ export default async function RootLayout({
|
||||
{/* Development Tools */}
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<>
|
||||
<DebugModeToggle />
|
||||
<ErrorAnalyticsDashboard refreshInterval={5000} />
|
||||
<DevToolbar />
|
||||
</>
|
||||
)}
|
||||
</EnhancedErrorBoundary>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { useNotifications } from '@/components/notifications/NotificationProvider';
|
||||
import type { NotificationVariant } from '@/components/notifications/notificationTypes';
|
||||
import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, LogIn, Play } from 'lucide-react';
|
||||
import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, LogIn, AlertTriangle } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
|
||||
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
|
||||
|
||||
// Import our new components
|
||||
import { Accordion } from './Accordion';
|
||||
@@ -16,7 +17,6 @@ import { UrgencySection } from './sections/UrgencySection';
|
||||
import { NotificationSendSection } from './sections/NotificationSendSection';
|
||||
import { APIStatusSection } from './sections/APIStatusSection';
|
||||
import { LoginSection } from './sections/LoginSection';
|
||||
import { ReplaySection } from './sections/ReplaySection';
|
||||
|
||||
// Import types
|
||||
import type { DemoNotificationType, DemoUrgency, LoginMode } from './types';
|
||||
@@ -39,6 +39,9 @@ export default function DevToolbar() {
|
||||
const [circuitBreakers, setCircuitBreakers] = useState(() => CircuitBreakerRegistry.getInstance().getStatus());
|
||||
const [checkingHealth, setCheckingHealth] = useState(false);
|
||||
|
||||
// Error Stats State
|
||||
const [errorStats, setErrorStats] = useState({ total: 0, byType: {} as Record<string, number> });
|
||||
|
||||
// Accordion state - only one open at a time
|
||||
const [openAccordion, setOpenAccordion] = useState<string | null>('notifications');
|
||||
|
||||
@@ -136,6 +139,28 @@ export default function DevToolbar() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Error Stats Effect
|
||||
useEffect(() => {
|
||||
const updateErrorStats = () => {
|
||||
try {
|
||||
const handler = getGlobalErrorHandler();
|
||||
const stats = handler.getStats();
|
||||
setErrorStats(stats);
|
||||
} catch {
|
||||
// Handler might not be initialized yet
|
||||
setErrorStats({ total: 0, byType: {} });
|
||||
}
|
||||
};
|
||||
|
||||
// Initial update
|
||||
updateErrorStats();
|
||||
|
||||
// Poll for updates every 3 seconds
|
||||
const interval = setInterval(updateErrorStats, 3000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// API Health Check Handler
|
||||
const handleApiHealthCheck = async () => {
|
||||
setCheckingHealth(true);
|
||||
@@ -412,15 +437,43 @@ export default function DevToolbar() {
|
||||
/>
|
||||
</Accordion>
|
||||
|
||||
{/* Replay Section - Accordion */}
|
||||
{/* Error Stats Section - Accordion */}
|
||||
<Accordion
|
||||
title="Error Replay"
|
||||
icon={<Play className="w-4 h-4 text-gray-400" />}
|
||||
isOpen={openAccordion === 'replay'}
|
||||
onToggle={() => setOpenAccordion(openAccordion === 'replay' ? null : 'replay')}
|
||||
title="Error Stats"
|
||||
icon={<AlertTriangle className="w-4 h-4 text-gray-400" />}
|
||||
isOpen={openAccordion === 'errors'}
|
||||
onToggle={() => setOpenAccordion(openAccordion === 'errors' ? null : 'errors')}
|
||||
>
|
||||
<ReplaySection />
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between items-center p-2 bg-iron-gray/30 rounded">
|
||||
<span className="text-gray-400">Total Errors</span>
|
||||
<span className="font-mono font-bold text-red-400">{errorStats.total}</span>
|
||||
</div>
|
||||
{Object.keys(errorStats.byType).length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
{Object.entries(errorStats.byType).map(([type, count]) => (
|
||||
<div key={type} className="flex justify-between items-center p-1.5 bg-deep-graphite rounded">
|
||||
<span className="text-gray-300">{type}</span>
|
||||
<span className="font-mono text-yellow-400">{count}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-gray-500 py-2">No errors yet</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
const handler = getGlobalErrorHandler();
|
||||
handler.clearHistory();
|
||||
setErrorStats({ total: 0, byType: {} });
|
||||
}}
|
||||
className="w-full p-2 bg-iron-gray hover:bg-charcoal-outline text-gray-300 rounded border border-charcoal-outline text-xs"
|
||||
>
|
||||
Clear Error History
|
||||
</button>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ErrorReporter } from '../../interfaces/ErrorReporter';
|
||||
import { ApiError, ApiErrorType } from './ApiError';
|
||||
import { RetryHandler, CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG } from './RetryHandler';
|
||||
import { ApiConnectionMonitor } from './ApiConnectionMonitor';
|
||||
import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger';
|
||||
|
||||
export interface BaseApiClientOptions {
|
||||
timeout?: number;
|
||||
@@ -214,6 +215,28 @@ export class BaseApiClient {
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
let requestId: string | undefined;
|
||||
|
||||
// Log request start (only in development for maximum transparency)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
try {
|
||||
const apiLogger = getGlobalApiLogger();
|
||||
const headerObj: Record<string, string> = {};
|
||||
if (typeof headers === 'object') {
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
headerObj[key] = value;
|
||||
});
|
||||
}
|
||||
requestId = apiLogger.logRequest(
|
||||
endpoint,
|
||||
method,
|
||||
headerObj,
|
||||
data
|
||||
);
|
||||
} catch (e) {
|
||||
// Silent fail - logger might not be initialized
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, config);
|
||||
@@ -235,6 +258,17 @@ export class BaseApiClient {
|
||||
circuitBreaker.recordFailure();
|
||||
this.connectionMonitor.recordFailure(error);
|
||||
this.handleError(error);
|
||||
|
||||
// Log error
|
||||
if (process.env.NODE_ENV === 'development' && requestId) {
|
||||
try {
|
||||
const apiLogger = getGlobalApiLogger();
|
||||
apiLogger.logError(requestId, error, responseTime);
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -243,9 +277,31 @@ export class BaseApiClient {
|
||||
|
||||
const text = await response.text();
|
||||
if (!text) {
|
||||
// Log empty response
|
||||
if (process.env.NODE_ENV === 'development' && requestId) {
|
||||
try {
|
||||
const apiLogger = getGlobalApiLogger();
|
||||
apiLogger.logResponse(requestId, response, null, responseTime);
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
}
|
||||
return null as T;
|
||||
}
|
||||
return JSON.parse(text) as T;
|
||||
|
||||
const parsedData = JSON.parse(text) as T;
|
||||
|
||||
// Log successful response
|
||||
if (process.env.NODE_ENV === 'development' && requestId) {
|
||||
try {
|
||||
const apiLogger = getGlobalApiLogger();
|
||||
apiLogger.logResponse(requestId, response, parsedData, responseTime);
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
}
|
||||
|
||||
return parsedData;
|
||||
|
||||
} catch (error) {
|
||||
const responseTime = Date.now() - startTime;
|
||||
@@ -260,6 +316,16 @@ export class BaseApiClient {
|
||||
circuitBreaker.recordFailure();
|
||||
this.connectionMonitor.recordFailure(apiError);
|
||||
this.handleError(apiError);
|
||||
|
||||
// Log network error
|
||||
if (process.env.NODE_ENV === 'development' && requestId) {
|
||||
try {
|
||||
const apiLogger = getGlobalApiLogger();
|
||||
apiLogger.logError(requestId, apiError, responseTime);
|
||||
} catch (e) {
|
||||
// Silent fail
|
||||
}
|
||||
}
|
||||
|
||||
throw apiError;
|
||||
}
|
||||
|
||||
@@ -117,10 +117,6 @@ export class GlobalErrorHandler {
|
||||
// Store in history
|
||||
this.addToHistory(error, enhancedContext);
|
||||
|
||||
// Show dev overlay if enabled
|
||||
if (this.options.showDevOverlay) {
|
||||
this.showDevOverlay(error, enhancedContext);
|
||||
}
|
||||
|
||||
// Report to external if enabled
|
||||
if (this.options.reportToExternal) {
|
||||
@@ -161,10 +157,6 @@ export class GlobalErrorHandler {
|
||||
// Store in history
|
||||
this.addToHistory(error, enhancedContext);
|
||||
|
||||
// Show dev overlay if enabled
|
||||
if (this.options.showDevOverlay) {
|
||||
this.showDevOverlay(error, enhancedContext);
|
||||
}
|
||||
|
||||
// Report to external if enabled
|
||||
if (this.options.reportToExternal) {
|
||||
@@ -200,10 +192,7 @@ export class GlobalErrorHandler {
|
||||
// Store in history
|
||||
this.addToHistory(error, enhancedContext);
|
||||
|
||||
// Show dev overlay if enabled
|
||||
if (this.options.showDevOverlay) {
|
||||
this.showDevOverlay(error, enhancedContext);
|
||||
}
|
||||
// No overlay - just enhanced console logging
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -357,137 +346,6 @@ export class GlobalErrorHandler {
|
||||
this.logger.error(error.message, error, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show development overlay with error details
|
||||
*/
|
||||
private showDevOverlay(error: Error | ApiError, context: Record<string, unknown>): void {
|
||||
// Check if overlay already exists
|
||||
const existingOverlay = document.getElementById('gridpilot-error-overlay');
|
||||
if (existingOverlay) {
|
||||
// Update existing overlay
|
||||
this.updateDevOverlay(existingOverlay, error, context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'gridpilot-error-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
color: #fff;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
z-index: 999999;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
border: 4px solid #ff4444;
|
||||
box-shadow: 0 0 50px rgba(255, 68, 68, 0.5);
|
||||
`;
|
||||
|
||||
this.updateDevOverlay(overlay, error, context);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Add keyboard shortcut to dismiss
|
||||
const dismissHandler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' || e.key === 'Enter') {
|
||||
overlay.remove();
|
||||
document.removeEventListener('keydown', dismissHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', dismissHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing dev overlay
|
||||
*/
|
||||
private updateDevOverlay(overlay: HTMLElement, error: Error | ApiError, context: Record<string, unknown>): void {
|
||||
const isApiError = error instanceof ApiError;
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
|
||||
<div>
|
||||
<h1 style="color: #ff4444; margin: 0 0 10px 0; font-size: 24px;">
|
||||
🚨 UNCAUGHT ERROR - DEVELOPMENT MODE
|
||||
</h1>
|
||||
<div style="color: #888;">${timestamp} | Press ESC or ENTER to dismiss</div>
|
||||
</div>
|
||||
<button onclick="this.parentElement.parentElement.remove()"
|
||||
style="background: #ff4444; color: white; border: none; padding: 8px 16px; cursor: pointer; border-radius: 4px; font-weight: bold;">
|
||||
CLOSE
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
||||
<h3 style="color: #ffaa00; margin-top: 0;">Error Information</h3>
|
||||
<div style="line-height: 1.6;">
|
||||
<div><strong>Type:</strong> <span style="color: #ff4444;">${isApiError ? error.type : error.name}</span></div>
|
||||
<div><strong>Message:</strong> ${error.message}</div>
|
||||
${isApiError ? `<div><strong>Severity:</strong> ${error.getSeverity()}</div>` : ''}
|
||||
${isApiError ? `<div><strong>Retryable:</strong> ${error.isRetryable()}</div>` : ''}
|
||||
${isApiError ? `<div><strong>Connectivity:</strong> ${error.isConnectivityIssue()}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
||||
<h3 style="color: #00aaff; margin-top: 0;">Environment</h3>
|
||||
<div style="line-height: 1.6;">
|
||||
<div><strong>Mode:</strong> ${process.env.NODE_ENV}</div>
|
||||
<div><strong>App Mode:</strong> ${process.env.NEXT_PUBLIC_GRIDPILOT_MODE || 'pre-launch'}</div>
|
||||
<div><strong>URL:</strong> ${window.location.href}</div>
|
||||
<div><strong>User Agent:</strong> ${navigator.userAgent}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${isApiError && error.context ? `
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
||||
<h3 style="color: #00ff88; margin-top: 0;">API Context</h3>
|
||||
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0;">${JSON.stringify(error.context, null, 2)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
||||
<h3 style="color: #ff4444; margin-top: 0;">Stack Trace</h3>
|
||||
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0; white-space: pre-wrap;">${context.enhancedStack || error.stack || 'No stack trace available'}</pre>
|
||||
</div>
|
||||
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
||||
<h3 style="color: #ffaa00; margin-top: 0;">Additional Context</h3>
|
||||
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0;">${JSON.stringify(context, null, 2)}</pre>
|
||||
</div>
|
||||
|
||||
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
||||
<h3 style="color: #00aaff; margin-top: 0;">Quick Actions</h3>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button onclick="navigator.clipboard.writeText(\`${error.message}\n\nStack:\n${error.stack}\n\nContext:\n${JSON.stringify(context, null, 2)}\`)"
|
||||
style="background: #0066cc; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
||||
📋 Copy Error Details
|
||||
</button>
|
||||
<button onclick="window.location.reload()"
|
||||
style="background: #cc6600; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
||||
🔄 Reload Page
|
||||
</button>
|
||||
<button onclick="console.clear(); console.log('Error details:', ${JSON.stringify({ error: error.message, stack: error.stack, context }).replace(/"/g, '"')})"
|
||||
style="background: #6600cc; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
||||
📝 Log to Console
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: #222; border-radius: 4px; border-left: 4px solid #ffaa00;">
|
||||
<strong>💡 Tip:</strong> This overlay only appears in development mode. In production, errors are logged silently and handled gracefully.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report error to external services
|
||||
@@ -581,10 +439,8 @@ export class GlobalErrorHandler {
|
||||
this.logErrorWithMaximumDetail(error, context);
|
||||
this.addToHistory(error, context);
|
||||
|
||||
// Auto-capture for replay in development
|
||||
if (this.options.showDevOverlay) {
|
||||
this.showDevOverlay(error, context);
|
||||
|
||||
// Auto-capture for replay
|
||||
const replaySystem = getGlobalReplaySystem();
|
||||
replaySystem.autoCapture(error, context);
|
||||
}
|
||||
@@ -606,12 +462,6 @@ export class GlobalErrorHandler {
|
||||
console.error = (console as any)._originalError;
|
||||
}
|
||||
|
||||
// Remove overlay if exists
|
||||
const overlay = document.getElementById('gridpilot-error-overlay');
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
|
||||
this.isInitialized = false;
|
||||
|
||||
if (this.options.verboseLogging) {
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { ErrorReporter } from '../../interfaces/ErrorReporter';
|
||||
import { getGlobalErrorHandler } from '../GlobalErrorHandler';
|
||||
|
||||
export class ConsoleErrorReporter implements ErrorReporter {
|
||||
report(error: Error, context?: unknown): void {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.error(`[${timestamp}] Error reported:`, error.message, { error, context });
|
||||
|
||||
// Use enhanced global handler if available
|
||||
try {
|
||||
const globalHandler = getGlobalErrorHandler();
|
||||
const enhancedContext: Record<string, unknown> = {
|
||||
source: 'legacy_reporter',
|
||||
timestamp,
|
||||
};
|
||||
if (typeof context === 'object' && context !== null) {
|
||||
Object.assign(enhancedContext, context);
|
||||
}
|
||||
globalHandler.report(error, enhancedContext);
|
||||
} catch {
|
||||
// Fallback to basic logging if global handler not available
|
||||
console.error(`[${timestamp}] Error reported:`, error.message, { error, context });
|
||||
|
||||
// Also log to console with enhanced format
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.groupCollapsed(`%c[LEGACY ERROR] ${error.name}: ${error.message}`, 'color: #ff6600; font-weight: bold;');
|
||||
console.log('Error:', error);
|
||||
console.log('Context:', context);
|
||||
console.log('Stack:', error.stack);
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,15 @@ export class ConsoleLogger implements Logger {
|
||||
}
|
||||
|
||||
debug(message: string, context?: unknown): void {
|
||||
console.debug(this.formatMessage('debug', message, context));
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.debug(this.formatMessage('debug', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
info(message: string, context?: unknown): void {
|
||||
console.info(this.formatMessage('info', message, context));
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.info(this.formatMessage('info', message, context));
|
||||
}
|
||||
}
|
||||
|
||||
warn(message: string, context?: unknown): void {
|
||||
@@ -22,5 +26,14 @@ export class ConsoleLogger implements Logger {
|
||||
error(message: string, error?: Error, context?: unknown): void {
|
||||
const errorStr = error ? ` | Error: ${error.message}` : '';
|
||||
console.error(this.formatMessage('error', message, context) + errorStr);
|
||||
|
||||
// In development, also show enhanced error info
|
||||
if (process.env.NODE_ENV === 'development' && error) {
|
||||
console.groupCollapsed(`%c[ERROR DETAIL] ${message}`, 'color: #ff4444; font-weight: bold;');
|
||||
console.log('Error Object:', error);
|
||||
console.log('Stack Trace:', error.stack);
|
||||
console.log('Context:', context);
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,17 @@ export function isAlpha(): boolean {
|
||||
*/
|
||||
export function getPublicRoutes(): readonly string[] {
|
||||
return [
|
||||
// Core public pages
|
||||
'/',
|
||||
|
||||
// Public content routes (leagues, drivers, teams, leaderboards, races)
|
||||
'/leagues',
|
||||
'/drivers',
|
||||
'/teams',
|
||||
'/leaderboards',
|
||||
'/races',
|
||||
|
||||
// Auth routes
|
||||
'/api/signup',
|
||||
'/api/auth/signup',
|
||||
'/api/auth/login',
|
||||
@@ -85,8 +95,27 @@ export function getPublicRoutes(): readonly string[] {
|
||||
|
||||
/**
|
||||
* Check if a route is public (accessible in all modes)
|
||||
* Supports both exact matches and prefix matches for nested routes
|
||||
*/
|
||||
export function isPublicRoute(pathname: string): boolean {
|
||||
const publicRoutes = getPublicRoutes();
|
||||
return publicRoutes.includes(pathname);
|
||||
|
||||
// Check exact match first
|
||||
if (publicRoutes.includes(pathname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check prefix matches for nested routes
|
||||
// e.g., '/leagues' should match '/leagues/123', '/leagues/create', etc.
|
||||
const publicPrefixes = [
|
||||
'/leagues',
|
||||
'/drivers',
|
||||
'/teams',
|
||||
'/leaderboards',
|
||||
'/races',
|
||||
];
|
||||
|
||||
return publicPrefixes.some(prefix =>
|
||||
pathname === prefix || pathname.startsWith(prefix + '/')
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { ProtestsApiClient } from '../api/protests/ProtestsApiClient';
|
||||
import { PenaltiesApiClient } from '../api/penalties/PenaltiesApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl';
|
||||
import { PenaltyService } from './penalties/PenaltyService';
|
||||
import { ConsoleErrorReporter } from '../infrastructure/logging/ConsoleErrorReporter';
|
||||
import { EnhancedErrorReporter } from '../infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
|
||||
import { LandingService } from './landing/LandingService';
|
||||
|
||||
@@ -55,7 +55,11 @@ import { OnboardingService } from './onboarding/OnboardingService';
|
||||
* Services now directly instantiate View Models instead of using Presenters.
|
||||
*/
|
||||
export class ServiceFactory {
|
||||
private readonly errorReporter = new ConsoleErrorReporter();
|
||||
private readonly errorReporter = new EnhancedErrorReporter(new ConsoleLogger(), {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
private readonly logger = new ConsoleLogger();
|
||||
|
||||
private readonly apiClients: {
|
||||
|
||||
Reference in New Issue
Block a user