ui phase 1
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
'use client';
|
||||
import { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import AppButton from './AppButton';
|
||||
import AppIcon from './AppIcon';
|
||||
|
||||
interface Props {
|
||||
export interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
/** Diagnostic-only segment name (console log label) — never shown to the user. */
|
||||
name: string;
|
||||
/** Already-translated fallback copy — kept presentational/caller-owned (like every other shared
|
||||
* primitive in this kit) so this file never imports next-intl; it sits at the top of the `common`
|
||||
* barrel, so an eager i18n dependency here would drag `next-intl` into every test that imports
|
||||
* anything from `@/components`, mocked or not. */
|
||||
title: string;
|
||||
body: string;
|
||||
retryLabel: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -13,47 +24,63 @@ interface State {
|
||||
}
|
||||
|
||||
/**
|
||||
* Error boundary wrapper to save Application parts from falling
|
||||
* Error boundary wrapping a subtree of the app (a shell's content area). Renders a branded fallback with
|
||||
* a retry affordance (re-mounts the children by clearing `hasError`); the raw error + component stack are
|
||||
* **logged, never shown**, in production — only a development build renders the dev-only `<details>`
|
||||
* block.
|
||||
* @component ErrorBoundary
|
||||
* @param {string} [props.name] - name of the wrapped segment, "Error Boundary" by default
|
||||
*/
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
name: 'Error Boundary',
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, State> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
// The next render will show the Error UI
|
||||
return { hasError: true };
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Save information to help render Error UI
|
||||
this.setState({ error, errorInfo });
|
||||
// TODO: Add log error messages to an error reporting service here
|
||||
console.error(`[ErrorBoundary:${this.props.name}]`, error, errorInfo.componentStack);
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Error UI rendering
|
||||
const { error, errorInfo } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.props.name} - Something went wrong</h2>
|
||||
<details style={{ whiteSpace: 'pre-wrap' }}>
|
||||
{this.state?.error?.toString()}
|
||||
<br />
|
||||
{this.state?.errorInfo?.componentStack}
|
||||
</details>
|
||||
</div>
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', gap: 1.5, py: 6, px: 2, textAlign: 'center' }}>
|
||||
<AppIcon icon="warning" size={36} color="var(--bal-warning)" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
|
||||
{this.props.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary', maxWidth: 360 }}>
|
||||
{this.props.body}
|
||||
</Typography>
|
||||
<AppButton color="primary" variant="outlined" onClick={this.handleRetry}>
|
||||
{this.props.retryLabel}
|
||||
</AppButton>
|
||||
{process.env.NODE_ENV !== 'production' && (error || errorInfo) ? (
|
||||
<Typography
|
||||
component="details"
|
||||
variant="caption"
|
||||
sx={{ color: 'text.secondary', maxWidth: 480, textAlign: 'start', whiteSpace: 'pre-wrap' }}
|
||||
>
|
||||
<Typography component="summary" variant="caption" sx={{ cursor: 'pointer' }}>
|
||||
Dev-only error detail
|
||||
</Typography>
|
||||
{error?.toString()}
|
||||
{'\n'}
|
||||
{errorInfo?.componentStack}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// Normal UI rendering
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user