Fixes & commentaries

This commit is contained in:
RailTH
2024-05-09 18:23:47 +11:00
parent 9cfbe9c68a
commit 01fd3c087a
15 changed files with 78 additions and 100 deletions

View File

@@ -1,7 +1,7 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.b9cb408a.css", "main.css": "/static/css/main.b9cb408a.css",
"main.js": "/static/js/main.08d74509.js", "main.js": "/static/js/main.d5c997c3.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",
@@ -14,10 +14,10 @@
"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.b9cb408a.css.map": "/static/css/main.b9cb408a.css.map", "main.b9cb408a.css.map": "/static/css/main.b9cb408a.css.map",
"main.08d74509.js.map": "/static/js/main.08d74509.js.map" "main.d5c997c3.js.map": "/static/js/main.d5c997c3.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.b9cb408a.css", "static/css/main.b9cb408a.css",
"static/js/main.08d74509.js" "static/js/main.d5c997c3.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.08d74509.js"></script><link href="/static/css/main.b9cb408a.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.d5c997c3.js"></script><link href="/static/css/main.b9cb408a.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

View File

@@ -60,7 +60,7 @@ export default function App() {
{state.isPopupMapVisible && <PopupMap togglePopupMap={togglePopupMap}/>} {state.isPopupMapVisible && <PopupMap togglePopupMap={togglePopupMap}/>}
<main className="main"> <main className="main">
<Routes> <Routes>
<Route path="/" element={<HomePage products={filteredProducts} selectedCategory={selectedCategory}/>}/> <Route path="/" element={<HomePage products={filteredProducts}/>}/>
<Route path="profile/*" element={<ProfilePage />}/> <Route path="profile/*" element={<ProfilePage />}/>
<Route path="product/:id" element={<ProductPage/>}/> <Route path="product/:id" element={<ProductPage/>}/>
<Route path="payment" element={<PaymentPage />}/> <Route path="payment" element={<PaymentPage />}/>

View File

@@ -8,21 +8,20 @@ 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[]>([]); //состояние для категорий
useEffect(() => { useEffect(() => { //запрос к api для получения категорий
const fetchCategories = async () => { const fetchCategories = async () => {
try { try {
const response = await axios.get('http://127.0.0.1:8000/api/get/category'); const response = await axios.get('http://127.0.0.1:8000/api/get/category');
setCategories(response.data.categories); setCategories(response.data.categories);
console.log(response.data); } catch (error) {
} catch (error) {
console.error(`There was an error retrieving the data: ${error}`); console.error(`There was an error retrieving the data: ${error}`);
} }
}; };
fetchCategories(); fetchCategories();
}, []); }, []);
return( return(
<> <>

View File

@@ -1,13 +1,7 @@
import React from 'react'; import React from 'react';
import { DeveloperCard } from "../utils/types"
interface DeveloperCardProps { export default function DevCard({ avatar, name, info, url }: DeveloperCard) {
avatar: string;
name: string;
info: string;
url: string;
}
export default function DevCard({ avatar, name, info, url }: DeveloperCardProps) {
return ( return (
<div className="info-page__dev-card"> <div className="info-page__dev-card">
<div className="dev-card__inner"> <div className="dev-card__inner">

View File

@@ -7,8 +7,8 @@ type ReviewProps = {
review: Reviews; review: Reviews;
}; };
export default function Review({ review }: ReviewProps) { export default function Review({ review }: ReviewProps) { //соответствие типов данных в review с указанными типами и свойствами в интерфейсе Reviews
const readableDate = new Date(review.date).toLocaleDateString('ru-RU'); const readableDate = new Date(review.date).toLocaleDateString('ru-RU'); //приводит дату отзыва из запроса к api в читаемый вид (чч/мм/гг)
return( return(
<article className="review-article"> <article className="review-article">

View File

@@ -2,15 +2,14 @@ import React from "react";
import '../HomeStyle.scss'; import '../HomeStyle.scss';
import ProductCard from "../components/ProductCard"; import ProductCard from "../components/ProductCard";
import Banner from "../components/AdBanner"; import Banner from "../components/AdBanner";
import { Product, Category } from "../utils/types"; import { Product } from "../utils/types";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
interface HomePageProps { type HomePageProps = {
products: Product[]; products: Product[];
selectedCategory: Category | 'all';
} }
export default function HomePage({ products, selectedCategory }: HomePageProps) { export default function HomePage({ products }: HomePageProps) { //соответствие типов данных в products с указанными типами и свойствами в интерфейсе Product
return( return(
<section className="home-page"> <section className="home-page">
<Banner /> <Banner />

View File

@@ -2,40 +2,36 @@ import React, { useState } from "react";
import '../PaymentStyle.scss'; import '../PaymentStyle.scss';
export default function PaymentPage() { export default function PaymentPage() {
let price = 150 const [ccNumber, setCcNumber] = useState(""); //состояние номера кредитной карты
const [valueDate, setValueDate] = useState<number | ''>(''); //состояние даты истечения срока карты
const [valueCode, setValueCode] = useState<number | ''>(''); //состояние кода безопасности карты
const [ccNumber, setCcNumber] = useState(""); const formatAndSetCcNumber = (e: React.ChangeEvent<HTMLInputElement>) => { //в ccNumber максимум 16 цифр, разбивка пробелами по 4
const [valueDate, setValueDate] = useState<number | ''>(''); const inputVal = e.target.value.replace(/ /g, "");
const [valueCode, setValueCode] = useState<number | ''>(''); let inputNumbersOnly = inputVal.replace(/\D/g, "");
const formatAndSetCcNumber = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputVal = e.target.value.replace(/ /g, ""); //remove all the empty spaces in the input
let inputNumbersOnly = inputVal.replace(/\D/g, ""); // Get only digits
if (inputNumbersOnly.length > 16) { if (inputNumbersOnly.length > 16) {
//If entered value has a length greater than 16 then take only the first 16 digits
inputNumbersOnly = inputNumbersOnly.substr(0, 16); inputNumbersOnly = inputNumbersOnly.substr(0, 16);
} }
// Get nd array of 4 digits per an element EX: ["4242", "4242", ...]
const splits = inputNumbersOnly.match(/.{1,4}/g); const splits = inputNumbersOnly.match(/.{1,4}/g);
let spacedNumber = ""; let spacedNumber = "";
if (splits) { if (splits) {
spacedNumber = splits.join(" "); // Join all the splits with an empty space spacedNumber = splits.join(" ");
} }
setCcNumber(spacedNumber); // Set the new CC number setCcNumber(spacedNumber);
}; };
const handleChangeDate = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChangeDate = (event: React.ChangeEvent<HTMLInputElement>) => { //valueDate не более 4-х цифр
const inputValue = event.target.value; const inputValue = event.target.value;
if (inputValue.length <= 4) { if (inputValue.length <= 4) {
setValueDate(inputValue === '' ? '' : Number(inputValue)); setValueDate(inputValue === '' ? '' : Number(inputValue));
} }
}; };
const handleChangeCode = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChangeCode = (event: React.ChangeEvent<HTMLInputElement>) => { //valueCode не более 3-х цифр
const inputValue = event.target.value; const inputValue = event.target.value;
if (inputValue.length <= 3) { if (inputValue.length <= 3) {
setValueCode(inputValue === '' ? '' : Number(inputValue)); setValueCode(inputValue === '' ? '' : Number(inputValue));
@@ -45,7 +41,7 @@ export default function PaymentPage() {
return( return(
<section className="payment-page"> <section className="payment-page">
<h2 className="payment-page__price"> <h2 className="payment-page__price">
{price}
</h2> </h2>
<div className="payment-page__payment-card"> <div className="payment-page__payment-card">
<h3 className="payment-card__heading"> <h3 className="payment-card__heading">

View File

@@ -8,21 +8,29 @@ import ReviewForm from '../components/ReviewForm';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
export default function ProductPage() { export default function ProductPage() {
function trimText(text: string, limit: number) { function trimText(text: string, limit: number) { //сокращение описания
return text.length > limit ? text.substring(0, limit) + '...' : text; return text.length > limit ? text.substring(0, limit) + '...' : text;
} }
const { id } = useParams(); const { id } = useParams(); //возвращает id товара из Url
const [product, setProduct] = useState<Product | null>(null); const [product, setProduct] = useState<Product | null>(null); //состояние для данных о товаре
const [reviews, setReviews] = useState<Reviews[]>([]); const [reviews, setReviews] = useState<Reviews[]>([]); //состаяние для отзывов
const [averageRating, setAverageRating] = useState<number>(0); const [averageRating, setAverageRating] = useState<number>(0); //состояние для средней арифметической оценки товара
const [isDataFetched, setIsDataFetched] = useState(false); const [isDataFetched, setIsDataFetched] = useState(false); //состояние для отслеживания кэширования данных из запроса
const totalReviews = reviews.length; //количество отзывов
const countReviewsByRate = (rate: number): number => { //подсчёт отзывов с данной оценкой
return reviews.filter(review => review.rate === rate).length;
};
const percentageOfRate = (rate: number): number => { //расчёт процента отзывов с данной оценкой от количетсва всех отзывов
const count = countReviewsByRate(rate);
return (count / totalReviews) * 100;
};
useEffect(() => { useEffect(() => { //запрос к api данных о товаре
axios axios
.get('http://127.0.0.1:8000/api/get/products') .get('http://127.0.0.1:8000/api/get/products')
.then(response => { .then(response => {
const productData = response.data.products.find( const productData = response.data.products.find( //"извлечение" данных о товаре из массива по его id
(item: Product) => item.id.toString() === id (item: Product) => item.id.toString() === id
); );
setProduct(productData); setProduct(productData);
@@ -32,15 +40,20 @@ export default function ProductPage() {
}); });
}, [id]); }, [id]);
useEffect(() => { useEffect(() => { //запрос к api отзывов у товара
if (!isDataFetched) { if (!isDataFetched) { //проверка на кэширование данных
axios axios
.get(`http://127.0.0.1:8000/api/get/reviews/${id}`) .get(`http://127.0.0.1:8000/api/get/reviews/${id}`)
.then(response => { .then(response => {
setReviews(response.data.review); setReviews(response.data.review);
const totalRating = response.data.review.reduce((acc: number, review: Reviews) => acc + review.rate, 0); const totalRating = response.data.review.reduce((acc: number, review: Reviews) => acc + review.rate, 0); //общий рейтинг отзывов
const average = totalRating / response.data.review.length; const average = totalRating / response.data.review.length; //средннее арифметический рейтинг всех отзывов
setAverageRating(average); if (response.data.review.length > 0) { //проверка на наличие отзывов
setAverageRating(average);
}
else {
setAverageRating(0);
}
setIsDataFetched(true); setIsDataFetched(true);
}) })
.catch(error => { .catch(error => {
@@ -89,7 +102,7 @@ export default function ProductPage() {
{product.description} {product.description}
</p> </p>
<ul className="product-page__tags-ul"> <ul className="product-page__tags-ul">
{(product.tags.split('|')).map((tag, index) => ( {(product.tags.split('|')).map((tag, index) => ( //разделение тегов
<li key={index} className="product-page__tag-li"> <li key={index} className="product-page__tag-li">
{tag} {tag}
</li> </li>
@@ -107,7 +120,7 @@ export default function ProductPage() {
</span> </span>
<div className="rate-block__star-rating"> <div className="rate-block__star-rating">
<div className="star-rating__back-stars"> <div className="star-rating__back-stars">
{'★★★★★'.split('').map((star, i) => ( {'★★★★★'.split('').map((star, i) => ( //пожалуйста, не спрашивайте как это работает
<span key={`back-star-${i}`}>{star}</span> <span key={`back-star-${i}`}>{star}</span>
))} ))}
<div className="star-rating__front-stars" style={{ width: `${(averageRating / 5) * 100}%` }}> <div className="star-rating__front-stars" style={{ width: `${(averageRating / 5) * 100}%` }}>
@@ -119,46 +132,16 @@ export default function ProductPage() {
</div> </div>
</div> </div>
<div className='rate-block__progressbars-group'> <div className='rate-block__progressbars-group'>
<div className='progressbars-group__progressbar-container'> {[5, 4, 3, 2, 1].map(rate => (
<span className='rate-progressbar__rate-number'> <div className='progressbars-group__progressbar-container' key={rate}>
5 <span className='rate-progressbar__rate-number'>
</span> {rate}
<div className='progressbar-container__progressbar'> </span>
<div className='progressbar__active-line'></div> <div className='progressbar-container__progressbar'>
</div> <div className='progressbar__active-line' style={{ width: `${percentageOfRate(rate)}%` }}></div>
</div> </div>
<div className='progressbars-group__progressbar-container'>
<span className='rate-progressbar__rate-number'>
4
</span>
<div className='progressbar-container__progressbar'>
<div className='progressbar__active-line'></div>
</div>
</div>
<div className='progressbars-group__progressbar-container'>
<span className='rate-progressbar__rate-number'>
3
</span>
<div className='progressbar-container__progressbar'>
<div className='progressbar__active-line'></div>
</div>
</div>
<div className='progressbars-group__progressbar-container'>
<span className='rate-progressbar__rate-number'>
2
</span>
<div className='progressbar-container__progressbar'>
<div className='progressbar__active-line'></div>
</div>
</div>
<div className='progressbars-group__progressbar-container'>
<span className='rate-progressbar__rate-number'>
1
</span>
<div className='progressbar-container__progressbar'>
<div className='progressbar__active-line'></div>
</div>
</div> </div>
))}
</div> </div>
</div> </div>
<ReviewForm /> <ReviewForm />

View File

@@ -22,4 +22,11 @@ export interface Reviews {
product_id: number; product_id: number;
rate: number; rate: number;
user_id: number; user_id: number;
}
export interface DeveloperCard {
avatar: string;
name: string;
info: string;
url: string;
} }