diff --git a/package.json b/package.json index da9e1bf..dae096b 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,10 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@frontend/kitui": "^1.0.82", + "@frontend/kitui": "^1.0.86", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", + "@mui/x-date-pickers": "^7.13.0", "@popperjs/core": "^2.11.8", "axios": "^1.4.0", "buffer": "^6.0.3", @@ -29,6 +30,7 @@ "immer": "^10.0.2", "isomorphic-fetch": "^3.0.0", "js-big-decimal": "^2.0.7", + "moment": "^2.30.1", "notistack": "^3.0.1", "pdfjs-dist": "3.6.172", "react": "^18.2.0", @@ -39,6 +41,7 @@ "react-slick": "^0.29.0", "slick-carousel": "^1.8.1", "swr": "^2.2.5", + "transliteration": "^2.3.5", "use-debounce": "^10.0.0", "web-vitals": "^2.1.0", "yup": "^1.1.1", diff --git a/src/api/cart.ts b/src/api/cart.ts index ed136e4..38c115e 100644 --- a/src/api/cart.ts +++ b/src/api/cart.ts @@ -1,9 +1,10 @@ import { UserAccount } from "@frontend/kitui"; import makeRequest from "@api/makeRequest"; +import Cart from "@root/pages/Cart/Cart"; import { parseAxiosError } from "@root/utils/parse-error"; -const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`; +const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`; export const patchCart = async ( tariffId: string diff --git a/src/api/history.ts b/src/api/history.ts index d44846f..99857ad 100644 --- a/src/api/history.ts +++ b/src/api/history.ts @@ -44,7 +44,7 @@ export type RawDetails = { Value: string | number | KeyValue[][]; }; -const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`; +const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`; export const getHistory = async (): Promise< [GetHistoryResponse | GetHistoryResponse2 | null, string?] diff --git a/src/api/makeRequest.ts b/src/api/makeRequest.ts index a356f5c..613e2df 100644 --- a/src/api/makeRequest.ts +++ b/src/api/makeRequest.ts @@ -5,7 +5,7 @@ import { clearUserData } from "@root/stores/user"; import { clearCustomTariffs } from "@root/stores/customTariffs"; import { clearTickets } from "@root/stores/tickets"; import { redirect } from "react-router-dom"; -import { setNotEnoughMoneyAmount } from "@stores/cart"; +import { setNotEnoughMoneyAmount } from "@stores/notEnoughMoneyAmount"; interface MakeRequest { method?: Method | undefined; diff --git a/src/api/recentlyPurchasedTariffs.ts b/src/api/recentlyPurchasedTariffs.ts index d154faa..39e9592 100644 --- a/src/api/recentlyPurchasedTariffs.ts +++ b/src/api/recentlyPurchasedTariffs.ts @@ -1,7 +1,7 @@ import makeRequest from "@api/makeRequest"; import { parseAxiosError } from "@root/utils/parse-error"; -const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`; +const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`; type GetRecentlyPurchasedTariffsResponse = { id: string; diff --git a/src/api/tariff.ts b/src/api/tariff.ts index a4f9e3e..1f58944 100644 --- a/src/api/tariff.ts +++ b/src/api/tariff.ts @@ -8,6 +8,7 @@ import type { } from "@root/model/privilege"; import type { GetTariffsResponse } from "@root/model/tariff"; import { removeTariffFromCart } from "@root/stores/user"; +import axios from "axios"; interface CreateTariffBody { name: string; @@ -134,3 +135,24 @@ export const getTariffArray = async (tariffIds: string[] | undefined) => { return tariffs; }; + +const apiUrl = process.env.REACT_APP_DOMAIN + "/requestquiz"; +export async function sendContactFormRequest(body: { + contact: string; + whoami: string; +}) { +try { + const a = await axios(apiUrl + "/callme", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + data: body, +}); +return [a]; +} catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка при отправке запроса. ${error}`]; +} +} \ No newline at end of file diff --git a/src/api/verification.ts b/src/api/verification.ts index c3d7cbe..712e12b 100644 --- a/src/api/verification.ts +++ b/src/api/verification.ts @@ -55,7 +55,7 @@ export const sendDocuments = async ( const sendDocumentsResponse = await makeRequest({ method: "POST", url: API_URL, - body: jsonToFormdata({ ...documents, egrule: documents.inn }), + body: jsonToFormdata({ ...documents}), useToken: true, withCredentials: true, }); @@ -72,11 +72,23 @@ export const updateDocuments = async ( documents: UpdateDocumentsArgs ): Promise<[Verification | "OK" | null, string?]> => { try { + // .replace(/\s/g, '_') + // if (documents.inn) { + // documents.inn.append("name", "blob"); + // } + // if (documents.rule) { + // documents.rule.append("name", "blob"); + // } + // if (documents.certificate) { + // documents.certificate.append("name", "blob"); + // } + console.log("documents") + console.log(documents) const updateDocumentsResponse = await makeRequest({ method: "PUT", url: `${API_URL}`, body: jsonToFormdata( - documents.inn ? { ...documents, egrule: documents.inn } : documents + documents.inn ? { ...documents} : documents ), useToken: true, withCredentials: true, @@ -93,6 +105,8 @@ export const updateDocuments = async ( export const updateDocument = async ( body: FormData ): Promise<[Verification | "OK" | null, string?]> => { + console.log("body") + console.log(body) try { const updateDocumentResponse = await makeRequest({ method: "PATCH", diff --git a/src/api/wallet.ts b/src/api/wallet.ts index dd25e4b..73f903d 100644 --- a/src/api/wallet.ts +++ b/src/api/wallet.ts @@ -6,8 +6,12 @@ const isStaging = (() => { const host = window.location.hostname; return host.includes("s") ? "s" : ""; })(); +const isLocalhost = (() => { + const host = window.location.hostname; + return host.includes("localhost"); +})(); -const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0`; +const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1`; interface PaymentBody { type: string; @@ -19,12 +23,20 @@ export const sendPayment = async ({ body, fromSquiz, paymentPurpose, + cc }: { userId: string; body: PaymentBody; fromSquiz: boolean; paymentPurpose: "paycart" | "replenishwallet"; + cc?: boolean }): Promise<[SendPaymentResponse | null, string?]> => { + + let returnLink = `${isLocalhost ? "localhost:3000" : `https://${isStaging}hub.pena.digital`}/afterpay?from=${ + fromSquiz ? "quiz" : "hub" + }&purpose=${paymentPurpose}&userid=${userId}` + if (cc) returnLink = returnLink + "&cc=true" + const reqeustBody = { currency: "RUB", bankCard: { @@ -36,9 +48,7 @@ export const sendPayment = async ({ }, phoneNumber: "79000000000", login: "login_test", - returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${ - fromSquiz ? "quiz" : "hub" - }&purpose=${paymentPurpose}&userid=${userId}`, + returnUrl: returnLink, ...body, }; diff --git a/src/assets/Icons/CloseIcon.tsx b/src/assets/Icons/CloseIcon.tsx new file mode 100644 index 0000000..7b1bd62 --- /dev/null +++ b/src/assets/Icons/CloseIcon.tsx @@ -0,0 +1,38 @@ +import { useLocation } from "react-router-dom"; +import { Box, SxProps, Theme } from "@mui/material"; + +interface Props { + sx?: SxProps; +} +export default function CloseIcon({ sx }: Props) { + const location = useLocation(); + return ( + + + + + + ); +} diff --git a/src/assets/Icons/NotebookWithPencil copy.tsx b/src/assets/Icons/NotebookWithPencil copy.tsx new file mode 100644 index 0000000..c4581d1 --- /dev/null +++ b/src/assets/Icons/NotebookWithPencil copy.tsx @@ -0,0 +1,22 @@ +import { useLocation } from "react-router-dom"; +import { Box, SxProps, Theme } from "@mui/material"; + +interface Props { + sx?: SxProps; +} +export default function NotebookWithPencil({ sx }: Props) { + return ( + + + + + + + + ); +} \ No newline at end of file diff --git a/src/assets/Icons/NotebookWithPencil.tsx b/src/assets/Icons/NotebookWithPencil.tsx new file mode 100644 index 0000000..ca0c440 --- /dev/null +++ b/src/assets/Icons/NotebookWithPencil.tsx @@ -0,0 +1,20 @@ +import { useLocation } from "react-router-dom"; +import { Box, SxProps, Theme } from "@mui/material"; + +interface Props { + sx?: SxProps; +} +export default function CloseIcon({ sx }: Props) { + const location = useLocation(); + return ( + + + + + + + + ); +} \ No newline at end of file diff --git a/src/components/CustomTextField.tsx b/src/components/CustomTextField.tsx new file mode 100755 index 0000000..5090f98 --- /dev/null +++ b/src/components/CustomTextField.tsx @@ -0,0 +1,152 @@ +import type { ChangeEvent, FocusEvent, KeyboardEvent } from "react"; +import React, { useEffect, useState } from "react"; +import type { InputProps, SxProps, Theme } from "@mui/material"; +import { + Box, + FormControl, + Input, + InputLabel, + Typography, + useTheme, +} from "@mui/material"; + +interface CustomTextFieldProps { + placeholder: string; + id?: string; + value?: string; + error?: string; + emptyError?: boolean; + onChange?: (event: ChangeEvent) => void; + onKeyDown?: (event: KeyboardEvent) => void; + onBlur?: (event: FocusEvent) => void; + text?: string; + maxLength?: number; + sx?: SxProps; + sxForm?: SxProps; + InputProps?: Partial; + type?: string; + rows?: number; + className?: string; + disabled?: boolean; +} + +export default function CustomTextField({ + placeholder, + id, + value = "", + onChange, + onKeyDown, + onBlur, + text, + sx, + error, + emptyError, + InputProps, + maxLength = 200, + type = "", + rows = 0, + sxForm, + className, + disabled, +}: CustomTextFieldProps) { + const theme = useTheme(); + + const [inputValue, setInputValue] = useState(""); + const [isInputActive, setIsInputActive] = useState(false); + + useEffect(() => { + setInputValue(value); + }, [value]); + + const handleInputChange = (event: React.ChangeEvent) => { + if (event.target.value.length <= maxLength) { + const inputValue = event.target.value; + + if (type === "number") { + setInputValue(inputValue.replace(/\D/g, "")); + } else { + setInputValue(inputValue); + } + + if (onChange) { + onChange(event); + } + } + }; + + const handleInputFocus = () => { + setIsInputActive(true); + }; + + const handleInputBlur = (event: React.FocusEvent) => { + setIsInputActive(false); + + if (onBlur) { + onBlur(event); + } + }; + + return ( + + {error && ( + + {error} + + )} + 0} + rows={rows} + disabled={disabled} + disableUnderline + sx={{ + maxLength: maxLength, + borderRadius: "10px", + fontSize: "18px", + lineHeight: "21px", + p: "13px", + border: `${isInputActive ? "black 2px" : "#9A9AAF 1px"} solid`, + backgroundColor: theme.palette.background.default, + height: "48px", + ...sx, + }} + data-cy="textfield" + /> + {isInputActive && inputValue.length >= maxLength - 7 && ( + + {inputValue.length} + / + {maxLength} + + )} + + ); +} diff --git a/src/components/CustomWrapperDrawer.tsx b/src/components/CustomWrapperDrawer.tsx index 642b204..118a13e 100644 --- a/src/components/CustomWrapperDrawer.tsx +++ b/src/components/CustomWrapperDrawer.tsx @@ -13,7 +13,7 @@ import { ReactComponent as CrossIcon } from "@root/assets/Icons/cross.svg"; import type { MouseEvent } from "react"; import CustomTariffAccordion from "@root/components/CustomTariffAccordion"; -import { setNotEnoughMoneyAmount } from "@root/stores/cart"; +import { setNotEnoughMoneyAmount } from "@root/stores/notEnoughMoneyAmount"; const name: Record = { templategen: "Шаблонизатор", diff --git a/src/components/Drawers.tsx b/src/components/Drawers.tsx index 0a94792..042ea4a 100644 --- a/src/components/Drawers.tsx +++ b/src/components/Drawers.tsx @@ -19,8 +19,8 @@ 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"; -import { setNotEnoughMoneyAmount, useCartStore } from "@root/stores/cart"; -import { useDiffMoney } from "@root/stores/diffMoney"; +import { setNotEnoughMoneyAmount, startPayCartProcess, useNotEnoughMoneyAmount } from "@root/stores/notEnoughMoneyAmount"; +import { RSCOpen } from "@root/stores/requestSquizCreate"; function Drawers() { const [openNotificationsModal, setOpenNotificationsModal] = useState(false); @@ -34,9 +34,10 @@ function Drawers() { const cart = useCart(); console.log("боковой cart", cart.priceAfterDiscounts) const userAccount = useUserStore((state) => state.userAccount); + const userId = useUserStore((state) => state.userId) || ""; const tickets = useTicketStore((state) => state.tickets); - const notEnoughMoneyAmount = useCartStore(state => state.notEnoughMoneyAmount); - const { setNewDiff } = useDiffMoney() + const notEnoughMoneyAmount = useNotEnoughMoneyAmount(state => state.notEnoughMoneyAmount); + const siteReadyPayCart = useNotEnoughMoneyAmount(state => state.siteReadyPayCart) const notificationsCount = tickets.filter( ({ user, top_message }) => user !== top_message.user_id && top_message.shown.me !== 1 @@ -45,12 +46,15 @@ function Drawers() { async function handlePayClick() { setLoading(true); + const isCC = cart.services.length > 0 && cart.services[0].tariffs.some(t => t.privileges[0].privilegeId === "quizManual") + const [payCartResponse, payCartError] = await payCart(); if (payCartError) { if (payCartError.includes("insufficient funds: ")) { const notEnoughMoneyAmount = parseInt(payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, "")); setNotEnoughMoneyAmount(notEnoughMoneyAmount); + startPayCartProcess(userId) } setLoading(false); @@ -60,6 +64,7 @@ function Drawers() { } if (payCartResponse) { + if (isCC) RSCOpen() setUserAccount(payCartResponse); } @@ -69,10 +74,8 @@ function Drawers() { function handleReplenishWallet() { setIsDrawerOpen(false); - if (location.pathname.includes("/payment")) { - setNewDiff(notEnoughMoneyAmount) - } - navigate("/payment", { state: { notEnoughMoneyAmount } }); + if (siteReadyPayCart === null) startPayCartProcess(userId) + navigate("/payment"); } return ( @@ -180,7 +183,6 @@ function Drawers() { { setIsDrawerOpen(false) - setNotEnoughMoneyAmount(0) }} sx={{ background: "rgba(0, 0, 0, 0.55)" }}> - После нажатия кнопки оплатить, вы будете перенаправлены на форму оплаты, для оплаты ВСЕЙ корзины (рекомендуем перед оплатой, убрать все лишнее) + После нажатия кнопки оплатить (пополнить), вы будете перенаправлены на форму оплаты, для оплаты ВСЕЙ корзины (рекомендуем перед оплатой, убрать все лишнее) онлайн-консультант - - время работы 10:00-3:00 по мск - + + + + + + + ); +} diff --git a/src/components/NavbarLanding/NavbarFull.tsx b/src/components/NavbarLanding/NavbarFull.tsx index ebfd2be..3d214bb 100644 --- a/src/components/NavbarLanding/NavbarFull.tsx +++ b/src/components/NavbarLanding/NavbarFull.tsx @@ -28,7 +28,7 @@ import { import { currencyFormatter } from "@root/utils/currencyFormatter"; import { clearCustomTariffs } from "@root/stores/customTariffs"; import { clearTickets } from "@root/stores/tickets"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import {setNotEnoughMoneyAmount} from "@stores/notEnoughMoneyAmount" interface Props { isLoggedIn: boolean; diff --git a/src/components/NavbarSite/DialogMenu.tsx b/src/components/NavbarSite/DialogMenu.tsx index bee1989..45dc74f 100644 --- a/src/components/NavbarSite/DialogMenu.tsx +++ b/src/components/NavbarSite/DialogMenu.tsx @@ -17,7 +17,7 @@ import { logout } from "@root/api/auth"; import { enqueueSnackbar } from "notistack"; import { clearCustomTariffs } from "@root/stores/customTariffs"; import { clearTickets } from "@root/stores/tickets"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import {setNotEnoughMoneyAmount} from "@stores/notEnoughMoneyAmount" type MenuItem = { name: string; diff --git a/src/components/NavbarSite/NavbarFull.tsx b/src/components/NavbarSite/NavbarFull.tsx index 67bb46e..adab65d 100644 --- a/src/components/NavbarSite/NavbarFull.tsx +++ b/src/components/NavbarSite/NavbarFull.tsx @@ -17,7 +17,7 @@ import { currencyFormatter } from "@root/utils/currencyFormatter"; import { clearTickets } from "@root/stores/tickets"; import type { ReactNode } from "react"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import {setNotEnoughMoneyAmount} from "@stores/notEnoughMoneyAmount" interface Props { children: ReactNode; diff --git a/src/components/NavbarSite/NavbarPanel.tsx b/src/components/NavbarSite/NavbarPanel.tsx index 2627163..6a34df0 100644 --- a/src/components/NavbarSite/NavbarPanel.tsx +++ b/src/components/NavbarSite/NavbarPanel.tsx @@ -18,7 +18,7 @@ import { clearTickets } from "@root/stores/tickets"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import walletIcon from "@root/assets/Icons/wallet_icon.svg"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import {setNotEnoughMoneyAmount} from "@stores/notEnoughMoneyAmount" export const NavbarPanel = () => { const navigate = useNavigate(); diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index f24a1bf..ee461a1 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -72,10 +72,10 @@ export const Select = ({ items, selectedItem, setSelectedItem }: SelectProps) => className="select" value="" open={opened} + onClick={() => setOpened((isOpened) => !isOpened)} MenuProps={{ disablePortal: true }} sx={{ width: "100%",zIndex: 1, }} onChange={selectItem} - onClick={() => setOpened((isOpened) => !isOpened)} > {items.map((item, index) => ( diff --git a/src/components/TotalPrice.tsx b/src/components/TotalPrice.tsx index 3d2e3f9..a5a5c71 100644 --- a/src/components/TotalPrice.tsx +++ b/src/components/TotalPrice.tsx @@ -7,8 +7,10 @@ import { Loader } from "./Loader"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import { payCart } from "@root/api/cart"; -import { setUserAccount } from "@root/stores/user"; -import { setNotEnoughMoneyAmount, useCartStore } from "@root/stores/cart"; +import { setUserAccount, useUserStore } from "@root/stores/user"; +import { setNotEnoughMoneyAmount, startPayCartProcess, useNotEnoughMoneyAmount } from "@root/stores/notEnoughMoneyAmount"; +import { RSCOpen } from "@root/stores/requestSquizCreate"; +import { useCart } from "@root/utils/hooks/useCart"; interface Props { priceBeforeDiscounts: number; @@ -19,13 +21,16 @@ interface Props { export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts, isConstructor = false }: Props) { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); - const notEnoughMoneyAmount = useCartStore(state => state.notEnoughMoneyAmount); + const notEnoughMoneyAmount = useNotEnoughMoneyAmount(state => state.notEnoughMoneyAmount); + const userId = useUserStore(store => store.userId) || "" const [loading, setLoading] = useState(false); const navigate = useNavigate(); + const cart = useCart(); async function handlePayClick() { setLoading(true); + const isCC = cart.services.length > 0 && cart.services[0].tariffs.some(t => t.privileges[0].privilegeId === "quizManual") const [payCartResponse, payCartError] = await payCart(); if (payCartError) { @@ -41,6 +46,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts, } if (payCartResponse) { + if (isCC) RSCOpen() setUserAccount(payCartResponse); } @@ -48,6 +54,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts, } function handleReplenishWallet() { + startPayCartProcess(userId) navigate("/payment", { state: { notEnoughMoneyAmount } }); } diff --git a/src/index.tsx b/src/index.tsx index b0c6e67..ac0895d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, @@ -9,6 +9,7 @@ import { useNavigate, } from "react-router-dom"; import { CssBaseline, ThemeProvider } from "@mui/material"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; import Faq from "./pages/Faq/Faq"; import Wallet from "./pages/Wallet"; import Payment from "./pages/Payment/Payment"; @@ -35,6 +36,8 @@ import { setUser, setUserAccount, useUserStore, + OriginalUserSquizAccount, + setQuizUserAccount } from "./stores/user"; import TariffConstructor from "./pages/TariffConstructor/TariffConstructor"; import { @@ -44,6 +47,7 @@ import { useUserFetcher, } from "@frontend/kitui"; import { pdfjs } from "react-pdf"; +import { ruRU } from "@mui/x-date-pickers/locales"; import { theme } from "./utils/theme"; import PPofData from "@root/docs/PPofData"; import Docs from "@root/docs/docs"; @@ -55,15 +59,22 @@ import OutdatedLink from "@root/pages/auth/OutdatedLink"; import { verify } from "./pages/AccountSettings/helper"; import AfterPay from "./pages/AfterPay"; import { PageNotFound } from "./pages/PageNotFound"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import { useNotEnoughMoneyAmount } from "@stores/notEnoughMoneyAmount" +import { usePipeSubscriber } from "./utils/hooks/usePipeSubscriber"; +import { useAfterPay } from "./utils/hooks/useAutoPay"; +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { ModalRequestCreate } from "./pages/Tariffs/ModalRequestCreate"; +import * as crutch from "./useUserAccountFetcher"; +import { useCart } from "./utils/hooks/useCart"; pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`; +const localeText = + ruRU.components.MuiLocalizationProvider.defaultProps.localeText; const App = () => { const location = useLocation(); - const userId = useUserStore((state) => state.userId); + const userId = useUserStore(state => state.userId); const navigate = useNavigate(); - if(location.pathname !== "/cart"){setNotEnoughMoneyAmount(0)} useUserFetcher({ url: process.env.REACT_APP_DOMAIN + `/user/${userId}`, userId, @@ -79,7 +90,7 @@ const App = () => { }); useUserAccountFetcher({ - url: process.env.REACT_APP_DOMAIN + "/customer/v1.0.0/account", + url: process.env.REACT_APP_DOMAIN + "/customer/v1.0.1/account", userId, onNewUserAccount: setUserAccount, onError: (error) => { @@ -93,8 +104,32 @@ const App = () => { }, }); + crutch.useUserAccountFetcher({ + url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`, + userId, + onNewUserAccount: setQuizUserAccount, + onError: (error) => { + const errorMessage = getMessageFromFetchError(error); + if (errorMessage) { + enqueueSnackbar(errorMessage); + clearUserData(); + clearAuthToken(); + navigate("/signin"); + } + }, + }); + + usePipeSubscriber() + verify(userId); + useAfterPay() + +console.log("location") +console.log(location) +console.log("window.location") +console.log(window.location) + if (location.state?.redirectTo) return ( { } /> )} + + + } /> { to="/" replace state={{ - redirectTo: window.location.pathname + window.location.search, + redirectTo: `/changepwd${location.search}`, }} /> } @@ -202,11 +240,17 @@ const root = ReactDOM.createRoot( root.render( // - - - - - + + + + + + + // ); diff --git a/src/model/auth.ts b/src/model/auth.ts index 0050435..fdcbdd6 100644 --- a/src/model/auth.ts +++ b/src/model/auth.ts @@ -1,4 +1,5 @@ import type { Attachment } from "@root/model/attachment" +import { UserAccount } from "@frontend/kitui" export type File = { name: "inn" | "rule" | "egrule" | "certificate"; @@ -8,21 +9,21 @@ export type File = { export interface Verification { _id: string; accepted: boolean; - status: "org" | "nko"; + status: UserAccount["status"]; updated_at: string; comment: string; files: File[]; } export type SendDocumentsArgs = { - status: "org" | "nko"; + status: UserAccount["status"]; inn: Attachment; rule: Attachment; certificate?: Attachment; }; export type UpdateDocumentsArgs = { - status: "org" | "nko"; + status: UserAccount["status"]; inn?: Attachment; rule?: Attachment; certificate?: Attachment; diff --git a/src/pages/AccountSettings/AccountSettings.tsx b/src/pages/AccountSettings/AccountSettings.tsx index 10a3649..567ee66 100644 --- a/src/pages/AccountSettings/AccountSettings.tsx +++ b/src/pages/AccountSettings/AccountSettings.tsx @@ -33,6 +33,8 @@ function AccountSettings() { const comment = useUserStore((state) => state.comment) const userId = useUserStore((state) => state.userId) ?? "" + console.log(user) + const [onChangeTypeLP, setOnChangeTypeLP] = useState("") const [readySend, setReadySend] = useState(false) @@ -51,10 +53,10 @@ function AccountSettings() { const type = onChangeTypeLP setOnChangeTypeLP("") setReadySend(false) - if (type === "email" && user?.email !== settingsFields.email.value) setSettingsField("email", user?.email || "") + if (type === "email" && user?.email !== settingsFields.email.value) setSettingsField("email", user?.email || user?.login || "") if (type === "password") setSettingsField("password", "") if (type === "all") { - setSettingsField("email", user?.email || "") + setSettingsField("email", user?.email || user?.login || "") setSettingsField("password", "") } } diff --git a/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx b/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx index 6ee36ef..f03b57d 100644 --- a/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx +++ b/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx @@ -63,10 +63,8 @@ export default function DocumentItem({ useEffect(() => { if (typeof documentUrl === 'string') { if (!documentUrl.includes("pena.digital")) { - console.log(documentUrl) fetch(documentUrl) .then(e => { - console.log(e) setReadyShowDocument(true) }) .catch(e => console.log(e)) diff --git a/src/pages/AccountSettings/DocumentsDialog/DocumentUploadItem.tsx b/src/pages/AccountSettings/DocumentsDialog/DocumentUploadItem.tsx index 5572813..3687f90 100644 --- a/src/pages/AccountSettings/DocumentsDialog/DocumentUploadItem.tsx +++ b/src/pages/AccountSettings/DocumentsDialog/DocumentUploadItem.tsx @@ -43,10 +43,8 @@ export default function DocumentUploadItem({ useEffect(() => { if (typeof urlOrFile === 'string') { if (!urlOrFile.includes("pena.digital")) { - console.log(documentUrl) fetch(documentUrl) .then(e => { - console.log(e) setReadyShowDocument(true) }) .catch(e => console.log(e)) diff --git a/src/pages/AccountSettings/UserFields.tsx b/src/pages/AccountSettings/UserFields.tsx index 946d69d..96287e3 100644 --- a/src/pages/AccountSettings/UserFields.tsx +++ b/src/pages/AccountSettings/UserFields.tsx @@ -19,7 +19,7 @@ export default function UserFields({ const upMd = useMediaQuery(theme.breakpoints.up("md")) const { settingsFields, user } = useUserStore((state) => state) - + const a = useUserStore((state) => state) const textFieldProps = { gap: upMd ? "16px" : "10px", diff --git a/src/pages/AfterPay/index.tsx b/src/pages/AfterPay/index.tsx index cc827c6..8f64e5f 100644 --- a/src/pages/AfterPay/index.tsx +++ b/src/pages/AfterPay/index.tsx @@ -1,135 +1,28 @@ -import { useState, useEffect } from "react"; -import { - Box, - Button, - Typography, - useTheme, - useMediaQuery, -} from "@mui/material"; -import { payCart } from "@root/api/cart"; -import { useUserStore } from "@root/stores/user"; -import wallet_icon from "@root/assets/Icons/ColorWallet.svg"; -import { Link, useSearchParams } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; -const MINUTE = 1000 * 60; +//Раньше эта страничка благодарила за покупку и перенаправляла сталкера своей дорогой. +//Чтобы не мелькать хабом перед перенаправлением на другие домены - логика останется на этом отдельном url адресе + +//Эта страничка ТОЛЬКО перенаправляет на нужный адрес. Процесс автопокупки после пополнения лежит на хуке useAutoPay export default () => { - const [redirectUrl, setRedirectUrl] = useState("/"); - const theme = useTheme(); - const phone = useMediaQuery(theme.breakpoints.down(375)); - const userId = useUserStore((state) => state.user?._id); + const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const paymentUserId = searchParams.get("userid"); - useEffect(() => { - const from = searchParams.get("from") || "hub"; - const purpose = searchParams.get("purpose"); + const userId = searchParams.get("userid"); + const from = searchParams.get("from") || "hub"; + const purpose = searchParams.get("purpose") + const isCC = searchParams.get("cc") - if (purpose === "paycart" || from !== "quiz") { - let tryCount = 0; + const host = window.location.hostname; + const domain = (host.includes("s") ? "s" : "") + from; + const pathname = from.includes("hub") ? "/tariffs" : "/list"; - if (userId !== paymentUserId) { - return; - } + let link = `https://${domain}.pena.digital${pathname}?purpose=${purpose}&userid=${userId}` - const payCartPendingRequestDeadline = localStorage.getItem( - "payCartPendingRequestDeadline" - ); - const deadline = payCartPendingRequestDeadline - ? Number(payCartPendingRequestDeadline) - : Date.now() + 20 * MINUTE; + console.log("isCC") + if (isCC) link = link + "&cc=true" - localStorage.setItem( - "payCartPendingRequestDeadline", - deadline.toString() - ); - - tryPayCart(); - - async function tryPayCart() { - tryCount += 1; - - - if (Date.now() > deadline) { - localStorage.removeItem("payCartPendingRequestDeadline"); - return; - } - - const [, payCartError] = await payCart(); - - if (!payCartError) { - localStorage.removeItem("payCartPendingRequestDeadline"); - return; - } - - setTimeout(tryPayCart, tryCount > 5 ? MINUTE : MINUTE / 6); - } - } - - const host = window.location.hostname; - const domain = (host.includes("s") ? "s" : "") + from; - const pathname = from === "hub" ? "/tariffs" : "/list"; - - setRedirectUrl( - `https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}` - ); - }, []); - - return ( - - - - - Спасибо за оплату! - - - Ваш платеж принят и в течение 10 минут товары будут зачислены - - - - - ); + document.location.href = link + return <>; }; diff --git a/src/pages/Landing/Section2.tsx b/src/pages/Landing/Section2.tsx index b2c4e4b..a72a95d 100644 --- a/src/pages/Landing/Section2.tsx +++ b/src/pages/Landing/Section2.tsx @@ -63,7 +63,7 @@ export default function Section2() { {/* {upMd ? : }*/} - {upMd ? + {upMd ? : (""); - 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(); - console.log("location", location); - console.log("location", location.state); - const verificationStatus = useUserStore((state) => state.verificationStatus); - const userId = useUserStore((state) => state.userId); const navigate = useNavigate(); const handleCustomBackNavigation = useHistoryTracker(); - const {diffMoney, setNewDiff} = useDiffMoney() + const userId = useUserStore((state) => state.userId) || ""; + const verificationStatus = useUserStore((state) => state.verificationStatus); - const notEnoughMoneyAmount = - (location.state?.notEnoughMoneyAmount as number) ?? 0; + 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 [selectedPaymentMethod, setSelectedPaymentMethod] = useState(""); + const [paymentValueField, setPaymentValueField] = useState("0"); + + const [fromSquiz, setIsFromSquiz] = useState(false); + const [cc, setCC] = useState(false); + + + const [warnModalOpen, setWarnModalOpen] = useState(false); + const [sorryModalOpen, setSorryModalOpen] = useState(false); + + const notEnoughMoneyAmount = useNotEnoughMoneyAmount(state => state.notEnoughMoneyAmount) + const siteReadyPayCart = useNotEnoughMoneyAmount(state => state.siteReadyPayCart) const paymentValue = parseFloat( bigDecimal.multiply(parseFloat(paymentValueField), 100) ); - useEffect(() => { - console.log(diffMoney) - if (diffMoney > 0) { - setNewDiff(0) - setPaymentValueField((diffMoney / 100).toString()) - } - }, [diffMoney]) + //Отмена состояния сайта "в процессе покупки корзины, просто нехватило деняк" + useEffect(() => { + return () => { + //При выходе со страницы оплаты мы точно не в состоянии покупки корзины + cancelPayCartProcess() + } + }, []) + + //Тут записываем начальную сумму в инпут (если стоит флаг что мы в процессе покупки) + useEffect(() => { + if (siteReadyPayCart !== null && siteReadyPayCart[userId] !== undefined && calcTimeOfReadyPayCart(siteReadyPayCart[userId])) + setPaymentValueField((notEnoughMoneyAmount / 100).toString());//Сколько нехватило на хабе + }, [notEnoughMoneyAmount, siteReadyPayCart]) useLayoutEffect(() => { - setPaymentValueField((notEnoughMoneyAmount / 100).toString()); + //Предустановленное значение - это либо 0, либо сколько нам нехватило на хабе, либо сколько нам нехватило на квизе const params = new URLSearchParams(window.location.search); const fromSquiz = params.get("action"); - if (fromSquiz === "squizpay") { + const userid = params.get("user"); + const currentCC = params.get("cc"); + if (currentCC) setCC(true) + + if (fromSquiz === "squizpay" && userid !== null) { setIsFromSquiz(true); - setPaymentValueField((Number(params.get("dif") || "0") / 100).toString()); } + //Принимаем параметры из странички и очищаем url адрес до красивого состояния navigate(`/payment`, { replace: true, }); - }, [ ]); - - async function handleChoosePaymentClick() { - if (!selectedPaymentMethod) { - enqueueSnackbar("Введите метод оплаты"); - return; - } - + }, []); + //https://shub.pena.digital/quizpayment?action=squizpay&dif=9800&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2ODVhNTc4OTgzZWU3N2Y4ZTFlNjNkYyIsImF1ZCI6InBlbmEiLCJpc3MiOiJwZW5hLWF1dGgtc2VydmljZSIsImlhdCI6MTcyMjIyMjgzMywiZXhwIjoxNzI3NDA2ODMzfQ.My1KJWFk034MiMdImQSlzf5p4Sn5Dhboj2VvPQteh59tD_CwXyPtePEyev3thV_58IbOOgJ5cgeBm0JKn7atgMgRMpNQVdeYKtf6HYvVoAqkrMcT1LHgAlEQ0TcaXssFKCQGuiCVltHY3UE-kQv5TeydBpO3U9BDKvMqRqv5-Xo&userid=6685a578983ee77f8e1e63dc + const handlePaymentClick = () => { if (Number(paymentValueField) === 0) { enqueueSnackbar("Введите сумму"); return; } - - if (selectedPaymentMethod !== "rspay") { - const [sendPaymentResponse, sendPaymentError] = await sendPayment({ - userId: userId ?? "", - fromSquiz, - body: { - type: selectedPaymentMethod, - amount: Number( - bigDecimal.floor( - bigDecimal.multiply(Number(paymentValueField), 100) - ) - ), - }, - paymentPurpose: notEnoughMoneyAmount ? "paycart" : "replenishwallet", - }); - - if (sendPaymentError) { - return enqueueSnackbar(sendPaymentError); - } - - if (sendPaymentResponse) { - setPaymentLink(sendPaymentResponse.link); - } - - return; - } else { - if (verificationStatus !== VerificationStatus.VERIFICATED) { - setWarnModalOpen(true); - - return; - } - + if (selectedPaymentMethod === "rspay") { if (Number(paymentValueField) < 900) { enqueueSnackbar("Минимальная сумма 900р"); - return; } - - const [sendRSPaymentResponse] = await sendRSPayment( - Number(paymentValueField) - ); - - if (sendRSPaymentResponse) { - return enqueueSnackbar(sendRSPaymentResponse); + if (verificationStatus !== VerificationStatus.VERIFICATED) { + setWarnModalOpen(true); + return; } - - enqueueSnackbar( - "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." - ); - - navigate("/settings"); + startPayRS() + } else { + startPayCard() } } + const startPayRS = async () => { + const [sendRSPaymentResponse] = await sendRSPayment( + Number(paymentValueField) + ); + + if (sendRSPaymentResponse) { + return enqueueSnackbar(sendRSPaymentResponse); + } + + enqueueSnackbar( + "Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг." + ); + + navigate("/settings"); + } + + const startPayCard = async () => { + + if (!selectedPaymentMethod) { + enqueueSnackbar("Введите метод оплаты"); + return + } + const [sendPaymentResponse, sendPaymentError] = await sendPayment({ + userId: userId ?? "", + fromSquiz, + body: { + type: selectedPaymentMethod, + amount: Number( + bigDecimal.floor( + bigDecimal.multiply(Number(paymentValueField), 100) + ) + ), + }, + paymentPurpose: notEnoughMoneyAmount ? "paycart" : "replenishwallet", + cc + }); + + if (sendPaymentError) { + return enqueueSnackbar(sendPaymentError); + } + + //Произошёл запрос на пополнение счёта. Нам вернули ссылку для перехода на страницу пополнения. + if (sendPaymentResponse) { + document.location.href = sendPaymentResponse.link; + } + } + + async function handleApplyPromocode() { if (!promocodeField) return; @@ -250,7 +264,6 @@ export default function Payment() { image={image} onClick={() => { setSelectedPaymentMethod(name); - setPaymentLink(""); }} unpopular={false} /> @@ -261,7 +274,6 @@ export default function Payment() { image={rsPayLogo} onClick={async () => { setSelectedPaymentMethod("rspay"); - setPaymentLink(""); }} unpopular={false} /> @@ -297,76 +309,59 @@ export default function Payment() { > {upMd && Выберите способ оплаты} К оплате - {paymentLink ? ( - - {currencyFormatter.format( - Number(bigDecimal.divide(bigDecimal.floor(paymentValue), 100)) - )} - - ) : ( - { - const value = parseFloat( - e.target.value.replace(/^0+(?=\d\.)/, "") - ); - setPaymentValueField(isNaN(value) ? "" : value.toString()); - }} - id="payment-amount" - gap={upMd ? "16px" : "10px"} - color={"#F2F3F7"} - FormInputSx={{ mb: "28px" }} - /> - )} + { + siteReadyPayCart?.[userId] && notEnoughMoneyAmount > 0 ? + + {currencyFormatter.format( + Number(bigDecimal.divide(bigDecimal.floor(paymentValue), 100)) + )} + + : + { + const value = parseFloat( + e.target.value.replace(/^0+(?=\d\.)/, "") + ); + setPaymentValueField(isNaN(value) ? "" : value.toString()); + }} + id="payment-amount" + gap={upMd ? "16px" : "10px"} + color={"#F2F3F7"} + FormInputSx={{ mb: "28px" }} + /> + } + - {paymentLink ? ( - - ) : ( - - )} + diff --git a/src/pages/QuizPayment/QuizPayment.tsx b/src/pages/QuizPayment/QuizPayment.tsx index db853eb..08f6fab 100644 --- a/src/pages/QuizPayment/QuizPayment.tsx +++ b/src/pages/QuizPayment/QuizPayment.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { ApologyPage } from "../ApologyPage"; -import { useNavigate } from "react-router-dom"; +import { redirect, useNavigate } from "react-router-dom"; import { clearAuthToken, getMessageFromFetchError, @@ -19,13 +19,14 @@ import { import { logout } from "@root/api/auth"; import { clearCustomTariffs } from "@root/stores/customTariffs"; import { clearTickets } from "@root/stores/tickets"; -import {setNotEnoughMoneyAmount} from "@stores/cart" +import { setNotEnoughMoneyAmount, startPayCartProcess } from "@stores/notEnoughMoneyAmount" 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 action = params.get("action"); +let dif = params.get("dif"); +let token = params.get("data"); +let userId = params.get("userid"); +let currentCC = params.get("cc"); let first = true; @@ -33,15 +34,23 @@ export default function QuizPayment() { const navigate = useNavigate(); const [message, setMessage] = useState("Идёт загрузка"); const user = useUserStore((state) => state.user); + const currentUserId = useUserStore((state) => state.user?._id); + const fromSquiz = params.get("action"); - useEffect( - function redirectIfSignedIn() { - if (!first && user?._id === userId) - navigate(`/payment?action=${action}&dif=${dif}&user=${userId}`, { + useEffect(() => { + if (!first && userId !== null && currentUserId === userId) { + if (fromSquiz === "squizpay") { + setNotEnoughMoneyAmount(Number(params.get("dif") || 0));//Сколько нехватило на квизе + startPayCartProcess(userId) + let link = `/payment?action=${action}&dif=${dif}&user=${userId}` + if (currentCC) link = link + "&cc=true" + navigate(link, { replace: true, }); - }, - [navigate, user] + } + } + }, + [currentUserId] ); @@ -64,24 +73,13 @@ export default function QuizPayment() { clearCustomTariffs(); clearTickets(); setNotEnoughMoneyAmount(0) - await logout(); + logout(); } + + setUserId(userId); setAuthToken(token); - // setAuthToken(data.data.accessToken) - setUserId(userId); - - // useUserFetcher({ - // url: process.env.REACT_APP_DOMAIN + `/user/${userId}`, - // userId, - // onNewUser: (user) => { - // setUser(user) - // navigate(`/payment?action=${action}&dif=${dif}`, { replace: true }) - - // }, - // onError: () => { }, - // }) return; })(); } else { diff --git a/src/pages/TariffConstructor/TariffConstructor.tsx b/src/pages/TariffConstructor/TariffConstructor.tsx index 95ead4e..058a6e2 100644 --- a/src/pages/TariffConstructor/TariffConstructor.tsx +++ b/src/pages/TariffConstructor/TariffConstructor.tsx @@ -42,8 +42,6 @@ function TariffConstructor() { }} > {Object.entries(customTariffs).filter(([serviceKey]) => serviceKey === "squiz").map(([serviceKey, privileges], index) => { - console.log("privileges") - console.log(privileges) return ( { if (Number(value) < Number(sliderSettingsByType[measurement]?.min)) { diff --git a/src/pages/Tariffs/ModalRequestCreate.tsx b/src/pages/Tariffs/ModalRequestCreate.tsx new file mode 100644 index 0000000..1e081ba --- /dev/null +++ b/src/pages/Tariffs/ModalRequestCreate.tsx @@ -0,0 +1,329 @@ +import { Box, Button, IconButton, Modal, Typography, useMediaQuery, useTheme } from "@mui/material" + +import { Form, Formik, useFormik } from "formik" +import CustomTextField from "@components/CustomTextField" +import InfoButton from "@components/InfoButton"; +import { sendContactFormRequest } from "@api/tariff" +import { TimePicker } from "@mui/x-date-pickers" +import moment, { Moment } from "moment" +import { enqueueSnackbar } from "notistack" +import { useState } from "react" +import CloseIcon from "@root/assets/Icons/CloseIcon"; +import { useRequestSquizCreate, RSCClose } from "@root/stores/requestSquizCreate" + + +interface Values { + contact: string; + dogiebusiness: string; + imagination: string; + name: string; + time?: Moment | null; +} +const initialValues: Values = { + contact: "",//phone number + // whoami: {}, + dogiebusiness: "", + imagination: "", + name: "", + time: null +}; + +interface FP { + title: string + desc?: string + placeholder: string + value: string + onChange: any + rows?: number +} + +const Field = ({ + title, + desc, + placeholder, + value, + onChange, + rows, +}: FP) => { + return ( + + {title} + {desc && {desc}} + + + ) +} + +export const ModalRequestCreate = () => { + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down(650)); + const [isSending, setIsSending] = useState(false) + const open = useRequestSquizCreate(store => store.isRSCOpen) + console.log(open) + + if (isSending) return ( + { + RSCClose() + setIsSending(false) + }} + + > + + Спасибо за заявку! + С вами свяжутся в течение 24 часов + + + + + ) + return ( + + + + + + Заполните форму, чтобы оставить заявку на создание квиза + + + + + + + + { + if (values.contact.length < 8) return enqueueSnackbar("Пожалуйста, оставьте контактные данные") + const resp = await sendContactFormRequest({ + contact: values.contact, + whoami: JSON.stringify({ + dogiebusiness: values.dogiebusiness, + imagination: values.imagination, + name: values.name, + time: moment(values.time).format("hh:mm") + }) + }) + //@ts-ignore + if (resp[0]?.status === 200) { + enqueueSnackbar("Запрос успешно отправлен") + setIsSending(true) + } + if (resp[1]) { + //@ts-ignore + enqueueSnackbar(resp[1]) + } + }} + > + {({ values, isSubmitting, setFieldValue }) => (<> +
+ setFieldValue("name", target.value)} + + /> + setFieldValue("dogiebusiness", target.value)} + + /> + setFieldValue("contact", target.value)} + desc="(Telegram, WhatsApp, номер телефона)" + /> + setFieldValue("imagination", target.value)} + rows={2} + /> + + + Во сколько вам можно позвонить? + Москва (GMT+3) + + setFieldValue("time", e)} + ampm={false} + value={values.time} + views={['hours', 'minutes']} format="hh:mm" + sx={{ + border: "#c4c4c4 1px solid", + borderRadius: "4px", + "& .MuiInputBase-root": { + display: "flex" + } + }} + /> + + + + + + + + + )} +
+
+
+
+ ) +} diff --git a/src/pages/Tariffs/TariffCard.tsx b/src/pages/Tariffs/TariffCard.tsx index a5ba727..78195b3 100644 --- a/src/pages/Tariffs/TariffCard.tsx +++ b/src/pages/Tariffs/TariffCard.tsx @@ -1,7 +1,8 @@ -import { Box, Typography, Tooltip, SxProps, Theme, Button, Badge, useTheme, useMediaQuery, } from "@mui/material"; +import { Box, Typography, Tooltip, SxProps, Theme, Button, Badge, useTheme, useMediaQuery, IconButton, } from "@mui/material"; import { MouseEventHandler, ReactNode } from "react"; import { cardShadow } from "@root/utils/theme"; import { relative } from "path"; +import NotebookWithPencil from "@root/assets/Icons/NotebookWithPencil copy"; interface Props { icon: ReactNode; @@ -15,11 +16,12 @@ interface Props { text?: string; }; price?: ReactNode; + sendRequestToCreate?: () => void; } -export default function TariffCard({ icon, headerText, text, sx, price, buttonProps, discount }: Props) { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(600)); +export default function TariffCard({ icon, headerText, text, sx, price, buttonProps, discount, sendRequestToCreate }: Props) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); return ( )} - {/* {headerText}} placement="top"> */} - - {headerText} - + {/* {headerText}} placement="top"> */} + + {headerText} + {/* ( @@ -100,29 +102,67 @@ export default function TariffCard({ icon, headerText, text, sx, price, buttonPr ))} placement="top" > */} - - - {text} - - + + + {text} + + {/* */} - {buttonProps && ( - - )} + + {buttonProps && ( + + )} + {Boolean(sendRequestToCreate) && ( + + {isMobile ? + + + + : + + } + + )} +
); } diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index a74554a..bb8dda2 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -89,14 +89,14 @@ export default function Tariffs() { : diff --git a/src/pages/Tariffs/TariffsPage.tsx b/src/pages/Tariffs/TariffsPage.tsx index 6ce564b..acbd81a 100644 --- a/src/pages/Tariffs/TariffsPage.tsx +++ b/src/pages/Tariffs/TariffsPage.tsx @@ -28,8 +28,10 @@ import TariffCard from "./TariffCard"; import { useDiscounts } from "@root/api/price"; import { Select } from "@components/Select"; import { Tabs } from "@components/Tabs"; +import { useRequestSquizCreate, RSCOpen, RSCClose } from "@root/stores/requestSquizCreate" -const subPages = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"']; +const subPagesTime = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"']; +const subPagesVolume = ["Заявки квиз", 'Заказать создание квиза']; const StepperText: Record = { volume: "Тарифы на объём", @@ -43,10 +45,13 @@ function TariffPage() { const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const location = useLocation(); const tariffs = useTariffStore((state) => state.tariffs); - const [selectedItem, setSelectedItem] = useState(0); + const [selectedItemTime, setSelectedItemTime] = useState(0); + const [selectedItemVolume, setSelectedItemVolume] = useState(0); const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0; const userId = useUserStore((state) => state.user?._id) ?? ""; + const userPrivilegies = useUserStore(store => store.quizUserAccount?.privileges); + console.log(userPrivilegies) const discounts = useDiscounts(userId); const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko"; @@ -68,25 +73,14 @@ function TariffPage() { } const filteredTariffs = tariffs.filter((tariff) => { - if (tariff.privileges[0] === undefined) return false; - if ( - (tariff.privileges[0].type === "day") === (unit === "time") && - !tariff.isDeleted && - !tariff.isCustom - ) { - if ( - ((selectedItem === 0 && unit === "time") || unit !== "time") && - tariff.privileges[0].serviceKey === "squiz" && - tariff.privileges[0].privilegeId !== "squizHideBadge" - ) - return true; + if (tariff.privileges[0] === undefined || tariff.isDeleted || tariff.isCustom) return false; + if (unit === "time") { + if (selectedItemTime === 0 && tariff.privileges[0].privilegeId === "quizUnlimTime") return true + if (selectedItemTime === 1 && tariff.privileges[0].privilegeId === "squizHideBadge") return true } - if ( - selectedItem === 1 && - unit === "time" && - tariff.privileges[0].privilegeId === "squizHideBadge" - ) { - return true; + if (unit === "volume") { + if (selectedItemVolume === 0 && tariff.privileges[0].privilegeId === "quizCnt") return true + if (selectedItemVolume === 1 && tariff.privileges[0].privilegeId === "quizManual") return true } return false; }); @@ -95,6 +89,7 @@ function TariffPage() { filteredTariffs: Tariff[], addFreeTariff = false ) => { + const isCC = userPrivilegies?.quizManual?.amount !== undefined && userPrivilegies?.quizManual?.amount > 0 && unit === "volume" && selectedItemVolume === 1 const tariffElements = filteredTariffs .filter((tariff) => tariff.privileges.length > 0) .map((tariff, index) => { @@ -104,7 +99,7 @@ function TariffPage() { purchasesAmount, currentTariffs ?? [], isUserNko, - userId + userId, ); return ( @@ -133,6 +128,7 @@ function TariffPage() { text: "Выбрать", onClick: () => handleTariffItemClick(tariff._id), }} + sendRequestToCreate={isCC ? RSCOpen : undefined} headerText={tariff.name} text={tariff.description || ""} price={ @@ -206,15 +202,32 @@ function TariffPage() { <> {isMobile ? ( + ) : ( + )} @@ -231,7 +244,7 @@ function TariffPage() { }))`, }} > - {createTariffElements(filteredTariffs, true)} + {createTariffElements(filteredTariffs, unit === "time" || (unit === "volume" && selectedItemVolume !== 1))}
{/*{recentlyPurchased.length > 0 && (*/} {/* <>*/} diff --git a/src/pages/auth/RecoverPassword.tsx b/src/pages/auth/RecoverPassword.tsx index 77a261b..544de32 100644 --- a/src/pages/auth/RecoverPassword.tsx +++ b/src/pages/auth/RecoverPassword.tsx @@ -75,6 +75,8 @@ export default function RecoverPassword() { useEffect(() => { const params = new URLSearchParams(window.location.search); const authToken = params.get("auth"); + console.log("authToken") + console.log(authToken) setTokenUser(authToken); history.pushState(null, document.title, "/changepwd"); diff --git a/src/stores/cart.ts b/src/stores/cart.ts deleted file mode 100644 index 0a6b120..0000000 --- a/src/stores/cart.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { create } from "zustand"; -import { devtools } from "zustand/middleware"; - -interface CartStore { - notEnoughMoneyAmount: number; -} - -export const useCartStore = create()( - devtools( - (get, set) => ({ - notEnoughMoneyAmount: 0, - }), - { - name: "Cart", - enabled: process.env.NODE_ENV === "development", - trace: true, - actionsBlacklist: "rejected", - } - ) -); - -export const setNotEnoughMoneyAmount = (amount: number) => useCartStore.setState({ notEnoughMoneyAmount: amount }); diff --git a/src/stores/diffMoney.ts b/src/stores/diffMoney.ts deleted file mode 100644 index 2e3a49a..0000000 --- a/src/stores/diffMoney.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { HistoryRecord, HistoryRecord2 } from "@root/api/history"; -import { create } from "zustand"; -import { devtools, persist } from "zustand/middleware"; - - -type DiffMoneyType = { - diffMoney: number; - setNewDiff: (diff: number) => void -}; - -export const useDiffMoney = create((set) => ({ - diffMoney: 0, - setNewDiff: (diff: number) => set({diffMoney: diff}) - })); diff --git a/src/stores/notEnoughMoneyAmount.ts b/src/stores/notEnoughMoneyAmount.ts new file mode 100644 index 0000000..ebf270e --- /dev/null +++ b/src/stores/notEnoughMoneyAmount.ts @@ -0,0 +1,38 @@ +import moment from "moment"; +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; + +interface CartStore { + notEnoughMoneyAmount: number; + siteReadyPayCart: Record | null; +} +const initialState: CartStore = { + notEnoughMoneyAmount: 0, + siteReadyPayCart: null +} + +//Была попытка оплатить корзину. Тут записанна недостающая сумма. +export const useNotEnoughMoneyAmount = create()( + devtools( + (get, set) => initialState, + { + name: "notEnoughMoneyAmount", + enabled: process.env.NODE_ENV === "development", + trace: true, + actionsBlacklist: "rejected", + } + ) +); + +export const setNotEnoughMoneyAmount = (amount: number) => useNotEnoughMoneyAmount.setState({ notEnoughMoneyAmount: amount }); + +export const setSiteReadyPayCart = (flag: Record | null) => useNotEnoughMoneyAmount.setState({ siteReadyPayCart: flag }); +export const startPayCartProcess = (userId: string) => setSiteReadyPayCart({ [userId]: moment().add(20, 'minutes').format("X") }); +export const cancelPayCartProcess = () => setSiteReadyPayCart(null); +export const calcTimeOfReadyPayCart = (deadline: string) => { + const ready = Number(deadline) > Number(moment().format("X")) + if (!ready) { + cancelPayCartProcess() + } + return ready +} diff --git a/src/stores/requestSquizCreate.ts b/src/stores/requestSquizCreate.ts new file mode 100644 index 0000000..114c50e --- /dev/null +++ b/src/stores/requestSquizCreate.ts @@ -0,0 +1,25 @@ +import { Privilege } from "@frontend/kitui"; +import { create } from "zustand" +import { devtools } from "zustand/middleware" + + +interface RequestSquizCreate { + isRSCOpen: boolean; +} + +const initialState: RequestSquizCreate = { + isRSCOpen: false +} + +export const useRequestSquizCreate = create()( + devtools( + (get, set) => initialState, + { + name: "Privileges", + enabled: process.env.NODE_ENV === "development", + } + ) +) + +export const RSCOpen = () => useRequestSquizCreate.setState({ isRSCOpen: true }) +export const RSCClose = () => useRequestSquizCreate.setState({ isRSCOpen: false }) \ No newline at end of file diff --git a/src/stores/user.ts b/src/stores/user.ts index ccd8835..47563d9 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -14,23 +14,42 @@ import { patchUser } from "@root/api/user" import { UserAccountSettingsFieldStatus, VerificationStatus } from "@root/model/account" import { patchCurrency, deleteCart, patchCart } from "@root/api/cart" import { User, UserAccount, UserName, getInitials, patchUserAccount } from "@frontend/kitui" -import { setNotEnoughMoneyAmount } from "./cart"; +import { cancelPayCartProcess, setNotEnoughMoneyAmount, setSiteReadyPayCart, useNotEnoughMoneyAmount } from "./notEnoughMoneyAmount"; + +type Privilege = { + amount: number; + created_at: string; + id: string; + privilege_id: string; + privilege_name: string; + }; interface UserStore { - userId: string | null; - user: User | null; - userAccount: UserAccount | null; - settingsFields: UserSettingsFieldStatus & UserAccountSettingsFieldStatus & { hasError: boolean }; - verificationStatus: VerificationStatus; - verificationType: "juridical" | "nko"; - isDocumentsDialogOpen: boolean; - dialogType: "juridical" | "nko" | ""; - documents: UserDocuments; - documentsUrl: UserDocumentsUrl; - comment: string; - initials: string; + userId: string | null; + user: User | null; + userAccount: UserAccount | null; + settingsFields: UserSettingsFieldStatus & UserAccountSettingsFieldStatus & { hasError: boolean }; + verificationStatus: VerificationStatus; + verificationType: "juridical" | "nko"; + isDocumentsDialogOpen: boolean; + dialogType: "juridical" | "nko" | ""; + documents: UserDocuments; + documentsUrl: UserDocumentsUrl; + comment: string; + initials: string; + quizUserAccount: OriginalUserSquizAccount | null; } + +export type OriginalUserSquizAccount = { + created_at: string; + deleted: boolean; + email: string; + id: string; + privileges: Record; + privilege_name: string; + }; + const defaultFieldValues = { value: "", error: null, @@ -58,6 +77,7 @@ const initialState: UserStore = { userId: null, user: null, userAccount: null, + quizUserAccount: null, settingsFields: { ...defaultFields }, verificationStatus: VerificationStatus.NOT_VERIFICATED, verificationType: "juridical", @@ -103,10 +123,22 @@ export const useUserStore = create()( export const setVerificationStatus = (verificationStatus: VerificationStatus) => useUserStore.setState({ verificationStatus }) -export const setVerificationType = (verificationType: "nko" | "org") => +export const setVerificationType = (verificationType: UserAccount["status"]) => useUserStore.setState({ verificationType: verificationType === "org" ? "juridical" : "nko", }) +export const setUserStatus = (status: UserAccount["status"]) => + useUserStore.setState( + produce((state) => { + if (state.userAccount !== null) state.userAccount.status = status + }) + ) +export const setWallet = (wallet: UserAccount["wallet"]) => + useUserStore.setState( + produce((state) => { + if (state.userAccount !== null) state.userAccount.wallet = wallet + }) + ) export const setUserId = (userId: string | null) => useUserStore.setState({ userId }) export const setUser = (user: User) => @@ -114,7 +146,7 @@ export const setUser = (user: User) => produce((state) => { state.user = user - state.settingsFields.email.value = user?.email ?? "" + state.settingsFields.email.value = user?.email || user?.login || "" state.settingsFields.phoneNumber.value = user?.phoneNumber ?? "" state.settingsFields.password.value = "" }) @@ -139,12 +171,36 @@ export const setUserAccount = (user: UserAccount) => } ) -export const setCart = (cart: string[]) => +export const setQuizUserAccount = (quizUserAccount: OriginalUserSquizAccount) => useUserStore.setState({ quizUserAccount }); + +export const setNewNames = (name: UserName) => useUserStore.setState( produce((state) => { - if (state.userAccount) state.userAccount.cart = cart + state.settingsFields.firstname.value = name.firstname ?? "" + state.settingsFields.secondname.value = name.secondname ?? "" + state.settingsFields.middlename.value = name.middlename ?? "" + state.settingsFields.orgname.value = name.orgname ?? "" + }), + false, + { + type: "setNewNames", + payload: name, + } + ) + +export const setCart = (cart: string[]) => { + + useUserStore.setState( + produce((state) => { + if (state.userAccount) { + state.userAccount.cart = cart + } }) ) + //Изменение корзины ведёт к отмене этих 2 параметров всегда + setNotEnoughMoneyAmount(0) + cancelPayCartProcess() +} export const setComment = (comment: string) => useUserStore.setState({ comment }) @@ -216,7 +272,7 @@ export const setUploadedDocument = (type: UserDocumentTypes, fileName: string, u }) ) -export const setSettingsField = (fieldName: UserSettingsField | keyof UserName, value: string) => +export const setSettingsField = (fieldName: UserSettingsField | keyof UserName, value: string) => useUserStore.setState( produce((state) => { if (!state.settingsFields) return @@ -247,15 +303,15 @@ export const sendUserData = async () => { if (!state.settingsFields) return const isPatchingUser = - state.settingsFields.email.touched || - state.settingsFields.password.touched || - state.settingsFields.phoneNumber.touched + state.settingsFields.email.touched || + state.settingsFields.password.touched || + state.settingsFields.phoneNumber.touched const isPatchingUserAccount = - state.settingsFields.firstname.touched || - state.settingsFields.secondname.touched || - state.settingsFields.middlename.touched || - state.settingsFields.orgname.touched + state.settingsFields.firstname.touched || + state.settingsFields.secondname.touched || + state.settingsFields.middlename.touched || + state.settingsFields.orgname.touched const userPayload: PatchUserRequest = {} @@ -273,7 +329,7 @@ export const sendUserData = async () => { await Promise.all([ isPatchingUser && patchUser(userPayload).then(([user]) => user && setUser(user)), - isPatchingUserAccount && patchUserAccount(userAccountPayload).then(setUserAccount), + isPatchingUserAccount && patchUserAccount(userAccountPayload, "v1.0.1").then(setUserAccount), ]) } @@ -283,11 +339,11 @@ export const addTariffToCart = async (tariffId: string) => { if (patchCartError === undefined) { setCart(patchCartResponse) } - return({patchCartResponse, patchCartError}) + return ({ patchCartResponse, patchCartError }) } export const removeTariffFromCart = async (tariffId: string) => { - setNotEnoughMoneyAmount(0); + setNotEnoughMoneyAmount(0); const [deleteCartResponse, deleteCartError] = await deleteCart(tariffId) if (!deleteCartError) { diff --git a/src/useUserAccountFetcher.ts b/src/useUserAccountFetcher.ts new file mode 100644 index 0000000..5079863 --- /dev/null +++ b/src/useUserAccountFetcher.ts @@ -0,0 +1,61 @@ +import { useEffect, useLayoutEffect, useRef } from "react"; +import { createUserAccount, devlog, getAuthToken, setAuthToken } from "@frontend/kitui"; +import { isAxiosError } from "axios"; + +import makeRequest from "@api/makeRequest"; + +import type { UserAccount } from "@frontend/kitui"; + +export const useUserAccountFetcher = ({ + onError, + onNewUserAccount, + url, + userId, +}: { + url: string; + userId: string | null; + onNewUserAccount: (response: T) => void; + onError?: (error: any) => void; +}) => { + console.log("начало работы useUserAccountFetcher") + const onNewUserAccountRef = useRef(onNewUserAccount); + const onErrorRef = useRef(onError); + useLayoutEffect(() => { + onNewUserAccountRef.current = onNewUserAccount; + onErrorRef.current = onError; + }, [onError, onNewUserAccount]); + useEffect(() => { + if (!userId) return; + const controller = new AbortController(); + makeRequest({ + url, + contentType: true, + method: "GET", + useToken: true, + withCredentials: false, + signal: controller.signal, + }) + .then((result) => { + devlog("User account", result); + onNewUserAccountRef.current(result); + }) + .catch((error) => { + devlog("Error fetching user account", error); + if (isAxiosError(error) && error.response?.status === 404) { + createUserAccount(controller.signal, url.replace("get", "create"), "v1.0.1") + .then((result) => { + devlog("Created user account", result); + onNewUserAccountRef.current(result as T); + }) + .catch((error) => { + if (error.response?.status === 409) return; + devlog("Error creating user account", error); + onErrorRef.current?.(error); + }); + } else { + onErrorRef.current?.(error); + } + }); + return () => controller.abort(); + }, [url, userId]); +}; diff --git a/src/utils/hooks/useAutoPay.ts b/src/utils/hooks/useAutoPay.ts new file mode 100644 index 0000000..c9ceec8 --- /dev/null +++ b/src/utils/hooks/useAutoPay.ts @@ -0,0 +1,72 @@ +import { payCart } from "@root/api/cart"; +import { calcTimeOfReadyPayCart, cancelPayCartProcess, setNotEnoughMoneyAmount, setSiteReadyPayCart, startPayCartProcess, useNotEnoughMoneyAmount } from "@root/stores/notEnoughMoneyAmount"; +import { useUserStore } from "@root/stores/user"; +import moment from "moment"; +import { enqueueSnackbar } from "notistack"; +import { useEffect } from "react"; +import { useSearchParams } from "react-router-dom"; +import { RSCOpen } from "@root/stores/requestSquizCreate"; +import { useCartTariffs } from "./useCartTariffs"; + +export const useAfterPay = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const userId = useUserStore(store => store.userId) + const userAccount = useUserStore(state => state.userAccount); + const siteReadyPayCart = useNotEnoughMoneyAmount(state => state.siteReadyPayCart); + const cartTariffs = useCartTariffs(); + console.log("cartTariffs") + // console.log(cartTariffs) + // const isCC = cartTariffs !== null && cartTariffs !== undefined && cartTariffs.length > 0 && cartTariffs.some(t => t.privileges[0].privilegeId === "quizManual") + + const purpose = searchParams.get("purpose"); + const from = searchParams.get("from"); + const action = searchParams.get("action"); + const paymentUserId = searchParams.get("userid"); + + + useEffect(() => { + //Звёзды сошлись, будем оплачивать корзину + if (from !== "quiz" && paymentUserId && paymentUserId === userId) { + //Чистим url адрес от параметров. (Если нет action. Если есть - значит мы пришли из квиза) + if (action === null) setSearchParams({}, { replace: true }) + // navigate(`/tariffs`, { + // replace: true, + // }); + + if (purpose === "paycart") { + (async () => { + + //Проверяем можем ли мы оплатить корзину здесь и сейчас + const [, payCartError] = await payCart(); + + if (payCartError) { + //Не получилось купить корзину. Ставим флаг, что сайт в состоянии ожидания пополнения счёта для оплаты + startPayCartProcess(paymentUserId) + } else { + enqueueSnackbar("Товары успешно приобретены") + cancelPayCartProcess() + if (true) RSCOpen() + } + })() + } + } + }, [purpose, from, paymentUserId]) + + useEffect(() => { + if (userId !== null && siteReadyPayCart !== null && siteReadyPayCart[userId] !== undefined) { + const deadline = siteReadyPayCart[userId] + if (calcTimeOfReadyPayCart(deadline)) { + + //Время ещё не вышло. У нас стоит флаг покупать корзину если время не вышло. + (async () => { + const [, payCartError] = await payCart(); + + if (!payCartError) { + enqueueSnackbar("Товары успешно приобретены") + cancelPayCartProcess() + } + })() + } + } + }, [userAccount, userId, siteReadyPayCart]) +} \ No newline at end of file diff --git a/src/utils/hooks/usePipeSubscriber.ts b/src/utils/hooks/usePipeSubscriber.ts new file mode 100644 index 0000000..60bcb70 --- /dev/null +++ b/src/utils/hooks/usePipeSubscriber.ts @@ -0,0 +1,66 @@ +import { Ticket, UserAccount, UserName, getAuthToken, useSSESubscription } from "@frontend/kitui" +import { setCart, setNewNames, setUserStatus, setWallet, useUserStore } from "@root/stores/user"; +import { useSSETab } from "./useSSETab"; +import { cancelPayCartProcess } from "@root/stores/notEnoughMoneyAmount"; + +type Ping = [{ event: "ping" }] + +type SomeChange = [{ + "id": UserAccount["_id"], + "userId": UserAccount["userId"], + "cart": UserAccount["cart"], + "wallet": { + "cash": UserAccount["wallet"]["cash"], + "currency": UserAccount["wallet"]["currency"], + "spent": UserAccount["wallet"]["spent"], + "purchasesAmount": UserAccount["wallet"]["purchasesAmount"], + "money": UserAccount["wallet"]["money"], + "lastPaymentId": string; + }, + "name": UserName, + "status": UserAccount["status"], + "isDeleted": UserAccount["isDeleted"], + "createdAt": UserAccount["createdAt"]; + "updatedAt": UserAccount["updatedAt"]; + "from": string; + "partner": string; +}] + +type PipeMessage = Ping | SomeChange + +export const usePipeSubscriber = () => { + const token = getAuthToken(); + const userId = useUserStore((state) => state.userId); + const { isActiveSSETab, updateSSEValue } = useSSETab("pipe"); + + useSSESubscription({ + enabled: Boolean(token) && Boolean(userId) && isActiveSSETab, + url: + process.env.REACT_APP_DOMAIN + + `/customer/v1.0.1/account/pipe?Authorization=${token}`, + onNewData: (data) => { + let message = data[0] as PipeMessage + updateSSEValue(message) + + //Пропускаем пингование + if ('event' in message && message.event === "ping") return + + + if ('cart' in message) { + setCart(message.cart as string[]) + cancelPayCartProcess() + } + else if ('wallet' in message) { + setWallet(message.wallet as UserAccount["wallet"]) + cancelPayCartProcess() + } + else if ('name' in message) { + setNewNames(message.name as UserName) + } + else if ('status' in message) { + setUserStatus(message.status as UserAccount["status"]) + } + }, + marker: "pipe", + }); +} \ No newline at end of file diff --git a/src/utils/hooks/useSSETab.ts b/src/utils/hooks/useSSETab.ts index 6ff9833..8f71636 100644 --- a/src/utils/hooks/useSSETab.ts +++ b/src/utils/hooks/useSSETab.ts @@ -74,6 +74,7 @@ export const useSSETab = ( }; const setOpenTime = () => { + //Время установлено - пропускаем if (openTimeSetted) { return; } diff --git a/src/utils/jsonToFormdata.ts b/src/utils/jsonToFormdata.ts index 3267290..3f36bfd 100644 --- a/src/utils/jsonToFormdata.ts +++ b/src/utils/jsonToFormdata.ts @@ -1,4 +1,5 @@ import type { Attachment } from "@root/model/attachment" +import { transliterate } from 'transliteration'; type KeyValue = { [key: string]: T; @@ -24,11 +25,15 @@ export const jsonToFormdata = ( json: KeyValue ): FormData => { const formData = new FormData() + if (json.egrule !== undefined) delete json.egrule + console.log("json") + console.log(json) for (const key in json) { if (!key) continue const value = json[key] + if (typeof value !== "string") value.name = transliterate(value.name.replace(/\s/g, '_')) if (typeof value !== "string") { formData.append(key, convertBinaryStringToFile(value)) @@ -38,6 +43,7 @@ export const jsonToFormdata = ( formData.append(key, value) } - +console.log("formData") +console.log(formData) return formData } diff --git a/src/utils/routes/ProtectedRoute.tsx b/src/utils/routes/ProtectedRoute.tsx index 9c269d2..9ed892d 100644 --- a/src/utils/routes/ProtectedRoute.tsx +++ b/src/utils/routes/ProtectedRoute.tsx @@ -1,8 +1,9 @@ -import { Navigate, Outlet } from "react-router-dom" +import { Navigate, Outlet, useLocation } from "react-router-dom" import { useUserStore } from "@root/stores/user" export default function PrivateRoute() { + const location = useLocation() const user = useUserStore(state => state.user) return user ? : diff --git a/yarn.lock b/yarn.lock index af2935e..bccc5e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1138,6 +1138,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -1626,10 +1633,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== -"@frontend/kitui@^1.0.82": - version "1.0.82" - resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.82.tgz#696ab79a14357608c20bcdf47aadd5a77155f609" - integrity sha1-aWq3mhQ1dgjCC830eq3Vp3FV9gk= +"@frontend/kitui@^1.0.86": + version "1.0.86" + resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.86.tgz#03a56b99403b62810134f3d7da01ea3fc8cc4f7f" + integrity sha1-A6VrmUA7YoEBNPPX2gHqP8jMT38= dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2" @@ -2203,6 +2210,15 @@ "@mui/utils" "^5.15.14" prop-types "^15.8.1" +"@mui/private-theming@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.6.tgz#547671e7ae3f86b68d1289a0b90af04dfcc1c8c9" + integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.16.6" + prop-types "^15.8.1" + "@mui/styled-engine@^5.15.14": version "5.15.14" resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" @@ -2213,6 +2229,16 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/styled-engine@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852" + integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/system@^5.15.15": version "5.15.15" resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" @@ -2227,11 +2253,30 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/system@^5.16.5": + version "5.16.7" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.7.tgz#4583ca5bf3b38942e02c15a1e622ba869ac51393" + integrity sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.16.6" + "@mui/styled-engine" "^5.16.6" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.6" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/types@^7.2.14": version "7.2.14" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== +"@mui/types@^7.2.15": + version "7.2.15" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53" + integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q== + "@mui/utils@^5.15.14": version "5.15.14" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" @@ -2242,6 +2287,31 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.16.5", "@mui/utils@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711" + integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/types" "^7.2.15" + "@types/prop-types" "^15.7.12" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^18.3.1" + +"@mui/x-date-pickers@^7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.13.0.tgz#1afe20dc7ee30c9c1f91c232f3c61f94d2b8427b" + integrity sha512-cmpAfkzOjUgL4I8WenU4elm1QJO8vWpGmIPCezT3Q9wFjGL1QApQhJ5gMZ+X4tM6Gha9AhIWNQX5eXHKbSoyFQ== + dependencies: + "@babel/runtime" "^7.25.0" + "@mui/system" "^5.16.5" + "@mui/utils" "^5.16.5" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -2873,7 +2943,7 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== -"@types/prop-types@*", "@types/prop-types@^15.7.11": +"@types/prop-types@*", "@types/prop-types@^15.7.11", "@types/prop-types@^15.7.12": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== @@ -4449,6 +4519,11 @@ clsx@^2.0.0, clsx@^2.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -8912,6 +8987,11 @@ mlly@^1.2.0, mlly@^1.4.2: pkg-types "^1.0.3" ufo "^1.3.2" +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -10329,6 +10409,11 @@ react-is@^18.0.0, react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-pdf@^7.1.2: version "7.7.1" resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-7.7.1.tgz#8f5c4716a8ca65a0889825ef01e3a37956291334" @@ -11786,6 +11871,13 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +transliteration@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/transliteration/-/transliteration-2.3.5.tgz#8f92309575f69e4a8a525dab4ff705ebcf961c45" + integrity sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw== + dependencies: + yargs "^17.5.1" + tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -12825,7 +12917,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.3.1: +yargs@^17.3.1, yargs@^17.5.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==