mirror of
https://github.com/vernu/textbee.git
synced 2026-07-30 17:07:46 -04:00
The Device type declared a `status` field that the schema does not have
and the API never sends, so `device.status === 'online'` was always
false. The badge took its colour from that phantom field and its text
from `enabled`, so an enabled, working device was styled exactly like a
disabled one. Both now come from `enabled`.
Devices, API keys and webhooks each rendered a bare unstyled
"Error: {error.message}" with no way to recover, putting raw transport
strings like "Request failed with status code 500" in front of users.
formatError already handles axios rejections and rate limits and was
used by none of them.
Adds components/shared/error-state.tsx as the counterpart to the
existing EmptyState, routing all three through formatError and offering
a retry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
'use client'
|
|
|
|
import type { ComponentType } from 'react'
|
|
import { AlertCircle, RefreshCw } from 'lucide-react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { formatError } from '@/lib/utils/errorHandler'
|
|
|
|
type ErrorStateProps = {
|
|
error: unknown
|
|
title?: string
|
|
onRetry?: () => void
|
|
icon?: ComponentType<{ className?: string }>
|
|
}
|
|
|
|
/**
|
|
* Shared error placeholder, the counterpart to EmptyState.
|
|
*
|
|
* Several sections rendered a bare `Error: {error.message}` with no styling
|
|
* and no way to recover, which put raw transport strings like "Request failed
|
|
* with status code 500" in front of users. formatError already knows how to
|
|
* turn an axios rejection into something readable, including rate limits, so
|
|
* this routes everything through it.
|
|
*/
|
|
export default function ErrorState({
|
|
error,
|
|
title = 'Something went wrong',
|
|
onRetry,
|
|
icon: Icon = AlertCircle,
|
|
}: ErrorStateProps) {
|
|
const { message } = formatError(error)
|
|
|
|
return (
|
|
<div
|
|
role='alert'
|
|
className='flex flex-col items-center justify-center gap-2 py-12 text-center'
|
|
>
|
|
<div className='rounded-full bg-destructive/10 p-3'>
|
|
<Icon className='h-6 w-6 text-destructive' />
|
|
</div>
|
|
<p className='text-sm font-medium text-foreground'>{title}</p>
|
|
<p className='max-w-sm text-xs text-muted-foreground'>{message}</p>
|
|
{onRetry && (
|
|
<Button variant='outline' size='sm' className='mt-2' onClick={onRetry}>
|
|
<RefreshCw className='mr-1.5 h-3.5 w-3.5' />
|
|
Try again
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|