registration, login, admin panel
19
src/App.js
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import Header from './components/Header';
|
||||
import HomePage from './components/HomePage';
|
||||
import LoginPage from './components/LoginPage';
|
||||
import RegisterPage from './components/RegisterPage';
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import Header from "./components/Header";
|
||||
import HomePage from "./components/HomePage";
|
||||
import LoginPage from "./components/LoginPage";
|
||||
import RegisterPage from "./components/RegisterPage";
|
||||
import CategoriesPage from "./components/CategoriesPage";
|
||||
import AdminPage from "./components/AdminPage"; // 1. Импортируем компонент AdminPage
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -15,7 +17,8 @@ function App() {
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
{/* Другие маршруты */}
|
||||
<Route path="/categories" element={<CategoriesPage />} />
|
||||
<Route path="/admin" element={<AdminPage />} /> {/* 2. Добавляем Route для AdminPage */}
|
||||
</Routes>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
||||
163
src/components/AdminPage.css
Normal file
@@ -0,0 +1,163 @@
|
||||
.admin-page {
|
||||
font-family: "Arial", sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f9f5f0;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.admin-page h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
color: #8b4513;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recipe-form {
|
||||
background-color: #fff8f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #d3b48c;
|
||||
}
|
||||
|
||||
.recipe-form h2 {
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 15px;
|
||||
color: #8b4513;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recipe-form label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recipe-form input[type="text"],
|
||||
.recipe-form textarea {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #d3b48c;
|
||||
font-size: 1em;
|
||||
color: #543d2d;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.recipe-form button {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.recipe-form button:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
.recipe-form input[type="text"],
|
||||
.recipe-form textarea,
|
||||
.recipe-form button,
|
||||
.recipe-form label {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.recipe-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #fff8f0;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #d3b48c;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recipe-table h2 {
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 15px;
|
||||
color: #8b4513;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recipe-table th,
|
||||
.recipe-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #d3b48c;
|
||||
}
|
||||
|
||||
.recipe-table th {
|
||||
background-color: #e6b88a;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recipe-table tbody tr:nth-child(odd) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.recipe-table tbody tr:hover {
|
||||
background-color: #f2dfd1;
|
||||
}
|
||||
|
||||
.recipe-table button {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.recipe-table button:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
.recipe-table input[type="text"],
|
||||
.recipe-table textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #d3b48c;
|
||||
font-size: 1em;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-page {
|
||||
padding: 10px;
|
||||
}
|
||||
.admin-page h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
.recipe-form {
|
||||
padding: 15px;
|
||||
}
|
||||
.recipe-table th,
|
||||
.recipe-table td {
|
||||
padding: 8px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.recipe-table button {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
204
src/components/AdminPage.js
Normal file
@@ -0,0 +1,204 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./AdminPage.css";
|
||||
|
||||
function AdminPage() {
|
||||
const navigate = useNavigate();
|
||||
const [recipes, setRecipes] = useState([]);
|
||||
const [newRecipe, setNewRecipe] = useState({
|
||||
name: "",
|
||||
ingredients: "",
|
||||
instructions: "",
|
||||
});
|
||||
const [editingRecipeId, setEditingRecipeId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
navigate("/login");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
//загр рецептов
|
||||
useEffect(() => {
|
||||
const storedRecipes = localStorage.getItem("recipes");
|
||||
if (storedRecipes) {
|
||||
setRecipes(JSON.parse(storedRecipes));
|
||||
}
|
||||
}, []);
|
||||
|
||||
//сохр рецептов при изменении
|
||||
useEffect(() => {
|
||||
localStorage.setItem("recipes", JSON.stringify(recipes));
|
||||
}, [recipes]);
|
||||
|
||||
const handleInputChange = (e, recipeId = null) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
if (recipeId) {
|
||||
setRecipes((prevRecipes) =>
|
||||
prevRecipes.map((recipe) =>
|
||||
recipe.id === recipeId ? { ...recipe, [name]: value } : recipe
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setNewRecipe((prevState) => ({
|
||||
...prevState,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const addRecipe = (e) => {
|
||||
e.preventDefault();
|
||||
if (newRecipe.name && newRecipe.ingredients && newRecipe.instructions) {
|
||||
const newRecipeWithId = { ...newRecipe, id: Date.now() };
|
||||
setRecipes([...recipes, newRecipeWithId]);
|
||||
setNewRecipe({ name: "", ingredients: "", instructions: "" });
|
||||
} else {
|
||||
alert("Пожалуйста, заполните все поля.");
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRecipe = (recipeId) => {
|
||||
if (window.confirm("Вы уверены, что хотите удалить этот рецепт?")) {
|
||||
const updatedRecipes = recipes.filter((recipe) => recipe.id !== recipeId);
|
||||
setRecipes(updatedRecipes);
|
||||
}
|
||||
};
|
||||
|
||||
const startEditing = (recipeId) => {
|
||||
setEditingRecipeId(recipeId);
|
||||
};
|
||||
|
||||
const stopEditing = () => {
|
||||
setEditingRecipeId(null);
|
||||
};
|
||||
|
||||
const saveRecipe = (recipeId) => {
|
||||
const recipeIndex = recipes.findIndex(recipe => recipe.id === recipeId);
|
||||
|
||||
if (recipeIndex !== -1) {
|
||||
const updatedRecipes = [...recipes];
|
||||
updatedRecipes[recipeIndex] = {
|
||||
...updatedRecipes[recipeIndex],
|
||||
name: updatedRecipes[recipeIndex].name,
|
||||
ingredients: updatedRecipes[recipeIndex].ingredients,
|
||||
instructions: updatedRecipes[recipeIndex].instructions
|
||||
};
|
||||
setRecipes(updatedRecipes);
|
||||
localStorage.setItem("recipes", JSON.stringify(updatedRecipes));
|
||||
}
|
||||
stopEditing();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<h1>Админ-панель</h1>
|
||||
|
||||
<div className="recipe-form">
|
||||
<h2>Добавить рецепт</h2>
|
||||
<form onSubmit={addRecipe}>
|
||||
<label>Название:</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={newRecipe.name}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
||||
<label>Ингредиенты:</label>
|
||||
<textarea
|
||||
name="ingredients"
|
||||
value={newRecipe.ingredients}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
||||
<label>Инструкции:</label>
|
||||
<textarea
|
||||
name="instructions"
|
||||
value={newRecipe.instructions}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
||||
<button type="submit">Добавить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="recipe-table">
|
||||
<h2>Список рецептов</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Ингредиенты</th>
|
||||
<th>Инструкции</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recipes.map((recipe) => (
|
||||
<tr key={recipe.id}>
|
||||
<td>
|
||||
{editingRecipeId === recipe.id ? (
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={recipe.name}
|
||||
onChange={(e) => handleInputChange(e, recipe.id)}
|
||||
/>
|
||||
) : (
|
||||
recipe.name
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{editingRecipeId === recipe.id ? (
|
||||
<textarea
|
||||
name="ingredients"
|
||||
value={recipe.ingredients}
|
||||
onChange={(e) => handleInputChange(e, recipe.id)}
|
||||
/>
|
||||
) : (
|
||||
recipe.ingredients
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{editingRecipeId === recipe.id ? (
|
||||
<textarea
|
||||
name="instructions"
|
||||
value={recipe.instructions}
|
||||
onChange={(e) => handleInputChange(e, recipe.id)}
|
||||
/>
|
||||
) : (
|
||||
recipe.instructions
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{editingRecipeId === recipe.id ? (
|
||||
<>
|
||||
<button onClick={() => saveRecipe(recipe.id)}>
|
||||
Сохранить
|
||||
</button>
|
||||
<button onClick={stopEditing}>Отменить</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button onClick={() => startEditing(recipe.id)}>
|
||||
Редактировать
|
||||
</button>
|
||||
<button onClick={() => deleteRecipe(recipe.id)}>
|
||||
Удалить
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminPage;
|
||||
57
src/components/CategoriesPage.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.categories-page {
|
||||
padding: 40px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.categories-page h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: #8b4513;
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.category-card img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.category-card h3 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.category-card p {
|
||||
font-size: 1em;
|
||||
line-height: 1.6;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: linear-gradient(to bottom right, #fff, #f2f2f2);
|
||||
}
|
||||
55
src/components/CategoriesPage.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "./CategoriesPage.css";
|
||||
|
||||
function CategoriesPage() {
|
||||
const categories = [
|
||||
{
|
||||
id: "salads",
|
||||
name: "Салаты",
|
||||
imageUrl: "",
|
||||
description: "Вкусные и полезные салаты на любой вкус.",
|
||||
},
|
||||
{
|
||||
id: "soups",
|
||||
name: "Супы",
|
||||
imageUrl: "",
|
||||
description: "Согревающие и сытные супы.",
|
||||
},
|
||||
{
|
||||
id: "main-courses",
|
||||
name: "Основные блюда",
|
||||
imageUrl: "",
|
||||
description: "Разнообразие основных блюд для всей семьи.",
|
||||
},
|
||||
{
|
||||
id: "desserts",
|
||||
name: "Десерты",
|
||||
imageUrl: "",
|
||||
description: "Сладкие угощения для сладкоежек.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="categories-page">
|
||||
<h1>Категории рецептов</h1>
|
||||
<div className="category-grid">
|
||||
{categories.map((category) => (
|
||||
<Link
|
||||
to={`/category/${category.id}`}
|
||||
key={category.id}
|
||||
className="category-card"
|
||||
>
|
||||
{category.imageUrl && (
|
||||
<img src={category.imageUrl} alt={category.name} />
|
||||
)}
|
||||
<h3>{category.name}</h3>
|
||||
{category.description && <p>{category.description}</p>}{" "}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoriesPage;
|
||||
@@ -1,93 +1,105 @@
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
background-color: #a8571d;
|
||||
color: #f9f5f0;
|
||||
padding: 1.2rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#logo a {
|
||||
}
|
||||
|
||||
#logo a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
color: #f9f5f0;
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
nav li {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
nav li {
|
||||
margin-left: 1.2rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: #f2dfd1;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
#search-form {
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#search-form button {
|
||||
background-color: #e44d26;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
padding: 0.6rem;
|
||||
border: 1px solid #d3b48c;
|
||||
border-radius: 6px;
|
||||
margin-right: 0.6rem;
|
||||
background-color: #fff8f0;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
#search-input::placeholder {
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
#search-form button {
|
||||
background-color: #e67e22;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#search-form button:hover {
|
||||
background-color: #d13d17;
|
||||
}
|
||||
|
||||
#auth a {
|
||||
margin-left: 1rem;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#search-form button:hover {
|
||||
background-color: #d35400;
|
||||
}
|
||||
|
||||
#auth a {
|
||||
margin-left: 1.2rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Мобильная адаптивность (пример) */
|
||||
@media (max-width: 768px) {
|
||||
color: #f2dfd1;
|
||||
}
|
||||
|
||||
#auth a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
nav li {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './Header.css';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "./Header.css";
|
||||
import logImage from "../img/log.png";
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
@@ -8,22 +9,37 @@ function Header() {
|
||||
<div className="container">
|
||||
<div id="logo">
|
||||
<Link to="/">
|
||||
<img src="" alt="Логотип сайта" width="100" />
|
||||
<img src={logImage} alt="Логотип сайта" width="80" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><Link to="/">Главная</Link></li>
|
||||
<li><Link to="/categories">Категории</Link></li>
|
||||
<li>
|
||||
<Link to="/">Главная</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/categories">Категории</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/admin">Админ-панель</Link>
|
||||
</li>
|
||||
<li>
|
||||
<form id="search-form">
|
||||
<input type="text" id="search-input" placeholder="Поиск рецептов" />
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
placeholder="Поиск рецептов"
|
||||
/>
|
||||
<button type="submit">Найти</button>
|
||||
</form>
|
||||
</li>
|
||||
<li><Link to="/popular">Популярные рецепты</Link></li>
|
||||
<li><Link to="/contact">Контакты</Link></li>
|
||||
<li>
|
||||
<Link to="/popular">Популярные рецепты</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/contact">Контакты</Link>
|
||||
</li>
|
||||
<li>
|
||||
<div id="auth">
|
||||
<Link to="/login">Вход</Link>
|
||||
@@ -37,4 +53,4 @@ function Header() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
||||
203
src/components/HomePage.css
Normal file
@@ -0,0 +1,203 @@
|
||||
.home-page {
|
||||
font-family: "Arial", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f9f5f0;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 60px 20px;
|
||||
color: #8b4513;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.hero-image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
width: 40%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3.5em;
|
||||
margin-bottom: 20px;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.3em;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
padding: 14px 28px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 7px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
.categories {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.categories h2 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 30px;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.category-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
display: block;
|
||||
width: 220px;
|
||||
padding: 25px;
|
||||
background-color: #fff8f0;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: #543d2d;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #d3b48c;
|
||||
}
|
||||
|
||||
.category-card img {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
border-radius: 7px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.category-card span {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.featured-recipes {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
background-color: #f2dfd1;
|
||||
}
|
||||
|
||||
.featured-recipes h2 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 30px;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.recipe-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.recipe-card {
|
||||
width: 320px;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recipe-card img {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.recipe-card h3 {
|
||||
padding: 12px 22px;
|
||||
margin: 0;
|
||||
font-size: 1.6em;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.recipe-card p {
|
||||
padding: 0 22px;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.5;
|
||||
color: #665140;
|
||||
}
|
||||
|
||||
.recipe-card a {
|
||||
display: block;
|
||||
padding: 12px 22px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
transition: background-color 0.3s ease;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recipe-card a:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
padding: 40px 15px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: 3em;
|
||||
}
|
||||
.category-grid {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.category-card {
|
||||
width: 90%;
|
||||
}
|
||||
.recipe-grid {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.recipe-card {
|
||||
width: 90%;
|
||||
}
|
||||
.hero-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
.hero-image, .hero-text {
|
||||
width: 100%;
|
||||
}
|
||||
.hero-image {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import './HomePage.css';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "./HomePage.css";
|
||||
import SoupImage from "../img/Soup.jpg";
|
||||
import BasicImage from "../img/Basic.jpg";
|
||||
import DessertImage from "../img/Dessert.jpg";
|
||||
import SaladImage from "../img/Salad.jpg";
|
||||
import SolyankaImage from "../img/Solyanka.jpg";
|
||||
import MeatballsImage from "../img/Meatballs.jpg";
|
||||
import CaesarImage from "../img/Caesar.jpg";
|
||||
import HeroImage from "../img/HomePage.jpg";
|
||||
|
||||
function HomePage() {
|
||||
return (
|
||||
<div className="home-page">
|
||||
<section className="hero">
|
||||
<div className="hero-content">
|
||||
<h1>Добро пожаловать в мир вкусных рецептов!</h1>
|
||||
<p>Найдите идеальный рецепт для любого случая.</p>
|
||||
<Link to="/categories" className="cta-button">
|
||||
Посмотреть категории
|
||||
</Link>
|
||||
<div className="hero-content">
|
||||
<div className="hero-image">
|
||||
<img src={HeroImage} alt="Hero Image" />
|
||||
</div>
|
||||
<div className="hero-text">
|
||||
<h1>Добро пожаловать в мир вкусных рецептов!</h1>
|
||||
<p>Найдите идеальный рецепт для любого случая.</p>
|
||||
<Link to="/categories" className="cta-button">
|
||||
Посмотреть категории
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="categories">
|
||||
<h2>Категории рецептов</h2>
|
||||
<div className="category-grid">
|
||||
<Link to="/category/salads" className="category-card">
|
||||
<img src="" alt="Салаты" />
|
||||
<img src={SaladImage} alt="Салаты" />
|
||||
<span>Салаты</span>
|
||||
</Link>
|
||||
|
||||
<Link to="/category/soups" className="category-card">
|
||||
<img src="" alt="Супы" />
|
||||
<img src={SoupImage} alt="Супы" />
|
||||
<span>Супы</span>
|
||||
</Link>
|
||||
|
||||
<Link to="/category/main-courses" className="category-card">
|
||||
<img src="" alt="Основные блюда" />
|
||||
<img src={BasicImage} alt="Основные блюда" />
|
||||
<span>Основные блюда</span>
|
||||
</Link>
|
||||
|
||||
<Link to="/category/desserts" className="category-card">
|
||||
<img src="" alt="Десерты" />
|
||||
<img src={DessertImage} alt="Десерты" />
|
||||
<span>Десерты</span>
|
||||
</Link>
|
||||
|
||||
{/*больше категорий*/}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -46,24 +56,30 @@ function HomePage() {
|
||||
<h2>Популярные рецепты</h2>
|
||||
<div className="recipe-grid">
|
||||
<div className="recipe-card">
|
||||
<img src="" alt="Название рецепта 1" />
|
||||
<h3>Название рецепта 1</h3>
|
||||
<p>Краткое описание рецепта 1.</p>
|
||||
<img src={SolyankaImage} alt="Название рецепта 1" />
|
||||
<h3>Солянка</h3>
|
||||
<p>Старинное русское национальное суповое блюдо</p>
|
||||
<Link to="/recipe/1">Подробнее</Link>
|
||||
</div>
|
||||
|
||||
<div className="recipe-card">
|
||||
<img src="" alt="Название рецепта 2" />
|
||||
<h3>Название рецепта 2</h3>
|
||||
<p>Краткое описание рецепта 2.</p>
|
||||
<img src={MeatballsImage} alt="Название рецепта 2" />
|
||||
<h3>Тефтели</h3>
|
||||
<p>Готовятся они очень быстро, а едят их все с удовольствием.</p>
|
||||
<Link to="/recipe/2">Подробнее</Link>
|
||||
</div>
|
||||
|
||||
{/*больше избранных рецептов*/}
|
||||
<div className="recipe-card">
|
||||
<img src={CaesarImage} alt="Название рецепта 2" />
|
||||
<h3>Цезарь</h3>
|
||||
<p>Овощной салат, популярное блюдо американской кухни </p>
|
||||
<Link to="/recipe/3">Подробнее</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
export default HomePage;
|
||||
|
||||
84
src/components/LoginPage.css
Normal file
@@ -0,0 +1,84 @@
|
||||
.login-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f9f5f0;
|
||||
font-family: "Arial", sans-serif;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.login-page h3 {
|
||||
margin-bottom: 25px;
|
||||
color: #8b4513;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.login-page form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
padding: 30px;
|
||||
background-color: #fff8f0;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #d3b48c;
|
||||
}
|
||||
|
||||
.login-page input[type="text"],
|
||||
.login-page input[type="password"] {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #d3b48c;
|
||||
border-radius: 7px;
|
||||
background-color: #fff;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.login-page input::placeholder {
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.login-page label {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.login-page label input[type="checkbox"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.login-page a {
|
||||
color: #e67e22;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.login-page a:hover {
|
||||
text-decoration: underline;
|
||||
color: #d35400;
|
||||
}
|
||||
|
||||
.login-page button[type="submit"] {
|
||||
padding: 12px 20px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.login-page button[type="submit"]:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
.login-page p {
|
||||
margin-top: 25px;
|
||||
color: #543d2d;
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import './LoginPage.css';
|
||||
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
localStorage.setItem('token', 'dummy_token');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<h3>Вход</h3>
|
||||
<form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input type="text" placeholder="Логин или Email" /><br />
|
||||
<input type="password" placeholder="Пароль" /><br />
|
||||
<label><input type="checkbox" /> Запомнить меня</label><br />
|
||||
|
||||
85
src/components/RegisterPage.css
Normal file
@@ -0,0 +1,85 @@
|
||||
.register-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f9f5f0;
|
||||
font-family: "Arial", sans-serif;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.register-page h3 {
|
||||
margin-bottom: 25px;
|
||||
color: #8b4513;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.register-page form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
padding: 30px;
|
||||
background-color: #fff8f0;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #d3b48c;
|
||||
}
|
||||
|
||||
.register-page input[type="text"],
|
||||
.register-page input[type="email"],
|
||||
.register-page input[type="password"] {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #d3b48c;
|
||||
border-radius: 7px;
|
||||
background-color: #fff;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.register-page input::placeholder {
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.register-page label {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.register-page label input[type="checkbox"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.register-page button[type="submit"] {
|
||||
padding: 12px 20px;
|
||||
background-color: #a8571d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.register-page button[type="submit"]:hover {
|
||||
background-color: #70380e;
|
||||
}
|
||||
|
||||
.register-page p {
|
||||
margin-top: 25px;
|
||||
color: #543d2d;
|
||||
}
|
||||
|
||||
.register-page a {
|
||||
color: #e67e22;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.register-page a:hover {
|
||||
text-decoration: underline;
|
||||
color: #d35400;
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import './RegisterPage.css';
|
||||
|
||||
function RegisterPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
localStorage.setItem('token', 'dummy_token');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="register-page">
|
||||
<h3>Регистрация</h3>
|
||||
<form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input type="text" placeholder="Имя" /><br />
|
||||
<input type="email" placeholder="Электронная почта" /><br />
|
||||
<input type="text" placeholder="Логин" /><br />
|
||||
|
||||
BIN
src/img/Background.jpg
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
src/img/Basic.jpg
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
src/img/Caesar.jpg
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
src/img/Dessert.jpg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
src/img/HomePage.jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
src/img/Meatballs.jpg
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/img/Salad.jpg
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
src/img/Solyanka.jpg
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
src/img/Soup.jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
src/img/log.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
12
src/index.js
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
);
|
||||
|
||||