error-handling #1
11
src/App.js
11
src/App.js
|
@ -8,6 +8,7 @@ import Items from './components/Items';
|
||||||
import Navbar from './components/Navbar'; // Correct import here
|
import Navbar from './components/Navbar'; // Correct import here
|
||||||
import Admin from './components/Admin'; // Correct import here
|
import Admin from './components/Admin'; // Correct import here
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
import './styles.css'
|
import './styles.css'
|
||||||
|
|
||||||
export const AppContext = createContext();
|
export const AppContext = createContext();
|
||||||
|
@ -26,10 +27,12 @@ function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={{ token, setToken }}>
|
<AppContext.Provider value={{ token, setToken }}>
|
||||||
<Router>
|
<ErrorBoundary>
|
||||||
<Navbar />
|
<Router>
|
||||||
<AppRoutes token={token} setToken={setToken} />
|
<Navbar />
|
||||||
</Router>
|
<AppRoutes token={token} setToken={setToken} />
|
||||||
|
</Router>
|
||||||
|
</ErrorBoundary>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Alert, Button, Container } from '@mui/material';
|
||||||
|
|
||||||
|
class ErrorBoundary extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Alert severity="error" sx={{ mt: 2 }}>
|
||||||
|
Something went wrong
|
||||||
|
<Button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
>
|
||||||
|
Reload Page
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
Button,
|
Button,
|
||||||
|
@ -27,6 +27,30 @@ import { useApiCall } from './hooks/useApiCall';
|
||||||
import { api } from '../services/api';
|
import { api } from '../services/api';
|
||||||
import ItemDetails from './ItemDetails';
|
import ItemDetails from './ItemDetails';
|
||||||
|
|
||||||
|
const Item = React.memo(({ item, onDelete, onEdit, itemImages }) => (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<Avatar src={itemImages[item.ID] || '/images/default.jpg'} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{item.name}</TableCell>
|
||||||
|
<TableCell>{item.description}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Box display="flex" justifyContent="space-between" width="100%">
|
||||||
|
<Tooltip title="Edit Item">
|
||||||
|
<IconButton onClick={() => onEdit(item)} size="large" sx={{ mr: 1 }}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Delete Item">
|
||||||
|
<IconButton onClick={() => onDelete(item.ID)} size="large" color="error">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
));
|
||||||
|
|
||||||
export default function Items({ token }) {
|
export default function Items({ token }) {
|
||||||
const { id: boxId } = useParams();
|
const { id: boxId } = useParams();
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
|
@ -53,7 +77,7 @@ export default function Items({ token }) {
|
||||||
const getItems = async () => {
|
const getItems = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetchItems(() =>
|
const response = await fetchItems(() =>
|
||||||
api.items.getAll(token, boxId)
|
boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token)
|
||||||
);
|
);
|
||||||
setItems(response.data);
|
setItems(response.data);
|
||||||
|
|
||||||
|
@ -103,7 +127,7 @@ export default function Items({ token }) {
|
||||||
return imageName;
|
return imageName;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveNewItem = async () => {
|
const handleSaveNewItem = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const newItemResponse = await createItem(() =>
|
const newItemResponse = await createItem(() =>
|
||||||
api.items.create(token, {
|
api.items.create(token, {
|
||||||
|
@ -124,30 +148,45 @@ export default function Items({ token }) {
|
||||||
await uploadImage(() =>
|
await uploadImage(() =>
|
||||||
api.items.uploadImage(token, newItemResponse.data.id, formData)
|
api.items.uploadImage(token, newItemResponse.data.id, formData)
|
||||||
);
|
);
|
||||||
|
if (newItemResponse.data.id) {
|
||||||
|
try {
|
||||||
|
const imageResponse = await api.items.getImage(token, newItemResponse.data.id);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
setItemImages(prev => ({
|
||||||
|
...prev,
|
||||||
|
[newItemResponse.data.id]: reader.result
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(imageResponse.data);
|
||||||
|
} catch (err) {
|
||||||
|
setItemImages(prev => ({
|
||||||
|
...prev,
|
||||||
|
[newItemResponse.data.id]: '/default.jpg'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseAddItemDialog();
|
handleCloseAddItemDialog();
|
||||||
|
|
||||||
// Refresh items list
|
|
||||||
const response = await fetchItems(() =>
|
const response = await fetchItems(() =>
|
||||||
api.items.getAll(token, boxId)
|
boxId ? api.items.getByBox(token, boxId) : api.items.getAll(token)
|
||||||
);
|
);
|
||||||
setItems(response.data);
|
setItems(response.data);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
};
|
}, [token, boxId, newItemName, newItemDescription, createItem, uploadImage, fetchItems]);
|
||||||
|
|
||||||
const handleDeleteItem = async (itemId) => {
|
const handleDeleteItem = useCallback(async (itemId) => {
|
||||||
try {
|
try {
|
||||||
await deleteItem(() =>
|
await deleteItem(() => api.items.delete(token, itemId));
|
||||||
api.items.delete(token, itemId)
|
setItems(prev => prev.filter(item => item.ID !== itemId));
|
||||||
);
|
|
||||||
setItems(items.filter(item => item.ID !== itemId));
|
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
};
|
}, [token, deleteItem]);
|
||||||
|
|
||||||
const handleEditItem = (item) => {
|
const handleEditItem = useCallback((item) => {
|
||||||
setEditingItem(item);
|
setEditingItem(item);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleSaveEdit = async () => {
|
const handleSaveEdit = async () => {
|
||||||
setEditingItem(null);
|
setEditingItem(null);
|
||||||
|
@ -157,9 +196,12 @@ export default function Items({ token }) {
|
||||||
setItems(response.data);
|
setItems(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredItems = items.filter(item =>
|
const filteredItems = useMemo(() =>
|
||||||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
items.filter(item =>
|
||||||
item.description.toLowerCase().includes(searchQuery.toLowerCase())
|
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
item.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
),
|
||||||
|
[items, searchQuery]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -251,40 +293,17 @@ export default function Items({ token }) {
|
||||||
<TableCell>Actions</TableCell>
|
<TableCell>Actions</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{filteredItems.map((item) => (
|
{filteredItems.map((item) => (
|
||||||
<TableRow key={item.ID}>
|
<Item
|
||||||
<TableCell>
|
key={item.ID}
|
||||||
<Avatar src={itemImages[item.ID] || '/images/default.jpg'} />
|
item={item}
|
||||||
</TableCell>
|
itemImages={itemImages}
|
||||||
<TableCell>{item.name}</TableCell>
|
onDelete={handleDeleteItem}
|
||||||
<TableCell>{item.description}</TableCell>
|
onEdit={handleEditItem}
|
||||||
<TableCell>
|
/>
|
||||||
<Box display="flex" justifyContent="space-between" width="100%">
|
))}
|
||||||
<Tooltip title="Edit Item">
|
</TableBody>
|
||||||
<IconButton
|
|
||||||
onClick={() => handleEditItem(item)}
|
|
||||||
size="large"
|
|
||||||
sx={{ mr: 1 }}
|
|
||||||
>
|
|
||||||
<EditIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title="Delete Item">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => handleDeleteItem(item.ID)}
|
|
||||||
size="large"
|
|
||||||
color="error"
|
|
||||||
disabled={deletingItem}
|
|
||||||
>
|
|
||||||
{deletingItem ? <CircularProgress size={20} /> : <DeleteIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue