feat: add error handling with toast notifications across various tools and admin page
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Bot, SendHorizontal, Sparkles, X } from 'lucide-react';
|
import { Bot, SendHorizontal, Sparkles, X } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { getToolSEO } from '@/config/seoData';
|
import { getToolSEO } from '@/config/seoData';
|
||||||
import { streamAssistantChat, type AssistantHistoryMessage } from '@/services/api';
|
import { streamAssistantChat, type AssistantHistoryMessage } from '@/services/api';
|
||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
@@ -181,6 +182,7 @@ export default function SiteAssistant() {
|
|||||||
? requestError.message
|
? requestError.message
|
||||||
: t('assistant.unavailable');
|
: t('assistant.unavailable');
|
||||||
setError(message);
|
setError(message);
|
||||||
|
toast.error(message);
|
||||||
|
|
||||||
setMessages((currentMessages) => currentMessages.map((currentMessage) => (
|
setMessages((currentMessages) => currentMessages.map((currentMessage) => (
|
||||||
currentMessage.id === assistantMessageId
|
currentMessage.id === assistantMessageId
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import ProgressBar from '@/components/shared/ProgressBar';
|
|||||||
import AdSlot from '@/components/layout/AdSlot';
|
import AdSlot from '@/components/layout/AdSlot';
|
||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
const BARCODE_TYPES = ['code128', 'code39', 'ean13', 'ean8', 'upca', 'isbn13', 'isbn10', 'issn', 'pzn'] as const;
|
const BARCODE_TYPES = ['code128', 'code39', 'ean13', 'ean8', 'upca', 'isbn13', 'isbn10', 'issn', 'pzn'] as const;
|
||||||
@@ -32,7 +33,9 @@ export default function BarcodeGenerator() {
|
|||||||
});
|
});
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to generate barcode.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
export default function CropPdf() {
|
export default function CropPdf() {
|
||||||
@@ -40,7 +41,9 @@ export default function CropPdf() {
|
|||||||
const res = await api.post<TaskResponse>('/pdf-tools/crop', fd);
|
const res = await api.post<TaskResponse>('/pdf-tools/crop', fd);
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to crop PDF.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
export default function ImageCrop() {
|
export default function ImageCrop() {
|
||||||
@@ -40,7 +41,9 @@ export default function ImageCrop() {
|
|||||||
const res = await api.post<TaskResponse>('/image/crop', fd);
|
const res = await api.post<TaskResponse>('/image/crop', fd);
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to crop image.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
export default function ImageRotateFlip() {
|
export default function ImageRotateFlip() {
|
||||||
@@ -41,7 +42,9 @@ export default function ImageRotateFlip() {
|
|||||||
const res = await api.post<TaskResponse>('/image/rotate-flip', fd);
|
const res = await api.post<TaskResponse>('/image/rotate-flip', fd);
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to transform image.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import ProgressBar from '@/components/shared/ProgressBar';
|
import ProgressBar from '@/components/shared/ProgressBar';
|
||||||
import DownloadButton from '@/components/shared/DownloadButton';
|
import DownloadButton from '@/components/shared/DownloadButton';
|
||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { uploadFiles } from '@/services/api';
|
import { uploadFiles } from '@/services/api';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
@@ -66,7 +67,9 @@ export default function ImagesToPdf() {
|
|||||||
setTaskId(data.task_id);
|
setTaskId(data.task_id);
|
||||||
setPhase('processing');
|
setPhase('processing');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Upload failed.');
|
const msg = err instanceof Error ? err.message : t('common.errors.uploadFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ProgressBar from '@/components/shared/ProgressBar';
|
|||||||
import DownloadButton from '@/components/shared/DownloadButton';
|
import DownloadButton from '@/components/shared/DownloadButton';
|
||||||
import AdSlot from '@/components/layout/AdSlot';
|
import AdSlot from '@/components/layout/AdSlot';
|
||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { uploadFiles } from '@/services/api';
|
import { uploadFiles } from '@/services/api';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
@@ -66,7 +67,9 @@ export default function MergePdf() {
|
|||||||
setTaskId(data.task_id);
|
setTaskId(data.task_id);
|
||||||
setPhase('processing');
|
setPhase('processing');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Upload failed.');
|
const msg = err instanceof Error ? err.message : t('common.errors.uploadFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async';
|
|||||||
import { GitBranch } from 'lucide-react';
|
import { GitBranch } from 'lucide-react';
|
||||||
import AdSlot from '@/components/layout/AdSlot';
|
import AdSlot from '@/components/layout/AdSlot';
|
||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { startTask, uploadFile } from '@/services/api';
|
import { startTask, uploadFile } from '@/services/api';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
@@ -91,7 +92,9 @@ export default function PdfFlowchart() {
|
|||||||
const data = await uploadFile('/flowchart/extract', file);
|
const data = await uploadFile('/flowchart/extract', file);
|
||||||
setTaskId(data.task_id);
|
setTaskId(data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Upload failed.');
|
const msg = err instanceof Error ? err.message : t('common.errors.uploadFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -104,7 +107,9 @@ export default function PdfFlowchart() {
|
|||||||
const data = await startTask('/flowchart/extract-sample');
|
const data = await startTask('/flowchart/extract-sample');
|
||||||
setTaskId(data.task_id);
|
setTaskId(data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Sample failed.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
export default function PdfMetadata() {
|
export default function PdfMetadata() {
|
||||||
@@ -37,7 +38,9 @@ export default function PdfMetadata() {
|
|||||||
const res = await api.post<TaskResponse>('/pdf-tools/metadata', fd);
|
const res = await api.post<TaskResponse>('/pdf-tools/metadata', fd);
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to edit metadata.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import ProgressBar from '@/components/shared/ProgressBar';
|
|||||||
import AdSlot from '@/components/layout/AdSlot';
|
import AdSlot from '@/components/layout/AdSlot';
|
||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse, type TaskResult } from '@/services/api';
|
import api, { type TaskResponse, type TaskResult } from '@/services/api';
|
||||||
import { dispatchRatingPrompt } from '@/utils/ratingPrompt';
|
import { dispatchRatingPrompt } from '@/utils/ratingPrompt';
|
||||||
|
|
||||||
@@ -31,7 +32,9 @@ export default function QrCodeGenerator() {
|
|||||||
const res = await api.post<TaskResponse>('/qrcode/generate', { data: data.trim(), size });
|
const res = await api.post<TaskResponse>('/qrcode/generate', { data: data.trim(), size });
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to generate QR code.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
|
|||||||
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
import { generateToolSchema } from '@/utils/seo';
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
import { useFileStore } from '@/stores/fileStore';
|
import { useFileStore } from '@/stores/fileStore';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import api, { type TaskResponse } from '@/services/api';
|
import api, { type TaskResponse } from '@/services/api';
|
||||||
|
|
||||||
export default function SignPdf() {
|
export default function SignPdf() {
|
||||||
@@ -40,7 +41,9 @@ export default function SignPdf() {
|
|||||||
const res = await api.post<TaskResponse>('/pdf-tools/sign', fd);
|
const res = await api.post<TaskResponse>('/pdf-tools/sign', fd);
|
||||||
setTaskId(res.data.task_id);
|
setTaskId(res.data.task_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to sign PDF.');
|
const msg = err instanceof Error ? err.message : t('common.errors.processingFailed');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
setPhase('done');
|
setPhase('done');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState, type FormEvent } from 'react';
|
import { useEffect, useMemo, useState, type FormEvent } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { Helmet } from 'react-helmet-async';
|
import { Helmet } from 'react-helmet-async';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
@@ -182,6 +183,12 @@ const TRANSLATIONS: Record<Lang, Record<string, string>> = {
|
|||||||
btnPro: 'Pro',
|
btnPro: 'Pro',
|
||||||
btnUser: 'User',
|
btnUser: 'User',
|
||||||
btnAdmin: 'Admin',
|
btnAdmin: 'Admin',
|
||||||
|
// Errors
|
||||||
|
loadError: 'Failed to load dashboard data.',
|
||||||
|
loginError: 'Unable to sign in.',
|
||||||
|
updatePlanError: 'Unable to update plan.',
|
||||||
|
updateRoleError: 'Unable to update role.',
|
||||||
|
updateContactError: 'Unable to update contact message.',
|
||||||
},
|
},
|
||||||
ar: {
|
ar: {
|
||||||
// Page & header
|
// Page & header
|
||||||
@@ -317,6 +324,12 @@ const TRANSLATIONS: Record<Lang, Record<string, string>> = {
|
|||||||
btnPro: 'Pro',
|
btnPro: 'Pro',
|
||||||
btnUser: 'مستخدم',
|
btnUser: 'مستخدم',
|
||||||
btnAdmin: 'مشرف',
|
btnAdmin: 'مشرف',
|
||||||
|
// Errors
|
||||||
|
loadError: 'فشل تحميل بيانات لوحة التحكم.',
|
||||||
|
loginError: 'تعذّر تسجيل الدخول.',
|
||||||
|
updatePlanError: 'تعذّر تحديث الخطة.',
|
||||||
|
updateRoleError: 'تعذّر تحديث الدور.',
|
||||||
|
updateContactError: 'تعذّر تحديث رسالة التواصل.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -475,7 +488,9 @@ export default function InternalAdminPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : 'Failed to load dashboard data.');
|
const msg = e instanceof Error ? e.message : t('loadError');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -492,7 +507,9 @@ export default function InternalAdminPage() {
|
|||||||
}
|
}
|
||||||
setPassword('');
|
setPassword('');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoginError(e instanceof Error ? e.message : 'Unable to sign in.');
|
const msg = e instanceof Error ? e.message : t('loginError');
|
||||||
|
setLoginError(msg);
|
||||||
|
toast.error(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +521,9 @@ export default function InternalAdminPage() {
|
|||||||
await updateInternalAdminUserPlan(userId, plan);
|
await updateInternalAdminUserPlan(userId, plan);
|
||||||
await loadTab('users');
|
await loadTab('users');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : 'Unable to update plan.');
|
const msg = e instanceof Error ? e.message : t('updatePlanError');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setUpdatingUserId(null);
|
setUpdatingUserId(null);
|
||||||
}
|
}
|
||||||
@@ -518,7 +537,9 @@ export default function InternalAdminPage() {
|
|||||||
await updateInternalAdminUserRole(userId, role);
|
await updateInternalAdminUserRole(userId, role);
|
||||||
await loadTab('users');
|
await loadTab('users');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : 'Unable to update role.');
|
const msg = e instanceof Error ? e.message : t('updateRoleError');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setUpdatingRoleUserId(null);
|
setUpdatingRoleUserId(null);
|
||||||
}
|
}
|
||||||
@@ -532,7 +553,9 @@ export default function InternalAdminPage() {
|
|||||||
await markInternalAdminContactRead(messageId);
|
await markInternalAdminContactRead(messageId);
|
||||||
await loadTab('contacts');
|
await loadTab('contacts');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : 'Unable to update contact message.');
|
const msg = e instanceof Error ? e.message : t('updateContactError');
|
||||||
|
setError(msg);
|
||||||
|
toast.error(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setMarkingMessageId(null);
|
setMarkingMessageId(null);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user