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:
@@ -3,7 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '@/lib/api';
|
||||
import { toast } from 'react-toastify';
|
||||
import { notify } from '@/lib/notify';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
import type {
|
||||
PricingCatalog,
|
||||
@@ -39,15 +39,6 @@ function toPricingCatalogPatch(catalog: PricingCatalog) {
|
||||
};
|
||||
}
|
||||
|
||||
function formatApiError(err: unknown, fallback: string): string {
|
||||
if (!err || typeof err !== 'object' || !('response' in err)) return fallback;
|
||||
const message = (err as { response?: { data?: { message?: string | string[] } } }).response
|
||||
?.data?.message;
|
||||
if (Array.isArray(message)) return message.join(', ');
|
||||
if (typeof message === 'string') return message;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function cloneCatalog(catalog: PricingCatalog): PricingCatalog {
|
||||
const runtimes: PricingCatalog['runtimes'] = {};
|
||||
for (const key of Object.keys(catalog.runtimes)) {
|
||||
@@ -377,12 +368,12 @@ export default function AdminBillingPage() {
|
||||
queryClient.invalidateQueries({ queryKey: ['pricing-catalog'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['custom-domain-price'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['optional-services-pricing'] });
|
||||
toast.success(b.saved);
|
||||
notify.success(b.saved);
|
||||
setEditing(false);
|
||||
setDraft(null);
|
||||
},
|
||||
onError: (err: unknown) => {
|
||||
toast.error(formatApiError(err, b.saveFailed));
|
||||
notify.error(err, b.saveFailed);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -743,11 +734,11 @@ function LifecycleSettingsSection() {
|
||||
mutationFn: (body: Record<string, number>) => api.patch('/lifecycle/settings', body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['lifecycle-settings'] });
|
||||
toast.success(b.lifecycleSaved);
|
||||
notify.success(b.lifecycleSaved);
|
||||
setEditing(false);
|
||||
},
|
||||
onError: (err: unknown) => {
|
||||
toast.error(formatApiError(err, b.saveFailedShort));
|
||||
notify.error(err, b.saveFailedShort);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user