Merge branch 'dev' into 'main'
Dev See merge request frontend/marketplace!28
This commit is contained in:
commit
1df8502813
@ -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",
|
||||
|
4
src/assets/Icons/bell.svg
Normal file
4
src/assets/Icons/bell.svg
Normal file
@ -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 |
5
src/assets/Icons/exclamation_point.svg
Normal file
5
src/assets/Icons/exclamation_point.svg
Normal file
@ -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 |
3
src/assets/Icons/wallet_icon.svg
Normal file
3
src/assets/Icons/wallet_icon.svg
Normal file
@ -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>
|
||||
);
|
||||
}
|
||||
|
118
src/components/NotificationsModal.tsx
Normal file
118
src/components/NotificationsModal.tsx
Normal file
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user