mirror of
https://github.com/yawaflua/SusMarket.git
synced 2025-12-08 19:49:36 +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": {
|
||||
"main.css": "/static/css/main.f8545ce2.css",
|
||||
"main.js": "/static/js/main.29450137.js",
|
||||
"main.css": "/static/css/main.c2ca56ed.css",
|
||||
"main.js": "/static/js/main.b01594ed.js",
|
||||
"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/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__filled-star-icon.svg": "/static/media/rating__filled-star-icon.dc7d908d4d943b7f3b56.svg",
|
||||
"index.html": "/index.html",
|
||||
"main.f8545ce2.css.map": "/static/css/main.f8545ce2.css.map",
|
||||
"main.29450137.js.map": "/static/js/main.29450137.js.map"
|
||||
"main.c2ca56ed.css.map": "/static/css/main.c2ca56ed.css.map",
|
||||
"main.b01594ed.js.map": "/static/js/main.b01594ed.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.f8545ce2.css",
|
||||
"static/js/main.29450137.js"
|
||||
"static/css/main.c2ca56ed.css",
|
||||
"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 = () => {
|
||||
setState((prevState) => (
|
||||
{
|
||||
setState((prevState) => {
|
||||
if (!prevState.isPopupMapVisible) {
|
||||
document.body.classList.add('no-scroll');
|
||||
} else {
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
return {
|
||||
...prevState,
|
||||
isPopupMapVisible: !prevState.isPopupMapVisible,
|
||||
}
|
||||
));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Обработчик изменения поискового запроса
|
||||
|
||||
@@ -317,16 +317,34 @@ $accent-color: #EB5E28;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.review-form__image-attach {
|
||||
width: 34px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
.review-form__image-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
.review-form__image-attach {
|
||||
width: 34px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.review-form__image-input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.review-form__image-input {
|
||||
display: none;
|
||||
.review-form__image-name {
|
||||
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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { Category } from "../utils/types";
|
||||
|
||||
@@ -10,18 +10,15 @@ interface CatalogMenuProps {
|
||||
export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps): JSX.Element {
|
||||
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(() => {
|
||||
fetchCategories();
|
||||
}, [fetchCategories]);
|
||||
axios.get('http://127.0.0.1:8000/api/get/category')
|
||||
.then(response => {
|
||||
setCategories(response.data.categories);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`There was an error retrieving the data: ${error}`);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import axios from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -18,6 +18,18 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
||||
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) => {
|
||||
try {
|
||||
let response;
|
||||
@@ -27,16 +39,24 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
||||
} else {
|
||||
// Вход в систему
|
||||
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) {
|
||||
// Создание cookie файла
|
||||
Cookies.set('user', login, { expires: 1 }); // Cookie на 1 день
|
||||
Cookies.set('user_id', response.data.user[0].id, { expires: 1 })
|
||||
// Перенаправление на страницу профиля
|
||||
navigate('/profile');
|
||||
toggleLoginMenu(); // Закрытие меню входа
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при авторизации:', error);
|
||||
alert('Ошибка при авторизации: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +67,7 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="background-blackout" onClick={toggleLoginMenu}></div>
|
||||
<div className="background-blackout" onClick={handleClose}></div>
|
||||
<form className="popup-login" onSubmit={handleSubmit}>
|
||||
<div className="popup-login__top-container">
|
||||
<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;
|
||||
|
||||
@@ -6,16 +6,28 @@ interface PopupMapProps {
|
||||
togglePopupMap: () => void;
|
||||
}
|
||||
|
||||
export default function PopupMap({ togglePopupMap }: PopupMapProps): JSX.Element {
|
||||
export default function PopupMap({ togglePopupMap }: PopupMapProps) {
|
||||
const [selectedButton, setSelectedButton] = useState<ButtonState>(null);
|
||||
|
||||
const handleButtonClick = (buttonId: ButtonState) => {
|
||||
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(
|
||||
<>
|
||||
<div className="background-blackout" onClick={togglePopupMap}></div>
|
||||
<div className="background-blackout" onClick={handleClose}></div>
|
||||
<div className="popup-map">
|
||||
<div className="popup-map__menu-div">
|
||||
<div className="menu-div__container-div">
|
||||
|
||||
@@ -8,36 +8,42 @@ type ReviewProps = {
|
||||
review: Reviews;
|
||||
};
|
||||
|
||||
// Компонент для отображения отзыва
|
||||
export default function Review({ review }: ReviewProps) {
|
||||
// Состояние для логина пользователя
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
// Читаем дату отзыва
|
||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU');
|
||||
|
||||
// useEffect для получения логина пользователя
|
||||
useEffect(() => {
|
||||
const fetchUserName = async () => {
|
||||
try {
|
||||
const response = await axios.get(`http://127.0.0.1:8000/api/get/user/${review.user_id}`);
|
||||
if (response.status === 200) {
|
||||
setUserName(response.data.login);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении логина пользователя:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUserName();
|
||||
}, [review.user_id]);
|
||||
// Запрос к api для получения логина пользователя
|
||||
axios.get(`http://127.0.0.1:8000/api/get/user/${review.user_id}`)
|
||||
.then(response => {
|
||||
const user = response.data.user[0];
|
||||
// Устанавливаем логин пользователя
|
||||
setUserName(user.login);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при получении логина пользователя:', error);
|
||||
});
|
||||
}, [review.user_id]);
|
||||
|
||||
// Возвращаем JSX для отображения отзыва
|
||||
return (
|
||||
<article className="review-article">
|
||||
<div className="review-article__review-container">
|
||||
<div className="review-container__user-info">
|
||||
{/* Отображаем аватарку пользователя */}
|
||||
<img className="user-info__user-avatar" src={UserAvatar} alt="Review user avatar" />
|
||||
<h4 className="user-info__user-name">
|
||||
{/* Отображаем логин пользователя */}
|
||||
{userName}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="review-container__review-info">
|
||||
<div className="review-info__star-rate">
|
||||
{/* Отображаем рейтинг отзыва */}
|
||||
{[1, 2, 3, 4, 5].map(rate => (
|
||||
<input
|
||||
key={rate}
|
||||
@@ -51,14 +57,18 @@ export default function Review({ review }: ReviewProps) {
|
||||
))}
|
||||
</div>
|
||||
<time className="review-info__review-date" dateTime={new Date(review.date).toISOString()}>
|
||||
{/* Отображаем дату отзыва */}
|
||||
{readableDate}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
<p className="review-article__text-p">
|
||||
{/* Отображаем текст отзыва */}
|
||||
{review.commentary}
|
||||
</p>
|
||||
{/* Отображаем изображение товара, если оно есть */}
|
||||
{review.icons && <img className="review-article__product-image" src={review.icons} alt="Review product" />}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ interface ReviewState {
|
||||
export default function ReviewForm({ productId }: { productId: string }) {
|
||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 });
|
||||
const [userId, setUserId] = useState<string | null>(null);
|
||||
const [imageName, setImageName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const userIdFromCookie = Cookies.get('id');
|
||||
const userIdFromCookie = Cookies.get('user_id');
|
||||
if (userIdFromCookie) {
|
||||
setUserId(userIdFromCookie);
|
||||
}
|
||||
@@ -32,11 +33,14 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
||||
|
||||
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file = event.target.files[0];
|
||||
setImageName(file.name);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
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;
|
||||
}
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('product_id', productId);
|
||||
formData.append('text', review.text);
|
||||
formData.append('rating', String(review.rating));
|
||||
const params = new URLSearchParams();
|
||||
params.append('commentary', review.text);
|
||||
params.append('rate', String(review.rating));
|
||||
params.append('product', productId);
|
||||
params.append('user_id', userId);
|
||||
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: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
console.log('Review submitted successfully!');
|
||||
alert("Отзыв успешно отправлен!")
|
||||
// Опционально: добавить логику для обновления списка отзывов на странице после успешной отправки
|
||||
} catch (error) {
|
||||
console.error('Error submitting review:', error);
|
||||
@@ -94,17 +99,20 @@ export default function ReviewForm({ productId }: { productId: string }) {
|
||||
value={review.text}
|
||||
onChange={handleTextChange}
|
||||
/>
|
||||
<label htmlFor="review-image" className='review-form__image-attach'>
|
||||
<img src={ImageAttachIcon} alt="Прикрепить изображение" />
|
||||
</label>
|
||||
<input
|
||||
className='review-form__image-input'
|
||||
type="file"
|
||||
name="review image"
|
||||
id="review-image"
|
||||
accept='.png, .jpg, .jpeg'
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
<div className='review-form__image-container'>
|
||||
<label htmlFor="review-image" className='review-form__image-attach'>
|
||||
<img src={ImageAttachIcon} alt="Прикрепить изображение" />
|
||||
</label>
|
||||
<input
|
||||
className='review-form__image-input'
|
||||
type="file"
|
||||
name="review image"
|
||||
id="review-image"
|
||||
accept='.png, .jpg, .jpeg'
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
{imageName && <div className="review-form__image-name">{imageName}</div>}
|
||||
</div>
|
||||
<motion.button
|
||||
className='review-form__send-button'
|
||||
type='submit'
|
||||
|
||||
@@ -8,6 +8,10 @@ body {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
body.no-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-article {
|
||||
display: flex;
|
||||
width: 260px;
|
||||
|
||||
Reference in New Issue
Block a user