merge design-act into dev

This commit is contained in:
Nastya 2023-07-26 01:31:04 +03:00
parent 0c398d7252
commit ac1af76c2b
43 changed files with 2375 additions and 1161 deletions

@ -15,8 +15,10 @@
"@frontend/kitui": "^1.0.16",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@popperjs/core": "^2.11.8",
"axios": "^1.4.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"formik": "^2.2.9",
"immer": "^10.0.2",
"isomorphic-fetch": "^3.0.0",
@ -26,6 +28,8 @@
"react-dom": "^18.2.0",
"react-pdf": "^7.1.2",
"react-router-dom": "^6.4.3",
"react-slick": "^0.29.0",
"slick-carousel": "^1.8.1",
"web-vitals": "^2.1.0",
"yup": "^1.1.1",
"zustand": "^4.3.8"
@ -39,6 +43,7 @@
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/react-slick": "^0.23.10",
"craco-alias": "^3.0.1",
"jest": "^29.5.0",
"react-scripts": "5.0.1",

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#7E2AEA;stroke-width:2;stroke-miterlimit:10;}
.st1{fill:none;stroke:#7E2AEA;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
</style>
<g>
<path class="st0" d="M6.3,24.7C6.3,14.4,14.7,6,25.1,6s18.8,8.4,18.8,18.8s-8.4,18.8-18.8,18.8S6.3,35.1,6.3,24.7z"/>
<path class="st1" d="M27.4,17.7l-7.8,7l7.8,7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 725 B

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#7E2AEA;stroke-width:2;stroke-miterlimit:10;}
.st1{fill:none;stroke:#7E2AEA;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
</style>
<g>
<path class="st0" d="M43.8,24.7c0,10.4-8.4,18.8-18.8,18.8S6.3,35.1,6.3,24.7S14.7,6,25.1,6S43.8,14.4,43.8,24.7z"/>
<path class="st1" d="M22.7,31.8l7.8-7l-7.8-7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 724 B

@ -0,0 +1,5 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.25 17.125C7.94036 17.125 8.5 16.5654 8.5 15.875C8.5 15.1846 7.94036 14.625 7.25 14.625C6.55964 14.625 6 15.1846 6 15.875C6 16.5654 6.55964 17.125 7.25 17.125Z" fill="#9A9AAF"/>
<path d="M15.375 17.125C16.0654 17.125 16.625 16.5654 16.625 15.875C16.625 15.1846 16.0654 14.625 15.375 14.625C14.6846 14.625 14.125 15.1846 14.125 15.875C14.125 16.5654 14.6846 17.125 15.375 17.125Z" fill="#9A9AAF"/>
<path d="M4.30469 4.625H18.3203L16.2578 11.8437C16.1842 12.1057 16.0266 12.3363 15.8093 12.5C15.5919 12.6638 15.3268 12.7516 15.0547 12.75H7.57031C7.29819 12.7516 7.03308 12.6638 6.81572 12.5C6.59836 12.3363 6.44078 12.1057 6.36719 11.8437L3.53906 1.95313C3.50169 1.82246 3.42275 1.70754 3.3142 1.62578C3.20565 1.54401 3.0734 1.49986 2.9375 1.5H1.625" stroke="#9A9AAF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 948 B

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:none;stroke:#7E2AEA;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;}
</style>
<g>
<path class="st0" d="M18,36L18,36C8.1,36,0,27.9,0,18l0,0C0,8.1,8.1,0,18,0l0,0c9.9,0,18,8.1,18,18l0,0C36,27.9,27.9,36,18,36z"/>
</g>
<path class="st1" d="M10.9,15.2L18,23l7-7.8"/>
</svg>

After

Width:  |  Height:  |  Size: 686 B

@ -1,84 +1,103 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ExpandIcon from "./icons/ExpandIcon";
import type { ReactNode } from "react";
interface Props {
header: string;
text: string;
header: ReactNode;
text: string;
divide?: boolean;
}
export default function CustomAccordion({ header, text }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upXs = useMediaQuery(theme.breakpoints.up("xs"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
export default function CustomAccordion({
header,
text,
divide = false,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upXs = useMediaQuery(theme.breakpoints.up("xs"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
return (
return (
<Box
sx={{
backgroundColor: "white",
"&:first-of-type": {
borderTopLeftRadius: "12px",
borderTopRightRadius: "12px",
},
"&:last-of-type": {
borderBottomLeftRadius: "12px",
borderBottomRightRadius: "12px",
},
"&:not(:last-of-type)": {
borderBottom: `1px solid ${theme.palette.grey2.main}`,
},
}}
>
<Box
onClick={() => setIsExpanded((prev) => !prev)}
sx={{
minHeight: "72px",
px: "20px",
display: "flex",
alignItems: "stretch",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
rowGap: "10px",
flexDirection: upXs ? undefined : "column",
}}
>
<Box
sx={{
backgroundColor: "white",
"&:first-of-type": {
borderTopLeftRadius: "12px",
borderTopRightRadius: "12px",
},
"&:last-of-type": {
borderBottomLeftRadius: "12px",
borderBottomRightRadius: "12px",
},
"&:not(:last-of-type)": {
borderBottom: `1px solid ${theme.palette.grey2.main}`,
}
}}
sx={{
display: "flex",
alignItems: "center",
width: "100%",
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.grey3.main,
px: 0,
}}
>
<Box
onClick={() => setIsExpanded(prev => !prev)}
sx={{
height:upMd ? "72px" : undefined,
px: "20px",
pt: upMd ? "29px" : "26px",
pb: upMd ? "21px" : "20px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
rowGap:"10px",
flexDirection: upXs ? undefined :"column",
}}
>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.grey3.main,
px: 0,
}}
>{header}</Typography>
<Box sx={{width:"32px", height:"33px"}}>
<ExpandIcon isExpanded={isExpanded} />
</Box>
</Box>
{isExpanded &&
<Box
sx={{
px: "20px",
py: upMd ? "25px" : undefined,
pt: upMd ? undefined : "15px",
pb: upMd ? undefined : "25px",
backgroundColor: "#F1F2F6"
}}
>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>{text}</Typography>
</Box>
}
{header}
</Box>
);
}
<Box
sx={{
pl: "20px",
width: "52px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderLeft: divide ? "1px solid #000000" : "none",
}}
>
<ExpandIcon isExpanded={isExpanded} />
</Box>
</Box>
{isExpanded && (
<Box
sx={{
px: "20px",
py: upMd ? "25px" : undefined,
pt: upMd ? undefined : "15px",
pb: upMd ? undefined : "25px",
backgroundColor: "#F1F2F6",
}}
>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>
{text}
</Typography>
</Box>
)}
</Box>
);
}

@ -1,4 +1,14 @@
import { Typography, Drawer, useMediaQuery, useTheme, Box, IconButton, SvgIcon, Icon } from "@mui/material";
import {
Typography,
Drawer,
useMediaQuery,
useTheme,
Box,
IconButton,
SvgIcon,
Icon,
Badge,
} from "@mui/material";
import { IconsCreate } from "@root/lib/IconsCreate";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ClearIcon from "@mui/icons-material/Clear";
@ -9,186 +19,237 @@ import CustomButton from "./CustomButton";
import { useNavigate } from "react-router";
import { useCart } from "@root/utils/hooks/useCart";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { closeCartDrawer, openCartDrawer, useCartStore } from "@root/stores/cart";
import {
closeCartDrawer,
openCartDrawer,
useCartStore,
} from "@root/stores/cart";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
type DrawersProps = {
cartItemsAmount?: number;
};
export default function Drawers() {
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isDrawerOpen = useCartStore(state => state.isDrawerOpen);
const cart = useCart();
const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap);
const summaryPriceAfterDiscountsMap = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap);
export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isDrawerOpen = useCartStore((state) => state.isDrawerOpen);
const cart = useCart();
const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(
(state) => state.summaryPriceBeforeDiscountsMap
);
const summaryPriceAfterDiscountsMap = useCustomTariffsStore(
(state) => state.summaryPriceAfterDiscountsMap
);
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0);
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce(
(a, e) => a + e,
0
);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce(
(a, e) => a + e,
0
);
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice;
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice;
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
return (
<IconButton sx={{ p: 0 }}>
<Typography onClick={openCartDrawer} component="div" sx={{ position: "absolute" }}>
<IconsCreate svg={BasketIcon} bgcolor="#F2F3F7" />
</Typography>
{cart.itemCount && (
<Icon
component="div"
sx={{
position: "relative",
left: "8px",
bottom: "7px",
width: "16px",
height: "16px",
backgroundColor: "#7E2AEA",
borderRadius: "12px",
}}
>
<Typography
component="div"
sx={{
display: "flex",
fontSize: "12px",
mt: "4.5px",
width: "100%",
height: "9px",
color: "white",
alignItems: "center",
justifyContent: "center",
}}
>
{cart.itemCount}
</Typography>
</Icon>
return (
<IconButton sx={{ p: 0 }}>
<Typography
onClick={openCartDrawer}
component="div"
sx={{
position: "absolute",
"&:hover": {
"& .MuiBox-root": {
background: theme.palette.brightPurple.main,
},
"& .MuiBadge-badge": {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
},
}}
>
<Badge
badgeContent={cartItemsAmount}
sx={{
"& .MuiBadge-badge": {
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
top: "10px",
right: "10px",
fontWeight: 400,
},
}}
>
<IconsCreate svg={BasketIcon} bgcolor="#F2F3F7" />
</Badge>
</Typography>
{cart.itemCount && (
<Icon
component="div"
sx={{
position: "relative",
left: "8px",
bottom: "7px",
width: "16px",
height: "16px",
backgroundColor: "#7E2AEA",
borderRadius: "12px",
}}
>
<Typography
component="div"
sx={{
display: "flex",
fontSize: "12px",
mt: "4.5px",
width: "100%",
height: "9px",
color: "white",
alignItems: "center",
justifyContent: "center",
}}
>
{cart.itemCount}
</Typography>
</Icon>
)}
<Drawer anchor={"right"} open={isDrawerOpen} onClose={closeCartDrawer}>
<SectionWrapper
maxWidth="lg"
sx={{
pl: "0px",
pr: "0px",
width: "450px",
}}
>
<Box
sx={{
width: "100%",
pt: "20px",
pb: "20px",
display: "flex",
justifyContent: "space-between",
bgcolor: "#F2F3F7",
gap: "10px",
pl: "20px",
pr: "20px",
}}
>
{!upMd && (
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Drawer anchor={"right"} open={isDrawerOpen} onClose={closeCartDrawer}>
<SectionWrapper
maxWidth="lg"
sx={{
pl: "0px",
pr: "0px",
width: "450px",
}}
<Typography
component="div"
sx={{
fontSize: "18px",
lineHeight: "21px",
font: "Rubick",
}}
>
Корзина
</Typography>
<IconButton onClick={closeCartDrawer} sx={{ p: 0 }}>
<SvgIcon component={ClearIcon} />
</IconButton>
</Box>
<Box sx={{ pl: "20px", pr: "20px" }}>
{cart.services.map((serviceData) => (
<CustomWrapperDrawer
key={serviceData.serviceKey}
serviceData={serviceData}
/>
))}
<Box
sx={{
mt: "40px",
pt: upMd ? "30px" : undefined,
borderTop: upMd
? `1px solid ${theme.palette.grey2.main}`
: undefined,
}}
>
<Box
sx={{
width: upMd ? "100%" : undefined,
display: "flex",
flexWrap: "wrap",
flexDirection: "column",
}}
>
<Typography variant="h4" mb={upMd ? "18px" : "30px"}>
Итоговая цена
</Typography>
<Typography color={theme.palette.grey3.main}>
Текст-заполнитель это текст, который имеет Текст-заполнитель
это текст, который имеет Текст-заполнитель это текст,
который имеет Текст-заполнитель это текст, который имеет
Текст-заполнитель
</Typography>
</Box>
<Box
sx={{
color: theme.palette.grey3.main,
pb: "100px",
pt: "38px",
pl: upMd ? "20px" : undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: upMd ? "column" : "row",
alignItems: upMd ? "start" : "center",
mt: upMd ? "10px" : "30px",
gap: "15px",
}}
>
<Box
sx={{
width: "100%",
pt: "20px",
pb: "20px",
display: "flex",
justifyContent: "space-between",
bgcolor: "#F2F3F7",
gap: "10px",
pl: "20px",
pr: "20px",
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<ArrowBackIcon />
</IconButton>
)}
<Typography
component="div"
sx={{
fontSize: "18px",
lineHeight: "21px",
font: "Rubick",
}}
>
Корзина
</Typography>
<IconButton onClick={closeCartDrawer} sx={{ p: 0 }}>
<SvgIcon component={ClearIcon} />
</IconButton>
</Box>
<Box sx={{ pl: "20px", pr: "20px" }}>
{cart.services.map(serviceData =>
<CustomWrapperDrawer
key={serviceData.serviceKey}
serviceData={serviceData}
/>
)}
<Box
sx={{
mt: "40px",
pt: upMd ? "30px" : undefined,
borderTop: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}
>
<Box
sx={{
width: upMd ? "100%" : undefined,
display: "flex",
flexWrap: "wrap",
flexDirection: "column",
}}
>
<Typography variant="h4" mb={upMd ? "18px" : "30px"}>
Итоговая цена
</Typography>
<Typography color={theme.palette.grey3.main}>
Текст-заполнитель это текст, который имеет Текст-заполнитель это текст, который имеет
Текст-заполнитель это текст, который имеет Текст-заполнитель это текст, который имеет
Текст-заполнитель
</Typography>
</Box>
<Box
sx={{
color: theme.palette.grey3.main,
pb: "100px",
pt: "38px",
pl: upMd ? "20px" : undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: upMd ? "column" : "row",
alignItems: upMd ? "start" : "center",
mt: upMd ? "10px" : "30px",
gap: "15px",
}}
>
<Typography
color={theme.palette.orange.main}
sx={{
textDecoration: "line-through",
order: upMd ? 1 : 2,
}}
>
{currencyFormatter.format(totalPriceBeforeDiscounts / 100)}
</Typography>
<Typography
variant="p1"
sx={{
fontWeight: 500,
fontSize: "26px",
lineHeight: "31px",
order: upMd ? 2 : 1,
}}
>
{currencyFormatter.format(totalPriceAfterDiscounts / 100)}
</Typography>
</Box>
<CustomButton
variant="contained"
onClick={() => navigate("/basket")}
sx={{
mt: "25px",
backgroundColor: theme.palette.brightPurple.main,
}}
>
Оплатить
</CustomButton>
</Box>
</Box>
</Box>
</SectionWrapper>
</Drawer>
</IconButton>
);
<Typography
color={theme.palette.orange.main}
sx={{
textDecoration: "line-through",
order: upMd ? 1 : 2,
}}
>
{currencyFormatter.format(totalPriceBeforeDiscounts / 100)}
</Typography>
<Typography
variant="p1"
sx={{
fontWeight: 500,
fontSize: "26px",
lineHeight: "31px",
order: upMd ? 2 : 1,
}}
>
{currencyFormatter.format(totalPriceAfterDiscounts / 100)}
</Typography>
</Box>
<CustomButton
variant="contained"
onClick={() => navigate("/basket")}
sx={{
mt: "25px",
backgroundColor: theme.palette.brightPurple.main,
}}
>
Оплатить
</CustomButton>
</Box>
</Box>
</Box>
</SectionWrapper>
</Drawer>
</IconButton>
);
}

@ -80,6 +80,7 @@ export default function InputTextfield({
inputProps={{
sx: {
backgroundColor: color,
border: "1px solid" + theme.palette.grey2.main,
borderRadius: "8px",
height: "48px",
py: 0,

@ -1,18 +1,31 @@
import { useState } from "react";
import { Box, Typography, useTheme } from "@mui/material";
import { Link, useLocation } from "react-router-dom";
type MenuItem = {
name: string;
url: string;
subMenu?: MenuItem[];
};
export default function Menu() {
const [activeSubMenu, setActiveSubMenu] = useState<MenuItem[]>([]);
const theme = useTheme();
const location = useLocation();
const color = location.pathname === "/" ? "white" : "black";
const arrayMenu = [
{ name: "Тарифы", url: "/tariffs" },
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
const arrayMenu: MenuItem[] = [
{
name: "Тарифы",
url: "/tariffs",
subMenu: [
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
],
},
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
{ name: "Корзина", url: "/basket" },
];
@ -20,14 +33,30 @@ export default function Menu() {
<Box
sx={{
display: "flex",
alignItems: "center",
height: "100%",
gap: "30px",
overflow: "hidden",
}}
>
{arrayMenu.map(({ name, url }, index) => (
<Link key={name} style={{ textDecoration: "none" }} to={url}>
{arrayMenu.map(({ name, url, subMenu = [] }) => (
<Link
key={name}
style={{
textDecoration: "none",
height: "100%",
display: "flex",
alignItems: "center",
}}
to={url}
onMouseEnter={() => setActiveSubMenu(subMenu)}
>
<Typography
color={location.pathname === url ? theme.palette.brightPurple.main : color}
color={
location.pathname === url
? theme.palette.brightPurple.main
: color
}
variant="body2"
sx={{
whiteSpace: "nowrap",
@ -37,6 +66,41 @@ export default function Menu() {
</Typography>
</Link>
))}
<Box
sx={{
zIndex: "10",
position: "absolute",
top: "80px",
left: 0,
backgroundColor: theme.palette.background.paper,
width: "100%",
}}
onMouseLeave={() => setActiveSubMenu([])}
>
{activeSubMenu.map(({ name, url }) => (
<Link key={name} style={{ textDecoration: "none" }} to={url}>
<Typography
color={
location.pathname === url
? theme.palette.brightPurple.main
: color
}
variant="body2"
sx={{
padding: "15px",
whiteSpace: "nowrap",
paddingLeft: "185px",
"&:hover": {
color: theme.palette.brightPurple.main,
background: theme.palette.background.default,
},
}}
>
{name}
</Typography>
</Link>
))}
</Box>
</Box>
);
}

@ -1,42 +1,85 @@
import { Avatar, Box, IconButton, SxProps, Theme, Typography, useTheme } from "@mui/material";
import {
Avatar,
Box,
IconButton,
SxProps,
Theme,
Typography,
useTheme,
} from "@mui/material";
import { useNavigate } from "react-router-dom";
import { useUserStore } from "@root/stores/user";
interface Props {
sx?: SxProps<Theme>;
sx?: SxProps<Theme>;
}
export default function CustomAvatar({ sx }: Props) {
const theme = useTheme();
const navigate = useNavigate()
const theme = useTheme();
const navigate = useNavigate();
const { firstname, secondname } = useUserStore(
(state) => state.settingsFields
);
return (
<IconButton
onClick={() => navigate("/settings")}
sx={{
ml: "27px",
height: "36px", width: "36px",
...sx,
}}
const initials = firstname?.value?.[0] + secondname?.value?.[0];
return (
<IconButton
onClick={() => navigate("/settings")}
sx={{
ml: "27px",
height: "36px",
width: "36px",
...sx,
}}
>
<Avatar
sx={{
backgroundColor: theme.palette.orange.main,
}}
>
<Typography
sx={{
fontWeight: 500,
fontSize: "14px",
lineHeight: "20px",
zIndex: 1,
textTransform: "uppercase",
}}
>
<Avatar sx={{
backgroundColor: theme.palette.orange.main,
}}>
<Typography
sx={{
fontWeight: 500,
fontSize: "14px",
lineHeight: "20px",
zIndex: 1,
}}
>AA</Typography>
<Box sx={{ position: "absolute" }}>
<svg xmlns="http://www.w3.org/2000/svg" width="37" height="36" viewBox="0 0 37 36" fill="none">
<path fillRule="evenodd" clipRule="evenodd" d="M16.0896 15.3939C16.1897 9.41281 22.9128 5.35966 28.711 3.9153C34.7649 2.40721 41.974 3.19598 46.0209 7.93784C49.6931 12.2405 46.8503 18.5029 45.9355 24.0976C45.2565 28.2502 44.7264 32.5083 41.552 35.2692C38.1345 38.2416 32.8105 41.3312 29.1224 38.7209C25.459 36.1281 30.5336 29.8417 28.3428 25.9204C25.5777 20.9711 15.9947 21.0705 16.0896 15.3939Z" fill="#FC712F" />
<circle cx="28.7954" cy="-4.08489" r="5.51855" transform="rotate(-32.339 28.7954 -4.08489)" fill="#FC712F" />
<circle cx="25.1065" cy="28.2781" r="1.26958" transform="rotate(-32.339 25.1065 28.2781)" fill="#FC712F" />
</svg>
</Box>
</Avatar>
</IconButton>
);
}
{initials.length === 2 ? initials : "AA"}
</Typography>
<Box sx={{ position: "absolute" }}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="37"
height="36"
viewBox="0 0 37 36"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16.0896 15.3939C16.1897 9.41281 22.9128 5.35966 28.711 3.9153C34.7649 2.40721 41.974 3.19598 46.0209 7.93784C49.6931 12.2405 46.8503 18.5029 45.9355 24.0976C45.2565 28.2502 44.7264 32.5083 41.552 35.2692C38.1345 38.2416 32.8105 41.3312 29.1224 38.7209C25.459 36.1281 30.5336 29.8417 28.3428 25.9204C25.5777 20.9711 15.9947 21.0705 16.0896 15.3939Z"
fill="#FC712F"
/>
<circle
cx="28.7954"
cy="-4.08489"
r="5.51855"
transform="rotate(-32.339 28.7954 -4.08489)"
fill="#FC712F"
/>
<circle
cx="25.1065"
cy="28.2781"
r="1.26958"
transform="rotate(-32.339 25.1065 28.2781)"
fill="#FC712F"
/>
</svg>
</Box>
</Avatar>
</IconButton>
);
}

@ -1,3 +1,4 @@
import { useState } from "react";
import { TransitionProps } from "@mui/material/transitions";
import logotip from "../../assets/Icons/logoPenaHab.svg";
@ -6,180 +7,296 @@ import CustomAvatar from "./Avatar";
import CloseIcon from "../icons/CloseIcons";
import React from "react";
import {
AppBar,
Box,
Button,
Dialog,
IconButton,
List,
ListItem,
Slide,
Toolbar,
Typography,
useMediaQuery,
useTheme,
AppBar,
Box,
Button,
Dialog,
IconButton,
List,
ListItem,
Slide,
Toolbar,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Link, useLocation } from "react-router-dom";
import { useUserStore } from "@root/stores/user";
import { currencyFormatter } from "@root/utils/currencyFormatter";
const arrayMenu = [
{ name: "Главная", url: "/" },
{ name: "Тарифы", url: "/tariffs" },
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
{ name: "Корзина", url: "/basket" },
type MenuItem = {
name: string;
url: string;
subMenu?: MenuItem[];
};
const arrayMenu: MenuItem[] = [
{
name: "Тарифы",
url: "/tariffs",
subMenu: [
{ name: "Тарифы на время", url: "/tariffs/time" },
{ name: "Тарифы на объём", url: "/tariffs/volume" },
{ name: "Кастомный тариф", url: "/tariffconstructor" },
],
},
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "Корзина", url: "/basket" },
];
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement;
},
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<null>
ref: React.Ref<null>
) {
return <Slide direction={"left"} ref={ref} {...props} />;
return <Slide direction="right" ref={ref} {...props} />;
});
interface DialogMenuProps {
open: boolean;
handleClose: () => void;
open: boolean;
handleClose: () => void;
}
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
const theme = useTheme();
const location = useLocation();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const user = useUserStore((state) => state.user);
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
const theme = useTheme();
const location = useLocation();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
return (
<Dialog
fullScreen
sx={{ width: isMobile ? "100%" : "320px", ml: "auto", height: "100%" }}
open={open}
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar
sx={{
position: "relative",
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
boxShadow: "none",
height: isMobile ? "66px" : "100px",
}}
>
<Toolbar
sx={{
display: "flex",
justifyContent: "space-between",
svg: { color: "#000000" },
}}
>
{isMobile && (
<Box sx={{ mt: "6px" }}>
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
</Box>
)}
<IconButton sx={{ ml: "auto" }} edge="start" color="inherit" onClick={handleClose} aria-label="close">
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<List sx={{ background: location.pathname === "/" ? "#333647" : "#FFFFFF", height: "100vh", p: "0" }}>
<ListItem sx={{ pl: "40px", flexDirection: "column", alignItems: isMobile ? "start" : "end" }}>
{arrayMenu.map(({ name, url }, index) => (
<Button
key={index}
component={Link}
to={url}
onClick={handleClose}
state={user ? undefined : { backgroundLocation: location }}
disableRipple
variant="text"
sx={{
fontWeight: "500",
color: location.pathname === url ? "#7E2AEA" : location.pathname === "/" ? "white" : "black",
height: "20px",
textTransform: "none",
marginBottom: "25px",
fontSize: "16px",
"&:hover": {
background: "none",
color: "#7E2AEA",
},
}}
>
{name}
</Button>
))}
</ListItem>
{isMobile ? (
location.pathname === "/" ? (
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
ml: "40px",
mt: "35px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
) : (
<Box
sx={{
width: "100%",
height: "72px",
background: "#F2F3F7",
display: "flex",
alignItems: "center",
position: "absolute",
bottom: "0",
}}
>
<CustomAvatar />
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
</Box>
)
) : (
<Box
sx={{
position: "absolute",
right: "40px",
bottom: "60px",
}}
>
<img src={location.pathname === "/" ? logotip : logotipBlack} alt="icon" />
</Box>
)}
</List>
</Dialog>
const closeDialogMenu = () => {
setActiveSubMenuIndex(-1);
handleClose();
};
const handleSubMenu = (index: number) =>
setActiveSubMenuIndex((activeIndex) =>
activeIndex !== index ? index : -1
);
return (
<Dialog
fullScreen
sx={{
zIndex: 1501,
width: isMobile ? "100%" : "320px",
ml: "auto",
height: "100%",
"& .MuiPaper-root.MuiDialog-paper": {
background: theme.palette.background.default,
},
}}
open={open}
onClose={closeDialogMenu}
TransitionComponent={Transition}
>
<AppBar
sx={{
position: "relative",
display: "flex",
justifyContent: "center",
columnGap: "100px",
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
boxShadow: "none",
height: isMobile ? "66px" : "100px",
}}
>
<Toolbar
sx={{
position: "relative",
display: "flex",
justifyContent: "flex-start",
svg: { color: "#000000" },
}}
>
<IconButton
edge="start"
color="inherit"
onClick={closeDialogMenu}
aria-label="close"
>
<CloseIcon />
</IconButton>
{isMobile && (
<Box sx={{ ml: "auto" }}>
<img
src={location.pathname === "/" ? logotip : logotipBlack}
alt="icon"
/>
</Box>
)}
</Toolbar>
</AppBar>
<List
sx={{
maxWidth: "250px",
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
height: "100vh",
p: "0",
}}
>
<ListItem
sx={{
pl: 0,
pr: 0,
flexDirection: "column",
alignItems: isMobile ? "start" : "end",
}}
>
{arrayMenu.map(({ name, url, subMenu = [] }, index) => (
<Box sx={{ width: "100%" }} key={name + index}>
<Button
key={index}
component={Link}
to={url}
state={user ? undefined : { backgroundLocation: location }}
disableRipple
variant="text"
onClick={() =>
!subMenu.length ? closeDialogMenu() : handleSubMenu(index)
}
sx={{
padding: "10px 10px 10px 20px",
display: "block",
fontWeight: 500,
color:
location.pathname === url
? "#7E2AEA"
: location.pathname === "/"
? "white"
: "black",
textTransform: "none",
fontSize: "16px",
borderRadius: 0,
"&:hover, &:active": {
color: "#7E2AEA",
background:
index === activeSubMenuIndex
? theme.palette.background.default
: "none",
},
}}
>
<Box>{name}</Box>
</Button>
{subMenu.length ? (
<Box
sx={{
backgroundColor: theme.palette.background.paper,
width: "100%",
}}
>
{index === activeSubMenuIndex &&
subMenu.map(({ name, url }) => (
<Link
key={url}
style={{
paddingLeft: "30px",
display: "block",
textDecoration: "none",
}}
to={url}
onClick={closeDialogMenu}
>
<Typography
variant="body2"
sx={{
padding: "12px",
whiteSpace: "nowrap",
fontWeight: 400,
color:
location.pathname === url
? "#7E2AEA"
: location.pathname === "/"
? "white"
: "black",
}}
>
{name}
</Typography>
</Link>
))}
</Box>
) : (
<></>
)}
</Box>
))}
</ListItem>
{isMobile ? (
location.pathname === "/" ? (
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
width: "188px",
color: "white",
border: "1px solid white",
ml: "40px",
mt: "35px",
textTransform: "none",
fontWeight: "400",
fontSize: "18px",
lineHeight: "24px",
borderRadius: "8px",
padding: "8px 17px",
}}
>
Личный кабинет
</Button>
) : (
<Box
sx={{
width: "100%",
height: "72px",
background: "#F2F3F7",
display: "flex",
alignItems: "center",
position: "absolute",
bottom: "0",
}}
>
<CustomAvatar />
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography
variant="body2"
color={theme.palette.brightPurple.main}
>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
</Box>
)
) : (
<Box
sx={{
position: "absolute",
right: "40px",
bottom: "60px",
}}
>
<img
src={location.pathname === "/" ? logotip : logotipBlack}
alt="icon"
/>
</Box>
)}
</List>
</Dialog>
);
}

@ -1,5 +1,5 @@
import { useState } from "react";
import { IconButton, useTheme } from "@mui/material";
import { Badge, IconButton, useTheme } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import SectionWrapper from "../SectionWrapper";
@ -8,11 +8,17 @@ import PenaLogo from "../PenaLogo";
import DialogMenu from "./DialogMenu";
import { Link } from "react-router-dom";
import cartIcon from "@root/assets/Icons/cart.svg";
interface Props {
isLoggedIn: boolean;
cartItemsAmount?: number;
}
export default function NavbarCollapsed({ isLoggedIn }: Props) {
export default function NavbarCollapsed({
isLoggedIn,
cartItemsAmount = 5,
}: Props) {
const [open, setOpen] = useState(false);
const theme = useTheme();
@ -33,22 +39,59 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
backgroundColor: theme.palette.navbarbg.main,
position: "sticky",
top: 0,
zIndex: 1,
zIndex: 1501,
// borderBottom: "1px solid #E3E3E3",
}}
sx={{
height: "51px",
py: "6px",
display: "flex",
justifyContent: "space-between",
justifyContent: "flex-start",
alignItems: "center",
columnGap: "10px",
}}
>
<Link to="/"><PenaLogo width={100} /></Link>
<IconButton onClick={handleClickOpen} sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}>
<IconButton
onClick={handleClickOpen}
sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}
>
<MenuIcon sx={{ height: "30px", width: "30px" }} />
</IconButton>
<Link to="/cart">
<IconButton
aria-label="cart"
sx={{
background: theme.palette.background.default,
borderRadius: "6px",
"&:hover": {
background: theme.palette.brightPurple.main,
"& .MuiBadge-badge": {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
},
}}
>
<Badge
badgeContent={cartItemsAmount}
sx={{
"& .MuiBadge-badge": {
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
top: "2px",
right: "2px",
fontWeight: 400,
},
}}
>
<img src={cartIcon} alt="cart" />
</Badge>
</IconButton>
</Link>
<Link to="/" style={{ marginLeft: "auto" }}>
<PenaLogo width={100} />
</Link>
<DialogMenu open={open} handleClose={handleClose} />
</SectionWrapper>
);

@ -1,5 +1,12 @@
import { Link, useLocation, useNavigate } from "react-router-dom";
import { Box, Button, Container, IconButton, Typography, useTheme } from "@mui/material";
import {
Box,
Button,
Container,
IconButton,
Typography,
useTheme,
} from "@mui/material";
import SectionWrapper from "../SectionWrapper";
import LogoutIcon from "../icons/LogoutIcon";
import WalletIcon from "../icons/WalletIcon";
@ -15,119 +22,127 @@ import { clearCustomTariffs } from "@root/stores/customTariffs";
import { currencyFormatter } from "@root/utils/currencyFormatter";
interface Props {
isLoggedIn: boolean;
isLoggedIn: boolean;
}
export default function NavbarFull({ isLoggedIn }: Props) {
const theme = useTheme();
const location = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const cash = useUserStore(state => state.userAccount?.wallet.cash) ?? 0;
const theme = useTheme();
const location = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const cash = useUserStore((state) => state.userAccount?.wallet.cash) ?? 0;
async function handleLogoutClick() {
try {
await logout();
clearAuthToken();
clearUserData();
clearCustomTariffs();
navigate("/");
} catch (error: any) {
const message = getMessageFromFetchError(error, "Не удалось выйти");
if (message) enqueueSnackbar(message);
}
async function handleLogoutClick() {
try {
await logout();
clearAuthToken();
clearUserData();
clearCustomTariffs();
navigate("/");
} catch (error: any) {
const message = getMessageFromFetchError(error, "Не удалось выйти");
if (message) enqueueSnackbar(message);
}
}
return isLoggedIn ? (
<Container
component="nav"
disableGutters
maxWidth={false}
sx={{
px: "16px",
display: "flex",
height: "80px",
alignItems: "center",
gap: "60px",
bgcolor: "white",
borderBottom: "1px solid #E3E3E3",
}}
return isLoggedIn ? (
<Container
component="nav"
disableGutters
maxWidth={false}
sx={{
px: "16px",
display: "flex",
height: "80px",
alignItems: "center",
gap: "60px",
bgcolor: "white",
borderBottom: "1px solid #E3E3E3",
}}
>
<Link to="/">
<PenaLogo width={124} />
</Link>
<Menu />
<Box
sx={{
display: "flex",
ml: "auto",
}}
>
<Drawers cartItemsAmount={3} />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}
>
<Link to="/"><PenaLogo width={124} /></Link>
<Menu />
<Box
sx={{
display: "flex",
ml: "auto",
}}
>
<Drawers />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}
>
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
<CustomAvatar />
<IconButton
onClick={handleLogoutClick}
sx={{ ml: "20px", bgcolor: "#F2F3F7", borderRadius: "6px", height: "36px", width: "36px" }}
>
<LogoutIcon />
</IconButton>
</Box>
</Container>
) : (
<>
<SectionWrapper
component="nav"
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.lightPurple.main,
borderBottom: "1px solid #E3E3E3",
}}
sx={{
px: "20px",
display: "flex",
justifyContent: "space-between",
height: "80px",
alignItems: "center",
gap: "50px",
}}
>
<PenaLogo width={150} />
<Menu />
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
px: "18px",
py: "10px",
borderColor: "white",
borderRadius: "8px",
whiteSpace: "nowrap",
minWidth: "180px",
}}
>
Личный кабинет
</Button>
</SectionWrapper>
</>
);
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
sx={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
}}
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
<CustomAvatar />
<IconButton
onClick={handleLogoutClick}
sx={{
ml: "20px",
bgcolor: "#F2F3F7",
borderRadius: "6px",
height: "36px",
width: "36px",
}}
>
<LogoutIcon />
</IconButton>
</Box>
</Container>
) : (
<>
<SectionWrapper
component="nav"
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.lightPurple.main,
borderBottom: "1px solid #E3E3E3",
}}
sx={{
px: "20px",
display: "flex",
justifyContent: "space-between",
height: "80px",
alignItems: "center",
gap: "50px",
}}
>
<PenaLogo width={150} />
<Menu />
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
state={user ? undefined : { backgroundLocation: location }}
variant="outlined"
sx={{
px: "18px",
py: "10px",
borderColor: "white",
borderRadius: "8px",
whiteSpace: "nowrap",
minWidth: "180px",
}}
>
Личный кабинет
</Button>
</SectionWrapper>
</>
);
}

@ -0,0 +1,55 @@
import { useState } from "react";
import { Select as MuiSelect, MenuItem, useTheme } from "@mui/material";
import classnames from "classnames";
import { cardShadow } from "@root/utils/themes/shadow";
import "./select.css";
import checkIcon from "@root/assets/Icons/check.svg";
type SelectProps = {
items: string[];
selectedItem: number;
setSelectedItem: (num: number) => void;
};
export const Select = ({
items,
selectedItem,
setSelectedItem,
}: SelectProps) => {
const [opened, setOpened] = useState<boolean>(false);
const theme = useTheme();
return (
<MuiSelect
className="select"
value={selectedItem}
onChange={(event) => setSelectedItem(Number(event.target.value))}
sx={{
width: "100%",
color: theme.palette.brightPurple.main,
border: "2px solid #ffffff",
borderRadius: "30px",
fontWeight: "bold",
boxShadow: cardShadow,
}}
open={opened}
onClick={() => setOpened((isOpened) => !isOpened)}
IconComponent={() => (
<img
src={checkIcon}
alt="check"
className={classnames("select-icon", { opened })}
/>
)}
>
{items.map((item, index) => (
<MenuItem key={item + index} value={index}>
{item}
</MenuItem>
))}
</MuiSelect>
);
};

@ -0,0 +1,26 @@
.select.MuiInputBase-root.MuiOutlinedInput-root {
z-index: 1500;
background: #ebebf2;
box-shadow: 0px 5px 40px rgba(210, 208, 225, 0.58),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.4);
}
.MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline {
border: 0;
}
.MuiInputBase-root .select-icon {
position: absolute;
height: 36px;
right: 10px;
}
.MuiInputBase-root .opened {
transform: rotate(180deg);
}
.MuiPaper-root.MuiMenu-paper {
padding-top: 60px;
margin-top: -60px;
border-radius: 28px;
}

22
src/components/Tabs.tsx Normal file

@ -0,0 +1,22 @@
import { Tabs as MuiTabs } from "@mui/material";
import { CustomTab } from "@root/components/CustomTab";
type TabsProps = {
items: string[];
selectedItem: number;
setSelectedItem: (num: number) => void;
};
export const Tabs = ({ items, selectedItem, setSelectedItem }: TabsProps) => (
<MuiTabs
TabIndicatorProps={{ sx: { display: "none" } }}
value={selectedItem}
onChange={(event, newValue: number) => setSelectedItem(newValue)}
variant="scrollable"
scrollButtons={false}
>
{items.map((item, index) => (
<CustomTab key={item + index} value={index} label={item} />
))}
</MuiTabs>
);

@ -9,7 +9,6 @@ import { enqueueSnackbar } from "notistack";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
interface Props {
priceBeforeDiscounts: number;
priceAfterDiscounts: number;
@ -43,7 +42,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts }
<Box sx={{
display: "flex",
flexDirection: upMd ? "row" : "column",
mt: upMd ? "80px" : "70px",
mt: upMd ? "50px" : "70px",
pt: upMd ? "30px" : undefined,
borderTop: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}>

@ -102,6 +102,7 @@ import {
</InputAdornment>
),
sx: {
border: "1px solid" + theme.palette.grey2.main,
backgroundColor: color,
borderRadius: "8px",
height: "48px",

@ -11,9 +11,10 @@ import Landing from "./pages/Landing/Landing";
import Tariffs from "./pages/Tariffs/Tariffs";
import SigninDialog from "./pages/auth/Signin";
import SignupDialog from "./pages/auth/Signup";
import PaymentHistory from "./pages/PaymentHistory/PaymentHistory";
import History from "./pages/History";
import Basket from "./pages/Basket/Basket";
import TariffPage from "./pages/Tariffs/TariffsPage";
import SavedTariffs from "./pages/SavedTariffs";
import lightTheme from "@utils/themes/light";
import PrivateRoute from "@utils/routes/privateRoute";
import reportWebVitals from "./reportWebVitals";
@ -29,7 +30,9 @@ import { setCustomTariffs } from "@root/stores/customTariffs";
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs";
import { useDiscounts } from "./utils/hooks/useDiscounts";
import { setDiscounts } from "./stores/discounts";
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.js", import.meta.url).toString();
const App = () => {
const location = useLocation();
@ -109,7 +112,8 @@ const App = () => {
<Route path="/wallet" element={<Wallet />} />
<Route path="/payment" element={<Payment />} />
<Route path="/settings" element={<AccountSettings />} />
<Route path="/paymenthistory" element={<PaymentHistory />} />
<Route path="/history" element={<History />} />
<Route path="/tariffconstructor/savedtariffs" element={<SavedTariffs />} />
</Route>
</Route>
</Routes>

@ -29,7 +29,7 @@ export interface LoginRequest {
export interface Verification {
_id: string;
accepted: boolean;
status: VerificationStatus;
status: "org" | "nko";
updated_at: string;
comment: string;
files: File[];

@ -1,45 +1,69 @@
import axios from "axios";
import { Box, SxProps, Theme, Typography, useTheme } from "@mui/material";
import { UserDocument } from "@root/model/user";
import { Document, Page } from "react-pdf";
import { Buffer } from "buffer";
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice";
interface Props {
text: string;
document: UserDocument;
sx?: SxProps<Theme>;
text: string;
documentUrl: string;
sx?: SxProps<Theme>;
}
export default function DocumentItem({ text, document, sx }: Props) {
const theme = useTheme();
export default function DocumentItem({ text, documentUrl = "", sx }: Props) {
const theme = useTheme();
return (
<Box sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
gap: "10px",
...sx,
}}>
<Typography sx={{
color: "#4D4D4D",
fontWeight: 500,
fontVariantNumeric: "tabular-nums",
}}>{text}</Typography>
{document.uploadedFileName &&
<Typography sx={{
color: theme.palette.brightPurple.main,
}}>{document.uploadedFileName}</Typography>
}
{document.imageSrc &&
<img
src={document.imageSrc}
alt="document"
style={{
maxWidth: "80px",
maxHeight: "200px",
objectFit: "contain",
display: "block",
}}
/>}
</Box>
);
}
const downloadFile = async () => {
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
responseType: "arraybuffer",
});
if (!data) {
return;
}
downloadFileToDevice("document.pdf", Buffer.from(data));
return;
};
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
gap: "10px",
...sx,
}}
>
<Typography
sx={{
color: "#4D4D4D",
fontWeight: 500,
fontVariantNumeric: "tabular-nums",
}}
>
{text}
</Typography>
{documentUrl && (
<>
<Typography
sx={{ color: theme.palette.brightPurple.main, cursor: "pointer" }}
onClick={downloadFile}
>
{documentUrl.split("/").pop()?.split(".")?.[0]}
</Typography>
<Document file={documentUrl}>
<Page
pageNumber={1}
width={200}
renderTextLayer={false}
renderAnnotationLayer={false}
/>
</Document>
</>
)}
</Box>
);
}

@ -8,6 +8,7 @@ import {
} from "@root/stores/user";
import DocumentUploadItem from "./DocumentUploadItem";
import DocumentItem from "./DocumentItem";
import { VerificationStatus } from "@root/model/account";
import { sendDocuments } from "@root/api/verification";
import { readFile } from "@root/utils/readFile";
import { verify } from "../helper";
@ -37,15 +38,15 @@ export default function JuridicalDocumentsDialog() {
};
const documentElements =
verificationStatus === "verificated" ? (
verificationStatus === VerificationStatus.VERIFICATED ? (
<>
<DocumentItem
text="1. Скан ИНН организации НКО (выписка из ЕГЮРЛ)"
document={documents["ИНН"]}
documentUrl={documentsUrl["ИНН"]}
/>
<DocumentItem
text="2. Устав организации"
document={documents["Устав"]}
documentUrl={documentsUrl["ИНН"]}
/>
</>
) : (
@ -107,7 +108,7 @@ export default function JuridicalDocumentsDialog() {
}}
>
<Typography variant="h5" lineHeight="100%">
{verificationStatus === "verificated"
{verificationStatus === VerificationStatus.VERIFICATED
? "Ваши документы"
: "Загрузите документы"}
</Typography>
@ -132,19 +133,21 @@ export default function JuridicalDocumentsDialog() {
{documentElements}
</Box>
</Box>
<CustomButton
onClick={sendUploadedDocuments}
variant="contained"
sx={{
width: "180px",
height: "44px",
alignSelf: "end",
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
}}
>
Отправить
</CustomButton>
{verificationStatus === VerificationStatus.NOT_VERIFICATED && (
<CustomButton
onClick={sendUploadedDocuments}
variant="contained"
sx={{
width: "180px",
height: "44px",
alignSelf: "end",
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
}}
>
Отправить
</CustomButton>
)}
</Dialog>
);
}

@ -9,7 +9,7 @@ import {
import DocumentUploadItem from "./DocumentUploadItem";
import DocumentItem from "./DocumentItem";
import { verify } from "../helper";
import { VerificationStatus } from "@root/model/account";
import { sendDocuments } from "@root/api/verification";
import { readFile } from "@root/utils/readFile";
@ -48,19 +48,19 @@ export default function NkoDocumentsDialog() {
};
const documentElements =
verificationStatus === "verificated" ? (
verificationStatus === VerificationStatus.VERIFICATED ? (
<>
<DocumentItem
text="1. Свидетельство о регистрации НКО"
document={documents["Свидетельство о регистрации НКО"]}
documentUrl={documentsUrl["Свидетельство о регистрации НКО"]}
/>
<DocumentItem
text="2. Скан ИНН организации НКО (выписка из ЕГЮРЛ)"
document={documents["ИНН"]}
documentUrl={documentsUrl["ИНН"]}
/>
<DocumentItem
text="3. Устав организации"
document={documents["Устав"]}
documentUrl={documentsUrl["Устав"]}
/>
</>
) : (
@ -134,7 +134,7 @@ export default function NkoDocumentsDialog() {
}}
>
<Typography variant="h5" lineHeight="100%">
{verificationStatus === "verificated"
{verificationStatus === VerificationStatus.VERIFICATED
? "Ваши документы"
: "Загрузите документы"}
</Typography>
@ -159,19 +159,21 @@ export default function NkoDocumentsDialog() {
{documentElements}
</Box>
</Box>
<CustomButton
onClick={sendUploadedDocuments}
variant="contained"
sx={{
width: "180px",
height: "44px",
alignSelf: "end",
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
}}
>
Отправить
</CustomButton>
{verificationStatus === VerificationStatus.NOT_VERIFICATED && (
<CustomButton
onClick={sendUploadedDocuments}
variant="contained"
sx={{
width: "180px",
height: "44px",
alignSelf: "end",
backgroundColor: theme.palette.brightPurple.main,
textColor: "white",
}}
>
Отправить
</CustomButton>
)}
</Dialog>
);
}

@ -3,6 +3,7 @@ import { devlog } from "@frontend/kitui";
import { verification } from "@root/api/verification";
import {
setVerificationStatus,
setVerificationType,
setComment,
setDocumentUrl,
} from "@root/stores/user";
@ -38,6 +39,7 @@ export const verify = (id: string) =>
verification(id)
.then((result) => {
updateVerificationStatus(result);
setVerificationType(result.status);
setComment(result.comment);
result.files.forEach((file) =>
setDocumentUrl(DOCUMENT_TYPE_MAP[file.name], file.url)

@ -1,23 +1,28 @@
import { IconButton, useMediaQuery, useTheme } from "@mui/material";
import { Tabs } from "@mui/material";
import { Box } from "@mui/material";
import { Typography } from "@mui/material";
import { useState } from "react";
import { CustomTab } from "../../components/CustomTab";
import SectionWrapper from "../../components/SectionWrapper";
import AccordionWrapper from "./AccordionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ComplexNavText from "../../components/ComplexNavText";
import { Tabs } from "@root/components/Tabs";
const subPages = [
"Pena hub",
"Шаблоны",
"Опросы",
"Ссылки",
"Финансовые",
"Юридические",
"Юридические лица",
];
export default function Faq() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [tabIndex, setTabIndex] = useState<number>(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setTabIndex(newValue);
};
return (
<SectionWrapper
maxWidth="lg"
@ -26,7 +31,9 @@ export default function Faq() {
mb: upMd ? "70px" : "37px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы —" text2=" Вопросы и ответы" />}
{upMd && (
<ComplexNavText text1="Все тарифы —" text2=" Вопросы и ответы" />
)}
<Box
sx={{
mt: "20px",
@ -36,27 +43,19 @@ export default function Faq() {
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Вопросы и ответы</Typography>
</Box>
<Tabs
TabIndicatorProps={{ sx: { display: "none" } }}
value={tabIndex}
onChange={handleChange}
variant="scrollable"
scrollButtons={false}
>
<CustomTab value={0} label="Pena hub" />
<CustomTab value={1} label="Шаблоны" />
<CustomTab value={2} label="Опросы" />
<CustomTab value={3} label="Ссылки" />
<CustomTab value={4} label="Финансовые" />
<CustomTab value={5} label="Юридические" />
<CustomTab value={6} label="Юридические лица" />
</Tabs>
items={subPages}
selectedItem={tabIndex}
setSelectedItem={setTabIndex}
/>
<TabPanel value={tabIndex} index={0} mt={upMd ? "27px" : "10px"}>
<AccordionWrapper
content={[

@ -0,0 +1,149 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomAccordion from "@components/CustomAccordion";
import { cardShadow } from "@root/utils/themes/shadow";
export type History = {
title: string;
date: string;
info: string;
description: string;
payMethod?: string;
expired?: boolean;
};
interface AccordionWrapperProps {
content: History[];
}
export default function AccordionWrapper({ content }: AccordionWrapperProps) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isMobile = useMediaQuery(theme.breakpoints.down(560));
return (
<Box
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
{content.map((accordionItem, index) => (
<CustomAccordion
key={index}
divide
text={accordionItem.description}
header={
<Box
sx={{
width: "100%",
height: upMd ? "72px" : undefined,
padding: "20px 20px 20px 0",
display: "flex",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
gap: "20px",
alignItems: upSm ? "center" : undefined,
flexDirection: upSm ? undefined : "column",
}}
>
<Box
sx={{
display: "flex",
alignItems: upSm ? "center" : undefined,
justifyContent: "space-between",
flexDirection: upSm ? undefined : "column",
gap: upMd ? "40px" : "10px",
}}
>
<Typography
sx={{
width: "110px",
fontSize: upMd ? "20px" : "18px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: accordionItem.expired
? theme.palette.text.disabled
: theme.palette.text.secondary,
px: 0,
}}
>
{accordionItem.date}
</Typography>
<Typography
sx={{
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: accordionItem.expired
? theme.palette.text.disabled
: theme.palette.grey3.main,
px: 0,
}}
>
{accordionItem.title}
</Typography>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
flexFlow: "1",
flexBasis: "60%",
}}
>
<Typography
sx={{
display: upMd ? undefined : "none",
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 400,
color: accordionItem.expired
? theme.palette.text.disabled
: theme.palette.grey3.main,
px: 0,
}}
>
{accordionItem.payMethod && (
<Typography>
Способ оплаты: {accordionItem.payMethod}
</Typography>
)}
</Typography>
<Box
sx={{
display: "flex",
height: "100%",
alignItems: "center",
gap: upSm ? "111px" : "17px",
width: "100%",
maxWidth: isTablet ? null : "160px",
}}
>
<Typography
sx={{
marginLeft: isTablet ? (isMobile ? null : "auto") : null,
color: accordionItem.expired
? theme.palette.text.disabled
: theme.palette.grey3.main,
fontSize: upSm ? "20px" : "16px",
fontWeight: 500,
textAlign: "left",
}}
>
{accordionItem.info}
</Typography>
</Box>
</Box>
</Box>
}
/>
))}
</Box>
);
}

@ -0,0 +1,139 @@
import type { History } from "./AccordionWrapper";
const PAYMENT_HISTORY: History[] = [
{
date: "28.05.2022",
title: "Шаблонизатор",
payMethod: "QIWI Кошелек",
info: "3 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "28.05.2022",
title: "Сокращатель ссылок",
payMethod: "Юмани",
info: "2 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "28.05.2022",
title: "Шаблонизатор",
payMethod: "QIWI Кошелек",
info: "1 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "08.04.2022",
title: "Шаблонизатор",
payMethod: "QIWI Кошелек",
info: "3 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "28.05.2022",
title: "Сокращатель ссылок",
payMethod: "Юмани",
info: "5 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "18.03.2022",
title: "Шаблонизатор",
payMethod: "Юмани",
info: "6 190 руб.",
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
];
const PURCHASED_TARIFFS_HISTORY: History[] = [
{
date: "28.05.2022",
title: "Шаблонизатор",
info: "5 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "27.05.2022",
title: "Опросник",
info: "9 месяцев 1 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "20.05.2022",
title: "Шаблонизатор",
info: "Безлимит",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "08.04.2022",
title: "Опросник",
info: "10 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "28.05.2022",
title: "Сокращатель ссылок",
info: "3 дня",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "18.03.2022",
title: "Шаблонизатор",
info: "9 месяцев 1 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
];
const FINISHED_TARIFFS_HISTORY: History[] = [
{
date: "28.05.2022",
title: "Шаблонизатор",
info: "5 000 шаблонов",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "28.05.2022",
title: "Сокращатель ссылок",
info: "10 месяцев",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "20.05.2022",
title: "Шаблонизатор",
info: "Безлимит",
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "08.04.2022",
title: "Опросник",
info: "5 000 шаблонов",
expired: true,
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "01.03.2022",
title: "Шаблонизатор",
info: "3 дня",
expired: true,
description: "Тариф на время/ объем/ кастомный или другая информация",
},
{
date: "19.02.2022",
title: "Сокращатель ссылок",
info: "9 месяцев 1 000 шаблонов",
expired: true,
description: "Тариф на время/ объем/ кастомный или другая информация",
},
];
export const HISTORY: History[][] = [
PAYMENT_HISTORY,
PURCHASED_TARIFFS_HISTORY,
FINISHED_TARIFFS_HISTORY,
];

@ -0,0 +1,76 @@
import { useState } from "react";
import {
Box,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ComplexNavText from "@root/components/ComplexNavText";
import SectionWrapper from "@root/components/SectionWrapper";
import { Select } from "@root/components/Select";
import { Tabs } from "@root/components/Tabs";
import AccordionWrapper from "./AccordionWrapper";
import { HISTORY } from "./historyMocks";
const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"];
export default function History() {
const [selectedItem, setSelectedItem] = useState<number>(0);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "70px" : "37px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="История" />}
<Box
sx={{
mt: "20px",
mb: upMd ? "40px" : "20px",
display: "flex",
gap: "10px",
}}
>
{!upMd && (
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">История</Typography>
</Box>
{isMobile ? (
<Select
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
) : (
<Tabs
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
)}
{HISTORY.map((history, index) => (
<Box
hidden={selectedItem !== index}
sx={{ mt: upMd ? "27px" : "10px" }}
>
<AccordionWrapper content={history} />
</Box>
))}
</SectionWrapper>
);
}

@ -1,32 +0,0 @@
import { Box } from "@mui/material";
import CustomAccordion from "./CustomAccordionPayHistory";
import { cardShadow } from "@root/utils/themes/shadow";
interface Props {
content:{title:string, date:string, payMethod:string, totalPrice:number, text:string}[];
}
export default function AccordionWrapper({content}:Props) {
return (
<Box
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
{content.map((accordionItem, index) => (
<CustomAccordion
key={index}
title={accordionItem.title}
date={accordionItem.date}
payMethod={accordionItem.payMethod}
totalPrice={accordionItem.totalPrice}
text={accordionItem.text}
/>
))}
</Box>
);
}

@ -1,147 +0,0 @@
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ExpandIcon from "../../components/icons/ExpandIcon";
interface Props {
title:string,
date:string,
payMethod:string,
totalPrice:number,
text:string,
}
export default function CustomAccordion({ title, date, payMethod, totalPrice,text }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
return (
<Box
sx={{
backgroundColor: "white",
"&:first-of-type": {
borderTopLeftRadius: "12px",
borderTopRightRadius: "12px",
},
"&:last-of-type": {
borderBottomLeftRadius: "12px",
borderBottomRightRadius: "12px",
},
"&:not(:last-of-type)": {
borderBottom: `1px solid ${theme.palette.grey2.main}`,
}
}}
>
<Box
onClick={() => setIsExpanded(prev => !prev)}
sx={{
height: upMd ? "72px" : undefined,
px: "20px",
pt:"20px",
pb:"20px",
display: "flex",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
gap:'20px',
alignItems: upSm ? 'center': undefined,
flexDirection: upSm ? undefined:'column',
}}
>
<Box
sx={{display:'flex', alignItems: upSm ? 'center': undefined, justifyContent:'space-between',
flexDirection: upSm ? undefined : 'column', gap: upMd ? "40px" : "10px",
}}>
<Typography
sx={{
fontSize: upMd ? "20px" : "18px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.text.secondary,
px: 0,
}}
>{date}</Typography>
<Typography
sx={{
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.grey3.main,
px: 0,
}}
>{title}</Typography>
</Box>
<Box
sx={{display:'flex', justifyContent:'space-between',flexFlow:'1', flexBasis:"60%"}}
>
<Typography
sx={{
display:upMd ? undefined : 'none',
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 400,
color: theme.palette.grey3.main,
px: 0,
}}
>Способ оплаты: {payMethod}</Typography>
<Box sx={{display:"flex", justifyContent: upSm ? "flex-end":undefined, height:"100%", alignItems:"center",gap:upSm ? "111px": "17px"}}>
<Typography sx={{color: theme.palette.grey3.main,fontSize:upSm ? "20px": "16px", fontWeight:500, whiteSpace:'nowrap' }}>{totalPrice} руб.</Typography>
<Box sx={{borderLeft:upSm ? "1px solid #9A9AAF": "none", paddingLeft:upSm ? "24px" : 0, height:"100%", display:"flex", justifyContent:"center", alignItems:"center"}}>
<ExpandIcon isExpanded={isExpanded} />
</Box>
</Box>
</Box>
</Box>
{isExpanded &&
<Box
sx={{
px: "20px",
py: upMd ? "25px" : undefined,
pt: upMd ? undefined : "15px",
pb: upMd ? undefined : "25px",
backgroundColor: "#F1F2F6",
display:"flex",
justifyContent:"space-between",
alignItems: upMd ? 'center': undefined,
flexDirection: upMd ? undefined:'column',
flexWrap:'wrap',
gap:'10px',
}}
>
<Typography
sx={{
display: upMd ? 'none' :undefined ,
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>Способ оплаты: {payMethod}</Typography>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>{text} </Typography>
</Box>
}
</Box>
);
}

@ -1,92 +0,0 @@
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ComplexNavText from "../../components/ComplexNavText";
import SectionWrapper from "../../components/SectionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import AccordionWrapper from "./AccordionWrapper";
interface TabPanelProps {
index: number;
value: number;
children?: React.ReactNode;
mt: string;
}
function TabPanel({ index, value, children, mt }: TabPanelProps) {
return (
<Box
hidden={index !== value}
sx={{ mt }}
>
{children}
</Box>
);
}
export default function PaymentHistory() {
const contentArray = [
{
title:"Шаблонизатор",
date:"28.05.2022",
payMethod:"QIWI Кошелек",
totalPrice:3190,
text:"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
title:"Сокращатель ссылок",
date:"08.04.2022",
payMethod:"Юмани",
totalPrice:2190,
text:"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
title:"Шаблонизатор",
date:"28.05.2022",
payMethod:"QIWI Кошелек",
totalPrice:1190,
text:"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
]
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [tabIndex, setTabIndex] = useState<number>(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setTabIndex(newValue);
};
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "70px" : "37px",
}}
>
{upMd &&
<ComplexNavText text1="Все тарифы — " text2="История платежей" />
}
<Box
sx={{
mt: "20px",
mb: upMd ? "40px" : "20px",
display: "flex",
gap: "10px",
}}
>
{!upMd &&
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<ArrowBackIcon />
</IconButton>
}
<Typography variant="h4">История платежей</Typography>
</Box>
<TabPanel value={tabIndex} index={0} mt={upMd ? "27px" : "10px"}>
<AccordionWrapper
content={contentArray}
/>
</TabPanel>
</SectionWrapper >
)
}

@ -0,0 +1,125 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomButton from "@root/components/CustomButton";
import CustomAccordion from "@components/CustomAccordion";
import { cardShadow } from "@root/utils/themes/shadow";
type SavedTariff = {
date: string;
title: string;
amount: string;
price: number;
description: string;
};
interface Props {
content: SavedTariff[];
}
export default function AccordionWrapper({ content }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isMobile = useMediaQuery(theme.breakpoints.down(560));
return (
<Box
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
{content.map(({ date, title, amount, price, description }, index) => (
<CustomAccordion
key={index}
divide
text={description}
header={
<Box
sx={{
paddingTop: isMobile ? "10px" : null,
paddingBottom: isMobile ? "10px" : null,
display: "flex",
width: "100%",
alignItems: upSm ? "center" : undefined,
justifyContent: "space-between",
flexDirection: upSm ? undefined : "column",
gap: upMd ? "40px" : "10px",
}}
>
<Typography
sx={{
width: "110px",
color: theme.palette.text.secondary,
fontSize: upMd ? "20px" : "18px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
px: 0,
}}
>
{date}
</Typography>
<Typography
sx={{
width: "100%",
maxWidth: "200px",
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
px: 0,
}}
>
{title}
</Typography>
<Typography
sx={{
width: "100%",
maxWidth: "180px",
display: upMd ? undefined : "none",
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 400,
px: 0,
}}
>
{amount}
</Typography>
<Typography
sx={{
width: "100%",
maxWidth: "120px",
textAlign: "right",
display: upMd ? undefined : "none",
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
px: 0,
}}
>
{new Intl.NumberFormat("ru-RU").format(price)} руб.
</Typography>
<CustomButton
variant="contained"
sx={{
mr: "25px",
display: "block",
width: "100%",
maxWidth: "180px",
backgroundColor: theme.palette.background.default,
color: theme.palette.text.secondary,
border: "1px solid #9A9AAF",
":hover": {
backgroundColor: theme.palette.background.default,
},
}}
>
Купить
</CustomButton>
</Box>
}
/>
))}
</Box>
);
}

@ -0,0 +1,100 @@
import { IconButton, useMediaQuery, useTheme } from "@mui/material";
import { Box } from "@mui/material";
import { Typography } from "@mui/material";
import SectionWrapper from "../../components/SectionWrapper";
import AccordionWrapper from "./AccordionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ComplexNavText from "../../components/ComplexNavText";
export default function Faq() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "70px" : "37px",
}}
>
{upMd && (
<ComplexNavText
text1="Все тарифы — Кастомный тариф —"
text2="Сохраненные тарифы"
/>
)}
<Box
sx={{
mt: "20px",
mb: upMd ? "40px" : "20px",
display: "flex",
gap: "10px",
}}
>
{!upMd && (
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Сохраненные тарифы</Typography>
</Box>
<Box mt={upMd ? "27px" : "10px"}>
<AccordionWrapper
content={[
{
date: "20.05.2022",
title: "Шаблонизатор",
amount: "5 000 шаблонов",
price: 3190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "27.05.2022",
title: "Опросник",
amount: "9 месяцев 1 000 шаблонов",
price: 2190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "20.05.2022",
title: "Шаблонизатор",
amount: "Безлимит",
price: 1190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "08.04.2022",
title: "Опросник",
amount: "10 000 шаблонов",
price: 3190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "28.05.2022",
title: "Сокращатель ссылок",
amount: "3 дня",
price: 5190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
{
date: "18.03.2022",
title: "Шаблонизатор",
amount: "9 месяцев 1 000 шаблонов",
price: 6190,
description:
"Дата действия приобретенной лицензии (в формате дд.мм.гггг-дд.мм.гггг) Или же объем",
},
]}
/>
</Box>
</SectionWrapper>
);
}

@ -1,4 +1,5 @@
import { Box, IconButton, useMediaQuery, useTheme } from "@mui/material";
import { Link } from "react-router-dom";
import SectionWrapper from "@components/SectionWrapper";
import ComplexNavText from "@root/components/ComplexNavText";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
@ -8,9 +9,9 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import TotalPrice from "@root/components/TotalPrice";
interface ServiceMap {
[key: string]: string;
[key: string]: string;
}
const servicemap: ServiceMap = { "templategen": "Шаблонизатор" };
const servicemap: ServiceMap = { templategen: "Шаблонизатор" };
export default function TariffConstructor() {
const theme = useTheme();
@ -22,48 +23,72 @@ export default function TariffConstructor() {
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0);
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box sx={{
mt: "20px",
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
sx={{
mt: "20px",
display: "flex",
flexDirection: "column",
gap: "80px",
}}
>
{Object.entries(customTariffs).map(([serviceKey, tariffs], index) => (
<Box key={serviceKey}>
<Box
sx={{
mb: "40px",
display: "flex",
flexDirection: "column",
gap: "80px",
}}>
{Object.entries(customTariffs).map(([serviceKey, tariffs], index) =>
<Box key={serviceKey}>
<Box sx={{
mb: "40px",
display: "flex",
gap: "10px",
}}>
{!upMd && index === 0 && (
<IconButton sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={servicemap[serviceKey]}
/>
</Box>
<CustomTariffCard serviceKey={serviceKey} tariffs={tariffs} />
</Box>
)}
gap: "10px",
}}
>
{!upMd && index === 0 && (
<IconButton
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}
>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={servicemap[serviceKey]}
/>
</Box>
<TotalPrice priceBeforeDiscounts={basePrice} priceAfterDiscounts={discountedPrice} />
</SectionWrapper>
);
<CustomTariffCard serviceKey={serviceKey} tariffs={tariffs} />
</Box>
))}
</Box>
{upMd && (
<Link
to="/tariffconstructor/savedtariffs"
style={{
display: "block",
marginTop: "60px",
textUnderlinePosition: "under",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
>
Ваши сохраненные тарифы
</Link>
)}
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}
/>
</SectionWrapper>
);
}

@ -18,6 +18,7 @@ export default function FreeTariffCard() {
sx={{
backgroundColor: "#7E2AEA",
color: "white",
minHeight: "206px",
}}
/>
);

@ -1,12 +1,13 @@
import { useState } from "react";
import { useLocation } from "react-router-dom";
import { Box, Tabs, Typography, useMediaQuery, useTheme } from "@mui/material";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import SectionWrapper from "@components/SectionWrapper";
import ComplexNavText from "@root/components/ComplexNavText";
import { useTariffs } from "@root/utils/hooks/useTariffs";
import { updateTariffs, useTariffStore } from "@root/stores/tariffs";
import { enqueueSnackbar } from "notistack";
import { CustomTab } from "@root/components/CustomTab";
import { Select } from "@root/components/Select";
import { Tabs } from "@root/components/Tabs";
import TariffCard from "./TariffCard";
import NumberIcon from "@root/components/NumberIcon";
import { currencyFormatter } from "@root/utils/currencyFormatter";
@ -16,119 +17,148 @@ import FreeTariffCard from "./FreeTariffCard";
import { addTariffToCart, useUserStore } from "@root/stores/user";
import { useDiscountStore } from "@root/stores/discounts";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
import { Slider } from "./slider";
import { useCartStore } from "@root/stores/cart";
const subPages = ["Шаблонизатор", "Опросник", "Сокращатель ссылок"];
export default function TariffPage() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const location = useLocation();
const tariffs = useTariffStore(state => state.tariffs);
const [tabIndex, setTabIndex] = useState<number>(0);
const [selectedItem, setSelectedItem] = useState<number>(0);
const discounts = useDiscountStore(state => state.discounts);
const customTariffs = useCustomTariffsStore(state => state.customTariffsMap);
const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0;
const cart = useCartStore(state => state.cart);
const unit: string = String(location.pathname).slice(9);
const StepperText: Record<string, string> = { volume: "Тарифы на объём", time: "Тарифы на время" };
const StepperText: Record<string, string> = {
volume: "Тарифы на объём",
time: "Тарифы на время",
};
useTariffs({
apiPage: 0,
tariffsPerPage: 100,
onNewTariffs: updateTariffs,
onError: error => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
}
});
useTariffs({
apiPage: 0,
tariffsPerPage: 100,
onNewTariffs: updateTariffs,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
},
});
function handleTariffItemClick(tariffId: string) {
addTariffToCart(tariffId).then(() => {
enqueueSnackbar("Тариф добавлен в корзину");
}).catch(error => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
function handleTariffItemClick(tariffId: string) {
addTariffToCart(tariffId)
.then(() => {
enqueueSnackbar("Тариф добавлен в корзину");
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
const filteredTariffs = tariffs.filter(tariff => {
return tariff.privilegies.map(p => p.type).includes("day") === (unit === "time") && !tariff.isDeleted;
});
const filteredTariffs = tariffs.filter((tariff) => {
return (
tariff.privilegies.map((p) => p.type).includes("day") ===
(unit === "time") && !tariff.isDeleted
);
});
const tariffElements = filteredTariffs.map((tariff, index) => {
const { price, tariffPriceAfterDiscounts } = calcIndividualTariffPrices(
tariff,
discounts,
customTariffs,
purchasesAmount,
cart,
);
return (
<TariffCard
key={tariff._id}
icon={<NumberIcon
number={index + 1}
color={unit === "time" ? "#7E2AEA" : "#FB5607"}
backgroundColor={unit === "time" ? "#EEE4FC" : "#FEDFD0"}
/>}
buttonProps={{
text: "Выбрать",
onClick: () => handleTariffItemClick(tariff._id),
}}
headerText={tariff.name}
text={tariff.privilegies.map(p => `${p.name} - ${p.amount}`)}
price={<>
{price !== undefined && price !== tariffPriceAfterDiscounts &&
<Typography variant="oldPrice">{currencyFormatter.format(price / 100)}</Typography>
}
{tariffPriceAfterDiscounts !== undefined &&
<Typography variant="price">{currencyFormatter.format(tariffPriceAfterDiscounts / 100)}</Typography>
}
</>}
/>
);
});
if (tariffElements.length < 6) tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
else tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
const tariffElements = filteredTariffs.map((tariff, index) => {
const { price, tariffPriceAfterDiscounts } = calcIndividualTariffPrices(
tariff,
discounts,
customTariffs,
purchasesAmount,
cart
);
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "20px",
mb: upMd ? "90px" : "63px",
display: "flex",
flexDirection: "column",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2={StepperText[unit]} />}
<Typography variant="h4" sx={{ marginBottom: "23px", mt: "20px" }}>
{StepperText[unit]}
</Typography>
<Tabs
TabIndicatorProps={{ sx: { display: "none" } }}
value={tabIndex}
onChange={(event, newValue) => setTabIndex(newValue)}
variant="scrollable"
scrollButtons={false}
>
<CustomTab value={0} label="Шаблонизатор" />
<CustomTab value={1} label="Опросник" />
<CustomTab value={2} label="Сокращатель ссылок" />
</Tabs>
<Box sx={{
mt: "40px",
mb: "30px",
display: "grid",
gap: "40px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
}}>
{tariffElements}
</Box>
</SectionWrapper>
<TariffCard
key={tariff._id}
icon={
<NumberIcon
number={index + 1}
color={unit === "time" ? "#7E2AEA" : "#FB5607"}
backgroundColor={unit === "time" ? "#EEE4FC" : "#FEDFD0"}
/>
}
buttonProps={{
text: "Выбрать",
onClick: () => handleTariffItemClick(tariff._id),
}}
headerText={tariff.name}
text={tariff.privilegies.map((p) => `${p.name} - ${p.amount}`)}
price={
<>
{price !== undefined && price !== tariffPriceAfterDiscounts && (
<Typography variant="oldPrice">
{currencyFormatter.format(price / 100)}
</Typography>
)}
{tariffPriceAfterDiscounts !== undefined && (
<Typography variant="price">
{currencyFormatter.format(tariffPriceAfterDiscounts / 100)}
</Typography>
)}
</>
}
/>
);
});
if (tariffElements.length < 6)
tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
else tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "20px",
mb: upMd ? "90px" : "63px",
display: "flex",
flexDirection: "column",
}}
>
{upMd && (
<ComplexNavText text1="Все тарифы — " text2={StepperText[unit]} />
)}
<Typography variant="h4" sx={{ marginBottom: "23px", mt: "20px" }}>
{StepperText[unit]}
</Typography>
{isMobile ? (
<Select
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
) : (
<Tabs
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
)}
<Box
sx={{
mt: "40px",
mb: "30px",
display: "grid",
gap: "40px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
}}
>
{tariffElements}
</Box>
<Typography variant="h4" sx={{ mt: "50px", mb: "40px" }}>
Ранее вы покупали
</Typography>
<Slider items={tariffElements} />
</SectionWrapper>
);
}

@ -0,0 +1 @@
export { Slider } from "./slider";

@ -0,0 +1,63 @@
.slider .slick-slide {
width: 100%;
max-width: 360px;
height: auto;
}
.slider .slick-slide > div,
.slider .slick-slide > div > div {
height: 100%;
}
.slider .slick-slide > div > div.MuiBox-root {
box-shadow: 0 0 20px rgba(0, 0, 0, 0.08);
}
.slider .slick-list {
padding-left: 26px;
}
.slider .slick-track {
display: flex;
column-gap: 15px;
padding: 0 0 30px 0;
}
.slider .slick-arrow {
height: 50px;
width: 50px;
z-index: 1;
top: 45%;
}
.slider .slick-arrow.slick-prev {
left: -5px;
}
.slider .slick-arrow.slick-next {
right: -5px;
}
.slider .slick-dots {
bottom: -15px;
}
.slider .dot {
background: transparent;
border: 1.5px solid #9a9aaf;
border-radius: 50%;
height: 10px;
width: 10px;
margin: 0 8px;
}
.slider .dot.active {
background: #9a9aaf;
}
@media (max-width: 1200px) {
.slick-slider {
max-width: 790px;
margin: auto;
}
}

@ -0,0 +1,92 @@
import { useState, useEffect } from "react";
import { Box, useTheme, useMediaQuery } from "@mui/material";
import classNames from "classnames";
import SliderSlick from "react-slick";
import arrowLeftIcon from "@root/assets/Icons/arrow_left.svg";
import arrowRightIcon from "@root/assets/Icons/arrow_right.svg";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import "./slider.css";
import type { ReactNode } from "react";
type SliderProps = {
items: ReactNode[];
};
export const Slider = ({ items }: SliderProps) => {
const [range, setRange] = useState<number>(3);
const [activeRange, setActiveRange] = useState<number[]>([0, 1, 2]);
const theme = useTheme();
const isMiddle = useMediaQuery(theme.breakpoints.down(1200));
const isTablet = useMediaQuery(theme.breakpoints.down(830));
useEffect(() => {
if (isMiddle) {
setRange(2);
setActiveRange([0, 1]);
return;
}
setRange(3);
setActiveRange([0, 1, 2]);
}, [isMiddle, isTablet]);
const calculateRange = (
activeItem: number,
itemsLength: number
): number[] => [
activeItem,
activeItem + 1 >= itemsLength ? 0 : activeItem + 1,
activeItem + range - 1 === itemsLength
? 0
: activeItem + range - 1 >= itemsLength
? 1
: activeItem + range - 1,
];
return (
<>
{items.length < 4 || (items.length < 3 && isMiddle) || isTablet ? (
<Box
sx={{
mt: "40px",
mb: "30px",
display: "grid",
gap: "40px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
margin: isTablet ? "auto" : null,
}}
>
{items}
</Box>
) : (
<SliderSlick
className="slider"
dots
infinite
variableWidth
slidesToShow={range}
prevArrow={<img src={arrowLeftIcon} alt="prev" />}
nextArrow={<img src={arrowRightIcon} alt="next" />}
beforeChange={(_, active) =>
setActiveRange(calculateRange(active, items.length))
}
customPaging={(slideNumber) => (
<li
className={classNames("dot", {
active: activeRange.includes(slideNumber),
})}
/>
)}
>
{items}
</SliderSlick>
)}
</>
);
};

@ -1,4 +1,4 @@
import { CartData, Discount, Tariff } from "@frontend/kitui";
import { CartData, Discount, Tariff } from "@frontend/kitui";
import { calcCart } from "@root/utils/calcCart/calcCart";
import { produce } from "immer";
import { create } from "zustand";

@ -107,9 +107,13 @@ export const useUserStore = create<UserStore>()(
export const setVerificationStatus = (verificationStatus: VerificationStatus) =>
useUserStore.setState({ verificationStatus });
export const setVerificationType = (verificationType: "nko" | "org") =>
useUserStore.setState({
verificationType: verificationType === "org" ? "juridical" : "nko",
});
export const setUserId = (userId: string | null) =>
useUserStore.setState({ userId });
export const setUser = (user: User) =>
useUserStore.setState(
produce<UserStore>((state) => {
@ -298,16 +302,21 @@ export const removeTariffFromCart = async (tariffId: string) => {
};
export const changeUserCurrency = async (currency: string) => {
const result = await patchCurrency(currency);
setUserAccount(result);
const result = await patchCurrency(currency);
setUserAccount(result);
};
const validators: Record<UserSettingsField | keyof UserName, StringSchema> = {
email: string().email("Неверный email"),
phoneNumber: string().matches(/^[+\d|\d]*$/, "Неверный номер телефона").min(6, "Номер телефона должен содержать минимум 6 символов"),
password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы в пароле").optional(),
firstname: string(),
secondname: string(),
middlename: string(),
orgname: string(),
email: string().email("Неверный email"),
phoneNumber: string()
.matches(/^[+\d|\d]*$/, "Неверный номер телефона")
.min(6, "Номер телефона должен содержать минимум 6 символов"),
password: string()
.min(8, "Минимум 8 символов")
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы в пароле")
.optional(),
firstname: string(),
secondname: string(),
middlename: string(),
orgname: string(),
};

@ -2,6 +2,7 @@ import { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
import { CartData, Discount, Tariff, findCartDiscount, findLoyaltyDiscount, findPrivilegeDiscount, findServiceDiscount } from "@frontend/kitui";
export function calcIndividualTariffPrices(
tariff: Tariff,
discounts: Discount[],

271
yarn.lock

@ -1116,13 +1116,20 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.10"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.20.1"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz"
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
dependencies:
regenerator-runtime "^0.13.10"
"@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.10", "@babel/template@^7.3.3":
version "7.18.10"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz"
@ -1356,6 +1363,17 @@
"@emotion/weak-memoize" "^0.3.0"
stylis "4.1.3"
"@emotion/cache@^11.11.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/sheet" "^1.2.2"
"@emotion/utils" "^1.2.1"
"@emotion/weak-memoize" "^0.3.1"
stylis "4.2.0"
"@emotion/hash@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz"
@ -1368,11 +1386,23 @@
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/is-prop-valid@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc"
integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/memoize@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
"@emotion/react@^11.10.5":
version "11.10.5"
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz"
@ -1403,6 +1433,11 @@
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz"
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==
"@emotion/sheet@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
"@emotion/styled@^11.10.5":
version "11.10.5"
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz"
@ -1430,11 +1465,21 @@
resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==
"@emotion/utils@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
"@emotion/weak-memoize@^0.3.0":
version "0.3.0"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@emotion/weak-memoize@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
"@eslint/eslintrc@^1.3.3":
version "1.3.3"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz"
@ -2002,91 +2047,106 @@
resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.106.tgz"
integrity sha512-xJQQtwPCPwr6hGWTBdvDwHYwExn3Bw7nPQkN8Fuz8kHpZqoMVWQvvaFS557AIkkI2AFLV3DxVIMjbCvrIntBWg==
dependencies:
"@babel/runtime" "^7.20.1"
"@emotion/is-prop-valid" "^1.2.0"
"@mui/types" "^7.2.1"
"@mui/utils" "^5.10.14"
"@popperjs/core" "^2.11.6"
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"
"@mui/base@5.0.0-beta.7":
version "5.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.7.tgz#01cb99ac098af0ba989c7abc1474e3291c29414f"
integrity sha512-Pjbwm6gjiS96kOMF7E5fjEJsenc0tZBesrLQ4rrdi3eT/c/yhSWnPbCUkHSz8bnS0l3/VQ8bA+oERSGSV2PK6A==
dependencies:
"@babel/runtime" "^7.22.5"
"@emotion/is-prop-valid" "^1.2.1"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.13.7"
"@popperjs/core" "^2.11.8"
clsx "^1.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
"@mui/core-downloads-tracker@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.14.tgz"
integrity sha512-qLgIJNOR9Dre8JiZ/neVzOf4jf88J6YtOkQqugtMrleLjbfRVUSS4LWl9CSOjNq76quYdmYWnSDgfQqOooT2cQ==
"@mui/core-downloads-tracker@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.0.tgz#ca394a1c53c215f4c6bf7f7460d8211298d7bbf6"
integrity sha512-SYBOVCatVDUf/lbrLGah09bHhX5WfUXg7kSskfLILr6SvKRni0NLp0aonxQ0SMALVVK3Qwa6cW4CdWuwS0gC1w==
"@mui/icons-material@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.10.14.tgz"
integrity sha512-qtH60slQa+7MZRn6kyui8rKuoGDglPqaHX+pzBKNvd8JCOlrnfY5DmGGDdToTXyXl8xJ8nhANZbrbpg7UVKq/Q==
version "5.14.0"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.0.tgz#6452b00bc98dc407f20a927245e273113703166f"
integrity sha512-z7lYNteDi1GMkF9JP/m2RWuCYK1M/FlaeBSUK7/IhIYzIXNhAVjfD8jRq5vFBV31qkEi2aGBS2z5SfLXwH6U0A==
dependencies:
"@babel/runtime" "^7.20.1"
"@babel/runtime" "^7.22.5"
"@mui/material@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/material/-/material-5.10.14.tgz"
integrity sha512-HWzKVAykePMx54WtxVwZyL1W4k3xlHYIqwMw0CaXAvgB3UE9yjABZuuGr8vG5Z6CSNWamzd+s1x8u7pQPFl9og==
version "5.14.0"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.0.tgz#3d2afb4a3643774370cb5add873abcbbe8e7af27"
integrity sha512-HP7CP71NhMkui2HUIEKl2/JfuHMuoarSUWAKlNw6s17bl/Num9rN61EM6uUzc2A2zHjj/00A66GnvDnmixEJEw==
dependencies:
"@babel/runtime" "^7.20.1"
"@mui/base" "5.0.0-alpha.106"
"@mui/core-downloads-tracker" "^5.10.14"
"@mui/system" "^5.10.14"
"@mui/types" "^7.2.1"
"@mui/utils" "^5.10.14"
"@types/react-transition-group" "^4.4.5"
"@babel/runtime" "^7.22.5"
"@mui/base" "5.0.0-beta.7"
"@mui/core-downloads-tracker" "^5.14.0"
"@mui/system" "^5.14.0"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.13.7"
"@types/react-transition-group" "^4.4.6"
clsx "^1.2.1"
csstype "^3.1.1"
csstype "^3.1.2"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
"@mui/private-theming@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.10.14.tgz"
integrity sha512-3aIBe8WK65CwAPDY8nB11hYnzE1CZMymi76UnaFrA/DdGDwl5Y8F6uB+StKrkVmsqF1po7Mp2odqVkHj320gXw==
"@mui/private-theming@^5.13.7":
version "5.13.7"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.7.tgz#2f8ef5da066f3c6c6423bd4260d003a28d10b099"
integrity sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==
dependencies:
"@babel/runtime" "^7.20.1"
"@mui/utils" "^5.10.14"
"@babel/runtime" "^7.22.5"
"@mui/utils" "^5.13.7"
prop-types "^15.8.1"
"@mui/styled-engine@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.10.14.tgz"
integrity sha512-bgKdM57ExogWpIfhL/ngSlzF4FhbH00vYF+Y5VALTob4uslFqje0xzoWmbfcCn4cZt2NXxZJIwhsq4vzo5itlw==
"@mui/styled-engine@^5.13.2":
version "5.13.2"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.13.2.tgz#c87bd61c0ab8086d34828b6defe97c02bcd642ef"
integrity sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==
dependencies:
"@babel/runtime" "^7.20.1"
"@emotion/cache" "^11.10.5"
csstype "^3.1.1"
"@babel/runtime" "^7.21.0"
"@emotion/cache" "^11.11.0"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/system@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/system/-/system-5.10.14.tgz"
integrity sha512-2de7XCjRb1j8Od0Stmo0LwFMLpOMNT4wzfINuExXI1TVSuyxXIXUxiC5FEgJW3GMvf/a7SUR8VOiMoKlKWzukw==
"@mui/system@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.0.tgz#b7eeb799ae67d27b887fec4917ebd5e2be5a2faa"
integrity sha512-0HZGkX8miJbiNw+rjlZ9l0Cfkz1bSqfSHQH0EH9J+nx0aAm5cBleg9piOlLdCNIWGgecCqsw4x62erGrGjjcJg==
dependencies:
"@babel/runtime" "^7.20.1"
"@mui/private-theming" "^5.10.14"
"@mui/styled-engine" "^5.10.14"
"@mui/types" "^7.2.1"
"@mui/utils" "^5.10.14"
"@babel/runtime" "^7.22.5"
"@mui/private-theming" "^5.13.7"
"@mui/styled-engine" "^5.13.2"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.13.7"
clsx "^1.2.1"
csstype "^3.1.1"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/types@^7.2.1":
version "7.2.1"
resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.1.tgz"
integrity sha512-c5mSM7ivD8EsqK6HUi9hQPr5V7TJ/IRThUQ9nWNYPdhCGriTSQV4vL6DflT99LkM+wLiIS1rVjphpEWxERep7A==
"@mui/types@^7.2.4":
version "7.2.4"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328"
integrity sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==
"@mui/utils@^5.10.14":
version "5.10.14"
resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.10.14.tgz"
integrity sha512-12p59+wDZpA++XVJmKwqsZmrA1nmUQ5d0a1yQWtcDjxNyER1EDzozYN/db+FY2i5ceQh2TynPTEwGms2mXDwFg==
"@mui/utils@^5.13.7":
version "5.13.7"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.7.tgz#7e6a8336e05eb2642667a5c02eb605351e27ec20"
integrity sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA==
dependencies:
"@babel/runtime" "^7.20.1"
"@babel/runtime" "^7.22.5"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^16.7.1 || ^17.0.0"
"@types/react-is" "^18.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
@ -2133,10 +2193,10 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@popperjs/core@^2.11.6":
version "2.11.6"
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@remix-run/router@1.0.3":
version "1.0.3"
@ -2647,17 +2707,24 @@
dependencies:
"@types/react" "*"
"@types/react-is@^16.7.1 || ^17.0.0":
version "17.0.3"
resolved "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz"
integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
"@types/react-is@^18.2.1":
version "18.2.1"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-18.2.1.tgz#61d01c2a6fc089a53520c0b66996d458fdc46863"
integrity sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.5":
version "4.4.5"
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz"
integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
"@types/react-slick@^0.23.10":
version "0.23.10"
resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.10.tgz#56126e6e4e95cdce7771535b2811c2c1931a7caa"
integrity sha512-ZiqdencANDZy6sWOWJ54LDvebuXFEhDlHtXU9FFipQR2BcYU2QJxZhvJPW6YK7cocibUiNn+YvDTbt1HtCIBVA==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.6":
version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==
dependencies:
"@types/react" "*"
@ -3771,6 +3838,11 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
classnames@^2.2.5, classnames@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
clean-css@^5.2.2:
version "5.3.1"
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz"
@ -4243,11 +4315,16 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
csstype@^3.0.2, csstype@^3.1.1:
csstype@^3.0.2:
version "3.1.1"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz"
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
csstype@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
damerau-levenshtein@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz"
@ -4495,7 +4572,7 @@ dom-converter@^0.2.0:
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
@ -4642,6 +4719,11 @@ enhanced-resolve@^5.10.0:
graceful-fs "^4.2.4"
tapable "^2.2.0"
enquire.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814"
integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
@ -7184,6 +7266,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json2mq@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==
dependencies:
string-convert "^0.2.0"
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
@ -8960,9 +9049,20 @@ react-scripts@5.0.1:
optionalDependencies:
fsevents "^2.3.2"
react-slick@^0.29.0:
version "0.29.0"
resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.29.0.tgz#0bed5ea42bf75a23d40c0259b828ed27627b51bb"
integrity sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA==
dependencies:
classnames "^2.2.5"
enquire.js "^2.1.6"
json2mq "^0.2.0"
lodash.debounce "^4.0.8"
resize-observer-polyfill "^1.5.0"
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
@ -9059,6 +9159,11 @@ regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.9:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz"
integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-transform@^0.15.0:
version "0.15.0"
resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz"
@ -9140,6 +9245,11 @@ requires-port@^1.0.0:
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resize-observer-polyfill@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz"
@ -9492,6 +9602,11 @@ slash@^4.0.0:
resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz"
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
slick-carousel@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/slick-carousel/-/slick-carousel-1.8.1.tgz#a4bfb29014887bb66ce528b90bd0cda262cc8f8d"
integrity sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==
sockjs@^0.3.24:
version "0.3.24"
resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz"
@ -9618,6 +9733,11 @@ statuses@2.0.1:
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
string-convert@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz"
@ -9767,6 +9887,11 @@ stylis@4.1.3:
resolved "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz"
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"