Restyle toasts and centralize friendly error handling.

Replace the default react-toastify look with project-styled toast cards
(icon chip, rounded shell, RTL-aware container, type-colored progress
bar) via a new notify helper and globals.css overrides.

Add a central error layer (src/lib/errors.ts): classify any caught error
by HTTP status / network condition, log the full technical detail
(including the raw backend message) to the console only, and surface a
friendly, localized message to the user. Raw backend messages are no
longer shown. All ~190 toast call sites across 22 files move to notify,
routing backend errors through notify.error(err, fallback); dead
apiErrorMessage/formatApiError helpers removed. Adds an `errors` section
to the fa/en dictionaries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
keyhan
2026-06-13 11:45:26 +03:30
parent 97cd5e989a
commit 91a66d5645
29 changed files with 501 additions and 250 deletions
@@ -3,7 +3,7 @@
import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { FileText, Search, User, Wallet, CreditCard, XCircle, Download } from 'lucide-react';
import { toast } from 'react-toastify';
import { notify } from '@/lib/notify';
import api from '@/lib/api';
import { useT, useLocale } from '@/i18n/I18nProvider';
import type { Invoice, InvoiceStatus, PaymentMethod } from '@/types';
@@ -51,12 +51,12 @@ export default function AdminInvoicesPage() {
mutationFn: ({ invoiceId, nextStatus, reason }: { invoiceId: string; nextStatus: InvoiceStatus; reason: string }) =>
api.patch(`/billing/admin/invoices/${invoiceId}/status`, { status: nextStatus, reason }).then((r) => r.data),
onSuccess: () => {
toast.success(inv.statusUpdated);
notify.success(inv.statusUpdated);
setStatusReason('');
queryClient.invalidateQueries({ queryKey: ['admin-invoices'] });
queryClient.invalidateQueries({ queryKey: ['admin-invoice', selectedId] });
},
onError: (err: any) => toast.error(err.response?.data?.message || inv.statusUpdateFailed),
onError: (err: any) => notify.error(err, inv.statusUpdateFailed),
});
const downloadPdfMutation = useMutation({
@@ -71,7 +71,7 @@ export default function AdminInvoicesPage() {
link.remove();
window.URL.revokeObjectURL(url);
},
onError: () => toast.error(inv.downloadFailed),
onError: () => notify.error(inv.downloadFailed),
});
const formatPrice = (amount: number) => Number(amount || 0).toLocaleString('en-US');
@@ -80,7 +80,7 @@ export default function AdminInvoicesPage() {
const handleStatusUpdate = (nextStatus: InvoiceStatus) => {
if (!selectedInvoice) return;
if (!statusReason.trim()) {
toast.error(inv.reasonRequired);
notify.error(inv.reasonRequired);
return;
}
updateStatusMutation.mutate({