feat: add toast when endpoints are failing

This commit is contained in:
Rodolfo Ruiz
2025-07-03 17:23:09 -06:00
parent 2849ee2e6b
commit eb49416000
6 changed files with 91 additions and 48 deletions

41
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@mui/icons-material": "^7.1.0", "@mui/icons-material": "^7.1.0",
"@mui/material": "^7.1.0", "@mui/material": "^7.1.0",
"@mui/x-data-grid": "^8.5.0", "@mui/x-data-grid": "^8.5.0",
"notistack": "^3.0.2",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0" "react-dom": "^19.1.0"
}, },
@@ -2993,6 +2994,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/goober": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -3461,6 +3471,37 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/notistack": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.2.tgz",
"integrity": "sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA==",
"license": "MIT",
"dependencies": {
"clsx": "^1.1.0",
"goober": "^2.0.33"
},
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/notistack"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/notistack/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View File

@@ -16,6 +16,7 @@
"@mui/icons-material": "^7.1.0", "@mui/icons-material": "^7.1.0",
"@mui/material": "^7.1.0", "@mui/material": "^7.1.0",
"@mui/x-data-grid": "^8.5.0", "@mui/x-data-grid": "^8.5.0",
"notistack": "^3.0.2",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0" "react-dom": "^19.1.0"
}, },

View File

@@ -1,65 +1,44 @@
const API_BASE_URL = 'http://portainer.white-enciso.pro:4001/api/v1/MongoSample'; const API_BASE_URL = 'http://portainer.white-enciso.pro:4001/api/v1/MongoSample';
export async function getExternalData() { export async function getExternalData() {
try { const response = await fetch(`${API_BASE_URL}/GetAll`);
const response = await fetch(`${API_BASE_URL}/GetAll`); if (!response.ok) throw new Error('Failed to fetch external data');
if (!response.ok) throw new Error('Failed to fetch external data'); const data = await response.json();
const data = await response.json(); return data;
return data;
} catch (error) {
console.error('Error fetching external data:', error);
return [];
}
} }
export async function createExternalData(data) { export async function createExternalData(data) {
try { const response = await fetch(`${API_BASE_URL}/Create`, {
const response = await fetch(`${API_BASE_URL}/Create`, { method: 'POST',
method: 'POST', headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json' },
}, body: JSON.stringify(data),
body: JSON.stringify(data) });
}); if (!response.ok) throw new Error('Failed to create external data');
if (!response.ok) throw new Error('Failed to create external data'); return await response.json();
const result = await response.json();
return result;
} catch (error) {
console.error('Error creating external data:', error);
throw error;
}
} }
export async function updateExternalData(data) { export async function updateExternalData(data) {
const response = await fetch(`${API_BASE_URL}/Update`, { const response = await fetch(`${API_BASE_URL}/Update`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify(data) body: JSON.stringify(data),
}); });
if (!response.ok) throw new Error('Failed to update item');
if (!response.ok) {
throw new Error('Failed to update item');
}
return await response.json(); return await response.json();
} }
export async function deleteExternalData(_Id) { export async function deleteExternalData(_Id) {
try { const response = await fetch(`${API_BASE_URL}/Delete`, {
const response = await fetch(`${API_BASE_URL}/Delete`, { method: 'DELETE',
method: 'DELETE', headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', },
}, body: JSON.stringify({ _Id }),
body: JSON.stringify({ _Id }), });
}); if (!response.ok) throw new Error('Failed to delete external data');
return await response.json();
if (!response.ok) throw new Error('Failed to delete external data');
return await response.json();
} catch (error) {
console.error('Error deleting external data:', error);
throw error;
}
} }

12
src/hooks/useApiToast.jsx Normal file
View File

@@ -0,0 +1,12 @@
import { useSnackbar } from 'notistack';
export default function useApiToast() {
const { enqueueSnackbar } = useSnackbar();
const handleError = (error, defaultMessage = 'API error') => {
console.error(error);
enqueueSnackbar(error.message || defaultMessage, { variant: 'error' });
};
return { handleError };
}

View File

@@ -1,6 +1,6 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { SnackbarProvider } from 'notistack';
import { ThemeProvider } from '@mui/material/styles'; import { ThemeProvider } from '@mui/material/styles';
import theme from './theme'; import theme from './theme';
import './index.css' import './index.css'
@@ -9,7 +9,14 @@ import App from './App.jsx'
createRoot(document.getElementById('root')).render( createRoot(document.getElementById('root')).render(
<StrictMode> <StrictMode>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<App /> <SnackbarProvider maxSnack={3}
autoHideDuration={5000}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<App />
</SnackbarProvider>
</ThemeProvider> </ThemeProvider>
</StrictMode>, </StrictMode>,
) )

View File

@@ -6,6 +6,7 @@ import EditRoundedIcon from '@mui/icons-material/EditRounded';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import AddOrEditAdminForm from './AddOrEditAdminForm'; import AddOrEditAdminForm from './AddOrEditAdminForm';
import { getExternalData, deleteExternalData } from '../../api/mongo/actions'; import { getExternalData, deleteExternalData } from '../../api/mongo/actions';
import useApiToast from '../../hooks/useApiToast';
const columnsBase = [ const columnsBase = [
{ field: 'name', headerName: 'Name', flex: 2 }, { field: 'name', headerName: 'Name', flex: 2 },
@@ -39,6 +40,7 @@ export default function Admin() {
const [editingData, setEditingData] = useState(null); const [editingData, setEditingData] = useState(null);
const [confirmOpen, setConfirmOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false);
const [rowToDelete, setRowToDelete] = useState(null); const [rowToDelete, setRowToDelete] = useState(null);
const { handleError } = useApiToast();
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
@@ -75,6 +77,7 @@ export default function Admin() {
setRows(safeData); setRows(safeData);
} catch (error) { } catch (error) {
console.error('Error loading data:', error); console.error('Error loading data:', error);
handleError(error, 'Failed to load data');
setRows([]); setRows([]);
} }
}; };