refactor: makeRequests decomposed

This commit is contained in:
IlyaDoronin 2024-05-27 18:43:38 +03:00
parent 8e79f16640
commit 9261f3e797
21 changed files with 1586 additions and 1415 deletions

@ -9,7 +9,7 @@ import type {
RegisterResponse,
} from "@frontend/kitui";
const apiUrl = process.env.REACT_APP_DOMAIN + "/auth";
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
export async function register(
login: string,
@ -21,7 +21,7 @@ export async function register(
RegisterRequest,
RegisterResponse
>({
url: apiUrl + "/register",
url: `${API_URL}/register`,
body: { login, password, phoneNumber },
useToken: false,
withCredentials: true,
@ -41,7 +41,7 @@ export async function login(
): Promise<[LoginResponse | null, string?]> {
try {
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
url: apiUrl + "/login",
url: `${API_URL}/login`,
body: { login, password },
useToken: false,
withCredentials: true,
@ -60,14 +60,20 @@ export async function recover(
): Promise<[unknown | null, string?]> {
try {
const formData = new FormData();
formData.append("email", email);
formData.append("RedirectionURL", process.env.REACT_APP_DOMAIN + "/changepwd")
formData.append(
"RedirectionURL",
`${process.env.REACT_APP_DOMAIN}/changepwd`
);
const recoverResponse = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
body: formData,
useToken: false,
withCredentials: true,
});
return [recoverResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
@ -79,7 +85,7 @@ export async function recover(
export async function logout(): Promise<[unknown, string?]> {
try {
const logoutResponse = await makeRequest<never, void>({
url: apiUrl + "/logout",
url: `${API_URL}/logout`,
method: "POST",
useToken: true,
withCredentials: true,

@ -1,26 +1,27 @@
import { UserAccount } from "@frontend/kitui"
import makeRequest from "@api/makeRequest"
import { UserAccount } from "@frontend/kitui";
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@root/utils/parse-error"
import { parseAxiosError } from "@root/utils/parse-error";
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer"
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer`;
export async function patchCart(
tariffId: string
): Promise<[string[], string?]> {
try {
const patchCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`,
url: `${API_URL}/cart?id=${tariffId}`,
method: "PATCH",
useToken: true,
})
});
return [patchCartResponse.cart]
return [patchCartResponse.cart];
} catch (nativeError) {
let [error, status] = parseAxiosError(nativeError)
if (status === 400 && error.indexOf("invalid id") !== -1) error = "Данный тариф более недоступен"
let [error, status] = parseAxiosError(nativeError);
if (status === 400 && error.indexOf("invalid id") !== -1)
error = "Данный тариф более недоступен";
return [[], `Не удалось добавить товар в корзину. ${error}`]
return [[], `Не удалось добавить товар в корзину. ${error}`];
}
}
@ -29,32 +30,32 @@ export async function deleteCart(
): Promise<[string[], string?]> {
try {
const deleteCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`,
url: `${API_URL}/cart?id=${tariffId}`,
method: "DELETE",
useToken: true,
})
});
return [deleteCartResponse.cart]
return [deleteCartResponse.cart];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [[], `Не удалось удалить товар из корзины. ${error}`]
return [[], `Не удалось удалить товар из корзины. ${error}`];
}
}
export async function payCart(): Promise<[UserAccount | null, string?]> {
try {
const payCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + "/cart/pay",
url: `${API_URL}/cart/pay`,
method: "POST",
useToken: true,
})
});
return [payCartResponse]
return [payCartResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось оплатить товар из корзины. ${error}`]
return [null, `Не удалось оплатить товар из корзины. ${error}`];
}
}
@ -66,18 +67,18 @@ export async function patchCurrency(
{ currency: string },
UserAccount
>({
url: apiUrl + "/wallet",
url: `${API_URL}/wallet`,
method: "PATCH",
useToken: true,
body: {
currency,
},
})
});
return [patchCurrencyResponse]
return [patchCurrencyResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось изменить валюту. ${error}`]
return [null, `Не удалось изменить валюту. ${error}`];
}
}

@ -1,6 +1,6 @@
import {Tariff} from "@frontend/kitui"
import {parseAxiosError} from "@root/utils/parse-error"
import makeRequest from "@api/makeRequest"
import { Tariff } from "@frontend/kitui";
import { parseAxiosError } from "@root/utils/parse-error";
import makeRequest from "@api/makeRequest";
export interface GetHistoryResponse {
totalPages: number;
@ -37,24 +37,30 @@ export type HistoryRecord2 = {
userId: string;
};
export type KeyValue = { Key: string; Value: string | number };
export type RawDetails = {
Key: "tariffs" | "price";
Value: string | number | KeyValue[][];
}
};
export async function getHistory(): Promise<[GetHistoryResponse | GetHistoryResponse2 | null, string?]> {
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer`;
export async function getHistory(): Promise<
[GetHistoryResponse | GetHistoryResponse2 | null, string?]
> {
try {
const historyResponse = await makeRequest<never, GetHistoryResponse|GetHistoryResponse2>({
url: process.env.REACT_APP_DOMAIN + "/customer/history?page=1&limit=100&type=payCart",
method: "get",
const historyResponse = await makeRequest<
never,
GetHistoryResponse | GetHistoryResponse2
>({
url: `${API_URL}/history?page=1&limit=100&type=payCart`,
method: "GET",
useToken: true,
})
});
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
return [historyResponse] as [GetHistoryResponse2]
return [historyResponse] as [GetHistoryResponse2];
}
const checked = historyResponse.records.map((data) => {
@ -67,18 +73,56 @@ export async function getHistory(): Promise<[GetHistoryResponse | GetHistoryResp
//@ts-ignore
//data.rawDetails = buffer
const checkedRowDetails = [
(data.rawDetails as HistoryRecord["rawDetails"]).find((details) => details.Key === "tariffs") as RawDetails,
(data.rawDetails as HistoryRecord["rawDetails"]).find((details) => details.Key === "price") as KeyValue
]
return {...data, rawDetails: checkedRowDetails} as HistoryRecord
})
(data.rawDetails as HistoryRecord["rawDetails"]).find(
(details) => details.Key === "tariffs"
) as RawDetails,
(data.rawDetails as HistoryRecord["rawDetails"]).find(
(details) => details.Key === "price"
) as KeyValue,
];
return { ...data, rawDetails: checkedRowDetails } as HistoryRecord;
});
historyResponse.records = checked || []
return [historyResponse]
historyResponse.records = checked || [];
return [historyResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить историю. ${error}`]
return [null, `Не удалось получить историю. ${error}`];
}
}
export const sendReport = async (
id: string
): Promise<[unknown | null, string?]> => {
try {
const sendReportResponse = await makeRequest<{ id: string }, unknown>({
url: `${API_URL}/sendReport`,
method: "POST",
body: { id },
});
return [sendReportResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [[], `Не удалось отправить отчёт. ${error}`];
}
};
export const sendReportById = async (
tariffId: string
): Promise<[unknown | null, string?]> => {
try {
const sendReportResponse = await makeRequest<never, unknown>({
url: `${API_URL}/sendReport/${tariffId}`,
method: "POST",
});
return [sendReportResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [[], `Не удалось отправить отчёт. ${error}`];
}
};

@ -5,30 +5,44 @@ 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/cart";
interface MakeRequest { method?: Method | undefined; url: string; body?: unknown; useToken?: boolean | undefined; contentType?: boolean | undefined; responseType?: ResponseType | undefined; signal?: AbortSignal | undefined; withCredentials?: boolean | undefined; }
interface MakeRequest {
method?: Method | undefined;
url: string;
body?: unknown;
useToken?: boolean | undefined;
contentType?: boolean | undefined;
responseType?: ResponseType | undefined;
signal?: AbortSignal | undefined;
withCredentials?: boolean | undefined;
}
interface ErrorResponseData {
message?: string;
}
async function makeRequest<TRequest = unknown, TResponse = unknown>(data: MakeRequest): Promise<TResponse> {
async function makeRequest<TRequest = unknown, TResponse = unknown>(
data: MakeRequest
): Promise<TResponse> {
try {
const response = await KIT.makeRequest<unknown>(data)
const response = await KIT.makeRequest<unknown>(data);
return response as TResponse
return response as TResponse;
} catch (e) {
const error = e as AxiosError;
if (error.response?.status === 400 && (error.response?.data as ErrorResponseData)?.message === "refreshToken is empty") {
if (
error.response?.status === 400 &&
(error.response?.data as ErrorResponseData)?.message ===
"refreshToken is empty"
) {
clearAuthToken();
clearUserData();
clearCustomTariffs();
clearTickets();
setNotEnoughMoneyAmount(0)
setNotEnoughMoneyAmount(0);
redirect("/");
}
throw e
};
};
throw e;
}
}
export default makeRequest;

@ -5,7 +5,7 @@ import { parseAxiosError } from "@root/utils/parse-error";
import { enqueueSnackbar } from "notistack";
import useSWR from "swr";
const apiUrl = process.env.REACT_APP_DOMAIN + "/price";
const API_URL = `${process.env.REACT_APP_DOMAIN}/price`;
export async function getDiscounts(userId: string | null) {
if (userId === null) {
@ -14,7 +14,7 @@ export async function getDiscounts(userId: string | null) {
try {
const discountsResponse = await makeRequest<never, GetDiscountsResponse>({
url: `${apiUrl}/discount/user/${userId}`,
url: `${API_URL}/discount/user/${userId}`,
method: "get",
useToken: true,
});

@ -1,28 +1,19 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
const apiUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
const API_URL = `${process.env.REACT_APP_DOMAIN}/codeword/promocode`;
export async function activatePromocode(promocode: string) {
try {
const response = await makeRequest<
| {
codeword: string;
}
| {
fastLink: string;
},
{
greetings: string;
}
{ codeword: string } | { fastLink: string },
{ greetings: string }
>({
url: apiUrl + "/activate",
url: `${API_URL}/activate`,
method: "POST",
contentType: true,
body: {
codeword: promocode,
},
body: { codeword: promocode },
});
return response.greetings;

@ -1,17 +1,22 @@
import makeRequest from "@api/makeRequest"
import { parseAxiosError } from "@root/utils/parse-error"
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@root/utils/parse-error";
export async function getRecentlyPurchasedTariffs(): Promise<[any | null, string?]> {
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer`;
export async function getRecentlyPurchasedTariffs(): Promise<
[any | null, string?]
> {
try {
const recentlyPurchased = await makeRequest<never, any>({
url: process.env.REACT_APP_DOMAIN + "/customer/recent",
method: "get",
url: `${API_URL}/recent`,
method: "GET",
useToken: true,
})
return [recentlyPurchased]
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
});
return [null, `Не удалось получить историю. ${error}`]
return [recentlyPurchased];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить историю. ${error}`];
}
}

@ -1,12 +1,15 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { Tariff } from "@frontend/kitui";
import { parseAxiosError } from "@root/utils/parse-error";
import type { PrivilegeWithoutPrice, ServiceKeyToPrivilegesMap } from "@root/model/privilege";
import type {
PrivilegeWithoutPrice,
ServiceKeyToPrivilegesMap,
} from "@root/model/privilege";
import type { GetTariffsResponse } from "@root/model/tariff";
import { removeTariffFromCart } from "@root/stores/user";
const apiUrl = process.env.REACT_APP_DOMAIN + "/strator"
const API_URL = `${process.env.REACT_APP_DOMAIN}/strator`;
export async function getTariffs(
apiPage: number,
@ -15,7 +18,7 @@ export async function getTariffs(
): Promise<[GetTariffsResponse | null, string?]> {
try {
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
url: apiUrl + `/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
url: `${API_URL}/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
method: "get",
useToken: true,
signal,
@ -36,10 +39,12 @@ interface CreateTariffBody {
privileges: PrivilegeWithoutPrice[];
}
export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff | null, string?]> {
export async function createTariff(
tariff: CreateTariffBody
): Promise<[Tariff | null, string?]> {
try {
const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({
url: `${apiUrl}/tariff`,
url: `${API_URL}/tariff`,
method: "post",
useToken: true,
body: tariff,
@ -53,10 +58,12 @@ export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff |
}
}
export async function getTariffById(tariffId: string): Promise<[Tariff | null, string?, number?]> {
export async function getTariffById(
tariffId: string
): Promise<[Tariff | null, string?, number?]> {
try {
const getTariffByIdResponse = await makeRequest<never, Tariff>({
url: `${apiUrl}/tariff/${tariffId}`,
url: `${API_URL}/tariff/${tariffId}`,
method: "get",
useToken: true,
});
@ -73,8 +80,11 @@ export async function getCustomTariffs(
signal: AbortSignal | undefined
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> {
try {
const customTariffsResponse = await makeRequest<null, ServiceKeyToPrivilegesMap>({
url: apiUrl + "/privilege/service",
const customTariffsResponse = await makeRequest<
null,
ServiceKeyToPrivilegesMap
>({
url: `${API_URL}/privilege/service`,
signal,
method: "get",
useToken: true,
@ -82,7 +92,7 @@ export async function getCustomTariffs(
const tempCustomTariffsResponse = {
...customTariffsResponse,
squiz: customTariffsResponse.squiz
squiz: customTariffsResponse.squiz,
};
return [tempCustomTariffsResponse];
@ -96,13 +106,15 @@ export async function getCustomTariffs(
export async function getTariffArray(tariffIds: string[] | undefined) {
if (!tariffIds) return null;
const responses = await Promise.allSettled(tariffIds.map(tariffId =>
const responses = await Promise.allSettled(
tariffIds.map((tariffId) =>
makeRequest<never, Tariff>({
url: `${apiUrl}/tariff/${tariffId}`,
url: `${API_URL}/tariff/${tariffId}`,
method: "get",
useToken: true,
})
));
)
);
const tariffs: Tariff[] = [];

@ -1,9 +1,13 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@root/utils/parse-error";
import { SendTicketMessageRequest } from "@frontend/kitui";
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
type SendFileResponse = {
message: string;
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym`;
export async function sendTicketMessage(
ticketId: string,
@ -14,7 +18,7 @@ export async function sendTicketMessage(
SendTicketMessageRequest,
null
>({
url: `${apiUrl}/send`,
url: `${API_URL}/send`,
method: "POST",
useToken: true,
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
@ -31,7 +35,7 @@ export async function sendTicketMessage(
export async function shownMessage(id: string): Promise<[null, string?]> {
try {
const shownMessageResponse = await makeRequest<{ id: string }, null>({
url: apiUrl + "/shown",
url: `${API_URL}/shown`,
method: "POST",
useToken: true,
body: { id },
@ -44,3 +48,27 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
return [null, `Не удалось прочесть сообщение. ${error}`];
}
}
export const sendFile = async (
ticketId: string,
file: File
): Promise<[SendFileResponse | null, string?]> => {
try {
const body = new FormData();
body.append(file.name, file);
body.append("ticket", ticketId);
const sendResponse = await makeRequest<FormData, SendFileResponse>({
method: "POST",
url: `${process.env.REACT_APP_DOMAIN}/sendFiles`,
body,
});
return [sendResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отправить файл. ${error}`];
}
};

@ -1,27 +1,27 @@
import { User } from "@frontend/kitui"
import makeRequest from "@api/makeRequest"
import { PatchUserRequest } from "@root/model/user"
import { parseAxiosError } from "@root/utils/parse-error"
import { User } from "@frontend/kitui";
import makeRequest from "@api/makeRequest";
import { PatchUserRequest } from "@root/model/user";
import { parseAxiosError } from "@root/utils/parse-error";
const apiUrl = process.env.REACT_APP_DOMAIN + "/user"
const API_URL = `${process.env.REACT_APP_DOMAIN}/user`;
export async function patchUser(
user: PatchUserRequest
): Promise<[User | null, string?]> {
try {
const patchUserResponse = await makeRequest<PatchUserRequest, User>({
url: apiUrl+"/",
url: `${API_URL}/`,
contentType: true,
method: "PATCH",
useToken: true,
withCredentials: false,
body: user,
})
});
return [patchUserResponse]
return [patchUserResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось изменить пользователя. ${error}`]
return [null, `Не удалось изменить пользователя. ${error}`];
}
}

@ -1,42 +1,49 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { jsonToFormdata } from "@root/utils/jsonToFormdata"
import { parseAxiosError } from "@root/utils/parse-error"
import { jsonToFormdata } from "@root/utils/jsonToFormdata";
import { parseAxiosError } from "@root/utils/parse-error";
import type {
Verification,
SendDocumentsArgs,
UpdateDocumentsArgs,
} from "@root/model/auth"
import { AxiosError } from "axios"
} from "@root/model/auth";
const apiUrl = process.env.REACT_APP_DOMAIN + "/verification/v1.0.0"
const API_URL = `${process.env.REACT_APP_DOMAIN}/verification/v1.0.0/verification`;
export async function verification(
userId: string
): Promise<[Verification | null, string?]> {
try {
const verificationResponse = await makeRequest<never, Verification>({
url: apiUrl + "/verification/" + userId,
url: `${API_URL}/${userId}`,
method: "GET",
useToken: true,
withCredentials: true,
})
});
verificationResponse.files = verificationResponse.files.map((obj) => {
obj.url = obj.url.replace("https://hub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "").replace("https://shub.pena.digital", process.env.REACT_APP_DOMAIN?.toString() || "")
return obj
})
obj.url = obj.url
.replace(
"https://hub.pena.digital",
process.env.REACT_APP_DOMAIN?.toString() || ""
)
.replace(
"https://shub.pena.digital",
process.env.REACT_APP_DOMAIN?.toString() || ""
);
return obj;
});
return [verificationResponse]
return [verificationResponse];
} catch (nativeError) {
const err = nativeError as AxiosError
if (err.response?.status === 404) {
return [null, `нет данных`]
}
const [error] = parseAxiosError(nativeError)
const [error, status] = parseAxiosError(nativeError);
return [null, `Ошибка запроса верификации. ${error}`]
if (status === 404) {
return [null, "нет данных"];
}
return [null, `Ошибка запроса верификации. ${error}`];
}
}
@ -45,18 +52,18 @@ export async function sendDocuments(
): Promise<[Verification | "OK" | null, string?]> {
try {
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification",
url: API_URL,
method: "POST",
useToken: true,
withCredentials: true,
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
})
});
return [sendDocumentsResponse]
return [sendDocumentsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка отправки документов. ${error}`]
return [null, `Ошибка отправки документов. ${error}`];
}
}
@ -65,19 +72,39 @@ export async function updateDocuments(
): Promise<[Verification | "OK" | null, string?]> {
try {
const updateDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification/file",
url: `${API_URL}/file`,
method: "PATCH",
useToken: true,
withCredentials: true,
body: jsonToFormdata(
documents.inn ? { ...documents, egrule: documents.inn } : documents
),
})
});
return [updateDocumentsResponse]
return [updateDocumentsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка обновления документов. ${error}`]
return [null, `Ошибка обновления документов. ${error}`];
}
}
export const updateDocument = async (
body: FormData
): Promise<[Verification | "OK" | null, string?]> => {
try {
const updateDocumentResponse = await makeRequest<FormData, Verification>({
url: API_URL,
method: "PATCH",
body,
useToken: true,
withCredentials: true,
});
return [updateDocumentResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка обновления документа. ${error}`];
}
};

@ -10,7 +10,6 @@ import {
useMediaQuery,
useTheme,
} from "@mui/material";
import makeRequest from "@api/makeRequest"
import {
createTicket,
getMessageFromFetchError,
@ -28,13 +27,17 @@ import {
useMemo,
useRef,
useState,
WheelEvent
WheelEvent,
} from "react";
import ChatMessage from "../ChatMessage";
import SendIcon from "../icons/SendIcon";
import ArrowLeft from "@root/assets/Icons/arrowLeft";
import UserCircleIcon from "./UserCircleIcon";
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
import {
sendTicketMessage,
shownMessage,
sendFile as sendFileRequest,
} from "@root/api/ticket";
import { useSSETab } from "@root/utils/hooks/useSSETab";
import {
ACCEPT_SEND_MEDIA_TYPES_MAP,
@ -115,7 +118,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
? offHoursMessage
: workingHoursMessage;
return ({
return {
created_at: new Date().toISOString(),
files: [],
id: "111",
@ -125,8 +128,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
shown: { me: 1 },
ticket_id: "111",
user_id: "greetingMessage",
});
};
}, [open]);
useTicketMessages({
@ -182,7 +184,8 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
onSuccess: (result) => {
if (result.data?.length) {
const currentTicket = result.data.find(
({ origin, state }) => !origin.includes("/support") && state !== "close"
({ origin, state }) =>
!origin.includes("/support") && state !== "close"
);
if (!currentTicket) {
@ -228,7 +231,6 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
scrollToBottom();
}, [open]);
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
@ -353,19 +355,15 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const ticketId = ticket.sessionData?.ticketId || data?.Ticket;
if (ticketId !== undefined) {
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
try {
const body = new FormData();
body.append(file.name, file);
body.append("ticket", ticketId);
await makeRequest({
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
body: body,
method: "POST",
});
} catch (error: any) {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
const [, sendFileError] = await sendFileRequest(
ticketId,
file
);
if(sendFileError) {
enqueueSnackbar(sendFileError)
}
}
return true;
}
@ -547,8 +545,13 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
);
})}
{!ticket.sessionData?.ticketId && (
<ChatMessage unAuthenticated text={getGreetingMessage.message} createdAt={getGreetingMessage.created_at} isSelf={false} />)
}
<ChatMessage
unAuthenticated
text={getGreetingMessage.message}
createdAt={getGreetingMessage.created_at}
isSelf={false}
/>
)}
</Box>
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
<InputBase

@ -1,77 +1,79 @@
import axios from "axios"
import { Box, IconButton, SxProps, Theme, Typography, useTheme } from "@mui/material"
import { Document, Page } from "react-pdf"
import { Buffer } from "buffer"
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice"
import EditIcon from "@mui/icons-material/Edit"
import { ChangeEvent, useRef } from "react"
import { SendDocumentsArgs, Verification } from "@root/model/auth"
import makeRequest from "@api/makeRequest"
import { jsonToFormdata } from "@utils/jsonToFormdata"
import { parseAxiosError } from "@utils/parse-error"
import { readFile } from "@root/utils/readFile"
import { enqueueSnackbar } from "notistack"
import axios from "axios";
import {
Box,
IconButton,
SxProps,
Theme,
Typography,
useTheme,
} from "@mui/material";
import { Document, Page } from "react-pdf";
import { Buffer } from "buffer";
import { downloadFileToDevice } from "@root/utils/downloadFileToDevice";
import EditIcon from "@mui/icons-material/Edit";
import { ChangeEvent, useRef } from "react";
import { SendDocumentsArgs, Verification } from "@root/model/auth";
import { updateDocument } from "@api/verification";
import { jsonToFormdata } from "@utils/jsonToFormdata";
import { parseAxiosError } from "@utils/parse-error";
import { readFile } from "@root/utils/readFile";
import { enqueueSnackbar } from "notistack";
type KeyNames =
"inn" |
"rule" |
"certificate"
type KeyNames = "inn" | "rule" | "certificate";
interface Props {
text: string;
documentUrl: string;
sx?: SxProps<Theme>;
keyName: KeyNames
keyName: KeyNames;
}
export default function DocumentItem({
text,
documentUrl = "",
sx,
keyName,
}: Props) {
const theme = useTheme();
export default function DocumentItem({ text, documentUrl = "", sx, keyName }: Props) {
const theme = useTheme()
const fileInputRef = useRef<HTMLInputElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null);
function handleChooseFileClick() {
fileInputRef.current?.click()
fileInputRef.current?.click();
}
const downloadFile = async () => {
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
responseType: "arraybuffer",
})
});
if (!data) {
return
return;
}
downloadFileToDevice(
`${documentUrl.split("/").pop()?.split(".")?.[0] || "document"}.pdf`,
Buffer.from(data)
)
);
return
}
return;
};
async function sendDocument(
e: ChangeEvent<HTMLInputElement>
) {
async function sendDocument(e: ChangeEvent<HTMLInputElement>) {
const target = e.target as HTMLInputElement;
const file = target?.files?.[0] || null;
if (file !== null) {
const readedFile = await readFile(file, "binary")
try {
await makeRequest<FormData, Verification>({
url: `${process.env.REACT_APP_DOMAIN}/verification/v1.0.0/verification`,
method: "PATCH",
useToken: true,
withCredentials: true,
body: jsonToFormdata({ [keyName]: readedFile }),
})
const readedFile = await readFile(file, "binary");
const [, updateDocumentError] = await updateDocument(
jsonToFormdata({ [keyName]: readedFile })
);
enqueueSnackbar("Данные обновлены")
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
enqueueSnackbar(`Ошибка отправки документов. ${error}`)
if (updateDocumentError) {
return enqueueSnackbar(
`Ошибка отправки документов. ${updateDocumentError}`
);
}
enqueueSnackbar("Данные обновлены");
}
}
@ -103,9 +105,7 @@ export default function DocumentItem({ text, documentUrl = "", sx, keyName }: Pr
>
{documentUrl.split("/").pop()?.split(".")?.[0]}
</Typography>
<IconButton
onClick={handleChooseFileClick}
>
<IconButton onClick={handleChooseFileClick}>
<EditIcon sx={{ color: theme.palette.purple.main }} />
</IconButton>
<input
@ -120,7 +120,6 @@ export default function DocumentItem({ text, documentUrl = "", sx, keyName }: Pr
</Box>
<Document file={documentUrl}>
<Page
pageNumber={1}
width={200}
@ -131,5 +130,5 @@ export default function DocumentItem({ text, documentUrl = "", sx, keyName }: Pr
</>
)}
</Box>
)
);
}

@ -3,19 +3,17 @@ import {
IconButton,
Typography,
useMediaQuery,
useTheme
} from "@mui/material"
import CustomAccordion from "@components/CustomAccordion"
import File from "@components/icons/File"
import {getDeclension} from "@utils/declension"
import {enqueueSnackbar} from "notistack"
import {addTariffToCart, useUserStore} from "@root/stores/user"
import ForwardToInboxOutlinedIcon
from "@mui/icons-material/ForwardToInboxOutlined";
import {makeRequest} from "@frontend/kitui";
import {KeyValue, RawDetails} from "@api/history";
import {useNavigate} from "react-router-dom"
import {VerificationStatus} from "@root/model/account"
useTheme,
} from "@mui/material";
import CustomAccordion from "@components/CustomAccordion";
import File from "@components/icons/File";
import { getDeclension } from "@utils/declension";
import { enqueueSnackbar } from "notistack";
import { addTariffToCart, useUserStore } from "@root/stores/user";
import ForwardToInboxOutlinedIcon from "@mui/icons-material/ForwardToInboxOutlined";
import { KeyValue, RawDetails, sendReport } from "@api/history";
import { useNavigate } from "react-router-dom";
import { VerificationStatus } from "@root/model/account";
export type History = {
title: string;
@ -31,56 +29,60 @@ interface AccordionWrapperProps {
last?: boolean;
first?: boolean;
createdAt: string;
onClickMail?: any
mainId: string
onClickMail?: any;
mainId: string;
}
export default function AccordionWrapper({ content, last, first, createdAt, onClickMail, mainId }: AccordionWrapperProps) {
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const isTablet = useMediaQuery(theme.breakpoints.down(900))
const isMobile = useMediaQuery(theme.breakpoints.down(560))
export default function AccordionWrapper({
content,
last,
first,
createdAt,
onClickMail,
mainId,
}: AccordionWrapperProps) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isMobile = useMediaQuery(theme.breakpoints.down(560));
const navigate = useNavigate();
const verificationStatus = useUserStore((state) => state.verificationStatus)
const OrgName = useUserStore((state) => state.userAccount?.name.orgname)
const verificationStatus = useUserStore((state) => state.verificationStatus);
const OrgName = useUserStore((state) => state.userAccount?.name.orgname);
const valuesByKey: any = {}
const valuesByKey: any = {};
if (Array.isArray(content[0].Value) && Array.isArray(content[0].Value[0])) {
(content[0].Value[0] as KeyValue[]).forEach((item: KeyValue) => {
valuesByKey[item.Key] = item.Value;
});
}
const extractDateFromString = (tariffName: string) => {
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/)
return dateMatch ? dateMatch[0] : null
}
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
return dateMatch ? dateMatch[0] : null;
};
async function handleTariffItemClick(tariffId: string) {
const { patchCartError } = await addTariffToCart(tariffId)
const { patchCartError } = await addTariffToCart(tariffId);
if (patchCartError) {
enqueueSnackbar(patchCartError)
enqueueSnackbar(patchCartError);
} else {
enqueueSnackbar("Тариф добавлен в корзину")
enqueueSnackbar("Тариф добавлен в корзину");
}
}
async function sendBillByEmail(logId: string) {
if (verificationStatus === VerificationStatus.VERIFICATED && OrgName) {
try {
await makeRequest({
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport`,
body: {id: logId},
method: "POST",
});
return enqueueSnackbar("Акт будет отправлен на почту, указанную при регистрации");
} catch (e) {
const [, sendReportError] = await sendReport(logId);
if (!sendReportError) {
return enqueueSnackbar(
"Акт будет отправлен на почту, указанную при регистрации"
);
}
enqueueSnackbar("Извините, произошла ошибка");
}
}
navigate("/settings")
navigate("/settings");
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
enqueueSnackbar("Пройдите верификацию и заполните название организации");
} else if (!OrgName) {
@ -101,12 +103,11 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
first={first}
divide
text={valuesByKey.privileges.map((e: KeyValue[]) => (
<Typography
key={valuesByKey.id}
>
{e[1].Value} - {e[5].Value} {getDeclension(Number(e[5].Value), e[7].Value.toString())}
</Typography>)
)}
<Typography key={valuesByKey.id}>
{e[1].Value} - {e[5].Value}{" "}
{getDeclension(Number(e[5].Value), e[7].Value.toString())}
</Typography>
))}
header={
<>
<Box
@ -138,7 +139,9 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
fontSize: upMd ? "20px" : "18px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.text.secondary,
color: valuesByKey.expired
? theme.palette.text.disabled
: theme.palette.text.secondary,
px: 0,
whiteSpace: "nowrap",
}}
@ -152,12 +155,14 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
color: valuesByKey.expired
? theme.palette.text.disabled
: theme.palette.gray.dark,
px: 0,
width: "200px",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
textOverflow: "ellipsis",
}}
>
{valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name}
@ -179,19 +184,25 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 400,
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
color: valuesByKey.expired
? theme.palette.text.disabled
: theme.palette.gray.dark,
px: 0,
}}
title={`>Способ оплаты: ${valuesByKey.payMethod}</Typography>}`}
>
{valuesByKey.payMethod && <Typography
{valuesByKey.payMethod && (
<Typography
sx={{
maxWidth: "300px",
width: "300px",
overflow: "hidden",
textOverflow: "ellipsis"
textOverflow: "ellipsis",
}}
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
>
Способ оплаты: {valuesByKey.payMethod}
</Typography>
)}
</Typography>
<Box
sx={{
@ -205,18 +216,27 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
>
<Typography
sx={{
marginLeft: isTablet ? (isMobile ? null : "auto") : null,
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
marginLeft: isTablet
? isMobile
? null
: "auto"
: null,
color: valuesByKey.expired
? theme.palette.text.disabled
: theme.palette.gray.dark,
fontSize: upSm ? "20px" : "16px",
fontWeight: 500,
textAlign: "left",
}}
>
{Number(content[1].Value) / 100 ? Number(content[1].Value) / 100 : "nodata"} руб.
{Number(content[1].Value) / 100
? Number(content[1].Value) / 100
: "nodata"}{" "}
руб.
</Typography>
</Box>
</Box>
{!isMobile &&
{!isMobile && (
<>
<IconButton
onClick={(e) => {
@ -235,16 +255,19 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
"&:active": {
bgcolor: "black",
color: "white",
}
},
}}
>
<ForwardToInboxOutlinedIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
<ForwardToInboxOutlinedIcon
fontSize={"medium"}
sx={{ opacity: 0.9 }}
/>
</IconButton>
<IconButton
title="Добавить в корзину тариф"
onClick={(e) => {
e.stopPropagation()
handleTariffItemClick(valuesByKey.id)
e.stopPropagation();
handleTariffItemClick(valuesByKey.id);
}}
sx={{
ml: "20px",
@ -258,17 +281,16 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
"&:active": {
bgcolor: "black",
stroke: "white",
}
},
}}
>
<File></File>
</IconButton>
</>
}
)}
</Box>
</Box>
{isMobile &&
{isMobile && (
<>
<IconButton
onClick={(e) => {
@ -287,16 +309,19 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
"&:active": {
bgcolor: "black",
color: "white",
}
},
}}
>
<ForwardToInboxOutlinedIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
<ForwardToInboxOutlinedIcon
fontSize={"medium"}
sx={{ opacity: 0.9 }}
/>
</IconButton>
<IconButton
title="Добавить в корзину тариф"
onClick={(e) => {
e.stopPropagation()
handleTariffItemClick(valuesByKey.id)
e.stopPropagation();
handleTariffItemClick(valuesByKey.id);
}}
sx={{
mr: "10px",
@ -310,17 +335,16 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
"&:active": {
bgcolor: "black",
stroke: "white",
}
},
}}
>
<File></File>
</IconButton>
</>
}
)}
</>
}
/>
</Box>
)
);
}

@ -3,18 +3,19 @@ import {
IconButton,
Typography,
useMediaQuery,
useTheme
useTheme,
} from "@mui/material";
import CustomAccordion from "@components/CustomAccordion";
import File from "@components/icons/File";
import { getDeclension } from "@utils/declension";
import { enqueueSnackbar } from "notistack";
import {addTariffToCart, useUserStore} from "@root/stores/user"
import { addTariffToCart, useUserStore } from "@root/stores/user";
import { makeRequest, Tariff } from "@frontend/kitui";
import { currencyFormatter } from "@root/utils/currencyFormatter";
import ForwardToInboxIcon from '@mui/icons-material/ForwardToInbox';
import {VerificationStatus} from "@root/model/account"
import {useNavigate} from "react-router-dom"
import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox";
import { VerificationStatus } from "@root/model/account";
import { useNavigate } from "react-router-dom";
import { sendReport } from "@api/history";
export type History = {
title: string;
@ -31,18 +32,25 @@ interface AccordionWrapperProps {
last?: boolean;
first?: boolean;
createdAt: string;
mainId: string
mainId: string;
}
export default function AccordionWrapper2({ tariff, price, last, first, createdAt, mainId }: AccordionWrapperProps) {
export default function AccordionWrapper2({
tariff,
price,
last,
first,
createdAt,
mainId,
}: AccordionWrapperProps) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isMobile = useMediaQuery(theme.breakpoints.down(560));
const navigate = useNavigate();
const verificationStatus = useUserStore((state) => state.verificationStatus)
const OrgName = useUserStore((state) => state.userAccount?.name.orgname)
const verificationStatus = useUserStore((state) => state.verificationStatus);
const OrgName = useUserStore((state) => state.userAccount?.name.orgname);
async function handleTariffItemClick(tariffId: string) {
const { patchCartError } = await addTariffToCart(tariffId);
if (patchCartError) {
@ -54,18 +62,17 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
async function sendBillByEmail(logId: string) {
if (verificationStatus === VerificationStatus.VERIFICATED && OrgName) {
try {
await makeRequest({
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport`,
body: {id: logId},
method: "POST",
});
return enqueueSnackbar("Акт будет отправлен на почту, указанную при регистрации");
} catch (e) {
const [, sendReportError] = await sendReport(logId);
if (!sendReportError) {
return enqueueSnackbar(
"Акт будет отправлен на почту, указанную при регистрации"
);
}
enqueueSnackbar("Извините, произошла ошибка");
}
}
navigate("/settings")
navigate("/settings");
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
enqueueSnackbar("Пройдите верификацию и заполните название организации");
} else if (!OrgName) {
@ -85,9 +92,13 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
last={last}
first={first}
divide
text={tariff.privileges.map(privilege => (
`${privilege.description} - ${privilege.amount} ${getDeclension(Number(privilege.serviceKey), privilege.value)} `
))}
text={tariff.privileges.map(
(privilege) =>
`${privilege.description} - ${privilege.amount} ${getDeclension(
Number(privilege.serviceKey),
privilege.value
)} `
)}
header={
<>
<Box
@ -119,7 +130,9 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
fontSize: upMd ? "20px" : "18px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.text.secondary,
color: /* valuesByKey.expired */ false
? theme.palette.text.disabled
: theme.palette.text.secondary,
px: 0,
whiteSpace: "nowrap",
}}
@ -133,7 +146,9 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
fontSize: upMd ? "18px" : "16px",
lineHeight: upMd ? undefined : "19px",
fontWeight: 500,
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.gray.dark,
color: /* valuesByKey.expired */ false
? theme.palette.text.disabled
: theme.palette.gray.dark,
px: 0,
width: upMd ? "200px" : "auto",
maxWidth: "200px",
@ -185,8 +200,14 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
>
<Typography
sx={{
marginLeft: isTablet ? (isMobile ? null : "auto") : null,
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.gray.dark,
marginLeft: isTablet
? isMobile
? null
: "auto"
: null,
color: /* valuesByKey.expired */ false
? theme.palette.text.disabled
: theme.palette.gray.dark,
fontSize: upSm ? "20px" : "16px",
fontWeight: 500,
textAlign: "left",
@ -196,7 +217,7 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
</Typography>
</Box>
</Box>
{!isMobile &&
{!isMobile && (
<>
<IconButton
onClick={(e) => {
@ -215,10 +236,13 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
"&:active": {
bgcolor: "black",
color: "white",
}
},
}}
>
<ForwardToInboxIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
<ForwardToInboxIcon
fontSize={"medium"}
sx={{ opacity: 0.9 }}
/>
</IconButton>
<IconButton
title="Добавить в корзину тариф"
@ -238,17 +262,16 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
"&:active": {
bgcolor: "black",
stroke: "white",
}
},
}}
>
<File></File>
</IconButton>
</>
}
)}
</Box>
</Box>
{isMobile &&
{isMobile && (
<>
<IconButton
onClick={(e) => {
@ -267,10 +290,13 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
"&:active": {
bgcolor: "black",
color: "white",
}
},
}}
>
<ForwardToInboxIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
<ForwardToInboxIcon
fontSize={"medium"}
sx={{ opacity: 0.9 }}
/>
</IconButton>
<IconButton
title="Добавить в корзину тариф"
@ -290,14 +316,13 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
"&:active": {
bgcolor: "black",
stroke: "white",
}
},
}}
>
<File></File>
</IconButton>
</>
}
)}
</>
}
/>

@ -4,7 +4,7 @@ import {
IconButton,
Typography,
useMediaQuery,
useTheme
useTheme,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
@ -18,8 +18,11 @@ import {ErrorBoundary} from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import { useHistoryStore } from "@root/stores/history";
import { enqueueSnackbar } from "notistack";
import {makeRequest} from "@frontend/kitui";
import {HistoryRecord, HistoryRecord2} from "@root/api/history";
import {
HistoryRecord,
HistoryRecord2,
sendReportById,
} from "@root/api/history";
import AccordionWrapper2 from "./AccordionWrapper2";
const subPages = ["Платежи"];
@ -32,7 +35,7 @@ export default function History() {
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const historyData = useHistoryStore(state => state.history);
const historyData = useHistoryStore((state) => state.history);
const handleCustomBackNavigation = useHistoryTracker();
const extractDateFromString = (tariffName: string) => {
@ -41,17 +44,13 @@ export default function History() {
};
async function handleHistoryResponse(tariffId: string) {
try {
await makeRequest(
{
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport/${tariffId}`,
method: "POST",
const [, sendReportError] = await sendReportById(tariffId);
if (sendReportError) {
return enqueueSnackbar(sendReportError);
}
);
enqueueSnackbar("Запрос отправлен");
} catch (e) {
enqueueSnackbar("извините, произошла ошибка");
}
}
return (
<SectionWrapper
@ -72,7 +71,10 @@ export default function History() {
}}
>
{isMobile && (
<IconButton onClick={handleCustomBackNavigation} sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
<IconButton
onClick={handleCustomBackNavigation}
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
@ -86,27 +88,37 @@ export default function History() {
</Typography>
</Box>
{isMobile ? (
<Select items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
<Select
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
) : (
<Tabs items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
<Tabs
items={subPages}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
)}
<ErrorBoundary
fallback={
<Typography mt="8px">Ошибка загрузки истории</Typography>
}
fallback={<Typography mt="8px">Ошибка загрузки истории</Typography>}
onError={handleComponentError}
>
{historyData?.length === 0 && <Typography textAlign="center">Нет данных</Typography>}
{historyData?.length === 0 && (
<Typography textAlign="center">Нет данных</Typography>
)}
{/* Для ненормального rawDetails */}
{historyData?.filter((e): e is HistoryRecord => {
{historyData
?.filter((e): e is HistoryRecord => {
e.createdAt = extractDateFromString(e.createdAt);
return (
!e.isDeleted
&& e.key === "payCart"
&& Array.isArray(e.rawDetails)
&& Array.isArray(e.rawDetails[0].Value)
!e.isDeleted &&
e.key === "payCart" &&
Array.isArray(e.rawDetails) &&
Array.isArray(e.rawDetails[0].Value)
);
}).map((e, index) => {
})
.map((e, index) => {
return (
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
<AccordionWrapper
@ -125,15 +137,17 @@ export default function History() {
);
})}
{/* Для нормального rawDetails */}
{historyData?.filter((e): e is HistoryRecord2 => {
{historyData
?.filter((e): e is HistoryRecord2 => {
e.createdAt = extractDateFromString(e.createdAt);
return (
!e.isDeleted
&& e.key === "payCart"
&& !Array.isArray(e.rawDetails)
&& !!e.rawDetails.tariffs[0]
!e.isDeleted &&
e.key === "payCart" &&
!Array.isArray(e.rawDetails) &&
!!e.rawDetails.tariffs[0]
);
}).map((e, index) => {
})
.map((e, index) => {
return (
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
<AccordionWrapper2

@ -1,5 +1,4 @@
import { useEffect, useState } from "react";
import axios, { AxiosResponse } from "axios";
import { ApologyPage } from "../ApologyPage";
import { useNavigate } from "react-router-dom";
import {
@ -22,19 +21,6 @@ import { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets";
import {setNotEnoughMoneyAmount} from "@stores/cart"
function refresh(token: string) {
return axios<never, AxiosResponse<{ accessToken: string }>>(
process.env.REACT_APP_DOMAIN + "/auth/refresh",
{
headers: {
Authorization: "Bearer " + token,
"Content-Type": "application/json",
},
method: "POST",
}
);
}
const params = new URLSearchParams(window.location.search);
const action = params.get("action");
const dif = params.get("dif");

@ -14,7 +14,6 @@ import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import SendIcon from "@components/icons/SendIcon";
import makeRequest from "@api/makeRequest"
import { throttle, useToken } from "@frontend/kitui";
import { enqueueSnackbar } from "notistack";
import { useTicketStore } from "@root/stores/tickets";
@ -35,7 +34,11 @@ import {
useSSESubscription,
useTicketMessages,
} from "@frontend/kitui";
import { shownMessage, sendTicketMessage } from "@root/api/ticket";
import {
shownMessage,
sendTicketMessage,
sendFile as sendFileRequest,
} from "@root/api/ticket";
import { withErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import { useSSETab } from "@root/utils/hooks/useSSETab";
@ -196,20 +199,12 @@ function SupportChat() {
return;
}
try {
const body = new FormData();
const [, sendFileError] = await sendFileRequest(ticketId, file);
body.append(file.name, file);
body.append("ticket", ticketId);
await makeRequest({
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
body: body,
method: "POST",
});
} catch (error: any) {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
if (sendFileError) {
enqueueSnackbar(sendFileError);
}
return true;
}
};

@ -21,7 +21,6 @@ import { setUserId, useUserStore } from "@root/stores/user";
import { cardShadow } from "@root/utils/theme";
import AmoButton from "./AmoButton";
import { recover } from "@root/api/auth";
import {AxiosError} from "axios"
interface Values {
email: string;

@ -19,8 +19,8 @@ import { useEffect, useState } from "react";
import { useUserStore } from "@root/stores/user";
import { cardShadow } from "@root/utils/theme";
import makeRequest from "@api/makeRequest"
import { setAuthToken } from "@frontend/kitui"
import { patchUser } from "@api/user";
import { setAuthToken } from "@frontend/kitui";
interface Values {
password: string;
}
@ -32,7 +32,10 @@ const initialValues: Values = {
const validationSchema = object({
password: string()
.min(8, "Минимум 8 символов")
.matches(/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/, "Некорректные символы")
.matches(
/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/,
"Некорректные символы"
)
.required("Поле обязательно"),
});
@ -49,32 +52,35 @@ export default function RecoverPassword() {
validationSchema,
onSubmit: async (values, formikHelpers) => {
if (tokenUser) {
setAuthToken(tokenUser || "")
try {
const response = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/user/",
method: "PATCH",
body: {password: values.password},
setAuthToken(tokenUser || "");
const [, patchUserError] = await patchUser({
password: values.password,
});
setIsDialogOpen(false)
navigate("/")
enqueueSnackbar("Пароль успешно сменён")
} catch (error) {
setAuthToken("")
enqueueSnackbar("Извините, произошла ошибка, попробуйте повторить позже")}
if (!patchUserError) {
setIsDialogOpen(false);
navigate("/");
enqueueSnackbar("Пароль успешно сменён");
} else {
enqueueSnackbar("Неверный url-адрес")
setAuthToken("");
enqueueSnackbar(
"Извините, произошла ошибка, попробуйте повторить позже"
);
}
} else {
enqueueSnackbar("Неверный url-адрес");
}
},
});
useEffect(() => {
const params = new URLSearchParams(window.location.search)
const authToken = params.get("auth")
setTokenUser(authToken)
const params = new URLSearchParams(window.location.search);
const authToken = params.get("auth");
setTokenUser(authToken);
history.pushState(null, document.title, "/changepwd");
return () => {setAuthToken("")}
return () => {
setAuthToken("");
};
}, []);
function handleClose() {
@ -185,8 +191,7 @@ export default function RecoverPassword() {
gap: "10px",
mt: "auto",
}}
>
</Box>
></Box>
</Box>
</Dialog>
);

@ -1,5 +1,4 @@
import { ErrorInfo } from "react"
import { ErrorInfo } from "react";
interface ComponentError {
timestamp: number;
@ -14,29 +13,23 @@ export function handleComponentError(error: Error, info: ErrorInfo) {
message: error.message,
callStack: error.stack,
componentStack: info.componentStack,
};
queueErrorRequest(componentError);
}
queueErrorRequest(componentError)
}
let errorsQueue: ComponentError[] = []
let timeoutId: ReturnType<typeof setTimeout>
let errorsQueue: ComponentError[] = [];
let timeoutId: ReturnType<typeof setTimeout>;
function queueErrorRequest(error: ComponentError) {
errorsQueue.push(error)
errorsQueue.push(error);
clearTimeout(timeoutId)
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
sendErrorsToServer()
}, 1000)
sendErrorsToServer();
}, 1000);
}
async function sendErrorsToServer() {
// makeRequest({
// url: "",
// method: "POST",
// body: errorsQueue,
// useToken: true,
// });
errorsQueue = []
errorsQueue = [];
}