Add fetch timeout/retry, friendly error messages, and VAT rate constant

- fetchWithTimeout (10s) + fetchWithRetry (3 attempts, exponential backoff)
- DataError class with type classification (config/network/auth/timeout/unknown)
- User-friendly error messages in EN/AR instead of raw error strings
- Extract VAT_RATE constant (was hardcoded 1.15)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-25 18:08:24 +03:00
parent ed29e7c22c
commit 8934ba1e51
5 changed files with 103 additions and 26 deletions

View File

@@ -5,7 +5,8 @@ import Comparison from './components/Comparison';
import Slides from './components/Slides';
import { fetchData, getCacheStatus, refreshData } from './services/dataService';
import { useLanguage } from './contexts/LanguageContext';
import type { MuseumRecord, CacheStatus } from './types';
import type { MuseumRecord, CacheStatus, DataErrorType } from './types';
import { DataError } from './types';
import './App.css';
interface NavLinkProps {
@@ -35,7 +36,7 @@ function App() {
const [data, setData] = useState<MuseumRecord[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [refreshing, setRefreshing] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [error, setError] = useState<{ message: string; type: DataErrorType } | null>(null);
const [isOffline, setIsOffline] = useState<boolean>(false);
const [cacheInfo, setCacheInfo] = useState<CacheStatus | null>(null);
const [showDataLabels, setShowDataLabels] = useState<boolean>(false);
@@ -62,7 +63,8 @@ function App() {
const status = getCacheStatus();
setCacheInfo(status);
} catch (err) {
setError((err as Error).message);
const type = err instanceof DataError ? err.type : 'unknown';
setError({ message: (err as Error).message, type });
console.error(err);
} finally {
setLoading(false);
@@ -92,8 +94,10 @@ function App() {
return (
<div className="error-container" dir={dir}>
<h2>{t('app.error')}</h2>
<p style={{maxWidth: '400px', textAlign: 'center', color: '#64748b'}}>{error}</p>
<button onClick={() => window.location.reload()}>{t('app.retry')}</button>
<p style={{maxWidth: '400px', textAlign: 'center', color: '#64748b'}}>
{t(`errors.${error.type}`)}
</p>
<button onClick={() => loadData()}>{t('app.retry')}</button>
</div>
);
}