Merge branch 'dev' into 'main'

Dev

See merge request frontend/marketplace!28
This commit is contained in:
Nastya 2023-08-04 14:49:05 +00:00
commit 1df8502813
48 changed files with 1425 additions and 1643 deletions

@ -12,7 +12,7 @@
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.16",
"@frontend/kitui": "^1.0.17",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@popperjs/core": "^2.11.8",

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.17458 14.8781L6.06831 15.6206H6.06831L6.17458 14.8781ZM13.8254 14.8781L13.7191 14.1357H13.7191L13.8254 14.8781ZM6.08301 6.66667C6.08301 4.50355 7.83656 2.75 9.99967 2.75V1.25C7.00813 1.25 4.58301 3.67512 4.58301 6.66667H6.08301ZM6.08301 8.27792V6.66667H4.58301V8.27792H6.08301ZM4.75 12.3175C4.75 12.0528 4.80329 11.8024 4.89902 11.5751L3.51659 10.9929C3.34466 11.4012 3.25 11.8493 3.25 12.3175H4.75ZM6.28085 14.1357C5.40271 14.01 4.75 13.2427 4.75 12.3175H3.25C3.25 13.9506 4.41397 15.3838 6.06831 15.6206L6.28085 14.1357ZM10 14.4675C8.89615 14.4675 7.46917 14.3058 6.28085 14.1357L6.06831 15.6206C7.27007 15.7926 8.78438 15.9675 10 15.9675V14.4675ZM13.7191 14.1357C12.5308 14.3058 11.1038 14.4675 10 14.4675V15.9675C11.2156 15.9675 12.7299 15.7926 13.9317 15.6206L13.7191 14.1357ZM15.25 12.3175C15.25 13.2427 14.5973 14.01 13.7191 14.1357L13.9317 15.6206C15.586 15.3838 16.75 13.9506 16.75 12.3175H15.25ZM15.1009 11.5749C15.1967 11.8023 15.25 12.0527 15.25 12.3175H16.75C16.75 11.8492 16.6553 11.4011 16.4833 10.9927L15.1009 11.5749ZM13.9163 6.66667V8.27698H15.4163V6.66667H13.9163ZM9.99967 2.75C12.1628 2.75 13.9163 4.50355 13.9163 6.66667H15.4163C15.4163 3.67512 12.9912 1.25 9.99967 1.25V2.75ZM16.4833 10.9927C16.3676 10.718 16.2341 10.4521 16.1085 10.2066C15.9792 9.95372 15.8606 9.72663 15.7536 9.49614C15.5397 9.03537 15.4163 8.64925 15.4163 8.27698H13.9163C13.9163 8.97611 14.1479 9.59971 14.393 10.1277C14.5155 10.3915 14.6523 10.6536 14.773 10.8896C14.8975 11.133 15.0087 11.3559 15.1009 11.5749L16.4833 10.9927ZM4.58301 8.27792C4.58301 8.65008 4.45972 9.0361 4.24596 9.49675C4.13904 9.72718 4.02048 9.95421 3.89121 10.207C3.7657 10.4525 3.63223 10.7183 3.51659 10.9929L4.89902 11.5751C4.99122 11.3561 5.10231 11.1333 5.22675 10.8899C5.34743 10.6539 5.48419 10.3919 5.60661 10.1281C5.85154 9.6003 6.08301 8.97685 6.08301 8.27792H4.58301Z" fill="#9A9AAF" />
<path d="M11.667 17.2173C11.3087 17.7796 10.696 18.1502 10.0003 18.1502C9.30467 18.1502 8.69197 17.7796 8.33366 17.2173" stroke-width="1.5" stroke-linecap="round" stroke="#9A9AAF" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,5 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="25" height="25" rx="6" fill="#FFE500" fill-opacity="0.5"/>
<path d="M11.2297 6.95313C11.2297 8.90625 12.1527 14.6387 12.5 14.6387C12.8472 14.6387 13.6643 8.90625 13.6643 6.95313C13.6643 5.65104 13.1944 5 12.5 5C11.8055 5 11.2297 5.65104 11.2297 6.95313Z" fill="#FB5607" stroke="#FB5607" stroke-linejoin="round"/>
<circle cx="12.5" cy="18.1936" r="1.38889" fill="#FB5607"/>
</svg>

After

Width:  |  Height:  |  Size: 489 B

@ -0,0 +1,3 @@
<svg width="22" height="19" viewBox="0 0 22 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.5714 7.29869V2.29869C19.5714 1.50971 18.9318 0.870118 18.1429 0.870118L2.42857 0.870117C1.63959 0.870117 1 1.50971 1 2.29869L1 16.5844C1 17.3734 1.63959 18.013 2.42857 18.013L18.1429 18.013C18.9318 18.013 19.5714 17.3734 19.5714 16.5844V11.5844M20.901 6.5844H13.8571C12.2792 6.5844 11 7.86359 11 9.44154C11 11.0195 12.2792 12.2987 13.8571 12.2987H20.901C20.9557 12.2987 21 12.2544 21 12.1997V6.68341C21 6.62873 20.9557 6.5844 20.901 6.5844Z" stroke="#9A9AAF" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 596 B

@ -1,28 +0,0 @@
import { Box } from "@mui/material";
import CustomAccordionBasket from "./CustomAccordionBasket";
import { cardShadow } from "@root/utils/themes/shadow";
interface Props {
content: { title: string; data: [string, number][] }[];
}
export default function AccordionWrapperBasket({ content }: Props) {
return (
<Box
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
{content.map((accordionItem, index) => (
<CustomAccordionBasket
key={index}
header={accordionItem.title}
dataSection={accordionItem.data}
totalPrice={3920}
/>
))}
</Box>
);
}

@ -1,46 +0,0 @@
import { Typography, useTheme } from "@mui/material";
import { useNavigate } from "react-router-dom";
interface Props {
text1: string;
text2?: string;
}
export default function ComplexNavText({ text1, text2 }: Props) {
const theme = useTheme();
const navigate = useNavigate();
return (
<Typography component="div" sx={{ display: "flex" }}>
<Typography
component="div"
onClick={() => navigate("/tariffs")}
sx={{
cursor: "pointer",
fontWeight: 400,
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey2.main,
}}
>
{text1}
</Typography>
{text2 &&
<Typography
component="span"
sx={{
cursor: "default",
fontWeight: 400,
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.fadePurple.main,
textUnderlinePosition: "under",
textDecorationColor: theme.palette.brightPurple.main,
}}
>
{text2}
</Typography>
}
</Typography>
);
}

@ -1,154 +0,0 @@
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ClearIcon from "@mui/icons-material/Clear";
import ExpandIcon from "./icons/ExpandIcon";
interface Props {
header: string;
totalPrice: number;
dataSection: [string, number][];
}
function TotalSum(mass: [string, number][]): number {
let sum: number = 0;
mass.forEach((element) => {
sum += element[1];
});
return sum;
}
export default function CustomAccordionBasket({ header, totalPrice, dataSection }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
let sum: number = TotalSum(dataSection);
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: "72px",
px: "20px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
}}
>
<Typography
sx={{
fontSize: upMd ? "20px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.text.secondary,
px: 0,
}}
>
{header}
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
height: "100%",
alignItems: "center",
gap: upSm ? "111px" : "17px",
}}
>
<Typography sx={{ color: theme.palette.grey3.main, fontSize: upSm ? "20px" : "16px", fontWeight: 500 }}>
{sum} руб.
</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>
{isExpanded &&
dataSection.map((item, index) => {
return (
<Box
key={index}
sx={{
px: "20px",
py: upMd ? "25px" : undefined,
pt: upMd ? undefined : "15px",
pb: upMd ? undefined : "25px",
backgroundColor: "#F1F2F6",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: "15px",
}}
>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>
{item[0]}
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
gap: "10px",
alignItems: "center",
width: upSm ? "195px" : "123px",
marginRight: upSm ? "65px" : 0,
}}
>
<Typography sx={{ color: theme.palette.grey3.main, fontSize: upSm ? "20px" : "16px", fontWeight: 500 }}>
{item[1]} руб.
</Typography>
{upSm ? (
<Typography
sx={{
color: theme.palette.text.secondary,
borderBottom: `1px solid ${theme.palette.text.secondary}`,
width: "max-content",
lineHeight: "19px",
}}
>
Удалить
</Typography>
) : (
<SvgIcon component={ClearIcon}></SvgIcon>
)}
</Box>
</Box>
);
})}
</Box>
);
}

@ -1,46 +0,0 @@
import { Dispatch, SetStateAction, useState } from "react";
import { List, ListItem, Typography, useMediaQuery, useTheme } from "@mui/material";
type Props = {
setType: Dispatch<SetStateAction<"templ" | "squiz" | "reducer">>;
_mocsk_: { name: string; type: "templ" | "squiz" | "reducer" }[];
};
export default function CustomRadioButtons({ setType, _mocsk_ }: Props) {
const theme = useTheme();
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [active, setActive] = useState<number>(0);
const activeType = (index: number, type: "templ" | "squiz" | "reducer") => {
setActive(index);
setType(type);
};
return (
<List
sx={{
marginLeft: "-10px",
width: upSm ? "430px" : "auto",
display: "flex",
flexWrap: upSm ? "" : "wrap",
fontWeight: "500",
fontSize: " 16px",
whiteSpace: "nowrap",
}}
>
{_mocsk_.map(({ name, type }, index) =>
active === index ? (
<ListItem key={type} onClick={() => activeType(index, type)} sx={{ color: "#7E2AEA", cursor: "pointer" }}>
<Typography component="span" sx={{ borderBottom: "1px solid #7E2AEA", fontSize: " 16px" }}>
{name}
</Typography>
</ListItem>
) : (
<ListItem key={type} onClick={() => activeType(index, type)} sx={{ cursor: "pointer" }}>
{name}
</ListItem>
)
)}
</List>
);
}

@ -1,3 +1,4 @@
import { useState, useRef, useCallback } from "react";
import {
Typography,
Drawer,
@ -6,16 +7,15 @@ import {
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";
import BasketIcon from "../assets/Icons/BasketIcon.svg";
import { useTickets } from "@frontend/kitui";
import SectionWrapper from "./SectionWrapper";
import CustomWrapperDrawer from "./CustomWrapperDrawer";
import CustomButton from "./CustomButton";
import { NotificationsModal } from "./NotificationsModal";
import { useNavigate } from "react-router";
import { useCart } from "@root/utils/hooks/useCart";
import { currencyFormatter } from "@root/utils/currencyFormatter";
@ -26,8 +26,19 @@ import {
} from "@root/stores/cart";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
import { useUserStore } from "@root/stores/user";
import {
updateTickets,
setTicketCount,
useTicketStore,
} from "@root/stores/tickets";
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg";
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg";
export default function Drawers() {
const [openNotificationsModal, setOpenNotificationsModal] =
useState<boolean>(false);
const bellRef = useRef<HTMLButtonElement | null>(null);
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
@ -40,6 +51,20 @@ export default function Drawers() {
(state) => state.summaryPriceAfterDiscountsMap
);
const userAccount = useUserStore((state) => state.userAccount);
const { tickets, ticketCount, apiPage, ticketsPerPage } = useTicketStore(
(state) => state
);
useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage: apiPage,
onNewTickets: useCallback((result) => {
if (result.data) updateTickets(result.data);
setTicketCount(result.count);
}, []),
onError: () => {},
});
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce(
(a, e) => a + e,
@ -54,13 +79,33 @@ export default function Drawers() {
const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice;
return (
<IconButton sx={{ p: 0 }}>
<Typography
onClick={openCartDrawer}
component="div"
<Box sx={{ display: "flex", gap: "20px" }}>
<IconButton
ref={bellRef}
aria-label="cart"
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
sx={{
position: "absolute",
cursor: "pointer",
borderRadius: "6px",
background: openNotificationsModal
? theme.palette.brightPurple.main
: theme.palette.background.default,
"& .MuiBadge-badge": {
background: openNotificationsModal
? theme.palette.background.default
: theme.palette.brightPurple.main,
color: openNotificationsModal
? theme.palette.brightPurple.main
: theme.palette.background.default,
},
"& svg > path:first-child": {
fill: openNotificationsModal ? "#FFFFFF" : "#9A9AAF",
},
"& svg > path:last-child": {
stroke: openNotificationsModal ? "#FFFFFF" : "#9A9AAF",
},
"&:hover": {
background: theme.palette.brightPurple.main,
"& .MuiBox-root": {
background: theme.palette.brightPurple.main,
},
@ -68,6 +113,57 @@ export default function Drawers() {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:first-child": { fill: "#FFFFFF" },
"& svg > path:last-child": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={ticketCount}
sx={{
"& .MuiBadge-badge": {
display: ticketCount ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
top: "2px",
right: "2px",
fontWeight: 400,
},
}}
>
<BellIcon />
</Badge>
</IconButton>
<NotificationsModal
open={openNotificationsModal}
setOpen={setOpenNotificationsModal}
anchorElement={bellRef.current}
notifications={tickets.map((ticket) => ({
text: "У вас новое сообщение от техподдержки",
date: new Date(ticket.updated_at).toLocaleDateString(),
watched: ticket.user === ticket.top_message.user_id,
}))}
/>
<IconButton
onClick={openCartDrawer}
component="div"
sx={{
cursor: "pointer",
background: theme.palette.background.default,
borderRadius: "6px",
"&:hover": {
background: theme.palette.brightPurple.main,
"& .MuiBox-root": {
background: theme.palette.brightPurple.main,
},
"& .MuiBadge-badge": {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:nth-child(1)": { fill: "#FFFFFF" },
"& svg > path:nth-child(2)": { fill: "#FFFFFF" },
"& svg > path:nth-child(3)": { stroke: "#FFFFFF" },
},
}}
>
@ -75,49 +171,19 @@ export default function Drawers() {
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
display: userAccount?.cart.length ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
top: "10px",
right: "10px",
top: "2px",
right: "2px",
fontWeight: 400,
},
}}
>
<IconsCreate svg={BasketIcon} bgcolor="#F2F3F7" />
<CartIcon />
</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>
)}
</IconButton>
<Drawer anchor={"right"} open={isDrawerOpen} onClose={closeCartDrawer}>
<SectionWrapper
maxWidth="lg"
@ -247,6 +313,6 @@ export default function Drawers() {
</Box>
</SectionWrapper>
</Drawer>
</IconButton>
</Box>
);
}

@ -19,7 +19,9 @@ interface Props {
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
}
export default function InputTextfield({
@ -41,7 +43,9 @@ export default function InputTextfield({
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const placeholderFont = upMd
? undefined
: { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
return (
<FormControl
@ -53,7 +57,7 @@ export default function InputTextfield({
...FormInputSx,
}}
>
{label &&
{label && (
<InputLabel
shrink
htmlFor={id}
@ -66,7 +70,7 @@ export default function InputTextfield({
>
{label}
</InputLabel>
}
)}
<TextField
{...TextfieldProps}
fullWidth
@ -79,6 +83,7 @@ export default function InputTextfield({
}}
inputProps={{
sx: {
boxSizing: "border-box",
backgroundColor: color,
border: "1px solid" + theme.palette.grey2.main,
borderRadius: "8px",

@ -1,13 +1,10 @@
import { Outlet } from "react-router-dom";
import Navbar from "./Navbar/Navbar";
export default function Layout() {
return (
<>
<Navbar isLoggedIn={true} />
<Navbar isLoggedIn={true}>
<Outlet />
</>
</Navbar>
);
}

@ -1,29 +1,20 @@
import { useState } from "react";
import { TransitionProps } from "@mui/material/transitions";
import logotip from "../../assets/Icons/logoPenaHab.svg";
import logotipBlack from "../../assets/Icons/black_logo_PenaHab.svg";
import CustomAvatar from "./Avatar";
import CloseIcon from "../icons/CloseIcons";
import React from "react";
import { Link, useLocation } from "react-router-dom";
import {
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";
import CustomAvatar from "./Avatar";
type MenuItem = {
name: string;
url: string;
@ -46,22 +37,11 @@ const arrayMenu: MenuItem[] = [
{ name: "История", url: "/history" },
];
const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<null>
) {
return <Slide direction="right" ref={ref} {...props} />;
});
interface DialogMenuProps {
open: boolean;
handleClose: () => void;
}
export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
export default function DialogMenu({ handleClose }: DialogMenuProps) {
const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number>(-1);
const theme = useTheme();
const location = useLocation();
@ -81,64 +61,12 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
);
return (
<Dialog
fullScreen
sx={{
zIndex: 1501,
width: isMobile ? "100%" : "320px",
mr: "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>
<Box sx={{ height: "100%", maxHeight: "calc(100vh - 51px)" }}>
<List
sx={{
maxWidth: "250px",
background: location.pathname === "/" ? "#333647" : "#FFFFFF",
height: "100vh",
p: "0",
height: "100%",
}}
>
<ListItem
@ -229,8 +157,7 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
</Box>
))}
</ListItem>
{isMobile ? (
location.pathname === "/" ? (
{location.pathname === "/" ? (
<Button
component={Link}
to={user ? "/tariffs" : "/signin"}
@ -283,22 +210,8 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
</Typography>
</Box>
</Box>
)
) : (
<Box
sx={{
position: "absolute",
right: "40px",
bottom: "60px",
}}
>
<img
src={location.pathname === "/" ? logotip : logotipBlack}
alt="icon"
/>
</Box>
)}
</List>
</Dialog>
</Box>
);
}

@ -2,14 +2,24 @@ import { useMediaQuery, useTheme } from "@mui/material";
import NavbarCollapsed from "./NavbarCollapsed";
import NavbarFull from "./NavbarFull";
import type { ReactNode } from "react";
interface Props {
isCollapsed?: boolean;
isLoggedIn: boolean;
children: ReactNode;
}
export default function Navbar({ isLoggedIn, isCollapsed = false }: Props) {
export default function Navbar({ isLoggedIn, children }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return upMd ? <NavbarFull isLoggedIn={isLoggedIn} /> : <NavbarCollapsed isLoggedIn={isLoggedIn} />;
return (
<>
{upMd ? (
<NavbarFull isLoggedIn={isLoggedIn}>{children}</NavbarFull>
) : (
<NavbarCollapsed isLoggedIn={isLoggedIn}>{children}</NavbarCollapsed>
)}
</>
);
}

@ -1,34 +1,70 @@
import { useState } from "react";
import { Badge, IconButton, useTheme } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { useState, useRef, useEffect, useCallback } from "react";
import { Box, Badge, Drawer, IconButton, useTheme } from "@mui/material";
import { Link } from "react-router-dom";
import { useTickets } from "@frontend/kitui";
import SectionWrapper from "../SectionWrapper";
import { NotificationsModal } from "../NotificationsModal";
import { useUserStore } from "@root/stores/user";
import {
updateTickets,
setTicketCount,
useTicketStore,
} from "@root/stores/tickets";
import MenuIcon from "@mui/icons-material/Menu";
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg";
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg";
import PenaLogo from "../PenaLogo";
import DialogMenu from "./DialogMenu";
import PenaLogo from "../PenaLogo";
import CloseIcon from "../icons/CloseIcons";
import cartIcon from "@root/assets/Icons/cart.svg";
import type { ReactNode } from "react";
interface Props {
isLoggedIn: boolean;
children: ReactNode;
}
export default function NavbarCollapsed({ isLoggedIn }: Props) {
export default function NavbarCollapsed({ isLoggedIn, children }: Props) {
const [open, setOpen] = useState(false);
const [openNotificationsModal, setOpenNotificationsModal] =
useState<boolean>(false);
const bellRef = useRef<HTMLButtonElement | null>(null);
const userAccount = useUserStore((state) => state.userAccount);
const { ticketCount, tickets, apiPage, ticketsPerPage } = useTicketStore(
(state) => state
);
useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage: apiPage,
onNewTickets: useCallback((result) => {
if (result.data) updateTickets(result.data);
setTicketCount(result.count);
}, []),
onError: () => {},
});
const theme = useTheme();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
useEffect(() => {
if (open) {
document.body.style.overflow = "hidden";
return;
}
document.body.style.overflow = "unset";
}, [open]);
return (
<SectionWrapper
component="nav"
@ -37,28 +73,38 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
backgroundColor: theme.palette.navbarbg.main,
position: "sticky",
top: 0,
zIndex: 1501,
// borderBottom: "1px solid #E3E3E3",
}}
sx={{ height: "51px", padding: "0" }}
>
<Box
sx={{
height: "51px",
py: "6px",
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
columnGap: "10px",
alignItems: "center",
height: "100%",
padding: "0 18px",
}}
>
<IconButton
onClick={handleClickOpen}
sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}
onClick={() => setOpen((isOpened) => !isOpened)}
sx={{
p: 0,
width: "30px",
color: theme.palette.primary.main,
}}
>
{open ? (
<CloseIcon />
) : (
<MenuIcon sx={{ height: "30px", width: "30px" }} />
)}
</IconButton>
<Link to="/cart">
<IconButton
aria-label="cart"
sx={{
width: "30px",
height: "30px",
background: theme.palette.background.default,
borderRadius: "6px",
"&:hover": {
@ -67,6 +113,9 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:nth-child(1)": { fill: "#FFFFFF" },
"& svg > path:nth-child(2)": { fill: "#FFFFFF" },
"& svg > path:nth-child(3)": { stroke: "#FFFFFF" },
},
}}
>
@ -74,23 +123,113 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
display: userAccount?.cart.length ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.8) translate(50%, -50%)",
transform: "scale(0.7) translate(50%, -50%)",
top: "2px",
right: "2px",
right: "3px",
fontWeight: 400,
},
}}
>
<img src={cartIcon} alt="cart" />
<CartIcon />
</Badge>
</IconButton>
</Link>
<IconButton
ref={bellRef}
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
aria-label="cart"
sx={{
width: "30px",
height: "30px",
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,
},
"& svg > path:first-child": { fill: "#FFFFFF" },
"& svg > path:last-child": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={ticketCount}
sx={{
"& .MuiBadge-badge": {
display: ticketCount ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.7) translate(50%, -50%)",
top: "3px",
right: "3px",
fontWeight: 400,
},
}}
>
<BellIcon />
</Badge>
</IconButton>
<NotificationsModal
open={openNotificationsModal}
setOpen={setOpenNotificationsModal}
anchorElement={bellRef.current}
notifications={tickets.map((ticket) => ({
text: "У вас новое сообщение от техподдержки",
date: new Date(ticket.updated_at).toLocaleDateString(),
watched: ticket.user === ticket.top_message.user_id,
}))}
/>
<Link to="/" style={{ marginLeft: "auto" }}>
<PenaLogo width={100} />
</Link>
<DialogMenu open={open} handleClose={handleClose} />
</Box>
<Box sx={{ display: "flex", overflow: open ? "hidden" : "unset" }}>
<Drawer
sx={{
width: 210,
position: "relative",
zIndex: open ? "none" : "-1",
"& .MuiDrawer-paper": {
position: "absolute",
top: "0",
width: 210,
height: "100%",
},
}}
variant="persistent"
anchor="left"
open={open}
>
<DialogMenu handleClose={handleClose} />
</Drawer>
<Box
sx={{
width: "100%",
minWidth: "100%",
minHeight: "calc(100vh - 51px)",
flexGrow: 1,
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
marginLeft: `-${210}px`,
...(open && {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: 0,
}),
}}
>
{children}
</Box>
</Box>
</SectionWrapper>
);
}

@ -9,7 +9,6 @@ import {
} from "@mui/material";
import SectionWrapper from "../SectionWrapper";
import LogoutIcon from "../icons/LogoutIcon";
import WalletIcon from "../icons/WalletIcon";
import CustomAvatar from "./Avatar";
import Drawers from "../Drawers";
import PenaLogo from "../PenaLogo";
@ -19,13 +18,19 @@ import { enqueueSnackbar } from "notistack";
import { clearUserData, useUserStore } from "@root/stores/user";
import { clearAuthToken, getMessageFromFetchError } from "@frontend/kitui";
import { clearCustomTariffs } from "@root/stores/customTariffs";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import walletIcon from "@root/assets/Icons/wallet_icon.svg";
import type { ReactNode } from "react";
interface Props {
isLoggedIn: boolean;
children: ReactNode;
}
export default function NavbarFull({ isLoggedIn }: Props) {
export default function NavbarFull({ isLoggedIn, children }: Props) {
const theme = useTheme();
const navigate = useNavigate();
const location = useLocation();
@ -45,7 +50,9 @@ export default function NavbarFull({ isLoggedIn }: Props) {
}
}
return isLoggedIn ? (
return (
<Box>
{isLoggedIn ? (
<Container
component="nav"
disableGutters
@ -64,18 +71,21 @@ export default function NavbarFull({ isLoggedIn }: Props) {
<PenaLogo width={124} />
</Link>
<Menu />
<Box
sx={{
display: "flex",
ml: "auto",
}}
>
<Box sx={{ display: "flex", ml: "auto" }}>
<Drawers />
<IconButton
sx={{ p: 0, ml: "8px" }}
sx={{
display: "flex",
alignItems: "center",
ml: "20px",
bgcolor: "#F2F3F7",
borderRadius: "6px",
height: "36px",
width: "36px",
}}
onClick={() => navigate("/wallet")}
>
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
<img src={walletIcon} alt="wallet" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
@ -87,7 +97,10 @@ export default function NavbarFull({ isLoggedIn }: Props) {
>
Мой баланс
</Typography>
<Typography variant="body2" color={theme.palette.brightPurple.main}>
<Typography
variant="body2"
color={theme.palette.brightPurple.main}
>
{currencyFormatter.format(cash / 100)}
</Typography>
</Box>
@ -144,5 +157,8 @@ export default function NavbarFull({ isLoggedIn }: Props) {
</Button>
</SectionWrapper>
</>
)}
<Box>{children}</Box>
</Box>
);
}

@ -0,0 +1,118 @@
import {
Popover,
List,
ListItem,
Typography,
useTheme,
useMediaQuery,
} from "@mui/material";
type Notification = {
text: string;
date: string;
watched?: boolean;
};
type NotificationsModalProps = {
open: boolean;
setOpen: (isOpen: boolean) => void;
anchorElement: Element | null;
notifications: Notification[];
};
export const NotificationsModal = ({
open,
setOpen,
anchorElement,
notifications,
}: NotificationsModalProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
return (
<Popover
open={open}
anchorEl={anchorElement}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
transformOrigin={{ vertical: "top", horizontal: "right" }}
sx={{
"& .MuiPopover-paper": {
maxWidth: isMobile ? "calc(100vw - 30px)" : 600,
width: "100%",
maxHeight: "310px",
borderRadius: "8px",
boxShadow:
"0px 3px 18px rgba(49, 28, 77, 0.1), 0px 3px 34px rgba(49, 28, 77, 0.15)",
"&::-webkit-scrollbar": { width: "6px" },
"&::-webkit-scrollbar-track": {
background: "#F0F0F6",
margin: "5px",
borderRadius: "5px",
},
"&::-webkit-scrollbar-thumb": {
width: "4px",
background: "#9A9AAF",
borderRadius: "5px",
},
},
}}
>
<List sx={{ width: "100%", padding: "5px" }}>
{notifications.map(({ text, date, watched = true }) => (
<ListItem
sx={{
display: "flex",
alignItems: isMobile ? "normal" : "center",
justifyContent: "space-between",
flexDirection: isMobile ? "column-reverse" : "unset",
borderBottom: "1px solid #F2F3F7",
padding: "20px 10px",
background: watched ? "none" : "#F0F0F6",
borderRadius: watched ? "0" : "8px",
"&:first-child": {
borderTop: "1px solid #F2F3F7",
},
}}
>
<Typography
sx={{
position: "relative",
fontSize: "16px",
lineHeight: "19px",
paddingLeft: watched ? "0" : "35px",
fontWeight: watched ? "normal" : "bold",
"&::before": {
content: '""',
display: watched ? "none" : "block",
position: "absolute",
left: "10px",
top: isMobile ? "5px" : "50%",
transform: isMobile ? "none" : "translateY(-50%)",
height: "8px",
width: "8px",
borderRadius: "50%",
background: "#7E2AEA",
},
}}
>
{text}
</Typography>
<Typography
sx={{
fontSize: "14px",
lineHeight: "19px",
color: "#9A9AAF",
fontWeight: watched ? "normal" : "bold",
paddingLeft: isMobile ? (watched ? "0" : "35px") : "0",
marginBottom: isMobile ? "5px" : "0",
}}
>
{date}
</Typography>
</ListItem>
))}
</List>
</Popover>
);
};

@ -1,12 +1,18 @@
import { useState } from "react";
import { Select as MuiSelect, MenuItem, useTheme } from "@mui/material";
import { useState, useRef } from "react";
import {
Select as MuiSelect,
MenuItem,
Box,
Typography,
useTheme,
} from "@mui/material";
import classnames from "classnames";
import { cardShadow } from "@root/utils/themes/shadow";
import checkIcon from "@root/assets/Icons/check.svg";
import "./select.css";
import checkIcon from "@root/assets/Icons/check.svg";
import type { SelectChangeEvent } from "@mui/material";
type SelectProps = {
items: string[];
@ -20,36 +26,73 @@ export const Select = ({
setSelectedItem,
}: SelectProps) => {
const [opened, setOpened] = useState<boolean>(false);
const [currentValue, setCurrentValue] = useState<string>(items[selectedItem]);
const ref = useRef<HTMLDivElement | null>(null);
const theme = useTheme();
const selectItem = (event: SelectChangeEvent<HTMLDivElement>) => {
setCurrentValue(items[Number(event.target.value)]);
setSelectedItem(Number(event.target.value));
};
return (
<MuiSelect
className="select"
value={selectedItem}
onChange={(event) => setSelectedItem(Number(event.target.value))}
<Box>
<Box
sx={{
zIndex: 1500,
position: "relative",
width: "100%",
height: "56px",
padding: "16px 50px 16px 14px",
color: theme.palette.brightPurple.main,
border: "2px solid #ffffff",
borderRadius: "30px",
fontWeight: "bold",
boxShadow: cardShadow,
background: "#EFF0F5",
boxShadow:
"0px 5px 40px #d2d0e194, 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.4)",
}}
open={opened}
onClick={() => setOpened((isOpened) => !isOpened)}
IconComponent={() => (
onClick={() => ref.current?.click()}
>
<Typography
sx={{
fontWeight: "bold",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{currentValue}
</Typography>
<img
src={checkIcon}
alt="check"
style={{
position: "absolute",
top: "50%",
right: "10px",
transform: "translateY(-50%)",
height: "36px",
width: "36px",
}}
className={classnames("select-icon", { opened })}
/>
)}
</Box>
<MuiSelect
ref={ref}
className="select"
open={opened}
MenuProps={{ disablePortal: true }}
sx={{ width: "100%" }}
onChange={selectItem}
onClick={() => setOpened((isOpened) => !isOpened)}
>
{items.map((item, index) => (
<MenuItem key={item + index} value={index}>
<MenuItem key={item + index} value={index} sx={{ padding: "12px" }}>
{item}
</MenuItem>
))}
</MuiSelect>
</Box>
);
};

@ -1,10 +1,3 @@
.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;
}
@ -20,7 +13,16 @@
}
.MuiPaper-root.MuiMenu-paper {
padding-top: 60px;
margin-top: -60px;
padding-top: 50px;
margin-top: -50px;
border-radius: 28px;
}
.MuiInputBase-root.MuiOutlinedInput-root {
display: block;
}
.MuiInputBase-root.MuiOutlinedInput-root > div:first-child,
.MuiInputBase-root.MuiOutlinedInput-root .MuiSelect-icon {
display: none;
}

@ -1,24 +0,0 @@
import { Typography } from "@mui/material";
import { useNavigate } from "react-router";
type Props = {
text: string;
};
export default function StepperSquiz({ text }: Props) {
const navigate = useNavigate();
return (
<Typography component="div">
<Typography
onClick={() => navigate("/tariffs")}
component="div"
sx={{ cursor: "pointer", fontWeight: "400px", fontSize: "12px", lineHeight: "14px", marginBottom: "19px" }}
>
Все тарифы
</Typography>
<Typography component="span" sx={{ fontWeight: "400px", fontSize: "12px", color: "#C19AF5", cursor: "default" }}>
{text}
</Typography>
</Typography>
);
}

@ -1,8 +0,0 @@
import React from "react";
interface Props {
style: {width:string,height:string}
}
export default function (props:Props) {
}

@ -1,7 +1,7 @@
import { Box } from "@mui/material";
export default function UploadIcon() {
export default function SendIcon() {
return (
<Box sx={{

@ -5,7 +5,7 @@ interface Props {
bgcolor: string;
}
export default function WalletIcon({ color, bgcolor }: Props) {
export default function SendIcon({ color, bgcolor }: Props) {
return (
<Box
sx={{
@ -19,7 +19,13 @@ export default function WalletIcon({ color, bgcolor }: Props) {
ml: "8px",
}}
>
<svg width="22" height="19" viewBox="0 0 22 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="22"
height="19"
viewBox="0 0 22 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.5714 7.29857V2.29857C19.5714 1.50959 18.9318 0.869996 18.1429 0.869996L2.42857 0.869995C1.63959 0.869995 1 1.50959 1 2.29857L1 16.5843C1 17.3733 1.63959 18.0128 2.42857 18.0128L18.1429 18.0128C18.9318 18.0128 19.5714 17.3733 19.5714 16.5843V11.5843M20.901 6.58428H13.8571C12.2792 6.58428 11 7.86347 11 9.44142C11 11.0194 12.2792 12.2986 13.8571 12.2986H20.901C20.9557 12.2986 21 12.2542 21 12.1996V6.68329C21 6.62861 20.9557 6.58428 20.901 6.58428Z"
stroke={color}

@ -9,10 +9,10 @@ import {
useMediaQuery,
useTheme,
} from "@mui/material";
import * as React from 'react';
import InputAdornment from '@mui/material/InputAdornment';
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import * as React from "react";
import InputAdornment from "@mui/material/InputAdornment";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
interface Props {
id: string;
@ -22,10 +22,12 @@ import {
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
}
export default function ({
export default function PasswordInput({
id,
label,
bold = false,
@ -44,18 +46,21 @@ import {
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const placeholderFont = upMd
? undefined
: { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
const [showPassword, setShowPassword] = React.useState(false);
const handleClickShowPassword = () => setShowPassword((show) => !show);
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
const handleMouseDownPassword = (
event: React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
};
return (
<FormControl
fullWidth
variant="standard"
@ -63,7 +68,7 @@ import {
gap,
// mt: "10px",
...FormInputSx,
position: "relative"
position: "relative",
}}
>
<InputLabel
@ -96,26 +101,34 @@ import {
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
sx={{
position: "absolute",
right: "15px",
top: "5px",
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
sx: {
padding: "0px",
border: "1px solid" + theme.palette.grey2.main,
backgroundColor: color,
borderRadius: "8px",
height: "48px",
py: 0,
color: "black",
...placeholderFont,
"& .MuiInputBase-input": {
boxSizing: "border-box",
height: "100%",
padding: "14px",
},
},
}}
onChange={onChange}
type={showPassword ? 'text' : 'password'}
type={showPassword ? "text" : "password"}
/>
</FormControl>
);
}

@ -5,14 +5,7 @@ import {
useMediaQuery,
useTheme,
} from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
import SectionWrapper from "@components/SectionWrapper";
import card1Image from "@root/assets/landing/card1.png";
import card2Image from "@root/assets/landing/card2.png";
import card3Image from "@root/assets/landing/card3.png";
import cardImageBig from "@root/assets/landing/card1big.png";
export default function () {
const theme = useTheme();

@ -1,25 +1,23 @@
import {Box, Typography, useMediaQuery, useTheme} from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
import SectionWrapper from "@components/SectionWrapper";
import card1Image from "@root/assets/landing/card1.png";
import card2Image from "@root/assets/landing/card2.png";
import card3Image from "@root/assets/landing/card3.png";
import cardImageBig from "@root/assets/landing/card1big.png";
export default function () {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));console.log("я узкий")
return <Box sx={{
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<Box
sx={{
mt: upMd ? "93px" : "55px",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-evenly",
columnGap: "40px",
rowGap: "50px",
backgroundColor: "\"#E6E6EB"
}}>
backgroundColor: '"#E6E6EB',
}}
>
<CardWithLink
headerText="Шаблонизатор"
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
@ -28,4 +26,5 @@ export default function () {
isHighlighted={!upMd}
/>
</Box>
);
}

@ -1,19 +1,6 @@
import {
Box,
Typography,
useMediaQuery,
useTheme,
Button,
SxProps,
Theme,
} from "@mui/material";
import { Box, Typography, Button, SxProps, Theme } from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
import SectionWrapper from "@components/SectionWrapper";
import card1Image from "@root/assets/landing/card1.png";
import card2Image from "@root/assets/landing/card2.png";
import card3Image from "@root/assets/landing/card3.png";
import cardImageBig from "@root/assets/landing/card1big.png";
interface Props {
@ -22,9 +9,6 @@ interface Props {
}
export default function ({ light = true, sx }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<Box
sx={{

@ -1,23 +0,0 @@
import { Box } from "@mui/material";
interface Props {
bgcolor: string;
svg: string;
}
export const IconsCreate = ({ bgcolor, svg }: Props) => (
<Box
component="div"
sx={{
bgcolor,
height: "36px",
width: "36px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "6px",
}}
>
<img src={svg} alt="svg" />
</Box>
);

@ -1,4 +1,3 @@
import type { VerificationStatus } from "@root/model/account";
import type { Attachment } from "@root/model/attachment";
type File = {

@ -3,7 +3,6 @@ import SectionWrapper from "@components/SectionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import TotalPrice from "@components/TotalPrice";
import CustomWrapper from "./CustomWrapper";
import ComplexNavText from "@root/components/ComplexNavText";
import { useCart } from "@root/utils/hooks/useCart";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
@ -29,7 +28,6 @@ export default function Basket() {
mb: upMd ? "70px" : "37px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Корзина" />}
<Box
sx={{
mt: "20px",

@ -5,7 +5,6 @@ import { useState } from "react";
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 = [
@ -31,9 +30,6 @@ export default function Faq() {
mb: upMd ? "70px" : "37px",
}}
>
{upMd && (
<ComplexNavText text1="Все тарифы —" text2=" Вопросы и ответы" />
)}
<Box
sx={{
mt: "20px",

@ -8,7 +8,6 @@ import {
} 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";
@ -32,7 +31,6 @@ export default function History() {
mb: upMd ? "70px" : "37px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="История" />}
<Box
sx={{
mt: "20px",

@ -1,15 +1,9 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
import WideTemplCard from "@components/wideTemplCard";
import TemplCardPhonePink from "@components/templCardPhonePink";
import SectionWrapper from "@components/SectionWrapper";
import card1Image from "@root/assets/landing/card1.png";
import card2Image from "@root/assets/landing/card2.png";
import card3Image from "@root/assets/landing/card3.png";
import cardImageBig from "@root/assets/landing/card1big.png";
interface Props {
templaterOnly?: boolean;

@ -1,8 +1,13 @@
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import {
Box,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import CustomButton from "@components/CustomButton";
import SectionWrapper from "@components/SectionWrapper";
import ComplexNavText from "@components/ComplexNavText";
import PaymentMethodCard from "./PaymentMethodCard";
import mastercardLogo from "../../assets/bank-logo/logo-mastercard.png";
import visaLogo from "../../assets/bank-logo/logo-visa.png";
@ -18,7 +23,6 @@ import { enqueueSnackbar } from "notistack";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { useLocation } from "react-router-dom";
const paymentMethods = [
{ name: "Mastercard", image: mastercardLogo },
{ name: "Visa", image: visaLogo },
@ -27,34 +31,44 @@ const paymentMethods = [
{ name: "Тинькофф", image: tinkoffLogo },
] as const;
type PaymentMethod = typeof paymentMethods[number]["name"];
type PaymentMethod = (typeof paymentMethods)[number]["name"];
export default function Payment() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
const [selectedPaymentMethod, setSelectedPaymentMethod] =
useState<PaymentMethod | null>(null);
const [paymentValueField, setPaymentValueField] = useState<string>("0");
const [paymentLink, setPaymentLink] = useState<string>("");
const location = useLocation();
const notEnoughMoneyAmount = location.state?.notEnoughMoneyAmount as number ?? 0;
const notEnoughMoneyAmount =
(location.state?.notEnoughMoneyAmount as number) ?? 0;
useEffect(() => {
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setPaymentLink("");
}, [selectedPaymentMethod]);
const paymentValue = parseFloat(paymentValueField) * 100;
function handleChoosePaymentClick() {
sendPayment().then(result => {
if (Number(paymentValueField) !== 0) {
sendPayment()
.then((result) => {
setPaymentLink(result.link);
}).catch(error => {
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
}
return (
<SectionWrapper
@ -64,7 +78,6 @@ export default function Payment() {
mb: "70px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Способ оплаты" />}
<Box
sx={{
mt: "20px",
@ -74,7 +87,9 @@ export default function Payment() {
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
@ -91,9 +106,7 @@ export default function Payment() {
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: upMd
? cardShadow
: undefined,
boxShadow: upMd ? cardShadow : undefined,
}}
>
<Box
@ -107,7 +120,7 @@ export default function Payment() {
alignContent: "start",
}}
>
{paymentMethods.map(method =>
{paymentMethods.map((method) => (
<PaymentMethodCard
isSelected={selectedPaymentMethod === method.name}
key={method.name}
@ -115,7 +128,7 @@ export default function Payment() {
image={method.image}
onClick={() => setSelectedPaymentMethod(method.name)}
/>
)}
))}
</Box>
<Box
sx={{
@ -128,7 +141,9 @@ export default function Payment() {
p: upMd ? "20px" : undefined,
pl: upMd ? "33px" : undefined,
mt: upMd ? undefined : "30px",
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
borderLeft: upMd
? `1px solid ${theme.palette.grey2.main}`
: undefined,
}}
>
<Box
@ -140,7 +155,7 @@ export default function Payment() {
>
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
<Typography mb="20px">К оплате</Typography>
{paymentLink ?
{paymentLink ? (
<Typography
sx={{
fontWeight: 500,
@ -151,24 +166,22 @@ export default function Payment() {
>
{currencyFormatter.format(paymentValue / 100)}
</Typography>
:
) : (
<InputTextfield
TextfieldProps={{
placeholder: "К оплате",
value: paymentValueField,
type: "number",
}}
onChange={e => setPaymentValueField(e.target.value)}
onChange={(e) => setPaymentValueField(e.target.value)}
id="payment-amount"
gap={upMd ? "16px" : "10px"}
color={"#F2F3F7"}
FormInputSx={{
mb: "28px",
}}
FormInputSx={{ mb: "28px" }}
/>
}
)}
</Box>
{paymentLink ?
{paymentLink ? (
<CustomButton
component="a"
href={paymentLink}
@ -181,7 +194,7 @@ export default function Payment() {
>
Оплатить
</CustomButton>
:
) : (
<CustomButton
disabled={!isFinite(paymentValue)}
variant={"outlined"}
@ -194,7 +207,7 @@ export default function Payment() {
>
Выбрать
</CustomButton>
}
)}
</Box>
</Box>
</SectionWrapper>

@ -1,10 +1,13 @@
import { IconButton, useMediaQuery, useTheme } from "@mui/material";
import { Box } from "@mui/material";
import { Typography } from "@mui/material";
import {
IconButton,
Box,
Typography,
useMediaQuery,
useTheme,
} 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();
@ -18,12 +21,6 @@ export default function Faq() {
mb: upMd ? "70px" : "37px",
}}
>
{upMd && (
<ComplexNavText
text1="Все тарифы — Кастомный тариф —"
text2="Сохраненные тарифы"
/>
)}
<Box
sx={{
mt: "20px",

@ -1,38 +1,47 @@
import { Typography, Box, useTheme, useMediaQuery, IconButton } from "@mui/material";
import {
Typography,
Box,
useTheme,
useMediaQuery,
IconButton,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useParams } from "react-router-dom";
import { Link, useParams } from "react-router-dom";
import SectionWrapper from "@components/SectionWrapper";
import ComplexNavText from "@components/ComplexNavText";
import SupportChat from "./SupportChat";
import CreateTicket from "./CreateTicket";
import TicketList from "./TicketList/TicketList";
import { useCallback } from "react";
import { Ticket, getMessageFromFetchError, useToken } from "@frontend/kitui";
import { updateTickets, setTicketCount, clearTickets, useTicketStore } from "@root/stores/tickets";
import {
updateTickets,
setTicketCount,
clearTickets,
useTicketStore,
} from "@root/stores/tickets";
import { enqueueSnackbar } from "notistack";
import { useSSESubscription, useTickets } from "@frontend/kitui";
export default function Support() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const ticketId = useParams().ticketId;
const ticketApiPage = useTicketStore(state => state.apiPage);
const ticketsPerPage = useTicketStore(state => state.ticketsPerPage);
const ticketApiPage = useTicketStore((state) => state.apiPage);
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage);
const token = useToken();
const fetchState = useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage,
onNewTickets: useCallback(result => {
onNewTickets: useCallback((result) => {
if (result.data) updateTickets(result.data);
setTicketCount(result.count);
}, []),
onError: useCallback((error: Error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
}, [])
}, []),
});
useSSESubscription<Ticket>({
@ -42,7 +51,7 @@ export default function Support() {
onDisconnect: useCallback(() => {
clearTickets();
}, []),
marker: "ticket"
marker: "ticket",
});
return (
@ -50,11 +59,11 @@ export default function Support() {
maxWidth="lg"
sx={{
pt: upMd ? "25px" : "20px",
pb: upMd ? "82px" : "43px",
height: "100%",
pb: upMd ? "82px" : "20px",
height: "calc(100vh - 51px)",
maxHeight: "calc(100vh - 51px)",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Запрос в службу техподдержки" />}
<Box
sx={{
mt: "20px",
@ -63,12 +72,23 @@ export default function Support() {
gap: "10px",
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<Link
to="/support"
style={{
textDecoration: "none",
display: "flex",
alignItems: "center",
columnGap: "10px",
color: theme.palette.common.black,
}}
>
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Запрос в службу техподдержки</Typography>
</Link>
</Box>
{ticketId ? (
<SupportChat />

@ -17,6 +17,7 @@ import { getMessageFromFetchError, useEventListener, useSSESubscription, useTick
export default function SupportChat() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.up(460));
const [messageField, setMessageField] = useState<string>("");
const tickets = useTicketStore(state => state.tickets);
const messages = useMessageStore(state => state.messages);
@ -134,6 +135,7 @@ export default function SupportChat() {
borderRadius: "12px",
p: upMd ? "20px" : undefined,
gap: "40px",
height: !upMd ? `calc(100% - ${isMobile ? 90 : 115}px)` : null,
boxShadow: upMd
? cardShadow
: undefined,

@ -1,20 +1,23 @@
import { Link as RouterLink } from "react-router-dom";
import { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomButton from "@components/CustomButton";
import { cardShadow } from "@root/utils/themes/shadow";
interface Props {
name: string;
body: string;
time: string;
ticketId: string;
}
import ExclamationPointIcon from "@root/assets/Icons/exclamation_point.svg";
export default function TicketCard({ name, body, time, ticketId }: Props) {
import type { Ticket } from "@frontend/kitui";
type TicketCardProps = {
ticket: Ticket;
};
export default function TicketCard({ ticket }: TicketCardProps) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const adminReplied = ticket.user !== ticket.top_message.user_id;
const timeText = useMemo(
() => (
@ -28,10 +31,10 @@ export default function TicketCard({ name, body, time, ticketId }: Props) {
mb: "5px",
}}
>
{time}
{new Date(ticket.updated_at).toLocaleDateString()}
</Typography>
),
[theme.palette.grey2.main, time]
[theme.palette.grey2.main, ticket]
);
return (
@ -48,8 +51,22 @@ export default function TicketCard({ name, body, time, ticketId }: Props) {
boxShadow: cardShadow,
}}
>
{!upMd && timeText}
{!upMd && <Typography>{timeText}</Typography>}
<Box>
{adminReplied && (
<Box
sx={{
display: "flex",
alignItems: "center",
columnGap: "10px",
marginTop: !upMd ? "-20px" : null,
marginBottom: "20px",
}}
>
<img src={ExclamationPointIcon} alt="ExclamationPoint" />
<Typography sx={{ color: "#FB5607" }}>Вам ответили</Typography>
</Box>
)}
<Typography
sx={{
mb: "20px",
@ -58,9 +75,11 @@ export default function TicketCard({ name, body, time, ticketId }: Props) {
fontWeight: 500,
}}
>
{name}
{ticket.title}
</Typography>
<Typography color={theme.palette.grey3.main}>
{ticket.top_message.message}
</Typography>
<Typography color={theme.palette.grey3.main}>{body}</Typography>
</Box>
<Box
sx={{
@ -75,7 +94,7 @@ export default function TicketCard({ name, body, time, ticketId }: Props) {
<CustomButton
variant="outlined"
component={RouterLink}
to={`/support/${ticketId}`}
to={`/support/${ticket.id}`}
sx={{
py: "9px",
color: theme.palette.brightPurple.main,

@ -1,21 +1,32 @@
import { CircularProgress, List, ListItem, Box, useTheme, Pagination } from "@mui/material";
import {
CircularProgress,
List,
ListItem,
Box,
useTheme,
Pagination,
} from "@mui/material";
import TicketCard from "./TicketCard";
import { setTicketApiPage, useTicketStore } from "@root/stores/tickets";
import { Ticket } from "@frontend/kitui";
interface Props {
fetchState: "fetching" | "idle" | "all fetched";
}
export default function TicketList({ fetchState }: Props) {
const theme = useTheme();
const tickets = useTicketStore(state => state.tickets);
const ticketCount = useTicketStore(state => state.ticketCount);
const ticketApiPage = useTicketStore(state => state.apiPage);
const ticketsPerPage = useTicketStore(state => state.ticketsPerPage);
const tickets = useTicketStore((state) => state.tickets);
const ticketCount = useTicketStore((state) => state.ticketCount);
const ticketApiPage = useTicketStore((state) => state.apiPage);
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage);
const sortedTickets = tickets.sort(sortTicketsByUpdateTime).slice(ticketApiPage * ticketsPerPage, (ticketApiPage + 1) * ticketsPerPage);
const sortedTickets = tickets
.sort(sortTicketsByUpdateTime)
.slice(
ticketApiPage * ticketsPerPage,
(ticketApiPage + 1) * ticketsPerPage
);
return (
<Box
@ -39,12 +50,7 @@ export default function TicketList({ fetchState }: Props) {
>
{sortedTickets.map((ticket) => (
<ListItem key={ticket.id} disablePadding>
<TicketCard
name={ticket.title}
body={ticket.top_message.message}
time={new Date(ticket.updated_at).toLocaleDateString()}
ticketId={ticket.id}
/>
<TicketCard ticket={ticket} />
</ListItem>
))}
{fetchState === "fetching" && (
@ -59,18 +65,21 @@ export default function TicketList({ fetchState }: Props) {
justifyContent: "center",
}}
>
<CircularProgress sx={{ color: theme.palette.brightPurple.main }} size={60} />
<CircularProgress
sx={{ color: theme.palette.brightPurple.main }}
size={60}
/>
</Box>
)}
</List>
{ticketCount > ticketsPerPage &&
{ticketCount > ticketsPerPage && (
<Pagination
count={Math.ceil(ticketCount / ticketsPerPage)}
page={ticketApiPage + 1}
onChange={(e, value) => setTicketApiPage(value - 1)}
sx={{ alignSelf: "center" }}
/>
}
)}
</Box>
);
}

@ -1,82 +0,0 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomButton from "@root/components/CustomButton";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
import { currencyFormatter } from "@root/utils/currencyFormatter";
export default function Summary() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
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);
return (
<Box sx={{
display: "flex",
flexDirection: upMd ? "row" : "column",
mt: upMd ? "80px" : "70px",
pt: upMd ? "30px" : undefined,
borderTop: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}>
<Box sx={{
width: upMd ? "68.5%" : undefined,
pr: upMd ? "15%" : 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,
width: upMd ? "31.5%" : undefined,
pl: upMd ? "33px" : undefined,
}}>
<Box sx={{
display: "flex",
flexDirection: upMd ? "column" : "row",
alignItems: upMd ? "start" : "center",
mt: upMd ? "10px" : "30px",
gap: "15px",
}}>
<Typography
variant="oldPrice"
sx={{ order: upMd ? 1 : 2 }}
>
{currencyFormatter.format(basePrice / 100)} руб.
</Typography>
<Typography
variant="price"
sx={{
fontWeight: 500,
fontSize: "26px",
lineHeight: "31px",
order: upMd ? 2 : 1,
}}
>
{currencyFormatter.format(discountedPrice / 100)} руб.
</Typography>
</Box>
<CustomButton
variant="contained"
sx={{
mt: "25px",
backgroundColor: theme.palette.brightPurple.main,
}}
>
Выбрать
</CustomButton>
</Box>
</Box>
);
}

@ -1,7 +1,6 @@
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";
import ComplexHeader from "@root/components/ComplexHeader";
import CustomTariffCard from "./CustomTariffCard";
@ -39,7 +38,6 @@ export default function TariffConstructor() {
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
sx={{
mt: "20px",

@ -1,60 +0,0 @@
import { Box, SxProps, Theme, Typography, useTheme } from "@mui/material";
import CustomButton from "@root/components/CustomButton";
interface Props {
image?: string;
headerText: string;
text: string;
linkHref: string;
sx?: SxProps<Theme>;
}
export default function ImageTextButtonCard({ image, headerText, text, linkHref, sx }: Props) {
const theme = useTheme();
return (
<Box sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
flexBasis: "300px",
alignItems: "start",
p: "20px",
maxWidth: "360px",
backgroundColor: "#E6E6EB",
border: "1px solid #DBDBDB",
borderRadius: "12px",
boxShadow: "0 10px 0 -5px #BABBC8",
...sx,
}}>
{image &&
<img
src={image}
alt=""
style={{
objectFit: "contain",
width: "100%",
display: "block",
marginBottom: "-10px",
}}
/>
}
<Typography variant="h5">{headerText}</Typography>
<Typography mt="15px" mb="27px" maxWidth="90%">{text}</Typography>
<CustomButton
variant="contained"
sx={{
backgroundColor: "white",
color: theme.palette.primary.main,
mt: "auto",
"&:hover": {
backgroundColor: "#dddddd",
}
}}
>
Подробнее
</CustomButton>
</Box >
);
}

@ -96,12 +96,13 @@ export default function TariffCard({
<Tooltip key={index} title={line} placement="top">
<Typography
sx={{
height: "42px",
height: "65px",
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2,
MozBoxOrient: "vertical",
WebkitLineClamp: 3,
}}
>
{line}
@ -115,12 +116,13 @@ export default function TariffCard({
sx={{
minHeight: "calc(1.185*2em)",
marginBottom: "auto",
height: "42px",
height: "65px",
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2,
MozBoxOrient: "vertical",
WebkitLineClamp: 3,
}}
>
{text}
@ -134,7 +136,7 @@ export default function TariffCard({
sx={{
color: theme.palette.brightPurple.main,
borderColor: theme.palette.brightPurple.main,
mt: "35px",
mt: "10px",
...buttonProps.sx,
}}
>

@ -1,93 +0,0 @@
import { Box, SxProps, Theme, useTheme } from "@mui/material";
import Typography from "@mui/material/Typography";
import CustomButton from "@components/CustomButton";
interface Props {
icon: React.ReactNode;
headerText: string;
text: string;
money?: number;
sx: SxProps<Theme>;
href: string;
buttonBorderColor?: string;
buttonTextColor?: string;
moneyColor?: string;
onclick: () => void;
textButton: string;
}
export default function TariffCardTimeAndVolume({
icon,
headerText,
text,
sx,
href,
buttonBorderColor,
buttonTextColor,
money = 0,
moneyColor,
onclick,
textButton,
}: Props) {
const theme = useTheme();
return (
<Box
component="div"
sx={{
maxWidth: "360px",
width: "360px",
bgcolor: "white",
borderRadius: "12px",
display: "flex",
flexDirection: "column",
alignItems: "start",
p: "20px",
...sx,
}}
>
<Box
component="div"
sx={{
display: "flex",
justifyContent: "space-around",
alignItems: "center",
width: "100%",
}}
>
<Typography component="div">{icon}</Typography>
<Typography
component="div"
variant="h5"
sx={{
display: "flex",
justifyContent: "right",
width: "100%",
color: `${moneyColor ? moneyColor : "#4D4D4D"}`,
}}
>
{money} руб.
</Typography>
</Box>
<Typography variant="h5" sx={{ mt: "14px", mb: "10px" }}>
{headerText}
</Typography>
<Typography component="div" sx={{ minHeight: "calc(1.185*2em)" }}>
{text}
</Typography>
<CustomButton
onClick={onclick}
variant="outlined"
sx={{
color: `${buttonTextColor ? buttonTextColor : theme.palette.brightPurple.main}`,
borderColor: `${buttonBorderColor ? buttonBorderColor : theme.palette.brightPurple.main}`,
mt: "33px",
}}
>
{textButton}
</CustomButton>
</Box>
);
}

@ -1,18 +1,13 @@
import { Outlet, Route, Routes, useNavigate } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { useMediaQuery, useTheme, Box, Link, Typography } from "@mui/material";
import SectionWrapper from "@components/SectionWrapper";
import CustomIcon from "@components/icons/CustomIcon";
import CalendarIcon from "@components/icons/CalendarIcon";
import PieChartIcon from "@components/icons/PieChartIcon";
import TariffCard from "./TariffCard";
import card1Image from "@root/assets/landing/card1.png";
import card2Image from "@root/assets/landing/card2.png";
import card3Image from "@root/assets/landing/card3.png";
import ImageTextButtonCard from "./ImageTextButtonCard";
import WideTemplCard from "@components/wideTemplCard";
import TemplCardPhoneLight from "@components/templCardPhoneLight";
export default function Tariffs() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
@ -34,39 +29,55 @@ export default function Tariffs() {
width: upMd ? "100%" : undefined,
mt: "40px",
mb: "30px",
display: "flex",
display: "grid",
flexWrap: "wrap",
justifyContent: "space-evenly",
justifyContent: "center",
gap: upMd ? "40px" : "30px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
}}
>
<TariffCard
icon={<CalendarIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
icon={
<CalendarIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Тарифы на время"
text="безлимит на 1 месяц , 3 , 6 , 12"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("time")
onClick: () => navigate("time"),
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={<PieChartIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
icon={
<PieChartIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Тариф на объем"
text="200 шаблонов, 1000 шаблонов, 5000 шаблонов, 10 000 шаблонов"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("volume")
onClick: () => navigate("volume"),
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={<CustomIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
icon={
<CustomIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Кастом"
text="Текст-заполнитель — это текст, который имеет "
buttonProps={{
text: "Подробнее",
onClick: () => navigate("/tariffconstructor")
onClick: () => navigate("/tariffconstructor"),
}}
sx={{ maxWidth: "360px" }}
/>
@ -88,36 +99,11 @@ export default function Tariffs() {
Наши продукты
</Typography>
{upMd ?
{upMd ? (
<WideTemplCard sx={{ marginTop: "60px" }} />
:
<TemplCardPhoneLight />}
{/*<Box sx={{*/}
{/* mt: upMd ? "55px" : "40px",*/}
{/* display: "flex",*/}
{/* flexWrap: "wrap",*/}
{/* justifyContent: "space-evenly",*/}
{/* gap: upMd ? "40px" : "30px",*/}
{/*}}>*/}
{/* <ImageTextButtonCard*/}
{/* headerText="Шаблонизатор"*/}
{/* text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "*/}
{/* linkHref="#"*/}
{/* image={card1Image}*/}
{/* />*/}
{/* <ImageTextButtonCard*/}
{/* headerText="Опросник"*/}
{/* text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "*/}
{/* linkHref="#"*/}
{/* image={card2Image}*/}
{/* />*/}
{/* <ImageTextButtonCard*/}
{/* headerText="Сокращатель ссылок"*/}
{/* text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного текста, но является "*/}
{/* linkHref="#"*/}
{/* image={card3Image}*/}
{/* />*/}
{/*</Box>*/}
) : (
<TemplCardPhoneLight />
)}
</SectionWrapper>
);
}

@ -2,7 +2,6 @@ import { useState } from "react";
import { useLocation } from "react-router-dom";
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";
@ -128,9 +127,6 @@ export default function TariffPage() {
flexDirection: "column",
}}
>
{upMd && (
<ComplexNavText text1="Все тарифы — " text2={StepperText[unit]} />
)}
<Typography variant="h4" sx={{ marginBottom: "23px", mt: "20px" }}>
{StepperText[unit]}
</Typography>
@ -149,6 +145,7 @@ export default function TariffPage() {
)}
<Box
sx={{
justifyContent: "center",
mt: "40px",
mb: "30px",
display: "grid",

@ -4,7 +4,6 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import CustomButton from "@components/CustomButton";
import WalletIcon from "@components/icons/WalletIcon";
import SectionWrapper from "@components/SectionWrapper";
import ComplexNavText from "@components/ComplexNavText";
import { cardShadow } from "@root/utils/themes/shadow";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { useUserStore } from "@root/stores/user";
@ -58,7 +57,6 @@ export default function Wallet() {
mb: "70px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Мой кошелёк" />}
<Box
sx={{
mt: "20px",

@ -656,6 +656,7 @@ const templategenTariff1: Tariff = {
isCustom: false,
privilegies: [
{
_id: "p1",
name: "n1",
privilegeId: "p1",
serviceKey: "templategen",
@ -678,6 +679,7 @@ const templategenTariff2: Tariff = {
isCustom: false,
privilegies: [
{
_id: "p5",
name: "n5",
privilegeId: "p5",
serviceKey: "templategen",
@ -700,6 +702,7 @@ const squizTariff: Tariff = {
isCustom: false,
privilegies: [
{
_id: "p2",
name: "n2",
privilegeId: "p2",
serviceKey: "squiz",
@ -722,6 +725,7 @@ const reducerTariff: Tariff = {
isCustom: false,
privilegies: [
{
_id: "p3",
name: "n3",
privilegeId: "p3",
serviceKey: "reducer",

@ -1495,10 +1495,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@frontend/kitui@^1.0.16":
version "1.0.16"
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.16.tgz#bd3f9912d02a983a30a985c7d8732df7bfb3390d"
integrity sha1-vT+ZEtAqmDowqYXH2HMt97+zOQ0=
"@frontend/kitui@^1.0.17":
version "1.0.17"
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.17.tgz#a5bddaaa18b168be0e1814d5cfbd86e4030d15af"
integrity sha1-pb3aqhixaL4OGBTVz72G5AMNFa8=
dependencies:
immer "^10.0.2"
reconnecting-eventsource "^1.6.2"