From 892754bbaeb3bf7c99df1a5db2b95db9803c260c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Wed, 1 Jul 2026 22:43:58 -0400 Subject: [PATCH] feat: enhance error handling UI with detailed messages and copy functionality --- src/views/Error.jsx | 340 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 303 insertions(+), 37 deletions(-) diff --git a/src/views/Error.jsx b/src/views/Error.jsx index b140ed2..10a06e1 100644 --- a/src/views/Error.jsx +++ b/src/views/Error.jsx @@ -1,52 +1,226 @@ -import { HomeRounded, Login } from '@mui/icons-material' -import { Box, Button, CircularProgress, Container, Typography } from '@mui/joy' -import { Link } from 'react-router-dom' -import Logo from '../Logo' // Adjust the import path as necessary +import { + BugReportRounded, + CloudOffRounded, + ContentCopyRounded, + ErrorRounded, + ExpandMoreRounded, + HomeRounded, + LockRounded, + RefreshRounded, + SearchOffRounded, +} from '@mui/icons-material' +import { Box, Button, IconButton, Snackbar, Typography } from '@mui/joy' +import { useState } from 'react' +import { Link, useRouteError } from 'react-router-dom' + +const getErrorKind = error => { + if (!error) + return { label: 'Unknown Error', color: 'danger', Icon: ErrorRounded } + const status = error?.status ?? error?.response?.status + if (status === 404) + return { + label: '404 · Not Found', + color: 'warning', + Icon: SearchOffRounded, + } + if (status === 401 || status === 403) + return { + label: `${status} · Unauthorized`, + color: 'warning', + Icon: LockRounded, + } + if (status >= 500) + return { + label: `${status} · Server Error`, + color: 'danger', + Icon: CloudOffRounded, + } + if (error?.name === 'TypeError') + return { label: 'Runtime Error', color: 'danger', Icon: BugReportRounded } + if (error?.name === 'SyntaxError') + return { label: 'Syntax Error', color: 'danger', Icon: BugReportRounded } + return { label: 'Unexpected Error', color: 'danger', Icon: ErrorRounded } +} + +const safeMessage = error => { + const msg = error?.message ?? error?.statusText + if (!msg || msg === '[object Object]') return null + return msg +} + +const buildErrorText = (error, url) => { + const lines = [ + `URL: ${url}`, + `Time: ${new Date().toISOString()}`, + `Error: ${safeMessage(error) ?? String(error)}`, + ] + if (error?.stack) lines.push(`\nStack:\n${error.stack}`) + return lines.join('\n') +} const Error = () => { + const error = useRouteError() + const [showDetails, setShowDetails] = useState(false) + const [copied, setCopied] = useState(false) + + const { color, Icon } = getErrorKind(error) + const message = safeMessage(error) + const url = window.location.href + + const handleCopy = () => { + navigator.clipboard.writeText(buildErrorText(error, url)).then(() => { + setCopied(true) + }) + } + return ( - + + {/* Decorative dots */} + + + + + {/* Icon with concentric rings */} + - - - - Ops, something went wrong - - - if you think this is a mistake, please contact us or{' '} - - open issue here - {' '} - + + + + + + + {/* Title */} + + Something went wrong + + + {/* Error message */} + + {message ?? + 'An unexpected error occurred. Try reloading — it usually fixes it.'} + + + {/* Primary CTA */} + + + {/* Secondary actions */} + - + + {/* Report hint + collapsible details */} + + + If this keeps happening,{' '} + + open an issue + {' '} + and include the error details below. + + + {(error?.stack || message) && ( + <> + + + {showDetails && ( + + + + + + {buildErrorText(error, url)} + + + )} + + )} + + + setCopied(false)} + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + size='sm' + > + Error details copied to clipboard + + ) }