Merge branch 'dev' into 'staging'
Dev See merge request frontend/marketplace!125
This commit is contained in:
commit
e3c43782f7
@ -1,8 +1,8 @@
|
|||||||
import { makeRequest } from "@frontend/kitui"
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet"
|
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error"
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer"
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
||||||
|
|
||||||
const testPaymentBody: SendPaymentRequest = {
|
const testPaymentBody: SendPaymentRequest = {
|
||||||
type: "bankCard",
|
type: "bankCard",
|
||||||
@ -18,12 +18,16 @@ const testPaymentBody: SendPaymentRequest = {
|
|||||||
phoneNumber: "79000000000",
|
phoneNumber: "79000000000",
|
||||||
login: "login_test",
|
login: "login_test",
|
||||||
returnUrl: window.location.origin + "/wallet",
|
returnUrl: window.location.origin + "/wallet",
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function sendPayment(
|
export async function sendPayment({
|
||||||
{body = testPaymentBody, fromSquiz = false}: {body?: SendPaymentRequest, fromSquiz:boolean}
|
body = testPaymentBody,
|
||||||
): Promise<[SendPaymentResponse | null, string?]> {
|
fromSquiz = false,
|
||||||
if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub"
|
}: {
|
||||||
|
body?: SendPaymentRequest;
|
||||||
|
fromSquiz: boolean;
|
||||||
|
}): Promise<[SendPaymentResponse | null, string?]> {
|
||||||
|
if (fromSquiz) body.returnUrl = "squiz.pena.digital/list?action=fromhub";
|
||||||
try {
|
try {
|
||||||
const sendPaymentResponse = await makeRequest<
|
const sendPaymentResponse = await makeRequest<
|
||||||
SendPaymentRequest,
|
SendPaymentRequest,
|
||||||
@ -35,12 +39,29 @@ export async function sendPayment(
|
|||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
body,
|
body,
|
||||||
})
|
});
|
||||||
|
|
||||||
return [sendPaymentResponse]
|
return [sendPaymentResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError)
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Ошибка оплаты. ${error}`]
|
return [null, `Ошибка оплаты. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendRSPayment = async (): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
await makeRequest<never, string>({
|
||||||
|
url: apiUrl + "/wallet/rspay",
|
||||||
|
method: "POST",
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return `Ошибка оплаты. ${error}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
BIN
src/assets/bank-logo/rs-pay.png
Normal file
BIN
src/assets/bank-logo/rs-pay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -34,6 +34,7 @@ import {
|
|||||||
createTicket,
|
createTicket,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
||||||
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -61,6 +62,10 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
(state) => state.unauthTicketMessageFetchState
|
(state) => state.unauthTicketMessageFetchState
|
||||||
);
|
);
|
||||||
const chatBoxRef = useRef<HTMLDivElement>(null);
|
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
||||||
|
"ticket",
|
||||||
|
addOrUpdateUnauthMessages
|
||||||
|
);
|
||||||
|
|
||||||
useTicketMessages({
|
useTicketMessages({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
||||||
@ -81,11 +86,14 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useSSESubscription<TicketMessage>({
|
useSSESubscription<TicketMessage>({
|
||||||
enabled: Boolean(sessionData),
|
enabled: isActiveSSETab && Boolean(sessionData),
|
||||||
url:
|
url:
|
||||||
process.env.REACT_APP_DOMAIN +
|
process.env.REACT_APP_DOMAIN +
|
||||||
`/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
`/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
||||||
onNewData: addOrUpdateUnauthMessages,
|
onNewData: (ticketMessages) => {
|
||||||
|
updateSSEValue(ticketMessages);
|
||||||
|
addOrUpdateUnauthMessages(ticketMessages);
|
||||||
|
},
|
||||||
onDisconnect: useCallback(() => {
|
onDisconnect: useCallback(() => {
|
||||||
setUnauthIsPreventAutoscroll(false);
|
setUnauthIsPreventAutoscroll(false);
|
||||||
}, []),
|
}, []),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Outlet } from "react-router-dom"
|
import { Outlet } from "react-router-dom";
|
||||||
import Navbar from "./NavbarSite/Navbar"
|
import Navbar from "./NavbarSite/Navbar";
|
||||||
import {
|
import {
|
||||||
Ticket,
|
Ticket,
|
||||||
getMessageFromFetchError,
|
getMessageFromFetchError,
|
||||||
@ -8,75 +8,91 @@ import {
|
|||||||
useSSESubscription,
|
useSSESubscription,
|
||||||
useTicketsFetcher,
|
useTicketsFetcher,
|
||||||
useToken,
|
useToken,
|
||||||
} from "@frontend/kitui"
|
} from "@frontend/kitui";
|
||||||
import { updateTickets, setTicketCount, useTicketStore, setTicketsFetchState } from "@root/stores/tickets"
|
import {
|
||||||
import { enqueueSnackbar } from "notistack"
|
updateTickets,
|
||||||
import { updateTariffs } from "@root/stores/tariffs"
|
setTicketCount,
|
||||||
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs"
|
useTicketStore,
|
||||||
import { setCustomTariffs } from "@root/stores/customTariffs"
|
setTicketsFetchState,
|
||||||
import { useDiscounts } from "@root/utils/hooks/useDiscounts"
|
} from "@root/stores/tickets";
|
||||||
import { setDiscounts } from "@root/stores/discounts"
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { setPrivileges } from "@root/stores/privileges"
|
import { updateTariffs } from "@root/stores/tariffs";
|
||||||
|
import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs";
|
||||||
|
import { setCustomTariffs } from "@root/stores/customTariffs";
|
||||||
|
import { useDiscounts } from "@root/utils/hooks/useDiscounts";
|
||||||
|
import { setDiscounts } from "@root/stores/discounts";
|
||||||
|
import { setPrivileges } from "@root/stores/privileges";
|
||||||
import { useHistoryData } from "@root/utils/hooks/useHistoryData";
|
import { useHistoryData } from "@root/utils/hooks/useHistoryData";
|
||||||
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
|
||||||
export default function ProtectedLayout() {
|
export default function ProtectedLayout() {
|
||||||
const token = useToken()
|
const token = useToken();
|
||||||
const ticketApiPage = useTicketStore((state) => state.apiPage)
|
const ticketApiPage = useTicketStore((state) => state.apiPage);
|
||||||
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage)
|
const ticketsPerPage = useTicketStore((state) => state.ticketsPerPage);
|
||||||
|
const { isActiveSSETab, updateSSEValue } = useSSETab<Ticket[]>(
|
||||||
|
"auth",
|
||||||
|
(data) => {
|
||||||
|
updateTickets(data.filter((d) => Boolean(d.id)));
|
||||||
|
setTicketCount(data.length);
|
||||||
|
}
|
||||||
|
);
|
||||||
useSSESubscription<Ticket>({
|
useSSESubscription<Ticket>({
|
||||||
url: process.env.REACT_APP_DOMAIN + `/heruvym/subscribe?Authorization=${token}`,
|
enabled: isActiveSSETab,
|
||||||
|
url:
|
||||||
|
process.env.REACT_APP_DOMAIN +
|
||||||
|
`/heruvym/subscribe?Authorization=${token}`,
|
||||||
onNewData: (data) => {
|
onNewData: (data) => {
|
||||||
updateTickets(data.filter((d) => Boolean(d.id)))
|
updateSSEValue(data);
|
||||||
setTicketCount(data.length)
|
updateTickets(data.filter((d) => Boolean(d.id)));
|
||||||
|
setTicketCount(data.length);
|
||||||
},
|
},
|
||||||
marker: "ticket",
|
marker: "ticket",
|
||||||
})
|
});
|
||||||
|
|
||||||
useTicketsFetcher({
|
useTicketsFetcher({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/getTickets",
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/getTickets",
|
||||||
ticketsPerPage,
|
ticketsPerPage,
|
||||||
ticketApiPage,
|
ticketApiPage,
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
if (result.data) updateTickets(result.data)
|
if (result.data) updateTickets(result.data);
|
||||||
setTicketCount(result.count)
|
setTicketCount(result.count);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const message = getMessageFromFetchError(error)
|
const message = getMessageFromFetchError(error);
|
||||||
if (message) enqueueSnackbar(message)
|
if (message) enqueueSnackbar(message);
|
||||||
},
|
},
|
||||||
onFetchStateChange: setTicketsFetchState,
|
onFetchStateChange: setTicketsFetchState,
|
||||||
})
|
});
|
||||||
|
|
||||||
useAllTariffsFetcher({
|
useAllTariffsFetcher({
|
||||||
onSuccess: updateTariffs,
|
onSuccess: updateTariffs,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const errorMessage = getMessageFromFetchError(error)
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
if (errorMessage) enqueueSnackbar(errorMessage)
|
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
useCustomTariffs({
|
useCustomTariffs({
|
||||||
onNewUser: setCustomTariffs,
|
onNewUser: setCustomTariffs,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
if (error) enqueueSnackbar(error)
|
if (error) enqueueSnackbar(error);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
useDiscounts({
|
useDiscounts({
|
||||||
onNewDiscounts: setDiscounts,
|
onNewDiscounts: setDiscounts,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const message = getMessageFromFetchError(error)
|
const message = getMessageFromFetchError(error);
|
||||||
if (message) enqueueSnackbar(message)
|
if (message) enqueueSnackbar(message);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
usePrivilegeFetcher({
|
usePrivilegeFetcher({
|
||||||
onSuccess: setPrivileges,
|
onSuccess: setPrivileges,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.log("usePrivilegeFetcher error :>> ", error)
|
console.log("usePrivilegeFetcher error :>> ", error);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
useHistoryData();
|
useHistoryData();
|
||||||
|
|
||||||
@ -84,5 +100,5 @@ export default function ProtectedLayout() {
|
|||||||
<Navbar>
|
<Navbar>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Navbar>
|
</Navbar>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,85 +5,107 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material"
|
} from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
import SectionWrapper from "@components/SectionWrapper"
|
import SectionWrapper from "@components/SectionWrapper";
|
||||||
import PaymentMethodCard from "./PaymentMethodCard"
|
import PaymentMethodCard from "./PaymentMethodCard";
|
||||||
import mastercardLogo from "../../assets/bank-logo/logo-mastercard.png"
|
import mastercardLogo from "@root/assets/bank-logo/logo-mastercard.png";
|
||||||
import visaLogo from "../../assets/bank-logo/logo-visa.png"
|
import visaLogo from "@root/assets/bank-logo/logo-visa.png";
|
||||||
import qiwiLogo from "../../assets/bank-logo/logo-qiwi.png"
|
import qiwiLogo from "@root/assets/bank-logo/logo-qiwi.png";
|
||||||
import mirLogo from "../../assets/bank-logo/logo-mir.png"
|
import mirLogo from "@root/assets/bank-logo/logo-mir.png";
|
||||||
import tinkoffLogo from "../../assets/bank-logo/logo-tinkoff.png"
|
import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png";
|
||||||
import { cardShadow } from "@root/utils/theme"
|
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
||||||
import { useEffect, useLayoutEffect, useState } from "react"
|
import { cardShadow } from "@root/utils/theme";
|
||||||
import InputTextfield from "@root/components/InputTextfield"
|
import { useEffect, useLayoutEffect, useState } from "react";
|
||||||
import { sendPayment } from "@root/api/wallet"
|
import InputTextfield from "@root/components/InputTextfield";
|
||||||
import { getMessageFromFetchError } from "@frontend/kitui"
|
import { sendPayment, sendRSPayment } from "@root/api/wallet";
|
||||||
import { enqueueSnackbar } from "notistack"
|
import { getMessageFromFetchError } from "@frontend/kitui";
|
||||||
import { currencyFormatter } from "@root/utils/currencyFormatter"
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useLocation, useNavigate } from "react-router-dom"
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
|
||||||
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import { VerificationStatus } from "@root/model/account";
|
||||||
|
import { WarnModal } from "./WarnModal";
|
||||||
|
|
||||||
const paymentMethods = [
|
type PaymentMethod = {
|
||||||
{ name: "Mastercard", image: mastercardLogo },
|
label: string;
|
||||||
{ name: "Visa", image: visaLogo },
|
name: string;
|
||||||
{ name: "QIWI Кошелек", image: qiwiLogo },
|
image: string;
|
||||||
{ name: "Мир", image: mirLogo },
|
unpopular?: boolean;
|
||||||
{ name: "Тинькофф", image: tinkoffLogo },
|
};
|
||||||
] as const
|
|
||||||
|
|
||||||
type PaymentMethod = (typeof paymentMethods)[number]["name"];
|
const paymentMethods: PaymentMethod[] = [
|
||||||
|
{ label: "Mastercard", name: "mastercard", image: mastercardLogo },
|
||||||
|
{ label: "Visa", name: "visa", image: visaLogo },
|
||||||
|
{ label: "QIWI Кошелек", name: "qiwi", image: qiwiLogo },
|
||||||
|
{ label: "Мир", name: "mir", image: mirLogo },
|
||||||
|
{ label: "Тинькофф", name: "tinkoff", image: tinkoffLogo },
|
||||||
|
];
|
||||||
|
|
||||||
|
type PaymentMethodType = (typeof paymentMethods)[number]["name"];
|
||||||
|
|
||||||
export default function Payment() {
|
export default function Payment() {
|
||||||
const theme = useTheme()
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
||||||
useState<PaymentMethod | null>(null)
|
useState<PaymentMethodType | null>(null);
|
||||||
const [paymentValueField, setPaymentValueField] = useState<string>("0")
|
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
||||||
const [paymentLink, setPaymentLink] = useState<string>("")
|
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
||||||
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false)
|
const [paymentLink, setPaymentLink] = useState<string>("");
|
||||||
const location = useLocation()
|
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
|
||||||
|
const location = useLocation();
|
||||||
|
const verificationStatus = useUserStore((state) => state.verificationStatus);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const notEnoughMoneyAmount =
|
const notEnoughMoneyAmount =
|
||||||
(location.state?.notEnoughMoneyAmount as number) ?? 0
|
(location.state?.notEnoughMoneyAmount as number) ?? 0;
|
||||||
|
|
||||||
const paymentValue = parseFloat(paymentValueField) * 100
|
const paymentValue = parseFloat(paymentValueField) * 100;
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
|
||||||
setPaymentValueField((notEnoughMoneyAmount / 100).toString())
|
const params = new URLSearchParams(window.location.search);
|
||||||
const params = new URLSearchParams(window.location.search)
|
const fromSquiz = params.get("action");
|
||||||
const fromSquiz = params.get("action")
|
|
||||||
if (fromSquiz === "squizpay") {
|
if (fromSquiz === "squizpay") {
|
||||||
setIsFromSquiz(true)
|
setIsFromSquiz(true);
|
||||||
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString())
|
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
|
||||||
}
|
}
|
||||||
history.pushState(null, document.title, "/payment");
|
history.pushState(null, document.title, "/payment");
|
||||||
console.log(fromSquiz)
|
console.log(fromSquiz);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPaymentLink("")
|
setPaymentLink("");
|
||||||
}, [selectedPaymentMethod])
|
}, [selectedPaymentMethod]);
|
||||||
|
|
||||||
async function handleChoosePaymentClick() {
|
async function handleChoosePaymentClick() {
|
||||||
if (Number(paymentValueField) !== 0) {
|
if (Number(paymentValueField) === 0) {
|
||||||
const [sendPaymentResponse, sendPaymentError] = await sendPayment({fromSquiz})
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPaymentMethod !== "rspay") {
|
||||||
|
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
||||||
|
fromSquiz,
|
||||||
|
});
|
||||||
|
|
||||||
if (sendPaymentError) {
|
if (sendPaymentError) {
|
||||||
return enqueueSnackbar(sendPaymentError)
|
return enqueueSnackbar(sendPaymentError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendPaymentResponse) {
|
if (sendPaymentResponse) {
|
||||||
setPaymentLink(sendPaymentResponse.link)
|
setPaymentLink(sendPaymentResponse.link);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCustomBackNavigation = useHistoryTracker()
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomBackNavigation = useHistoryTracker();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionWrapper
|
<SectionWrapper
|
||||||
@ -137,15 +159,42 @@ export default function Payment() {
|
|||||||
alignContent: "start",
|
alignContent: "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{paymentMethods.map((method) => (
|
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
|
||||||
<PaymentMethodCard
|
<PaymentMethodCard
|
||||||
isSelected={selectedPaymentMethod === method.name}
|
isSelected={selectedPaymentMethod === name}
|
||||||
key={method.name}
|
key={name}
|
||||||
name={method.name}
|
label={label}
|
||||||
image={method.image}
|
image={image}
|
||||||
onClick={() => setSelectedPaymentMethod(method.name)}
|
onClick={() => setSelectedPaymentMethod(name)}
|
||||||
|
unpopular={unpopular}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<PaymentMethodCard
|
||||||
|
isSelected={false}
|
||||||
|
label={"Расчётный счёт"}
|
||||||
|
image={rsPayLogo}
|
||||||
|
onClick={async() => {
|
||||||
|
|
||||||
|
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
|
setWarnModalOpen(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendRSPaymentError = await sendRSPayment();
|
||||||
|
|
||||||
|
if (sendRSPaymentError) {
|
||||||
|
return enqueueSnackbar(sendRSPaymentError);
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar(
|
||||||
|
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
||||||
|
);
|
||||||
|
|
||||||
|
navigate("/settings");
|
||||||
|
}}
|
||||||
|
unpopular={true}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -237,6 +286,7 @@ export default function Payment() {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material"
|
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
label: string;
|
||||||
image: string;
|
image: string;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
|
unpopular?: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PaymentMethodCard({ name, image, isSelected, onClick }: Props) {
|
export default function PaymentMethodCard({
|
||||||
const theme = useTheme()
|
label,
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
|
image,
|
||||||
|
isSelected,
|
||||||
|
unpopular,
|
||||||
|
onClick,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@ -20,24 +27,29 @@ export default function PaymentMethodCard({ name, image, isSelected, onClick }:
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "start",
|
justifyContent: "start",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
|
filter: unpopular ? "saturate(0.6) brightness(0.85)" : null,
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
border: isSelected ? `1px solid ${theme.palette.purple.main}` : `1px solid ${theme.palette.gray.main}`,
|
border: isSelected
|
||||||
gap: "20px",
|
? `1px solid ${theme.palette.purple.main}`
|
||||||
|
: `1px solid ${theme.palette.gray.main}`,
|
||||||
|
gap: "15px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
boxShadow: isSelected ? `0 0 0 1.5px ${theme.palette.purple.main};` : "none",
|
boxShadow: isSelected
|
||||||
|
? `0 0 0 1.5px ${theme.palette.purple.main};`
|
||||||
|
: "none",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.purple.main,
|
backgroundColor: theme.palette.purple.main,
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
border: `1px solid ${theme.palette.purple.main}`,
|
||||||
"& > p": {
|
"& > p": {
|
||||||
color: "white",
|
color: "white",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<img src={image} alt="payment method" />
|
<img src={image} alt="payment method" />
|
||||||
<Typography sx={{ color: theme.palette.gray.dark }}>{name}</Typography>
|
<Typography sx={{ color: theme.palette.gray.dark }}>{label}</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
61
src/pages/Payment/WarnModal.tsx
Normal file
61
src/pages/Payment/WarnModal.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Modal, Box, Typography, Button, useTheme } from "@mui/material";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
type WarnModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (isOpen: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WarnModal = ({ open, setOpen }: WarnModalProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
margin: "10px",
|
||||||
|
padding: "25px",
|
||||||
|
maxWidth: "600px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
textAlign: "center",
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||||
|
Верификация не пройдена.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "20px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="pena-outlined-purple" onClick={() => setOpen(false)}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="pena-outlined-purple"
|
||||||
|
onClick={() => navigate("/settings")}
|
||||||
|
>
|
||||||
|
Пройти верификацию
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -9,14 +9,14 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material"
|
} from "@mui/material";
|
||||||
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"
|
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useParams } from "react-router-dom"
|
import { useParams } from "react-router-dom";
|
||||||
import SendIcon from "@components/icons/SendIcon"
|
import SendIcon from "@components/icons/SendIcon";
|
||||||
import { throttle, useToken } from "@frontend/kitui"
|
import { throttle, useToken } from "@frontend/kitui";
|
||||||
import { enqueueSnackbar } from "notistack"
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useTicketStore } from "@root/stores/tickets"
|
import { useTicketStore } from "@root/stores/tickets";
|
||||||
import {
|
import {
|
||||||
addOrUpdateMessages,
|
addOrUpdateMessages,
|
||||||
clearMessageState,
|
clearMessageState,
|
||||||
@ -24,38 +24,43 @@ import {
|
|||||||
setIsPreventAutoscroll,
|
setIsPreventAutoscroll,
|
||||||
setTicketMessageFetchState,
|
setTicketMessageFetchState,
|
||||||
useMessageStore,
|
useMessageStore,
|
||||||
} from "@root/stores/messages"
|
} from "@root/stores/messages";
|
||||||
import { TicketMessage } from "@frontend/kitui"
|
import { TicketMessage } from "@frontend/kitui";
|
||||||
import ChatMessage from "@root/components/ChatMessage"
|
import ChatMessage from "@root/components/ChatMessage";
|
||||||
import { cardShadow } from "@root/utils/theme"
|
import { cardShadow } from "@root/utils/theme";
|
||||||
import {
|
import {
|
||||||
getMessageFromFetchError,
|
getMessageFromFetchError,
|
||||||
useEventListener,
|
useEventListener,
|
||||||
useSSESubscription,
|
useSSESubscription,
|
||||||
useTicketMessages,
|
useTicketMessages,
|
||||||
} from "@frontend/kitui"
|
} from "@frontend/kitui";
|
||||||
import { shownMessage, sendTicketMessage } from "@root/api/ticket"
|
import { shownMessage, sendTicketMessage } from "@root/api/ticket";
|
||||||
import { withErrorBoundary } from "react-error-boundary"
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
import { handleComponentError } from "@root/utils/handleComponentError"
|
import { handleComponentError } from "@root/utils/handleComponentError";
|
||||||
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
|
||||||
function SupportChat() {
|
function SupportChat() {
|
||||||
const theme = useTheme()
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.up(460))
|
const isMobile = useMediaQuery(theme.breakpoints.up(460));
|
||||||
const [messageField, setMessageField] = useState<string>("")
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
const tickets = useTicketStore((state) => state.tickets)
|
const tickets = useTicketStore((state) => state.tickets);
|
||||||
const messages = useMessageStore((state) => state.messages)
|
const messages = useMessageStore((state) => state.messages);
|
||||||
const messageApiPage = useMessageStore((state) => state.apiPage)
|
const messageApiPage = useMessageStore((state) => state.apiPage);
|
||||||
const lastMessageId = useMessageStore((state) => state.lastMessageId)
|
const lastMessageId = useMessageStore((state) => state.lastMessageId);
|
||||||
const messagesPerPage = useMessageStore((state) => state.messagesPerPage)
|
const messagesPerPage = useMessageStore((state) => state.messagesPerPage);
|
||||||
const isPreventAutoscroll = useMessageStore(
|
const isPreventAutoscroll = useMessageStore(
|
||||||
(state) => state.isPreventAutoscroll
|
(state) => state.isPreventAutoscroll
|
||||||
)
|
);
|
||||||
const token = useToken()
|
const token = useToken();
|
||||||
const ticketId = useParams().ticketId
|
const ticketId = useParams().ticketId;
|
||||||
const ticket = tickets.find((ticket) => ticket.id === ticketId)
|
const ticket = tickets.find((ticket) => ticket.id === ticketId);
|
||||||
const chatBoxRef = useRef<HTMLDivElement>(null)
|
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||||
const fetchState = useMessageStore((state) => state.ticketMessageFetchState)
|
const fetchState = useMessageStore((state) => state.ticketMessageFetchState);
|
||||||
|
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
||||||
|
"supportChat",
|
||||||
|
addOrUpdateMessages
|
||||||
|
);
|
||||||
|
|
||||||
useTicketMessages({
|
useTicketMessages({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
|
||||||
@ -64,96 +69,101 @@ function SupportChat() {
|
|||||||
messageApiPage,
|
messageApiPage,
|
||||||
onSuccess: (messages) => {
|
onSuccess: (messages) => {
|
||||||
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
|
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
|
||||||
chatBoxRef.current.scrollTop = 1
|
chatBoxRef.current.scrollTop = 1;
|
||||||
addOrUpdateMessages(messages)
|
addOrUpdateMessages(messages);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const message = getMessageFromFetchError(error)
|
const message = getMessageFromFetchError(error);
|
||||||
if (message) enqueueSnackbar(message)
|
if (message) enqueueSnackbar(message);
|
||||||
},
|
},
|
||||||
onFetchStateChange: setTicketMessageFetchState,
|
onFetchStateChange: setTicketMessageFetchState,
|
||||||
})
|
});
|
||||||
|
|
||||||
useSSESubscription<TicketMessage>({
|
useSSESubscription<TicketMessage>({
|
||||||
enabled: Boolean(token) && Boolean(ticketId),
|
enabled: isActiveSSETab && Boolean(token) && Boolean(ticketId),
|
||||||
url: process.env.REACT_APP_DOMAIN + `/heruvym/ticket?ticket=${ticketId}&Authorization=${token}`,
|
url:
|
||||||
onNewData: addOrUpdateMessages,
|
process.env.REACT_APP_DOMAIN +
|
||||||
|
`/heruvym/ticket?ticket=${ticketId}&Authorization=${token}`,
|
||||||
|
onNewData: (ticketMessages) => {
|
||||||
|
updateSSEValue(ticketMessages);
|
||||||
|
addOrUpdateMessages(ticketMessages);
|
||||||
|
},
|
||||||
onDisconnect: useCallback(() => {
|
onDisconnect: useCallback(() => {
|
||||||
clearMessageState()
|
clearMessageState();
|
||||||
setIsPreventAutoscroll(false)
|
setIsPreventAutoscroll(false);
|
||||||
}, []),
|
}, []),
|
||||||
marker: "ticket message",
|
marker: "ticket message",
|
||||||
})
|
});
|
||||||
|
|
||||||
const throttledScrollHandler = useMemo(
|
const throttledScrollHandler = useMemo(
|
||||||
() =>
|
() =>
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
const chatBox = chatBoxRef.current
|
const chatBox = chatBoxRef.current;
|
||||||
if (!chatBox) return
|
if (!chatBox) return;
|
||||||
|
|
||||||
const scrollBottom =
|
const scrollBottom =
|
||||||
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight
|
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight;
|
||||||
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight * 20
|
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight * 20;
|
||||||
setIsPreventAutoscroll(isPreventAutoscroll)
|
setIsPreventAutoscroll(isPreventAutoscroll);
|
||||||
|
|
||||||
if (fetchState !== "idle") return
|
if (fetchState !== "idle") return;
|
||||||
|
|
||||||
if (chatBox.scrollTop < chatBox.clientHeight) {
|
if (chatBox.scrollTop < chatBox.clientHeight) {
|
||||||
incrementMessageApiPage()
|
incrementMessageApiPage();
|
||||||
}
|
}
|
||||||
}, 200),
|
}, 200),
|
||||||
[fetchState]
|
[fetchState]
|
||||||
)
|
);
|
||||||
|
|
||||||
useEventListener("scroll", throttledScrollHandler, chatBoxRef)
|
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
function scrollOnNewMessage() {
|
function scrollOnNewMessage() {
|
||||||
if (!chatBoxRef.current) return
|
if (!chatBoxRef.current) return;
|
||||||
|
|
||||||
if (!isPreventAutoscroll) {
|
if (!isPreventAutoscroll) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollToBottom()
|
scrollToBottom();
|
||||||
}, 50)
|
}, 50);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[lastMessageId]
|
[lastMessageId]
|
||||||
)
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ticket) {
|
if (ticket) {
|
||||||
shownMessage(ticket.top_message.id)
|
shownMessage(ticket.top_message.id);
|
||||||
}
|
}
|
||||||
}, [ticket])
|
}, [ticket]);
|
||||||
|
|
||||||
async function handleSendMessage() {
|
async function handleSendMessage() {
|
||||||
if (!ticket || !messageField) return
|
if (!ticket || !messageField) return;
|
||||||
|
|
||||||
const [, sendTicketMessageError] = await sendTicketMessage(
|
const [, sendTicketMessageError] = await sendTicketMessage(
|
||||||
ticket.id,
|
ticket.id,
|
||||||
messageField
|
messageField
|
||||||
)
|
);
|
||||||
|
|
||||||
if (sendTicketMessageError) {
|
if (sendTicketMessageError) {
|
||||||
return enqueueSnackbar(sendTicketMessageError)
|
return enqueueSnackbar(sendTicketMessageError);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessageField("")
|
setMessageField("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom(behavior?: ScrollBehavior) {
|
function scrollToBottom(behavior?: ScrollBehavior) {
|
||||||
if (!chatBoxRef.current) return
|
if (!chatBoxRef.current) return;
|
||||||
|
|
||||||
const chatBox = chatBoxRef.current
|
const chatBox = chatBoxRef.current;
|
||||||
chatBox.scroll({
|
chatBox.scroll({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: chatBox.scrollHeight,
|
top: chatBox.scrollHeight,
|
||||||
behavior,
|
behavior,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdAt = ticket && new Date(ticket.created_at)
|
const createdAt = ticket && new Date(ticket.created_at);
|
||||||
const createdAtString =
|
const createdAtString =
|
||||||
createdAt &&
|
createdAt &&
|
||||||
createdAt.toLocaleDateString(undefined, {
|
createdAt.toLocaleDateString(undefined, {
|
||||||
@ -165,7 +175,7 @@ function SupportChat() {
|
|||||||
createdAt.toLocaleTimeString(undefined, {
|
createdAt.toLocaleTimeString(undefined, {
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -318,10 +328,14 @@ function SupportChat() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withErrorBoundary(SupportChat, {
|
export default withErrorBoundary(SupportChat, {
|
||||||
fallback: <Typography mt="8px" textAlign="center">Не удалось отобразить чат</Typography>,
|
fallback: (
|
||||||
|
<Typography mt="8px" textAlign="center">
|
||||||
|
Не удалось отобразить чат
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
onError: handleComponentError,
|
onError: handleComponentError,
|
||||||
})
|
});
|
||||||
|
93
src/utils/hooks/useSSETab.ts
Normal file
93
src/utils/hooks/useSSETab.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
type UseSSETabResult = {
|
||||||
|
isActiveSSETab: boolean;
|
||||||
|
updateSSEValue: <T>(value: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSSETab = <T = unknown>(
|
||||||
|
sseName: string,
|
||||||
|
onUpdateValue?: (value: T) => void
|
||||||
|
): UseSSETabResult => {
|
||||||
|
const [openTimeSetted, seteOpenTimeSetted] = useState<boolean>(false);
|
||||||
|
const [activeSSETab, setActiveSSETab] = useState<boolean>(false);
|
||||||
|
const updateTimeIntervalId = useRef<NodeJS.Timer | null>(null);
|
||||||
|
const checkConnectionIntervalId = useRef<NodeJS.Timer | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpenTime();
|
||||||
|
checkConnectionIntervalId.current = setInterval(checkConnection, 5000);
|
||||||
|
|
||||||
|
const onUpdate = (event: StorageEvent) => {
|
||||||
|
if (event.key === `sse-${sseName}-update` && event.newValue) {
|
||||||
|
onUpdateValue?.(JSON.parse(event.newValue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("storage", onUpdate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (checkConnectionIntervalId.current) {
|
||||||
|
clearInterval(checkConnectionIntervalId.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener("storage", onUpdate);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeSSETab) {
|
||||||
|
if (updateTimeIntervalId.current) {
|
||||||
|
clearInterval(updateTimeIntervalId.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTime();
|
||||||
|
updateTimeIntervalId.current = setInterval(updateTime, 5000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setActiveSSETab(false);
|
||||||
|
|
||||||
|
if (updateTimeIntervalId.current) {
|
||||||
|
clearInterval(updateTimeIntervalId.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [activeSSETab]);
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
const time = new Date().getTime();
|
||||||
|
|
||||||
|
localStorage.setItem(`sse-${sseName}`, String(time));
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkConnection = (): boolean => {
|
||||||
|
const time = new Date().getTime();
|
||||||
|
const lastMessageTime = Number(localStorage.getItem(`sse-${sseName}`));
|
||||||
|
|
||||||
|
if (time - lastMessageTime > 15000) {
|
||||||
|
setActiveSSETab(true);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setOpenTime = () => {
|
||||||
|
if (openTimeSetted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkConnection()) {
|
||||||
|
setActiveSSETab(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
seteOpenTimeSetted(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSSEValue = <T>(value: T) => {
|
||||||
|
localStorage.setItem(`sse-${sseName}-update`, JSON.stringify(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
return { isActiveSSETab: activeSSETab, updateSSEValue };
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user