tariffs save fix

This commit is contained in:
ArtChaos189 2023-08-21 18:58:48 +03:00
parent 63ae005bb8
commit 52f1b30c5f
8 changed files with 377 additions and 261 deletions

@ -0,0 +1,79 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { useState } from "react";
import ExpandIcon from "./icons/ExpandIcon";
import type { ReactNode } from "react";
interface Props {
header: ReactNode;
divide?: boolean;
privilege: ReactNode;
}
export default function CustomSaveAccordion({ header, divide = false, privilege }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upXs = useMediaQuery(theme.breakpoints.up("xs"));
const [isExpanded, setIsExpanded] = useState<boolean>(false);
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={{
minHeight: "72px",
px: "20px",
display: "flex",
alignItems: "stretch",
justifyContent: "space-between",
cursor: "pointer",
userSelect: "none",
rowGap: "10px",
flexDirection: upXs ? undefined : "column",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
width: "100%",
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: theme.palette.grey3.main,
px: 0,
}}
>
{header}
</Box>
<Box
sx={{
pl: "20px",
width: "52px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderLeft: divide ? "1px solid #000000" : "none",
}}
>
<ExpandIcon isExpanded={isExpanded} />
</Box>
</Box>
{isExpanded && privilege}
</Box>
);
}

@ -10,107 +10,116 @@ import { useNavigate } from "react-router-dom";
import { useState } from "react";
interface Props {
priceBeforeDiscounts: number;
priceAfterDiscounts: number;
priceBeforeDiscounts: number;
priceAfterDiscounts: number;
}
export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0);
const navigate = useNavigate();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0);
const navigate = useNavigate();
function handlePayClick() {
payCart().then(result => {
setUserAccount(result);
}).catch(error => {
if (isAxiosError(error) && error.response?.status === 402) {
const notEnoughMoneyAmount = parseInt((error.response.data.message as string).replace("insufficient funds: ", ""));
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
} else {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
}
});
}
function handlePayClick() {
payCart()
.then((result) => {
setUserAccount(result);
})
.catch((error) => {
if (isAxiosError(error) && error.response?.status === 500) {
enqueueSnackbar("В корзине нет товаров");
}
if (isAxiosError(error) && error.response?.status === 402) {
const notEnoughMoneyAmount = parseInt(
(error.response.data.message as string).replace("insufficient funds: ", "")
);
setNotEnoughMoneyAmount(notEnoughMoneyAmount);
}
if (!isAxiosError(error)) {
enqueueSnackbar(error.response.data.message);
}
});
}
function handleReplenishWallet() {
navigate("/payment", { state: { notEnoughMoneyAmount } });
}
function handleReplenishWallet() {
navigate("/payment", { state: { notEnoughMoneyAmount } });
}
return (
<Box sx={{
return (
<Box
sx={{
display: "flex",
flexDirection: upMd ? "row" : "column",
mt: upMd ? "50px" : "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 ? "row" : "column",
mt: upMd ? "50px" : "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",
mb: "15px",
gap: "15px",
}}>
<Typography
variant="oldPrice"
sx={{ order: upMd ? 1 : 2 }}
>
{currencyFormatter.format(priceBeforeDiscounts / 100)}
</Typography>
<Typography
variant="price"
sx={{
fontWeight: 500,
fontSize: "26px",
lineHeight: "31px",
order: upMd ? 2 : 1,
}}
>
{currencyFormatter.format(priceAfterDiscounts / 100)}
</Typography>
</Box>
{notEnoughMoneyAmount > 0 &&
<Alert
severity="error"
variant="filled"
>
Нехватает {currencyFormatter.format(notEnoughMoneyAmount / 100)}
</Alert>
}
<CustomButton
variant="contained"
onClick={notEnoughMoneyAmount === 0 ? handlePayClick : handleReplenishWallet}
sx={{
mt: "10px",
backgroundColor: theme.palette.brightPurple.main,
}}
>
{notEnoughMoneyAmount === 0 ? "Оплатить" : "Пополнить"}
</CustomButton>
</Box>
flexDirection: upMd ? "column" : "row",
alignItems: upMd ? "start" : "center",
mt: upMd ? "10px" : "30px",
mb: "15px",
gap: "15px",
}}
>
<Typography variant="oldPrice" sx={{ order: upMd ? 1 : 2 }}>
{currencyFormatter.format(priceBeforeDiscounts / 100)}
</Typography>
<Typography
variant="price"
sx={{
fontWeight: 500,
fontSize: "26px",
lineHeight: "31px",
order: upMd ? 2 : 1,
}}
>
{currencyFormatter.format(priceAfterDiscounts / 100)}
</Typography>
</Box>
);
{notEnoughMoneyAmount > 0 && (
<Alert severity="error" variant="filled">
Нехватает {currencyFormatter.format(notEnoughMoneyAmount / 100)}
</Alert>
)}
<CustomButton
variant="contained"
onClick={notEnoughMoneyAmount === 0 ? handlePayClick : handleReplenishWallet}
sx={{
mt: "10px",
backgroundColor: theme.palette.brightPurple.main,
}}
>
{notEnoughMoneyAmount === 0 ? "Оплатить" : "Пополнить"}
</CustomButton>
</Box>
</Box>
);
}

@ -145,6 +145,8 @@ export default function CustomWrapper({ serviceData }: Props) {
serviceData.tariffs.map((tariff) => {
const privilege = tariff.privileges[0];
console.log(tariff);
return tariff.privileges.length > 1 ? (
<CustomTariffAccordion key={tariff.id} tariffCartData={tariff} />
) : (

@ -4,19 +4,16 @@ import { enqueueSnackbar } from "notistack";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import CustomButton from "@root/components/CustomButton";
import { createAndSendTariff } from "@root/stores/customTariffs";
import { updateTariffs } from "@root/stores/tariffs";
import { addTariffToCart } from "@root/stores/user";
import CustomAccordion from "@components/CustomAccordion";
import { cardShadow } from "@root/utils/themes/shadow";
import { PrivilegeCartData, getMessageFromFetchError } from "@frontend/kitui";
import { TariffCartData, getMessageFromFetchError } from "@frontend/kitui";
import CustomSaveAccordion from "../../components/CustomSaveAccordion";
interface Props {
content: PrivilegeCartData[];
name: string;
content: TariffCartData[];
}
const SaveWrapper: FC<Props> = ({ content, name }) => {
const SaveWrapper: FC<Props> = ({ content }) => {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
@ -46,12 +43,10 @@ const SaveWrapper: FC<Props> = ({ content, name }) => {
boxShadow: cardShadow,
}}
>
{content.map(({ price, description, privilegeId }, index) => (
<CustomAccordion
key={index}
{content.map(({ name, id, price, isCustom, privileges }) => (
<CustomSaveAccordion
key={id}
divide
text={description}
price={price}
header={
<Box
sx={{
@ -101,10 +96,13 @@ const SaveWrapper: FC<Props> = ({ content, name }) => {
px: 0,
}}
>
{new Intl.NumberFormat("ru-RU").format(price)} руб.
{new Intl.NumberFormat("ru-RU").format(price / 100)} руб.
</Typography>
<CustomButton
onClick={() => handleTariffItemClick(privilegeId)}
onClick={(event) => {
event.stopPropagation();
handleTariffItemClick(id);
}}
variant="contained"
sx={{
mr: "25px",
@ -117,12 +115,39 @@ const SaveWrapper: FC<Props> = ({ content, name }) => {
":hover": {
backgroundColor: theme.palette.background.default,
},
zIndex: "100",
}}
>
Купить
</CustomButton>
</Box>
}
privilege={privileges.map(({ description, price, privilegeId }) => (
<Box
sx={{
display: "flex",
justifyContent: "space-between",
px: "20px",
py: upMd ? "25px" : undefined,
pt: upMd ? undefined : "15px",
pb: upMd ? undefined : "25px",
backgroundColor: "#F1F2F6",
}}
>
<Typography
sx={{
fontSize: upMd ? undefined : "16px",
lineHeight: upMd ? undefined : "19px",
color: theme.palette.grey3.main,
}}
>
{description}
</Typography>
<Typography sx={{ display: price ? "block" : "none", fontSize: "18px", mr: "120px" }}>
{price / 100} руб.
</Typography>
</Box>
))}
/>
))}
</Box>

@ -1,19 +1,21 @@
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 { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
import { useCart } from "@root/utils/hooks/useCart";
import SaveWrapper from "./SaveWrapper";
import { useTariffStore } from "@root/stores/tariffs";
export default function Faq() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const cart = useCart();
const handleCustomBackNavigation = useHistoryTracker();
const tariffs = useTariffStore((state) => state.tariffs);
console.log(cart);
console.log(tariffs);
const handleCustomBackNavigation = useHistoryTracker();
return (
<SectionWrapper
@ -40,9 +42,7 @@ export default function Faq() {
</Box>
<Box mt={upMd ? "27px" : "10px"}>
{cart.services.map(({ serviceKey, tariffs }) =>
serviceKey === "custom"
? tariffs.map(({ privileges, name }) => <SaveWrapper name={name} content={privileges} />)
: null
serviceKey === "custom" ? <SaveWrapper content={tariffs} /> : null
)}
</Box>
</SectionWrapper>

@ -1,124 +1,112 @@
import {
Box,
Typography,
Tooltip,
SxProps,
Theme,
useTheme,
} from "@mui/material";
import { Box, Typography, Tooltip, SxProps, Theme, useTheme } from "@mui/material";
import CustomButton from "@components/CustomButton";
import { MouseEventHandler, ReactNode } from "react";
import { cardShadow } from "@root/utils/themes/shadow";
interface Props {
icon: ReactNode;
headerText: string;
text: string | string[];
icon: ReactNode;
headerText: string;
text: string | string[];
sx?: SxProps<Theme>;
buttonProps?: {
sx?: SxProps<Theme>;
buttonProps?: {
sx?: SxProps<Theme>;
onClick?: MouseEventHandler<HTMLButtonElement>;
text?: string;
};
price?: ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement>;
text?: string;
};
price?: ReactNode;
}
export default function TariffCard({
icon,
headerText,
text,
sx,
price,
buttonProps,
}: Props) {
const theme = useTheme();
export default function TariffCard({ icon, headerText, text, sx, price, buttonProps }: Props) {
const theme = useTheme();
text = Array.isArray(text) ? text : [text];
text = Array.isArray(text) ? text : [text];
return (
<Box
return (
<Box
sx={{
width: "100%",
minHeight: "250px",
bgcolor: "white",
borderRadius: "12px",
display: "flex",
flexDirection: "column",
alignItems: "start",
p: "20px",
boxShadow: cardShadow,
...sx,
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: "16px",
}}
>
{icon}
{price && (
<Box
sx={{
width: "100%",
minHeight: "250px",
bgcolor: "white",
borderRadius: "12px",
display: "flex",
flexDirection: "column",
alignItems: "start",
p: "20px",
boxShadow: cardShadow,
...sx,
display: "flex",
alignItems: "baseline",
flexWrap: "wrap",
columnGap: "10px",
rowGap: 0,
}}
>
{price}
</Box>
)}
</Box>
<Tooltip title={<Typography>{headerText}</Typography>} placement="top">
<Typography
variant="h5"
sx={{
mt: "14px",
mb: "10px",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
width: "100%",
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: "16px",
}}
>
{icon}
{price && (
<Box
sx={{
display: "flex",
alignItems: "baseline",
flexWrap: "wrap",
columnGap: "10px",
rowGap: 0,
}}
>
{price}
</Box>
)}
</Box>
<Tooltip title={<Typography>{headerText}</Typography>} placement="top">
<Typography
variant="h5"
sx={{
mt: "14px",
mb: "10px",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
width: "100%",
}}
>
{headerText}
</Typography>
</Tooltip>
<Tooltip
title={text.map((line, index) => (
<Typography key={index}>{line}</Typography>
))}
placement="top"
>
<Box sx={{
overflow: "hidden",
textOverflow: "clip",
mb: "auto",
}}>
{text.map((line, index) => (
<Typography key={index}>{line}</Typography>
))}
</Box>
</Tooltip>
{buttonProps && (
<CustomButton
onClick={buttonProps.onClick}
variant="outlined"
sx={{
color: theme.palette.brightPurple.main,
borderColor: theme.palette.brightPurple.main,
mt: "10px",
...buttonProps.sx,
}}
>
{buttonProps.text}
</CustomButton>
)}
{headerText}
</Typography>
</Tooltip>
<Tooltip
title={text.map((line, index) => (
<Typography key={index}>{line}</Typography>
))}
placement="top"
>
<Box
sx={{
overflow: "hidden",
textOverflow: "clip",
mb: "auto",
}}
>
{text.map((line, index) => (
<Typography key={index}>{line}</Typography>
))}
</Box>
);
</Tooltip>
{buttonProps && (
<CustomButton
onClick={buttonProps.onClick}
variant="outlined"
sx={{
color: theme.palette.brightPurple.main,
borderColor: theme.palette.brightPurple.main,
mt: "10px",
...buttonProps.sx,
}}
>
{buttonProps.text}
</CustomButton>
)}
</Box>
);
}

@ -39,6 +39,8 @@ export default function TariffPage() {
const unit: string = String(location.pathname).slice(9);
const currentTariffs = Object.values(cartTariffMap).filter((tariff): tariff is Tariff => typeof tariff === "object");
console.log(currentTariffs);
useAllTariffsFetcher({
onSuccess: updateTariffs,
onError: (error) => {
@ -74,6 +76,8 @@ export default function TariffPage() {
);
});
console.log(isCustomTariffs);
const createTariffElements = (filteredTariffs: Tariff[]) => {
const tariffElements = filteredTariffs
.filter((tariff) => tariff.privilegies.length > 0)

@ -7,50 +7,59 @@ import { addCartTariffs, removeMissingCartTariffs, setCartTariffStatus, useCartS
import { isAxiosError } from "axios";
import { useDiscountStore } from "@root/stores/discounts";
export function useCart() {
const tariffs = useTariffStore(state => state.tariffs);
const cartTariffMap = useCartStore(state => state.cartTariffMap);
const cartTariffIds = useUserStore(state => state.userAccount?.cart);
const cart = useCartStore(state => state.cart);
const discounts = useDiscountStore(state => state.discounts);
const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0;
const tariffs = useTariffStore((state) => state.tariffs);
const cartTariffMap = useCartStore((state) => state.cartTariffMap);
const cartTariffIds = useUserStore((state) => state.userAccount?.cart);
const cart = useCartStore((state) => state.cart);
const discounts = useDiscountStore((state) => state.discounts);
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.purchasesAmount) ?? 0;
useEffect(function addTariffsToCart() {
const knownTariffs: Tariff[] = [];
useEffect(
function addTariffsToCart() {
const knownTariffs: Tariff[] = [];
cartTariffIds?.forEach(tariffId => {
if (typeof cartTariffMap[tariffId] === "object") return;
cartTariffIds?.forEach((tariffId) => {
if (typeof cartTariffMap[tariffId] === "object") return;
const tariff = tariffs.find(tariff => tariff._id === tariffId);
if (tariff) return knownTariffs.push(tariff);
const tariff = tariffs.find((tariff) => tariff._id === tariffId);
if (tariff) return knownTariffs.push(tariff);
if (!cartTariffMap[tariffId]) {
setCartTariffStatus(tariffId, "loading");
if (!cartTariffMap[tariffId]) {
setCartTariffStatus(tariffId, "loading");
getTariffById(tariffId).then(tariff => {
devlog("Unknown tariff", tariff);
addCartTariffs([tariff], discounts, purchasesAmount);
}).catch(error => {
devlog(`Error fetching unknown tariff ${tariffId}`, error);
setCartTariffStatus(tariffId, "not found");
if (isAxiosError(error) && error.response?.status === 404) {
removeTariffFromCart(tariffId).then(() => {
devlog(`Unexistant tariff with id ${tariffId} deleted from cart`);
}).catch(error => {
devlog("Error deleting unexistant tariff from cart", error);
});
}
});
}
});
getTariffById(tariffId)
.then((tariff) => {
devlog("Unknown tariff", tariff);
addCartTariffs([tariff], discounts, purchasesAmount);
})
.catch((error) => {
devlog(`Error fetching unknown tariff ${tariffId}`, error);
setCartTariffStatus(tariffId, "not found");
if (isAxiosError(error) && error.response?.status === 404) {
removeTariffFromCart(tariffId)
.then(() => {
devlog(`Unexistant tariff with id ${tariffId} deleted from cart`);
})
.catch((error) => {
devlog("Error deleting unexistant tariff from cart", error);
});
}
});
}
});
if (knownTariffs.length > 0) addCartTariffs(knownTariffs, discounts, purchasesAmount);
}, [cartTariffIds, cartTariffMap, discounts, purchasesAmount, tariffs]);
if (knownTariffs.length > 0) addCartTariffs(knownTariffs, discounts, purchasesAmount);
},
[cartTariffIds, cartTariffMap, discounts, purchasesAmount, tariffs]
);
useEffect(function cleanUpCart() {
if (cartTariffIds) removeMissingCartTariffs(cartTariffIds, discounts, purchasesAmount);
}, [cartTariffIds, discounts, purchasesAmount]);
useEffect(
function cleanUpCart() {
if (cartTariffIds) removeMissingCartTariffs(cartTariffIds, discounts, purchasesAmount);
},
[cartTariffIds, discounts, purchasesAmount]
);
return cart;
return cart;
}