refactor: use existing Radix toast system for errors

Replace custom ErrorToast with the existing Radix-based toast system.

**Changes:**
- Remove custom ErrorToast component (redundant)
- Update ArcadeErrorContext to use useToast() from ToastContext
- Simpler, more consistent with existing codebase
- Better accessibility (Radix UI primitives)

**Benefits:**
- No duplicate toast infrastructure
- Consistent styling with rest of app
- Already set up and working
- Radix UI accessibility built-in

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-20 07:32:12 -06:00
parent e8c52561a2
commit 59901c5533
2 changed files with 11 additions and 240 deletions

View File

@ -1,187 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { css } from '../../styled-system/css'
export interface ErrorToastProps {
message: string
details?: string
onDismiss: () => void
autoHideDuration?: number // milliseconds, default 10000 (10s)
}
/**
* Error toast notification component
* Shows prominent error messages to users with optional details
*/
export function ErrorToast({
message,
details,
onDismiss,
autoHideDuration = 10000,
}: ErrorToastProps) {
const [isVisible, setIsVisible] = useState(true)
const [showDetails, setShowDetails] = useState(false)
useEffect(() => {
if (autoHideDuration > 0) {
const timer = setTimeout(() => {
setIsVisible(false)
setTimeout(onDismiss, 300) // Wait for fade-out animation
}, autoHideDuration)
return () => clearTimeout(timer)
}
}, [autoHideDuration, onDismiss])
if (!isVisible) return null
return (
<div
data-component="error-toast"
className={css({
position: 'fixed',
bottom: '24px',
right: '24px',
maxWidth: '400px',
backgroundColor: 'red.700',
color: 'white',
padding: '16px',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
zIndex: 20000, // Above everything
transition: 'all 0.3s ease-out',
'@media (max-width: 480px)': {
left: '16px',
right: '16px',
bottom: '16px',
maxWidth: 'none',
},
})}
>
{/* Header with dismiss button */}
<div
className={css({
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'space-between',
marginBottom: '8px',
})}
>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '8px',
flex: 1,
})}
>
<span className={css({ fontSize: '20px' })} aria-label="Error">
</span>
<strong className={css({ fontSize: '16px', fontWeight: 'bold' })}>Error</strong>
</div>
<button
onClick={() => {
setIsVisible(false)
setTimeout(onDismiss, 300)
}}
data-action="dismiss-error"
className={css({
background: 'transparent',
border: 'none',
color: 'white',
fontSize: '20px',
cursor: 'pointer',
padding: '0',
lineHeight: '1',
opacity: 0.7,
transition: 'opacity 0.2s',
_hover: {
opacity: 1,
},
})}
aria-label="Dismiss error"
>
×
</button>
</div>
{/* Error message */}
<div className={css({ fontSize: '14px', lineHeight: '1.5', marginBottom: details ? '8px' : '0' })}>
{message}
</div>
{/* Optional details */}
{details && (
<div>
<button
onClick={() => setShowDetails(!showDetails)}
data-action="toggle-error-details"
className={css({
background: 'transparent',
border: 'none',
color: 'white',
fontSize: '12px',
cursor: 'pointer',
padding: '4px 0',
textDecoration: 'underline',
opacity: 0.8,
_hover: {
opacity: 1,
},
})}
>
{showDetails ? 'Hide' : 'Show'} technical details
</button>
{showDetails && (
<pre
className={css({
marginTop: '8px',
padding: '8px',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
borderRadius: '4px',
fontSize: '11px',
overflow: 'auto',
maxHeight: '200px',
fontFamily: 'monospace',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
})}
>
{details}
</pre>
)}
</div>
)}
</div>
)
}
/**
* Error toast container that manages multiple toasts
*/
export function ErrorToastContainer({ errors }: { errors: Array<{ id: string; message: string; details?: string }> }) {
const [visibleErrors, setVisibleErrors] = useState(errors)
useEffect(() => {
setVisibleErrors(errors)
}, [errors])
return (
<>
{visibleErrors.map((error, index) => (
<ErrorToast
key={error.id}
message={error.message}
details={error.details}
onDismiss={() => {
setVisibleErrors((prev) => prev.filter((e) => e.id !== error.id))
}}
/>
))}
</>
)
}

View File

@ -1,72 +1,30 @@
'use client'
import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react'
import { ErrorToast } from '@/components/ErrorToast'
interface ArcadeError {
id: string
message: string
details?: string
timestamp: number
}
import React, { createContext, useContext, useCallback, type ReactNode } from 'react'
import { useToast } from '@/components/common/ToastContext'
interface ArcadeErrorContextValue {
errors: ArcadeError[]
addError: (message: string, details?: string) => void
clearError: (id: string) => void
clearAllErrors: () => void
}
const ArcadeErrorContext = createContext<ArcadeErrorContextValue | null>(null)
/**
* Provider for arcade error management
* Manages error toast notifications across arcade games
* Uses the existing Radix-based toast system for error notifications
*/
export function ArcadeErrorProvider({ children }: { children: ReactNode }) {
const [errors, setErrors] = useState<ArcadeError[]>([])
const { showError } = useToast()
const addError = useCallback((message: string, details?: string) => {
const error: ArcadeError = {
id: `error-${Date.now()}-${Math.random()}`,
message,
details,
timestamp: Date.now(),
}
setErrors((prev) => [...prev, error])
// Auto-remove after 15 seconds as fallback
setTimeout(() => {
setErrors((prev) => prev.filter((e) => e.id !== error.id))
}, 15000)
}, [])
const clearError = useCallback((id: string) => {
setErrors((prev) => prev.filter((e) => e.id !== id))
}, [])
const clearAllErrors = useCallback(() => {
setErrors([])
}, [])
const addError = useCallback(
(message: string, details?: string) => {
showError(message, details)
},
[showError]
)
return (
<ArcadeErrorContext.Provider value={{ errors, addError, clearError, clearAllErrors }}>
{children}
{/* Render error toasts */}
<div data-component="arcade-error-toasts">
{errors.map((error) => (
<ErrorToast
key={error.id}
message={error.message}
details={error.details}
onDismiss={() => clearError(error.id)}
autoHideDuration={10000}
/>
))}
</div>
</ArcadeErrorContext.Provider>
<ArcadeErrorContext.Provider value={{ addError }}>{children}</ArcadeErrorContext.Provider>
)
}