lab_6_start
This commit is contained in:
15
index.html
Normal file
15
index.html
Normal 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>
|
||||
15
newSite.html
15
newSite.html
@@ -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
56
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
40
src/App.jsx
40
src/App.jsx
@@ -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
32
src/components/Footer.jsx
Normal 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
28
src/components/Header.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
28
src/pages/AccountPage.jsx
Normal 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
36
src/pages/BasketPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
src/pages/FavoritesPage.jsx
Normal file
32
src/pages/FavoritesPage.jsx
Normal 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
28
src/pages/MainPage.jsx
Normal 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
57
src/pages/OrderPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user