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:
parent
e8c52561a2
commit
59901c5533
|
|
@ -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))
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue