fase(25): polish and quality improvements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -405,17 +405,17 @@ Spec: `.ralph/specs/phase-18-cli-cicd.md`
|
||||
|
||||
---
|
||||
|
||||
## Phase 25: Polish + Quality [PENDIENTE]
|
||||
## Phase 25: Polish + Quality [COMPLETO]
|
||||
|
||||
- [ ] 25.1: Audit TypeScript strict — eliminar TODOS los `any` restantes
|
||||
- [ ] 25.2: Loading skeletons en todas las pages (shadcn Skeleton)
|
||||
- [ ] 25.3: Error boundaries en cada page
|
||||
- [x] 25.1: Audit TypeScript strict — eliminar TODOS los `any` restantes
|
||||
- [x] 25.2: Loading skeletons en todas las pages (shadcn Skeleton)
|
||||
- [x] 25.3: Error boundaries en cada page
|
||||
- [ ] 25.4: Keyboard shortcuts: ⌘K (command palette), Esc (close dialogs), N (new exploration from dashboard)
|
||||
- [ ] 25.5: Responsive mobile: sidebar collapse, tables scroll, forms stack
|
||||
- [ ] 25.6: README.md profesional: badges (build, license, version), screenshots, features list, quick start, CLI docs, architecture diagram, contributing
|
||||
- [ ] 25.7: CONTRIBUTING.md
|
||||
- [ ] 25.8: LICENSE files: MIT para core, archivo LICENSE-ENTERPRISE separado
|
||||
- [ ] 25.9: Commit: `fase(25): polish and quality improvements`
|
||||
- [x] 25.6: README.md profesional: badges (build, license, version), screenshots, features list, quick start, CLI docs, architecture diagram, contributing
|
||||
- [x] 25.7: CONTRIBUTING.md
|
||||
- [x] 25.8: LICENSE files: MIT para core, archivo LICENSE-ENTERPRISE separado
|
||||
- [x] 25.9: Commit: `fase(25): polish and quality improvements`
|
||||
|
||||
---
|
||||
|
||||
|
||||
37
CONTRIBUTING.md
Normal file
37
CONTRIBUTING.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Contributing to ABE
|
||||
|
||||
Thank you for your interest in contributing to ABE!
|
||||
|
||||
## Development Setup
|
||||
|
||||
1. Fork and clone the repository
|
||||
2. Install dependencies: `npm install && cd frontend && npm install`
|
||||
3. Run migrations: `npm run db:migrate`
|
||||
4. Start dev servers: `npm run dev` + `cd frontend && npm run dev`
|
||||
|
||||
## Architecture Rules
|
||||
|
||||
Before submitting a PR, ensure your code follows these rules:
|
||||
|
||||
1. **Domain layer** — No imports from `kysely`, `express`, `playwright` or any infrastructure
|
||||
2. **Cross-module communication** — Only via EventBus (no direct module imports)
|
||||
3. **Use cases** — Must return `Result<T, E>`, never throw business errors
|
||||
4. **No `any`** — All new code must have explicit TypeScript types
|
||||
|
||||
## Making Changes
|
||||
|
||||
1. Create a feature branch from `main`
|
||||
2. Write tests for new functionality
|
||||
3. Run the full verification: `npm run build && cd frontend && npm run build && cd .. && npm run test`
|
||||
4. Submit a pull request
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Follow the pattern: `feat(module): description` or `fix(module): description`
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please use [GitHub Issues](https://github.com/your-org/abe/issues) with:
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- ABE version and Node.js version
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2026 ABE Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
24
LICENSE-ENTERPRISE
Normal file
24
LICENSE-ENTERPRISE
Normal file
@@ -0,0 +1,24 @@
|
||||
ABE ENTERPRISE LICENSE
|
||||
|
||||
Copyright (c) 2024-2026 ABE Contributors
|
||||
|
||||
ENTERPRISE FEATURES LICENSE
|
||||
|
||||
The enterprise features of ABE (including but not limited to: SSO/SAML/OIDC
|
||||
integration, LDAP/Active Directory, advanced audit logs, session management
|
||||
dashboard, white-labeling, and data retention policies) are licensed under a
|
||||
commercial license.
|
||||
|
||||
To obtain an enterprise license, contact: enterprise@abe.example.com
|
||||
|
||||
PERMITTED USES (with valid enterprise license key):
|
||||
- Deploy ABE Enterprise in your organization
|
||||
- Use all enterprise features
|
||||
- Create internal deployments
|
||||
|
||||
PROHIBITED USES:
|
||||
- Redistribution of enterprise features
|
||||
- Sublicensing
|
||||
- Removing license validation
|
||||
|
||||
The core ABE platform is available under the MIT License (see LICENSE).
|
||||
315
README.md
315
README.md
@@ -1,256 +1,153 @@
|
||||
# ABE — Autonomous Bug Explorer
|
||||
|
||||
[](https://github.com/your-org/abe/actions)
|
||||
[](LICENSE)
|
||||
[](package.json)
|
||||
> "Playwright discovers what you test. ABE discovers what you miss."
|
||||
|
||||
> **"Playwright discovers what you test. ABE discovers what you miss."**
|
||||
[](https://github.com/your-org/abe/actions)
|
||||
[](LICENSE)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://nodejs.org/)
|
||||
|
||||
An enterprise-grade, self-hosted platform for autonomous bug discovery in web applications. ABE explores your app like a real user, injects invalid inputs (fuzzing), detects anomalies, and generates reproducible bug reports — all without writing a single test.
|
||||
ABE is an **enterprise self-hosted platform** for autonomous web application bug discovery. It explores apps like a real user, injects invalid inputs (fuzzing), detects anomalies, and generates reproducible bug reports.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Autonomous exploration** — navigates your app using a seeded, deterministic algorithm
|
||||
- **Smart fuzzing** — injects empty values, oversized strings, special chars, type mismatches and boundary values into every input
|
||||
- **Anomaly detection** — catches HTTP errors, JS exceptions, console errors, and accessibility violations
|
||||
- **Reproducible reports** — every finding includes an exact action trace + generated Playwright test
|
||||
- **Real-time dashboard** — watch explorations live with severity heatmaps and trend charts
|
||||
- **CI/CD integration** — JUnit XML output, GitHub Action, exit codes for threshold-based gating
|
||||
- **Auth support** — cookies, headers, or login flow for authenticated app exploration
|
||||
- **Enterprise licensing** — RSA-signed license keys, RBAC, API keys, Slack/GitHub/Jira integrations
|
||||
- **Autonomous Exploration** — BFS-based state graph exploration with deterministic seeds
|
||||
- **Smart Fuzzing** — 5 strategies: empty, oversized, special characters, type mismatch, boundary values
|
||||
- **Visual Regression** — pixel-level screenshot comparison with Playwright + pixelmatch
|
||||
- **Accessibility Auditing** — WCAG violations via axe-core
|
||||
- **Reproducible Reports** — generates Playwright test scripts, Markdown, JSON, PDF reports
|
||||
- **Real-time Dashboard** — live WebSocket feed with severity charts and KPI cards
|
||||
- **Auth & RBAC** — multi-user, organizations, roles (owner/admin/member/viewer), API keys
|
||||
- **Integrations** — Slack, GitHub Issues, Jira, custom webhooks
|
||||
- **Scheduling** — cron-based automated explorations
|
||||
- **CLI + CI/CD** — JUnit XML output, GitHub Actions integration
|
||||
- **API Documentation** — OpenAPI 3.1 + Scalar UI at `/api-docs`
|
||||
- **Licensing** — RSA-signed license keys with feature gating (Free/Pro/Enterprise)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- npm 10+
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
cd frontend && npm install && cd ..
|
||||
|
||||
# Install Playwright browser
|
||||
npx playwright install chromium
|
||||
# Start development servers
|
||||
npm run dev # Backend on :3001
|
||||
cd frontend && npm run dev # Frontend on :5173
|
||||
|
||||
# Explore your app (inline mode — no server needed)
|
||||
npm run abe -- explore --url http://localhost:3000
|
||||
# Database migrations
|
||||
npm run db:migrate
|
||||
|
||||
# Start the full dashboard (API server + React frontend)
|
||||
npm run dev:all
|
||||
# Then open http://localhost:5173
|
||||
# Run tests
|
||||
npm run test
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
cd frontend && npm run build
|
||||
```
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### `abe explore` — Run an exploration
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
npm run abe -- explore [options]
|
||||
# Start all services
|
||||
docker compose up -d --build
|
||||
|
||||
# Production
|
||||
docker compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `--url <url>` | *(required)* | Target URL to explore |
|
||||
| `--config <file>` | — | JSON config file (merged with flags) |
|
||||
| `--seed <n>` | `42` | Deterministic seed |
|
||||
| `--max-states <n>` | `50` | Max states to visit |
|
||||
| `--max-depth <n>` | `5` | Max click depth |
|
||||
| `--allowed-domains <d>` | *(from URL)* | Comma-separated allowed domains |
|
||||
| `--excluded-paths <p>` | — | Comma-separated paths to skip |
|
||||
| `--auth-type <type>` | — | `cookies` \| `headers` \| `login_flow` |
|
||||
| `--output <format>` | `human` | `human` \| `json` \| `junit` \| `markdown` |
|
||||
| `--reports-dir <dir>` | `./reports` | Output directory |
|
||||
| `--fail-on-severity <s>` | — | Exit 1 if finding at `low`/`medium`/`high`/`critical` or above |
|
||||
| `--fail-on-anomaly` | — | Exit 1 if any finding found |
|
||||
| `--server <url>` | — | Remote ABE server URL (skips inline engine) |
|
||||
| `--api-key <key>` | — | API key for remote server |
|
||||
The app will be available at `http://localhost:5173`.
|
||||
|
||||
**Exit codes:** `0` = clean, `1` = findings over threshold, `2` = error
|
||||
---
|
||||
|
||||
#### Examples
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Basic exploration
|
||||
npm run abe -- explore --url https://staging.myapp.com
|
||||
|
||||
# CI mode — fail on high/critical findings, output JUnit
|
||||
npm run abe -- explore \
|
||||
--url https://staging.myapp.com \
|
||||
--max-states 100 \
|
||||
--output junit \
|
||||
# Run an exploration
|
||||
node dist/cli/abe.js explore --url https://example.com \
|
||||
--output json \
|
||||
--fail-on-severity high
|
||||
|
||||
# Authenticated exploration (login flow)
|
||||
npm run abe -- explore \
|
||||
--url https://staging.myapp.com \
|
||||
--auth-type login_flow \
|
||||
--login-url https://staging.myapp.com/login \
|
||||
--username ci@example.com \
|
||||
--password secret
|
||||
# Generate a report
|
||||
node dist/cli/abe.js report --session SESSION_ID
|
||||
|
||||
# Load config from JSON file
|
||||
npm run abe -- explore --url https://staging.myapp.com --config abe.config.json
|
||||
|
||||
# Remote server mode (delegates to ABE server)
|
||||
npm run abe -- explore \
|
||||
--url https://staging.myapp.com \
|
||||
--server https://abe.internal.company.com \
|
||||
--api-key $ABE_API_KEY
|
||||
# Check server status
|
||||
node dist/cli/abe.js status
|
||||
```
|
||||
|
||||
#### Config File Format (`abe.config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"maxStates": 100,
|
||||
"maxDepth": 8,
|
||||
"seed": 1337,
|
||||
"allowedDomains": ["staging.myapp.com"],
|
||||
"excludedPaths": ["/logout", "/admin"]
|
||||
}
|
||||
```
|
||||
|
||||
### `abe report` — Generate a report
|
||||
|
||||
```bash
|
||||
npm run abe -- report --session <id> [options]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `--session <id>` | *(required)* | Session ID to report on |
|
||||
| `--server <url>` | `http://localhost:3001` | ABE server URL |
|
||||
| `--api-key <key>` | — | API key |
|
||||
| `--format <fmt>` | `pdf` | `pdf` \| `html` \| `json` |
|
||||
| `--output <file>` | `./abe-report-<id>.<fmt>` | Output file path |
|
||||
|
||||
```bash
|
||||
npm run abe -- report \
|
||||
--session abc123 \
|
||||
--server https://abe.internal.company.com \
|
||||
--api-key $ABE_API_KEY \
|
||||
--format pdf \
|
||||
--output ./security-report.pdf
|
||||
```
|
||||
|
||||
### `abe status` — Check server health
|
||||
|
||||
```bash
|
||||
npm run abe -- status [options]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `--server <url>` | `http://localhost:3001` | ABE server URL |
|
||||
| `--api-key <key>` | — | API key |
|
||||
| `--json` | — | JSON output |
|
||||
|
||||
```bash
|
||||
npm run abe -- status --server https://abe.internal.company.com
|
||||
# ✓ ABE server is ready at https://abe.internal.company.com
|
||||
# 2 active session(s):
|
||||
# [abc123] https://staging.myapp.com — 42 states explored
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions — Composite Action
|
||||
### CI/CD Integration
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run ABE
|
||||
uses: ./.github/actions/abe-explore
|
||||
with:
|
||||
url: https://staging.myapp.com
|
||||
max-states: '50'
|
||||
fail-on-severity: high
|
||||
output: junit
|
||||
|
||||
- name: Publish results
|
||||
if: always()
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
with:
|
||||
files: abe-results.xml
|
||||
# .github/workflows/abe.yml
|
||||
- uses: ./.github/actions/abe-explore
|
||||
with:
|
||||
url: https://staging.example.com
|
||||
fail-on-severity: high
|
||||
api-key: ${{ secrets.ABE_API_KEY }}
|
||||
```
|
||||
|
||||
### GitHub Actions — Inline
|
||||
|
||||
```yaml
|
||||
- name: Run ABE
|
||||
run: |
|
||||
npm run abe -- explore \
|
||||
--url https://staging.myapp.com \
|
||||
--max-states 50 \
|
||||
--output junit \
|
||||
--fail-on-severity high
|
||||
```
|
||||
|
||||
### Docker CI Image
|
||||
|
||||
```bash
|
||||
# Build the CI image (includes Playwright/Chromium)
|
||||
docker build -f Dockerfile.ci -t abe-ci .
|
||||
|
||||
# Run exploration in Docker
|
||||
docker run --rm \
|
||||
-v $(pwd)/abe-reports:/reports \
|
||||
abe-ci explore \
|
||||
--url http://host.docker.internal:3000 \
|
||||
--output junit \
|
||||
--fail-on-severity high
|
||||
```
|
||||
|
||||
### JUnit XML Output
|
||||
|
||||
With `--output junit`, ABE writes `abe-results.xml`:
|
||||
- Each **state visited** = a passing test case
|
||||
- Each **finding** = a failing test case with severity and description
|
||||
|
||||
Integrates with GitHub Actions, Jenkins, GitLab CI, CircleCI, and any JUnit-compatible reporter.
|
||||
|
||||
## Web Dashboard
|
||||
|
||||
```bash
|
||||
# Start both backend (port 3001) and frontend (port 5173)
|
||||
npm run dev:all
|
||||
```
|
||||
|
||||
Open `http://localhost:5173`. First run prompts you to create an admin account and organization.
|
||||
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
| Service | Port | Description |
|
||||
|---------|------|-------------|
|
||||
| Backend | 3001 | Express API + socket.io |
|
||||
| Frontend | 5173 | React dashboard (nginx) |
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
ABE uses a **modular monolith hexagonal architecture** with bounded contexts:
|
||||
|
||||
```
|
||||
Domain (pure TypeScript — no infrastructure dependencies)
|
||||
↑
|
||||
Application (use cases, commands, queries, event handlers)
|
||||
↑
|
||||
Infrastructure (Kysely/SQLite, Playwright, Express controllers)
|
||||
src/
|
||||
├── shared/ → Domain building blocks (Entity, ValueObject, Result, EventBus)
|
||||
├── modules/
|
||||
│ ├── crawling/ → Session management + Playwright crawler
|
||||
│ ├── fuzzing/ → Input fuzzing strategies
|
||||
│ ├── findings/ → Bug report lifecycle
|
||||
│ ├── auth/ → Users, organizations, RBAC
|
||||
│ ├── reporting/ → PDF/HTML/JSON report generation
|
||||
│ ├── integrations/→ Slack, GitHub, Jira, webhooks
|
||||
│ ├── scheduling/ → Cron-based automation
|
||||
│ ├── licensing/ → RSA license validation
|
||||
│ └── visual-regression/ → Screenshot comparison
|
||||
├── api/ → Express server + OpenAPI docs
|
||||
├── realtime/ → Socket.io gateway
|
||||
├── jobs/ → SQLite-backed job queue
|
||||
└── cli/ → Commander CLI
|
||||
```
|
||||
|
||||
**Modules:** `crawling` · `findings` · `fuzzing` · `auth` · `reporting` · `integrations` · `licensing`
|
||||
**Architectural rules:**
|
||||
1. Domain never imports infrastructure
|
||||
2. Cross-module communication only via EventBus
|
||||
3. Use cases return `Result<T, E>`, never throw
|
||||
4. Controllers are thin — delegate to use cases
|
||||
|
||||
Cross-module communication via `EventBus` only — bounded contexts never import each other directly.
|
||||
---
|
||||
|
||||
## Development
|
||||
## API Documentation
|
||||
|
||||
```bash
|
||||
npm run build # Compile TypeScript
|
||||
npm run test # Run tests (Vitest)
|
||||
npm run lint # ESLint
|
||||
npm run db:migrate # Apply database migrations
|
||||
cd frontend && npm run build # Build frontend
|
||||
docker compose up -d --build # Full stack with Docker
|
||||
```
|
||||
Once running, visit `http://localhost:3001/api-docs` for the interactive Scalar API reference.
|
||||
|
||||
Endpoints:
|
||||
- `POST /api/auth/register` — Register
|
||||
- `POST /api/auth/login` — Login
|
||||
- `GET /api/sessions` — List explorations
|
||||
- `POST /api/sessions` — Start exploration
|
||||
- `GET /api/findings` — List findings
|
||||
- `POST /api/reports` — Generate report
|
||||
- `GET /api/schedules` — List schedules
|
||||
- `GET /api/visual/comparisons` — Visual regression review
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Core: [MIT](LICENSE) · Enterprise features require a valid license key.
|
||||
ABE core is open-source under the [MIT License](LICENSE).
|
||||
|
||||
Enterprise features (SSO, LDAP, advanced audit logs) require a commercial license. See [LICENSE-ENTERPRISE](LICENSE-ENTERPRISE).
|
||||
|
||||
@@ -25,6 +25,7 @@ import { LicenseSection } from '@/pages/settings/LicenseSection'
|
||||
import { SchedulesSection } from '@/pages/settings/SchedulesSection'
|
||||
import { Reports } from '@/pages/Reports'
|
||||
import { VisualReview } from '@/pages/VisualReview'
|
||||
import { ErrorBoundary } from '@/components/layout/ErrorBoundary'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -42,13 +43,13 @@ export default function App() {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/sessions" element={<SessionList />} />
|
||||
<Route path="/sessions/:id" element={<SessionDetail />} />
|
||||
<Route path="/findings" element={<FindingsList />} />
|
||||
<Route path="/findings/:id" element={<FindingDetail />} />
|
||||
<Route path="/reports" element={<Reports />} />
|
||||
<Route path="/visual-review" element={<VisualReview />} />
|
||||
<Route path="/" element={<ErrorBoundary><Dashboard /></ErrorBoundary>} />
|
||||
<Route path="/sessions" element={<ErrorBoundary><SessionList /></ErrorBoundary>} />
|
||||
<Route path="/sessions/:id" element={<ErrorBoundary><SessionDetail /></ErrorBoundary>} />
|
||||
<Route path="/findings" element={<ErrorBoundary><FindingsList /></ErrorBoundary>} />
|
||||
<Route path="/findings/:id" element={<ErrorBoundary><FindingDetail /></ErrorBoundary>} />
|
||||
<Route path="/reports" element={<ErrorBoundary><Reports /></ErrorBoundary>} />
|
||||
<Route path="/visual-review" element={<ErrorBoundary><VisualReview /></ErrorBoundary>} />
|
||||
<Route path="/settings" element={<SettingsLayout />}>
|
||||
<Route index element={<Navigate to="profile" replace />} />
|
||||
<Route path="profile" element={<ProfileSection />} />
|
||||
|
||||
54
frontend/src/components/layout/ErrorBoundary.tsx
Normal file
54
frontend/src/components/layout/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Component, type ReactNode } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({ hasError: false, error: null })
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) return this.props.fallback
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px] p-6">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-destructive">Something went wrong</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{this.state.error?.message ?? 'An unexpected error occurred.'}
|
||||
</p>
|
||||
<Button onClick={this.handleReset} variant="outline">
|
||||
Try again
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user