Coding of product page is finished & other fixes

Coding of reviews & sending reviews, and other fixes
This commit is contained in:
RailTH
2024-05-18 16:41:22 +11:00
parent 0808a0c6f3
commit e310fa5488
19 changed files with 153 additions and 79 deletions

View File

@@ -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"
] ]
} }

View File

@@ -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>

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

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

View File

@@ -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,
} };
)); });
}; };
// Обработчик изменения поискового запроса // Обработчик изменения поискового запроса

View File

@@ -317,6 +317,11 @@ $accent-color: #EB5E28;
font-size: 24px; font-size: 24px;
} }
.review-form__image-container {
width: 100%;
display: flex;
gap: 20px;
.review-form__image-attach { .review-form__image-attach {
width: 34px; width: 34px;
@@ -329,6 +334,19 @@ $accent-color: #EB5E28;
display: none; 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 { .review-form__send-button {
padding: 20px; padding: 20px;
border-radius: 15px; border-radius: 15px;

View File

@@ -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 (
<> <>

View File

@@ -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">

View File

@@ -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">

View File

@@ -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) { })
.catch(error => {
console.error('Ошибка при получении логина пользователя:', error); console.error('Ошибка при получении логина пользователя:', error);
} });
};
fetchUserName();
}, [review.user_id]); }, [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>
); );
} }

View File

@@ -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,6 +99,7 @@ export default function ReviewForm({ productId }: { productId: string }) {
value={review.text} value={review.text}
onChange={handleTextChange} onChange={handleTextChange}
/> />
<div className='review-form__image-container'>
<label htmlFor="review-image" className='review-form__image-attach'> <label htmlFor="review-image" className='review-form__image-attach'>
<img src={ImageAttachIcon} alt="Прикрепить изображение" /> <img src={ImageAttachIcon} alt="Прикрепить изображение" />
</label> </label>
@@ -105,6 +111,8 @@ export default function ReviewForm({ productId }: { productId: string }) {
accept='.png, .jpg, .jpeg' accept='.png, .jpg, .jpeg'
onChange={handleImageChange} 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'

View File

@@ -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;