че то накалякал, потом буду исправлять сто проц

бб
This commit is contained in:
2025-11-07 20:43:13 +04:00
parent b7aa56e629
commit be23112345
6 changed files with 235 additions and 186 deletions

View File

@@ -8,32 +8,42 @@ import CatalogPage from './pages/CatalogPage.jsx';
import ContactsPage from './pages/ContactsPage.jsx';
import LikesPage from './pages/LikesPage.jsx';
import BasketPage from './pages/BasketPage.jsx';
import ErrorPage from './pages/ErrorPage.jsx';
import Footer from './components/Footer.jsx';
import Navbar from './components/Navbar.jsx';
import './App.css'
// Создаем отдельный компонент для роутинга
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<IndexPage />} />
<Route path="/catalog" element={<CatalogPage />} />
<Route path="/contacts" element={<ContactsPage />} />
<Route path="/likes" element={<LikesPage />} />
<Route path="/basket" element={<BasketPage />} />
</Routes>
);
}
export default function App() {
return (
<BasketProvider>
<LikesProvider>
<ToastContainer />
<BrowserRouter>
<BrowserRouter
future={{
v7_startTransition: true,
v7_relativeSplatPath: true
}}
>
<BasketProvider>
<LikesProvider>
<ToastContainer />
<Navbar />
<main style={{ flex: 1 }}>
<Routes>
<Route path="/" element={<IndexPage />} />
<Route path="/catalog" element={<CatalogPage />} />
<Route path="/contacts" element={<ContactsPage />} />
<Route path="/likes" element={<LikesPage />} />
<Route path="/basket" element={<BasketPage />} />
<Route path="*" element={<ErrorPage/>}/>
</Routes>
<main>
<AppRoutes />
</main>
<Footer />
</BrowserRouter>
</LikesProvider>
</BasketProvider>
</LikesProvider>
</BasketProvider>
</BrowserRouter>
);
}

View File

@@ -88,11 +88,22 @@ export default function BasketPage() {
{basketItems.map(item => (
<div key={item.id} className="row align-items-center mb-3 basket-item">
<div className="col-md-2">
<img src={item.image} alt={item.name} className="img-fluid rounded" style={{maxHeight: "80px"}} />
<img
src={item.image}
alt={item.name}
className="img-fluid rounded"
style={{maxHeight: "80px", objectFit: 'cover', width: '100%'}}
onError={(e) => {
e.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtc2l6ZT0iMTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=';
}}
/>
</div>
<div className="col-md-4">
<h6 className="mb-1">{item.name}</h6>
<p className="text-muted small mb-0">{item.description}</p>
<small className="text-muted">
Category: {item.category} | Condition: {item.condition}
</small>
</div>
<div className="col-md-2">
<span className="fw-bold">${item.price}</span>
@@ -103,6 +114,7 @@ export default function BasketPage() {
className="btn btn-outline-secondary"
type="button"
onClick={() => updateQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1}
>
-
</button>

View File

@@ -1,4 +1,4 @@
import { useContext } from 'react';
// src/pages/LikesPage.jsx
import { Link } from 'react-router-dom';
import { useLikesContext } from '../context/LikesContext.jsx';
import { useBasketContext } from '../context/BasketContext.jsx';
@@ -72,24 +72,34 @@ export default function LikesPage() {
{likesItems.map(item => (
<div key={item.id} className="col-md-4">
<div className="card h-100 border-0 shadow">
<img src={item.image} className="card-img-top" alt={item.name} />
<img
src={item.image}
className="card-img-top"
alt={item.name}
style={{ height: '250px', objectFit: 'cover' }}
onError={(e) => {
e.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtc2l6ZT0iMTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=';
}}
/>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
<p className="card-text">{item.description}</p>
<div className="row">
<div className="col-6">
<h6 className="mb-1">Category:</h6>
<p className="card-text">{item.category || '-'}</p>
<p className="card-text">{item.category}</p>
</div>
<div className="col-6">
<h6 className="mb-1">Condition:</h6>
<p className="card-text">{item.condition || '-'}</p>
<p className="card-text">{item.condition}</p>
</div>
</div>
</div>
<div className="card-footer bg-transparent">
<div className="d-flex justify-content-between align-items-end">
<span className="fw-bold text-muted fs-3">${item.price}</span>
<span className="fw-bold text-muted fs-3">
${item.price}
</span>
<div className="d-flex flex-column gap-1">
<button
className="btn btn-sm btn-outline-primary"

View File

@@ -1,14 +1,13 @@
// src/hooks/useBasket.js
import { useState, useEffect } from 'react';
const apiUrl = 'http://localhost:3000';
const apiUrl = 'http://localhost:8080/api/1.0';
export const useBasket = () => {
const [basketItems, setBasketItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Загрузить корзину из JSON Server при монтировании
useEffect(() => {
loadBasketFromServer();
}, []);
@@ -19,92 +18,54 @@ export const useBasket = () => {
const response = await fetch(`${apiUrl}/basket`);
if (response.ok) {
const data = await response.json();
setBasketItems(data);
console.log('Basket data from server:', data); // Для отладки
// Преобразуем данные в формат, который ожидает фронтенд
const mappedItems = data.map(item => {
const product = item.product;
return {
id: product?.id || item.productId,
name: product?.name || 'Без названия',
price: product?.price || 0,
description: product?.description || 'Описание отсутствует',
image: product?.imageURL || product?.image || 'img/placeholder.jpg',
// Корректно мапим категорию и состояние
category: product?.category?.name || 'Unknown',
condition: product?.condition?.name || 'Unknown',
categoryId: product?.categoryId,
conditionId: product?.conditionId,
quantity: item.quantity || 1,
// Сохраняем оригинальные данные для удаления
_basketItemId: item.id
};
});
setBasketItems(mappedItems);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (err) {
console.error('Ошибка при загрузке корзины:', err);
setError('Ошибка загрузки корзины');
setError('Ошибка загрузки корзины: ' + err.message);
} finally {
setLoading(false);
}
};
// Добавить товар в корзину
const addToBasket = async (product) => {
try {
const existingItem = basketItems.find(item => item.id === product.id);
if (existingItem) {
return await updateBasketItem(product.id, existingItem.quantity + 1);
} else {
const basketItem = {
...product,
quantity: 1,
addedAt: new Date().toISOString()
};
const response = await fetch(`${apiUrl}/basket`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(basketItem)
});
if (response.ok) {
const newItem = await response.json();
setBasketItems(prev => [...prev, newItem]);
return newItem;
}
}
return null;
} catch (err) {
console.error('Ошибка при добавлении в корзину:', err);
setError('Ошибка добавления в корзину');
throw err;
}
};
// Обновить количество товара в корзине
const updateBasketItem = async (productId, quantity) => {
try {
const response = await fetch(`${apiUrl}/basket/${productId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ quantity })
});
if (response.ok) {
const updatedItem = await response.json();
setBasketItems(prev =>
prev.map(item =>
item.id === productId ? updatedItem : item
)
);
return updatedItem;
}
return null;
} catch (err) {
console.error('Ошибка при обновлении корзины:', err);
setError('Ошибка обновления корзины');
throw err;
}
};
// Удалить товар из корзины
const removeFromBasket = async (productId) => {
try {
const response = await fetch(`${apiUrl}/basket/${productId}`, {
// Находим ID элемента корзины
const basketItem = basketItems.find(item => item.id === productId);
if (!basketItem) throw new Error('Товар не найден в корзине');
const response = await fetch(`${apiUrl}/basket/${basketItem._basketItemId}`, {
method: 'DELETE'
});
if (response.ok) {
setBasketItems(prev => prev.filter(item => item.id !== productId));
return true;
}
return false;
if (!response.ok) throw new Error('Ошибка удаления из корзины');
await loadBasketFromServer();
return true;
} catch (err) {
console.error('Ошибка при удалении из корзины:', err);
setError('Ошибка удаления из корзины');
@@ -112,14 +73,70 @@ export const useBasket = () => {
}
};
// Очистить корзину
// Остальные функции остаются такими же...
const addToBasket = async (product) => {
try {
const basketItem = {
productId: product.id,
quantity: 1
};
const response = await fetch(`${apiUrl}/basket`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(basketItem)
});
if (!response.ok) throw new Error('Ошибка добавления в корзину');
await loadBasketFromServer();
return true;
} catch (err) {
console.error('Ошибка при добавлении в корзину:', err);
setError('Ошибка добавления в корзину');
throw err;
}
};
const updateQuantity = async (productId, quantity) => {
try {
// Находим ID элемента корзины
const basketItem = basketItems.find(item => item.id === productId);
if (!basketItem) throw new Error('Товар не найден в корзине');
const updatedItem = {
quantity: Math.max(1, quantity)
};
const response = await fetch(`${apiUrl}/basket/${basketItem._basketItemId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedItem)
});
if (!response.ok) throw new Error('Ошибка обновления количества');
await loadBasketFromServer();
return true;
} catch (err) {
console.error('Ошибка при обновлении корзины:', err);
setError('Ошибка обновления корзины');
throw err;
}
};
const clearBasket = async () => {
try {
for (const item of basketItems) {
await fetch(`${apiUrl}/basket/${item.id}`, {
method: 'DELETE'
});
}
const response = await fetch(`${apiUrl}/basket`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Ошибка очистки корзины');
setBasketItems([]);
return true;
} catch (err) {
@@ -129,31 +146,24 @@ export const useBasket = () => {
}
};
// Рассчитать общую сумму
const calculateTotal = () => {
return basketItems.reduce((total, item) => total + (parseFloat(item.price) * item.quantity), 0);
};
// Получить количество товаров в корзине
const getBasketCount = () => {
return basketItems.reduce((total, item) => total + item.quantity, 0);
};
// Обновить состояние корзины
const refetchBasket = () => {
loadBasketFromServer();
};
return {
basketItems,
loading,
error,
addToBasket,
removeFromBasket,
updateQuantity: updateBasketItem,
updateQuantity,
clearBasket,
calculateTotal,
getBasketCount,
refetch: refetchBasket
refetch: loadBasketFromServer
};
};

View File

@@ -1,14 +1,13 @@
// src/hooks/useLikes.js
import { useState, useEffect } from 'react';
const apiUrl = 'http://localhost:3000';
const apiUrl = 'http://localhost:8080/api/1.0';
export const useLikes = () => {
const [likesItems, setLikesItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Загрузить избранное из JSON Server при монтировании
useEffect(() => {
loadLikesFromServer();
}, []);
@@ -19,58 +18,53 @@ export const useLikes = () => {
const response = await fetch(`${apiUrl}/likes`);
if (response.ok) {
const data = await response.json();
setLikesItems(data);
console.log('Likes data from server:', data); // Для отладки
// Преобразуем данные в формат, который ожидает фронтенд
const mappedItems = data.map(item => {
const product = item.product;
return {
id: product?.id || item.productId,
name: product?.name || 'Без названия',
price: product?.price || 0,
description: product?.description || 'Описание отсутствует',
image: product?.imageURL || product?.image || 'img/placeholder.jpg',
// Корректно мапим категорию и состояние
category: product?.category?.name || 'Unknown',
condition: product?.condition?.name || 'Unknown',
categoryId: product?.categoryId,
conditionId: product?.conditionId,
// Сохраняем оригинальные данные для удаления
_likeItemId: item.id
};
});
setLikesItems(mappedItems);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (err) {
console.error('Ошибка при загрузке избранного:', err);
setError('Ошибка загрузки избранного');
setError('Ошибка загрузки избранного: ' + err.message);
} finally {
setLoading(false);
}
};
// Добавить в избранное
const addToLikes = async (product) => {
try {
const exists = likesItems.find(item => item.id === product.id);
if (!exists) {
const response = await fetch(`${apiUrl}/likes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...product,
likedAt: new Date().toISOString()
})
});
if (response.ok) {
const newItem = await response.json();
setLikesItems(prev => [...prev, newItem]);
return newItem;
}
}
return null;
} catch (err) {
console.error('Ошибка при добавлении в избранное:', err);
setError('Ошибка добавления в избранное');
throw err;
}
};
// Удалить из избранного
const removeFromLikes = async (productId) => {
try {
const response = await fetch(`${apiUrl}/likes/${productId}`, {
method: 'DELETE'
// Находим ID элемента избранного
const likeItem = likesItems.find(item => item.id === productId);
if (!likeItem) throw new Error('Товар не найден в избранном');
const response = await fetch(`${apiUrl}/likes/${likeItem._likeItemId}`, {
method: 'DELETE'
});
if (response.ok) {
setLikesItems(prev => prev.filter(item => item.id !== productId));
return true;
}
return false;
if (!response.ok) throw new Error('Ошибка удаления из избранного');
await loadLikesFromServer();
return true;
} catch (err) {
console.error('Ошибка при удалении из избранного:', err);
setError('Ошибка удаления из избранного');
@@ -78,30 +72,54 @@ export const useLikes = () => {
}
};
// Проверить, есть ли товар в избранном
// Остальные функции остаются такими же...
const addToLikes = async (product) => {
try {
const likeItem = {
productId: product.id
};
const response = await fetch(`${apiUrl}/likes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(likeItem)
});
if (!response.ok) throw new Error('Ошибка добавления в избранное');
await loadLikesFromServer();
return true;
} catch (err) {
console.error('Ошибка при добавлении в избранное:', err);
setError('Ошибка добавления в избранное');
throw err;
}
};
const isLiked = (productId) => {
return likesItems.some(item => item.id === productId);
};
// Переключить избранное (добавить/удалить)
const toggleLike = async (product) => {
if (isLiked(product.id)) {
await removeFromLikes(product.id);
return false; // Удален
return false;
} else {
await addToLikes(product);
return true; // Добавлен
return true;
}
};
// Очистить избранное
const clearLikes = async () => {
try {
for (const item of likesItems) {
await fetch(`${apiUrl}/likes/${item.id}`, {
method: 'DELETE'
});
}
const response = await fetch(`${apiUrl}/likes`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Ошибка очистки избранного');
setLikesItems([]);
return true;
} catch (err) {
@@ -111,16 +129,10 @@ export const useLikes = () => {
}
};
// Получить количество избранных товаров
const getLikesCount = () => {
return likesItems.length;
};
// Обновить состояние избранного
const refetchLikes = () => {
loadLikesFromServer();
};
return {
likesItems,
loading,
@@ -131,6 +143,6 @@ export const useLikes = () => {
toggleLike,
clearLikes,
getLikesCount,
refetch: refetchLikes
refetch: loadLikesFromServer
};
};

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
const apiUrl = 'http://localhost:3000';
const apiUrl = 'http://localhost:8080/api/1.0';
export const useProducts = () => {
const [products, setProducts] = useState([]);
@@ -9,7 +9,6 @@ export const useProducts = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Загрузка всех данных
useEffect(() => {
loadAllData();
}, []);
@@ -32,7 +31,7 @@ export const useProducts = () => {
const loadProducts = async () => {
try {
const response = await fetch(`${apiUrl}/shmots`);
const response = await fetch(`${apiUrl}/shmotki`);
if (!response.ok) throw new Error('Ошибка загрузки товаров');
const data = await response.json();
setProducts(data);
@@ -44,7 +43,7 @@ export const useProducts = () => {
const loadCategories = async () => {
try {
const response = await fetch(`${apiUrl}/category`);
const response = await fetch(`${apiUrl}/categories`);
if (!response.ok) throw new Error('Ошибка загрузки категорий');
const data = await response.json();
setCategories(data);
@@ -56,7 +55,7 @@ export const useProducts = () => {
const loadConditions = async () => {
try {
const response = await fetch(`${apiUrl}/condition`);
const response = await fetch(`${apiUrl}/conditions`);
if (!response.ok) throw new Error('Ошибка загрузки состояний');
const data = await response.json();
setConditions(data);
@@ -66,7 +65,6 @@ export const useProducts = () => {
}
};
// Добавление товара
const addProduct = async (productData) => {
try {
const newProduct = {
@@ -93,8 +91,7 @@ export const useProducts = () => {
throw err;
}
};
// Обновление товара
const updateProduct = async (id, productData) => {
try {
const updatedProduct = {
@@ -122,8 +119,7 @@ export const useProducts = () => {
throw err;
}
};
// Удаление товара
const deleteProduct = async (id) => {
try {
const response = await fetch(`${apiUrl}/shmots/${id}`, {
@@ -138,8 +134,7 @@ export const useProducts = () => {
throw err;
}
};
// Получение названий по ID
const getCategoryName = (categoryId) => {
const category = categories.find(cat => cat.id === categoryId);
return category ? category.name : 'Unknown';