website refactor
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
import React, { Component, ReactNode, useState } from 'react';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { ErrorDisplay } from '@/components/shared/state/ErrorDisplay';
|
||||
import { DevErrorPanel } from './DevErrorPanel';
|
||||
import { ErrorDisplay } from '@/ui/ErrorDisplay';
|
||||
import { DevErrorPanel } from '@/ui/DevErrorPanel';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import React from 'react';
|
||||
@@ -21,32 +20,30 @@ interface AppErrorBoundaryViewProps {
|
||||
*/
|
||||
export function AppErrorBoundaryView({ title, description, children }: AppErrorBoundaryViewProps) {
|
||||
return (
|
||||
<Stack gap={6} align="center" fullWidth>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1.5rem', width: '100%' }}>
|
||||
{/* Header Icon */}
|
||||
<Stack
|
||||
p={4}
|
||||
rounded="full"
|
||||
bg="bg-warning-amber"
|
||||
bgOpacity={0.1}
|
||||
border
|
||||
borderColor="border-warning-amber"
|
||||
<div
|
||||
style={{
|
||||
padding: '1rem',
|
||||
borderRadius: '9999px',
|
||||
backgroundColor: 'rgba(255, 190, 77, 0.1)',
|
||||
border: '1px solid rgba(255, 190, 77, 0.3)'
|
||||
}}
|
||||
>
|
||||
<Icon icon={AlertTriangle} size={8} color="var(--warning-amber)" />
|
||||
</Stack>
|
||||
<Icon icon={AlertTriangle} size={8} intent="warning" />
|
||||
</div>
|
||||
|
||||
{/* Typography */}
|
||||
<Stack gap={2} align="center">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Heading level={1} weight="bold">
|
||||
<Text uppercase letterSpacing="tighter">
|
||||
{title}
|
||||
</Text>
|
||||
{title}
|
||||
</Heading>
|
||||
<Text color="text-gray-400" align="center" maxWidth="md" leading="relaxed">
|
||||
<Text variant="low" align="center" style={{ maxWidth: '32rem' }} leading="relaxed">
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,382 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Grid } from '@/ui/primitives/Grid';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Activity, AlertTriangle, Copy, RefreshCw, Terminal, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface DevErrorPanelProps {
|
||||
error: ApiError;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Developer-focused error panel with detailed debugging information
|
||||
*/
|
||||
export function DevErrorPanel({ error, onReset }: DevErrorPanelProps) {
|
||||
const [connectionStatus, setConnectionStatus] = useState(connectionMonitor.getHealth());
|
||||
const [circuitBreakers, setCircuitBreakers] = useState(CircuitBreakerRegistry.getInstance().getStatus());
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Update status on mount
|
||||
const health = connectionMonitor.getHealth();
|
||||
setConnectionStatus(health);
|
||||
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
|
||||
|
||||
// Listen for status changes
|
||||
const handleStatusChange = () => {
|
||||
setConnectionStatus(connectionMonitor.getHealth());
|
||||
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
|
||||
};
|
||||
|
||||
connectionMonitor.on('success', handleStatusChange);
|
||||
connectionMonitor.on('failure', handleStatusChange);
|
||||
connectionMonitor.on('connected', handleStatusChange);
|
||||
connectionMonitor.on('disconnected', handleStatusChange);
|
||||
connectionMonitor.on('degraded', handleStatusChange);
|
||||
|
||||
return () => {
|
||||
connectionMonitor.off('success', handleStatusChange);
|
||||
connectionMonitor.off('failure', handleStatusChange);
|
||||
connectionMonitor.off('connected', handleStatusChange);
|
||||
connectionMonitor.off('disconnected', handleStatusChange);
|
||||
connectionMonitor.off('degraded', handleStatusChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
const debugInfo = {
|
||||
error: {
|
||||
type: error.type,
|
||||
message: error.message,
|
||||
context: error.context,
|
||||
stack: error.stack,
|
||||
},
|
||||
connection: connectionStatus,
|
||||
circuitBreakers,
|
||||
timestamp: new Date().toISOString(),
|
||||
userAgent: navigator.userAgent,
|
||||
url: window.location.href,
|
||||
};
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2));
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
// Silent failure for clipboard operations
|
||||
}
|
||||
};
|
||||
|
||||
const triggerHealthCheck = async () => {
|
||||
await connectionMonitor.performHealthCheck();
|
||||
setConnectionStatus(connectionMonitor.getHealth());
|
||||
};
|
||||
|
||||
const resetCircuitBreakers = () => {
|
||||
CircuitBreakerRegistry.getInstance().resetAll();
|
||||
setCircuitBreakers(CircuitBreakerRegistry.getInstance().getStatus());
|
||||
};
|
||||
|
||||
const getSeverityVariant = (): 'danger' | 'warning' | 'info' | 'default' => {
|
||||
switch (error.getSeverity()) {
|
||||
case 'error': return 'danger';
|
||||
case 'warn': return 'warning';
|
||||
case 'info': return 'info';
|
||||
default: return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const reliability = connectionMonitor.getReliability();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
position="fixed"
|
||||
inset="0"
|
||||
zIndex={50}
|
||||
overflow="auto"
|
||||
bg="bg-deep-graphite"
|
||||
p={4}
|
||||
>
|
||||
<Stack maxWidth="6xl" mx="auto" fullWidth>
|
||||
<Stack gap={4}>
|
||||
{/* Header */}
|
||||
<Stack bg="bg-iron-gray" border borderColor="border-charcoal-outline" rounded="lg" p={4} direction="row" align="center" justify="between">
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Icon icon={Terminal} size={5} color="rgb(59, 130, 246)" />
|
||||
<Heading level={2}>API Error Debug Panel</Heading>
|
||||
<Badge variant={getSeverityVariant()}>
|
||||
{error.type}
|
||||
</Badge>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={2}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={copyToClipboard}
|
||||
icon={<Icon icon={Copy} size={4} />}
|
||||
>
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onReset}
|
||||
icon={<Icon icon={X} size={4} />}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Error Details */}
|
||||
<Grid cols={1} lgCols={2} gap={4}>
|
||||
<Stack gap={4}>
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2} direction="row" align="center" gap={2}>
|
||||
<Icon icon={AlertTriangle} size={4} color="text-white" />
|
||||
<Text weight="semibold" color="text-white">Error Details</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
<Stack gap={2} style={{ fontSize: '0.75rem' }}>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Type:</Text>
|
||||
<Text className="col-span-2" color="text-red-400" weight="bold">{error.type}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Message:</Text>
|
||||
<Text className="col-span-2" color="text-gray-300">{error.message}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Endpoint:</Text>
|
||||
<Text className="col-span-2" color="text-primary-blue">{error.context.endpoint || 'N/A'}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Method:</Text>
|
||||
<Text className="col-span-2" color="text-warning-amber">{error.context.method || 'N/A'}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Status:</Text>
|
||||
<Text className="col-span-2">{error.context.statusCode || 'N/A'}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Retry Count:</Text>
|
||||
<Text className="col-span-2">{error.context.retryCount || 0}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Timestamp:</Text>
|
||||
<Text className="col-span-2" color="text-gray-500">{error.context.timestamp}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Retryable:</Text>
|
||||
<Text className="col-span-2" color={error.isRetryable() ? 'text-performance-green' : 'text-red-400'}>
|
||||
{error.isRetryable() ? 'Yes' : 'No'}
|
||||
</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Connectivity:</Text>
|
||||
<Text className="col-span-2" color={error.isConnectivityIssue() ? 'text-red-400' : 'text-performance-green'}>
|
||||
{error.isConnectivityIssue() ? 'Yes' : 'No'}
|
||||
</Text>
|
||||
</Grid>
|
||||
{error.context.troubleshooting && (
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Troubleshoot:</Text>
|
||||
<Text className="col-span-2" color="text-warning-amber">{error.context.troubleshooting}</Text>
|
||||
</Grid>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Connection Status */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2} direction="row" align="center" gap={2}>
|
||||
<Icon icon={Activity} size={4} color="text-white" />
|
||||
<Text weight="semibold" color="text-white">Connection Health</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
<Stack gap={2} style={{ fontSize: '0.75rem' }}>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Status:</Text>
|
||||
<Text className="col-span-2" weight="bold" color={
|
||||
connectionStatus.status === 'connected' ? 'text-performance-green' :
|
||||
connectionStatus.status === 'degraded' ? 'text-warning-amber' :
|
||||
'text-red-400'
|
||||
}>
|
||||
{connectionStatus.status.toUpperCase()}
|
||||
</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Reliability:</Text>
|
||||
<Text className="col-span-2">{reliability.toFixed(2)}%</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Total Requests:</Text>
|
||||
<Text className="col-span-2">{connectionStatus.totalRequests}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Successful:</Text>
|
||||
<Text className="col-span-2" color="text-performance-green">{connectionStatus.successfulRequests}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Failed:</Text>
|
||||
<Text className="col-span-2" color="text-red-400">{connectionStatus.failedRequests}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Consecutive Failures:</Text>
|
||||
<Text className="col-span-2">{connectionStatus.consecutiveFailures}</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Avg Response:</Text>
|
||||
<Text className="col-span-2">{connectionStatus.averageResponseTime.toFixed(2)}ms</Text>
|
||||
</Grid>
|
||||
<Grid cols={3} gap={2}>
|
||||
<Text color="text-gray-500">Last Check:</Text>
|
||||
<Text className="col-span-2" color="text-gray-500">
|
||||
{connectionStatus.lastCheck?.toLocaleTimeString() || 'Never'}
|
||||
</Text>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
{/* Right Column */}
|
||||
<Stack gap={4}>
|
||||
{/* Circuit Breakers */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2} direction="row" align="center" gap={2}>
|
||||
<Text size="lg">⚡</Text>
|
||||
<Text weight="semibold" color="text-white">Circuit Breakers</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
{Object.keys(circuitBreakers).length === 0 ? (
|
||||
<Stack align="center" py={4}>
|
||||
<Text color="text-gray-500">No circuit breakers active</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={2} maxHeight="12rem" overflow="auto" style={{ fontSize: '0.75rem' }}>
|
||||
{Object.entries(circuitBreakers).map(([endpoint, status]) => (
|
||||
<Stack key={endpoint} direction="row" align="center" justify="between" p={2} bg="bg-deep-graphite" rounded="md" border borderColor="border-charcoal-outline">
|
||||
<Text color="text-primary-blue" truncate flexGrow={1}>{endpoint}</Text>
|
||||
<Stack px={2} py={1} rounded="sm" bg={
|
||||
status.state === 'CLOSED' ? 'bg-green-500/20' :
|
||||
status.state === 'OPEN' ? 'bg-red-500/20' :
|
||||
'bg-yellow-500/20'
|
||||
}>
|
||||
<Text color={
|
||||
status.state === 'CLOSED' ? 'text-performance-green' :
|
||||
status.state === 'OPEN' ? 'text-red-400' :
|
||||
'text-warning-amber'
|
||||
}>
|
||||
{status.state}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text color="text-gray-500" className="ml-2">{status.failures} failures</Text>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2}>
|
||||
<Text weight="semibold" color="text-white">Actions</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
<Stack gap={2}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={triggerHealthCheck}
|
||||
fullWidth
|
||||
icon={<Icon icon={RefreshCw} size={4} />}
|
||||
>
|
||||
Run Health Check
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={resetCircuitBreakers}
|
||||
fullWidth
|
||||
icon={<Text size="lg">🔄</Text>}
|
||||
>
|
||||
Reset Circuit Breakers
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => {
|
||||
connectionMonitor.reset();
|
||||
setConnectionStatus(connectionMonitor.getHealth());
|
||||
}}
|
||||
fullWidth
|
||||
icon={<Text size="lg">🗑️</Text>}
|
||||
>
|
||||
Reset Connection Stats
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Quick Fixes */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2}>
|
||||
<Text weight="semibold" color="text-white">Quick Fixes</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
<Stack gap={2} style={{ fontSize: '0.75rem' }}>
|
||||
<Text color="text-gray-400">Common solutions:</Text>
|
||||
<Stack as="ul" gap={1} pl={4}>
|
||||
<Stack as="li"><Text color="text-gray-300">Check API server is running</Text></Stack>
|
||||
<Stack as="li"><Text color="text-gray-300">Verify CORS configuration</Text></Stack>
|
||||
<Stack as="li"><Text color="text-gray-300">Check environment variables</Text></Stack>
|
||||
<Stack as="li"><Text color="text-gray-300">Review network connectivity</Text></Stack>
|
||||
<Stack as="li"><Text color="text-gray-300">Check API rate limits</Text></Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Raw Error */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2}>
|
||||
<Text weight="semibold" color="text-white">Raw Error</Text>
|
||||
</Stack>
|
||||
<Stack p={4}>
|
||||
<Stack as="pre" p={2} bg="bg-deep-graphite" rounded="md" overflow="auto" maxHeight="8rem" style={{ fontSize: '0.75rem' }} color="text-gray-400">
|
||||
{JSON.stringify({
|
||||
type: error.type,
|
||||
message: error.message,
|
||||
context: error.context,
|
||||
}, null, 2)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
{/* Console Output */}
|
||||
<Card p={0} rounded="lg" overflow="hidden" variant="outline" borderColor="border-charcoal-outline" className="bg-panel-gray/40">
|
||||
<Stack bg="bg-charcoal-outline" px={4} py={2} direction="row" align="center" gap={2}>
|
||||
<Icon icon={Terminal} size={4} color="text-white" />
|
||||
<Text weight="semibold" color="text-white">Console Output</Text>
|
||||
</Stack>
|
||||
<Stack p={4} bg="bg-deep-graphite" style={{ fontSize: '0.75rem' }}>
|
||||
<Text color="text-gray-500" block mb={2}>{'>'} {error.getDeveloperMessage()}</Text>
|
||||
<Text color="text-gray-600" block>Check browser console for full stack trace and additional debug info.</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ChevronDown, ChevronUp, Copy, Terminal } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Accordion } from '@/ui/Accordion';
|
||||
import { Copy } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface ErrorDetailsProps {
|
||||
error: Error & { digest?: string };
|
||||
@@ -19,7 +19,6 @@ interface ErrorDetailsProps {
|
||||
* Part of the 500 route redesign.
|
||||
*/
|
||||
export function ErrorDetails({ error }: ErrorDetailsProps) {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyError = async () => {
|
||||
@@ -41,62 +40,28 @@ export function ErrorDetails({ error }: ErrorDetailsProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={4} fullWidth pt={4} borderTop borderColor="border-white">
|
||||
<Stack
|
||||
as="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
direction="row"
|
||||
align="center"
|
||||
justify="center"
|
||||
gap={2}
|
||||
color="text-gray-500"
|
||||
className="transition-all hover:text-gray-300"
|
||||
>
|
||||
<Icon icon={Terminal} size={3} />
|
||||
<Text
|
||||
size="xs"
|
||||
weight="medium"
|
||||
uppercase
|
||||
letterSpacing="widest"
|
||||
color="inherit"
|
||||
>
|
||||
{showDetails ? 'Hide Technical Logs' : 'Show Technical Logs'}
|
||||
</Text>
|
||||
{showDetails ? <Icon icon={ChevronUp} size={3} /> : <Icon icon={ChevronDown} size={3} />}
|
||||
</Stack>
|
||||
|
||||
{showDetails && (
|
||||
<Stack gap={3}>
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="md"
|
||||
p={4}
|
||||
fullWidth
|
||||
maxHeight="48"
|
||||
overflow="auto"
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/40"
|
||||
>
|
||||
<Text font="mono" size="xs" color="text-gray-500" block leading="relaxed">
|
||||
<div style={{ width: '100%', marginTop: '1.5rem', paddingTop: '1.5rem', borderTop: '1px solid var(--ui-color-border-muted)' }}>
|
||||
<Accordion title="Technical Logs">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<Card variant="outline">
|
||||
<Text font="mono" size="xs" variant="low" block leading="relaxed" style={{ maxHeight: '12rem', overflow: 'auto' }}>
|
||||
{error.stack || 'No stack trace available'}
|
||||
{error.digest && `\n\nDigest: ${error.digest}`}
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Stack direction="row" justify="end">
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={copyError}
|
||||
icon={<Icon icon={Copy} size={3} />}
|
||||
height="8"
|
||||
fontSize="10px"
|
||||
icon={<Icon icon={Copy} size={3} intent="low" />}
|
||||
>
|
||||
{copied ? 'Copied to Clipboard' : 'Copy Error Details'}
|
||||
{copied ? 'Copied!' : 'Copy Details'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ChevronDown, ChevronUp, Copy } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Accordion } from '@/ui/Accordion';
|
||||
import { Copy } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface ErrorDetailsBlockProps {
|
||||
error: Error & { digest?: string };
|
||||
@@ -19,7 +19,6 @@ interface ErrorDetailsBlockProps {
|
||||
* Follows "Precision Racing Minimal" theme.
|
||||
*/
|
||||
export function ErrorDetailsBlock({ error }: ErrorDetailsBlockProps) {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyError = async () => {
|
||||
@@ -41,61 +40,28 @@ export function ErrorDetailsBlock({ error }: ErrorDetailsBlockProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={4} fullWidth pt={4} borderTop borderColor="border-white">
|
||||
<Stack
|
||||
as="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
direction="row"
|
||||
align="center"
|
||||
justify="center"
|
||||
gap={2}
|
||||
className="transition-all"
|
||||
>
|
||||
<Text
|
||||
size="xs"
|
||||
color="text-gray-500"
|
||||
className="hover:text-gray-300 flex items-center gap-2"
|
||||
uppercase
|
||||
letterSpacing="widest"
|
||||
weight="medium"
|
||||
>
|
||||
{showDetails ? <Icon icon={ChevronUp} size={3} /> : <Icon icon={ChevronDown} size={3} />}
|
||||
{showDetails ? 'Hide Technical Logs' : 'Show Technical Logs'}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
{showDetails && (
|
||||
<Stack gap={3}>
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="md"
|
||||
p={4}
|
||||
fullWidth
|
||||
maxHeight="48"
|
||||
overflow="auto"
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/40"
|
||||
>
|
||||
<Text font="mono" size="xs" color="text-gray-500" block leading="relaxed">
|
||||
<div style={{ width: '100%', marginTop: '1.5rem', paddingTop: '1.5rem', borderTop: '1px solid var(--ui-color-border-muted)' }}>
|
||||
<Accordion title="Technical Logs">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<Card variant="outline">
|
||||
<Text font="mono" size="xs" variant="low" block leading="relaxed" style={{ maxHeight: '12rem', overflow: 'auto' }}>
|
||||
{error.stack || 'No stack trace available'}
|
||||
{error.digest && `\n\nDigest: ${error.digest}`}
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Stack direction="row" justify="end">
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={copyError}
|
||||
icon={<Icon icon={Copy} size={3} />}
|
||||
height="8"
|
||||
fontSize="10px"
|
||||
icon={<Icon icon={Copy} size={3} intent="low" />}
|
||||
>
|
||||
{copied ? 'Copied to Clipboard' : 'Copy Error Details'}
|
||||
{copied ? 'Copied!' : 'Copy Details'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Home, RefreshCw } from 'lucide-react';
|
||||
import { ErrorActionButtons } from '@/ui/ErrorActionButtons';
|
||||
import React from 'react';
|
||||
|
||||
interface ErrorRecoveryActionsProps {
|
||||
onRetry: () => void;
|
||||
@@ -18,30 +16,9 @@ interface ErrorRecoveryActionsProps {
|
||||
*/
|
||||
export function ErrorRecoveryActions({ onRetry, onHome }: ErrorRecoveryActionsProps) {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
wrap
|
||||
align="center"
|
||||
justify="center"
|
||||
gap={3}
|
||||
fullWidth
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onRetry}
|
||||
icon={<Icon icon={RefreshCw} size={4} />}
|
||||
width="160px"
|
||||
>
|
||||
Retry Session
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onHome}
|
||||
icon={<Icon icon={Home} size={4} />}
|
||||
width="160px"
|
||||
>
|
||||
Return to Pits
|
||||
</Button>
|
||||
</Stack>
|
||||
<ErrorActionButtons
|
||||
onRetry={onRetry}
|
||||
onGoHome={onHome}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { AppErrorBoundaryView } from './AppErrorBoundaryView';
|
||||
import { ErrorDetailsBlock } from './ErrorDetailsBlock';
|
||||
import { ErrorRecoveryActions } from './ErrorRecoveryActions';
|
||||
import React from 'react';
|
||||
|
||||
interface ErrorScreenProps {
|
||||
error: Error & { digest?: string };
|
||||
@@ -22,54 +23,27 @@ interface ErrorScreenProps {
|
||||
*/
|
||||
export function ErrorScreen({ error, reset, onHome }: ErrorScreenProps) {
|
||||
return (
|
||||
<Stack
|
||||
as="main"
|
||||
minHeight="screen"
|
||||
fullWidth
|
||||
align="center"
|
||||
justify="center"
|
||||
bg="bg-deep-graphite"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
px={6}
|
||||
>
|
||||
<ErrorPageContainer size="lg" variant="glass">
|
||||
{/* Background Accents */}
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.05} />
|
||||
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="lg"
|
||||
p={8}
|
||||
maxWidth="2xl"
|
||||
fullWidth
|
||||
position="relative"
|
||||
zIndex={10}
|
||||
borderColor="border-white"
|
||||
className="bg-white/5 backdrop-blur-md"
|
||||
<AppErrorBoundaryView
|
||||
title="System Malfunction"
|
||||
description="The application encountered an unexpected state. Our telemetry has logged the incident."
|
||||
>
|
||||
<AppErrorBoundaryView
|
||||
title="System Malfunction"
|
||||
description="The application encountered an unexpected state. Our telemetry has logged the incident."
|
||||
>
|
||||
{/* Error Message Summary */}
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="md"
|
||||
p={4}
|
||||
fullWidth
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/20"
|
||||
>
|
||||
<Text font="mono" size="sm" color="text-warning-amber" block>
|
||||
{/* Error Message Summary */}
|
||||
<div style={{ width: '100%', marginBottom: '1.5rem' }}>
|
||||
<Card variant="outline">
|
||||
<Text font="mono" size="sm" variant="warning" block>
|
||||
{error.message || 'Unknown execution error'}
|
||||
</Text>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<ErrorRecoveryActions onRetry={reset} onHome={onHome} />
|
||||
|
||||
<ErrorDetailsBlock error={error} />
|
||||
</AppErrorBoundaryView>
|
||||
</Card>
|
||||
</Stack>
|
||||
<ErrorRecoveryActions onRetry={reset} onHome={onHome} />
|
||||
|
||||
<ErrorDetailsBlock error={error} />
|
||||
</AppErrorBoundaryView>
|
||||
</ErrorPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import { Card } from '@/ui/Card';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { AlertTriangle, Home, RefreshCw, Terminal } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface GlobalErrorScreenProps {
|
||||
error: Error & { digest?: string };
|
||||
@@ -23,88 +24,44 @@ interface GlobalErrorScreenProps {
|
||||
*/
|
||||
export function GlobalErrorScreen({ error, reset, onHome }: GlobalErrorScreenProps) {
|
||||
return (
|
||||
<Stack
|
||||
as="main"
|
||||
minHeight="screen"
|
||||
fullWidth
|
||||
align="center"
|
||||
justify="center"
|
||||
bg="bg-base-black"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
px={6}
|
||||
>
|
||||
<ErrorPageContainer size="lg" variant="glass">
|
||||
{/* Background Accents - Subtle telemetry vibe */}
|
||||
<Glow color="primary" size="xl" position="center" opacity={0.03} />
|
||||
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="none"
|
||||
p={0}
|
||||
maxWidth="2xl"
|
||||
fullWidth
|
||||
position="relative"
|
||||
zIndex={10}
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/10"
|
||||
>
|
||||
{/* System Status Header */}
|
||||
<Stack
|
||||
borderBottom
|
||||
borderColor="border-white"
|
||||
px={6}
|
||||
py={4}
|
||||
direction="row"
|
||||
align="center"
|
||||
justify="between"
|
||||
className="bg-white/5"
|
||||
>
|
||||
<Stack direction="row" gap={3} align="center">
|
||||
<Icon icon={AlertTriangle} size={5} color="var(--warning-amber)" />
|
||||
<Heading level={2} weight="bold">
|
||||
<Text uppercase letterSpacing="widest" size="sm">
|
||||
System Fault Detected
|
||||
</Text>
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Text font="mono" size="xs" color="text-gray-500" uppercase>
|
||||
Status: Critical
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1.5rem', paddingBottom: '1rem', borderBottom: '1px solid var(--ui-color-border-default)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
<Icon icon={AlertTriangle} size={5} intent="warning" />
|
||||
<Heading level={2} weight="bold">
|
||||
System Fault Detected
|
||||
</Heading>
|
||||
</div>
|
||||
<Text font="mono" size="xs" variant="low" uppercase>
|
||||
Status: Critical
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||
{/* Fault Description */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||
<Text variant="med" size="base" leading="relaxed">
|
||||
The application kernel encountered an unrecoverable execution error.
|
||||
Telemetry has been captured for diagnostic review.
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack p={8}>
|
||||
<Stack gap={8}>
|
||||
{/* Fault Description */}
|
||||
<Stack gap={4}>
|
||||
<Text color="text-gray-400" size="base" leading="relaxed">
|
||||
The application kernel encountered an unrecoverable execution error.
|
||||
Telemetry has been captured for diagnostic review.
|
||||
</Text>
|
||||
<SystemStatusPanel error={error} />
|
||||
</div>
|
||||
|
||||
<SystemStatusPanel error={error} />
|
||||
</Stack>
|
||||
{/* Recovery Actions */}
|
||||
<RecoveryActions onRetry={reset} onHome={onHome} />
|
||||
</div>
|
||||
|
||||
{/* Recovery Actions */}
|
||||
<RecoveryActions onRetry={reset} onHome={onHome} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Footer / Metadata */}
|
||||
<Stack
|
||||
borderTop
|
||||
borderColor="border-white"
|
||||
px={6}
|
||||
py={3}
|
||||
direction="row"
|
||||
justify="end"
|
||||
className="bg-white/5"
|
||||
>
|
||||
<Text font="mono" size="xs" color="text-gray-600">
|
||||
GP-CORE-ERR-{error.digest?.substring(0, 8).toUpperCase() || 'UNKNOWN'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
{/* Footer / Metadata */}
|
||||
<div style={{ marginTop: '2rem', paddingTop: '1rem', borderTop: '1px solid var(--ui-color-border-default)', textAlign: 'right' }}>
|
||||
<Text font="mono" size="xs" variant="low">
|
||||
GP-CORE-ERR-{error.digest?.substring(0, 8).toUpperCase() || 'UNKNOWN'}
|
||||
</Text>
|
||||
</div>
|
||||
</ErrorPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,30 +72,23 @@ export function GlobalErrorScreen({ error, reset, onHome }: GlobalErrorScreenPro
|
||||
*/
|
||||
function SystemStatusPanel({ error }: { error: Error & { digest?: string } }) {
|
||||
return (
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="none"
|
||||
p={4}
|
||||
fullWidth
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/20"
|
||||
>
|
||||
<Stack gap={3}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Terminal} size={3} color="var(--gray-500)" />
|
||||
<Text font="mono" size="xs" color="text-gray-500" uppercase letterSpacing="wider">
|
||||
<Card variant="outline">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Icon icon={Terminal} size={3} intent="low" />
|
||||
<Text font="mono" size="xs" variant="low" uppercase>
|
||||
Fault Log
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text font="mono" size="sm" color="text-warning-amber" block>
|
||||
</div>
|
||||
<Text font="mono" size="sm" variant="warning" block>
|
||||
{error.message || 'Unknown execution fault'}
|
||||
</Text>
|
||||
{error.digest && (
|
||||
<Text font="mono" size="xs" color="text-gray-600" block>
|
||||
<Text font="mono" size="xs" variant="low" block>
|
||||
Digest: {error.digest}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -150,19 +100,11 @@ function SystemStatusPanel({ error }: { error: Error & { digest?: string } }) {
|
||||
*/
|
||||
function RecoveryActions({ onRetry, onHome }: { onRetry: () => void; onHome: () => void }) {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
wrap
|
||||
align="center"
|
||||
gap={4}
|
||||
fullWidth
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onRetry}
|
||||
icon={<Icon icon={RefreshCw} size={4} />}
|
||||
rounded="none"
|
||||
px={8}
|
||||
>
|
||||
Reboot Session
|
||||
</Button>
|
||||
@@ -170,11 +112,9 @@ function RecoveryActions({ onRetry, onHome }: { onRetry: () => void; onHome: ()
|
||||
variant="secondary"
|
||||
onClick={onHome}
|
||||
icon={<Icon icon={Home} size={4} />}
|
||||
rounded="none"
|
||||
px={8}
|
||||
>
|
||||
Return to Pits
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { StatusDot } from '@/ui/StatusDot';
|
||||
|
||||
interface NotFoundActionsProps {
|
||||
primaryLabel: string;
|
||||
@@ -17,12 +18,11 @@ interface NotFoundActionsProps {
|
||||
*/
|
||||
export function NotFoundActions({ primaryLabel, onPrimaryClick }: NotFoundActionsProps) {
|
||||
return (
|
||||
<Stack direction="row" gap={4} align="center" justify="center">
|
||||
<Group direction="row" gap={4} align="center" justify="center">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={onPrimaryClick}
|
||||
minWidth="200px"
|
||||
>
|
||||
{primaryLabel}
|
||||
</Button>
|
||||
@@ -32,18 +32,13 @@ export function NotFoundActions({ primaryLabel, onPrimaryClick }: NotFoundAction
|
||||
size="lg"
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
<Stack direction="row" gap={2} align="center">
|
||||
<Stack
|
||||
width={2}
|
||||
height={2}
|
||||
rounded="full"
|
||||
bg="soft-steel"
|
||||
/>
|
||||
<Text size="xs" weight="bold" uppercase letterSpacing="widest" color="text-gray-400">
|
||||
<Group direction="row" gap={2} align="center">
|
||||
<StatusDot intent="telemetry" size="sm" />
|
||||
<Text size="xs" weight="bold" uppercase variant="low">
|
||||
Previous Sector
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import React from 'react';
|
||||
|
||||
interface NotFoundCallToActionProps {
|
||||
label: string;
|
||||
@@ -17,7 +17,7 @@ interface NotFoundCallToActionProps {
|
||||
*/
|
||||
export function NotFoundCallToAction({ label, onClick }: NotFoundCallToActionProps) {
|
||||
return (
|
||||
<Stack gap={4} align="center">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
@@ -25,9 +25,9 @@ export function NotFoundCallToAction({ label, onClick }: NotFoundCallToActionPro
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
<Text size="xs" color="text-gray-500" uppercase letterSpacing="widest">
|
||||
<Text size="xs" variant="low" uppercase>
|
||||
Telemetry connection lost
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
|
||||
interface NotFoundDiagnosticsProps {
|
||||
errorCode: string;
|
||||
@@ -15,35 +16,19 @@ interface NotFoundDiagnosticsProps {
|
||||
*/
|
||||
export function NotFoundDiagnostics({ errorCode }: NotFoundDiagnosticsProps) {
|
||||
return (
|
||||
<Stack gap={3} align="center">
|
||||
<Stack
|
||||
px={3}
|
||||
py={1}
|
||||
border
|
||||
borderColor="primary-accent"
|
||||
bg="primary-accent"
|
||||
bgOpacity={0.1}
|
||||
rounded="sm"
|
||||
>
|
||||
<Text
|
||||
size="xs"
|
||||
weight="bold"
|
||||
color="text-primary-accent"
|
||||
uppercase
|
||||
letterSpacing="widest"
|
||||
>
|
||||
{errorCode}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Group direction="column" gap={3} align="center">
|
||||
<Badge variant="primary" size="md">
|
||||
{errorCode}
|
||||
</Badge>
|
||||
<Text
|
||||
size="xs"
|
||||
color="text-gray-500"
|
||||
variant="low"
|
||||
uppercase
|
||||
letterSpacing="widest"
|
||||
weight="medium"
|
||||
align="center"
|
||||
>
|
||||
Telemetry connection lost // Sector data unavailable
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { NavGroup } from '@/ui/NavGroup';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Link } from '@/ui/Link';
|
||||
import React from 'react';
|
||||
|
||||
interface NotFoundHelpLinksProps {
|
||||
@@ -16,31 +17,20 @@ interface NotFoundHelpLinksProps {
|
||||
*/
|
||||
export function NotFoundHelpLinks({ links }: NotFoundHelpLinksProps) {
|
||||
return (
|
||||
<Stack direction="row" gap={6} align="center" wrap center>
|
||||
{links.map((link, index) => (
|
||||
<React.Fragment key={link.href}>
|
||||
<Stack
|
||||
as="a"
|
||||
href={link.href}
|
||||
transition
|
||||
display="inline-block"
|
||||
<NavGroup direction="horizontal" gap={6} align="center">
|
||||
{links.map((link) => (
|
||||
<Link key={link.href} href={link.href} variant="ghost" underline="none">
|
||||
<Text
|
||||
variant="low"
|
||||
hoverVariant="primary"
|
||||
weight="medium"
|
||||
size="xs"
|
||||
uppercase
|
||||
>
|
||||
<Text
|
||||
color="text-gray-400"
|
||||
hoverTextColor="primary-accent"
|
||||
weight="medium"
|
||||
size="xs"
|
||||
letterSpacing="widest"
|
||||
uppercase
|
||||
>
|
||||
{link.label}
|
||||
</Text>
|
||||
</Stack>
|
||||
{index < links.length - 1 && (
|
||||
<Stack width="1px" height="12px" bg="border-gray" opacity={0.5} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
{link.label}
|
||||
</Text>
|
||||
</Link>
|
||||
))}
|
||||
</Stack>
|
||||
</NavGroup>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { Card } from '@/ui/Card';
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { Glow } from '@/ui/Glow';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { FooterSection } from '@/ui/FooterSection';
|
||||
import { NotFoundActions } from './NotFoundActions';
|
||||
import { NotFoundDiagnostics } from './NotFoundDiagnostics';
|
||||
import { NotFoundHelpLinks } from './NotFoundHelpLinks';
|
||||
import React from 'react';
|
||||
|
||||
interface NotFoundScreenProps {
|
||||
errorCode: string;
|
||||
@@ -37,105 +38,44 @@ export function NotFoundScreen({
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
as="main"
|
||||
minHeight="screen"
|
||||
align="center"
|
||||
justify="center"
|
||||
bg="graphite-black"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
fullWidth
|
||||
>
|
||||
<ErrorPageContainer size="lg" variant="glass">
|
||||
{/* Background Glow Accent */}
|
||||
<Glow color="primary" size="xl" opacity={0.1} position="center" />
|
||||
|
||||
<Card
|
||||
variant="outline"
|
||||
p={12}
|
||||
rounded="none"
|
||||
maxWidth="2xl"
|
||||
fullWidth
|
||||
mx={6}
|
||||
position="relative"
|
||||
zIndex={10}
|
||||
className="bg-white/5 backdrop-blur-md"
|
||||
<NotFoundDiagnostics errorCode={errorCode} />
|
||||
|
||||
<Text
|
||||
as="h1"
|
||||
size="4xl"
|
||||
weight="bold"
|
||||
variant="high"
|
||||
uppercase
|
||||
block
|
||||
align="center"
|
||||
style={{ marginTop: '1rem', marginBottom: '2rem' }}
|
||||
>
|
||||
<Stack gap={12} align="center">
|
||||
{/* Header Section */}
|
||||
<Stack gap={4} align="center">
|
||||
<NotFoundDiagnostics errorCode={errorCode} />
|
||||
|
||||
<Text
|
||||
as="h1"
|
||||
size="4xl"
|
||||
weight="bold"
|
||||
color="text-white"
|
||||
letterSpacing="tighter"
|
||||
uppercase
|
||||
block
|
||||
leading="none"
|
||||
textAlign="center"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</Stack>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
{/* Visual Separator */}
|
||||
<Stack width="full" height="1px" bg="primary-accent" opacity={0.3} position="relative" align="center" justify="center">
|
||||
<Stack
|
||||
w="3"
|
||||
h="3"
|
||||
bg="primary-accent"
|
||||
>{null}</Stack>
|
||||
</Stack>
|
||||
<Text
|
||||
size="xl"
|
||||
variant="med"
|
||||
block
|
||||
weight="medium"
|
||||
align="center"
|
||||
style={{ marginBottom: '3rem' }}
|
||||
>
|
||||
{message}
|
||||
</Text>
|
||||
|
||||
{/* Message Section */}
|
||||
<Text
|
||||
size="xl"
|
||||
color="text-gray-400"
|
||||
maxWidth="lg"
|
||||
leading="relaxed"
|
||||
block
|
||||
weight="medium"
|
||||
textAlign="center"
|
||||
>
|
||||
{message}
|
||||
</Text>
|
||||
<NotFoundActions
|
||||
primaryLabel={actionLabel}
|
||||
onPrimaryClick={onActionClick}
|
||||
/>
|
||||
|
||||
{/* Actions Section */}
|
||||
<NotFoundActions
|
||||
primaryLabel={actionLabel}
|
||||
onPrimaryClick={onActionClick}
|
||||
/>
|
||||
|
||||
{/* Footer Section */}
|
||||
<Stack pt={8} width="full">
|
||||
<Stack height="1px" width="full" bg="border-gray" opacity={0.1} mb={8}>{null}</Stack>
|
||||
<NotFoundHelpLinks links={helpLinks} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Subtle Edge Details */}
|
||||
<Stack
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
h="2px"
|
||||
bg="primary-accent"
|
||||
opacity={0.1}
|
||||
>{null}</Stack>
|
||||
<Stack
|
||||
position="absolute"
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
h="2px"
|
||||
bg="primary-accent"
|
||||
opacity={0.1}
|
||||
>{null}</Stack>
|
||||
</Stack>
|
||||
<FooterSection>
|
||||
<NotFoundHelpLinks links={helpLinks} />
|
||||
</FooterSection>
|
||||
</ErrorPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Home, LifeBuoy, RefreshCw } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface RecoveryActionsProps {
|
||||
onRetry: () => void;
|
||||
@@ -18,19 +18,11 @@ interface RecoveryActionsProps {
|
||||
*/
|
||||
export function RecoveryActions({ onRetry, onHome }: RecoveryActionsProps) {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
wrap
|
||||
align="center"
|
||||
justify="center"
|
||||
gap={3}
|
||||
fullWidth
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.75rem', flexWrap: 'wrap', width: '100%' }}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onRetry}
|
||||
icon={<Icon icon={RefreshCw} size={4} />}
|
||||
width="160px"
|
||||
>
|
||||
Retry Session
|
||||
</Button>
|
||||
@@ -38,7 +30,6 @@ export function RecoveryActions({ onRetry, onHome }: RecoveryActionsProps) {
|
||||
variant="secondary"
|
||||
onClick={onHome}
|
||||
icon={<Icon icon={Home} size={4} />}
|
||||
width="160px"
|
||||
>
|
||||
Return to Pits
|
||||
</Button>
|
||||
@@ -49,10 +40,9 @@ export function RecoveryActions({ onRetry, onHome }: RecoveryActionsProps) {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
icon={<Icon icon={LifeBuoy} size={4} />}
|
||||
width="160px"
|
||||
>
|
||||
Contact Support
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface ServerErrorPanelProps {
|
||||
message?: string;
|
||||
@@ -20,57 +20,53 @@ interface ServerErrorPanelProps {
|
||||
*/
|
||||
export function ServerErrorPanel({ message, incidentId }: ServerErrorPanelProps) {
|
||||
return (
|
||||
<Stack gap={6} align="center" fullWidth>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1.5rem', width: '100%' }}>
|
||||
{/* Status Indicator */}
|
||||
<Stack
|
||||
p={4}
|
||||
rounded="full"
|
||||
bg="bg-warning-amber"
|
||||
{...({ bgOpacity: 0.1 } as any)}
|
||||
border
|
||||
borderColor="border-warning-amber"
|
||||
align="center"
|
||||
justify="center"
|
||||
<div
|
||||
style={{
|
||||
padding: '1rem',
|
||||
borderRadius: '9999px',
|
||||
backgroundColor: 'rgba(255, 190, 77, 0.1)',
|
||||
border: '1px solid rgba(255, 190, 77, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Icon icon={AlertTriangle} size={8} color="var(--warning-amber)" />
|
||||
</Stack>
|
||||
<Icon icon={AlertTriangle} size={8} intent="warning" />
|
||||
</div>
|
||||
|
||||
{/* Primary Message */}
|
||||
<Stack gap={2} align="center">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Heading level={1} weight="bold">
|
||||
CRITICAL_SYSTEM_FAILURE
|
||||
</Heading>
|
||||
<Text color="text-gray-400" textAlign="center" maxWidth="md">
|
||||
<Text variant="low" align="center" style={{ maxWidth: '32rem' }}>
|
||||
The application engine encountered an unrecoverable state.
|
||||
Telemetry has been dispatched to engineering.
|
||||
</Text>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
{/* Technical Summary */}
|
||||
<Card
|
||||
variant="outline"
|
||||
rounded="md"
|
||||
p={4}
|
||||
fullWidth
|
||||
borderColor="border-white"
|
||||
className="bg-graphite-black/20"
|
||||
>
|
||||
<Stack gap={2}>
|
||||
<Text font="mono" size="sm" color="text-warning-amber" block>
|
||||
STATUS: 500_INTERNAL_SERVER_ERROR
|
||||
</Text>
|
||||
{message && (
|
||||
<Text font="mono" size="xs" color="text-gray-400" block>
|
||||
EXCEPTION: {message}
|
||||
<div style={{ width: '100%' }}>
|
||||
<Card variant="outline">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||
<Text font="mono" size="sm" variant="warning" block>
|
||||
STATUS: 500_INTERNAL_SERVER_ERROR
|
||||
</Text>
|
||||
)}
|
||||
{incidentId && (
|
||||
<Text font="mono" size="xs" color="text-gray-500" block>
|
||||
INCIDENT_ID: {incidentId}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
{message && (
|
||||
<Text font="mono" size="xs" variant="low" block>
|
||||
EXCEPTION: {message}
|
||||
</Text>
|
||||
)}
|
||||
{incidentId && (
|
||||
<Text font="mono" size="xs" variant="low" block>
|
||||
INCIDENT_ID: {incidentId}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user