1b4b497fdf
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Has been cancelled
Version and Release / version-bump (push) Has been cancelled
Version and Release / trigger-drone (push) Has been cancelled
- Remove all console.log/debug statements from production code - Add NODE_ENV checks for development-only logging - Remove test scripts (test-feedback, test-image-security, test-backup-*, test-restore) - Remove one-time fix scripts (fix-temp-photos, fix-migration-state, mark-migration-applied) - Remove sensitive files (.env.backup, ADMIN_CREDENTIALS.txt) - Update package.json to remove references to deleted scripts - Replace console statements with logger utility in backend - Secure error boundaries to not expose stack traces in production This makes the codebase production-ready with no debug output or test scripts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import React, { Component } from 'react';
|
|
import type { ReactNode } from 'react';
|
|
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
|
import { Button } from './Button';
|
|
import i18n from '../../i18n/config';
|
|
|
|
interface Props {
|
|
children: ReactNode;
|
|
fallback?: ReactNode;
|
|
}
|
|
|
|
interface State {
|
|
hasError: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
export class ErrorBoundary extends Component<Props, State> {
|
|
constructor(props: Props) {
|
|
super(props);
|
|
this.state = { hasError: false, error: null };
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Error caught by boundary:', error, errorInfo);
|
|
console.error('Component stack:', errorInfo.componentStack);
|
|
console.error('Error message:', error.message);
|
|
console.error('Error stack:', error.stack);
|
|
}
|
|
}
|
|
|
|
handleReset = () => {
|
|
this.setState({ hasError: false, error: null });
|
|
window.location.reload();
|
|
};
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
if (this.props.fallback) {
|
|
return <>{this.props.fallback}</>;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-[400px] flex items-center justify-center p-4">
|
|
<div className="text-center max-w-md">
|
|
<AlertTriangle className="w-12 h-12 text-red-500 mx-auto mb-4" />
|
|
<h2 className="text-lg font-semibold text-neutral-900 mb-2">
|
|
{i18n.t('errors.somethingWentWrong')}
|
|
</h2>
|
|
<p className="text-sm text-neutral-600 mb-6">
|
|
{this.state.error?.message || i18n.t('errors.tryAgainLater')}
|
|
</p>
|
|
<Button
|
|
onClick={this.handleReset}
|
|
leftIcon={<RefreshCw className="w-4 h-4" />}
|
|
>
|
|
{i18n.t('errors.refreshPage')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|
|
|
|
// Page-level error boundary with more prominent UI
|
|
export class PageErrorBoundary extends Component<Props, State> {
|
|
constructor(props: Props) {
|
|
super(props);
|
|
this.state = { hasError: false, error: null };
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.error('Page error:', error, errorInfo);
|
|
}
|
|
}
|
|
|
|
handleReset = () => {
|
|
this.setState({ hasError: false, error: null });
|
|
window.location.href = '/';
|
|
};
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
return (
|
|
<div className="min-h-screen bg-neutral-50 flex items-center justify-center p-4">
|
|
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full text-center">
|
|
<AlertTriangle className="w-16 h-16 text-red-500 mx-auto mb-6" />
|
|
<h1 className="text-2xl font-bold text-neutral-900 mb-4">
|
|
{i18n.t('errors.oopsSomethingWentWrong')}
|
|
</h1>
|
|
<p className="text-neutral-600 mb-8">
|
|
{i18n.t('errors.unexpectedError')}
|
|
</p>
|
|
<div className="space-y-3">
|
|
<Button
|
|
variant="primary"
|
|
onClick={this.handleReset}
|
|
leftIcon={<RefreshCw className="w-4 h-4" />}
|
|
className="w-full"
|
|
>
|
|
{i18n.t('errors.goToHomepage')}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => window.location.reload()}
|
|
className="w-full"
|
|
>
|
|
{i18n.t('gallery.tryAgain')}
|
|
</Button>
|
|
</div>
|
|
{import.meta.env.DEV && this.state.error && (
|
|
<details className="mt-8 text-left">
|
|
<summary className="text-sm text-neutral-500 cursor-pointer hover:text-neutral-700">
|
|
{i18n.t('errors.errorDetails')}
|
|
</summary>
|
|
<pre className="mt-2 text-xs bg-neutral-100 p-3 rounded overflow-auto">
|
|
{this.state.error.stack}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
} |