Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tenant-dashboard/src/app/dashboard/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client';

import RouteErrorBoundary from '@/components/error/RouteErrorBoundary';

export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return <RouteErrorBoundary error={error} reset={reset} />;
}
13 changes: 13 additions & 0 deletions tenant-dashboard/src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client';

import RouteErrorBoundary from '@/components/error/RouteErrorBoundary';

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return <RouteErrorBoundary error={error} reset={reset} />;
}
3 changes: 1 addition & 2 deletions tenant-dashboard/src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React from "react";
import { Toaster } from "react-hot-toast";
// Temporarily disabled: import { PostHogProvider } from "./posthog-provider";
import { ErrorBoundary } from "@/components/error-boundary";
import { ErrorBoundary } from "@/components/error/ErrorBoundary";
import { PWAPrompts, ConnectionStatus } from "@/components/pwa/PWAPrompts";
import { PerformanceProvider } from "@/components/performance/PerformanceProvider";
import { AuthProvider } from "@/lib/auth/auth-context";
Expand All @@ -19,7 +19,6 @@ export function Providers({ children }: ProvidersProps) {

return (
<ErrorBoundary
showDetails={process.env.NODE_ENV === "development"}
onError={(error, errorInfo) => {
// Additional error handling if needed
console.error("Root error boundary triggered:", error, errorInfo);
Expand Down
13 changes: 13 additions & 0 deletions tenant-dashboard/src/app/settings/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client';

import RouteErrorBoundary from '@/components/error/RouteErrorBoundary';

export default function SettingsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return <RouteErrorBoundary error={error} reset={reset} />;
}
36 changes: 36 additions & 0 deletions tenant-dashboard/src/components/error/AsyncErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import React, { Suspense } from 'react';
import { ErrorBoundary } from './ErrorBoundary';
import { Loader2 } from 'lucide-react';

interface AsyncErrorBoundaryProps {
children: React.ReactNode;
loadingFallback?: React.ReactNode;
errorFallback?: React.ReactNode;
componentName?: string;
}

export function AsyncErrorBoundary({
children,
loadingFallback,
errorFallback,
componentName = 'component'
}: AsyncErrorBoundaryProps) {
const defaultLoadingFallback = (
<div className="flex items-center justify-center p-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">
Loading {componentName}...
</span>
</div>
);

return (
<ErrorBoundary fallback={errorFallback}>
<Suspense fallback={loadingFallback || defaultLoadingFallback}>
{children}
</Suspense>
</ErrorBoundary>
);
}
55 changes: 55 additions & 0 deletions tenant-dashboard/src/components/error/ChunkErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import React from 'react';
import { ErrorBoundary } from './ErrorBoundary';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';

interface ChunkErrorBoundaryProps {
children: React.ReactNode;
chunkName?: string;
}

export function ChunkErrorBoundary({ children, chunkName }: ChunkErrorBoundaryProps) {
const handleChunkError = (error: Error) => {
// Check if it's a chunk loading error
if (
error.message?.includes('Loading chunk') ||
error.message?.includes('Failed to fetch') ||
error.name === 'ChunkLoadError'
) {
// Reload the page to fetch the latest chunks
if (typeof window !== 'undefined') {
window.location.reload();
}
}
};

return (
<ErrorBoundary
onError={handleChunkError}
fallback={
<div className="p-6">
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Loading Error</AlertTitle>
<AlertDescription className="mt-2">
<p>Failed to load {chunkName || 'this section'}. This might be due to a recent update.</p>
<Button
onClick={() => window.location.reload()}
size="sm"
className="mt-3"
>
<RefreshCw className="mr-2 h-4 w-4" />
Reload Page
</Button>
</AlertDescription>
</Alert>
</div>
}
>
{children}
</ErrorBoundary>
);
}
110 changes: 110 additions & 0 deletions tenant-dashboard/src/components/error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use client';

import React, { Component, ErrorInfo, ReactNode } from 'react';
import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import Link from 'next/link';

interface Props {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}

interface State {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}

export class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
error: null,
errorInfo: null,
};

public static getDerivedStateFromError(error: Error): State {
return { hasError: true, error, errorInfo: null };
}

public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
this.setState({ errorInfo });

// Call optional error handler
if (this.props.onError) {
this.props.onError(error, errorInfo);
}

// In production, you might want to log to an error reporting service
if (process.env.NODE_ENV === 'production') {
// TODO: Send error to error reporting service
// logErrorToService(error, errorInfo);
}
}

private handleReset = () => {
this.setState({ hasError: false, error: null, errorInfo: null });
};

public render() {
if (this.state.hasError) {
// Use custom fallback if provided
if (this.props.fallback) {
return <>{this.props.fallback}</>;
}

// Default error UI
return (
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
<Card className="max-w-md w-full">
<CardHeader>
<div className="flex items-center space-x-2">
<AlertTriangle className="h-5 w-5 text-destructive" />
<CardTitle>Something went wrong</CardTitle>
</div>
<CardDescription>
An unexpected error occurred. The error has been logged and we'll look into it.
</CardDescription>
</CardHeader>
{process.env.NODE_ENV === 'development' && this.state.error && (
<CardContent>
<div className="bg-muted p-3 rounded-md">
<p className="text-sm font-mono text-destructive">
{this.state.error.toString()}
</p>
{this.state.errorInfo && (
<details className="mt-2">
<summary className="cursor-pointer text-xs text-muted-foreground hover:text-foreground">
Stack trace
</summary>
<pre className="mt-2 text-xs overflow-auto">
{this.state.errorInfo.componentStack}
</pre>
</details>
)}
</div>
</CardContent>
)}
<CardFooter className="flex space-x-2">
<Button onClick={this.handleReset} variant="default">
<RefreshCw className="mr-2 h-4 w-4" />
Try again
</Button>
<Button asChild variant="outline">
<Link href="/dashboard">
<Home className="mr-2 h-4 w-4" />
Go to Dashboard
</Link>
</Button>
</CardFooter>
</Card>
</div>
);
}

return this.props.children;
}
}
61 changes: 61 additions & 0 deletions tenant-dashboard/src/components/error/RouteErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import React from 'react';
import { useRouter } from 'next/navigation';
import { AlertTriangle, ArrowLeft, Home } from 'lucide-react';
import { Button } from '@/components/ui/button';

interface RouteErrorBoundaryProps {
error: Error & { digest?: string };
reset: () => void;
}

export default function RouteErrorBoundary({ error, reset }: RouteErrorBoundaryProps) {
const router = useRouter();

React.useEffect(() => {
// Log the error to an error reporting service
console.error('Route error:', error);
}, [error]);

return (
<div className="min-h-[400px] flex items-center justify-center p-4">
<div className="text-center max-w-md">
<div className="mb-6">
<AlertTriangle className="h-12 w-12 text-destructive mx-auto" />
</div>
<h2 className="text-2xl font-semibold mb-2">Page Error</h2>
<p className="text-muted-foreground mb-6">
Sorry, this page encountered an error and couldn't load properly.
</p>

{process.env.NODE_ENV === 'development' && (
<div className="mb-6 p-3 bg-muted rounded-lg text-left">
<p className="text-sm font-mono text-destructive break-all">
{error.message || 'An unexpected error occurred'}
</p>
{error.digest && (
<p className="text-xs text-muted-foreground mt-2">
Error ID: {error.digest}
</p>
)}
</div>
)}

<div className="flex justify-center space-x-3">
<Button onClick={() => router.back()} variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
Go Back
</Button>
<Button onClick={reset}>
Try Again
</Button>
<Button onClick={() => router.push('/dashboard')} variant="outline">
<Home className="mr-2 h-4 w-4" />
Dashboard
</Button>
</div>
</div>
</div>
);
}
11 changes: 2 additions & 9 deletions tenant-dashboard/src/components/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useSettingsStore } from '@/stores/settingsStore';
import { ProfileSettings } from './ProfileSettings';
import { ApiKeyManager } from './ApiKeyManager';
import { NotificationSettings } from './NotificationSettings';
import { AppearanceSettings } from './AppearanceSettings';

interface SettingsPageProps {}

Expand Down Expand Up @@ -63,7 +64,6 @@ export function SettingsPage({}: SettingsPageProps) {
title: 'Appearance',
description: 'Customize the look and feel',
icon: Palette,
disabled: true, // Prepare for future dark mode
},
];

Expand All @@ -76,14 +76,7 @@ export function SettingsPage({}: SettingsPageProps) {
case 'notifications':
return <NotificationSettings />;
case 'appearance':
return (
<div className="flex items-center justify-center h-48 text-muted-foreground">
<div className="text-center">
<Palette className="h-12 w-12 mx-auto mb-4" />
<p>Theme customization coming soon</p>
</div>
</div>
);
return <AppearanceSettings />;
default:
return null;
}
Expand Down
Loading
Loading