diff --git a/package.json b/package.json index 2dd8f33..13b43a9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@frontend/kitui": "^1.0.108", "@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.30.3", "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 bb704cb..0e3ba58 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 ed1604d..12693e9 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; @@ -20,12 +24,14 @@ export const sendPayment = async ({ body, fromSquiz, paymentPurpose, + cc }: { userId: string; wayback: string; body: PaymentBody; fromSquiz: boolean; paymentPurpose: "paycart" | "replenishwallet"; + cc?: boolean }): Promise<[SendPaymentResponse | null, string?]> => { console.log(" я sendPayment и вот мой wayback = " + wayback) @@ -35,6 +41,7 @@ let returnUrl= `https://${isStaging}hub.pena.digital/afterpay?from=${ }&purpose=${paymentPurpose}&userid=${userId}` if (wayback) returnUrl += `&wayback=${wayback}` +if (cc) returnUrl = returnLink + "&cc=true" console.log("returnUrl") console.log(returnUrl) 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/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 6fc5036..8f64e5f 100644 --- a/src/pages/AfterPay/index.tsx +++ b/src/pages/AfterPay/index.tsx @@ -1,139 +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"); - const wayback = searchParams.get("wayback"); - 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"; - - let redirect = `https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}` - if (wayback) redirect += `&wayback=${wayback}` - - setRedirectUrl(redirect); - }, []); - - return ( - - - - - Спасибо за оплату! - - - Ваш платеж принят и в течение 10 минут товары будут зачислены - - - - - ); + document.location.href = link + return <>; }; - -//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDZlODc4hLWF1d__aACP1IpA&userid=6846e878c149e4d24ebf50f3&from=AI&wayback=ai_27964 \ No newline at end of file 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 ? : state.userId) || ""; + const verificationStatus = useUserStore((state) => state.verificationStatus); + 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 [warnModalOpen, setWarnModalOpen] = useState(false); - const [sorryModalOpen, setSorryModalOpen] = useState(false); + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(""); const [paymentValueField, setPaymentValueField] = useState("0"); const [paymentLink, setPaymentLink] = useState(""); + const [fromSquiz, setIsFromSquiz] = useState(false); const location = useLocation(); const verificationStatus = useUserStore((state) => state.verificationStatus); const userId = useUserStore((state) => state.userId); const navigate = useNavigate(); const handleCustomBackNavigation = useHistoryTracker(); + 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 [fromSquiz, setIsFromSquiz] = useState(false); const [waybackToSomeSite, setWaybackToSomeSite] = useState(""); @@ -85,96 +96,105 @@ export default function Payment() { bigDecimal.multiply(parseFloat(paymentValueField), 100) ); + //Отмена состояния сайта "в процессе покупки корзины, просто нехватило деняк" useEffect(() => { - if (diffMoney > 0) { - setNewDiff(0); - setPaymentValueField((diffMoney / 100).toString()); + return () => { + //При выходе со страницы оплаты мы точно не в состоянии покупки корзины + cancelPayCartProcess() } - }, [diffMoney]) + }, []) + + //Тут записываем начальную сумму в инпут (если стоит флаг что мы в процессе покупки) + 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()); } - - const wayback = params.get("wayback"); - if (wayback) setWaybackToSomeSite(wayback); - console.log(" я получил wayback = " + wayback) - + //Принимаем параметры из странички и очищаем 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, - wayback: waybackToSomeSite, - 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; @@ -304,76 +324,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 ? ( - - ) : ( - - )} + @@ -381,4 +384,4 @@ export default function Payment() { ); } -//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDcxNjAxYzE0OWU0ZDI0ZWJmNTExNyIsImF1ZCI6InBlbmEiLCJpc3MiOiJwZW5hLWF1dGgtc2VydmljZSIsImlhdCI6MTc0OTQ4OTE1MywiZXhwIjoxNzU0NjczMTUzfQ.SqEXFSPzP3ugIscqLywGkjFJmFqx13zOtxGAjZQ6SzUw8w9ZXjE9uFn8VbLBMGPqeJbvcT2jRV2bB5qtqMy1T3aNuSZ9AZW0jY1hGvFB-bSrYguMV1yErLkR45SvdK2jGI6dg3p6LRqCHb2JMl8vur_KKaus3GiJlsTNNR0fhgI&userid=68471601c149e4d24ebf5117&from=AI&wayback=ai_27969 \ No newline at end of file +//https://shub.pena.digital/quizpayment?action=squizpay&dif=50000&data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NDcxNjAxYzE0OWU0ZDI0ZWJmNTExNyIsImF1ZCI6InBlbmEiLCJpc3MiOiJwZW5hLWF1dGgtc2VydmljZSIsImlhdCI6MTc0OTQ4OTE1MywiZXhwIjoxNzU0NjczMTUzfQ.SqEXFSPzP3ugIscqLywGkjFJmFqx13zOtxGAjZQ6SzUw8w9ZXjE9uFn8VbLBMGPqeJbvcT2jRV2bB5qtqMy1T3aNuSZ9AZW0jY1hGvFB-bSrYguMV1yErLkR45SvdK2jGI6dg3p6LRqCHb2JMl8vur_KKaus3GiJlsTNNR0fhgI&userid=68471601c149e4d24ebf5117&from=AI&wayback=ai_27969 diff --git a/src/pages/QuizPayment/QuizPayment.tsx b/src/pages/QuizPayment/QuizPayment.tsx index 3bba7f4..5231c33 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,14 +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"); -const wayback = params.get("wayback"); +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; @@ -34,6 +34,8 @@ 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() { @@ -46,6 +48,21 @@ export default function QuizPayment() { } }, [navigate, user] + + 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, + }); + } + } + }, + [currentUserId] ); @@ -68,24 +85,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/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 c37541a..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, "1.0.1").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 ? :