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,7 +1,47 @@
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 }) => ({
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": {
@ -23,4 +63,7 @@ export default styled(Slider)<SliderProps>(({ theme }) => ({
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,6 +1,7 @@
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;
@ -8,7 +9,11 @@ interface Props {
onChange: (value: number) => void;
}
export default function NumberInputWithUnitAdornment({ id, adornmentText, onChange }: Props) {
export default function NumberInputWithUnitAdornment({
id,
adornmentText,
onChange,
}: Props) {
const theme = useTheme();
const [valueField, setValueField] = useState<string>("");
@ -19,13 +24,18 @@ export default function NumberInputWithUnitAdornment({ id, adornmentText, onChan
placeholder="Введите вручную"
id={id}
value={valueField}
onChange={e => {
let n = parseInt(e.target.value);
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",
@ -35,13 +45,13 @@ export default function NumberInputWithUnitAdornment({ id, adornmentText, onChan
height: "48px",
borderRadius: "8px",
backgroundColor: "#F2F3F7",
"fieldset": {
fieldset: {
border: "1px solid" + theme.palette.grey2.main,
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.brightPurple.main,
},
"input": {
input: {
height: "31px",
borderRight: !valueField ? "none" : "1px solid #9A9AAF",
},
@ -56,13 +66,14 @@ export default function NumberInputWithUnitAdornment({ id, adornmentText, onChan
},
// Hiding arrows
"input::-webkit-outer-spin-button, input::-webkit-inner-spin-button": {
"WebkitAppearance": "none",
"input::-webkit-outer-spin-button, input::-webkit-inner-spin-button":
{
WebkitAppearance: "none",
margin: 0,
},
"input[type = number]": {
"MozAppearance": "textfield",
}
MozAppearance: "textfield",
},
},
}}
InputProps={{
@ -80,7 +91,7 @@ export default function NumberInputWithUnitAdornment({ id, adornmentText, onChan
{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";
@ -10,16 +16,20 @@ import cardImageBig from "@root/assets/landing/card1big.png";
export default function () {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));console.log("я узкий")
return <Box sx={{
const upMd = useMediaQuery(theme.breakpoints.up("md"));
console.log("я узкий");
return (
<Box
sx={{
mt: upMd ? "93px" : "55px",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-evenly",
columnGap: "40px",
rowGap: "50px",
backgroundColor: "inherit"
}}>
backgroundColor: "inherit",
}}
>
<Box
sx={{
display: "flex",
@ -30,17 +40,10 @@ export default function () {
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)
`,
boxShadow: "0 10px 0 -5px #BABBC8",
color: "black",
height: "520px",
justifyContent: "space-between"
justifyContent: "space-between",
}}
>
<img
@ -54,7 +57,10 @@ export default function () {
}}
/>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" mb="20px">"Текст- это текст, который имеет некоторые характеристики реального письменного текст"</Typography>
<Typography mt="20px" mb="20px">
"Текст- это текст, который имеет некоторые характеристики реального
письменного текст"
</Typography>
<Button
sx={{
@ -67,15 +73,18 @@ export default function () {
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white"
color: "white",
},
"&:active": {
backgroundColor: "black",
color: "white"
}
color: "white",
},
}}
variant="contained">Подробнее</Button>
variant="contained"
>
Подробнее
</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";
@ -17,7 +25,9 @@ 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,26 +35,22 @@ 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={{
boxShadow: "0 10px 0 -5px #BABBC8",
...sx,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
}}>
}}
>
<Typography variant="h5">Шаблонизатор</Typography>
<Typography mt="20px" maxWidth="552px">Текст- это текст, который имеет некоторые характеристики
реального
письменного текс</Typography>
{
light ?
<Typography mt="20px" maxWidth="552px">
Текст- это текст, который имеет некоторые характеристики реального
письменного текс
</Typography>
{light ? (
<Button
sx={{
mt: "28px",
@ -57,24 +63,29 @@ mt:"28px",
color: "black",
"&:hover": {
backgroundColor: "#581CA7",
color: "white"
color: "white",
},
"&:active": {
backgroundColor: "black",
color: "white"
}
color: "white",
},
}}
variant="contained">Подробнее</Button>
:
variant="contained"
>
Подробнее
</Button>
) : (
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={<ArrowForwardIcon sx={{height: "20px", width: "20px"}}/>}
endIcon={
<ArrowForwardIcon sx={{ height: "20px", width: "20px" }} />
}
sx={{
mt: "auto",
}}
/>
}
)}
</Box>
<img
src={cardImageBig}
@ -89,4 +100,5 @@ mt:"28px",
}}
/>
</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,8 +14,11 @@ 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;
@ -22,9 +31,11 @@ export default function CustomWrapper({ serviceData }: Props) {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
function handleItemDeleteClick(tariffId: string) {
removeTariffFromCart(tariffId).then(() => {
removeTariffFromCart(tariffId)
.then(() => {
enqueueSnackbar("Тариф удален");
}).catch(error => {
})
.catch((error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
});
@ -88,7 +99,13 @@ export default function CustomWrapper({ serviceData }: Props) {
gap: upSm ? "111px" : "17px",
}}
>
<Typography sx={{ color: theme.palette.grey3.main, fontSize: upSm ? "20px" : "16px", fontWeight: 500 }}>
<Typography
sx={{
color: theme.palette.grey3.main,
fontSize: upSm ? "20px" : "16px",
fontWeight: 500,
}}
>
{currencyFormatter.format(serviceData.price / 100)}
</Typography>
<Box
@ -106,7 +123,7 @@ export default function CustomWrapper({ serviceData }: Props) {
</Box>
</Box>
{isExpanded &&
serviceData.privileges.map(privilege => (
serviceData.privileges.map((privilege) => (
<Box
key={privilege.tariffId + privilege.privilegeId}
sx={{
@ -164,7 +181,11 @@ export default function CustomWrapper({ serviceData }: Props) {
Удалить
</Typography>
) : (
<SvgIcon onClick={() => handleItemDeleteClick(privilege.tariffId)} component={ClearIcon}></SvgIcon>
<SvgIcon
onClick={() => handleItemDeleteClick(privilege.tariffId)}
component={ClearIcon}
sx={{ fill: "#7E2AEA" }}
/>
)}
</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",
}}

@ -35,14 +35,16 @@ export default function Section3() {
justifyContent: "space-between",
}}
>
<Box sx={{
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
maxWidth: "500px",
width: upMd ? "43.1%" : undefined,
mb: "10px",
}}>
}}
>
<Typography
variant="h4"
sx={{
@ -63,7 +65,11 @@ export default function Section3() {
<UnderlinedLink
linkHref="#"
text="Подробнее"
endIcon={<ArrowForwardIcon sx={{ height: "20px", width: "20px", display: "inline" }} />}
endIcon={
<ArrowForwardIcon
sx={{ height: "20px", width: "20px", display: "inline" }}
/>
}
/>
</Box>
<PromoCard
@ -82,7 +88,7 @@ export default function Section3() {
textOrientation="row"
small={downXs}
backgroundImage={downXs ? cardPagesBackground5 : cardPagesBackground2}
sx={{ alignSelf: "center" }}
sx={{ alignSelf: "center", mt: upMd ? "-82px" : null }}
/>
<PromoCard
width={upMd ? "43.1%" : "100%"}

@ -1,14 +1,22 @@
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[];
@ -17,32 +25,44 @@ interface Props {
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 summaryPriceBeforeDiscounts = useCustomTariffsStore(
(state) => state.summaryPriceBeforeDiscountsMap
);
const summaryPriceAfterDiscounts = useCustomTariffsStore(
(state) => state.summaryPriceAfterDiscountsMap
);
const priceBeforeDiscounts = summaryPriceBeforeDiscounts[serviceKey] ?? 0;
const priceAfterDiscounts = summaryPriceAfterDiscounts[serviceKey] ?? 0;
async function handleConfirmClick() {
createAndSendTariff(serviceKey).then(result => {
createAndSendTariff(serviceKey)
.then((result) => {
devlog(result);
enqueueSnackbar("Тариф создан");
}).catch(error => {
const message = getMessageFromFetchError(error, "Не удалось создать тариф");
})
.catch((error) => {
const message = getMessageFromFetchError(
error,
"Не удалось создать тариф"
);
if (message) enqueueSnackbar(message);
});
}
return (
<Box sx={{
<Box
sx={{
backgroundColor: "white",
width: "100%",
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: cardShadow,
}}>
<Box sx={{
}}
>
<Box
sx={{
p: "20px",
pr: upMd ? "35px" : undefined,
display: "flex",
@ -51,16 +71,19 @@ export default function CustomTariffCard({ serviceKey, privileges }: Props) {
flexWrap: "wrap",
flexDirection: "column",
gap: "25px",
}}>
{privileges.map(privilege =>
<TariffPrivilegeSlider
key={privilege._id}
privilege={privilege}
}}
>
{privileges.map((privilege) => (
<TariffPrivilegeSlider key={privilege._id} privilege={privilege} />
))}
</Box>
{!upMd && (
<Divider
sx={{ mx: "20px", my: "10px", borderColor: theme.palette.grey2.main }}
/>
)}
</Box>
{!upMd && <Divider sx={{ mx: "20px", my: "10px", borderColor: theme.palette.grey2.main }} />}
<Box sx={{
<Box
sx={{
display: "flex",
flexBasis: 0,
flexGrow: 1,
@ -70,40 +93,41 @@ export default function CustomTariffCard({ serviceKey, privileges }: Props) {
color: theme.palette.grey3.main,
p: "20px",
pl: upMd ? "33px" : undefined,
borderLeft: upMd ? `1px solid ${theme.palette.grey2.main}` : undefined,
}}>
<Box sx={{
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>
}}
>
<Typography>
Чем больше пакеты, тем дешевле подписки и опции{" "}
</Typography>
</Box>
<Typography mb="20px" mt="18px">
Сумма с учетом скидки
</Typography>
<Box sx={{
<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>
}}
>
<Typography variant="price">
{currencyFormatter.format(priceAfterDiscounts / 100)}
</Typography>
<Typography variant="oldPrice" pt="3px">
{currencyFormatter.format(priceBeforeDiscounts / 100)}
</Typography>
</Box>
<CustomButton
variant="contained"

@ -9,16 +9,27 @@ 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 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
@ -37,7 +48,8 @@ export default function TariffConstructor() {
gap: "80px",
}}
>
{Object.entries(customTariffs).map(([serviceKey, privileges], index) => (
{Object.entries(customTariffs).map(
([serviceKey, privileges], index) => (
<Box key={serviceKey}>
<Box
sx={{
@ -63,11 +75,14 @@ export default function TariffConstructor() {
text2={serviceNameByKey[serviceKey]}
/>
</Box>
<CustomTariffCard serviceKey={serviceKey} privileges={privileges} />
<CustomTariffCard
serviceKey={serviceKey}
privileges={privileges}
/>
</Box>
))}
)
)}
</Box>
{upMd && (
<Link
to="/tariffconstructor/savedtariffs"
style={{
@ -80,7 +95,6 @@ export default function TariffConstructor() {
>
Ваши сохраненные тарифы
</Link>
)}
<TotalPrice
priceBeforeDiscounts={basePrice}
priceAfterDiscounts={discountedPrice}

@ -1,31 +1,30 @@
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 {
@ -35,14 +34,21 @@ interface Props {
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 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() {
useEffect(
function setStoreValue() {
setCustomTariffsUserValue(
privilege.serviceKey,
privilege._id,
@ -51,10 +57,20 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
currentCartTotal,
purchasesAmount
);
}, [currentCartTotal, discounts, purchasesAmount, privilege._id, privilege.serviceKey, throttledValue]);
},
[
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);
}
@ -62,60 +78,79 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
const quantityText = `${value} ${getDeclension(value, privilege.value)}`;
const quantityElement = (
<Box sx={{
<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">
}}
>
<Typography
variant="p1"
color={theme.palette.brightPurple.main}
textAlign="end"
>
{quantityText}
</Typography>
<Box sx={{
<Box
sx={{
display: "flex",
gap: "15px",
alignItems: "center",
flexWrap: "wrap",
}}>
<Typography sx={{ fontSize: "16px", lineHeight: "19px", mt: "1px" }}>или</Typography>
}}
>
<Typography sx={{ fontSize: "16px", lineHeight: "19px", mt: "1px" }}>
или
</Typography>
<NumberInputWithUnitAdornment
id={"privilege_input_" + privilege._id}
adornmentText={getDeclension(0, privilege.value)}
onChange={value => setValue(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" />;
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={{
<Box
sx={{
display: "flex",
flexDirection: "column",
mt: "40px",
}}>
<Box sx={{
}}
>
<Box
sx={{
display: "flex",
// flexWrap: "wrap",
alignItems: "center",
mb: "8px",
justifyContent: "space-between",
gap: "10px",
}}>
<Box sx={{
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "22px",
}}>
}}
>
{icon}
<Typography variant="h5">{privilege.name}</Typography>
</Box>
@ -123,10 +158,9 @@ export default function TariffPrivilegeSlider({ privilege }: Props) {
</Box>
<CustomSlider
value={value}
defaultValue={0}
min={0}
max={sliderSettingsByType[privilege.value].max || 100}
onChange={handleSliderChange}
{...sliderSettingsByType[privilege.value]}
/>
{!upMd && quantityElement}
</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={{*/}

@ -27,12 +27,15 @@ export default function TariffPage() {
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const location = useLocation();
const tariffs = useTariffStore(state => state.tariffs);
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 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> = {
@ -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}