feat: add page items
This commit is contained in:
@@ -8,6 +8,7 @@ import NotFoundPage from "./pages/NotFoundPage";
|
||||
import Contact from "./pages/Contact";
|
||||
import Cart from "./pages/Cart";
|
||||
import Profile from "./pages/Profile";
|
||||
import ItemPage from "./pages/ItemPage";
|
||||
|
||||
const PrivateRoute = ({ children }) => {
|
||||
const token = localStorage.getItem("token");
|
||||
@@ -37,6 +38,7 @@ export default function App() {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="items/:id" element={<ItemPage />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import Arrow from "../components/Arrow";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function CardItem({ item, onAddToCart }) {
|
||||
const navigate = useNavigate();
|
||||
const isPiece = item.unit_type === "piece";
|
||||
const step = isPiece ? 1 : 0.1;
|
||||
const minValue = isPiece ? 1 : 0.1;
|
||||
@@ -20,7 +22,10 @@ export default function CardItem({ item, onAddToCart }) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col text-white w-full">
|
||||
<div className="w-full rounded-md overflow-hidden bg-slate-800/60 flex h-[400px] md:h-[600px] flex-col items-center justify-center border border-slate-700/50 select-none">
|
||||
<div
|
||||
className="w-full rounded-md overflow-hidden bg-slate-800/60 flex h-[400px] md:h-[600px] flex-col items-center justify-center border border-slate-700/50 select-none cursor-pointer"
|
||||
onClick={() => navigate(`/items/${item.id}`)}
|
||||
>
|
||||
<img
|
||||
src={item.avatar_url}
|
||||
alt={item.name}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import PageLayout from "../components/PageLayout";
|
||||
import { getCart, removeFromCart, createOrder } from "../api/index";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function Cart() {
|
||||
const navigate = useNavigate();
|
||||
const [items, setItems] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -63,7 +66,8 @@ export default function Cart() {
|
||||
<img
|
||||
src={`/static/items/${ci.item.avatar_url}`}
|
||||
alt={ci.item.name}
|
||||
className="w-20 md:w-40 h-20 md:h-40 object-cover rounded-md shrink-0"
|
||||
className="w-20 md:w-40 h-20 md:h-40 object-cover rounded-md shrink-0 cursor-pointer"
|
||||
onClick={() => navigate(`/items/${ci.item.id}`)}
|
||||
/>
|
||||
<div className="grow">
|
||||
<p className="font-medium text-base md:text-lg">
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import PageLayout from "../components/PageLayout";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getItem, addToCart } from "../api/index";
|
||||
import Arrow from "../components/Arrow";
|
||||
|
||||
export default function ItemPage() {
|
||||
const { id } = useParams();
|
||||
const [item, setItem] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
getItem(id)
|
||||
.then((res) => setItem(res.data))
|
||||
.catch(() => { })
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
const isPiece = item?.unit_type === "piece";
|
||||
const step = isPiece ? 1 : 0.1;
|
||||
const minValue = isPiece ? 1 : 0.1;
|
||||
|
||||
useEffect(() => {
|
||||
if (item) setQuantity(isPiece ? 1 : 0.1);
|
||||
}, [item, isPiece]);
|
||||
|
||||
const handleIncrement = () => {
|
||||
setQuantity((prev) => parseFloat((prev + step).toFixed(3)));
|
||||
};
|
||||
const handleDecrement = () => {
|
||||
setQuantity((prev) =>
|
||||
prev > minValue ? parseFloat((prev - step).toFixed(3)) : minValue,
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddToCart = async () => {
|
||||
try {
|
||||
await addToCart({ item_id: item.id, quantity });
|
||||
alert(`${item.name} добавлен в корзину`);
|
||||
} catch {
|
||||
alert("Не удалось добавить товар в корзину");
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<p className="text-white p-6 md:p-16">Загрузка...</p>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<p className="text-white p-6 md:p-16">Товар не найден</p>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<main className="px-6 md:px-16 grow w-full mb-10 text-white">
|
||||
<div className="flex flex-col md:flex-row gap-8 md:gap-12 mt-6">
|
||||
{/* ЛЕВО — фото */}
|
||||
<div className="md:w-1/2">
|
||||
<img
|
||||
src={item.avatar_url}
|
||||
alt={item.name}
|
||||
className="w-full h-[300px] md:h-[500px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ПРАВО — всё инфо в одной колонке */}
|
||||
<div className="md:w-1/2 flex flex-col">
|
||||
<h1 className="text-2xl md:text-4xl font-medium uppercase mb-3">
|
||||
{item.name}
|
||||
</h1>
|
||||
|
||||
<p className="text-2xl md:text-3xl font-medium mb-6">
|
||||
{item.price} ₽
|
||||
<span className="text-base text-white/50"> / {item.unit}</span>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-2 text-white/70 text-sm md:text-base mb-8">
|
||||
<div className="flex justify-between border-b border-white/10 pb-2">
|
||||
<span className="text-white/50">Категория</span>
|
||||
<span>{item.category}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-white/10 pb-2">
|
||||
<span className="text-white/50">Город</span>
|
||||
<span>{item.city}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-white/10 pb-2">
|
||||
<span className="text-white/50">Тип</span>
|
||||
<span>{isPiece ? "Поштучно" : "На вес"}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-white/10 pb-2">
|
||||
<span className="text-white/50">В наличии</span>
|
||||
<span
|
||||
className={item.stock > 0 ? "text-green-400" : "text-red-400"}
|
||||
>
|
||||
{item.stock > 0 ? `${item.stock} ${item.unit}` : "нет"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6 mb-6">
|
||||
<span className="text-white/50 text-sm">Количество:</span>
|
||||
<button
|
||||
onClick={handleDecrement}
|
||||
className="w-10 h-10 flex items-center justify-center bg-white/10 hover:bg-white/20 rounded font-medium text-xl cursor-pointer select-none transition-colors"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span className="mx-2 font-mono text-sm min-w-20 text-center">
|
||||
{quantity} {item.unit}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleIncrement}
|
||||
className="w-10 h-10 flex items-center justify-center bg-white/10 hover:bg-white/20 rounded font-medium text-xl cursor-pointer select-none transition-colors"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-lg mb-6">
|
||||
Стоимость:{" "}
|
||||
<span className="font-medium">
|
||||
{parseFloat((quantity * item.price).toFixed(2))} ₽
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
disabled={item.stock <= 0}
|
||||
className="group flex items-center gap-4 text-base font-bold text-white cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>
|
||||
{item.stock > 0 ? "Добавить в корзину" : "Нет в наличии"}
|
||||
</span>
|
||||
<Arrow />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
@@ -97,7 +97,8 @@ export default function Profile() {
|
||||
<img
|
||||
src={`/static/items/${oi.item?.avatar_url}`}
|
||||
alt={oi.item?.name}
|
||||
className="w-10 h-10 object-cover rounded shrink-0"
|
||||
className="w-10 h-10 md:w-30 md:h-30 object-cover rounded shrink-0 cursor-pointer"
|
||||
onClick={() => navigate(`/items/${oi.item.id}`)}
|
||||
/>
|
||||
<span className="grow">{oi.item?.name}</span>
|
||||
<span className="whitespace-nowrap shrink-0">
|
||||
|
||||
Reference in New Issue
Block a user