Merge branch 'dev' into 'staging'
Dev See merge request frontend/squiz!390
This commit is contained in:
commit
cca1877021
@ -23,6 +23,7 @@ import { InfoPrivilege } from "./pages/InfoPrivilege";
|
||||
import AmoTokenExpiredDialog from "./pages/IntegrationsPage/IntegrationsModal/Amo/AmoTokenExpiredDialog";
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
import Main from "./pages/main";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
|
||||
const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull"));
|
||||
const QuizGallery = lazy(() => import("./pages/createQuize/QuizGallery"));
|
||||
|
@ -8,6 +8,7 @@ import { clearUserData } from "@root/user";
|
||||
import { clearQuizData } from "@root/quizes/store";
|
||||
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { selectSendingMethod } from "@/ui_kit/FloatingSupportChat/utils";
|
||||
|
||||
interface MakeRequest {
|
||||
method?: Method | undefined;
|
||||
@ -32,6 +33,11 @@ export const makeRequest = async <TRequest = unknown, TResponse = unknown>(
|
||||
} catch (nativeError) {
|
||||
const error = nativeError as AxiosError;
|
||||
|
||||
selectSendingMethod({
|
||||
messageField: `status: ${error.response?.status}. Message ${(error.response?.data as ExtendedAxiosResponse)?.message}`,
|
||||
isSnackbar: false,
|
||||
systemError: true
|
||||
});
|
||||
if (
|
||||
error.response?.status === 400 &&
|
||||
(error.response?.data as ExtendedAxiosResponse)?.message ===
|
||||
|
@ -12,7 +12,7 @@ export const getTariffs = async (
|
||||
try {
|
||||
const tariffs = await makeRequest<never, GetTariffsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}?page=${page}&limit=100`,
|
||||
url: `${API_URL}/getList?page=${page}&limit=100`,
|
||||
});
|
||||
return [tariffs];
|
||||
} catch (nativeError) {
|
||||
|
@ -18,6 +18,7 @@ const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0`;
|
||||
export const sendTicketMessage = async (
|
||||
ticketId: string,
|
||||
message: string,
|
||||
systemError: boolean
|
||||
): Promise<[null, string?]> => {
|
||||
try {
|
||||
const sendTicketMessageResponse = await makeRequest<
|
||||
@ -27,7 +28,8 @@ export const sendTicketMessage = async (
|
||||
url: `${API_URL}/send`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||
body: { ticket: ticketId, message: message, lang: "ru", files: [], system: systemError },
|
||||
|
||||
});
|
||||
|
||||
return [sendTicketMessageResponse];
|
||||
@ -82,11 +84,12 @@ export const sendFile = async (
|
||||
export const createTicket = async (
|
||||
message: string,
|
||||
useToken: boolean,
|
||||
systemError: boolean
|
||||
): Promise<[CreateTicketResponse | null, string?]> => {
|
||||
try {
|
||||
const createdTicket = await createTicketRequest({
|
||||
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/create`,
|
||||
body: { Title: "Unauth title", Message: message },
|
||||
body: { Title: "Unauth title", Message: message, system: systemError },
|
||||
useToken,
|
||||
});
|
||||
|
||||
|
@ -17,6 +17,8 @@ import CloseIcon from "@icons/CloseBold";
|
||||
|
||||
import type { SnackbarKey } from "notistack";
|
||||
import { CheckFastlink } from "@ui_kit/CheckFastlink";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { handleComponentError } from "./utils/handleComponentError";
|
||||
|
||||
moment.locale("ru");
|
||||
polyfillCountryFlagEmojis();
|
||||
@ -36,6 +38,8 @@ const snackbarAction = (snackbarId: SnackbarKey) => (
|
||||
</Button>
|
||||
);
|
||||
|
||||
const ApologyPage = () => <div><p>Что-то пошло не так</p></div>
|
||||
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
|
||||
root.render(
|
||||
@ -60,7 +64,13 @@ root.render(
|
||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||
>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ApologyPage}
|
||||
onError={handleComponentError}
|
||||
>
|
||||
<App />
|
||||
</ErrorBoundary>
|
||||
<CheckFastlink />
|
||||
</SnackbarProvider>
|
||||
</BrowserRouter>
|
||||
|
@ -128,10 +128,6 @@ export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, que
|
||||
})
|
||||
|
||||
}
|
||||
console.log("got questions")
|
||||
console.log(gottenQuestions)
|
||||
console.log("settingsResponse")
|
||||
console.log(settingsResponse.FieldsRule)
|
||||
|
||||
if (key === "Contact") {
|
||||
const MAP = settingsResponse.FieldsRule[key as QuestionKeys].ContactRuleMap
|
||||
|
@ -74,7 +74,7 @@ function TariffPage() {
|
||||
const tariffsList: Tariff[] = [];
|
||||
let page = 2
|
||||
const [tariffsResponse, tariffsResponseError] = await getTariffs(page - 1);
|
||||
|
||||
console.log(tariffsResponse)
|
||||
if (tariffsResponseError || !tariffsResponse) {
|
||||
return tariffsList;
|
||||
}
|
||||
@ -182,7 +182,7 @@ function TariffPage() {
|
||||
inCart();
|
||||
};
|
||||
|
||||
const filteredTariffs = tariffs.filter((tariff) => {
|
||||
const filteredTariffs = tariffs.filter((tariff, i) => {
|
||||
return (
|
||||
tariff.privileges[0].serviceKey === "squiz" &&
|
||||
!tariff.isDeleted &&
|
||||
|
@ -17,6 +17,8 @@ export const createTariffElements = (
|
||||
cc?: boolean,
|
||||
icon?: ReactNode
|
||||
) => {
|
||||
console.log("start work createTariffElements")
|
||||
console.log("filteredTariffs ", filteredTariffs)
|
||||
const tariffElements = filteredTariffs
|
||||
.filter((tariff) => tariff.privileges.length > 0)
|
||||
.map((tariff, index) => {
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
import { createTicket, sendFile as sendFileRequest } from "@api/ticket";
|
||||
import { selectSendingMethod } from "./utils";
|
||||
|
||||
type ModalWarningType =
|
||||
| "errorType"
|
||||
@ -47,10 +48,9 @@ const ACCEPT_SEND_FILE_TYPES_MAP = [
|
||||
|
||||
export default () => {
|
||||
const user = useUserStore((state) => state.user?._id);
|
||||
const ticket = useTicketStore(
|
||||
(state) => state[user ? "authData" : "unauthData"],
|
||||
);
|
||||
const ticket = useTicketStore(state => state[user ? "authData" : "unauthData"]);
|
||||
|
||||
//Запись SEE в учётник
|
||||
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
||||
"ticket",
|
||||
addOrUpdateUnauthMessages,
|
||||
@ -192,40 +192,13 @@ export default () => {
|
||||
|
||||
const sendMessage = async (messageField: string) => {
|
||||
if (!messageField || ticket.isMessageSending) return false;
|
||||
|
||||
setSseEnabled(true);
|
||||
let successful = false;
|
||||
setIsMessageSending(true);
|
||||
if (!ticket.sessionData?.ticketId) {
|
||||
const [data, createError] = await createTicket(
|
||||
messageField,
|
||||
Boolean(user),
|
||||
);
|
||||
|
||||
if (createError || !data) {
|
||||
successful = false;
|
||||
|
||||
enqueueSnackbar(createError);
|
||||
} else {
|
||||
successful = true;
|
||||
|
||||
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
|
||||
}
|
||||
|
||||
setIsMessageSending(false);
|
||||
} else {
|
||||
const [_, sendTicketMessageError] = await sendTicketMessage(
|
||||
ticket.sessionData?.ticketId,
|
||||
messageField,
|
||||
);
|
||||
successful = true;
|
||||
|
||||
if (sendTicketMessageError) {
|
||||
successful = false;
|
||||
enqueueSnackbar(sendTicketMessageError);
|
||||
}
|
||||
setIsMessageSending(false);
|
||||
}
|
||||
let successful = await selectSendingMethod({messageField});
|
||||
|
||||
setIsMessageSending(false);
|
||||
return successful;
|
||||
};
|
||||
const sendFile = async (file: File) => {
|
||||
|
266
src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts
Normal file
266
src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts
Normal file
@ -0,0 +1,266 @@
|
||||
import { sendTicketMessage, shownMessage } from "@/api/ticket";
|
||||
import { useSSETab } from "@/utils/hooks/useSSETab";
|
||||
import { parseAxiosError } from "@/utils/parse-error";
|
||||
import { TicketMessage, createTicket, useSSESubscription, useTicketMessages, useTicketsFetcher, sendFile as sf } from "@frontend/kitui";
|
||||
|
||||
import {
|
||||
addOrUpdateUnauthMessages,
|
||||
cleanAuthTicketData,
|
||||
cleanUnauthTicketData,
|
||||
setIsMessageSending,
|
||||
setTicketData,
|
||||
setUnauthIsPreventAutoscroll,
|
||||
setUnauthTicketMessageFetchState,
|
||||
useTicketStore,
|
||||
} from "@root/ticket";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
userId?: string;
|
||||
|
||||
}
|
||||
|
||||
type ModalWarningType =
|
||||
| "errorType"
|
||||
| "errorSize"
|
||||
| "picture"
|
||||
| "video"
|
||||
| "audio"
|
||||
| "document"
|
||||
| null;
|
||||
const MAX_FILE_SIZE = 419430400;
|
||||
const ACCEPT_SEND_FILE_TYPES_MAP = [
|
||||
".jpeg",
|
||||
".jpg",
|
||||
".png",
|
||||
".mp4",
|
||||
".doc",
|
||||
".docx",
|
||||
".pdf",
|
||||
".txt",
|
||||
".xlsx",
|
||||
".csv",
|
||||
] as const;
|
||||
export default ({ userId }:Props) => {
|
||||
const ticket = useTicketStore((state) => state[userId ? "authData" : "unauthData"]);
|
||||
|
||||
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
||||
"ticket",
|
||||
addOrUpdateUnauthMessages,
|
||||
);
|
||||
|
||||
const [modalWarningType, setModalWarningType] =
|
||||
useState<ModalWarningType>(null);
|
||||
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
|
||||
const [sseEnabled, setSseEnabled] = useState(true);
|
||||
|
||||
const handleChatClickOpen = () => {
|
||||
setIsChatOpened(true);
|
||||
};
|
||||
const handleChatClickClose = () => {
|
||||
setIsChatOpened(false);
|
||||
};
|
||||
const handleChatClickSwitch = () => {
|
||||
setIsChatOpened((state) => !state);
|
||||
};
|
||||
|
||||
const getGreetingMessage: TicketMessage = useMemo(() => {
|
||||
const workingHoursMessage =
|
||||
"Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
||||
const offHoursMessage =
|
||||
"Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
||||
const date = new Date();
|
||||
const currentHourUTC = date.getUTCHours();
|
||||
const MscTime = 3; // Москва UTC+3;
|
||||
const moscowHour = (currentHourUTC + MscTime) % 24;
|
||||
const greetingMessage =
|
||||
moscowHour >= 3 && moscowHour < 10
|
||||
? offHoursMessage
|
||||
: workingHoursMessage;
|
||||
|
||||
return {
|
||||
created_at: new Date().toISOString(),
|
||||
files: [],
|
||||
id: "111",
|
||||
message: greetingMessage,
|
||||
request_screenshot: "",
|
||||
session_id: "greetingMessage",
|
||||
shown: { me: 1 },
|
||||
ticket_id: "111",
|
||||
user_id: "greetingMessage",
|
||||
};
|
||||
}, [isChatOpened]);
|
||||
|
||||
useTicketsFetcher({
|
||||
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`,
|
||||
ticketsPerPage: 10,
|
||||
ticketApiPage: 0,
|
||||
onSuccess: (result) => {
|
||||
if (result.data?.length) {
|
||||
const currentTicket = result.data.find(
|
||||
({ origin }) => !origin.includes("/support"),
|
||||
);
|
||||
|
||||
if (!currentTicket) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTicketData({
|
||||
ticketId: currentTicket.id,
|
||||
sessionId: currentTicket.sess,
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
const message = parseAxiosError(error);
|
||||
if (message) enqueueSnackbar(message);
|
||||
},
|
||||
onFetchStateChange: () => {},
|
||||
enabled: Boolean(userId),
|
||||
});
|
||||
|
||||
useTicketMessages({
|
||||
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getMessages`,
|
||||
isUnauth: true,
|
||||
ticketId: ticket.sessionData?.ticketId,
|
||||
messagesPerPage: ticket.messagesPerPage,
|
||||
messageApiPage: ticket.apiPage,
|
||||
onSuccess: useCallback((messages) => {
|
||||
addOrUpdateUnauthMessages(messages);
|
||||
}, []),
|
||||
onError: useCallback((error: Error) => {
|
||||
if (error.name === "CanceledError") {
|
||||
return;
|
||||
}
|
||||
|
||||
const [message] = parseAxiosError(error);
|
||||
if (message) enqueueSnackbar(message);
|
||||
}, []),
|
||||
onFetchStateChange: setUnauthTicketMessageFetchState,
|
||||
});
|
||||
|
||||
useSSESubscription<TicketMessage>({
|
||||
enabled:
|
||||
sseEnabled && isActiveSSETab && Boolean(ticket.sessionData?.sessionId),
|
||||
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/ticket?ticket=${ticket.sessionData?.ticketId}&s=${ticket.sessionData?.sessionId}`,
|
||||
onNewData: (ticketMessages) => {
|
||||
const isTicketClosed = ticketMessages.some(
|
||||
(message) => message.session_id === "close",
|
||||
);
|
||||
if (isTicketClosed) {
|
||||
cleanAuthTicketData();
|
||||
addOrUpdateUnauthMessages([getGreetingMessage]);
|
||||
if (!userId) {
|
||||
cleanUnauthTicketData();
|
||||
localStorage.removeItem("unauth-ticket");
|
||||
}
|
||||
return;
|
||||
}
|
||||
updateSSEValue(ticketMessages);
|
||||
addOrUpdateUnauthMessages(ticketMessages);
|
||||
},
|
||||
onDisconnect: useCallback(() => {
|
||||
setUnauthIsPreventAutoscroll(false);
|
||||
setSseEnabled(false);
|
||||
}, []),
|
||||
marker: "ticket",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
cleanAuthTicketData();
|
||||
setSseEnabled(true);
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChatOpened) {
|
||||
const newMessages = ticket.messages.filter(
|
||||
({ shown }) => shown?.me !== 1,
|
||||
);
|
||||
|
||||
newMessages.map(async ({ id }) => {
|
||||
await shownMessage(id);
|
||||
});
|
||||
}
|
||||
}, [isChatOpened, ticket.messages]);
|
||||
|
||||
const sendMessage = async (messageField: string) => {
|
||||
if (!messageField || ticket.isMessageSending) return false;
|
||||
setSseEnabled(true);
|
||||
let successful = false;
|
||||
setIsMessageSending(true);
|
||||
if (!ticket.sessionData?.ticketId) {
|
||||
const [data, createError] = await createTicket(
|
||||
messageField,
|
||||
Boolean(userId),
|
||||
);
|
||||
|
||||
if (createError || !data) {
|
||||
successful = false;
|
||||
|
||||
enqueueSnackbar(createError);
|
||||
} else {
|
||||
successful = true;
|
||||
|
||||
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
|
||||
}
|
||||
|
||||
setIsMessageSending(false);
|
||||
} else {
|
||||
const [_, sendTicketMessageError] = await sendTicketMessage(
|
||||
ticket.sessionData?.ticketId,
|
||||
messageField,
|
||||
);
|
||||
successful = true;
|
||||
|
||||
if (sendTicketMessageError) {
|
||||
successful = false;
|
||||
enqueueSnackbar(sendTicketMessageError);
|
||||
}
|
||||
setIsMessageSending(false);
|
||||
}
|
||||
|
||||
return successful;
|
||||
};
|
||||
const sendFile = async (file: File) => {
|
||||
if (file === undefined) return true;
|
||||
|
||||
let ticketId = ticket.sessionData?.ticketId;
|
||||
if (!ticket.sessionData?.ticketId) {
|
||||
const [data, createError] = await createTicket("", Boolean(userId));
|
||||
ticketId = data?.Ticket;
|
||||
|
||||
if (createError || !data) {
|
||||
enqueueSnackbar(createError);
|
||||
} else {
|
||||
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
|
||||
}
|
||||
|
||||
setIsMessageSending(false);
|
||||
}
|
||||
|
||||
if (ticketId !== undefined) {
|
||||
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
|
||||
|
||||
const [_, sendFileError] = await sf(ticketId, file);
|
||||
|
||||
if (sendFileError) {
|
||||
enqueueSnackbar(sendFileError);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isChatOpened,
|
||||
handleChatClickOpen,
|
||||
handleChatClickClose,
|
||||
handleChatClickSwitch,
|
||||
sendMessage,
|
||||
sendFile,
|
||||
modalWarningType,
|
||||
setModalWarningType,
|
||||
getGreetingMessage
|
||||
};
|
||||
};
|
54
src/ui_kit/FloatingSupportChat/utils.ts
Normal file
54
src/ui_kit/FloatingSupportChat/utils.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { sendTicketMessage } from "@/api/ticket";
|
||||
import { setTicketData, useTicketStore } from "@/stores/ticket";
|
||||
import { useUserStore } from "@root/user";
|
||||
import { createTicket, sendFile as sendFileRequest } from "@api/ticket";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
interface SelectSendingMethod {
|
||||
messageField: string;
|
||||
isSnackbar?: boolean;
|
||||
systemError?: boolean;
|
||||
|
||||
}
|
||||
export const selectSendingMethod = async ({messageField, isSnackbar = true, systemError = false}: SelectSendingMethod) => {
|
||||
console.log("click")
|
||||
const user = useUserStore.getState().user?._id;
|
||||
const ticket = useTicketStore.getState()[user ? "authData" : "unauthData"];
|
||||
console.log(ticket)
|
||||
|
||||
console.log("click 2")
|
||||
let successful = false;
|
||||
|
||||
if (!ticket.sessionData?.ticketId) {
|
||||
console.log("autorisated 2")
|
||||
const [data, createError] = await createTicket(
|
||||
messageField,
|
||||
Boolean(user),
|
||||
systemError,
|
||||
);
|
||||
|
||||
if (createError || !data) {
|
||||
successful = false;
|
||||
|
||||
if (isSnackbar) enqueueSnackbar(createError);
|
||||
} else {
|
||||
successful = true;
|
||||
|
||||
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
|
||||
}
|
||||
|
||||
} else {
|
||||
const [_, sendTicketMessageError] = await sendTicketMessage(
|
||||
ticket.sessionData?.ticketId,
|
||||
messageField,
|
||||
systemError,
|
||||
);
|
||||
successful = true;
|
||||
|
||||
if (sendTicketMessageError) {
|
||||
successful = false;
|
||||
if (isSnackbar) enqueueSnackbar(sendTicketMessageError);
|
||||
}
|
||||
}
|
||||
return successful;
|
||||
}
|
47
src/utils/handleComponentError.ts
Normal file
47
src/utils/handleComponentError.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { selectSendingMethod } from "@/ui_kit/FloatingSupportChat/utils";
|
||||
import { ErrorInfo } from "react";
|
||||
|
||||
interface ComponentError {
|
||||
timestamp: number;
|
||||
message: string;
|
||||
callStack: string | undefined;
|
||||
componentStack: string | null | undefined;
|
||||
}
|
||||
|
||||
export function handleComponentError(error: Error, info: ErrorInfo) {
|
||||
const componentError: ComponentError = {
|
||||
timestamp: Math.floor(Date.now() / 1000),
|
||||
message: error.message,
|
||||
callStack: error.stack,
|
||||
componentStack: info.componentStack,
|
||||
};
|
||||
|
||||
queueErrorRequest(componentError);
|
||||
}
|
||||
|
||||
let errorsQueue: ComponentError[] = [];
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
function queueErrorRequest(error: ComponentError) {
|
||||
errorsQueue.push(error);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
sendErrorsToServer();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function sendErrorsToServer() {
|
||||
// makeRequest({
|
||||
// url: "",
|
||||
// method: "POST",
|
||||
// body: errorsQueue,
|
||||
// useToken: true,
|
||||
// });
|
||||
selectSendingMethod({
|
||||
messageField: `Fake-sending ${errorsQueue.length} errors to server ${JSON.stringify(errorsQueue)}`,
|
||||
isSnackbar: false,
|
||||
systemError: true
|
||||
});
|
||||
// errorsQueue = [];
|
||||
}
|
Loading…
Reference in New Issue
Block a user