diff --git a/src/api/promocode.ts b/src/api/promocode.ts new file mode 100644 index 0000000..1733d9d --- /dev/null +++ b/src/api/promocode.ts @@ -0,0 +1,32 @@ +import { makeRequest } from "@frontend/kitui"; +import { isAxiosError } from "axios"; + + +const apiUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode"; + +export async function activatePromocode(promocode: string) { + try { + const response = await makeRequest<{ + codeword: string; + } | { + fastLink: string; + }, { + greetings: string; + }>({ + url: apiUrl + "/activate", + method: "POST", + body: { + codeword: promocode, + } + }); + + return response.greetings; + } catch (error) { + let message = "Неизвестная ошибка"; + if (isAxiosError(error) && error.status === 404) { + message = "Промокод не найден"; + } + + throw new Error(message); + } +} diff --git a/src/components/icons/SimpleArrowDown.tsx b/src/components/icons/SimpleArrowDown.tsx new file mode 100644 index 0000000..3be39c3 --- /dev/null +++ b/src/components/icons/SimpleArrowDown.tsx @@ -0,0 +1,26 @@ +import { Box, SxProps, Theme } from "@mui/material"; + + +type Props = { + sx?: SxProps; +}; + +export default function SimpleArrowDown({ sx }: Props) { + + return ( + + + + + + + ); +} diff --git a/src/pages/Payment/Payment.tsx b/src/pages/Payment/Payment.tsx index 1c26153..e5895c9 100644 --- a/src/pages/Payment/Payment.tsx +++ b/src/pages/Payment/Payment.tsx @@ -1,10 +1,12 @@ import { - Box, - Button, - IconButton, - Typography, - useMediaQuery, - useTheme, + Box, + Button, + Collapse, + Grow, + IconButton, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import SectionWrapper from "@components/SectionWrapper"; @@ -28,314 +30,374 @@ import { useUserStore } from "@root/stores/user"; import { VerificationStatus } from "@root/model/account"; import { WarnModal } from "./WarnModal"; import { SorryModal } from "./SorryModal"; +import SimpleArrowDown from "@root/components/icons/SimpleArrowDown"; +import PromocodeTextField from "./PromocodeTextField"; +import { activatePromocode } from "@root/api/promocode"; type PaymentMethod = { - label: string; - name: string; - image: string; - unpopular?: boolean; + label: string; + name: string; + image: string; + unpopular?: boolean; }; const paymentMethods: PaymentMethod[] = [ - { label: "Тинькофф", name: "tinkoffBank", image: tinkoffLogo }, - { label: "СБП", name: "sbp", image: spbLogo }, - { label: "SberPay", name: "sberbank", image: sberpayLogo }, - { label: "B2B Сбербанк", name: "b2bSberbank", image: b2bLogo }, - { label: "ЮMoney", name: "yoomoney", image: umoneyLogo }, + { label: "Тинькофф", name: "tinkoffBank", image: tinkoffLogo }, + { label: "СБП", name: "sbp", image: spbLogo }, + { label: "SberPay", name: "sberbank", image: sberpayLogo }, + { label: "B2B Сбербанк", name: "b2bSberbank", image: b2bLogo }, + { label: "ЮMoney", name: "yoomoney", image: umoneyLogo }, ]; type PaymentMethodType = (typeof paymentMethods)[number]["name"]; export default function Payment() { - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); - const upSm = useMediaQuery(theme.breakpoints.up("sm")); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const upSm = useMediaQuery(theme.breakpoints.up("sm")); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const [promocodeField, setPromocodeField] = useState(""); + const [isPromocodeFieldOpen, setIsPromocodeFieldOpen] = useState(false); + const [selectedPaymentMethod, setSelectedPaymentMethod] = + useState(""); + const [warnModalOpen, setWarnModalOpen] = useState(false); + const [sorryModalOpen, setSorryModalOpen] = useState(false); + const [paymentValueField, setPaymentValueField] = useState("0"); + const [paymentLink, setPaymentLink] = useState(""); + const [fromSquiz, setIsFromSquiz] = useState(false); + const location = useLocation(); + const verificationStatus = useUserStore((state) => state.verificationStatus); + const navigate = useNavigate(); + const handleCustomBackNavigation = useHistoryTracker(); - const [selectedPaymentMethod, setSelectedPaymentMethod] = - useState(""); - const [warnModalOpen, setWarnModalOpen] = useState(false); - const [sorryModalOpen, setSorryModalOpen] = useState(false); - const [paymentValueField, setPaymentValueField] = useState("0"); - const [paymentLink, setPaymentLink] = useState(""); - const [fromSquiz, setIsFromSquiz] = useState(false); - const location = useLocation(); - const verificationStatus = useUserStore((state) => state.verificationStatus); - const navigate = useNavigate(); - const handleCustomBackNavigation = useHistoryTracker(); + const notEnoughMoneyAmount = + (location.state?.notEnoughMoneyAmount as number) ?? 0; - const notEnoughMoneyAmount = - (location.state?.notEnoughMoneyAmount as number) ?? 0; + const paymentValue = parseFloat(paymentValueField) * 100; - const paymentValue = parseFloat(paymentValueField) * 100; + useLayoutEffect(() => { + setPaymentValueField((notEnoughMoneyAmount / 100).toString()); + const params = new URLSearchParams(window.location.search); + const fromSquiz = params.get("action"); + if (fromSquiz === "squizpay") { + setIsFromSquiz(true); + setPaymentValueField((Number(params.get("dif") || "0") / 100).toString()); + } + history.pushState(null, document.title, "/payment"); + console.log(fromSquiz); + }, []); - useLayoutEffect(() => { - setPaymentValueField((notEnoughMoneyAmount / 100).toString()); - const params = new URLSearchParams(window.location.search); - const fromSquiz = params.get("action"); - if (fromSquiz === "squizpay") { - setIsFromSquiz(true); - setPaymentValueField((Number(params.get("dif") || "0") / 100).toString()); - } - history.pushState(null, document.title, "/payment"); - console.log(fromSquiz); - }, []); + async function handleChoosePaymentClick() { + if (!selectedPaymentMethod) { + enqueueSnackbar("Введите метод оплаты"); + return; + } - useEffect(() => { - setPaymentLink(""); - }, [selectedPaymentMethod]); + if (Number(paymentValueField) === 0) { + enqueueSnackbar("Введите сумму"); + return; + } - async function handleChoosePaymentClick() { - if (!selectedPaymentMethod) { - enqueueSnackbar("Введите метод оплаты") - return; - } + if (selectedPaymentMethod !== "rspay") { + const [sendPaymentResponse, sendPaymentError] = await sendPayment({ + fromSquiz, + body: { + type: selectedPaymentMethod, + amount: Number(paymentValueField) * 100, + }, + }); - if (Number(paymentValueField) === 0) { - enqueueSnackbar("Введите сумму") - return; - } + if (selectedPaymentMethod === "rspay") { + if (verificationStatus !== VerificationStatus.VERIFICATED) { + setWarnModalOpen(true); - if (selectedPaymentMethod !== "rspay") { - const [sendPaymentResponse, sendPaymentError] = await sendPayment({ - fromSquiz, - body: { - type: selectedPaymentMethod, - amount: Number(paymentValueField) * 100, - }, - }); + return; + } + console.log(paymentValueField); + if (Number(paymentValueField) < 900) { + enqueueSnackbar("Минимальная сумма 900р"); - if (selectedPaymentMethod === "rspay") { - if (verificationStatus !== VerificationStatus.VERIFICATED) { - setWarnModalOpen(true); + return; + } - return; - } - console.log(paymentValueField) - if (Number(paymentValueField) < 900){ - enqueueSnackbar("Минимальная сумма 900р") + const sendRSPaymentError = await sendRSPayment(Number(paymentValueField)); - return; - } + if (sendRSPaymentError) { + return enqueueSnackbar(sendRSPaymentError); + } - const sendRSPaymentError = await sendRSPayment(Number(paymentValueField)); + enqueueSnackbar( + "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." + ); - if (sendRSPaymentError) { - return enqueueSnackbar(sendRSPaymentError); - } + navigate("/settings"); + } - enqueueSnackbar( - "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." - ); + if (sendPaymentError) { + return enqueueSnackbar(sendPaymentError); + } - navigate("/settings"); - } + if (sendPaymentResponse) { + setPaymentLink(sendPaymentResponse.link); + } - if (sendPaymentError) { - return enqueueSnackbar(sendPaymentError); - } + return; + } else { + if (verificationStatus !== VerificationStatus.VERIFICATED) { + setWarnModalOpen(true); - if (sendPaymentResponse) { - setPaymentLink(sendPaymentResponse.link); - } + return; + } + console.log(paymentValueField); + if (Number(paymentValueField) < 900) { + enqueueSnackbar("Минимальная сумма 900р"); - return; - } else { - if (verificationStatus !== VerificationStatus.VERIFICATED) { - setWarnModalOpen(true); + return; + } - return; - } - console.log(paymentValueField) - if (Number(paymentValueField) < 900){ - enqueueSnackbar("Минимальная сумма 900р") + const sendRSPaymentError = await sendRSPayment(Number(paymentValueField)); - return; - } + if (sendRSPaymentError) { + return enqueueSnackbar(sendRSPaymentError); + } - const sendRSPaymentError = await sendRSPayment(Number(paymentValueField)); + enqueueSnackbar( + "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." + ); - if (sendRSPaymentError) { - return enqueueSnackbar(sendRSPaymentError); - } + navigate("/settings"); - enqueueSnackbar( - "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." - ); - - navigate("/settings"); + } } - } + function handleApplyPromocode() { + if (!promocodeField) return; + activatePromocode(promocodeField).then(response => { + enqueueSnackbar(response); + }).catch(error => { + enqueueSnackbar(error.message); + }); + } - return ( - - - {!upMd && ( - - - - )} - Способ оплаты - - {!upMd && ( - - Выберите способ оплаты - - )} - - - {paymentMethods.map(({ name, label, image, unpopular = false }) => ( - { - setSelectedPaymentMethod(name) - }} - unpopular={false} - /> - ))} - { - setSelectedPaymentMethod("rspay") - }} - unpopular={false} - /> - - - - {upMd && Выберите способ оплаты} - К оплате - {paymentLink ? ( - + - {currencyFormatter.format(paymentValue / 100)} - - ) : ( - setPaymentValueField(e.target.value.replace(/^0+(?=\d\.)/, ''))} - id="payment-amount" - gap={upMd ? "16px" : "10px"} - color={"#F2F3F7"} - FormInputSx={{ mb: "28px" }} - /> + > + {!upMd && ( + + + + )} + Способ оплаты + + {!upMd && ( + + Выберите способ оплаты + )} - - {paymentLink ? ( - - ) : ( - - )} - - - - - - ); + + + {paymentMethods.map(({ name, label, image, unpopular = false }) => ( + { + setSelectedPaymentMethod(name); + setPaymentLink(""); + }} + unpopular={false} + /> + ))} + { + setSelectedPaymentMethod("rspay"); + setPaymentLink(""); + }} + unpopular={false} + /> + + + + + setPromocodeField(e.target.value)} + onApplyClick={handleApplyPromocode} + /> + + + + + + {upMd && Выберите способ оплаты} + К оплате + {paymentLink ? ( + + {currencyFormatter.format(paymentValue / 100)} + + ) : ( + setPaymentValueField(e.target.value.replace(/^0+(?=\d\.)/, ""))} + id="payment-amount" + gap={upMd ? "16px" : "10px"} + color={"#F2F3F7"} + FormInputSx={{ mb: "28px" }} + /> + )} + + {paymentLink ? ( + + ) : ( + + )} + + + + + + ); } diff --git a/src/pages/Payment/PromocodeTextField.tsx b/src/pages/Payment/PromocodeTextField.tsx new file mode 100644 index 0000000..83c6c50 --- /dev/null +++ b/src/pages/Payment/PromocodeTextField.tsx @@ -0,0 +1,78 @@ +import { Box, Button, TextField, useMediaQuery, useTheme } from "@mui/material"; + + +interface Props { + value?: string; + onChange?: (e: React.ChangeEvent) => void; + onApplyClick?: () => void; +} + +export default function PromocodeTextField({ value, onChange, onApplyClick }: Props) { + const theme = useTheme(); + const upSm = useMediaQuery(theme.breakpoints.up("sm")); + + return ( + + + + + ); +}