mirror of
https://github.com/yawaflua/SusMarket.git
synced 2025-12-09 20:19:31 +02:00
Coding of product page is finished & other fixes
Coding of reviews & sending reviews, and other fixes
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.f8545ce2.css",
|
"main.css": "/static/css/main.c2ca56ed.css",
|
||||||
"main.js": "/static/js/main.29450137.js",
|
"main.js": "/static/js/main.b01594ed.js",
|
||||||
"static/media/scam-image.png": "/static/media/scam-image.c6c14289dc251ba2d2b1.png",
|
"static/media/scam-image.png": "/static/media/scam-image.c6c14289dc251ba2d2b1.png",
|
||||||
"static/media/info-page__railth-avatar.png": "/static/media/info-page__railth-avatar.cbf11c43b5ef243b38c0.png",
|
"static/media/info-page__railth-avatar.png": "/static/media/info-page__railth-avatar.cbf11c43b5ef243b38c0.png",
|
||||||
"static/media/add.webp": "/static/media/add.cd69f1e2a8c91109db0f.webp",
|
"static/media/add.webp": "/static/media/add.cd69f1e2a8c91109db0f.webp",
|
||||||
@@ -13,11 +13,11 @@
|
|||||||
"static/media/rating__star-icon.svg": "/static/media/rating__star-icon.73718a24d04eb67f5873.svg",
|
"static/media/rating__star-icon.svg": "/static/media/rating__star-icon.73718a24d04eb67f5873.svg",
|
||||||
"static/media/rating__filled-star-icon.svg": "/static/media/rating__filled-star-icon.dc7d908d4d943b7f3b56.svg",
|
"static/media/rating__filled-star-icon.svg": "/static/media/rating__filled-star-icon.dc7d908d4d943b7f3b56.svg",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"main.f8545ce2.css.map": "/static/css/main.f8545ce2.css.map",
|
"main.c2ca56ed.css.map": "/static/css/main.c2ca56ed.css.map",
|
||||||
"main.29450137.js.map": "/static/js/main.29450137.js.map"
|
"main.b01594ed.js.map": "/static/js/main.b01594ed.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.f8545ce2.css",
|
"static/css/main.c2ca56ed.css",
|
||||||
"static/js/main.29450137.js"
|
"static/js/main.b01594ed.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SusMarket</title><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.29450137.js"></script><link href="/static/css/main.f8545ce2.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>SusMarket</title><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.b01594ed.js"></script><link href="/static/css/main.c2ca56ed.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
||||||
2
reactapp/build/static/css/main.c2ca56ed.css
Normal file
2
reactapp/build/static/css/main.c2ca56ed.css
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/css/main.c2ca56ed.css.map
Normal file
1
reactapp/build/static/css/main.c2ca56ed.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
reactapp/build/static/js/main.b01594ed.js
Normal file
3
reactapp/build/static/js/main.b01594ed.js
Normal file
File diff suppressed because one or more lines are too long
1
reactapp/build/static/js/main.b01594ed.js.map
Normal file
1
reactapp/build/static/js/main.b01594ed.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -41,12 +41,17 @@ export default function App() {
|
|||||||
|
|
||||||
// Функция для переключения отображения/скрытия карты
|
// Функция для переключения отображения/скрытия карты
|
||||||
const togglePopupMap = () => {
|
const togglePopupMap = () => {
|
||||||
setState((prevState) => (
|
setState((prevState) => {
|
||||||
{
|
if (!prevState.isPopupMapVisible) {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
}
|
||||||
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
isPopupMapVisible: !prevState.isPopupMapVisible,
|
isPopupMapVisible: !prevState.isPopupMapVisible,
|
||||||
}
|
};
|
||||||
));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обработчик изменения поискового запроса
|
// Обработчик изменения поискового запроса
|
||||||
|
|||||||
@@ -317,16 +317,34 @@ $accent-color: #EB5E28;
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-form__image-attach {
|
.review-form__image-container {
|
||||||
width: 34px;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
img {
|
gap: 20px;
|
||||||
width: 100%;
|
|
||||||
|
.review-form__image-attach {
|
||||||
|
width: 34px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__image-input {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.review-form__image-input {
|
.review-form__image-name {
|
||||||
display: none;
|
color: black;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: $main-color;
|
||||||
|
border-radius: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-form__send-button {
|
.review-form__send-button {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Category } from "../utils/types";
|
import { Category } from "../utils/types";
|
||||||
|
|
||||||
@@ -10,18 +10,15 @@ interface CatalogMenuProps {
|
|||||||
export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps): JSX.Element {
|
export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps): JSX.Element {
|
||||||
const [categories, setCategories] = useState<Category[]>([]);
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
|
||||||
const fetchCategories = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get('http://127.0.0.1:8000/api/get/category');
|
|
||||||
setCategories(response.data.categories);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`There was an error retrieving the data: ${error}`);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCategories();
|
axios.get('http://127.0.0.1:8000/api/get/category')
|
||||||
}, [fetchCategories]);
|
.then(response => {
|
||||||
|
setCategories(response.data.categories);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`There was an error retrieving the data: ${error}`);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -18,6 +18,18 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
|||||||
setIsLoginMode(!isLoginMode);
|
setIsLoginMode(!isLoginMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
toggleLoginMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleAuth = async (isRegistering: boolean) => {
|
const handleAuth = async (isRegistering: boolean) => {
|
||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
@@ -27,16 +39,24 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
|||||||
} else {
|
} else {
|
||||||
// Вход в систему
|
// Вход в систему
|
||||||
response = await axios.get(`http://127.0.0.1:8000/api/get/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`);
|
response = await axios.get(`http://127.0.0.1:8000/api/get/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`);
|
||||||
|
// Проверка наличия пользователя
|
||||||
|
if (response.data.user.length === 0) {
|
||||||
|
alert('Пользователь не найден.');
|
||||||
|
// Здесь можно выполнить действия в случае отсутствия пользователя, например, вывести сообщение об ошибке
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
// Создание cookie файла
|
// Создание cookie файла
|
||||||
Cookies.set('user', login, { expires: 1 }); // Cookie на 1 день
|
Cookies.set('user', login, { expires: 1 }); // Cookie на 1 день
|
||||||
|
Cookies.set('user_id', response.data.user[0].id, { expires: 1 })
|
||||||
// Перенаправление на страницу профиля
|
// Перенаправление на страницу профиля
|
||||||
navigate('/profile');
|
navigate('/profile');
|
||||||
toggleLoginMenu(); // Закрытие меню входа
|
toggleLoginMenu(); // Закрытие меню входа
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при авторизации:', error);
|
alert('Ошибка при авторизации: ' + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +67,7 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
|||||||
|
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
<div className="background-blackout" onClick={toggleLoginMenu}></div>
|
<div className="background-blackout" onClick={handleClose}></div>
|
||||||
<form className="popup-login" onSubmit={handleSubmit}>
|
<form className="popup-login" onSubmit={handleSubmit}>
|
||||||
<div className="popup-login__top-container">
|
<div className="popup-login__top-container">
|
||||||
<div className="top-container__headings-text">
|
<div className="top-container__headings-text">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
type ButtonState = 1 | 2 | null;
|
type ButtonState = 1 | 2 | null;
|
||||||
|
|
||||||
@@ -6,16 +6,28 @@ interface PopupMapProps {
|
|||||||
togglePopupMap: () => void;
|
togglePopupMap: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PopupMap({ togglePopupMap }: PopupMapProps): JSX.Element {
|
export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||||
const [selectedButton, setSelectedButton] = useState<ButtonState>(null);
|
const [selectedButton, setSelectedButton] = useState<ButtonState>(null);
|
||||||
|
|
||||||
const handleButtonClick = (buttonId: ButtonState) => {
|
const handleButtonClick = (buttonId: ButtonState) => {
|
||||||
setSelectedButton(buttonId);
|
setSelectedButton(buttonId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
togglePopupMap();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
<div className="background-blackout" onClick={togglePopupMap}></div>
|
<div className="background-blackout" onClick={handleClose}></div>
|
||||||
<div className="popup-map">
|
<div className="popup-map">
|
||||||
<div className="popup-map__menu-div">
|
<div className="popup-map__menu-div">
|
||||||
<div className="menu-div__container-div">
|
<div className="menu-div__container-div">
|
||||||
|
|||||||
@@ -8,36 +8,42 @@ type ReviewProps = {
|
|||||||
review: Reviews;
|
review: Reviews;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Компонент для отображения отзыва
|
||||||
export default function Review({ review }: ReviewProps) {
|
export default function Review({ review }: ReviewProps) {
|
||||||
|
// Состояние для логина пользователя
|
||||||
const [userName, setUserName] = useState<string>("");
|
const [userName, setUserName] = useState<string>("");
|
||||||
|
// Читаем дату отзыва
|
||||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU');
|
const readableDate = new Date(review.date).toLocaleDateString('ru-RU');
|
||||||
|
|
||||||
|
// useEffect для получения логина пользователя
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserName = async () => {
|
// Запрос к api для получения логина пользователя
|
||||||
try {
|
axios.get(`http://127.0.0.1:8000/api/get/user/${review.user_id}`)
|
||||||
const response = await axios.get(`http://127.0.0.1:8000/api/get/user/${review.user_id}`);
|
.then(response => {
|
||||||
if (response.status === 200) {
|
const user = response.data.user[0];
|
||||||
setUserName(response.data.login);
|
// Устанавливаем логин пользователя
|
||||||
}
|
setUserName(user.login);
|
||||||
} catch (error) {
|
})
|
||||||
console.error('Ошибка при получении логина пользователя:', error);
|
.catch(error => {
|
||||||
}
|
console.error('Ошибка при получении логина пользователя:', error);
|
||||||
};
|
});
|
||||||
|
}, [review.user_id]);
|
||||||
fetchUserName();
|
|
||||||
}, [review.user_id]);
|
|
||||||
|
|
||||||
|
// Возвращаем JSX для отображения отзыва
|
||||||
return (
|
return (
|
||||||
<article className="review-article">
|
<article className="review-article">
|
||||||
<div className="review-article__review-container">
|
<div className="review-article__review-container">
|
||||||
<div className="review-container__user-info">
|
<div className="review-container__user-info">
|
||||||
|
{/* Отображаем аватарку пользователя */}
|
||||||
<img className="user-info__user-avatar" src={UserAvatar} alt="Review user avatar" />
|
<img className="user-info__user-avatar" src={UserAvatar} alt="Review user avatar" />
|
||||||
<h4 className="user-info__user-name">
|
<h4 className="user-info__user-name">
|
||||||
|
{/* Отображаем логин пользователя */}
|
||||||
{userName}
|
{userName}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="review-container__review-info">
|
<div className="review-container__review-info">
|
||||||
<div className="review-info__star-rate">
|
<div className="review-info__star-rate">
|
||||||
|
{/* Отображаем рейтинг отзыва */}
|
||||||
{[1, 2, 3, 4, 5].map(rate => (
|
{[1, 2, 3, 4, 5].map(rate => (
|
||||||
<input
|
<input
|
||||||
key={rate}
|
key={rate}
|
||||||
@@ -51,14 +57,18 @@ export default function Review({ review }: ReviewProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<time className="review-info__review-date" dateTime={new Date(review.date).toISOString()}>
|
<time className="review-info__review-date" dateTime={new Date(review.date).toISOString()}>
|
||||||
|
{/* Отображаем дату отзыва */}
|
||||||
{readableDate}
|
{readableDate}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="review-article__text-p">
|
<p className="review-article__text-p">
|
||||||
|
{/* Отображаем текст отзыва */}
|
||||||
{review.commentary}
|
{review.commentary}
|
||||||
</p>
|
</p>
|
||||||
|
{/* Отображаем изображение товара, если оно есть */}
|
||||||
{review.icons && <img className="review-article__product-image" src={review.icons} alt="Review product" />}
|
{review.icons && <img className="review-article__product-image" src={review.icons} alt="Review product" />}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ interface ReviewState {
|
|||||||
export default function ReviewForm({ productId }: { productId: string }) {
|
export default function ReviewForm({ productId }: { productId: string }) {
|
||||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 });
|
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 });
|
||||||
const [userId, setUserId] = useState<string | null>(null);
|
const [userId, setUserId] = useState<string | null>(null);
|
||||||
|
const [imageName, setImageName] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userIdFromCookie = Cookies.get('id');
|
const userIdFromCookie = Cookies.get('user_id');
|
||||||
if (userIdFromCookie) {
|
if (userIdFromCookie) {
|
||||||
setUserId(userIdFromCookie);
|
setUserId(userIdFromCookie);
|
||||||
}
|
}
|
||||||
@@ -32,11 +33,14 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
|||||||
|
|
||||||
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
if (event.target.files && event.target.files[0]) {
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
setImageName(file.name);
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
setReview({ ...review, image: reader.result });
|
setReview({ ...review, image: reader.result });
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(event.target.files[0]);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,20 +51,21 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const params = new URLSearchParams();
|
||||||
formData.append('product_id', productId);
|
params.append('commentary', review.text);
|
||||||
formData.append('text', review.text);
|
params.append('rate', String(review.rating));
|
||||||
formData.append('rating', String(review.rating));
|
params.append('product', productId);
|
||||||
|
params.append('user_id', userId);
|
||||||
if (review.image) {
|
if (review.image) {
|
||||||
formData.append('image', review.image as string);
|
params.append('icon', review.image as string);
|
||||||
}
|
}
|
||||||
formData.append('user_id', userId);
|
|
||||||
await axios.post('http://127.0.0.1:8000/api/post/review', formData, {
|
await axios.post('http://127.0.0.1:8000/api/post/review', params, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('Review submitted successfully!');
|
alert("Отзыв успешно отправлен!")
|
||||||
// Опционально: добавить логику для обновления списка отзывов на странице после успешной отправки
|
// Опционально: добавить логику для обновления списка отзывов на странице после успешной отправки
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error submitting review:', error);
|
console.error('Error submitting review:', error);
|
||||||
@@ -94,17 +99,20 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
|||||||
value={review.text}
|
value={review.text}
|
||||||
onChange={handleTextChange}
|
onChange={handleTextChange}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="review-image" className='review-form__image-attach'>
|
<div className='review-form__image-container'>
|
||||||
<img src={ImageAttachIcon} alt="Прикрепить изображение" />
|
<label htmlFor="review-image" className='review-form__image-attach'>
|
||||||
</label>
|
<img src={ImageAttachIcon} alt="Прикрепить изображение" />
|
||||||
<input
|
</label>
|
||||||
className='review-form__image-input'
|
<input
|
||||||
type="file"
|
className='review-form__image-input'
|
||||||
name="review image"
|
type="file"
|
||||||
id="review-image"
|
name="review image"
|
||||||
accept='.png, .jpg, .jpeg'
|
id="review-image"
|
||||||
onChange={handleImageChange}
|
accept='.png, .jpg, .jpeg'
|
||||||
/>
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
{imageName && <div className="review-form__image-name">{imageName}</div>}
|
||||||
|
</div>
|
||||||
<motion.button
|
<motion.button
|
||||||
className='review-form__send-button'
|
className='review-form__send-button'
|
||||||
type='submit'
|
type='submit'
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ body {
|
|||||||
font-family: 'Montserrat', sans-serif;
|
font-family: 'Montserrat', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.no-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.product-article {
|
.product-article {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 260px;
|
width: 260px;
|
||||||
|
|||||||
Reference in New Issue
Block a user