Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
858bd39322 | ||
|
|
4defafe3e7 | ||
|
|
da97a99436 | ||
|
|
d44d30cb05 | ||
|
|
ebbfcd0e93 | ||
|
|
e005c61030 | ||
| cbda4dee82 | |||
| 038f60d61e | |||
| d1b2cea66c | |||
| 311491d554 | |||
| 5fa65ad591 | |||
|
|
9c80373c59 | ||
|
|
49fe7787f3 | ||
|
|
65407801d3 | ||
|
|
0f37299eab |
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Зависимости Node.js
|
||||
/node_modules/
|
||||
|
||||
# Результаты сборки Vite
|
||||
/dist/
|
||||
|
||||
# Логи
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
pnpm-debug.log*
|
||||
*.log
|
||||
|
||||
# Файлы окружения
|
||||
.env
|
||||
.env.*.local
|
||||
|
||||
# Кеши
|
||||
/.cache/
|
||||
.vite/
|
||||
|
||||
# IDE и редакторы
|
||||
.vscode/
|
||||
.idea/
|
||||
/*.sublime-workspace
|
||||
/*.sublime-project
|
||||
|
||||
# Системные файлы
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Отчеты об покрытии тестами
|
||||
/coverage/
|
||||
|
||||
# Порты и артефакты
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Прочее
|
||||
/.DS_Store
|
||||
32
Account.html
32
Account.html
@@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Интернет-магазин: ЛК</title>
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.jpg" alt="'Название магазина'">
|
||||
<a href="newSite.html"><h1><b>"Название магазина"</b></h1></a>
|
||||
<navbar>
|
||||
<p><a href="Basket.html">Корзина</a></p>
|
||||
<p><a href="Order.html">Заказы</a></p>
|
||||
<p><a href="Favorites.html">Избранное</a></p>
|
||||
<p><a href="Account.html">Личный кабинет</a></p>
|
||||
</navbar>
|
||||
</header>
|
||||
<div class="account">
|
||||
<img src="images/бананы.jpg">
|
||||
<h3>Имя Фамилия</h3>
|
||||
<p>Описание</p>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Помощь:</p>
|
||||
<p>8 (800)-555-35-35</p>
|
||||
<p>vk.com</p>
|
||||
<p>tg.me</p>
|
||||
<p>ozon-zon-zon@mail.joke</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
40
Basket.html
40
Basket.html
@@ -1,40 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Интернет-магазин: Корзина</title>
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.jpg" alt="'Название магазина'">
|
||||
<a href="newSite.html"><h1><b>"Название магазина"</b></h1></a>
|
||||
<navbar>
|
||||
<p><a href="Basket.html">Корзина</a></p>
|
||||
<p><a href="Order.html">Заказы</a></p>
|
||||
<p><a href="Favorites.html">Избранное</a></p>
|
||||
<p><a href="Account.html">Личный кабинет</a></p>
|
||||
</navbar>
|
||||
</header>
|
||||
<div class="basket">
|
||||
<h1>Корзина</h1>
|
||||
<ol>
|
||||
<li><img src="images/glasses.jpg">Очки<p>349 руб.</p></li>
|
||||
<li><img src="images/chery.jpg">Chery Tiggo 7 Pro Max 64gb 128mp<p>5 руб.</p></li>
|
||||
<li><img src="images/vanadiy.jpg">Ванадий<p>2099 руб.</p></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="buy">
|
||||
<a href="https://xn----7sbon6aucai8a.xn--p1ai/wa-data/public/shop/products/98/27/2798/images/6060/6060.970.jpg" target="_blank">
|
||||
Оплатить
|
||||
</a>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Помощь:</p>
|
||||
<p>8 (800)-555-35-35</p>
|
||||
<p>vk.com</p>
|
||||
<p>tg.me</p>
|
||||
<p>ozon-zon-zon@mail.joke</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Интернет-магазин: Избранное</title>
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.jpg" alt="'Название магазина'">
|
||||
<a href="newSite.html"><h1><b>"Название магазина"</b></h1></a>
|
||||
<navbar>
|
||||
<p><a href="Basket.html">Корзина</a></p>
|
||||
<p><a href="Order.html">Заказы</a></p>
|
||||
<p><a href="Favorites.html">Избранное</a></p>
|
||||
<p><a href="Account.html">Личный кабинет</a></p>
|
||||
</navbar>
|
||||
</header>
|
||||
<div class="favourites">
|
||||
<h2>Избранное</h2>
|
||||
<ul>
|
||||
<li><img src ="images/masha.jpg">Женщина<p>бесценна</p></li>
|
||||
<li><img src ="images/screwdriver.jpg">Отвертка<p>219 руб</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Помощь:</p>
|
||||
<p>8 (800)-555-35-35</p>
|
||||
<p>vk.com</p>
|
||||
<p>tg.me</p>
|
||||
<p>ozon-zon-zon@mail.joke</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
45
Order.html
45
Order.html
@@ -1,45 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Интернет-магазин: Заказы</title>
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.jpg" alt="'Название магазина'">
|
||||
<a href="newSite.html"><h1><b>"Название магазина"</b></h1></a>
|
||||
<navbar>
|
||||
<p><a href="Basket.html">Корзина</a></p>
|
||||
<p><a href="Order.html">Заказы</a></p>
|
||||
<p><a href="Favorites.html">Избранное</a></p>
|
||||
<p><a href="Account.html">Личный кабинет</a></p>
|
||||
</navbar>
|
||||
</header>
|
||||
<div class="orders"><h1>Заказы</h1></div>
|
||||
<div class="typeOfOrder">
|
||||
<div class="inProcess">
|
||||
<h2>В процессе</h2>
|
||||
<ul>
|
||||
<li><img src="images/spoon.jpg">Ложка</li>
|
||||
<li><img src="images/fork.jpg">Вилка</li>
|
||||
<li><img src="images/knife.jpg">Нож</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="done">
|
||||
<h2>Завершённые</h2>
|
||||
<ul>
|
||||
<li><img src="images/iron.jpg">Утюг</li>
|
||||
<li><img src="images/bananas.jpg">Бананы</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Помощь:</p>
|
||||
<p>8 (800)-555-35-35</p>
|
||||
<p>vk.com</p>
|
||||
<p>tg.me</p>
|
||||
<p>ozon-zon-zon@mail.joke</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
228
css/style.css
228
css/style.css
@@ -1,228 +0,0 @@
|
||||
body {
|
||||
background-color: #edf7f6;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 50px;
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header h1 a{
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
navbar {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 35px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.goods {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
|
||||
.goods p {
|
||||
text-align: center;
|
||||
width: 270px;
|
||||
height: 285px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.goods img {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
object-fit: cover;
|
||||
margin: 0 auto; /* Центрирование */
|
||||
}
|
||||
|
||||
.typeOfOrder {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.typeOfOrder h2{
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
width: 180px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.inProcess h2{
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.done h2{
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.basket {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.basket h1 {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
width: 400px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
font-size: large;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.basket p {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 10px;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.orders h1 {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.favourites p {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 10px;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*.buy {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: greenyellow;
|
||||
text-align: center;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
width: 200px; /* Фиксированный размер кнопки *
|
||||
height: 70px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.buy a {
|
||||
font-size: xx-large;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
width: 100%; /* Растягиваем ссылку на всю кнопку *
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}*/
|
||||
.buy {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: greenyellow;
|
||||
text-align: center;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
width: 200px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.buy a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-size: xx-large;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.buy:hover {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.account {
|
||||
text-align: center;
|
||||
margin: 50px auto;
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
border: 2px solid black;
|
||||
border-radius: 15px;
|
||||
background-color: #f9f9f9;
|
||||
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.account img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 20px; /* Закругленные углы */
|
||||
border: 3px solid black;
|
||||
}
|
||||
|
||||
.account h3 {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
font-size: x-large;
|
||||
}
|
||||
/*
|
||||
.dialogue {
|
||||
text-align: left;
|
||||
margin-top: 10px;
|
||||
font-size: 20px;
|
||||
}*/
|
||||
74
db.json
Normal file
74
db.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"id": "1a54",
|
||||
"name": "новый товар",
|
||||
"price": 102,
|
||||
"image": "images/fork.jpg"
|
||||
},
|
||||
{
|
||||
"id": "f4f8",
|
||||
"name": "товарчик",
|
||||
"price": 111,
|
||||
"image": "images/chery.jpg"
|
||||
},
|
||||
{
|
||||
"id": "0fda",
|
||||
"name": "ложка",
|
||||
"price": 48,
|
||||
"image": "images/bananas.jpg"
|
||||
}
|
||||
],
|
||||
"basket": [
|
||||
{
|
||||
"id": "0fda",
|
||||
"name": "ложка",
|
||||
"price": 48,
|
||||
"image": "images/bananas.jpg"
|
||||
}
|
||||
],
|
||||
"favorites": [
|
||||
{
|
||||
"id": "1a54",
|
||||
"name": "новый товар",
|
||||
"price": 102,
|
||||
"image": "images/fork.jpg"
|
||||
}
|
||||
],
|
||||
"orders": [
|
||||
{
|
||||
"id": "1",
|
||||
"items": [
|
||||
{
|
||||
"name": "Ложка",
|
||||
"image": "images/spoon.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Вилка",
|
||||
"image": "images/fork.jpg"
|
||||
}
|
||||
],
|
||||
"status": "in-process"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"items": [
|
||||
{
|
||||
"name": "Утюг",
|
||||
"image": "images/iron.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Бананы",
|
||||
"image": "images/bananas.jpg"
|
||||
}
|
||||
],
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"profile": {
|
||||
"id": 1,
|
||||
"firstName": "Иван",
|
||||
"lastName": "Иванов",
|
||||
"image": "images/бананы.jpg"
|
||||
}
|
||||
}
|
||||
BIN
images/gmail.png
Normal file
BIN
images/gmail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 781 B |
BIN
images/phone.png
Normal file
BIN
images/phone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 595 B |
BIN
images/telegram.png
Normal file
BIN
images/telegram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
images/vk.png
Normal file
BIN
images/vk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 715 B |
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>
|
||||
36
newSite.html
36
newSite.html
@@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Интернет-магазин</title>
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="images/logo.jpg" alt="'Название магазина'">
|
||||
<a href="newSite.html"><h1><b>"Название магазина"</b></h1></a>
|
||||
<navbar>
|
||||
<p><a href="Basket.html">Корзина</a></p>
|
||||
<p><a href="Order.html">Заказы</a></p>
|
||||
<p><a href="Favorites.html">Избранное</a></p>
|
||||
<p><a href="Account.html">Личный кабинет</a></p>
|
||||
</navbar>
|
||||
</header>
|
||||
<h2>Рекомендуемые товары:</h2>
|
||||
<div class="goods">
|
||||
<p><img src="images/iron.jpg">Утюг</p>
|
||||
<p><img src="images/child.jpg">Ребёнок</p>
|
||||
<p><img src="images/screwdriver.jpg">Отвертка</p>
|
||||
<p><img src="images/skateboard.jpg">Скейтборд</p>
|
||||
<p><img src="images/bananas.jpg">Бананы</p>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Помощь:</p>
|
||||
<p>8 (800)-555-35-35</p>
|
||||
<p>vk.com</p>
|
||||
<p>tg.me</p>
|
||||
<p>ozon-zon-zon@mail.joke</p>
|
||||
<p><img src="images/бананы.jpg"></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
6335
package-lock.json
generated
Normal file
6335
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "ip",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "npm-run-all --parallel start json-server",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"server": "http-server -p 3000 ./dist/",
|
||||
"prod": "npm-run-all build server",
|
||||
"lint": "eslint …",
|
||||
"json-server": "json-server --watch db.json --port 5000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"bootstrap": "5.3.3",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "10.0.2",
|
||||
"eslint-plugin-html": "8.1.2",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-prettier": "5.2.3",
|
||||
"http-server": "14.1.1",
|
||||
"json-server": "^1.0.0-beta.3",
|
||||
"npm-run-all": "4.1.5",
|
||||
"vite": "6.2.0"
|
||||
}
|
||||
}
|
||||
26
src/App.jsx
Normal file
26
src/App.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
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() {
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
29
src/components/Footer.jsx
Normal file
29
src/components/Footer.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
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>
|
||||
</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 ms-3" 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>
|
||||
);
|
||||
}
|
||||
22
src/components/ProductCard.jsx
Normal file
22
src/components/ProductCard.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ProductCard({ product, onEdit, onDelete, onAddToFavorites }) {
|
||||
return (
|
||||
<div className="col">
|
||||
<div className="card h-100">
|
||||
{product.image && (
|
||||
<img src={product.image} className="card-img-top" alt={product.name} style={{ height: 300, objectFit: 'cover' }} />
|
||||
)}
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">{product.name}</h5>
|
||||
<p className="card-text">Цена: {product.price} ₽</p>
|
||||
<button className="btn btn-sm btn-outline-primary me-2" onClick={() => onEdit(product)}>Изменить</button>
|
||||
<button className="btn btn-sm btn-outline-danger me-2" onClick={() => onDelete(product.id)}>Удалить</button>
|
||||
<button className="btn btn-sm btn-outline-success" onClick={() => onAddToFavorites(product)}>
|
||||
<i className="bi bi-heart"></i> В избранное
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
72
src/components/ProductForm.jsx
Normal file
72
src/components/ProductForm.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function ProductForm({ initial, onSave, onCancel }) {
|
||||
const [form, setForm] = useState({ name: '', price: '', image: '' });
|
||||
|
||||
useEffect(() => {
|
||||
if (initial) {
|
||||
setForm({
|
||||
name: initial.name || '',
|
||||
price: initial.price || '',
|
||||
image: initial.image || ''
|
||||
});
|
||||
} else {
|
||||
setForm({ name: '', price: '', image: '' });
|
||||
}
|
||||
}, [initial]);
|
||||
|
||||
const handleChange = e => setForm({ ...form, [e.target.name]: e.target.value });
|
||||
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
onSave({ ...initial, name: form.name, price: Number(form.price), image: form.image });
|
||||
setForm({ name: '', price: '', image: '' });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="card p-3 mb-4">
|
||||
<div className="mb-2">
|
||||
<label className="form-label">Название</label>
|
||||
<input
|
||||
name="name"
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="form-label">Цена</label>
|
||||
<input
|
||||
name="price"
|
||||
value={form.price}
|
||||
onChange={handleChange}
|
||||
type="number"
|
||||
required
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="form-label">Ссылка на картинку</label>
|
||||
<input
|
||||
name="image"
|
||||
value={form.image}
|
||||
onChange={handleChange}
|
||||
className="form-control"
|
||||
placeholder="Например: images/glasses.jpg или https://example.com/photo.jpg"
|
||||
/>
|
||||
</div>
|
||||
{form.image && (
|
||||
<div className="mb-2 text-center">
|
||||
<img
|
||||
src={form.image}
|
||||
alt="Превью"
|
||||
style={{ maxHeight: 120, objectFit: 'contain', maxWidth: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<button type="submit" className="btn btn-primary mb-2" style={{ width: 'auto' }}>Сохранить</button>
|
||||
<button type="button" className="btn btn-secondary" style={{ width: 'auto' }} onClick={onCancel}>Отмена</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
18
src/components/ProductList.jsx
Normal file
18
src/components/ProductList.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
export default function ProductList({ products, onEdit, onDelete, onAddToFavorites }) {
|
||||
return (
|
||||
<div className="row row-cols-1 row-cols-md-3 g-4">
|
||||
{products.map(prod => (
|
||||
<ProductCard
|
||||
key={prod.id}
|
||||
product={prod}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onAddToFavorites={onAddToFavorites}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/hooks/useBasket.jsx
Normal file
35
src/hooks/useBasket.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useBasket() {
|
||||
const [basket, setBasket] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/basket')
|
||||
.then(res => res.json())
|
||||
.then(setBasket);
|
||||
}, []);
|
||||
|
||||
const addToBasket = async (item) => {
|
||||
const res = await fetch('http://localhost:5000/basket', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(item)
|
||||
});
|
||||
const newItem = await res.json();
|
||||
setBasket([...basket, newItem]);
|
||||
};
|
||||
|
||||
const removeFromBasket = async (id) => {
|
||||
await fetch(`http://localhost:5000/basket/${id}`, { method: 'DELETE' });
|
||||
setBasket(basket.filter(item => item.id !== id));
|
||||
};
|
||||
|
||||
const clearBasket = async () => {
|
||||
for (let item of basket) {
|
||||
await fetch(`http://localhost:5000/basket/${item.id}`, { method: 'DELETE' });
|
||||
}
|
||||
setBasket([]);
|
||||
};
|
||||
|
||||
return { basket, addToBasket, removeFromBasket, clearBasket };
|
||||
}
|
||||
28
src/hooks/useFavorites.jsx
Normal file
28
src/hooks/useFavorites.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useFavorites() {
|
||||
const [favorites, setFavorites] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/favorites')
|
||||
.then(res => res.json())
|
||||
.then(setFavorites);
|
||||
}, []);
|
||||
|
||||
const addToFavorites = async (item) => {
|
||||
const res = await fetch('http://localhost:5000/favorites', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(item)
|
||||
});
|
||||
const newItem = await res.json();
|
||||
setFavorites([...favorites, newItem]);
|
||||
};
|
||||
|
||||
const removeFromFavorites = async (id) => {
|
||||
await fetch(`http://localhost:5000/favorites/${id}`, { method: 'DELETE' });
|
||||
setFavorites(favorites.filter(item => item.id !== id));
|
||||
};
|
||||
|
||||
return { favorites, addToFavorites, removeFromFavorites };
|
||||
}
|
||||
26
src/hooks/useOrders.jsx
Normal file
26
src/hooks/useOrders.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useOrders() {
|
||||
const [orders, setOrders] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/orders')
|
||||
.then(res => res.json())
|
||||
.then(setOrders);
|
||||
}, []);
|
||||
|
||||
const addOrder = async (order) => {
|
||||
const res = await fetch('http://localhost:5000/orders', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(order)
|
||||
});
|
||||
const newOrder = await res.json();
|
||||
setOrders([...orders, newOrder]);
|
||||
};
|
||||
|
||||
const inProcess = orders.filter(o => o.status === "in-process");
|
||||
const completed = orders.filter(o => o.status === "completed");
|
||||
|
||||
return { orders, addOrder, inProcess, completed };
|
||||
}
|
||||
33
src/hooks/useProducts.jsx
Normal file
33
src/hooks/useProducts.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function useProducts() {
|
||||
const [products, setProducts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/products')
|
||||
.then(res => res.json())
|
||||
.then(setProducts);
|
||||
}, []);
|
||||
|
||||
const add = async prod => {
|
||||
const res = await fetch('http://localhost:5000/products', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prod)
|
||||
});
|
||||
const newProd = await res.json();
|
||||
setProducts([...products, newProd]);
|
||||
};
|
||||
|
||||
const update = async prod => {
|
||||
await fetch(`http://localhost:5000/products/${prod.id}`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(prod)
|
||||
});
|
||||
setProducts(products.map(p => p.id === prod.id ? prod : p));
|
||||
};
|
||||
|
||||
const remove = async id => {
|
||||
await fetch(`http://localhost:5000/products/${id}`, { method: 'DELETE' });
|
||||
setProducts(products.filter(p => p.id !== id));
|
||||
};
|
||||
|
||||
return { products, add, update, remove };
|
||||
}
|
||||
24
src/hooks/useProfile.jsx
Normal file
24
src/hooks/useProfile.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useProfile() {
|
||||
const [profile, setProfile] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:5000/profile')
|
||||
.then(res => res.json())
|
||||
.then(setProfile);
|
||||
}, []);
|
||||
|
||||
const updateProfile = async (newProfile) => {
|
||||
// PATCH или PUT — по ситуации
|
||||
const res = await fetch('http://localhost:5000/profile', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newProfile)
|
||||
});
|
||||
const updated = await res.json();
|
||||
setProfile(updated);
|
||||
};
|
||||
|
||||
return { profile, updateProfile };
|
||||
}
|
||||
8
src/index.jsx
Normal file
8
src/index.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
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 />);
|
||||
76
src/pages/AccountPage.jsx
Normal file
76
src/pages/AccountPage.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useState } from "react";
|
||||
import useProfile from "../hooks/useProfile";
|
||||
|
||||
export default function AccountPage() {
|
||||
const { profile, updateProfile } = useProfile();
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
const [form, setForm] = useState(null);
|
||||
|
||||
if (!profile) {
|
||||
return <div className="text-center">Загрузка...</div>;
|
||||
}
|
||||
|
||||
const handleEdit = () => {
|
||||
setForm(profile);
|
||||
setShowEdit(true);
|
||||
};
|
||||
|
||||
const handleChange = e => {
|
||||
setForm({ ...form, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleSave = async e => {
|
||||
e.preventDefault();
|
||||
await updateProfile(form);
|
||||
setShowEdit(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => setShowEdit(false);
|
||||
|
||||
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" onClick={handleEdit}>
|
||||
<i className="bi bi-pencil"></i> Редактировать профиль
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showEdit && (
|
||||
<div className="modal d-block" tabIndex="-1" style={{ background: 'rgba(0,0,0,0.3)' }}>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<form onSubmit={handleSave}>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Редактировать профиль</h5>
|
||||
<button type="button" className="btn-close" onClick={handleCancel}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="mb-2">
|
||||
<label className="form-label">Имя</label>
|
||||
<input className="form-control" name="firstName" value={form.firstName} onChange={handleChange} required />
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="form-label">Фамилия</label>
|
||||
<input className="form-control" name="lastName" value={form.lastName} onChange={handleChange} required />
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="form-label">URL аватара</label>
|
||||
<input className="form-control" name="image" value={form.image} onChange={handleChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="submit" className="btn btn-success">Сохранить</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={handleCancel}>Отмена</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/pages/BasketPage.jsx
Normal file
45
src/pages/BasketPage.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import useBasket from "../hooks/useBasket";
|
||||
import useOrders from "../hooks/useOrders";
|
||||
|
||||
export default function BasketPage() {
|
||||
|
||||
const { basket, removeFromBasket, clearBasket } = useBasket();
|
||||
const { addOrder } = useOrders();
|
||||
|
||||
const handleCheckout = () => {
|
||||
if (basket.length === 0) {
|
||||
alert("Корзина пуста");
|
||||
return;
|
||||
}
|
||||
// Сформируем заказ
|
||||
addOrder({
|
||||
items: basket,
|
||||
status: "in-process"
|
||||
});
|
||||
clearBasket(); // Очищаем корзину
|
||||
alert("Заказ оформлен!");
|
||||
};
|
||||
|
||||
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 w-100 ${basket.length === 0 ? "btn-secondary" : "btn-success"}`} onClick={handleCheckout}>
|
||||
Оплатить {}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
34
src/pages/FavoritesPage.jsx
Normal file
34
src/pages/FavoritesPage.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import useBasket from "../hooks/useBasket";
|
||||
import useFavorites from "../hooks/useFavorites";
|
||||
|
||||
export default function FavoritesPage() {
|
||||
const { favorites, removeFromFavorites } = useFavorites();
|
||||
const { basket, addToBasket } = useBasket();
|
||||
|
||||
const handleAddToBasket = (item) => {
|
||||
addToBasket(item);
|
||||
removeFromFavorites(item.id);
|
||||
};
|
||||
|
||||
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 className="btn btn-success me-2" onClick={() => handleAddToBasket(item)}>В корзину</button>
|
||||
<button onClick={() => removeFromFavorites(item.id)} className="btn btn-danger">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
src/pages/MainPage.jsx
Normal file
42
src/pages/MainPage.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useState } from "react";
|
||||
import useProducts from "../hooks/useProducts";
|
||||
import useFavorites from "../hooks/useFavorites";
|
||||
import ProductList from "../components/ProductList";
|
||||
import ProductForm from "../components/ProductForm";
|
||||
|
||||
export default function MainPage() {
|
||||
const { products, add, update, remove } = useProducts();
|
||||
const { favorites, addToFavorites } = useFavorites();
|
||||
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);
|
||||
|
||||
const handleAddToFavorites = product => {
|
||||
if (!favorites.some(fav => fav.id === product.id)) {
|
||||
addToFavorites(product);
|
||||
}
|
||||
else alert('Товар уже в избранном!');
|
||||
};
|
||||
|
||||
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}
|
||||
onAddToFavorites={handleAddToFavorites}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
58
src/pages/OrderPage.jsx
Normal file
58
src/pages/OrderPage.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import useOrders from "../hooks/useOrders";
|
||||
|
||||
export default function OrderPage() {
|
||||
const { inProcess, completed } = useOrders();
|
||||
|
||||
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">
|
||||
{inProcess.length === 0 ? (
|
||||
<div className="text-center text-muted">Нет заказов</div>
|
||||
) : (
|
||||
inProcess.map(order => (
|
||||
<ul className="list-group list-group-flush mb-3" key={order.id}>
|
||||
{order.items.map((item, idx) => (
|
||||
<li className="list-group-item d-flex align-items-center mb-2" key={idx}>
|
||||
<img src={item.image} className="me-2" style={{ width: 120, height: 120, objectFit: "cover" }} 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">
|
||||
{completed.length === 0 ? (
|
||||
<div className="text-center text-muted">Нет завершённых заказов</div>
|
||||
) : (
|
||||
completed.map(order => (
|
||||
<ul className="list-group list-group-flush mb-3" key={order.id}>
|
||||
{order.items.map((item, idx) => (
|
||||
<li className="list-group-item d-flex align-items-center mb-2" key={idx}>
|
||||
<img src={item.image} className="me-2" style={{ width: 120, height: 120, objectFit: "cover" }} alt={item.name} /> {item.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
vite.config.js
Normal file
28
vite.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from "vite";
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
root: '.',
|
||||
plugins: [react()],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, "newSite.html"),
|
||||
basketPage: resolve(__dirname, "Basket.html"),
|
||||
favouritesPage: resolve(__dirname, "Favorites.html"),
|
||||
orderPage: resolve(__dirname, "Order.html"),
|
||||
accountPage: resolve(__dirname, "Account.html"),
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
open: '/',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
BIN
Отчет2.docx
Normal file
BIN
Отчет2.docx
Normal file
Binary file not shown.
BIN
Отчет3.docx
Normal file
BIN
Отчет3.docx
Normal file
Binary file not shown.
BIN
Отчет4.docx
Normal file
BIN
Отчет4.docx
Normal file
Binary file not shown.
BIN
Отчет5.docx
Normal file
BIN
Отчет5.docx
Normal file
Binary file not shown.
BIN
Отчет6.docx
Normal file
BIN
Отчет6.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user