Star rate in reviews, login menu & other fixes
Added star rate to reviews, coding login menu, coding review form & other fixes
@@ -77,62 +77,12 @@ $accent-color: #EB5E28;
|
|||||||
border: 2px solid $main-color;
|
border: 2px solid $main-color;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
.rating-div__stars-container {
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 30px;
|
|
||||||
background-image: url(assets/icons/rating__star-icon.svg);
|
|
||||||
background-size: 30px auto;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
|
|
||||||
.rating-div__star-radio {
|
|
||||||
appearance: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-div__star-radio:checked, .rating-div__star-radio:hover {
|
|
||||||
background-image: url(assets/icons/rating__filled-star-icon.svg);
|
|
||||||
background-size: 30px auto;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-div__star-radio:hover ~ .rating-div__star-radio {
|
|
||||||
background-image: url(assets/icons/rating__star-icon.svg);
|
|
||||||
background-size: 30px auto;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-div__star-radio:nth-of-type(1) {
|
|
||||||
z-index: 5;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
.rating-div__star-radio:nth-of-type(2) {
|
|
||||||
z-index: 4;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
.rating-div__star-radio:nth-of-type(3) {
|
|
||||||
z-index: 3;
|
|
||||||
width: 90px;
|
|
||||||
}
|
|
||||||
.rating-div__star-radio:nth-of-type(4) {
|
|
||||||
z-index: 2;
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
.rating-div__star-radio:nth-of-type(5) {
|
|
||||||
z-index: 1;
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-div__rate-value {
|
.rating-div__rate-value {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +146,7 @@ $accent-color: #EB5E28;
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +165,7 @@ $accent-color: #EB5E28;
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,16 +182,18 @@ $accent-color: #EB5E28;
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
width: 310px;
|
width: 310px;
|
||||||
|
|
||||||
.rate-block__rating {
|
.rate-block__rating {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
.rate-block__rate-number {
|
.rate-block__rate-number {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +243,7 @@ $accent-color: #EB5E28;
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
.progressbars-group__progressbar-container {
|
.progressbars-group__progressbar-container {
|
||||||
@@ -324,10 +276,107 @@ $accent-color: #EB5E28;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-page__review-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 48px;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.review-form__heading {
|
||||||
|
color: white;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__stars-container {
|
||||||
|
position: relative;
|
||||||
|
width: 150px;
|
||||||
|
height: 30px;
|
||||||
|
background-image: url(assets/icons/rating__star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
|
||||||
|
.review-form__star-radio {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__star-radio:checked, .review-form__star-radio:hover {
|
||||||
|
background-image: url(assets/icons/rating__filled-star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__star-radio:hover ~ .review-form__star-radio {
|
||||||
|
background-image: url(assets/icons/rating__star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__star-radio:nth-of-type(1) {
|
||||||
|
z-index: 5;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
.review-form__star-radio:nth-of-type(2) {
|
||||||
|
z-index: 4;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.review-form__star-radio:nth-of-type(3) {
|
||||||
|
z-index: 3;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
.review-form__star-radio:nth-of-type(4) {
|
||||||
|
z-index: 2;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.review-form__star-radio:nth-of-type(5) {
|
||||||
|
z-index: 1;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 2px solid $main-color;
|
||||||
|
border-radius: 15px;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__image-attach {
|
||||||
|
width: 34px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__image-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-form__send-button {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
background-color: $accent-color;
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.product-page__reviews-container {
|
.product-page__reviews-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
|
margin-bottom: 30px;
|
||||||
gap: 50px;
|
gap: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +386,7 @@ $accent-color: #EB5E28;
|
|||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 39px;
|
line-height: 39px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip3_51">
|
<clipPath id="clip3_51">
|
||||||
<rect id="location_on_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
<rect id="location_on_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,7 +1,4 @@
|
|||||||
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip6_70">
|
<clipPath id="clip6_70">
|
||||||
<rect id="info_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
<rect id="info_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,7 +1,4 @@
|
|||||||
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip3_40">
|
<clipPath id="clip3_40">
|
||||||
<rect id="person_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
<rect id="person_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fill-opacity="0"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-330q0-17 11.5-28.5T400-720q17 0 28.5 11.5T440-680v330q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-350q0-17 11.5-28.5T680-720q17 0 28.5 11.5T720-680v350Z" fill="#CCC5B9"/></svg>
|
||||||
|
After Width: | Height: | Size: 515 B |
@@ -3,6 +3,7 @@ import { motion } from "framer-motion";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Logotype from "../assets/img/amongasik.png";
|
import Logotype from "../assets/img/amongasik.png";
|
||||||
import CatalogMenu from "./CatalogMenu";
|
import CatalogMenu from "./CatalogMenu";
|
||||||
|
import LoginMenu from "./LoginMenu";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
togglePopupMap: () => void;
|
togglePopupMap: () => void;
|
||||||
@@ -12,18 +13,33 @@ interface HeaderCatalogMenuState {
|
|||||||
isCatalogMenuVisible: boolean;
|
isCatalogMenuVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HeaderLoginMenuState {
|
||||||
|
isLoginMenuVisible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
||||||
const [state, setState] = useState<HeaderCatalogMenuState>({
|
const [stateCatalog, setStateCatalog] = useState<HeaderCatalogMenuState>({
|
||||||
isCatalogMenuVisible: false,
|
isCatalogMenuVisible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [stateLogin, setStateLogin] = useState<HeaderLoginMenuState>({
|
||||||
|
isLoginMenuVisible: false,
|
||||||
|
});
|
||||||
|
|
||||||
const toggleCatalogMenu = () => {
|
const toggleCatalogMenu = () => {
|
||||||
setState((prevState) => ({
|
setStateCatalog((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
isCatalogMenuVisible: !prevState.isCatalogMenuVisible,
|
isCatalogMenuVisible: !prevState.isCatalogMenuVisible,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleLoginMenu = () => {
|
||||||
|
setStateLogin((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
isLoginMenuVisible: !prevState.isLoginMenuVisible,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<nav className="header__main-nav">
|
<nav className="header__main-nav">
|
||||||
@@ -60,18 +76,17 @@ export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
|||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<Link
|
<motion.button
|
||||||
to="/profile"
|
|
||||||
className="header__profile-a"
|
className="header__profile-a"
|
||||||
|
whileTap={{scale: 0.9}}
|
||||||
|
transition={{duration: 0.2, type: "spring"}}
|
||||||
|
onClick={toggleLoginMenu}
|
||||||
>
|
>
|
||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
<motion.svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none"
|
<motion.svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none"
|
||||||
whileTap={{scale: 0.9}}
|
whileTap={{scale: 0.9}}
|
||||||
transition={{duration: 0.2, type: "spring"}}
|
transition={{duration: 0.2, type: "spring"}}
|
||||||
>
|
>
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip3_40">
|
<clipPath id="clip3_40">
|
||||||
<rect id="person_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
<rect id="person_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
||||||
@@ -83,7 +98,7 @@ export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
|||||||
</g>
|
</g>
|
||||||
</motion.svg>
|
</motion.svg>
|
||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
</Link>
|
</motion.button>
|
||||||
<motion.button
|
<motion.button
|
||||||
className="header__popupmap-button"
|
className="header__popupmap-button"
|
||||||
whileTap={{scale: 0.9}}
|
whileTap={{scale: 0.9}}
|
||||||
@@ -92,9 +107,6 @@ export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
|||||||
>
|
>
|
||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none">
|
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip3_51">
|
<clipPath id="clip3_51">
|
||||||
<rect id="location_on_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
<rect id="location_on_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
||||||
@@ -115,9 +127,6 @@ export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
|||||||
>
|
>
|
||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none">
|
<svg width="48.000000" height="48.000000" viewBox="0 0 48 48" fill="none">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip6_70">
|
<clipPath id="clip6_70">
|
||||||
<rect id="info_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
<rect id="info_FILL0_wght400_GRAD0_opsz48 1" width="48.000000" height="48.000000" fill="white" fillOpacity="0"/>
|
||||||
@@ -131,7 +140,8 @@ export default function Header({ togglePopupMap }: HeaderProps): JSX.Element {
|
|||||||
{/* Код для svg */}
|
{/* Код для svg */}
|
||||||
</motion.a>
|
</motion.a>
|
||||||
</nav>
|
</nav>
|
||||||
{state.isCatalogMenuVisible && <CatalogMenu toggleCatalogMenu={toggleCatalogMenu}/>}
|
{stateCatalog.isCatalogMenuVisible && <CatalogMenu toggleCatalogMenu={toggleCatalogMenu}/>}
|
||||||
|
{stateLogin.isLoginMenuVisible && <LoginMenu toggleLoginMenu={toggleLoginMenu}/>}
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,42 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
export default function LoginMenu() {
|
interface LoginMenuProps {
|
||||||
|
toggleLoginMenu: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoginMenu({ toggleLoginMenu }: LoginMenuProps): JSX.Element {
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
|
<div className="background-blackout" onClick={toggleLoginMenu}></div>
|
||||||
|
<form className="popup-login">
|
||||||
|
<div className="popup-login__top-container">
|
||||||
|
<div className="top-container__headings-text">
|
||||||
|
<h5 className="popup-menu__heading">
|
||||||
|
SusMarket <span>ID</span>
|
||||||
|
</h5>
|
||||||
|
<p className="top-container__text">
|
||||||
|
Войдите с SusMarket ID
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="popup-login__inputs-container">
|
||||||
|
<input type="text" name="userName" id="userName" className="popup-login__name-input" placeholder="Логин"/>
|
||||||
|
<input type="password" name="userPassword" id="userPassword" className="popup-login__password-input" placeholder="Пароль"/>
|
||||||
|
</div>
|
||||||
|
<div className="popup-login__bottom-container">
|
||||||
|
<p className="popup-menu__prompt-url">
|
||||||
|
У вас нет аккаунта? <u>Зарегестрироваться</u>
|
||||||
|
</p>
|
||||||
|
<motion.button
|
||||||
|
className="popup-login__login-button"
|
||||||
|
whileTap={{scale: 0.98}}
|
||||||
|
transition={{duration: 0.2, type: "spring"}}
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,44 @@ export default function Review() {
|
|||||||
Святослав Васильев
|
Святослав Васильев
|
||||||
</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">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="star-rate__star-radio"
|
||||||
|
name="review-rating"
|
||||||
|
value={1}
|
||||||
|
aria-label="Плохо"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="star-rate__star-radio"
|
||||||
|
name="review-rating"
|
||||||
|
value={2}
|
||||||
|
aria-label="Удовлетворительно"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="star-rate__star-radio"
|
||||||
|
name="review-rating"
|
||||||
|
value={3}
|
||||||
|
aria-label="Нормально"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="star-rate__star-radio"
|
||||||
|
name="review-rating"
|
||||||
|
value={4}
|
||||||
|
aria-label="Хорошо"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="star-rate__star-radio"
|
||||||
|
name="review-rating"
|
||||||
|
value={5}
|
||||||
|
aria-label="Отлично"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<time className="review-info__review-date" dateTime="2019-09-09">
|
<time className="review-info__review-date" dateTime="2019-09-09">
|
||||||
09.09.2019
|
09.09.2019
|
||||||
|
|||||||
82
reactapp/src/components/ReviewForm.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import '../ProductStyle.scss';
|
||||||
|
import ImageAttachIcon from "../assets/icons/review-form__add-image-icon.svg";
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
interface ReviewState {
|
||||||
|
text: string;
|
||||||
|
rating: number;
|
||||||
|
image: File | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReviewForm() {
|
||||||
|
const [review, setReview] = useState<ReviewState>({ text: '', rating: 1, image: null });
|
||||||
|
|
||||||
|
function handleTextChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||||
|
setReview({ ...review, text: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRatingChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
setReview({ ...review, rating: Number(event.target.value) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
if (event.target.files) {
|
||||||
|
setReview({ ...review, image: event.target.files[0] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(event: React.FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log(review);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
key={index}
|
||||||
|
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}
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
<motion.button
|
||||||
|
className='review-form__send-button'
|
||||||
|
type='submit'
|
||||||
|
whileTap={{scale: 0.98}}
|
||||||
|
transition={{duration: 0.2, type: "spring"}}
|
||||||
|
>
|
||||||
|
Отправить отзыв
|
||||||
|
</motion.button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -340,7 +340,7 @@ body {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,53 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.review-info__star-rate {
|
.review-info__star-rate {
|
||||||
display: flex;
|
position: relative;
|
||||||
|
width: 150px;
|
||||||
|
height: 30px;
|
||||||
|
background-image: url(assets/icons/rating__star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
|
||||||
|
.star-rate__star-radio {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rate__star-radio:checked, .rating-div__star-radio:hover {
|
||||||
|
background-image: url(assets/icons/rating__filled-star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rate__star-radio:hover ~ .rating-div__star-radio {
|
||||||
|
background-image: url(assets/icons/rating__star-icon.svg);
|
||||||
|
background-size: 30px auto;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rate__star-radio:nth-of-type(1) {
|
||||||
|
z-index: 5;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
.star-rate__star-radio:nth-of-type(2) {
|
||||||
|
z-index: 4;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.star-rate__star-radio:nth-of-type(3) {
|
||||||
|
z-index: 3;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
.star-rate__star-radio:nth-of-type(4) {
|
||||||
|
z-index: 2;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.star-rate__star-radio:nth-of-type(5) {
|
||||||
|
z-index: 1;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-info__review-date {
|
.review-info__review-date {
|
||||||
@@ -360,7 +406,7 @@ body {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +417,7 @@ body {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
letter-spacing: 0%;
|
letter-spacing: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,3 +426,118 @@ body {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-login {
|
||||||
|
width: 450px;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 108px;
|
||||||
|
transform:translateX(-50%);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 40px;
|
||||||
|
padding: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.25);
|
||||||
|
background: $background-color;
|
||||||
|
|
||||||
|
.popup-login__top-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.popup-login__close-button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50px;
|
||||||
|
background: rgb(53, 52, 49);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-container__headings-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.popup-menu__heading {
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 29px;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $accent-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-container__text {
|
||||||
|
color: $main-color;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-login__inputs-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.popup-login__name-input, .popup-login__password-input {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid $main-color;
|
||||||
|
border-radius: 15px;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 29px;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-login__bottom-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.popup-menu__prompt-url {
|
||||||
|
color: $main-color;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-login__login-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: rgb(235, 94, 40);
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 29px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import Review from '../components/Review';
|
import Review from '../components/Review';
|
||||||
import '../ProductStyle.scss';
|
import '../ProductStyle.scss';
|
||||||
import ProductImage from "../assets/img/product-image-1.webp";
|
import ProductImage from "../assets/img/product-image-1.webp";
|
||||||
import ShareIcon from "../assets/icons/share-icon.svg";
|
import ShareIcon from "../assets/icons/share-icon.svg";
|
||||||
|
import ReviewForm from '../components/ReviewForm';
|
||||||
|
|
||||||
interface StarRatingProps {
|
interface StarRatingProps {
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProductPage({ value }: StarRatingProps) {
|
export default function ProductPage({ value }: StarRatingProps) {
|
||||||
const [selectedRating, setSelectedRating] = useState(1);
|
|
||||||
|
|
||||||
const handleRatingChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setSelectedRating(Number(event.target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<section className="product-page">
|
<section className="product-page">
|
||||||
<section className="product-page__main-section">
|
<section className="product-page__main-section">
|
||||||
@@ -32,54 +27,6 @@ export default function ProductPage({ value }: StarRatingProps) {
|
|||||||
</span>
|
</span>
|
||||||
<div className="product-page__container-div">
|
<div className="product-page__container-div">
|
||||||
<div className="product-page__rating-share-div">
|
<div className="product-page__rating-share-div">
|
||||||
<div className="product-page__rating-div">
|
|
||||||
<div className="rating-div__stars-container">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="rating-div__star-radio"
|
|
||||||
name="rating" value={1}
|
|
||||||
aria-label="Плохо"
|
|
||||||
checked={selectedRating === 1}
|
|
||||||
onChange={handleRatingChange}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="rating-div__star-radio"
|
|
||||||
name="rating" value={2}
|
|
||||||
aria-label="Удовлетворительно"
|
|
||||||
checked={selectedRating === 2}
|
|
||||||
onChange={handleRatingChange}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="rating-div__star-radio"
|
|
||||||
name="rating" value={3}
|
|
||||||
aria-label="Нормально" checked={selectedRating === 3}
|
|
||||||
onChange={handleRatingChange}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="rating-div__star-radio"
|
|
||||||
name="rating"
|
|
||||||
value={4}
|
|
||||||
aria-label="Хорошо"
|
|
||||||
checked={selectedRating === 4}
|
|
||||||
onChange={handleRatingChange}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="rating-div__star-radio"
|
|
||||||
name="rating"
|
|
||||||
value={5}
|
|
||||||
aria-label="Отлично"
|
|
||||||
checked={selectedRating === 5}
|
|
||||||
onChange={handleRatingChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="rating-div__rate-value">
|
|
||||||
{selectedRating}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button className="product-page__share-button">
|
<button className="product-page__share-button">
|
||||||
<img src={ShareIcon as unknown as string} alt="" />
|
<img src={ShareIcon as unknown as string} alt="" />
|
||||||
</button>
|
</button>
|
||||||
@@ -182,12 +129,9 @@ export default function ProductPage({ value }: StarRatingProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ReviewForm />
|
||||||
<div className='product-page__reviews-container'>
|
<div className='product-page__reviews-container'>
|
||||||
<Review />
|
<Review />
|
||||||
<Review />
|
|
||||||
<Review />
|
|
||||||
<Review />
|
|
||||||
<Review />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
2
reactapp/src/utils/custom.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
declare module "*.svg" {
|
declare module "*.svg" {
|
||||||
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
|
const content: any;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||