WellNuo/admin/components/ui/ErrorMessage.tsx
Sergei 4b60a92777 Add basic UI components for web application
- Set up Tailwind CSS configuration for styling
- Create Button component with variants (primary, secondary, outline, ghost, danger)
- Create Input component with label, error, and helper text support
- Create Card component with composable subcomponents (Header, Title, Description, Content, Footer)
- Create LoadingSpinner component with size and fullscreen options
- Create ErrorMessage component with retry and dismiss actions
- Add comprehensive test suite using Jest and React Testing Library
- Configure ESLint and Jest for quality assurance

All components follow consistent design patterns from mobile app
and include proper TypeScript types and accessibility features.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 18:11:31 -08:00

127 lines
4.4 KiB
TypeScript

import React from 'react';
interface ErrorMessageProps {
message: string;
onRetry?: () => void;
onDismiss?: () => void;
className?: string;
}
export function ErrorMessage({
message,
onRetry,
onDismiss,
className = '',
}: ErrorMessageProps) {
return (
<div
className={`bg-red-50 border border-red-200 rounded-lg p-4 flex items-start justify-between ${className}`}
role="alert"
>
<div className="flex items-start gap-3 flex-1">
<svg
className="h-5 w-5 text-error flex-shrink-0 mt-0.5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
<p className="text-sm text-error flex-1">{message}</p>
</div>
<div className="flex items-center gap-2 ml-3">
{onRetry && (
<button
onClick={onRetry}
className="inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-primary hover:bg-red-100 rounded transition-colors"
type="button"
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
Retry
</button>
)}
{onDismiss && (
<button
onClick={onDismiss}
className="p-1 text-textMuted hover:text-textPrimary hover:bg-red-100 rounded transition-colors"
type="button"
aria-label="Dismiss"
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
)}
</div>
</div>
);
}
interface FullScreenErrorProps {
title?: string;
message: string;
onRetry?: () => void;
}
export function FullScreenError({
title = 'Something went wrong',
message,
onRetry,
}: FullScreenErrorProps) {
return (
<div className="flex items-center justify-center min-h-screen bg-background p-6">
<div className="text-center max-w-md">
<svg
className="h-16 w-16 text-textMuted mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<h1 className="text-2xl font-semibold text-textPrimary mb-2">
{title}
</h1>
<p className="text-base text-textSecondary mb-6">{message}</p>
{onRetry && (
<button
onClick={onRetry}
className="inline-flex items-center gap-2 px-6 py-3 bg-primary text-white font-medium rounded-lg hover:bg-blue-600 transition-colors"
type="button"
>
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
Try Again
</button>
)}
</div>
</div>
);
}