diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..1f72bd6 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..5007b2c Binary files /dev/null and b/public/favicon.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..d8a170d --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + diff --git a/public/favicon192.png b/public/favicon192.png new file mode 100644 index 0000000..c703bc9 Binary files /dev/null and b/public/favicon192.png differ diff --git a/public/favicon512.png b/public/favicon512.png new file mode 100644 index 0000000..513918a Binary files /dev/null and b/public/favicon512.png differ diff --git a/public/index.html b/public/index.html index 59a71d2..1807733 100644 --- a/public/index.html +++ b/public/index.html @@ -1,47 +1,28 @@ - + - - - - - - - - - - - - - React App + + + + + + + + - -
- - + +
\ No newline at end of file diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..56f9fd3 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,23 +1,10 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "PenaHub", + "name": "Pena Hub", "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], + { "src": "/favicon192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/favicon512.png", "type": "image/png", "sizes": "512x512" } + ], "start_url": ".", "display": "standalone", "theme_color": "#000000", diff --git a/src/api/verification.ts b/src/api/verification.ts index 974f511..b77ea2b 100644 --- a/src/api/verification.ts +++ b/src/api/verification.ts @@ -8,6 +8,7 @@ import type { SendDocumentsArgs, UpdateDocumentsArgs, } from "@root/model/auth" +import { AxiosError } from "axios" const apiUrl = process.env.REACT_APP_DOMAIN + "/verification" @@ -22,8 +23,19 @@ export async function verification( withCredentials: true, }) + verificationResponse.files = verificationResponse.files.map((obj) => { + obj.url = obj.url.replace("https://hub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "").replace("https://shub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "") + return obj + }) + console.log(verificationResponse) + return [verificationResponse] } catch (nativeError) { + const err = nativeError as AxiosError + if (err.response?.status === 404) { + return [null, `нет данных`] + } + console.log(nativeError) const [error] = parseAxiosError(nativeError) return [null, `Ошибка запроса верификации. ${error}`] diff --git a/src/api/wallet.ts b/src/api/wallet.ts index 9b9d0c5..73d57d4 100644 --- a/src/api/wallet.ts +++ b/src/api/wallet.ts @@ -1,46 +1,67 @@ -import { makeRequest } from "@frontend/kitui" -import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet" -import { parseAxiosError } from "@root/utils/parse-error" +import { makeRequest } from "@frontend/kitui"; +import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet"; +import { parseAxiosError } from "@root/utils/parse-error"; -const apiUrl = process.env.REACT_APP_DOMAIN + "/customer" +const apiUrl = process.env.REACT_APP_DOMAIN + "/customer"; const testPaymentBody: SendPaymentRequest = { - type: "bankCard", - amount: 15020, - currency: "RUB", - bankCard: { - number: "RUB", - expiryYear: "2021", - expiryMonth: "05", - csc: "05", - cardholder: "IVAN IVANOV", - }, - phoneNumber: "79000000000", - login: "login_test", - returnUrl: window.location.origin + "/wallet", -} + type: "bankCard", + amount: 15020, + currency: "RUB", + bankCard: { + number: "RUB", + expiryYear: "2021", + expiryMonth: "05", + csc: "05", + cardholder: "IVAN IVANOV", + }, + phoneNumber: "79000000000", + login: "login_test", + returnUrl: window.location.origin + "/wallet", +}; -export async function sendPayment( - {body = testPaymentBody, fromSquiz = false}: {body?: SendPaymentRequest, fromSquiz:boolean} -): Promise<[SendPaymentResponse | null, string?]> { - if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub" - try { - const sendPaymentResponse = await makeRequest< +export async function sendPayment({ + body = testPaymentBody, + fromSquiz = false, +}: { + body?: SendPaymentRequest; + fromSquiz: boolean; +}): Promise<[SendPaymentResponse | null, string?]> { + if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub"; + try { + const sendPaymentResponse = await makeRequest< SendPaymentRequest, SendPaymentResponse >({ - url: apiUrl + "/wallet", - contentType: true, - method: "POST", - useToken: true, - withCredentials: false, - body, - }) + url: apiUrl + "/wallet", + contentType: true, + method: "POST", + useToken: true, + withCredentials: false, + body, + }); - return [sendPaymentResponse] - } catch (nativeError) { - const [error] = parseAxiosError(nativeError) + return [sendPaymentResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); - return [null, `Ошибка оплаты. ${error}`] - } + return [null, `Ошибка оплаты. ${error}`]; + } } + +export const sendRSPayment = async (): Promise => { + try { + await makeRequest({ + url: apiUrl + "/wallet/rspay", + method: "POST", + useToken: true, + withCredentials: false, + }); + + return null; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return `Ошибка оплаты. ${error}`; + } +}; diff --git a/src/assets/bank-logo/rs-pay.png b/src/assets/bank-logo/rs-pay.png new file mode 100644 index 0000000..95270e8 Binary files /dev/null and b/src/assets/bank-logo/rs-pay.png differ diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx index 06f1ab3..8fc565d 100644 --- a/src/components/ChatMessage.tsx +++ b/src/components/ChatMessage.tsx @@ -15,21 +15,20 @@ export default function ChatMessage({ unAuthenticated = false, isSelf, text, cre const messageBackgroundColor = isSelf ? "white" : unAuthenticated ? "#EFF0F5" : theme.palette.gray.main - const date = new Date(createdAt) + const date = new Date(createdAt); + const today = isDateToday(date); const time = date.toLocaleString([], { hour: "2-digit", minute: "2-digit", - ...(!isDateToday(date) && { year: "2-digit", month: "2-digit", day: "2-digit" }) + ...(!today && { year: "2-digit", month: "2-digit", day: "2-digit" }) }) return ( void; + firstStep: number; }; export const CustomSlider = ({ @@ -13,11 +14,16 @@ export const CustomSlider = ({ min = 0, max = 100, onChange, + firstStep }: CustomSliderProps) => { const theme = useTheme() const [step, setStep] = useState(1) useEffect(() => { + if (value <= firstStep) { + return setStep(firstStep) + } + if (value < 100) { return setStep(10) } diff --git a/src/components/CustomWrapperDrawer.tsx b/src/components/CustomWrapperDrawer.tsx index 5786625..8d7eb55 100644 --- a/src/components/CustomWrapperDrawer.tsx +++ b/src/components/CustomWrapperDrawer.tsx @@ -18,7 +18,7 @@ const name: Record = { templategen: "Шаблонизатор", squiz: "Опросник", reducer: "Скоращатель ссылок", - custom: "Кастомные тарифы", + custom: "Мои тарифы", }; interface Props { diff --git a/src/components/FloatingSupportChat/Chat.tsx b/src/components/FloatingSupportChat/Chat.tsx index 8c9b375..eee11d2 100644 --- a/src/components/FloatingSupportChat/Chat.tsx +++ b/src/components/FloatingSupportChat/Chat.tsx @@ -33,14 +33,15 @@ import { useEventListener, createTicket, } from "@frontend/kitui"; -import { sendTicketMessage } from "@root/api/ticket"; +import { sendTicketMessage, shownMessage } from "@root/api/ticket"; import { useSSETab } from "@root/utils/hooks/useSSETab"; interface Props { + open: boolean; sx?: SxProps; } -export default function Chat({ sx }: Props) { +export default function Chat({ open = false, sx }: Props) { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); const [messageField, setMessageField] = useState(""); @@ -135,6 +136,16 @@ export default function Chat({ sx }: Props) { [lastMessageId] ); + useEffect(() => { + if (open) { + const newMessages = messages.filter(({ shown }) => shown.me !== 1); + + newMessages.map(async ({ id }) => { + await shownMessage(id); + }); + } + }, [open, messages]); + async function handleSendMessage() { if (!messageField || isMessageSending) return; @@ -200,130 +211,134 @@ export default function Chat({ sx }: Props) { }; return ( - - - + <> + {open && ( - Мария - - онлайн-консультант - - - - - - {sessionData && - messages.map((message) => ( - - ))} - - - + + Мария + + онлайн-консультант + + + + setMessageField(e.target.value)} - endAdornment={ - - - + + {sessionData && + messages.map((message) => ( + - - - } - /> - - - + ))} + + + setMessageField(e.target.value)} + endAdornment={ + + + + + + } + /> + + + + )} + ); } diff --git a/src/components/FloatingSupportChat/FloatingSupportChat.tsx b/src/components/FloatingSupportChat/FloatingSupportChat.tsx index 53471d7..cd1065d 100644 --- a/src/components/FloatingSupportChat/FloatingSupportChat.tsx +++ b/src/components/FloatingSupportChat/FloatingSupportChat.tsx @@ -1,94 +1,110 @@ -import { Box, Fab, Typography } from "@mui/material" -import { useState } from "react" -import CircleDoubleDown from "./CircleDoubleDownIcon" -import Chat from "./Chat" +import { useState } from "react"; +import { Box, Fab, Typography, Badge, useTheme } from "@mui/material"; + +import CircleDoubleDown from "./CircleDoubleDownIcon"; +import Chat from "./Chat"; + +import { useUnauthTicketStore } from "@root/stores/unauthTicket"; export default function FloatingSupportChat() { - const [isChatOpened, setIsChatOpened] = useState(false) + const [isChatOpened, setIsChatOpened] = useState(false); + const theme = useTheme(); + const { messages } = useUnauthTicketStore((state) => state); - const animation = { - "@keyframes runningStripe": { - "0%": { - left: "10%", - backgroundColor: "transparent", - }, - "10%": { - backgroundColor: "#ffffff", - }, - "50%": { - backgroundColor: "#ffffff", - transform: "translate(400px, 0)", - }, - "80%": { - backgroundColor: "#ffffff", - }, + const animation = { + "@keyframes runningStripe": { + "0%": { + left: "10%", + backgroundColor: "transparent", + }, + "10%": { + backgroundColor: "#ffffff", + }, + "50%": { + backgroundColor: "#ffffff", + transform: "translate(400px, 0)", + }, + "80%": { + backgroundColor: "#ffffff", + }, - "100%": { - backgroundColor: "transparent", - boxShadow: "none", - left: "100%", - }, - }, - } - return ( - - {isChatOpened && ( - - )} - setIsChatOpened((prev) => !prev)} - > - {!isChatOpened && ( - - )} + "100%": { + backgroundColor: "transparent", + boxShadow: "none", + left: "100%", + }, + }, + }; + return ( + + - - {!isChatOpened && Задайте нам вопрос} - - - ) + setIsChatOpened((prev) => !prev)} + > + {!isChatOpened && ( + + )} + shown.me !== 1).length} + sx={{ + "& .MuiBadge-badge": { + display: isChatOpened ? "none" : "flex", + color: "#FFFFFF", + background: theme.palette.purple.main, + }, + }} + > + + + + {!isChatOpened && ( + Задайте нам вопрос + )} + + + ); } diff --git a/src/components/NumberInputWithUnitAdornment.tsx b/src/components/NumberInputWithUnitAdornment.tsx index 397a124..40cf449 100644 --- a/src/components/NumberInputWithUnitAdornment.tsx +++ b/src/components/NumberInputWithUnitAdornment.tsx @@ -2,15 +2,24 @@ import { useState } from "react" import { InputAdornment, TextField, Typography, useTheme } from "@mui/material" import type { ChangeEvent } from "react" +import {Privilege} from "@frontend/kitui" interface Props { id: string; value: number; adornmentText: string; + privilege: Privilege; onChange: (value: number) => void; } -export default function NumberInputWithUnitAdornment({ id, value, adornmentText, onChange }: Props) { +const sliderSettingsByType = { + день: { max: 365, min: 0 }, + шаблон: { max: 5000, min: 0 }, + МБ: { max: 5000, min: 0 }, + заявка: { max: 5000, min: 0 } +} + +export default function NumberInputWithUnitAdornment({ id, value, adornmentText, privilege, onChange }: Props) { const theme = useTheme() const [changed, setChanged] = useState(false) @@ -20,7 +29,10 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText, size="small" placeholder="Введите вручную" id={id} - value={changed ? (value !== 0 ? value : "") : ""} + onBlur={(e) => {e.target.value = String(Number(String(e.target.value).replace(/^0+(?=\d\.)/, ''))) + console.log("сработало", e.target.value) + }} + value={changed ? (value !== sliderSettingsByType[privilege.value]?.min ? parseInt(String(value), 10) : sliderSettingsByType[privilege.value]?.min) : ""} onChange={({ target }: ChangeEvent) => { if (!changed) { setChanged(true) @@ -34,7 +46,7 @@ export default function NumberInputWithUnitAdornment({ id, value, adornmentText, if (!isFinite(newNumber) || newNumber < 0) { - onChange(0) + onChange(sliderSettingsByType[privilege.value]?.min) return } diff --git a/src/components/templCardPhoneLight.tsx b/src/components/templCardPhoneLight.tsx index 8d6bb41..93a0bca 100644 --- a/src/components/templCardPhoneLight.tsx +++ b/src/components/templCardPhoneLight.tsx @@ -5,9 +5,10 @@ interface Props { name?: string; desc?: string; image?: string; + href?: string; } -export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = card1Image }: Props) { +export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = card1Image, href = "#" }: Props) { const theme = useTheme() const upMd = useMediaQuery(theme.breakpoints.up("md")) const isMobile = useMediaQuery(theme.breakpoints.down(600)) @@ -54,7 +55,11 @@ export default function TemplCardPhoneLight({name="PenaDoc", desc="Самый у {desc} - + ) diff --git a/src/components/wideTemplCard.tsx b/src/components/wideTemplCard.tsx index 097f9a8..9c14835 100644 --- a/src/components/wideTemplCard.tsx +++ b/src/components/wideTemplCard.tsx @@ -9,9 +9,10 @@ interface Props { name?: string; desc?: string; image?: string; + href?: string; } -export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = cardImageBig }: Props) { +export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc="Самый удобный сервис для автоматизации документооборота и заполнения однотипных документов", image = cardImageBig, href="#" }: Props) { const theme = useTheme() const isTablet = useMediaQuery(theme.breakpoints.down(1000)) @@ -21,8 +22,7 @@ export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc=" position: "relative", display: "flex", justifyContent: "space-between", - py: "40px", - px: "20px", + p: "40px 20px 20px 20px", backgroundColor: light ? "#E6E6EB" : "#434657", borderRadius: "12px", ...sx, @@ -33,12 +33,19 @@ export default function WideTemplCard({ light = true, sx, name="PenaDoc", desc=" display: "flex", flexDirection: "column", alignItems: "start", + justifyContent: "space-between" }} > {name} - + {desc} + { }, }) + console.log(location) if (location.state?.redirectTo) return @@ -82,7 +84,6 @@ const App = () => { } /> } /> } /> - )} @@ -92,6 +93,7 @@ const App = () => { } /> } /> } /> + }> }> } /> @@ -110,6 +112,7 @@ const App = () => { }/> + } /> }> }/> }/> diff --git a/src/pages/AccountSettings/DocumentsDialog/JuridicalDocumentsDialog.tsx b/src/pages/AccountSettings/DocumentsDialog/JuridicalDocumentsDialog.tsx index 0d6046e..db705c2 100644 --- a/src/pages/AccountSettings/DocumentsDialog/JuridicalDocumentsDialog.tsx +++ b/src/pages/AccountSettings/DocumentsDialog/JuridicalDocumentsDialog.tsx @@ -28,6 +28,7 @@ export default function JuridicalDocumentsDialog() { const isOpen = useUserStore((state) => state.isDocumentsDialogOpen) const verificationStatus = useUserStore((state) => state.verificationStatus) const documents = useUserStore((state) => state.documents)//загруженные юзером файлы + console.log(documents) const documentsUrl = useUserStore((state) => state.documentsUrl)//ссылки с бекенда const userId = useUserStore((state) => state.userId) ?? "" diff --git a/src/pages/AccountSettings/helper.ts b/src/pages/AccountSettings/helper.ts index 8781e54..27ebaaa 100644 --- a/src/pages/AccountSettings/helper.ts +++ b/src/pages/AccountSettings/helper.ts @@ -48,6 +48,9 @@ export const verify = async (id: string) => { const [verificationResult, verificationError] = await verification(id) if (verificationError) { + + if (verificationError === "нет данных") return + setVerificationStatus(VerificationStatus.NOT_VERIFICATED) devlog("Error fetching user", verificationError) diff --git a/src/pages/ApologyPage.tsx b/src/pages/ApologyPage.tsx new file mode 100644 index 0000000..e6c9474 --- /dev/null +++ b/src/pages/ApologyPage.tsx @@ -0,0 +1,22 @@ +import { Box, Typography } from "@mui/material"; + +export const ApologyPage = ({ message }: { message: string }) => { + return ( + + + {message || "что-то пошло не так"} + + + ); +}; diff --git a/src/pages/Cart/CustomWrapper.tsx b/src/pages/Cart/CustomWrapper.tsx index 968a4a9..4b0e2d8 100644 --- a/src/pages/Cart/CustomWrapper.tsx +++ b/src/pages/Cart/CustomWrapper.tsx @@ -14,7 +14,7 @@ const name: Record = { templategen: "Шаблонизатор", squiz: "Опросник", reducer: "Сокращатель ссылок", - custom: "Кастомные тарифы", + custom: "Мои тарифы", } interface Props { diff --git a/src/pages/History/index.tsx b/src/pages/History/index.tsx index 2eec7f7..2084b0c 100644 --- a/src/pages/History/index.tsx +++ b/src/pages/History/index.tsx @@ -18,7 +18,8 @@ import EmailIcon from '@mui/icons-material/Email'; import {enqueueSnackbar} from "notistack" import { makeRequest } from "@frontend/kitui" -const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"] +const subPages = ["Платежи"] +// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"] export default function History() { const [selectedItem, setSelectedItem] = useState(0) @@ -93,6 +94,7 @@ export default function History() { } onError={handleComponentError} > + {historyData?.length === 0 && Нет данных} {historyData?.filter((e) => { e.createdAt = extractDateFromString(e.createdAt) return(!e.isDeleted && e.key === "payCart" && Array.isArray(e.rawDetails[0].Value) diff --git a/src/pages/Landing/Section5.tsx b/src/pages/Landing/Section5.tsx index 05987ef..30f61e7 100644 --- a/src/pages/Landing/Section5.tsx +++ b/src/pages/Landing/Section5.tsx @@ -47,7 +47,10 @@ export default function Section5() { gap: upMd ? "24px" : "20px", }} > - diff --git a/src/pages/Payment/Payment.tsx b/src/pages/Payment/Payment.tsx index da6732d..bcf7e23 100644 --- a/src/pages/Payment/Payment.tsx +++ b/src/pages/Payment/Payment.tsx @@ -1,241 +1,292 @@ import { - Box, - Button, - IconButton, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material" -import ArrowBackIcon from "@mui/icons-material/ArrowBack" -import SectionWrapper from "@components/SectionWrapper" -import PaymentMethodCard from "./PaymentMethodCard" -import mastercardLogo from "../../assets/bank-logo/logo-mastercard.png" -import visaLogo from "../../assets/bank-logo/logo-visa.png" -import qiwiLogo from "../../assets/bank-logo/logo-qiwi.png" -import mirLogo from "../../assets/bank-logo/logo-mir.png" -import tinkoffLogo from "../../assets/bank-logo/logo-tinkoff.png" -import { cardShadow } from "@root/utils/theme" -import { useEffect, useLayoutEffect, useState } from "react" -import InputTextfield from "@root/components/InputTextfield" -import { sendPayment } from "@root/api/wallet" -import { getMessageFromFetchError } from "@frontend/kitui" -import { enqueueSnackbar } from "notistack" -import { currencyFormatter } from "@root/utils/currencyFormatter" -import { useLocation, useNavigate } from "react-router-dom" -import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker" + Box, + Button, + IconButton, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import SectionWrapper from "@components/SectionWrapper"; +import PaymentMethodCard from "./PaymentMethodCard"; +import mastercardLogo from "@root/assets/bank-logo/logo-mastercard.png"; +import visaLogo from "@root/assets/bank-logo/logo-visa.png"; +import qiwiLogo from "@root/assets/bank-logo/logo-qiwi.png"; +import mirLogo from "@root/assets/bank-logo/logo-mir.png"; +import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png"; +import rsPayLogo from "@root/assets/bank-logo/rs-pay.png"; +import { cardShadow } from "@root/utils/theme"; +import { useEffect, useLayoutEffect, useState } from "react"; +import InputTextfield from "@root/components/InputTextfield"; +import { sendPayment, sendRSPayment } from "@root/api/wallet"; +import { getMessageFromFetchError } from "@frontend/kitui"; +import { enqueueSnackbar } from "notistack"; +import { currencyFormatter } from "@root/utils/currencyFormatter"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"; +import { useUserStore } from "@root/stores/user"; +import { VerificationStatus } from "@root/model/account"; +import { WarnModal } from "./WarnModal"; -const paymentMethods = [ - { name: "Mastercard", image: mastercardLogo }, - { name: "Visa", image: visaLogo }, - { name: "QIWI Кошелек", image: qiwiLogo }, - { name: "Мир", image: mirLogo }, - { name: "Тинькофф", image: tinkoffLogo }, -] as const +type PaymentMethod = { + label: string; + name: string; + image: string; + unpopular?: boolean; +}; -type PaymentMethod = (typeof paymentMethods)[number]["name"]; +const paymentMethods: PaymentMethod[] = [ + { label: "Mastercard", name: "mastercard", image: mastercardLogo }, + { label: "Visa", name: "visa", image: visaLogo }, + { label: "QIWI Кошелек", name: "qiwi", image: qiwiLogo }, + { label: "Мир", name: "mir", image: mirLogo }, + { label: "Тинькофф", name: "tinkoff", image: tinkoffLogo }, +]; + +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 [selectedPaymentMethod, setSelectedPaymentMethod] = - useState(null) - const [paymentValueField, setPaymentValueField] = useState("0") - const [paymentLink, setPaymentLink] = useState("") - const [fromSquiz, setIsFromSquiz] = useState(false) - const location = useLocation() + const [selectedPaymentMethod, setSelectedPaymentMethod] = + useState(null); + const [warnModalOpen, setWarnModalOpen] = 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 notEnoughMoneyAmount = - (location.state?.notEnoughMoneyAmount as number) ?? 0 + const notEnoughMoneyAmount = + (location.state?.notEnoughMoneyAmount as number) ?? 0; - const paymentValue = parseFloat(paymentValueField) * 100 - - useLayoutEffect(() => { - // eslint-disable-next-line react-hooks/exhaustive-deps - setPaymentValueField((notEnoughMoneyAmount / 100).toString()) - const params = new URLSearchParams(window.location.search) - const fromSquiz = params.get("action") - if (fromSquiz === "squizpay") { - setIsFromSquiz(true) - setPaymentValueField(params.get("dif") || "0") - } - console.log(fromSquiz) - }, []) + const paymentValue = parseFloat(paymentValueField) * 100; - useEffect(() => { - setPaymentLink("") - }, [selectedPaymentMethod]) + 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 (Number(paymentValueField) !== 0) { - const [sendPaymentResponse, sendPaymentError] = await sendPayment({fromSquiz}) + useEffect(() => { + setPaymentLink(""); + }, [selectedPaymentMethod]); - if (sendPaymentError) { - return enqueueSnackbar(sendPaymentError) - } + async function handleChoosePaymentClick() { + if (Number(paymentValueField) === 0) { + return; + } - if (sendPaymentResponse) { - setPaymentLink(sendPaymentResponse.link) - } - } - } + if (selectedPaymentMethod !== "rspay") { + const [sendPaymentResponse, sendPaymentError] = await sendPayment({ + fromSquiz, + }); - const handleCustomBackNavigation = useHistoryTracker() + if (sendPaymentError) { + return enqueueSnackbar(sendPaymentError); + } - return ( - - - {!upMd && ( - - - - )} - Способ оплаты - - {!upMd && ( - + if (sendPaymentResponse) { + setPaymentLink(sendPaymentResponse.link); + } + + return; + } + + } + + const handleCustomBackNavigation = useHistoryTracker(); + + return ( + + + {!upMd && ( + + + + )} + Способ оплаты + + {!upMd && ( + Выберите способ оплаты - - )} - - - {paymentMethods.map((method) => ( - setSelectedPaymentMethod(method.name)} - /> - ))} - - - - {upMd && Выберите способ оплаты} - К оплате - {paymentLink ? ( - - {currencyFormatter.format(paymentValue / 100)} - - ) : ( - setPaymentValueField(e.target.value)} - id="payment-amount" - gap={upMd ? "16px" : "10px"} - color={"#F2F3F7"} - FormInputSx={{ mb: "28px" }} - /> - )} - - {paymentLink ? ( - - ) : ( - + ) : ( + - )} - - - - ) + + )} + + + + + ); } diff --git a/src/pages/Payment/PaymentMethodCard.tsx b/src/pages/Payment/PaymentMethodCard.tsx index 2ed39a1..42c53d9 100644 --- a/src/pages/Payment/PaymentMethodCard.tsx +++ b/src/pages/Payment/PaymentMethodCard.tsx @@ -1,43 +1,55 @@ -import { Button, Typography, useMediaQuery, useTheme } from "@mui/material" +import { Button, Typography, useMediaQuery, useTheme } from "@mui/material"; interface Props { - name: string; - image: string; - isSelected?: boolean; - onClick: () => void; + label: string; + image: string; + isSelected?: boolean; + unpopular?: boolean; + onClick: () => void; } -export default function PaymentMethodCard({ name, image, isSelected, onClick }: Props) { - const theme = useTheme() - const upSm = useMediaQuery(theme.breakpoints.up("sm")) +export default function PaymentMethodCard({ + label, + image, + isSelected, + unpopular, + onClick, +}: Props) { + const theme = useTheme(); + const upSm = useMediaQuery(theme.breakpoints.up("sm")); - return ( - - ) + return ( + + ); } diff --git a/src/pages/Payment/WarnModal.tsx b/src/pages/Payment/WarnModal.tsx new file mode 100644 index 0000000..493d4a8 --- /dev/null +++ b/src/pages/Payment/WarnModal.tsx @@ -0,0 +1,61 @@ +import { Modal, Box, Typography, Button, useTheme } from "@mui/material"; +import { useNavigate } from "react-router-dom"; + +type WarnModalProps = { + open: boolean; + setOpen: (isOpen: boolean) => void; +}; + +export const WarnModal = ({ open, setOpen }: WarnModalProps) => { + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + setOpen(false)} + sx={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + + + + Верификация не пройдена. + + + + + + + + + ); +}; diff --git a/src/pages/QuizPayment/QuizPayment.tsx b/src/pages/QuizPayment/QuizPayment.tsx new file mode 100644 index 0000000..e35f2e6 --- /dev/null +++ b/src/pages/QuizPayment/QuizPayment.tsx @@ -0,0 +1,72 @@ +import { useEffect, useState } from "react" +import axios, { AxiosResponse } from "axios" +import { ApologyPage } from "../ApologyPage" +import { useNavigate } from "react-router-dom" +import { clearAuthToken, getMessageFromFetchError, setAuthToken, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui"; +import { clearUserData, setUser, setUserAccount, useUserStore } from "@root/stores/user"; + + +function refresh(token: string) { + return axios>(process.env.REACT_APP_DOMAIN + "/auth/refresh", { + headers: { + "Authorization": "Bearer " + token, + "Content-Type": "application/json", + }, + method: "POST" + }); +} + +const params = new URLSearchParams(window.location.search) +const action = params.get("action") +const dif = params.get("dif") +const token = params.get("data") +const userId = params.get("userid") + + +let first = true + +export default function QuizPayment() { + const navigate = useNavigate() + const [message, setMessage] = useState("Идёт загрузка") + + console.log("Я начал работать") + + if (first) { +history.pushState(null, document.title, "/quizpayment"); + try { + first = false + if (action && dif && token) { + (async () => { + // const data = await refresh(token) + console.log(token) + setAuthToken(token) + // setAuthToken(data.data.accessToken) + console.log("делаем юзера") + + useUserFetcher({ + url: process.env.REACT_APP_DOMAIN + `/user/${userId}`, + userId, + onNewUser: (user) => { + setUser(user) + navigate(`/payment?action=${action}&dif=${dif}`, { replace: true }) + + }, + onError: () => { }, + }) + return + })() + } + } catch (e) { + console.log(e) + setMessage("Произошла ошибка") + var link = document.createElement("a"); + link.href = "https://quiz.pena.digital/tariffs"; + document.body.appendChild(link); + link.click(); + } + } + + return ( + + ) +}; \ No newline at end of file diff --git a/src/pages/TariffConstructor/TariffConstructor.tsx b/src/pages/TariffConstructor/TariffConstructor.tsx index 1c5fa4e..5099084 100644 --- a/src/pages/TariffConstructor/TariffConstructor.tsx +++ b/src/pages/TariffConstructor/TariffConstructor.tsx @@ -44,6 +44,7 @@ function TariffConstructor() { > {Object.entries(customTariffs).filter(([serviceKey]) => serviceKey === "squiz").map(([serviceKey, privileges], index) => { console.log("serviceKey ",serviceKey) + console.log(Object.entries(customTariffs)) return )} - + diff --git a/src/pages/TariffConstructor/TariffItem.tsx b/src/pages/TariffConstructor/TariffItem.tsx index 61b1aa6..b247508 100644 --- a/src/pages/TariffConstructor/TariffItem.tsx +++ b/src/pages/TariffConstructor/TariffItem.tsx @@ -13,11 +13,12 @@ import { useEffect, useState } from "react" import { useDebouncedCallback } from 'use-debounce'; const sliderSettingsByType = { - день: { max: 365, min: 30 }, - шаблон: { max: 5000, min: 100 }, - МБ: { max: 5000, min: 100 }, + день: { max: 365, min: 0 }, + шаблон: { max: 5000, min: 0 }, + МБ: { max: 5000, min: 0 }, + заявка: { max: 5000, min: 0 } } -type PrivilegeName = "день" | "шаблон" | "МБ" +type PrivilegeName = "день" | "шаблон" | "МБ" | "заявка" interface Props { privilege: Privilege; @@ -26,10 +27,10 @@ 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 userValue = useCustomTariffsStore((state) => state.userValuesMap[privilege.serviceKey]?.[privilege._id]) ?? sliderSettingsByType[privilege.value]?.min const discounts = useDiscountStore((state) => state.discounts) const currentCartTotal = useCartStore((state) => state.cart.priceAfterDiscounts) - const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0 + const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? sliderSettingsByType[privilege.value]?.min const isUserNko = useUserStore(state => state.userAccount?.status) === "nko" const [value, setValue] = useState(userValue) const throttledValue = useThrottle(value, 200) @@ -67,10 +68,17 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { const setNotSmallNumber = useDebouncedCallback(() => { - if (value === 0) return + if (value === sliderSettingsByType[privilege.value]?.min) return if (Number(value) < Number(sliderSettingsByType[privilege.value]?.min)) { setValue(sliderSettingsByType[privilege.value]?.min) } + if (privilege.value === "день" && Number(value) < 30 && Number(value) !== 0) { + setValue(30) + } + + if (privilege.value !== "день" && Number(value) < 100 && Number(value) !== 0) { + setValue(100) + } }, 600) const quantityElement = ( @@ -99,6 +107,7 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { { setValue(value) @@ -152,9 +161,10 @@ export default function TariffPrivilegeSlider({ privilege }: Props) { {!upMd && quantityElement} diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index 5d8924e..a74554a 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -85,7 +85,22 @@ export default function Tariffs() { {/*{upMd ? : }*/} - {upMd ? : } + {upMd ? + + : + + } ) } diff --git a/src/pages/Tariffs/TariffsPage.tsx b/src/pages/Tariffs/TariffsPage.tsx index 8ad9513..c19f590 100644 --- a/src/pages/Tariffs/TariffsPage.tsx +++ b/src/pages/Tariffs/TariffsPage.tsx @@ -178,11 +178,11 @@ function TariffPage() { {StepperText[unit]} - {isMobile ? ( + {/* {isMobile ? (