lab_6_start

This commit is contained in:
2025-05-23 21:47:29 +02:00
parent d1b2cea66c
commit 038f60d61e
13 changed files with 339 additions and 44 deletions

15
index.html Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Интернет-магазин</title>
</head>
<body>
<main class="container">
<h2 class="text-center my-3">Рекомендуемые товары:</h2>
<div id="root"></div>
</main>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>

56
package-lock.json generated
View File

@@ -12,7 +12,8 @@
"bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.4.1",
@@ -2152,6 +2153,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
@@ -5107,6 +5117,44 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
"license": "MIT",
"dependencies": {
"react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -5411,6 +5459,12 @@
"semver": "bin/semver.js"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",

View File

@@ -18,7 +18,8 @@
"bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.4.1",

View File

@@ -1,28 +1,26 @@
import React, { useState } from 'react';
import useProducts from './hooks/useProducts';
import ProductList from './components/ProductList';
import ProductForm from './components/ProductForm';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
import MainPage from "./pages/MainPage";
import BasketPage from "./pages/BasketPage";
import FavoritesPage from "./pages/FavoritesPage";
import OrderPage from "./pages/OrderPage";
import AccountPage from "./pages/AccountPage";
export default function App() {
const { products, add, update, remove } = useProducts();
const [editing, setEditing] = useState(null);
const [showForm, setShowForm] = useState(false);
const handleAdd = () => { setEditing(null); setShowForm(true); };
const handleEdit = prod => { setEditing(prod); setShowForm(true); };
const handleDelete = id => remove(id);
const handleSave = prod => {
editing ? update({ ...prod, id: editing.id }) : add(prod);
setShowForm(false);
};
const handleCancel = () => setShowForm(false);
return (
<div className="container my-4">
{/* <h1 className="mb-4">Каталог товаров</h1> */}
<button className="btn btn-success mb-3" onClick={handleAdd}>Добавить товар</button>
{showForm && <ProductForm initial={editing} onSave={handleSave} onCancel={handleCancel} />}
<ProductList products={products} onEdit={handleEdit} onDelete={handleDelete} />
</div>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/basket" element={<BasketPage />} />
<Route path="/favorites" element={<FavoritesPage />} />
<Route path="/orders" element={<OrderPage />} />
<Route path="/account" element={<AccountPage />} />
</Routes>
<Footer />
</BrowserRouter>
);
}

32
src/components/Footer.jsx Normal file
View File

@@ -0,0 +1,32 @@
import React from "react";
export default function Footer() {
return (
<footer className="container mt-5">
<div className="bg-light p-4">
<h5>Помощь:</h5>
<div className="d-flex flex-wrap">
<div className="d-flex align-items-center me-4 mb-3">
<i className="bi bi-telephone-fill me-2"></i>
<a href="#" className="text-decoration-none text-dark">8 (800)-555-35-35</a>
</div>
<div className="d-flex align-items-center me-4 mb-3">
<img src="images/vk.png" alt="VK" className="me-2" style={{ width: 24, height: 24 }} />
<a href="https://vk.com/howmakesite_nn?from=search" target="_blank" className="text-decoration-none text-dark">vk.com</a>
</div>
<div className="d-flex align-items-center me-4 mb-3">
<img src="images/telegram.png" alt="Telegram" className="me-2" style={{ width: 24, height: 24 }} />
<a href="#" className="text-decoration-none text-dark">tg.me</a>
</div>
<div className="d-flex align-items-center mb-3">
<img src="images/gmail.png" alt="Gmail" className="me-2" style={{ width: 24, height: 24 }} />
<a href="mailto:ozon-zon-zon@mail.joke" className="text-decoration-none text-dark">ozon-zon-zon@mail.joke</a>
</div>
</div>
</div>
<div className="text-center mt-4">
<img src="images/бананы.jpg" alt="Бананы" className="img-fluid" />
</div>
</footer>
);
}

28
src/components/Header.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React from "react";
import { Link } from "react-router-dom";
export default function Header() {
return (
<header>
<div className="d-block mt-3 ms-3">
<img src="images/logo.jpg" alt="Название магазина" className="me-3" style={{ width: 200, height: "auto" }} />
<Link to="/" className="text-decoration-none text-dark m-1">
<h1 className="display-4 h3 mt-3"><b>Название магазина</b></h1>
</Link>
</div>
<nav>
<div className="dropdown">
<button className="btn btn-primary dropdown-toggle" type="button" id="navigationDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Навигация
</button>
<ul className="dropdown-menu" aria-labelledby="navigationDropdown">
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/account">Личный кабинет<i className="bi bi-person-circle ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/basket">Корзина<i className="bi bi-cart4 ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/orders">Заказы<i className="bi bi-receipt ms-2"></i></Link></li>
<li><Link className="dropdown-item d-flex justify-content-between align-items-center" to="/favorites">Избранное<i className="bi bi-heart-fill ms-2"></i></Link></li>
</ul>
</div>
</nav>
</header>
);
}

View File

@@ -1,7 +1,8 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import 'bootstrap/dist/css/bootstrap.min.css' // опционально
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
const container = document.getElementById('root')
createRoot(container).render(<App />)
const container = document.getElementById("root");
createRoot(container).render(<App />);

28
src/pages/AccountPage.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React, { useState } from "react";
// import useProfile from "../hooks/useProfile";
export default function AccountPage() {
// const { profile, updateProfile } = useProfile();
// Пример локального состояния
const [profile, setProfile] = useState({
firstName: "Иван",
lastName: "Иванов",
image: "images/бананы.jpg"
});
// Для модального окна и формы редактирования можно реализовать отдельный компонент
return (
<div className="container mt-5">
<div className="card text-center mx-auto" style={{ maxWidth: 400, maxHeight: 400 }}>
<img src={profile.image} className="card-img-top" alt="Профиль" style={{ width: "100%", height: 300, objectFit: "cover" }} />
<div className="card-body">
<h3 className="card-title">{profile.firstName} {profile.lastName}</h3>
<button className="btn btn-primary">
<i className="bi bi-pencil"></i> Редактировать профиль
</button>
</div>
</div>
</div>
);
}

36
src/pages/BasketPage.jsx Normal file
View File

@@ -0,0 +1,36 @@
import React from "react";
// импортируй кастомный хук для корзины, который ты сам реализуешь
// import useBasket from "../hooks/useBasket";
export default function BasketPage() {
// const { basket, removeFromBasket, checkout } = useBasket();
// Ниже пример статики, замени на динамику после реализации useBasket
const basket = [
{ id: 1, name: "Очки", price: 349, image: "images/glasses.jpg" },
{ id: 2, name: "Chery Tiggo 7 Pro Max", price: 5, image: "images/chery.jpg" },
{ id: 3, name: "Ванадий", price: 2099, image: "images/vanadiy.jpg" },
];
return (
<main className="container d-flex justify-content-center align-items-center" style={{ minHeight: "60vh" }}>
<div className="card p-4 shadow" style={{ minWidth: "60vw" }}>
<h2 className="text-center">Корзина</h2>
<ul className="list-group list-group-flush">
{basket.map(item => (
<li className="list-group-item d-flex align-items-center" key={item.id}>
<img src={item.image} alt={item.name} style={{ width: 100, height: 100 }} className="me-3" />
{item.name} <span className="ms-auto">{item.price} руб.</span>
{/* <button onClick={() => removeFromBasket(item.id)} className="btn btn-danger ms-2">Удалить</button> */}
</li>
))}
</ul>
<div className="text-center mt-3">
<button className="btn btn-success w-100">
Оплатить
</button>
</div>
</div>
</main>
);
}

View File

@@ -0,0 +1,32 @@
import React from "react";
// import useFavorites from "../hooks/useFavorites";
export default function FavoritesPage() {
// const { favorites, removeFromFavorites } = useFavorites();
// Ниже пример статических карточек
const favorites = [
{ id: 1, name: "Женщина", price: "бесценна", image: "images/masha.jpg" },
{ id: 2, name: "Отвертка", price: "219 руб", image: "images/screwdriver.jpg" },
];
return (
<div className="container mt-4">
<h2 className="mb-4">Избранное</h2>
<div className="row row-cols-1 row-cols-md-2 g-4">
{favorites.map(item => (
<div className="col" key={item.id}>
<div className="card mx-auto" style={{ width: "70%" }}>
<img src={item.image} className="card-img-top" alt={item.name} style={{ width: "100%", height: 300, objectFit: "cover" }} />
<div className="card-body text-center">
<h5 className="card-title">{item.name}</h5>
<p className="card-text">{item.price}</p>
{/* <button onClick={() => removeFromFavorites(item.id)} className="btn btn-danger">Удалить</button> */}
</div>
</div>
</div>
))}
</div>
</div>
);
}

28
src/pages/MainPage.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React, { useState } from "react";
import useProducts from "../hooks/useProducts";
import ProductList from "../components/ProductList";
import ProductForm from "../components/ProductForm";
export default function MainPage() {
const { products, add, update, remove } = useProducts();
const [editing, setEditing] = useState(null);
const [showForm, setShowForm] = useState(false);
const handleAdd = () => { setEditing(null); setShowForm(true); };
const handleEdit = prod => { setEditing(prod); setShowForm(true); };
const handleDelete = id => remove(id);
const handleSave = prod => {
editing ? update({ ...prod, id: editing.id }) : add(prod);
setShowForm(false);
};
const handleCancel = () => setShowForm(false);
return (
<main className="container my-4">
<button className="btn btn-success mb-3" onClick={handleAdd}>Добавить товар</button>
{showForm && <ProductForm initial={editing} onSave={handleSave} onCancel={handleCancel} />}
<h2 className="text-center my-3">Рекомендуемые товары:</h2>
<ProductList products={products} onEdit={handleEdit} onDelete={handleDelete} />
</main>
);
}

57
src/pages/OrderPage.jsx Normal file
View File

@@ -0,0 +1,57 @@
import React from "react";
// import useOrders from "../hooks/useOrders";
export default function OrderPage() {
// const { inProcess, completed } = useOrders();
// Пример статики
const inProcess = [
{ id: 1, name: "Ложка", image: "images/spoon.jpg" },
{ id: 2, name: "Вилка", image: "images/fork.jpg" },
{ id: 3, name: "Нож", image: "images/knife.jpg" },
];
const completed = [
{ id: 4, name: "Утюг", image: "images/iron.jpg" },
{ id: 5, name: "Бананы", image: "images/bananas.jpg" },
];
return (
<div className="container mt-4">
<h1 className="text-center">Заказы</h1>
<div className="row">
<div className="col-md-6">
<div className="card shadow-sm">
<div className="card-header bg-warning text-dark">
<h2 className="h5 m-0">В процессе</h2>
</div>
<div className="card-body">
<ul className="list-group list-group-flush">
{inProcess.map(item => (
<li className="list-group-item d-flex align-items-center mb-2" key={item.id}>
<img src={item.image} className="me-2" style={{ minWidth: 90 }} alt={item.name} /> {item.name}
</li>
))}
</ul>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card shadow-sm">
<div className="card-header bg-success text-white">
<h2 className="h5 m-0">Завершённые</h2>
</div>
<div className="card-body">
<ul className="list-group list-group-flush">
{completed.map(item => (
<li className="list-group-item d-flex align-items-center mb-2" key={item.id}>
<img src={item.image} className="me-2" style={{ minWidth: 90 }} alt={item.name} /> {item.name}
</li>
))}
</ul>
</div>
</div>
</div>
</div>
</div>
);
}