344 lines
9.6 KiB
TypeScript
344 lines
9.6 KiB
TypeScript
import { useState, useRef } from "react"
|
||
import {
|
||
Typography,
|
||
Drawer,
|
||
useMediaQuery,
|
||
useTheme,
|
||
Box,
|
||
IconButton,
|
||
Badge,
|
||
Button,
|
||
} from "@mui/material"
|
||
import SectionWrapper from "./SectionWrapper"
|
||
import CustomWrapperDrawer from "./CustomWrapperDrawer"
|
||
import { NotificationsModal } from "./NotificationsModal"
|
||
import { Loader } from "./Loader"
|
||
import { useCart } from "@root/utils/hooks/useCart"
|
||
import { currencyFormatter } from "@root/utils/currencyFormatter"
|
||
import {
|
||
closeCartDrawer,
|
||
openCartDrawer,
|
||
useCartStore,
|
||
} from "@root/stores/cart"
|
||
import { setUserAccount, useUserStore } from "@root/stores/user"
|
||
import { useTicketStore } from "@root/stores/tickets"
|
||
|
||
import { ReactComponent as BellIcon } from "@root/assets/Icons/bell.svg"
|
||
import { ReactComponent as CartIcon } from "@root/assets/Icons/cart.svg"
|
||
import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg"
|
||
|
||
import { payCart } from "@root/api/cart"
|
||
import { enqueueSnackbar } from "notistack"
|
||
import { Link, useNavigate } from "react-router-dom"
|
||
import { withErrorBoundary } from "react-error-boundary"
|
||
import { handleComponentError } from "@root/utils/handleComponentError"
|
||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"
|
||
|
||
function Drawers() {
|
||
const [openNotificationsModal, setOpenNotificationsModal] =
|
||
useState<boolean>(false)
|
||
const [loading, setLoading] = useState<boolean>(false)
|
||
const bellRef = useRef<HTMLButtonElement | null>(null)
|
||
const navigate = useNavigate()
|
||
const theme = useTheme()
|
||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
||
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
||
const isDrawerOpen = useCartStore((state) => state.isDrawerOpen)
|
||
const cart = useCart()
|
||
const userAccount = useUserStore((state) => state.userAccount)
|
||
const tickets = useTicketStore((state) => state.tickets)
|
||
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0)
|
||
|
||
const notificationsCount = tickets.filter(
|
||
({ user, top_message }) =>
|
||
user !== top_message.user_id && top_message.shown.me !== 1
|
||
).length
|
||
|
||
async function handlePayClick() {
|
||
setLoading(true)
|
||
|
||
const [payCartResponse, payCartError] = await payCart()
|
||
|
||
if (payCartError) {
|
||
if (payCartError.includes("insufficient funds: ")) {
|
||
const notEnoughMoneyAmount = parseInt(
|
||
payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, "")
|
||
)
|
||
|
||
setNotEnoughMoneyAmount(notEnoughMoneyAmount)
|
||
}
|
||
|
||
setLoading(false)
|
||
|
||
return enqueueSnackbar(payCartError)
|
||
}
|
||
|
||
if (payCartResponse) {
|
||
setUserAccount(payCartResponse)
|
||
}
|
||
|
||
setLoading(false)
|
||
closeCartDrawer()
|
||
}
|
||
|
||
function handleReplenishWallet() {
|
||
navigate("/payment", { state: { notEnoughMoneyAmount } })
|
||
}
|
||
|
||
return (
|
||
<Box sx={{ display: "flex", gap: isTablet ? "10px" : "20px" }}>
|
||
<IconButton
|
||
ref={bellRef}
|
||
aria-label="cart"
|
||
onClick={() => setOpenNotificationsModal((isOpened) => !isOpened)}
|
||
sx={{
|
||
cursor: "pointer",
|
||
borderRadius: "6px",
|
||
background: openNotificationsModal
|
||
? theme.palette.purple.main
|
||
: theme.palette.background.default,
|
||
"& .MuiBadge-badge": {
|
||
background: openNotificationsModal
|
||
? theme.palette.background.default
|
||
: theme.palette.purple.main,
|
||
color: openNotificationsModal
|
||
? theme.palette.purple.main
|
||
: theme.palette.background.default,
|
||
},
|
||
"& svg > path:first-of-type": {
|
||
fill: openNotificationsModal ? "#FFFFFF" : "#9A9AAF",
|
||
},
|
||
"& svg > path:last-child": {
|
||
stroke: openNotificationsModal ? "#FFFFFF" : "#9A9AAF",
|
||
},
|
||
"&:hover": {
|
||
background: theme.palette.purple.main,
|
||
"& .MuiBox-root": {
|
||
background: theme.palette.purple.main,
|
||
},
|
||
"& .MuiBadge-badge": {
|
||
background: theme.palette.background.default,
|
||
color: theme.palette.purple.main,
|
||
},
|
||
"& svg > path:first-of-type": { fill: "#FFFFFF" },
|
||
"& svg > path:last-child": { stroke: "#FFFFFF" },
|
||
},
|
||
}}
|
||
>
|
||
<Badge
|
||
badgeContent={notificationsCount}
|
||
sx={{
|
||
"& .MuiBadge-badge": {
|
||
display: notificationsCount ? "flex" : "none",
|
||
color: "#FFFFFF",
|
||
background: theme.palette.purple.main,
|
||
transform: "scale(0.8) translate(50%, -50%)",
|
||
top: "2px",
|
||
right: "2px",
|
||
fontWeight: 400,
|
||
},
|
||
}}
|
||
>
|
||
<BellIcon />
|
||
</Badge>
|
||
</IconButton>
|
||
<NotificationsModal
|
||
open={openNotificationsModal}
|
||
setOpen={setOpenNotificationsModal}
|
||
anchorElement={bellRef.current}
|
||
notifications={tickets
|
||
.filter(({ user, top_message }) => user !== top_message.user_id)
|
||
.map((ticket) => ({
|
||
text: "У вас новое сообщение от техподдержки",
|
||
date: new Date(ticket.updated_at).toLocaleDateString(),
|
||
url: `/support/${ticket.id}`,
|
||
watched:
|
||
ticket.user === ticket.top_message.user_id ||
|
||
ticket.top_message.shown.me === 1,
|
||
}))}
|
||
/>
|
||
<IconButton
|
||
onClick={openCartDrawer}
|
||
component="div"
|
||
sx={{
|
||
cursor: "pointer",
|
||
background: theme.palette.background.default,
|
||
borderRadius: "6px",
|
||
"&:hover": {
|
||
background: theme.palette.purple.main,
|
||
"& .MuiBox-root": {
|
||
background: theme.palette.purple.main,
|
||
},
|
||
"& .MuiBadge-badge": {
|
||
background: theme.palette.background.default,
|
||
color: theme.palette.purple.main,
|
||
},
|
||
"& svg > path:nth-of-type(1)": { fill: "#FFFFFF" },
|
||
"& svg > path:nth-of-type(2)": { fill: "#FFFFFF" },
|
||
"& svg > path:nth-of-type(3)": { stroke: "#FFFFFF" },
|
||
},
|
||
}}
|
||
>
|
||
<Badge
|
||
badgeContent={userAccount?.cart.length}
|
||
sx={{
|
||
"& .MuiBadge-badge": {
|
||
display: userAccount?.cart.length ? "flex" : "none",
|
||
color: "#FFFFFF",
|
||
background: theme.palette.purple.main,
|
||
transform: "scale(0.8) translate(50%, -50%)",
|
||
top: "2px",
|
||
right: "2px",
|
||
fontWeight: 400,
|
||
},
|
||
}}
|
||
>
|
||
<CartIcon />
|
||
</Badge>
|
||
</IconButton>
|
||
<Drawer
|
||
anchor={"right"}
|
||
open={isDrawerOpen}
|
||
onClose={closeCartDrawer}
|
||
sx={{ background: "rgba(0, 0, 0, 0.55)" }}
|
||
>
|
||
<SectionWrapper
|
||
maxWidth="lg"
|
||
sx={{
|
||
pl: "0px",
|
||
pr: "0px",
|
||
width: "450px",
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
width: "100%",
|
||
pt: "12px",
|
||
pb: "12px",
|
||
display: "flex",
|
||
justifyContent: "space-between",
|
||
bgcolor: "#F2F3F7",
|
||
gap: "10px",
|
||
pl: "20px",
|
||
pr: "20px",
|
||
}}
|
||
>
|
||
<Typography
|
||
component="div"
|
||
sx={{
|
||
fontSize: "18px",
|
||
lineHeight: "21px",
|
||
font: "Rubick",
|
||
}}
|
||
>
|
||
Корзина
|
||
</Typography>
|
||
<IconButton onClick={closeCartDrawer} sx={{ p: 0 }}>
|
||
<CrossIcon />
|
||
</IconButton>
|
||
</Box>
|
||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||
{cart.services.map((serviceData) => (
|
||
<CustomWrapperDrawer
|
||
key={serviceData.serviceKey}
|
||
serviceData={serviceData}
|
||
/>
|
||
))}
|
||
<Box
|
||
sx={{
|
||
mt: "40px",
|
||
pt: upMd ? "30px" : undefined,
|
||
borderTop: upMd
|
||
? `1px solid ${theme.palette.gray.main}`
|
||
: undefined,
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
width: upMd ? "100%" : undefined,
|
||
display: "flex",
|
||
flexWrap: "wrap",
|
||
flexDirection: "column",
|
||
}}
|
||
>
|
||
<Typography variant="h4" mb={upMd ? "18px" : "30px"}>
|
||
Итоговая цена
|
||
</Typography>
|
||
<Typography color={theme.palette.gray.dark}>
|
||
Текст-заполнитель — это текст, который имеет Текст-заполнитель
|
||
— это текст, который имеет Текст-заполнитель — это текст,
|
||
который имеет Текст-заполнитель — это текст, который имеет
|
||
Текст-заполнитель
|
||
</Typography>
|
||
</Box>
|
||
<Box
|
||
sx={{
|
||
color: theme.palette.gray.dark,
|
||
pb: "100px",
|
||
pt: "38px",
|
||
}}
|
||
>
|
||
<Badge badgeContent={(cart.priceBeforeDiscounts - cart.priceAfterDiscounts)?`${(cart.priceBeforeDiscounts - cart.priceAfterDiscounts)/(cart.priceBeforeDiscounts/100)}`:null} color={'success'}>
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexDirection: upMd ? "column" : "row",
|
||
alignItems: upMd ? "start" : "center",
|
||
mt: upMd ? "10px" : "30px",
|
||
gap: "15px",
|
||
}}
|
||
>
|
||
<Typography
|
||
color={theme.palette.orange.main}
|
||
sx={{
|
||
textDecoration: "line-through",
|
||
order: upMd ? 1 : 2,
|
||
}}
|
||
>
|
||
{currencyFormatter.format(cart.priceBeforeDiscounts / 100)}
|
||
</Typography>
|
||
<Typography
|
||
variant="p1"
|
||
sx={{
|
||
fontWeight: 500,
|
||
fontSize: "26px",
|
||
lineHeight: "31px",
|
||
order: upMd ? 2 : 1,
|
||
}}
|
||
>
|
||
{currencyFormatter.format(cart.priceAfterDiscounts / 100)}
|
||
</Typography>
|
||
</Box>
|
||
</Badge>
|
||
<Button
|
||
variant="pena-contained-dark"
|
||
onClick={() =>
|
||
notEnoughMoneyAmount === 0
|
||
? !loading && handlePayClick()
|
||
: handleReplenishWallet()
|
||
}
|
||
sx={{ mt: "25px" }}
|
||
>
|
||
{loading ? <Loader size={24} /> : "Оплатить"}
|
||
</Button>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
</SectionWrapper>
|
||
</Drawer>
|
||
</Box>
|
||
)
|
||
}
|
||
|
||
export default withErrorBoundary(Drawers, {
|
||
fallback: (
|
||
<Box sx={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
}}>
|
||
<ErrorOutlineIcon color="error" />
|
||
</Box>
|
||
),
|
||
onError: handleComponentError,
|
||
})
|