This commit is contained in:
2026-08-11 21:09:32 +07:00
parent e63c8f61aa
commit 94c0c47fd0
44 changed files with 5215 additions and 71 deletions
+8
View File
@@ -0,0 +1,8 @@
export function EmptyState({ title, description }: { title: string; description?: string }) {
return (
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed border-gray-300 bg-white px-6 py-12 text-center">
<p className="text-sm font-medium text-gray-700">{title}</p>
{description && <p className="mt-1 text-sm text-gray-500">{description}</p>}
</div>
);
}
+36
View File
@@ -0,0 +1,36 @@
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
}
/** Catches render-time errors in the tree and shows a fallback UI. */
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { hasError: false };
static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error('Unhandled error in component tree', error, info.componentStack);
}
render(): ReactNode {
if (this.state.hasError) {
return (
this.props.fallback ?? (
<div className="flex min-h-screen items-center justify-center px-4 text-center">
<p className="text-sm text-gray-600">Something went wrong. Please reload the page.</p>
</div>
)
);
}
return this.props.children;
}
}
+15
View File
@@ -0,0 +1,15 @@
import type { ApiError } from '@/types/api';
interface ErrorStateProps {
title?: string;
error?: ApiError | Error | null;
}
export function ErrorState({ title = 'Something went wrong', error }: ErrorStateProps) {
return (
<div className="flex flex-col items-center justify-center rounded-lg border border-red-200 bg-red-50 px-6 py-8 text-center">
<p className="text-sm font-medium text-red-700">{title}</p>
{error && <p className="mt-1 text-sm text-red-600">{error.message}</p>}
</div>
);
}
@@ -0,0 +1,9 @@
import { Spinner } from '@/shared/components/Spinner';
export function FullPageSpinner({ label = 'Loading' }: { label?: string }) {
return (
<div className="flex min-h-screen items-center justify-center">
<Spinner label={label} />
</div>
);
}
+18
View File
@@ -0,0 +1,18 @@
export function Spinner({ label = 'Loading' }: { label?: string }) {
return (
<span role="status" className="inline-flex items-center gap-2 text-sm text-gray-500">
<svg className="h-4 w-4 animate-spin text-indigo-600" viewBox="0 0 24 24" fill="none">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
{label}
</span>
);
}