Skip to content
Draft
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
1 change: 1 addition & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## [Unreleased]

### Added
- Global `ErrorBoundary` component with dual-theme support to catch runtime errors and provide a "Try Again" or "Go Home" recovery option.
- Inline form validation in Auth page with real-time feedback and proper ARIA accessibility support (`aria-invalid`, `aria-describedby`, `role="alert"`).
- Dashboard skeleton loading state (`DashboardSkeleton`) to improve perceived performance during data fetch.
- Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users.
Expand Down
24 changes: 24 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,30 @@ _Document errors and their solutions here as you encounter them._

## Recent Implementation Reviews

### ✅ Successful PR Pattern: Error Boundary Implementation (#237)

**Date:** 2026-01-13
**Context:** Review of Error Boundary implementation

**What was implemented:**
1. Created `ErrorBoundary` class component (required for error catching)
2. Created `ErrorFallback` functional component (for hooks/theme support)
3. Integrated into `App.tsx` wrapping `AppRoutes` but inside Providers
4. Built-in dual-theme support (Neo/Glass)

**Why it succeeded:**
- ✅ Separated Logic (Class) from UI (Function) to support hooks
- ✅ Leveraged existing `useTheme` context
- ✅ Provided clear recovery options (Reload, Go Home)
- ✅ Integrated safe fallback UI

**Key learnings:**
- React Error Boundaries *must* be class components
- To use hooks (like `useTheme`) in the fallback UI, create a separate functional component
- Wrap specific routes rather than the entire app to keep Providers active (like Theme/Auth)

---

### ✅ Successful PR Pattern: Toast Notification System (#227)

**Date:** 2026-01-13
Expand Down
8 changes: 4 additions & 4 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
- Impact: Guides new users, makes app feel polished
- Size: ~70 lines

- [ ] **[ux]** Error boundary with retry for API failures
- Files: Create `web/components/ErrorBoundary.tsx`, wrap app
- [x] **[ux]** Error boundary with retry for API failures
- Completed: 2026-01-13
- Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx`
- Context: Catch errors gracefully with retry button
- Impact: App doesn't crash, users can recover
- Size: ~60 lines
- Added: 2026-01-01
- Size: ~80 lines

### Mobile

Expand Down
3 changes: 3 additions & 0 deletions web/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthProvider, useAuth } from './contexts/AuthContext';
import { ThemeProvider } from './contexts/ThemeContext';
import { ToastProvider } from './contexts/ToastContext';
import { ToastContainer } from './components/ui/Toast';
import { ErrorBoundary } from './components/ErrorBoundary';
import { Auth } from './pages/Auth';
import { Dashboard } from './pages/Dashboard';
import { Friends } from './pages/Friends';
Expand Down Expand Up @@ -51,8 +52,10 @@ const App = () => {
<ToastProvider>
<AuthProvider>
<HashRouter>
<ErrorBoundary>
<AppRoutes />
<ToastContainer />
</ErrorBoundary>
</HashRouter>
</AuthProvider>
</ToastProvider>
Expand Down
97 changes: 97 additions & 0 deletions web/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { Component, ReactNode } from 'react';
import { useTheme } from '../contexts/ThemeContext';
import { THEMES } from '../constants';
import { Button } from './ui/Button';
import { AlertTriangle, RefreshCw, Home } from 'lucide-react';

interface ErrorFallbackProps {
error: Error | null;
resetError: () => void;
}

const ErrorFallback: React.FC<ErrorFallbackProps> = ({ error, resetError }) => {
const { style, mode } = useTheme();

const isNeo = style === THEMES.NEOBRUTALISM;
const isDark = mode === 'dark';

let containerClasses = "";
if (isNeo) {
containerClasses = `border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] bg-white ${isDark ? 'bg-zinc-800 text-white' : 'text-black'}`;
} else {
containerClasses = `rounded-2xl border border-white/10 shadow-2xl backdrop-blur-xl ${isDark ? 'bg-black/60 text-white' : 'bg-white/60 text-gray-900'}`;
}

return (
<div className="min-h-screen flex items-center justify-center p-4">
<div className={`w-full max-w-md p-8 text-center ${containerClasses}`}>
<div className={`mb-6 flex justify-center`}>
<div className={`p-4 rounded-full ${isNeo ? 'border-2 border-black bg-neo-second' : 'bg-red-500/20 text-red-500'}`}>
<AlertTriangle size={48} className={isNeo ? 'text-black' : ''} />
</div>
</div>

<h2 className={`text-2xl font-bold mb-2 ${isNeo ? 'uppercase font-mono tracking-wider' : ''}`}>
Something went wrong
</h2>

<p className={`mb-6 ${isDark ? 'text-gray-300' : 'text-gray-600'}`}>
{error?.message || "An unexpected error occurred. We're sorry for the inconvenience."}
</p>

<div className="flex flex-col gap-3">
<Button onClick={resetError} className="w-full">
<RefreshCw size={18} />
Try Again
</Button>

<Button
variant="ghost"
onClick={() => window.location.href = '/'}
className="w-full"
>
<Home size={18} />
Back to Home
</Button>
</div>
</div>
</div>
);
};

interface ErrorBoundaryProps {
children: ReactNode;
}

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}

export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Uncaught error:", error, errorInfo);
}

resetError = () => {
this.setState({ hasError: false, error: null });
window.location.reload();
};

render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} resetError={this.resetError} />;
}

return this.props.children;
}
}
Loading