108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { Copy, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { Box } from '@/ui/Box';
|
|
import { Surface } from '@/ui/Surface';
|
|
import { Text } from '@/ui/Text';
|
|
import { Button } from '@/ui/Button';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Stack } from '@/ui/Stack';
|
|
|
|
interface ErrorDetailsBlockProps {
|
|
error: Error & { digest?: string };
|
|
}
|
|
|
|
/**
|
|
* ErrorDetailsBlock
|
|
*
|
|
* Semantic component for technical error details.
|
|
* Follows "Precision Racing Minimal" theme.
|
|
*/
|
|
export function ErrorDetailsBlock({ error }: ErrorDetailsBlockProps) {
|
|
const [showDetails, setShowDetails] = useState(false);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const copyError = async () => {
|
|
const details = {
|
|
message: error.message,
|
|
digest: error.digest,
|
|
stack: error.stack,
|
|
url: typeof window !== 'undefined' ? window.location.href : 'unknown',
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(JSON.stringify(details, null, 2));
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch (err) {
|
|
// Silent fail
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Stack gap={4} fullWidth pt={4} borderTop borderColor="border-white" bgOpacity={0.1}>
|
|
<Box
|
|
as="button"
|
|
onClick={() => setShowDetails(!showDetails)}
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
gap={2}
|
|
transition
|
|
>
|
|
<Text
|
|
size="xs"
|
|
color="text-gray-500"
|
|
hoverTextColor="text-gray-300"
|
|
uppercase
|
|
letterSpacing="widest"
|
|
weight="medium"
|
|
display="flex"
|
|
alignItems="center"
|
|
gap={2}
|
|
>
|
|
{showDetails ? <Icon icon={ChevronUp} size={3} /> : <Icon icon={ChevronDown} size={3} />}
|
|
{showDetails ? 'Hide Technical Logs' : 'Show Technical Logs'}
|
|
</Text>
|
|
</Box>
|
|
|
|
{showDetails && (
|
|
<Stack gap={3}>
|
|
<Surface
|
|
variant="dark"
|
|
rounded="md"
|
|
padding={4}
|
|
fullWidth
|
|
maxHeight="48"
|
|
overflow="auto"
|
|
border
|
|
borderColor="border-white"
|
|
bgOpacity={0.4}
|
|
hideScrollbar={false}
|
|
>
|
|
<Text font="mono" size="xs" color="text-gray-500" block leading="relaxed">
|
|
{error.stack || 'No stack trace available'}
|
|
{error.digest && `\n\nDigest: ${error.digest}`}
|
|
</Text>
|
|
</Surface>
|
|
|
|
<Box display="flex" justifyContent="end">
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={copyError}
|
|
icon={<Icon icon={Copy} size={3} />}
|
|
height="8"
|
|
fontSize="10px"
|
|
>
|
|
{copied ? 'Copied to Clipboard' : 'Copy Error Details'}
|
|
</Button>
|
|
</Box>
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|