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": {
"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"
]
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,10 @@ body {
font-family: 'Montserrat', sans-serif;
}
body.no-scroll {
overflow: hidden;
}
.product-article {
display: flex;
width: 260px;