mirror of
https://github.com/yawaflua/SusMarket.git
synced 2026-04-24 16:50:41 +03:00
Code commenting & making code better
The same
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.1 on 2024-05-14 07:21
|
||||
# Generated by Django 4.1 on 2024-05-16 07:15
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
@@ -61,7 +61,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(db_index=True, primary_key=True, serialize=False, unique=True, verbose_name='ID Отзыва')),
|
||||
('commentary', models.TextField(max_length=300, verbose_name='Комментарий отзыва')),
|
||||
('rate', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='Оценка')),
|
||||
('icons', models.TextField(null=True, verbose_name='Изображение отзыва в BASE64')),
|
||||
('icons', models.TextField(blank=True, null=True, verbose_name='Изображение отзыва в BASE64')),
|
||||
('date', models.DateTimeField(auto_now=True, verbose_name='Дата создания отзыва')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='SusMarketBackend.product')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='SusMarketBackend.user')),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.f8545ce2.css",
|
||||
"main.js": "/static/js/main.142d0adb.js",
|
||||
"main.js": "/static/js/main.29450137.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",
|
||||
@@ -14,10 +14,10 @@
|
||||
"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.142d0adb.js.map": "/static/js/main.142d0adb.js.map"
|
||||
"main.29450137.js.map": "/static/js/main.29450137.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.f8545ce2.css",
|
||||
"static/js/main.142d0adb.js"
|
||||
"static/js/main.29450137.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.142d0adb.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.29450137.js"></script><link href="/static/css/main.f8545ce2.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
+32
-8
@@ -1,3 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import HomePage from "./pages/HomePage";
|
||||
import PaymentPage from "./pages/PaymentPage";
|
||||
import ProductPage from "./pages/ProductPage";
|
||||
@@ -6,24 +9,26 @@ import ScamPage from "./pages/ScamPage";
|
||||
import InfoPage from "./pages/InfoPage";
|
||||
import Header from "./components/Header";
|
||||
import PopupMap from "./components/PopupMap";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { Product, Category } from "./utils/types";
|
||||
import axios from 'axios';
|
||||
|
||||
interface AppPopupMapState {
|
||||
isPopupMapVisible: boolean;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
// Состояние для отображения/скрытия карты
|
||||
const [state, setState] = useState<AppPopupMapState>({
|
||||
isPopupMapVisible: false,
|
||||
});
|
||||
|
||||
// Массив товаров
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
// Выбранная категория или все категории
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category | 'all'>('all');
|
||||
// Поисковый запрос
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Получение товаров при загрузке компонента
|
||||
useEffect(() => {
|
||||
axios.get('http://127.0.0.1:8000/api/get/products')
|
||||
.then(response => {
|
||||
@@ -34,37 +39,56 @@ export default function App() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Функция для переключения отображения/скрытия карты
|
||||
const togglePopupMap = () => {
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
isPopupMapVisible: !prevState.isPopupMapVisible,
|
||||
}));
|
||||
setState((prevState) => (
|
||||
{
|
||||
...prevState,
|
||||
isPopupMapVisible: !prevState.isPopupMapVisible,
|
||||
}
|
||||
));
|
||||
};
|
||||
|
||||
// Обработчик изменения поискового запроса
|
||||
const handleSearchChange = (query: string) => {
|
||||
setSearchQuery(query);
|
||||
};
|
||||
|
||||
// Фильтрация продуктов по выбранной категории и поисковому запросу
|
||||
const filteredProducts = products.filter(product =>
|
||||
(selectedCategory === 'all' || product.category_id === selectedCategory.id) &&
|
||||
product.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
// Обработчик выбора категории
|
||||
const handleSelectCategory = (category: Category | 'all') => {
|
||||
setSelectedCategory(category);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header togglePopupMap={togglePopupMap} onSelectCategory={handleSelectCategory} onSearchChange={handleSearchChange}/>
|
||||
{/* Шапка сайта */}
|
||||
<Header
|
||||
togglePopupMap={togglePopupMap}
|
||||
onSelectCategory={handleSelectCategory}
|
||||
onSearchChange={handleSearchChange}
|
||||
/>
|
||||
{/* Карта */}
|
||||
{state.isPopupMapVisible && <PopupMap togglePopupMap={togglePopupMap}/>}
|
||||
{/* Основной контент */}
|
||||
<main className="main">
|
||||
<Routes>
|
||||
{/* Главная страница */}
|
||||
<Route path="/" element={<HomePage products={filteredProducts}/>}/>
|
||||
{/* Страница профиля */}
|
||||
<Route path="profile/*" element={<ProfilePage />}/>
|
||||
{/* Страница продукта */}
|
||||
<Route path="product/:id" element={<ProductPage/>}/>
|
||||
{/* Страница оплаты */}
|
||||
<Route path="payment" element={<PaymentPage />}/>
|
||||
{/* Страница с описанием */}
|
||||
<Route path="scam" element={<ScamPage />}/>
|
||||
{/* Страница информации */}
|
||||
<Route path="info" element={<InfoPage />}/>
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import axios from "axios";
|
||||
import { Category } from "../utils/types";
|
||||
|
||||
@@ -8,32 +8,40 @@ interface CatalogMenuProps {
|
||||
}
|
||||
|
||||
export default function CatalogMenu({ toggleCatalogMenu, onSelectCategory }: CatalogMenuProps): JSX.Element {
|
||||
const [categories, setCategories] = useState<Category[]>([]); //состояние для категорий
|
||||
|
||||
useEffect(() => { //запрос к api для получения категорий
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
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) {
|
||||
} catch (error) {
|
||||
console.error(`There was an error retrieving the data: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
return(
|
||||
}, [fetchCategories]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="background-blackout" onClick={toggleCatalogMenu}></div>
|
||||
<ul className="catalog-menu">
|
||||
{categories.map((category) => (
|
||||
<li key={category.id} className="catalog-menu__point-li" onClick={() => onSelectCategory(category)}>
|
||||
<img className="catalog-menu__category-icon" src={category.image} alt={category.title} />
|
||||
{category.title}
|
||||
</li>
|
||||
<li
|
||||
key={category.id}
|
||||
className="catalog-menu__point-li"
|
||||
onClick={() => onSelectCategory(category)}
|
||||
>
|
||||
<img
|
||||
className="catalog-menu__category-icon"
|
||||
src={category.image}
|
||||
alt={category.title}
|
||||
/>
|
||||
{category.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,35 +14,22 @@ interface HeaderProps {
|
||||
onSearchChange: (query: string) => void;
|
||||
}
|
||||
|
||||
interface HeaderCatalogMenuState {
|
||||
isCatalogMenuVisible: boolean;
|
||||
}
|
||||
|
||||
interface HeaderLoginMenuState {
|
||||
isLoginMenuVisible: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default function Header({ togglePopupMap, onSelectCategory, onSearchChange }: HeaderProps): JSX.Element {
|
||||
const [isCatalogMenuVisible, setIsCatalogMenuVisible] = useState(false);
|
||||
const [isLoginMenuVisible, setIsLoginMenuVisible] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const toggleCatalogMenu = () => {
|
||||
setIsCatalogMenuVisible(!isCatalogMenuVisible);
|
||||
setIsCatalogMenuVisible(prevState => !prevState);
|
||||
};
|
||||
|
||||
const toggleLoginMenu = () => {
|
||||
setIsLoginMenuVisible(!isLoginMenuVisible);
|
||||
setIsLoginMenuVisible(prevState => !prevState);
|
||||
};
|
||||
|
||||
const handleProfileClick = () => {
|
||||
const userCookie = Cookies.get('user');
|
||||
if (userCookie) {
|
||||
navigate('/profile');
|
||||
} else {
|
||||
toggleLoginMenu();
|
||||
}
|
||||
userCookie ? navigate('/profile') : toggleLoginMenu();
|
||||
};
|
||||
|
||||
const resetCategoryFilter = () => {
|
||||
|
||||
@@ -23,11 +23,7 @@ export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Elem
|
||||
let response;
|
||||
if (isRegistering) {
|
||||
// Регистрация пользователя
|
||||
const params = new URLSearchParams({
|
||||
login: login,
|
||||
password: password
|
||||
});
|
||||
response = await axios.get(`http://127.0.0.1:8000/api/post/user?${params.toString()}`);
|
||||
response = await axios.get(`http://127.0.0.1:8000/api/post/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`);
|
||||
} else {
|
||||
// Вход в систему
|
||||
response = await axios.get(`http://127.0.0.1:8000/api/get/user?login=${encodeURIComponent(login)}&password=${encodeURIComponent(password)}`);
|
||||
|
||||
@@ -20,10 +20,16 @@ export default function PopupMap({ togglePopupMap }: PopupMapProps): JSX.Element
|
||||
<div className="popup-map__menu-div">
|
||||
<div className="menu-div__container-div">
|
||||
<div className="menu-div__delivery-div">
|
||||
<button className={selectedButton === 1 ? 'delivery-div__delivery-button delivery-div__delivery-button_selected' : 'delivery-div__delivery-button'} onClick={() => handleButtonClick(1)}>
|
||||
<button
|
||||
className={`delivery-div__delivery-button ${selectedButton === 1 ? 'delivery-div__delivery-button_selected' : ''}`}
|
||||
onClick={() => handleButtonClick(1)}
|
||||
>
|
||||
Самовывоз
|
||||
</button>
|
||||
<button className={selectedButton === 2 ? 'delivery-div__delivery-button delivery-div__delivery-button_selected' : 'delivery-div__delivery-button'} onClick={() => handleButtonClick(2)}>
|
||||
<button
|
||||
className={`delivery-div__delivery-button ${selectedButton === 2 ? 'delivery-div__delivery-button_selected' : ''}`}
|
||||
onClick={() => handleButtonClick(2)}
|
||||
>
|
||||
Курьером
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import '../ProfileStyle.scss';
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
// import ProductCard from "./ProductCard";
|
||||
|
||||
export default function ProfilePurchases() {
|
||||
return(
|
||||
@@ -18,9 +17,7 @@ export default function ProfilePurchases() {
|
||||
<div className="purchases-container">
|
||||
<ProfileInfo />
|
||||
<div className="purchases-div">
|
||||
{/* <ProductCard ProductImg={ProductImage} ProductName="Абеме" Price={150}/>
|
||||
<ProductCard ProductImg={ProductImage} ProductName="Абеме" Price={1234523453}/>
|
||||
<ProductCard ProductImg={ProductImage} ProductName="Абеме" Price={10}/> */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { Reviews } from "../utils/types";
|
||||
import "../index.scss";
|
||||
import UserAvatar from "../assets/img/templateUser-avatar.png";
|
||||
@@ -7,57 +8,49 @@ type ReviewProps = {
|
||||
review: Reviews;
|
||||
};
|
||||
|
||||
export default function Review({ review }: ReviewProps) { //соответствие типов данных в review с указанными типами и свойствами в интерфейсе Reviews
|
||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU'); //приводит дату отзыва из запроса к api в читаемый вид (чч/мм/гг)
|
||||
|
||||
return(
|
||||
export default function Review({ review }: ReviewProps) {
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const readableDate = new Date(review.date).toLocaleDateString('ru-RU');
|
||||
|
||||
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]);
|
||||
|
||||
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">
|
||||
{review.user_id}
|
||||
{userName}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="review-container__review-info">
|
||||
<div className="review-info__star-rate">
|
||||
<input
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={1}
|
||||
aria-label="Плохо"
|
||||
checked={review.rate === 1}
|
||||
/>
|
||||
<input
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={2}
|
||||
aria-label="Удовлетворительно"
|
||||
checked={review.rate === 2}
|
||||
/>
|
||||
<input
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={3}
|
||||
aria-label="Нормально"
|
||||
checked={review.rate === 3}
|
||||
/>
|
||||
<input
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={4}
|
||||
aria-label="Хорошо"
|
||||
checked={review.rate === 4}
|
||||
/>
|
||||
<input
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={5}
|
||||
aria-label="Отлично"
|
||||
checked={review.rate === 5}
|
||||
/>
|
||||
{[1, 2, 3, 4, 5].map(rate => (
|
||||
<input
|
||||
key={rate}
|
||||
type="radio"
|
||||
className="star-rate__star-radio"
|
||||
value={rate}
|
||||
aria-label={rate === 1 ? "Плохо" : rate === 2 ? "Удовлетворительно" : rate === 3 ? "Нормально" : rate === 4 ? "Хорошо" : "Отлично"}
|
||||
checked={review.rate === rate}
|
||||
readOnly
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<time className="review-info__review-date" dateTime="2019-09-09">
|
||||
<time className="review-info__review-date" dateTime={new Date(review.date).toISOString()}>
|
||||
{readableDate}
|
||||
</time>
|
||||
</div>
|
||||
@@ -65,7 +58,7 @@ export default function Review({ review }: ReviewProps) { //соответств
|
||||
<p className="review-article__text-p">
|
||||
{review.commentary}
|
||||
</p>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import '../ProductStyle.scss';
|
||||
import ImageAttachIcon from "../assets/icons/review-form__add-image-icon.svg";
|
||||
import { motion } from 'framer-motion';
|
||||
import axios from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
interface ReviewState {
|
||||
text: string;
|
||||
rating: number;
|
||||
image: File | null;
|
||||
image?: string | ArrayBuffer | null; // Изображение может быть в формате base64
|
||||
}
|
||||
|
||||
export default function ReviewForm() {
|
||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1, image: null });
|
||||
export default function ReviewForm({ productId }: { productId: string }) {
|
||||
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1 });
|
||||
const [userId, setUserId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const userIdFromCookie = Cookies.get('id');
|
||||
if (userIdFromCookie) {
|
||||
setUserId(userIdFromCookie);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function handleTextChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
setReview({ ...review, text: event.target.value });
|
||||
@@ -21,59 +31,85 @@ export default function ReviewForm() {
|
||||
}
|
||||
|
||||
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (event.target.files) {
|
||||
setReview({ ...review, image: event.target.files[0] });
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setReview({ ...review, image: reader.result });
|
||||
};
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(event: React.FormEvent) {
|
||||
async function handleSubmit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
console.log(review);
|
||||
if (!userId) {
|
||||
console.error('User ID not found!');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('product_id', productId);
|
||||
formData.append('text', review.text);
|
||||
formData.append('rating', String(review.rating));
|
||||
if (review.image) {
|
||||
formData.append('image', review.image as string);
|
||||
}
|
||||
formData.append('user_id', userId);
|
||||
await axios.post('http://127.0.0.1:8000/api/post/review', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
console.log('Review submitted successfully!');
|
||||
// Опционально: добавить логику для обновления списка отзывов на странице после успешной отправки
|
||||
} catch (error) {
|
||||
console.error('Error submitting review:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return(
|
||||
|
||||
return (
|
||||
<form className='product-page__review-form' onSubmit={handleSubmit}>
|
||||
<h5 className='review-form__heading'>
|
||||
Оставить отзыв
|
||||
</h5>
|
||||
<div className="review-form__stars-container">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<input
|
||||
<input
|
||||
key={index}
|
||||
type="radio"
|
||||
className="review-form__star-radio"
|
||||
name="rating"
|
||||
value={index + 1}
|
||||
aria-label={`Рейтинг ${index + 1}`}
|
||||
checked={review.rating === index + 1}
|
||||
type="radio"
|
||||
className="review-form__star-radio"
|
||||
name="rating"
|
||||
value={index + 1}
|
||||
aria-label={`Рейтинг ${index + 1}`}
|
||||
checked={review.rating === index + 1}
|
||||
onChange={handleRatingChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
className='review-form__textarea'
|
||||
cols={30}
|
||||
rows={5}
|
||||
placeholder='Комментарий'
|
||||
value={review.text}
|
||||
<textarea
|
||||
className='review-form__textarea'
|
||||
cols={30}
|
||||
rows={5}
|
||||
placeholder='Комментарий'
|
||||
value={review.text}
|
||||
onChange={handleTextChange}
|
||||
/>
|
||||
<label htmlFor="review-image" className='review-form__image-attach'>
|
||||
<img src={ImageAttachIcon} alt="Прикрепить изображение"/>
|
||||
<img src={ImageAttachIcon} alt="Прикрепить изображение" />
|
||||
</label>
|
||||
<input
|
||||
className='review-form__image-input'
|
||||
type="file"
|
||||
name="review image"
|
||||
id="review-image"
|
||||
accept='.png, .jpg, .jpeg'
|
||||
<input
|
||||
className='review-form__image-input'
|
||||
type="file"
|
||||
name="review image"
|
||||
id="review-image"
|
||||
accept='.png, .jpg, .jpeg'
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
<motion.button
|
||||
className='review-form__send-button'
|
||||
<motion.button
|
||||
className='review-form__send-button'
|
||||
type='submit'
|
||||
whileTap={{scale: 0.98}}
|
||||
transition={{duration: 0.2, type: "spring"}}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ duration: 0.2, type: "spring" }}
|
||||
>
|
||||
Отправить отзыв
|
||||
</motion.button>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import '../HomeStyle.scss';
|
||||
import ProductCard from "../components/ProductCard";
|
||||
import Banner from "../components/AdBanner";
|
||||
import { Product } from "../utils/types";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type HomePageProps = {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
export default function HomePage({ products }: HomePageProps) { //соответствие типов данных в products с указанными типами и свойствами в интерфейсе Product
|
||||
export default function HomePage({ products }: HomePageProps) {
|
||||
return(
|
||||
<section className="home-page">
|
||||
<Banner />
|
||||
@@ -18,16 +18,17 @@ export default function HomePage({ products }: HomePageProps) { //соответ
|
||||
<Link to={`/product/${product.id}`} key={product.id}>
|
||||
<ProductCard
|
||||
title={product.title}
|
||||
icons={product.icons}
|
||||
price={product.price}
|
||||
category_id={product.category_id}
|
||||
id={product.id}
|
||||
description={product.description}
|
||||
tags={product.tags}
|
||||
id={product.id}
|
||||
category_id={product.category_id}
|
||||
price={product.price}
|
||||
icons={product.icons}
|
||||
description={product.description}
|
||||
/>
|
||||
</Link>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React from "react";
|
||||
import DevCard from "../components/DevCard";
|
||||
import "../InfoPageStyle.scss";
|
||||
import RailTHAvatar from "../assets/img/info-page__railth-avatar.png"
|
||||
import NoKesspenAvatar from "../assets/img/info-page__no-kesspen-avatar.png"
|
||||
import RailTHAvatar from "../assets/img/info-page__railth-avatar.png";
|
||||
import NoKesspenAvatar from "../assets/img/info-page__no-kesspen-avatar.png";
|
||||
|
||||
export default function InfoPage() {
|
||||
return(
|
||||
return (
|
||||
<section className="info-page">
|
||||
{/* Компонент DevCard отображает информацию о разработчике */}
|
||||
<DevCard
|
||||
avatar={NoKesspenAvatar}
|
||||
name="No_Kesspen"
|
||||
info="Backend разработчик"
|
||||
info="Backend & Frontend разработчик"
|
||||
url="https://github.com/KessPenGames"
|
||||
/>
|
||||
<DevCard
|
||||
@@ -20,5 +21,5 @@ export default function InfoPage() {
|
||||
url="https://github.com/Rail-TH"
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import '../PaymentStyle.scss';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
// Компонент страницы оплаты
|
||||
export default function PaymentPage() {
|
||||
const [ccNumber, setCcNumber] = useState(""); //состояние номера кредитной карты
|
||||
const [valueDate, setValueDate] = useState<number | ''>(''); //состояние даты истечения срока карты
|
||||
const [valueCode, setValueCode] = useState<number | ''>(''); //состояние кода безопасности карты
|
||||
|
||||
const formatAndSetCcNumber = (e: React.ChangeEvent<HTMLInputElement>) => { //в ccNumber максимум 16 цифр, разбивка пробелами по 4
|
||||
// Состояние номера кредитной карты
|
||||
const [ccNumber, setCcNumber] = useState("");
|
||||
// Состояние даты истечения срока карты
|
||||
const [valueDate, setValueDate] = useState<number | ''>('');
|
||||
// Состояние кода безопасности карты
|
||||
const [valueCode, setValueCode] = useState<number | ''>('');
|
||||
// Получение параметров из URL
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
// Получение цены из параметров URL
|
||||
const price = queryParams.get('price');
|
||||
|
||||
// Функция для форматирования и установки номера кредитной карты
|
||||
const formatAndSetCcNumber = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputVal = e.target.value.replace(/ /g, "");
|
||||
let inputNumbersOnly = inputVal.replace(/\D/g, "");
|
||||
|
||||
|
||||
if (inputNumbersOnly.length > 16) {
|
||||
inputNumbersOnly = inputNumbersOnly.substr(0, 16);
|
||||
}
|
||||
|
||||
const splits = inputNumbersOnly.match(/.{1,4}/g);
|
||||
|
||||
|
||||
let spacedNumber = "";
|
||||
if (splits) {
|
||||
spacedNumber = splits.join(" ");
|
||||
}
|
||||
|
||||
|
||||
setCcNumber(spacedNumber);
|
||||
};
|
||||
|
||||
const handleChangeDate = (event: React.ChangeEvent<HTMLInputElement>) => { //valueDate не более 4-х цифр
|
||||
// Функция для обработки изменения даты истечения срока карты
|
||||
const handleChangeDate = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = event.target.value;
|
||||
if (inputValue.length <= 4) {
|
||||
setValueDate(inputValue === '' ? '' : Number(inputValue));
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeCode = (event: React.ChangeEvent<HTMLInputElement>) => { //valueCode не более 3-х цифр
|
||||
// Функция для обработки изменения кода безопасности карты
|
||||
const handleChangeCode = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = event.target.value;
|
||||
if (inputValue.length <= 3) {
|
||||
setValueCode(inputValue === '' ? '' : Number(inputValue));
|
||||
@@ -40,22 +53,21 @@ export default function PaymentPage() {
|
||||
|
||||
return(
|
||||
<section className="payment-page">
|
||||
<h2 className="payment-page__price">
|
||||
₽
|
||||
</h2>
|
||||
{/* Отображение цены */}
|
||||
<h2 className="payment-page__price">{price} ₽ </h2>
|
||||
<div className="payment-page__payment-card">
|
||||
<h3 className="payment-card__heading">
|
||||
Оплата картой
|
||||
</h3>
|
||||
<input className="payment-card__input" type="text" name="" id="" placeholder="Номер" value={ccNumber} onChange={formatAndSetCcNumber}/>
|
||||
<h3 className="payment-card__heading"> Оплата картой </h3>
|
||||
{/* Ввод номера кредитной карты */}
|
||||
<input className="payment-card__input" type="text" placeholder="Номер" value={ccNumber} onChange={formatAndSetCcNumber}/>
|
||||
<div className="payment-card__inputs-group">
|
||||
<input className="payment-card__input" type="number" name="" id="" placeholder="ММ/ГГ" value={valueDate} onChange={handleChangeDate}/>
|
||||
<input className="payment-card__input" type="number" name="" id="" placeholder="CVC/CVV" value={valueCode} onChange={handleChangeCode}/>
|
||||
{/* Ввод даты истечения срока карты */}
|
||||
<input className="payment-card__input" type="number" placeholder="ММ/ГГ" value={valueDate} onChange={handleChangeDate}/>
|
||||
{/* Ввод кода безопасности карты */}
|
||||
<input className="payment-card__input" type="number" placeholder="CVC/CVV" value={valueCode} onChange={handleChangeCode}/>
|
||||
</div>
|
||||
</div>
|
||||
<a href="scam" className="payment-page__pay-link">
|
||||
Оплатить
|
||||
</a>
|
||||
{/* Кнопка для оплаты */}
|
||||
<a href="scam" className="payment-page__pay-link"> Оплатить </a>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import axios from 'axios';
|
||||
import '../ProductStyle.scss';
|
||||
import ShareIcon from "../assets/icons/share-icon.svg";
|
||||
import ReviewForm from '../components/ReviewForm';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
|
||||
export default function ProductPage() {
|
||||
function trimText(text: string, limit: number) { //сокращение описания
|
||||
@@ -87,9 +87,9 @@ export default function ProductPage() {
|
||||
<span className="product-page__price-span">
|
||||
{product.price} ₽
|
||||
</span>
|
||||
<a href="/payment" className="product-page__buy-link">
|
||||
<Link to={`/payment?price=${product.price}`} className="product-page__buy-link">
|
||||
Купить
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ export default function ProductPage() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<ReviewForm />
|
||||
<ReviewForm productId={product.id.toLocaleString('ru-RU')}/>
|
||||
<div className='product-page__reviews-container'>
|
||||
{reviews.map((review) => (
|
||||
<Review key={review.id} review={review} />
|
||||
@@ -153,4 +153,5 @@ export default function ProductPage() {
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ export default function ProfilePage() {
|
||||
</Routes>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user