Merge branch 'deploy' into 'main'

fix: design

See merge request frontend/marketplace!20
This commit is contained in:
Nastya 2023-07-28 16:37:39 +00:00
commit b572f162a0
23 changed files with 961 additions and 785 deletions

@ -1,26 +1,69 @@
import { Slider, SliderProps, styled } from "@mui/material";
import { useState, useEffect } from "react";
import { Slider, useTheme } from "@mui/material";
type CustomSliderProps = {
value: number;
min: number;
max: number;
onChange: (value: number | number[]) => void;
};
export default styled(Slider)<SliderProps>(({ theme }) => ({
color: theme.palette.brightPurple.main,
height: "12px",
"& .MuiSlider-track": {
border: "none",
},
"& .MuiSlider-rail": {
backgroundColor: "#F2F3F7",
border: `1px solid ${theme.palette.grey2.main}`,
},
"& .MuiSlider-thumb": {
height: 32,
width: 32,
border: `6px solid ${theme.palette.brightPurple.main}`,
backgroundColor: "white",
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
export const CustomSlider = ({
value,
min = 0,
max = 100,
onChange,
}: CustomSliderProps) => {
const theme = useTheme();
const [step, setStep] = useState<number>(1);
useEffect(() => {
if (value < 100) {
return setStep(10);
}
if (value < 500) {
return setStep(20);
}
if (value < 2000) {
return setStep(50);
}
setStep(150);
}, [value]);
return (
<Slider
value={value}
defaultValue={0}
min={min}
max={max}
step={step}
onChange={(_, newValue) => onChange(newValue)}
sx={{
color: theme.palette.brightPurple.main,
height: "12px",
"& .MuiSlider-track": {
border: "none",
},
},
}));
"& .MuiSlider-rail": {
backgroundColor: "#F2F3F7",
border: `1px solid ${theme.palette.grey2.main}`,
},
"& .MuiSlider-thumb": {
height: 32,
width: 32,
border: `6px solid ${theme.palette.brightPurple.main}`,
backgroundColor: "white",
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
},
},
}}
/>
);
};

@ -2,7 +2,6 @@ import { useState } from "react";
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import { cardShadow } from "@root/utils/themes/shadow";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { removeTariffFromCart } from "@root/stores/user";
import { enqueueSnackbar } from "notistack";
@ -35,7 +34,6 @@ export default function CustomWrapperDrawer({ serviceData }: Props) {
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<Box

@ -25,12 +25,9 @@ import {
useCartStore,
} from "@root/stores/cart";
import { useCustomTariffsStore } from "@root/stores/customTariffs";
import { useUserStore } from "@root/stores/user";
type DrawersProps = {
cartItemsAmount?: number;
};
export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
export default function Drawers() {
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
@ -42,6 +39,7 @@ export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
const summaryPriceAfterDiscountsMap = useCustomTariffsStore(
(state) => state.summaryPriceAfterDiscountsMap
);
const userAccount = useUserStore((state) => state.userAccount);
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce(
(a, e) => a + e,
@ -74,7 +72,7 @@ export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
}}
>
<Badge
badgeContent={cartItemsAmount}
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
color: "#FFFFFF",
@ -132,8 +130,8 @@ export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
<Box
sx={{
width: "100%",
pt: "20px",
pb: "20px",
pt: "12px",
pb: "12px",
display: "flex",
justifyContent: "space-between",
bgcolor: "#F2F3F7",
@ -202,7 +200,6 @@ export default function Drawers({ cartItemsAmount = 0 }: DrawersProps) {
color: theme.palette.grey3.main,
pb: "100px",
pt: "38px",
pl: upMd ? "20px" : undefined,
}}
>
<Box

@ -152,7 +152,7 @@ export default function Chat({ sx }: Props) {
<Box sx={{
display: "flex",
flexDirection: "column",
height: "clamp(250px, 100dvh - 90px, 600px)",
height: "clamp(250px, calc(100vh - 90px), 600px)",
backgroundColor: "#944FEE",
borderRadius: "8px",
...sx,

@ -28,6 +28,7 @@ export default function Menu() {
},
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "Корзина", url: "/basket" },
{ name: "История", url: "/history" },
];
return (

@ -42,6 +42,7 @@ const arrayMenu: MenuItem[] = [
},
{ name: "Вопросы и ответы", url: "/faq" },
{ name: "Корзина", url: "/basket" },
{ name: "История", url: "/history" },
];
const Transition = React.forwardRef(function Transition(

@ -1,25 +1,23 @@
import { useState } from "react";
import { Badge, IconButton, useTheme } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { Link } from "react-router-dom";
import SectionWrapper from "../SectionWrapper";
import { useUserStore } from "@root/stores/user";
import PenaLogo from "../PenaLogo";
import DialogMenu from "./DialogMenu";
import { Link } from "react-router-dom";
import cartIcon from "@root/assets/Icons/cart.svg";
interface Props {
isLoggedIn: boolean;
cartItemsAmount?: number;
}
export default function NavbarCollapsed({
isLoggedIn,
cartItemsAmount = 5,
}: Props) {
export default function NavbarCollapsed({ isLoggedIn }: Props) {
const [open, setOpen] = useState(false);
const userAccount = useUserStore((state) => state.userAccount);
const theme = useTheme();
@ -73,7 +71,7 @@ export default function NavbarCollapsed({
}}
>
<Badge
badgeContent={cartItemsAmount}
badgeContent={userAccount?.cart.length}
sx={{
"& .MuiBadge-badge": {
color: "#FFFFFF",

@ -70,7 +70,7 @@ export default function NavbarFull({ isLoggedIn }: Props) {
ml: "auto",
}}
>
<Drawers cartItemsAmount={3} />
<Drawers />
<IconButton
sx={{ p: 0, ml: "8px" }}
onClick={() => navigate("/wallet")}

@ -1,87 +1,98 @@
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material";
import { useState } from "react";
import { InputAdornment, TextField, Typography, useTheme } from "@mui/material";
import type { ChangeEvent } from "react";
interface Props {
id: string;
adornmentText: string;
onChange: (value: number) => void;
id: string;
adornmentText: string;
onChange: (value: number) => void;
}
export default function NumberInputWithUnitAdornment({ id, adornmentText, onChange }: Props) {
const theme = useTheme();
const [valueField, setValueField] = useState<string>("");
export default function NumberInputWithUnitAdornment({
id,
adornmentText,
onChange,
}: Props) {
const theme = useTheme();
const [valueField, setValueField] = useState<string>("");
return (
<TextField
type="number"
size="small"
placeholder="Введите вручную"
id={id}
value={valueField}
onChange={e => {
let n = parseInt(e.target.value);
return (
<TextField
type="number"
size="small"
placeholder="Введите вручную"
id={id}
value={valueField}
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
const newNumber = parseInt(target.value);
if (!isFinite(n)) n = 0;
if (!isFinite(newNumber) || newNumber < 0) {
onChange(0);
setValueField(String(0));
onChange(n);
setValueField(n.toString());
}}
return;
}
onChange(newNumber);
setValueField(String(newNumber));
}}
sx={{
maxWidth: "200px",
minWidth: "200px",
".MuiInputBase-root": {
pr: 0,
height: "48px",
borderRadius: "8px",
backgroundColor: "#F2F3F7",
fieldset: {
border: "1px solid" + theme.palette.grey2.main,
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.brightPurple.main,
},
input: {
height: "31px",
borderRight: !valueField ? "none" : "1px solid #9A9AAF",
},
"&.Mui-focused input": {
borderRight: "1px solid #9A9AAF",
},
"&:not(.Mui-focused) .MuiInputAdornment-root": {
display: !valueField ? "none" : undefined,
},
"&.Mui-focused ::-webkit-input-placeholder": {
color: "transparent",
},
// Hiding arrows
"input::-webkit-outer-spin-button, input::-webkit-inner-spin-button":
{
WebkitAppearance: "none",
margin: 0,
},
"input[type = number]": {
MozAppearance: "textfield",
},
},
}}
InputProps={{
endAdornment: (
<InputAdornment
position="end"
sx={{
maxWidth: "200px",
minWidth: "200px",
".MuiInputBase-root": {
pr: 0,
height: "48px",
borderRadius: "8px",
backgroundColor: "#F2F3F7",
"fieldset": {
border: "1px solid" + theme.palette.grey2.main,
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.brightPurple.main,
},
"input": {
height: "31px",
borderRight: !valueField ? "none" : "1px solid #9A9AAF",
},
"&.Mui-focused input": {
borderRight: "1px solid #9A9AAF",
},
"&:not(.Mui-focused) .MuiInputAdornment-root": {
display: !valueField ? "none" : undefined,
},
"&.Mui-focused ::-webkit-input-placeholder": {
color: "transparent",
},
// Hiding arrows
"input::-webkit-outer-spin-button, input::-webkit-inner-spin-button": {
"WebkitAppearance": "none",
margin: 0,
},
"input[type = number]": {
"MozAppearance": "textfield",
}
},
userSelect: "none",
pointerEvents: "none",
pl: "2px",
pr: "13px",
}}
InputProps={{
endAdornment: (
<InputAdornment
position="end"
sx={{
userSelect: "none",
pointerEvents: "none",
pl: "2px",
pr: "13px",
}}
>
<Typography variant="body2" color="#4D4D4D">
{adornmentText}
</Typography>
</InputAdornment>
)
}}
/>
);
>
<Typography variant="body2" color="#4D4D4D">
{adornmentText}
</Typography>
</InputAdornment>
),
}}
/>
);
}

@ -1,4 +1,10 @@
import {Box, Button, Typography, useMediaQuery, useTheme} from "@mui/material";
import {
Box,
Button,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
@ -9,73 +15,76 @@ 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"));
console.log("я узкий");
return (
<Box
sx={{
mt: upMd ? "93px" : "55px",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-evenly",
columnGap: "40px",
rowGap: "50px",
backgroundColor: "inherit"
}}>
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
alignItems: "start",
p: "20px",
maxWidth: "360px",
backgroundColor: " #E6E6EB",
borderRadius: "12px",
boxShadow: `
0px 100px 309px rgba(37, 39, 52, 0.24),
0px 41.7776px 129.093px rgba(37, 39, 52, 0.172525),
0px 22.3363px 69.0192px rgba(37, 39, 52, 0.143066),
0px 12.5216px 38.6916px rgba(37, 39, 52, 0.12),
0px 6.6501px 20.5488px rgba(37, 39, 52, 0.0969343),
0px 2.76726px 8.55082px rgba(37, 39, 52, 0.0674749)
`,
color: "black",
height: "520px",
justifyContent: "space-between"
}}
backgroundColor: "inherit",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
alignItems: "start",
p: "20px",
maxWidth: "360px",
backgroundColor: " #E6E6EB",
borderRadius: "12px",
boxShadow: "0 10px 0 -5px #BABBC8",
color: "black",
height: "520px",
justifyContent: "space-between",
}}
>
<img
src={card1Image}
alt=""
style={{
objectFit: "contain",
width: "100%",
display: "block",
pointerEvents: "none",
}}
/>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" mb="20px">
"Текст- это текст, который имеет некоторые характеристики реального
письменного текст"
</Typography>
<Button
sx={{
width: "180px",
paddingTop: "10px",
paddingBottom: "10px",
borderRadius: "8px",
boxShadow: "none",
backgroundColor: "white",
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white",
},
"&:active": {
backgroundColor: "black",
color: "white",
},
}}
variant="contained"
>
<img
src={card1Image}
alt=""
style={{
objectFit: "contain",
width: "100%",
display: "block",
pointerEvents: "none",
}}
/>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" mb="20px">"Текст- это текст, который имеет некоторые характеристики реального письменного текст"</Typography>
<Button
sx={{
width: "180px",
paddingTop: "10px",
paddingBottom: "10px",
borderRadius: "8px",
boxShadow: "none",
backgroundColor: "white",
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white"
},
"&:active": {
backgroundColor: "black",
color: "white"
}
}}
variant="contained">Подробнее</Button>
</Box>
Подробнее
</Button>
</Box>
</Box>
}
);
}

@ -1,4 +1,12 @@
import {Box, Typography, useMediaQuery, useTheme, Button, SxProps, Theme} from "@mui/material";
import {
Box,
Typography,
useMediaQuery,
useTheme,
Button,
SxProps,
Theme,
} from "@mui/material";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import CardWithLink from "@components/CardWithLink";
import UnderlinedLink from "@components/UnderlinedLink";
@ -9,15 +17,17 @@ import card3Image from "@root/assets/landing/card3.png";
import cardImageBig from "@root/assets/landing/card1big.png";
interface Props {
light?: boolean;
sx?: SxProps<Theme>;
light?: boolean;
sx?: SxProps<Theme>;
}
export default function ({light = true, sx}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
export default function ({ light = true, sx }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return <Box sx={{
return (
<Box
sx={{
position: "relative",
display: "flex",
justifyContent: "space-between",
@ -25,68 +35,70 @@ export default function ({light = true, sx}: Props) {
px: "20px",
backgroundColor: light ? "#E6E6EB" : "#434657",
borderRadius: "12px",
boxShadow: `
0px 100px 309px rgba(37, 39, 52, 0.24),
0px 41.7776px 129.093px rgba(37, 39, 52, 0.172525),
0px 22.3363px 69.0192px rgba(37, 39, 52, 0.143066),
0px 12.5216px 38.6916px rgba(37, 39, 52, 0.12),
0px 6.6501px 20.5488px rgba(37, 39, 52, 0.0969343),
0px 2.76726px 8.55082px rgba(37, 39, 52, 0.0674749)
`,
...sx
}}>
<Box sx={{
display: "flex",
flexDirection: "column",
}}>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" maxWidth="552px">Текст- это текст, который имеет некоторые характеристики
реального
письменного текс</Typography>
{
light ?
<Button
sx={{
mt:"28px",
width: "180px",
paddingTop: "10px",
paddingBottom: "10px",
borderRadius: "8px",
boxShadow: "none",
backgroundColor: "white",
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white"
},
"&:active": {
backgroundColor: "black",
color: "white"
}
}}
variant="contained">Подробнее</Button>
:
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={<ArrowForwardIcon sx={{height: "20px", width: "20px"}}/>}
sx={{
mt: "auto",
}}
/>
}
</Box>
<img
src={cardImageBig}
alt=""
style={{
display: "block",
objectFit: "contain",
pointerEvents: "none",
marginBottom: "-40px",
marginTop: "-110px",
maxWidth: "390px",
boxShadow: "0 10px 0 -5px #BABBC8",
...sx,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
}}
>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" maxWidth="552px">
Текст- это текст, который имеет некоторые характеристики реального
письменного текс
</Typography>
{light ? (
<Button
sx={{
mt: "28px",
width: "180px",
paddingTop: "10px",
paddingBottom: "10px",
borderRadius: "8px",
boxShadow: "none",
backgroundColor: "white",
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white",
},
"&:active": {
backgroundColor: "black",
color: "white",
},
}}
/>
variant="contained"
>
Подробнее
</Button>
) : (
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={
<ArrowForwardIcon sx={{ height: "20px", width: "20px" }} />
}
sx={{
mt: "auto",
}}
/>
)}
</Box>
<img
src={cardImageBig}
alt=""
style={{
display: "block",
objectFit: "contain",
pointerEvents: "none",
marginBottom: "-40px",
marginTop: "-110px",
maxWidth: "390px",
}}
/>
</Box>
}
);
}

@ -1,5 +1,11 @@
import { useState } from "react";
import { Box, SvgIcon, Typography, useMediaQuery, useTheme } from "@mui/material";
import {
Box,
SvgIcon,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import ExpandIcon from "@components/icons/ExpandIcon";
import ClearIcon from "@mui/icons-material/Clear";
import { cardShadow } from "@root/utils/themes/shadow";
@ -8,168 +14,183 @@ import { removeTariffFromCart } from "@root/stores/user";
import { enqueueSnackbar } from "notistack";
import { ServiceCartData, getMessageFromFetchError } from "@frontend/kitui";
const name: Record<string, string> = { templategen: "Шаблонизатор", squiz: "Опросник", reducer: "Сокращатель ссылок" };
const name: Record<string, string> = {
templategen: "Шаблонизатор",
squiz: "Опросник",
reducer: "Сокращатель ссылок",
};
interface Props {
serviceData: ServiceCartData;
serviceData: ServiceCartData;
}
export default function CustomWrapper({ serviceData }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
function handleItemDeleteClick(tariffId: string) {
removeTariffFromCart(tariffId).then(() => {
enqueueSnackbar("Тариф удален");
}).catch(error => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
function handleItemDeleteClick(tariffId: string) {
removeTariffFromCart(tariffId)
.then(() => {
enqueueSnackbar("Тариф удален");
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
}
return (
return (
<Box
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<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
sx={{
overflow: "hidden",
borderRadius: "12px",
boxShadow: cardShadow,
}}
onClick={() => setIsExpanded((prev) => !prev)}
sx={{
height: "72px",
px: "20px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
}}
>
<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}`,
},
}}
<Typography
sx={{
fontSize: upMd ? "20px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.text.secondary,
px: 0,
}}
>
{name[serviceData.serviceKey]}
</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,
}}
>
<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,
}}
>
{name[serviceData.serviceKey]}
</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 }}>
{currencyFormatter.format(serviceData.price / 100)}
</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 &&
serviceData.privileges.map(privilege => (
<Box
key={privilege.tariffId + privilege.privilegeId}
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,
}}
>
{privilege.description}
</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,
}}
>
{currencyFormatter.format(privilege.price / 100)}
</Typography>
{upSm ? (
<Typography
component="div"
onClick={() => handleItemDeleteClick(privilege.tariffId)}
sx={{
color: theme.palette.text.secondary,
borderBottom: `1px solid ${theme.palette.text.secondary}`,
width: "max-content",
lineHeight: "19px",
cursor: "pointer",
}}
>
Удалить
</Typography>
) : (
<SvgIcon onClick={() => handleItemDeleteClick(privilege.tariffId)} component={ClearIcon}></SvgIcon>
)}
</Box>
</Box>
))}
{currencyFormatter.format(serviceData.price / 100)}
</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 &&
serviceData.privileges.map((privilege) => (
<Box
key={privilege.tariffId + privilege.privilegeId}
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,
}}
>
{privilege.description}
</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,
}}
>
{currencyFormatter.format(privilege.price / 100)}
</Typography>
{upSm ? (
<Typography
component="div"
onClick={() => handleItemDeleteClick(privilege.tariffId)}
sx={{
color: theme.palette.text.secondary,
borderBottom: `1px solid ${theme.palette.text.secondary}`,
width: "max-content",
lineHeight: "19px",
cursor: "pointer",
}}
>
Удалить
</Typography>
) : (
<SvgIcon
onClick={() => handleItemDeleteClick(privilege.tariffId)}
component={ClearIcon}
sx={{ fill: "#7E2AEA" }}
/>
)}
</Box>
</Box>
))}
</Box>
</Box>
);
}

@ -37,7 +37,7 @@ export default function Faq() {
<Box
sx={{
mt: "20px",
mb: upMd ? "40px" : "20px",
mb: "20px",
display: "flex",
gap: "10px",
}}

@ -36,7 +36,7 @@ export default function History() {
<Box
sx={{
mt: "20px",
mb: upMd ? "40px" : "20px",
mb: "20px",
display: "flex",
gap: "10px",
}}

@ -11,88 +11,94 @@ import SectionWrapper from "@components/SectionWrapper";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
export default function Section3() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const downXs = useMediaQuery(theme.breakpoints.down("sm"));
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const downXs = useMediaQuery(theme.breakpoints.down("sm"));
return (
<SectionWrapper
component="section"
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.lightPurple.main,
}}
sx={{
display: "flex",
pt: upMd ? "170px" : "155px",
pb: upMd ? "100px" : "70px",
// width: "fit-content",
margin: "auto",
flexDirection: upMd ? "row" : "column",
flexWrap: "wrap",
rowGap: upMd ? "58px" : "30px",
columnGap: "13.8%",
justifyContent: "space-between",
}}
return (
<SectionWrapper
component="section"
maxWidth="lg"
outerContainerSx={{
backgroundColor: theme.palette.lightPurple.main,
}}
sx={{
display: "flex",
pt: upMd ? "170px" : "155px",
pb: upMd ? "100px" : "70px",
// width: "fit-content",
margin: "auto",
flexDirection: upMd ? "row" : "column",
flexWrap: "wrap",
rowGap: upMd ? "58px" : "30px",
columnGap: "13.8%",
justifyContent: "space-between",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
maxWidth: "500px",
width: upMd ? "43.1%" : undefined,
mb: "10px",
}}
>
<Typography
variant="h4"
sx={{
mb: upMd ? "70px" : "30px",
}}
>
<Box sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
maxWidth: "500px",
width: upMd ? "43.1%" : undefined,
mb: "10px",
}}>
<Typography
variant="h4"
sx={{
mb: upMd ? "70px" : "30px",
}}
>
Узнайте, как наши сервисы решают ваши задачи
</Typography>
<Box
sx={{
mb: upMd ? "20px" : "30px",
}}
>
<Typography>Покажут эффективность рекламы</Typography>
<Typography>Соберут все обращения клиентов</Typography>
<Typography>Повысят конверсию сайта</Typography>
</Box>
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px", display: "inline" }} />}
/>
</Box>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Общий кабинет"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="column"
small={downXs}
backgroundImage={downXs ? cardPagesBackground4 : cardPagesBackground1}
sx={{ alignSelf: "center" }}
Узнайте, как наши сервисы решают ваши задачи
</Typography>
<Box
sx={{
mb: upMd ? "20px" : "30px",
}}
>
<Typography>Покажут эффективность рекламы</Typography>
<Typography>Соберут все обращения клиентов</Typography>
<Typography>Повысят конверсию сайта</Typography>
</Box>
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={
<ArrowForwardIcon
sx={{ height: "20px", width: "20px", display: "inline" }}
/>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Общий кабинет"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="row"
small={downXs}
backgroundImage={downXs ? cardPagesBackground5 : cardPagesBackground2}
sx={{ alignSelf: "center" }}
/>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Гибкие тарифы"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="column"
small={downXs}
backgroundImage={downXs ? cardPagesBackground6 : cardPagesBackground3}
sx={{ mt: upMd ? "82px" : undefined, alignSelf: "center" }}
/>
</SectionWrapper>
);
}
}
/>
</Box>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Общий кабинет"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="column"
small={downXs}
backgroundImage={downXs ? cardPagesBackground4 : cardPagesBackground1}
sx={{ alignSelf: "center" }}
/>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Общий кабинет"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="row"
small={downXs}
backgroundImage={downXs ? cardPagesBackground5 : cardPagesBackground2}
sx={{ alignSelf: "center", mt: upMd ? "-82px" : null }}
/>
<PromoCard
width={upMd ? "43.1%" : "100%"}
headerText="Гибкие тарифы"
text="Текст-заполнитель — это текст, который имеет некоторые характеристики реального письменного"
textOrientation="column"
small={downXs}
backgroundImage={downXs ? cardPagesBackground6 : cardPagesBackground3}
sx={{ mt: upMd ? "82px" : undefined, alignSelf: "center" }}
/>
</SectionWrapper>
);
}

@ -1,120 +1,144 @@
import { Box, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
import {
Box,
Divider,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import CustomButton from "../../components/CustomButton";
import { Privilege } from "@root/model/privilege";
import TariffPrivilegeSlider from "./TariffItem";
import { createAndSendTariff, useCustomTariffsStore } from "@root/stores/customTariffs";
import {
createAndSendTariff,
useCustomTariffsStore,
} from "@root/stores/customTariffs";
import { cardShadow } from "@root/utils/themes/shadow";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import { devlog, getMessageFromFetchError } from "@frontend/kitui";
import { enqueueSnackbar } from "notistack";
interface Props {
serviceKey: string;
privileges: Privilege[];
serviceKey: string;
privileges: Privilege[];
}
export default function CustomTariffCard({ serviceKey, privileges }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const summaryPriceBeforeDiscounts = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap);
const summaryPriceAfterDiscounts = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const summaryPriceBeforeDiscounts = useCustomTariffsStore(
(state) => state.summaryPriceBeforeDiscountsMap
);
const summaryPriceAfterDiscounts = useCustomTariffsStore(
(state) => state.summaryPriceAfterDiscountsMap
);
const priceBeforeDiscounts = summaryPriceBeforeDiscounts[serviceKey] ?? 0;
const priceAfterDiscounts = summaryPriceAfterDiscounts[serviceKey] ?? 0;
const priceBeforeDiscounts = summaryPriceBeforeDiscounts[serviceKey] ?? 0;
const priceAfterDiscounts = summaryPriceAfterDiscounts[serviceKey] ?? 0;
async function handleConfirmClick() {
createAndSendTariff(serviceKey).then(result => {
devlog(result);
enqueueSnackbar("Тариф создан");
}).catch(error => {
const message = getMessageFromFetchError(error, "Не удалось создать тариф");
if (message) enqueueSnackbar(message);
});
}
async function handleConfirmClick() {
createAndSendTariff(serviceKey)
.then((result) => {
devlog(result);
enqueueSnackbar("Тариф создан");
})
.catch((error) => {
const message = getMessageFromFetchError(
error,
"Не удалось создать тариф"
);
if (message) enqueueSnackbar(message);
});
}
return (
<Box sx={{
backgroundColor: "white",
width: "100%",
return (
<Box
sx={{
backgroundColor: "white",
width: "100%",
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: cardShadow,
}}
>
<Box
sx={{
p: "20px",
pr: upMd ? "35px" : undefined,
display: "flex",
flexBasis: 0,
flexGrow: 2.37,
flexWrap: "wrap",
flexDirection: "column",
gap: "25px",
}}
>
{privileges.map((privilege) => (
<TariffPrivilegeSlider key={privilege._id} privilege={privilege} />
))}
</Box>
{!upMd && (
<Divider
sx={{ mx: "20px", my: "10px", borderColor: theme.palette.grey2.main }}
/>
)}
<Box
sx={{
display: "flex",
flexBasis: 0,
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.grey3.main,
p: "20px",
pl: upMd ? "33px" : undefined,
borderLeft: upMd
? `1px solid ${theme.palette.grey2.main}`
: undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: cardShadow,
}}>
<Box sx={{
p: "20px",
pr: upMd ? "35px" : undefined,
display: "flex",
flexBasis: 0,
flexGrow: 2.37,
flexWrap: "wrap",
flexDirection: "column",
gap: "25px",
}}>
{privileges.map(privilege =>
<TariffPrivilegeSlider
key={privilege._id}
privilege={privilege}
/>
)}
</Box>
{!upMd && <Divider sx={{ mx: "20px", my: "10px", borderColor: theme.palette.grey2.main }} />}
<Box sx={{
display: "flex",
flexBasis: 0,
flexGrow: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.grey3.main,
p: "20px",
pl: upMd ? "33px" : undefined,
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}>
<Box sx={{
display: "flex",
justifyContent: "space-between",
gap: "15%",
mb: "auto",
width: "100%",
}}>
<Typography>Чем больше пакеты, тем дешевле подписки и опции </Typography>
<Box sx={{
px: "6.7px",
height: "36px",
color: "white",
backgroundColor: theme.palette.orange.main,
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "8px",
}}>
{"-60%"}
</Box>
</Box>
<Typography mb="20px" mt="18px">
Сумма с учетом скидки
</Typography>
<Box sx={{
display: "flex",
alignItems: "center",
gap: "20px",
mb: "30px",
}}>
<Typography variant="price">{currencyFormatter.format(priceAfterDiscounts / 100)}</Typography>
<Typography variant="oldPrice" pt="3px">{currencyFormatter.format(priceBeforeDiscounts / 100)}</Typography>
</Box>
<CustomButton
variant="contained"
onClick={handleConfirmClick}
sx={{
backgroundColor: theme.palette.brightPurple.main,
}}
>
Выбрать
</CustomButton>
</Box>
justifyContent: "space-between",
gap: "15%",
mb: "auto",
width: "100%",
}}
>
<Typography>
Чем больше пакеты, тем дешевле подписки и опции{" "}
</Typography>
</Box>
);
<Typography mb="20px" mt="18px">
Сумма с учетом скидки
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
mb: "30px",
}}
>
<Typography variant="price">
{currencyFormatter.format(priceAfterDiscounts / 100)}
</Typography>
<Typography variant="oldPrice" pt="3px">
{currencyFormatter.format(priceBeforeDiscounts / 100)}
</Typography>
</Box>
<CustomButton
variant="contained"
onClick={handleConfirmClick}
sx={{
backgroundColor: theme.palette.brightPurple.main,
}}
>
Выбрать
</CustomButton>
</Box>
</Box>
);
}

@ -9,82 +9,96 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import TotalPrice from "@root/components/TotalPrice";
import { serviceNameByKey } from "@root/utils/serviceKeys";
export default function TariffConstructor() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const customTariffs = useCustomTariffsStore(state => state.customTariffsMap);
const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap);
const summaryPriceAfterDiscountsMap = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const customTariffs = useCustomTariffsStore(
(state) => state.customTariffsMap
);
const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(
(state) => state.summaryPriceBeforeDiscountsMap
);
const summaryPriceAfterDiscountsMap = useCustomTariffsStore(
(state) => state.summaryPriceAfterDiscountsMap
);
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0);
const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce(
(a, e) => a + e,
0
);
const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce(
(a, e) => a + e,
0
);
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: upMd ? "25px" : "20px",
mb: upMd ? "93px" : "48px",
}}
>
{upMd && <ComplexNavText text1="Все тарифы — " text2="Кастомный тариф" />}
<Box
sx={{
mt: "20px",
display: "flex",
flexDirection: "column",
gap: "80px",
}}
>
{Object.entries(customTariffs).map(
([serviceKey, privileges], index) => (
<Box key={serviceKey}>
<Box
sx={{
mt: "20px",
display: "flex",
flexDirection: "column",
gap: "80px",
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{Object.entries(customTariffs).map(([serviceKey, privileges], index) => (
<Box key={serviceKey}>
<Box
sx={{
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && index === 0 && (
<IconButton
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}
>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={serviceNameByKey[serviceKey]}
/>
</Box>
<CustomTariffCard serviceKey={serviceKey} privileges={privileges} />
</Box>
))}
</Box>
{upMd && (
<Link
to="/tariffconstructor/savedtariffs"
style={{
display: "block",
marginTop: "60px",
textUnderlinePosition: "under",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
>
{!upMd && index === 0 && (
<IconButton
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
}}
>
Ваши сохраненные тарифы
</Link>
)}
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}
/>
</SectionWrapper>
);
>
<ArrowBackIcon />
</IconButton>
)}
<ComplexHeader
text1="Кастомный тариф "
text2={serviceNameByKey[serviceKey]}
/>
</Box>
<CustomTariffCard
serviceKey={serviceKey}
privileges={privileges}
/>
</Box>
)
)}
</Box>
<Link
to="/tariffconstructor/savedtariffs"
style={{
display: "block",
marginTop: "60px",
textUnderlinePosition: "under",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
>
Ваши сохраненные тарифы
</Link>
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}
/>
</SectionWrapper>
);
}

@ -1,135 +1,169 @@
import { useThrottle } from "@frontend/kitui";
import { Box, SliderProps, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomSlider from "@root/components/CustomSlider";
import {
Box,
SliderProps,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { CustomSlider } from "@root/components/CustomSlider";
import NumberInputWithUnitAdornment from "@root/components/NumberInputWithUnitAdornment";
import CalendarIcon from "@root/components/icons/CalendarIcon";
import PieChartIcon from "@root/components/icons/PieChartIcon";
import { Privilege, PrivilegeValueType } from "@root/model/privilege";
import { useCartStore } from "@root/stores/cart";
import { setCustomTariffsUserValue, useCustomTariffsStore } from "@root/stores/customTariffs";
import {
setCustomTariffsUserValue,
useCustomTariffsStore,
} from "@root/stores/customTariffs";
import { useDiscountStore } from "@root/stores/discounts";
import { useUserStore } from "@root/stores/user";
import { getDeclension } from "@root/utils/declension";
import { useEffect, useState } from "react";
const sliderSettingsByType: Record<PrivilegeValueType, Partial<SliderProps>> = {
"день": {
max: 365,
step: 1,
},
"шаблон": {
max: 1000000,
step: 1000,
},
"МБ": {
max: 1000000,
step: 1000,
},
день: { max: 365 },
шаблон: { max: 1000000 },
МБ: { max: 1000000 },
};
interface Props {
privilege: Privilege;
privilege: Privilege;
}
export default function TariffPrivilegeSlider({ privilege }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const userValue = useCustomTariffsStore(state => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? 0;
const discounts = useDiscountStore(state => state.discounts);
const currentCartTotal = useCartStore(state => state.cart.priceAfterDiscounts);
const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0;
const [value, setValue] = useState<number>(userValue);
const throttledValue = useThrottle(value, 200);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const userValue =
useCustomTariffsStore(
(state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]
) ?? 0;
const discounts = useDiscountStore((state) => state.discounts);
const currentCartTotal = useCartStore(
(state) => state.cart.priceAfterDiscounts
);
const purchasesAmount =
useUserStore((state) => state.userAccount?.wallet.purchasesAmount) ?? 0;
const [value, setValue] = useState<number>(userValue);
const throttledValue = useThrottle(value, 200);
useEffect(function setStoreValue() {
setCustomTariffsUserValue(
privilege.serviceKey,
privilege._id,
throttledValue,
discounts,
currentCartTotal,
purchasesAmount
);
}, [currentCartTotal, discounts, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue]);
useEffect(
function setStoreValue() {
setCustomTariffsUserValue(
privilege.serviceKey,
privilege._id,
throttledValue,
discounts,
currentCartTotal,
purchasesAmount
);
},
[
currentCartTotal,
discounts,
purchasesAmount,
privilege._id,
privilege.serviceKey,
throttledValue,
]
);
function handleSliderChange(event: Event, value: number | number[]) {
if (Array.isArray(value)) throw new Error("Slider uses multiple values instead of one");
function handleSliderChange(value: number | number[]) {
if (Array.isArray(value))
throw new Error("Slider uses multiple values instead of one");
setValue(value);
}
setValue(value);
}
const quantityText = `${value} ${getDeclension(value, privilege.value)}`;
const quantityText = `${value} ${getDeclension(value, privilege.value)}`;
const quantityElement = (
<Box sx={{
const quantityElement = (
<Box
sx={{
display: "flex",
gap: "15px",
alignItems: "center",
justifyContent: upMd ? "end" : undefined,
flexWrap: "wrap",
mt: upMd ? undefined : "12px",
}}
>
<Typography
variant="p1"
color={theme.palette.brightPurple.main}
textAlign="end"
>
{quantityText}
</Typography>
<Box
sx={{
display: "flex",
gap: "15px",
alignItems: "center",
flexWrap: "wrap",
}}
>
<Typography sx={{ fontSize: "16px", lineHeight: "19px", mt: "1px" }}>
или
</Typography>
<NumberInputWithUnitAdornment
id={"privilege_input_" + privilege._id}
adornmentText={getDeclension(0, privilege.value)}
onChange={(value) => setValue(value)}
/>
</Box>
</Box>
);
const icon =
privilege.type === "day" ? (
<CalendarIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />
) : (
<PieChartIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />
);
return (
<Box>
<Typography sx={{ color: theme.palette.grey3.main, mb: "auto" }}>
{privilege.description}
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
mt: "40px",
}}
>
<Box
sx={{
display: "flex",
gap: "15px",
// flexWrap: "wrap",
alignItems: "center",
justifyContent: upMd ? "end" : undefined,
flexWrap: "wrap",
mt: upMd ? undefined : "12px",
}}>
<Typography variant="p1" color={theme.palette.brightPurple.main} textAlign="end">
{quantityText}
</Typography>
<Box sx={{
display: "flex",
gap: "15px",
alignItems: "center",
flexWrap: "wrap",
}}>
<Typography sx={{ fontSize: "16px", lineHeight: "19px", mt: "1px" }}>или</Typography>
<NumberInputWithUnitAdornment
id={"privilege_input_" + privilege._id}
adornmentText={getDeclension(0, privilege.value)}
onChange={value => setValue(value)}
/>
</Box>
mb: "8px",
justifyContent: "space-between",
gap: "10px",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "22px",
}}
>
{icon}
<Typography variant="h5">{privilege.name}</Typography>
</Box>
{upMd && quantityElement}
</Box>
);
const icon = privilege.type === "day"
? <CalendarIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />
: <PieChartIcon color={theme.palette.orange.main} bgcolor="#FEDFD0" />;
return (
<Box>
<Typography sx={{ color: theme.palette.grey3.main, mb: "auto" }}>
{privilege.description}
</Typography>
<Box sx={{
display: "flex",
flexDirection: "column",
mt: "40px",
}}>
<Box sx={{
display: "flex",
// flexWrap: "wrap",
alignItems: "center",
mb: "8px",
justifyContent: "space-between",
gap: "10px",
}}>
<Box sx={{
display: "flex",
alignItems: "center",
gap: "22px",
}}>
{icon}
<Typography variant="h5">{privilege.name}</Typography>
</Box>
{upMd && quantityElement}
</Box>
<CustomSlider
value={value}
defaultValue={0}
min={0}
onChange={handleSliderChange}
{...sliderSettingsByType[privilege.value]}
/>
{!upMd && quantityElement}
</Box>
</Box>
);
<CustomSlider
value={value}
min={0}
max={sliderSettingsByType[privilege.value].max || 100}
onChange={handleSliderChange}
/>
{!upMd && quantityElement}
</Box>
</Box>
);
}

@ -75,7 +75,7 @@ export default function TariffCard({ icon, headerText, text, sx, price, buttonPr
sx={{
color: theme.palette.brightPurple.main,
borderColor: theme.palette.brightPurple.main,
mt: "auto",
mt: "30px",
...buttonProps.sx,
}}
>

@ -89,7 +89,7 @@ export default function Tariffs() {
</Typography>
{upMd ?
<WideTemplCard sx={{ marginTop: "76px" }} />
<WideTemplCard sx={{ marginTop: "60px" }} />
:
<TemplCardPhoneLight />}
{/*<Box sx={{*/}

@ -23,17 +23,20 @@ import { useCartStore } from "@root/stores/cart";
const subPages = ["Шаблонизатор", "Опросник", "Сокращатель ссылок"];
export default function TariffPage() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const location = useLocation();
const tariffs = useTariffStore(state => state.tariffs);
const [selectedItem, setSelectedItem] = useState<number>(0);
const discounts = useDiscountStore(state => state.discounts);
const customTariffs = useCustomTariffsStore(state => state.customTariffsMap);
const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0;
const cart = useCartStore(state => state.cart);
const unit: string = String(location.pathname).slice(9);
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const location = useLocation();
const tariffs = useTariffStore((state) => state.tariffs);
const [selectedItem, setSelectedItem] = useState<number>(0);
const discounts = useDiscountStore((state) => state.discounts);
const customTariffs = useCustomTariffsStore(
(state) => state.customTariffsMap
);
const purchasesAmount =
useUserStore((state) => state.userAccount?.wallet.purchasesAmount) ?? 0;
const cart = useCartStore((state) => state.cart);
const unit: string = String(location.pathname).slice(9);
const StepperText: Record<string, string> = {
volume: "Тарифы на объём",
@ -155,7 +158,7 @@ export default function TariffPage() {
>
{tariffElements}
</Box>
<Typography variant="h4" sx={{ mt: "50px", mb: "40px" }}>
<Typography variant="h4" sx={{ mt: "40px" }}>
Ранее вы покупали
</Typography>
<Slider items={tariffElements} />

@ -1,3 +1,7 @@
.slider {
margin-top: 40px;
}
.slider .slick-slide {
width: 100%;
max-width: 360px;

@ -59,7 +59,7 @@ export const Slider = ({ items }: SliderProps) => {
display: "grid",
gap: "40px",
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 360px))",
margin: isTablet ? "auto" : null,
margin: isTablet ? "40px auto" : null,
}}
>
{items}