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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
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))}
|
||||
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"
|
||||
|
Loading…
Reference in New Issue
Block a user