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>
);
}

@ -1,95 +1,100 @@
import {
FormControl,
InputLabel,
SxProps,
TextField,
TextFieldProps,
Theme,
useMediaQuery,
useTheme,
FormControl,
InputLabel,
SxProps,
TextField,
TextFieldProps,
Theme,
useMediaQuery,
useTheme,
} from "@mui/material";
import "./text-input.css";
interface Props {
id: string;
label?: string;
bold?: boolean;
gap?: string;
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
id: string;
label?: string;
bold?: boolean;
gap?: string;
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
}
export default function InputTextfield({
id,
label,
bold = false,
gap = "10px",
onChange,
TextfieldProps,
color,
FormInputSx,
id,
label,
bold = false,
gap = "10px",
onChange,
TextfieldProps,
color,
FormInputSx,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const labelFont = upMd
? bold
? theme.typography.p1
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
const labelFont = upMd
? bold
? theme.typography.p1
: { ...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
fullWidth
variant="standard"
sx={{
gap,
// mt: "10px",
...FormInputSx,
}}
return (
<FormControl
fullWidth
variant="standard"
sx={{
gap,
// mt: "10px",
...FormInputSx,
}}
>
{label && (
<InputLabel
shrink
htmlFor={id}
sx={{
position: "inherit",
color: "black",
transform: "none",
...labelFont,
}}
>
{label &&
<InputLabel
shrink
htmlFor={id}
sx={{
position: "inherit",
color: "black",
transform: "none",
...labelFont,
}}
>
{label}
</InputLabel>
}
<TextField
{...TextfieldProps}
fullWidth
id={id}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "8px",
},
}}
inputProps={{
sx: {
backgroundColor: color,
border: "1px solid" + theme.palette.grey2.main,
borderRadius: "8px",
height: "48px",
py: 0,
color: "black",
...placeholderFont,
},
}}
onChange={onChange}
/>
</FormControl>
);
{label}
</InputLabel>
)}
<TextField
{...TextfieldProps}
fullWidth
id={id}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "8px",
},
}}
inputProps={{
sx: {
boxSizing: "border-box",
backgroundColor: color,
border: "1px solid" + theme.palette.grey2.main,
borderRadius: "8px",
height: "48px",
py: 0,
color: "black",
...placeholderFont,
},
}}
onChange={onChange}
/>
</FormControl>
);
}

@ -1,13 +1,10 @@
import { Outlet } from "react-router-dom";
import Navbar from "./Navbar/Navbar";
export default function Layout() {
return (
<>
<Navbar isLoggedIn={true} />
<Outlet />
</>
);
}
return (
<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,76 +157,61 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
</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>
)
{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",
right: "40px",
bottom: "60px",
bottom: "0",
}}
>
<img
src={location.pathname === "/" ? logotip : logotipBlack}
alt="icon"
/>
<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>
)}
</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,77 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
backgroundColor: theme.palette.navbarbg.main,
position: "sticky",
top: 0,
zIndex: 1501,
// borderBottom: "1px solid #E3E3E3",
}}
sx={{
height: "51px",
py: "6px",
display: "flex",
justifyContent: "flex-start",
alignItems: "center",
columnGap: "10px",
}}
sx={{ height: "51px", padding: "0" }}
>
<IconButton
onClick={handleClickOpen}
sx={{ p: 0, width: "30px", color: theme.palette.primary.main }}
<Box
sx={{
display: "flex",
columnGap: "10px",
alignItems: "center",
height: "100%",
padding: "0 18px",
}}
>
<MenuIcon sx={{ height: "30px", width: "30px" }} />
</IconButton>
<Link to="/cart">
<IconButton
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": {
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" },
},
}}
>
<Badge
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
display: userAccount?.cart.length ? "flex" : "none",
color: "#FFFFFF",
background: theme.palette.brightPurple.main,
transform: "scale(0.7) translate(50%, -50%)",
top: "2px",
right: "3px",
fontWeight: 400,
},
}}
>
<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": {
@ -67,30 +152,84 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
background: theme.palette.background.default,
color: theme.palette.brightPurple.main,
},
"& svg > path:first-child": { fill: "#FFFFFF" },
"& svg > path:last-child": { stroke: "#FFFFFF" },
},
}}
>
<Badge
badgeContent={userAccount?.cart.length}
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",
transform: "scale(0.7) translate(50%, -50%)",
top: "3px",
right: "3px",
fontWeight: 400,
},
}}
>
<img src={cartIcon} alt="cart" />
<BellIcon />
</Badge>
</IconButton>
</Link>
<Link to="/" style={{ marginLeft: "auto" }}>
<PenaLogo width={100} />
</Link>
<DialogMenu open={open} handleClose={handleClose} />
<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>
</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,104 +50,115 @@ export default function NavbarFull({ isLoggedIn }: Props) {
}
}
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 />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}
return (
<Box>
{isLoggedIn ? (
<Container
component="nav"
disableGutters
maxWidth={false}
sx={{
px: "16px",
display: "flex",
height: "80px",
alignItems: "center",
gap: "60px",
bgcolor: "white",
borderBottom: "1px solid #E3E3E3",
}}
>
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
</IconButton>
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
<Typography
<Link to="/">
<PenaLogo width={124} />
</Link>
<Menu />
<Box sx={{ display: "flex", ml: "auto" }}>
<Drawers />
<IconButton
sx={{
display: "flex",
alignItems: "center",
ml: "20px",
bgcolor: "#F2F3F7",
borderRadius: "6px",
height: "36px",
width: "36px",
}}
onClick={() => navigate("/wallet")}
>
<img src={walletIcon} alt="wallet" />
</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={{
fontSize: "12px",
lineHeight: "14px",
color: theme.palette.grey3.main,
px: "20px",
display: "flex",
justifyContent: "space-between",
height: "80px",
alignItems: "center",
gap: "50px",
}}
>
Мой баланс
</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>
</>
<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>
</>
)}
<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))}
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={() => (
<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",
background: "#EFF0F5",
boxShadow:
"0px 5px 40px #d2d0e194, 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.4)",
}}
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 })}
/>
)}
>
{items.map((item, index) => (
<MenuItem key={item + index} value={index}>
{item}
</MenuItem>
))}
</MuiSelect>
</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} 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}

@ -1,121 +1,134 @@
import {
FormControl,
IconButton,
InputLabel,
SxProps,
TextField,
TextFieldProps,
Theme,
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';
interface Props {
id: string;
label?: string;
bold?: boolean;
gap?: string;
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
}
export default function ({
id,
label,
bold = false,
gap = "10px",
onChange,
TextfieldProps,
color,
FormInputSx,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const labelFont = upMd
? bold
? theme.typography.p1
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
const placeholderFont = upMd ? undefined : { fontWeight: 400, fontSize: "16px", lineHeight: "19px" };
FormControl,
IconButton,
InputLabel,
SxProps,
TextField,
TextFieldProps,
Theme,
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";
const [showPassword, setShowPassword] = React.useState(false);
interface Props {
id: string;
label?: string;
bold?: boolean;
gap?: string;
color?: string;
FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps;
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
}
const handleClickShowPassword = () => setShowPassword((show) => !show);
export default function PasswordInput({
id,
label,
bold = false,
gap = "10px",
onChange,
TextfieldProps,
color,
FormInputSx,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
};
return (
<FormControl
fullWidth
variant="standard"
const labelFont = upMd
? bold
? theme.typography.p1
: { ...theme.typography.body1, fontWeight: 500 }
: theme.typography.body2;
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>
) => {
event.preventDefault();
};
return (
<FormControl
fullWidth
variant="standard"
sx={{
gap,
// mt: "10px",
...FormInputSx,
position: "relative",
}}
>
<InputLabel
shrink
htmlFor={id}
sx={{
gap,
// mt: "10px",
...FormInputSx,
position: "relative"
position: "inherit",
color: "black",
transform: "none",
...labelFont,
}}
>
<InputLabel
shrink
htmlFor={id}
sx={{
position: "inherit",
{label}
</InputLabel>
<TextField
{...TextfieldProps}
fullWidth
id={id}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "8px",
},
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
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",
color: "black",
transform: "none",
...labelFont,
}}
>
{label}
</InputLabel>
<TextField
{...TextfieldProps}
fullWidth
id={id}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "8px",
...placeholderFont,
"& .MuiInputBase-input": {
boxSizing: "border-box",
height: "100%",
padding: "14px",
},
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
sx: {
border: "1px solid" + theme.palette.grey2.main,
backgroundColor: color,
borderRadius: "8px",
height: "48px",
py: 0,
color: "black",
...placeholderFont,
},
}}
onChange={onChange}
type={showPassword ? 'text' : 'password'}
/>
</FormControl>
);
}
},
}}
onChange={onChange}
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,31 +1,30 @@
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 theme = useTheme();
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"
}}>
<CardWithLink
headerText="Шаблонизатор"
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
linkHref="#"
image={card1Image}
isHighlighted={!upMd}
/>
backgroundColor: '"#E6E6EB',
}}
>
<CardWithLink
headerText="Шаблонизатор"
text="Текст- это текст, который имеет некоторые характеристики реального письменного текс"
linkHref="#"
image={card1Image}
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,185 +23,193 @@ 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 },
{ name: "QIWI Кошелек", image: qiwiLogo },
{ name: "Мир", image: mirLogo },
{ name: "Тинькофф", image: tinkoffLogo },
{ name: "Mastercard", image: mastercardLogo },
{ name: "Visa", image: visaLogo },
{ name: "QIWI Кошелек", image: qiwiLogo },
{ name: "Мир", image: mirLogo },
{ 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 [paymentValueField, setPaymentValueField] = useState<string>("0");
const [paymentLink, setPaymentLink] = useState<string>("");
const location = useLocation();
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 [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(() => {
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const paymentValue = parseFloat(paymentValueField) * 100;
useEffect(() => {
setPaymentLink("");
}, [selectedPaymentMethod]);
function handleChoosePaymentClick() {
sendPayment().then(result => {
setPaymentLink(result.link);
}).catch(error => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
const paymentValue = parseFloat(paymentValueField) * 100;
function handleChoosePaymentClick() {
if (Number(paymentValueField) !== 0) {
sendPayment()
.then((result) => {
setPaymentLink(result.link);
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "25px",
mb: "70px",
}}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "25px",
mb: "70px",
}}
>
<Box
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && (
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Способ оплаты</Typography>
</Box>
{!upMd && (
<Typography variant="body2" mb="30px">
Выберите способ оплаты
</Typography>
)}
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: upMd ? cardShadow : undefined,
}}
>
<Box
sx={{
width: upMd ? "68.5%" : undefined,
p: upMd ? "20px" : undefined,
display: "flex",
flexDirection: upSm ? "row" : "column",
flexWrap: "wrap",
gap: upMd ? "14px" : "20px",
alignContent: "start",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Способ оплаты" />}
<Box
{paymentMethods.map((method) => (
<PaymentMethodCard
isSelected={selectedPaymentMethod === method.name}
key={method.name}
name={method.name}
image={method.image}
onClick={() => setSelectedPaymentMethod(method.name)}
/>
))}
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.grey3.main,
width: upMd ? "31.5%" : undefined,
p: upMd ? "20px" : undefined,
pl: upMd ? "33px" : undefined,
mt: upMd ? undefined : "30px",
borderLeft: upMd
? `1px solid ${theme.palette.grey2.main}`
: undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
maxWidth: "85%",
}}
>
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
<Typography mb="20px">К оплате</Typography>
{paymentLink ? (
<Typography
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
fontWeight: 500,
fontSize: "20px",
lineHeight: "48px",
mb: "28px",
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Способ оплаты</Typography>
</Box>
{!upMd && (
<Typography variant="body2" mb="30px">
Выберите способ оплаты
</Typography>
>
{currencyFormatter.format(paymentValue / 100)}
</Typography>
) : (
<InputTextfield
TextfieldProps={{
placeholder: "К оплате",
value: paymentValueField,
type: "number",
}}
onChange={(e) => setPaymentValueField(e.target.value)}
id="payment-amount"
gap={upMd ? "16px" : "10px"}
color={"#F2F3F7"}
FormInputSx={{ mb: "28px" }}
/>
)}
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: upMd
? cardShadow
: undefined,
}}
</Box>
{paymentLink ? (
<CustomButton
component="a"
href={paymentLink}
variant={"contained"}
sx={{
borderColor: theme.palette.brightPurple.main,
backgroundColor: theme.palette.brightPurple.main,
mt: "auto",
}}
>
<Box
sx={{
width: upMd ? "68.5%" : undefined,
p: upMd ? "20px" : undefined,
display: "flex",
flexDirection: upSm ? "row" : "column",
flexWrap: "wrap",
gap: upMd ? "14px" : "20px",
alignContent: "start",
}}
>
{paymentMethods.map(method =>
<PaymentMethodCard
isSelected={selectedPaymentMethod === method.name}
key={method.name}
name={method.name}
image={method.image}
onClick={() => setSelectedPaymentMethod(method.name)}
/>
)}
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.grey3.main,
width: upMd ? "31.5%" : undefined,
p: upMd ? "20px" : undefined,
pl: upMd ? "33px" : undefined,
mt: upMd ? undefined : "30px",
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
maxWidth: "85%",
}}
>
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
<Typography mb="20px">К оплате</Typography>
{paymentLink ?
<Typography
sx={{
fontWeight: 500,
fontSize: "20px",
lineHeight: "48px",
mb: "28px",
}}
>
{currencyFormatter.format(paymentValue / 100)}
</Typography>
:
<InputTextfield
TextfieldProps={{
placeholder: "К оплате",
value: paymentValueField,
type: "number",
}}
onChange={e => setPaymentValueField(e.target.value)}
id="payment-amount"
gap={upMd ? "16px" : "10px"}
color={"#F2F3F7"}
FormInputSx={{
mb: "28px",
}}
/>
}
</Box>
{paymentLink ?
<CustomButton
component="a"
href={paymentLink}
variant={"contained"}
sx={{
borderColor: theme.palette.brightPurple.main,
backgroundColor: theme.palette.brightPurple.main,
mt: "auto",
}}
>
Оплатить
</CustomButton>
:
<CustomButton
disabled={!isFinite(paymentValue)}
variant={"outlined"}
onClick={handleChoosePaymentClick}
sx={{
borderColor: theme.palette.brightPurple.main,
backgroundColor: "",
mt: "auto",
}}
>
Выбрать
</CustomButton>
}
</Box>
</Box>
</SectionWrapper>
);
Оплатить
</CustomButton>
) : (
<CustomButton
disabled={!isFinite(paymentValue)}
variant={"outlined"}
onClick={handleChoosePaymentClick}
sx={{
borderColor: theme.palette.brightPurple.main,
backgroundColor: "",
mt: "auto",
}}
>
Выбрать
</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,89 +1,109 @@
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 token = useToken();
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 token = useToken();
const fetchState = useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage,
onNewTickets: useCallback(result => {
if (result.data) updateTickets(result.data);
setTicketCount(result.count);
}, []),
onError: useCallback((error: Error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
}, [])
});
const fetchState = useTickets({
url: "https://hub.pena.digital/heruvym/getTickets",
ticketsPerPage,
ticketApiPage,
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>({
enabled: Boolean(token),
url: `https://admin.pena.digital/heruvym/subscribe?Authorization=${token}`,
onNewData: updateTickets,
onDisconnect: useCallback(() => {
clearTickets();
}, []),
marker: "ticket"
});
useSSESubscription<Ticket>({
enabled: Boolean(token),
url: `https://admin.pena.digital/heruvym/subscribe?Authorization=${token}`,
onNewData: updateTickets,
onDisconnect: useCallback(() => {
clearTickets();
}, []),
marker: "ticket",
});
return (
<SectionWrapper
maxWidth="lg"
sx={{
pt: upMd ? "25px" : "20px",
pb: upMd ? "82px" : "43px",
height: "100%",
}}
return (
<SectionWrapper
maxWidth="lg"
sx={{
pt: upMd ? "25px" : "20px",
pb: upMd ? "82px" : "20px",
height: "calc(100vh - 51px)",
maxHeight: "calc(100vh - 51px)",
}}
>
<Box
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
}}
>
<Link
to="/support"
style={{
textDecoration: "none",
display: "flex",
alignItems: "center",
columnGap: "10px",
color: theme.palette.common.black,
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Запрос в службу техподдержки" />}
<Box
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && (
<IconButton sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Запрос в службу техподдержки</Typography>
</Box>
{ticketId ? (
<SupportChat />
) : (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: upMd ? "40px" : "60px",
}}
>
<CreateTicket />
<TicketList fetchState={fetchState} />
</Box>
)}
</SectionWrapper>
);
<IconButton
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
<Typography variant="h4">Запрос в службу техподдержки</Typography>
</Link>
</Box>
{ticketId ? (
<SupportChat />
) : (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: upMd ? "40px" : "60px",
}}
>
<CreateTicket />
<TicketList fetchState={fetchState} />
</Box>
)}
</SectionWrapper>
);
}

@ -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,82 +1,91 @@
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";
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 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 sortedTickets = tickets.sort(sortTicketsByUpdateTime).slice(ticketApiPage * ticketsPerPage, (ticketApiPage + 1) * ticketsPerPage);
return (
<Box
sx={{
display: "flex",
gap: "40px",
flexDirection: "column",
}}
>
<List
sx={{
p: 0,
minHeight: "120px",
display: "flex",
flexDirection: "column",
gap: "40px",
opacity: fetchState === "fetching" ? 0.4 : 1,
transitionProperty: "opacity",
transitionDuration: "200ms",
}}
>
{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}
/>
</ListItem>
))}
{fetchState === "fetching" && (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
minHeight: "120px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CircularProgress sx={{ color: theme.palette.brightPurple.main }} size={60} />
</Box>
)}
</List>
{ticketCount > ticketsPerPage &&
<Pagination
count={Math.ceil(ticketCount / ticketsPerPage)}
page={ticketApiPage + 1}
onChange={(e, value) => setTicketApiPage(value - 1)}
sx={{ alignSelf: "center" }}
/>
}
</Box>
const sortedTickets = tickets
.sort(sortTicketsByUpdateTime)
.slice(
ticketApiPage * ticketsPerPage,
(ticketApiPage + 1) * ticketsPerPage
);
return (
<Box
sx={{
display: "flex",
gap: "40px",
flexDirection: "column",
}}
>
<List
sx={{
p: 0,
minHeight: "120px",
display: "flex",
flexDirection: "column",
gap: "40px",
opacity: fetchState === "fetching" ? 0.4 : 1,
transitionProperty: "opacity",
transitionDuration: "200ms",
}}
>
{sortedTickets.map((ticket) => (
<ListItem key={ticket.id} disablePadding>
<TicketCard ticket={ticket} />
</ListItem>
))}
{fetchState === "fetching" && (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
minHeight: "120px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CircularProgress
sx={{ color: theme.palette.brightPurple.main }}
size={60}
/>
</Box>
)}
</List>
{ticketCount > ticketsPerPage && (
<Pagination
count={Math.ceil(ticketCount / ticketsPerPage)}
page={ticketApiPage + 1}
onChange={(e, value) => setTicketApiPage(value - 1)}
sx={{ alignSelf: "center" }}
/>
)}
</Box>
);
}
function sortTicketsByUpdateTime(ticket1: Ticket, ticket2: Ticket) {
const date1 = new Date(ticket1.updated_at).getTime();
const date2 = new Date(ticket2.updated_at).getTime();
return date2 - date1;
}
const date1 = new Date(ticket1.updated_at).getTime();
const date2 = new Date(ticket2.updated_at).getTime();
return date2 - date1;
}

@ -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,123 +1,109 @@
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"));
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const navigate = useNavigate();
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "60px" : "20px",
mb: upMd ? "90px" : "75px",
display: "flex",
flexDirection: "column",
}}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "60px" : "20px",
mb: upMd ? "90px" : "75px",
display: "flex",
flexDirection: "column",
}}
>
<Typography variant="h4">Выберите удобный тариф</Typography>
<Box
sx={{
width: upMd ? "100%" : undefined,
mt: "40px",
mb: "30px",
display: "grid",
flexWrap: "wrap",
justifyContent: "center",
gap: upMd ? "40px" : "30px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
}}
>
<TariffCard
icon={
<CalendarIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Тарифы на время"
text="безлимит на 1 месяц , 3 , 6 , 12"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("time"),
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={
<PieChartIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Тариф на объем"
text="200 шаблонов, 1000 шаблонов, 5000 шаблонов, 10 000 шаблонов"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("volume"),
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={
<CustomIcon
color="white"
bgcolor={theme.palette.brightPurple.main}
/>
}
headerText="Кастом"
text="Текст-заполнитель — это текст, который имеет "
buttonProps={{
text: "Подробнее",
onClick: () => navigate("/tariffconstructor"),
}}
sx={{ maxWidth: "360px" }}
/>
</Box>
<Typography component="div">
{`Или попробуйте наш `}
<Link
href="#"
sx={{
color: theme.palette.brightPurple.main,
textUnderlinePosition: "under",
textDecorationColor: theme.palette.brightPurple.main,
}}
>
<Typography variant="h4">Выберите удобный тариф</Typography>
<Box
sx={{
width: upMd ? "100%" : undefined,
mt: "40px",
mb: "30px",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-evenly",
gap: upMd ? "40px" : "30px",
}}
>
<TariffCard
icon={<CalendarIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
headerText="Тарифы на время"
text="безлимит на 1 месяц , 3 , 6 , 12"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("time")
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={<PieChartIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
headerText="Тариф на объем"
text="200 шаблонов, 1000 шаблонов, 5000 шаблонов, 10 000 шаблонов"
buttonProps={{
text: "Подробнее",
onClick: () => navigate("volume")
}}
sx={{ maxWidth: "360px" }}
/>
<TariffCard
icon={<CustomIcon color="white" bgcolor={theme.palette.brightPurple.main} />}
headerText="Кастом"
text="Текст-заполнитель — это текст, который имеет "
buttonProps={{
text: "Подробнее",
onClick: () => navigate("/tariffconstructor")
}}
sx={{ maxWidth: "360px" }}
/>
</Box>
<Typography component="div">
{`Или попробуйте наш `}
<Link
href="#"
sx={{
color: theme.palette.brightPurple.main,
textUnderlinePosition: "under",
textDecorationColor: theme.palette.brightPurple.main,
}}
>
бесплатный план
</Link>
</Typography>
<Typography variant="h4" sx={{ mt: upMd ? "60px" : "70px" }}>
Наши продукты
</Typography>
бесплатный план
</Link>
</Typography>
<Typography variant="h4" sx={{ mt: upMd ? "60px" : "70px" }}>
Наши продукты
</Typography>
{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>*/}
</SectionWrapper>
);
{upMd ? (
<WideTemplCard sx={{ marginTop: "60px" }} />
) : (
<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"