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

@ -1,26 +1,27 @@
import { UserAccount } from "@frontend/kitui" import { UserAccount } from "@frontend/kitui";
import makeRequest from "@api/makeRequest" 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( export async function patchCart(
tariffId: string tariffId: string
): Promise<[string[], string?]> { ): Promise<[string[], string?]> {
try { try {
const patchCartResponse = await makeRequest<never, UserAccount>({ const patchCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`, url: `${API_URL}/cart?id=${tariffId}`,
method: "PATCH", method: "PATCH",
useToken: true, useToken: true,
}) });
return [patchCartResponse.cart] return [patchCartResponse.cart];
} catch (nativeError) { } catch (nativeError) {
let [error, status] = parseAxiosError(nativeError) let [error, status] = parseAxiosError(nativeError);
if (status === 400 && error.indexOf("invalid id") !== -1) error = "Данный тариф более недоступен" if (status === 400 && error.indexOf("invalid id") !== -1)
error = "Данный тариф более недоступен";
return [[], `Не удалось добавить товар в корзину. ${error}`] return [[], `Не удалось добавить товар в корзину. ${error}`];
} }
} }
@ -29,32 +30,32 @@ export async function deleteCart(
): Promise<[string[], string?]> { ): Promise<[string[], string?]> {
try { try {
const deleteCartResponse = await makeRequest<never, UserAccount>({ const deleteCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`, url: `${API_URL}/cart?id=${tariffId}`,
method: "DELETE", method: "DELETE",
useToken: true, useToken: true,
}) });
return [deleteCartResponse.cart] return [deleteCartResponse.cart];
} catch (nativeError) { } catch (nativeError) {
const [error] = parseAxiosError(nativeError) const [error] = parseAxiosError(nativeError);
return [[], `Не удалось удалить товар из корзины. ${error}`] return [[], `Не удалось удалить товар из корзины. ${error}`];
} }
} }
export async function payCart(): Promise<[UserAccount | null, string?]> { export async function payCart(): Promise<[UserAccount | null, string?]> {
try { try {
const payCartResponse = await makeRequest<never, UserAccount>({ const payCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + "/cart/pay", url: `${API_URL}/cart/pay`,
method: "POST", method: "POST",
useToken: true, useToken: true,
}) });
return [payCartResponse] return [payCartResponse];
} catch (nativeError) { } 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 }, { currency: string },
UserAccount UserAccount
>({ >({
url: apiUrl + "/wallet", url: `${API_URL}/wallet`,
method: "PATCH", method: "PATCH",
useToken: true, useToken: true,
body: { body: {
currency, currency,
}, },
}) });
return [patchCurrencyResponse] return [patchCurrencyResponse];
} catch (nativeError) { } 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 { Tariff } from "@frontend/kitui";
import {parseAxiosError} from "@root/utils/parse-error" import { parseAxiosError } from "@root/utils/parse-error";
import makeRequest from "@api/makeRequest" import makeRequest from "@api/makeRequest";
export interface GetHistoryResponse { export interface GetHistoryResponse {
totalPages: number; totalPages: number;
@ -37,24 +37,30 @@ export type HistoryRecord2 = {
userId: string; userId: string;
}; };
export type KeyValue = { Key: string; Value: string | number }; export type KeyValue = { Key: string; Value: string | number };
export type RawDetails = { export type RawDetails = {
Key: "tariffs" | "price"; Key: "tariffs" | "price";
Value: string | number | KeyValue[][]; 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 { try {
const historyResponse = await makeRequest<never, GetHistoryResponse|GetHistoryResponse2>({ const historyResponse = await makeRequest<
url: process.env.REACT_APP_DOMAIN + "/customer/history?page=1&limit=100&type=payCart", never,
method: "get", GetHistoryResponse | GetHistoryResponse2
>({
url: `${API_URL}/history?page=1&limit=100&type=payCart`,
method: "GET",
useToken: true, useToken: true,
}) });
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) { if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
return [historyResponse] as [GetHistoryResponse2] return [historyResponse] as [GetHistoryResponse2];
} }
const checked = historyResponse.records.map((data) => { const checked = historyResponse.records.map((data) => {
@ -67,18 +73,56 @@ export async function getHistory(): Promise<[GetHistoryResponse | GetHistoryResp
//@ts-ignore //@ts-ignore
//data.rawDetails = buffer //data.rawDetails = buffer
const checkedRowDetails = [ const checkedRowDetails = [
(data.rawDetails as HistoryRecord["rawDetails"]).find((details) => details.Key === "tariffs") as RawDetails, (data.rawDetails as HistoryRecord["rawDetails"]).find(
(data.rawDetails as HistoryRecord["rawDetails"]).find((details) => details.Key === "price") as KeyValue (details) => details.Key === "tariffs"
] ) as RawDetails,
return {...data, rawDetails: checkedRowDetails} as HistoryRecord (data.rawDetails as HistoryRecord["rawDetails"]).find(
}) (details) => details.Key === "price"
) as KeyValue,
];
return { ...data, rawDetails: checkedRowDetails } as HistoryRecord;
});
historyResponse.records = checked || [];
historyResponse.records = checked || [] return [historyResponse];
return [historyResponse]
} catch (nativeError) { } 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 { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets"; import { clearTickets } from "@root/stores/tickets";
import { redirect } from "react-router-dom"; 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 { interface ErrorResponseData {
message?: string; 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 { 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) { } catch (e) {
const error = e as AxiosError; 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(); clearAuthToken();
clearUserData(); clearUserData();
clearCustomTariffs(); clearCustomTariffs();
clearTickets(); clearTickets();
setNotEnoughMoneyAmount(0) setNotEnoughMoneyAmount(0);
redirect("/"); redirect("/");
} }
throw e throw e;
}; }
}; }
export default makeRequest; export default makeRequest;

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

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

@ -1,17 +1,22 @@
import makeRequest from "@api/makeRequest" import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@root/utils/parse-error" 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 { try {
const recentlyPurchased = await makeRequest<never, any>({ const recentlyPurchased = await makeRequest<never, any>({
url: process.env.REACT_APP_DOMAIN + "/customer/recent", url: `${API_URL}/recent`,
method: "get", method: "GET",
useToken: true, 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 { Tariff } from "@frontend/kitui";
import { parseAxiosError } from "@root/utils/parse-error"; 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 type { GetTariffsResponse } from "@root/model/tariff";
import { removeTariffFromCart } from "@root/stores/user"; 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( export async function getTariffs(
apiPage: number, apiPage: number,
@ -15,7 +18,7 @@ export async function getTariffs(
): Promise<[GetTariffsResponse | null, string?]> { ): Promise<[GetTariffsResponse | null, string?]> {
try { try {
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({ const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
url: apiUrl + `/tariff?page=${apiPage}&limit=${tariffsPerPage}`, url: `${API_URL}/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
method: "get", method: "get",
useToken: true, useToken: true,
signal, signal,
@ -36,10 +39,12 @@ interface CreateTariffBody {
privileges: PrivilegeWithoutPrice[]; privileges: PrivilegeWithoutPrice[];
} }
export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff | null, string?]> { export async function createTariff(
tariff: CreateTariffBody
): Promise<[Tariff | null, string?]> {
try { try {
const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({ const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({
url: `${apiUrl}/tariff`, url: `${API_URL}/tariff`,
method: "post", method: "post",
useToken: true, useToken: true,
body: tariff, 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 { try {
const getTariffByIdResponse = await makeRequest<never, Tariff>({ const getTariffByIdResponse = await makeRequest<never, Tariff>({
url: `${apiUrl}/tariff/${tariffId}`, url: `${API_URL}/tariff/${tariffId}`,
method: "get", method: "get",
useToken: true, useToken: true,
}); });
@ -73,8 +80,11 @@ export async function getCustomTariffs(
signal: AbortSignal | undefined signal: AbortSignal | undefined
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> { ): Promise<[ServiceKeyToPrivilegesMap | null, string?]> {
try { try {
const customTariffsResponse = await makeRequest<null, ServiceKeyToPrivilegesMap>({ const customTariffsResponse = await makeRequest<
url: apiUrl + "/privilege/service", null,
ServiceKeyToPrivilegesMap
>({
url: `${API_URL}/privilege/service`,
signal, signal,
method: "get", method: "get",
useToken: true, useToken: true,
@ -82,7 +92,7 @@ export async function getCustomTariffs(
const tempCustomTariffsResponse = { const tempCustomTariffsResponse = {
...customTariffsResponse, ...customTariffsResponse,
squiz: customTariffsResponse.squiz squiz: customTariffsResponse.squiz,
}; };
return [tempCustomTariffsResponse]; return [tempCustomTariffsResponse];
@ -96,13 +106,15 @@ export async function getCustomTariffs(
export async function getTariffArray(tariffIds: string[] | undefined) { export async function getTariffArray(tariffIds: string[] | undefined) {
if (!tariffIds) return null; if (!tariffIds) return null;
const responses = await Promise.allSettled(tariffIds.map(tariffId => const responses = await Promise.allSettled(
tariffIds.map((tariffId) =>
makeRequest<never, Tariff>({ makeRequest<never, Tariff>({
url: `${apiUrl}/tariff/${tariffId}`, url: `${API_URL}/tariff/${tariffId}`,
method: "get", method: "get",
useToken: true, useToken: true,
}) })
)); )
);
const tariffs: Tariff[] = []; 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 { parseAxiosError } from "@root/utils/parse-error";
import { SendTicketMessageRequest } from "@frontend/kitui"; 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( export async function sendTicketMessage(
ticketId: string, ticketId: string,
@ -14,7 +18,7 @@ export async function sendTicketMessage(
SendTicketMessageRequest, SendTicketMessageRequest,
null null
>({ >({
url: `${apiUrl}/send`, url: `${API_URL}/send`,
method: "POST", method: "POST",
useToken: true, useToken: true,
body: { ticket: ticketId, message: message, lang: "ru", files: [] }, 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?]> { export async function shownMessage(id: string): Promise<[null, string?]> {
try { try {
const shownMessageResponse = await makeRequest<{ id: string }, null>({ const shownMessageResponse = await makeRequest<{ id: string }, null>({
url: apiUrl + "/shown", url: `${API_URL}/shown`,
method: "POST", method: "POST",
useToken: true, useToken: true,
body: { id }, body: { id },
@ -44,3 +48,27 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
return [null, `Не удалось прочесть сообщение. ${error}`]; 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 { User } from "@frontend/kitui";
import makeRequest from "@api/makeRequest" import makeRequest from "@api/makeRequest";
import { PatchUserRequest } from "@root/model/user" import { PatchUserRequest } from "@root/model/user";
import { parseAxiosError } from "@root/utils/parse-error" 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( export async function patchUser(
user: PatchUserRequest user: PatchUserRequest
): Promise<[User | null, string?]> { ): Promise<[User | null, string?]> {
try { try {
const patchUserResponse = await makeRequest<PatchUserRequest, User>({ const patchUserResponse = await makeRequest<PatchUserRequest, User>({
url: apiUrl+"/", url: `${API_URL}/`,
contentType: true, contentType: true,
method: "PATCH", method: "PATCH",
useToken: true, useToken: true,
withCredentials: false, withCredentials: false,
body: user, body: user,
}) });
return [patchUserResponse] return [patchUserResponse];
} catch (nativeError) { } 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 { jsonToFormdata } from "@root/utils/jsonToFormdata";
import { parseAxiosError } from "@root/utils/parse-error" import { parseAxiosError } from "@root/utils/parse-error";
import type { import type {
Verification, Verification,
SendDocumentsArgs, SendDocumentsArgs,
UpdateDocumentsArgs, UpdateDocumentsArgs,
} from "@root/model/auth" } from "@root/model/auth";
import { AxiosError } from "axios"
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( export async function verification(
userId: string userId: string
): Promise<[Verification | null, string?]> { ): Promise<[Verification | null, string?]> {
try { try {
const verificationResponse = await makeRequest<never, Verification>({ const verificationResponse = await makeRequest<never, Verification>({
url: apiUrl + "/verification/" + userId, url: `${API_URL}/${userId}`,
method: "GET", method: "GET",
useToken: true, useToken: true,
withCredentials: true, withCredentials: true,
}) });
verificationResponse.files = verificationResponse.files.map((obj) => { 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() || "") obj.url = obj.url
return obj .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) { } catch (nativeError) {
const err = nativeError as AxiosError const [error, status] = parseAxiosError(nativeError);
if (err.response?.status === 404) {
return [null, `нет данных`]
}
const [error] = parseAxiosError(nativeError)
return [null, `Ошибка запроса верификации. ${error}`] if (status === 404) {
return [null, "нет данных"];
}
return [null, `Ошибка запроса верификации. ${error}`];
} }
} }
@ -45,39 +52,59 @@ export async function sendDocuments(
): Promise<[Verification | "OK" | null, string?]> { ): Promise<[Verification | "OK" | null, string?]> {
try { try {
const sendDocumentsResponse = await makeRequest<FormData, Verification>({ const sendDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification", url: API_URL,
method: "POST", method: "POST",
useToken: true, useToken: true,
withCredentials: true, withCredentials: true,
body: jsonToFormdata({ ...documents, egrule: documents.inn }), body: jsonToFormdata({ ...documents, egrule: documents.inn }),
}) });
return [sendDocumentsResponse] return [sendDocumentsResponse];
} catch (nativeError) { } catch (nativeError) {
const [error] = parseAxiosError(nativeError) const [error] = parseAxiosError(nativeError);
return [null, `Ошибка отправки документов. ${error}`] return [null, `Ошибка отправки документов. ${error}`];
} }
} }
export async function updateDocuments( export async function updateDocuments(
documents: UpdateDocumentsArgs documents: UpdateDocumentsArgs
): Promise<[Verification | "OK" | null, string? ]> { ): Promise<[Verification | "OK" | null, string?]> {
try { try {
const updateDocumentsResponse = await makeRequest<FormData, Verification>({ const updateDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification/file", url: `${API_URL}/file`,
method: "PATCH", method: "PATCH",
useToken: true, useToken: true,
withCredentials: true, withCredentials: true,
body: jsonToFormdata( body: jsonToFormdata(
documents.inn ? { ...documents, egrule: documents.inn } : documents documents.inn ? { ...documents, egrule: documents.inn } : documents
), ),
}) });
return [updateDocumentsResponse] return [updateDocumentsResponse];
} catch (nativeError) { } 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, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import makeRequest from "@api/makeRequest"
import { import {
createTicket, createTicket,
getMessageFromFetchError, getMessageFromFetchError,
@ -28,13 +27,17 @@ import {
useMemo, useMemo,
useRef, useRef,
useState, useState,
WheelEvent WheelEvent,
} from "react"; } from "react";
import ChatMessage from "../ChatMessage"; import ChatMessage from "../ChatMessage";
import SendIcon from "../icons/SendIcon"; import SendIcon from "../icons/SendIcon";
import ArrowLeft from "@root/assets/Icons/arrowLeft"; import ArrowLeft from "@root/assets/Icons/arrowLeft";
import UserCircleIcon from "./UserCircleIcon"; 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 { useSSETab } from "@root/utils/hooks/useSSETab";
import { import {
ACCEPT_SEND_MEDIA_TYPES_MAP, ACCEPT_SEND_MEDIA_TYPES_MAP,
@ -115,7 +118,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
? offHoursMessage ? offHoursMessage
: workingHoursMessage; : workingHoursMessage;
return ({ return {
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
files: [], files: [],
id: "111", id: "111",
@ -125,8 +128,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
shown: { me: 1 }, shown: { me: 1 },
ticket_id: "111", ticket_id: "111",
user_id: "greetingMessage", user_id: "greetingMessage",
}); };
}, [open]); }, [open]);
useTicketMessages({ useTicketMessages({
@ -182,7 +184,8 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
onSuccess: (result) => { onSuccess: (result) => {
if (result.data?.length) { if (result.data?.length) {
const currentTicket = result.data.find( const currentTicket = result.data.find(
({ origin, state }) => !origin.includes("/support") && state !== "close" ({ origin, state }) =>
!origin.includes("/support") && state !== "close"
); );
if (!currentTicket) { if (!currentTicket) {
@ -199,7 +202,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const message = getMessageFromFetchError(error); const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message); if (message) enqueueSnackbar(message);
}, },
onFetchStateChange: () => { }, onFetchStateChange: () => {},
enabled: Boolean(user), enabled: Boolean(user),
}); });
@ -228,7 +231,6 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
scrollToBottom(); scrollToBottom();
}, [open]); }, [open]);
useEffect( useEffect(
function scrollOnNewMessage() { function scrollOnNewMessage() {
if (!chatBoxRef.current) return; if (!chatBoxRef.current) return;
@ -353,19 +355,15 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const ticketId = ticket.sessionData?.ticketId || data?.Ticket; const ticketId = ticket.sessionData?.ticketId || data?.Ticket;
if (ticketId !== undefined) { if (ticketId !== undefined) {
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
try {
const body = new FormData();
body.append(file.name, file); const [, sendFileError] = await sendFileRequest(
body.append("ticket", ticketId); ticketId,
await makeRequest({ file
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles", );
body: body,
method: "POST", if(sendFileError) {
}); enqueueSnackbar(sendFileError)
} catch (error: any) { }
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
} }
return true; return true;
} }
@ -547,8 +545,13 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
); );
})} })}
{!ticket.sessionData?.ticketId && ( {!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> </Box>
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}> <FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
<InputBase <InputBase

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

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

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

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

@ -1,5 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import axios, { AxiosResponse } from "axios";
import { ApologyPage } from "../ApologyPage"; import { ApologyPage } from "../ApologyPage";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { import {
@ -22,19 +21,6 @@ import { clearCustomTariffs } from "@root/stores/customTariffs";
import { clearTickets } from "@root/stores/tickets"; import { clearTickets } from "@root/stores/tickets";
import {setNotEnoughMoneyAmount} from "@stores/cart" 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 params = new URLSearchParams(window.location.search);
const action = params.get("action"); const action = params.get("action");
const dif = params.get("dif"); 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 { 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 makeRequest from "@api/makeRequest"
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";
@ -35,7 +34,11 @@ import {
useSSESubscription, useSSESubscription,
useTicketMessages, useTicketMessages,
} from "@frontend/kitui"; } 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 { withErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError"; import { handleComponentError } from "@root/utils/handleComponentError";
import { useSSETab } from "@root/utils/hooks/useSSETab"; import { useSSETab } from "@root/utils/hooks/useSSETab";
@ -196,20 +199,12 @@ function SupportChat() {
return; return;
} }
try { const [, sendFileError] = await sendFileRequest(ticketId, file);
const body = new FormData();
body.append(file.name, file); if (sendFileError) {
body.append("ticket", ticketId); enqueueSnackbar(sendFileError);
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);
} }
return true; return true;
} }
}; };

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

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

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