Merge branch 'api' into 'dev'

refactor: makeRequests decomposed

See merge request frontend/marketplace!202
This commit is contained in:
Илья 2024-05-30 11:32:01 +00:00
commit 5606fffc25
32 changed files with 2221 additions and 1955 deletions

@ -9,19 +9,23 @@ import type {
RegisterResponse,
} from "@frontend/kitui";
const apiUrl = process.env.REACT_APP_DOMAIN + "/auth";
type RecoverResponse = {
message: string;
};
export async function register(
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
export const register = async (
login: string,
password: string,
phoneNumber: string
): Promise<[RegisterResponse | null, string?]> {
): Promise<[RegisterResponse | null, string?]> => {
try {
const registerResponse = await makeRequest<
RegisterRequest,
RegisterResponse
>({
url: apiUrl + "/register",
url: `${API_URL}/register`,
body: { login, password, phoneNumber },
useToken: false,
withCredentials: true,
@ -33,15 +37,15 @@ export async function register(
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
}
}
};
export async function login(
export const login = async (
login: string,
password: string
): Promise<[LoginResponse | null, string?]> {
): 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,
@ -53,34 +57,40 @@ export async function login(
return [null, `Не удалось войти. ${error}`];
}
}
};
export async function recover(
export const recover = async (
email: string
): Promise<[unknown | null, string?]> {
): Promise<[RecoverResponse | null, string?]> => {
try {
const formData = new FormData();
formData.append("email", email);
formData.append("RedirectionURL", process.env.REACT_APP_DOMAIN + "/changepwd")
const recoverResponse = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
formData.append(
"RedirectionURL",
`${process.env.REACT_APP_DOMAIN}/changepwd`
);
const recoverResponse = await makeRequest<FormData, RecoverResponse>({
url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
body: formData,
useToken: false,
withCredentials: true,
});
return [recoverResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось восстановить пароль. ${error}`];
}
}
};
export async function logout(): Promise<[unknown, string?]> {
export const logout = async (): Promise<[void | null, string?]> => {
try {
const logoutResponse = await makeRequest<never, void>({
url: apiUrl + "/logout",
method: "POST",
url: `${API_URL}/logout`,
useToken: true,
withCredentials: true,
});
@ -91,4 +101,4 @@ export async function logout(): Promise<[unknown, string?]> {
return [null, `Не удалось выйти. ${error}`];
}
}
};

@ -1,83 +1,82 @@
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(
export const patchCart = async (
tariffId: string
): Promise<[string[], string?]> {
): Promise<[string[], string?]> => {
try {
const patchCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`,
method: "PATCH",
url: `${API_URL}/cart?id=${tariffId}`,
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}`];
}
}
};
export async function deleteCart(
export const deleteCart = async (
tariffId: string
): Promise<[string[], string?]> {
): Promise<[string[], string?]> => {
try {
const deleteCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + `/cart?id=${tariffId}`,
method: "DELETE",
url: `${API_URL}/cart?id=${tariffId}`,
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?]> {
export const payCart = async (): Promise<[UserAccount | null, string?]> => {
try {
const payCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + "/cart/pay",
method: "POST",
url: `${API_URL}/cart/pay`,
useToken: true,
})
});
return [payCartResponse]
return [payCartResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError)
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось оплатить товар из корзины. ${error}`]
return [null, `Не удалось оплатить товар из корзины. ${error}`];
}
}
};
export async function patchCurrency(
export const patchCurrency = async (
currency: string
): Promise<[UserAccount | null, string?]> {
): Promise<[UserAccount | null, string?]> => {
try {
const patchCurrencyResponse = await makeRequest<
{ currency: string },
UserAccount
>({
url: apiUrl + "/wallet",
method: "PATCH",
url: `${API_URL}/wallet`,
useToken: true,
body: {
currency,
},
})
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,48 +37,87 @@ 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 const getHistory = async (): 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
>({
method: "GET",
url: `${API_URL}/history?page=1&limit=100&type=payCart`,
useToken: true,
})
});
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
return [historyResponse] as [GetHistoryResponse2]
return [historyResponse] as [GetHistoryResponse2];
}
const checked = historyResponse.records.map((data) => {
//const buffer:RawDetails[] = [];
/*(data.rawDetails as HistoryRecord["rawDetails"]).forEach((slot) => {
const index = regList[slot.Key]
buffer[index] = { ...slot }
})*/
//Чистим дыры с помощью .filter(() => true)
//@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<[void | null, string?]> => {
debugger;
try {
const sendReportResponse = await makeRequest<{ id: string }, void>({
method: "POST",
url: `${API_URL}/sendReport`,
body: { id },
});
return [sendReportResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отправить отчёт. ${error}`];
}
};
export const sendReportById = async (
tariffId: string
): Promise<[void | null, string?]> => {
debugger;
try {
const sendReportResponse = await makeRequest<never, void>({
method: "POST",
url: `${API_URL}/sendReport/${tariffId}`,
});
return [sendReportResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отправить отчёт. ${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;

@ -1,21 +1,21 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import type { GetDiscountsResponse } from "@root/model/discount";
import { useUserStore } from "@root/stores/user";
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) {
export const getDiscounts = async (userId: string | null) => {
if (userId === null) {
return;
}
try {
const discountsResponse = await makeRequest<never, GetDiscountsResponse>({
url: `${apiUrl}/discount/user/${userId}`,
method: "get",
method: "GET",
url: `${API_URL}/discount/user/${userId}`,
useToken: true,
});
@ -25,9 +25,9 @@ export async function getDiscounts(userId: string | null) {
throw new Error(`Ошибка получения списка скидок. ${error}`);
}
}
};
export function useDiscounts(userId: string | null) {
export const useDiscounts = (userId: string | null) => {
const { data } = useSWR("discounts", () => getDiscounts(userId), {
keepPreviousData: true,
onError: (error) => {
@ -38,4 +38,4 @@ export function useDiscounts(userId: string | null) {
});
return data;
}
};

@ -1,34 +1,27 @@
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) {
export const activatePromocode = async (
promocode: string
): Promise<[string | null, string?]> => {
try {
const response = await makeRequest<
| {
codeword: string;
}
| {
fastLink: string;
},
{
greetings: string;
}
{ codeword: string } | { fastLink: string },
{ greetings: string }
>({
url: apiUrl + "/activate",
method: "POST",
url: `${API_URL}/activate`,
body: { codeword: promocode },
contentType: true,
body: {
codeword: promocode,
},
});
return response.greetings;
return [response.greetings];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
throw new Error(error);
return [null, error];
}
}
};

@ -1,17 +1,30 @@
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`;
type GetRecentlyPurchasedTariffsResponse = {
id: string;
};
export const getRecentlyPurchasedTariffs = async (): Promise<
[GetRecentlyPurchasedTariffsResponse[] | null, string?]
> => {
try {
const recentlyPurchased = await makeRequest<never, any>({
url: process.env.REACT_APP_DOMAIN + "/customer/recent",
method: "get",
debugger;
const recentlyPurchased = await makeRequest<
never,
GetRecentlyPurchasedTariffsResponse[]
>({
method: "GET",
url: `${API_URL}/recent`,
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,22 +1,32 @@
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"
interface CreateTariffBody {
name: string;
price?: number;
isCustom: boolean;
privileges: PrivilegeWithoutPrice[];
}
export async function getTariffs(
const API_URL = `${process.env.REACT_APP_DOMAIN}/strator`;
export const getTariffs = async (
apiPage: number,
tariffsPerPage: number,
signal: AbortSignal | undefined
): Promise<[GetTariffsResponse | null, string?]> {
): Promise<[GetTariffsResponse | null, string?]> => {
try {
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
url: apiUrl + `/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
method: "get",
method: "GET",
url: `${API_URL}/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
useToken: true,
signal,
});
@ -27,22 +37,17 @@ export async function getTariffs(
return [null, `Не удалось получить список тарифов. ${error}`];
}
}
};
interface CreateTariffBody {
name: string;
price?: number;
isCustom: boolean;
privileges: PrivilegeWithoutPrice[];
}
export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff | null, string?]> {
export const createTariff = async (
tariff: CreateTariffBody
): Promise<[Tariff | null, string?]> => {
try {
const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({
url: `${apiUrl}/tariff`,
method: "post",
useToken: true,
method: "POST",
url: `${API_URL}/tariff`,
body: tariff,
useToken: true,
});
return [createTariffResponse];
@ -51,13 +56,15 @@ export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff |
return [null, `Не удалось создать тариф. ${error}`];
}
}
};
export async function getTariffById(tariffId: string): Promise<[Tariff | null, string?, number?]> {
export const getTariffById = async (
tariffId: string
): Promise<[Tariff | null, string?, number?]> => {
try {
const getTariffByIdResponse = await makeRequest<never, Tariff>({
url: `${apiUrl}/tariff/${tariffId}`,
method: "get",
method: "GET",
url: `${API_URL}/tariff/${tariffId}`,
useToken: true,
});
@ -67,22 +74,25 @@ export async function getTariffById(tariffId: string): Promise<[Tariff | null, s
return [null, `Не удалось получить тарифы. ${error}`, status];
}
}
};
export async function getCustomTariffs(
export const getCustomTariffs = async (
signal: AbortSignal | undefined
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> {
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> => {
try {
const customTariffsResponse = await makeRequest<null, ServiceKeyToPrivilegesMap>({
url: apiUrl + "/privilege/service",
const customTariffsResponse = await makeRequest<
null,
ServiceKeyToPrivilegesMap
>({
method: "GET",
url: `${API_URL}/privilege/service`,
signal,
method: "get",
useToken: true,
});
const tempCustomTariffsResponse = {
...customTariffsResponse,
squiz: customTariffsResponse.squiz
squiz: customTariffsResponse.squiz,
};
return [tempCustomTariffsResponse];
@ -91,18 +101,20 @@ export async function getCustomTariffs(
return [null, `Не удалось получить мои тарифы. ${error}`];
}
}
};
export async function getTariffArray(tariffIds: string[] | undefined) {
export const getTariffArray = async (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}`,
method: "get",
method: "GET",
url: `${API_URL}/tariff/${tariffId}`,
useToken: true,
})
));
)
);
const tariffs: Tariff[] = [];
@ -121,4 +133,4 @@ export async function getTariffArray(tariffIds: string[] | undefined) {
});
return tariffs;
}
};

@ -1,23 +1,29 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "@root/utils/parse-error";
import { createTicket as createTicketRequest } from "@frontend/kitui";
import type { CreateTicketResponse } from "@frontend/kitui";
import { SendTicketMessageRequest } from "@frontend/kitui";
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
type SendFileResponse = {
message: string;
};
export async function sendTicketMessage(
const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0`;
export const sendTicketMessage = async (
ticketId: string,
message: string
): Promise<[null, string?]> {
): Promise<[null, string?]> => {
try {
const sendTicketMessageResponse = await makeRequest<
SendTicketMessageRequest,
null
>({
url: `${apiUrl}/send`,
method: "POST",
useToken: true,
url: `${API_URL}/send`,
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
useToken: true,
});
return [sendTicketMessageResponse];
@ -26,15 +32,15 @@ export async function sendTicketMessage(
return [null, `Не удалось отправить сообщение. ${error}`];
}
}
};
export async function shownMessage(id: string): Promise<[null, string?]> {
export const shownMessage = async (id: string): Promise<[null, string?]> => {
try {
const shownMessageResponse = await makeRequest<{ id: string }, null>({
url: apiUrl + "/shown",
method: "POST",
useToken: true,
url: `${API_URL}/shown`,
body: { id },
useToken: true,
});
return [shownMessageResponse];
@ -43,4 +49,46 @@ 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: `${API_URL}/sendFiles`,
body,
});
return [sendResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отправить файл. ${error}`];
}
};
export const createTicket = async (
ticketNameField: string,
ticketBodyField: string
): Promise<[CreateTicketResponse | null, string?]> => {
try {
const createTicketResponse = await createTicketRequest({
url: `${API_URL}/create`,
body: { Title: ticketNameField, Message: ticketBodyField },
});
return [createTicketResponse];
} 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(
export const patchUser = async (
user: PatchUserRequest
): Promise<[User | null, string?]> {
): Promise<[User | null, string?]> => {
try {
const patchUserResponse = await makeRequest<PatchUserRequest, User>({
url: apiUrl+"/",
contentType: true,
method: "PATCH",
url: `${API_URL}/`,
body: user,
contentType: true,
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,83 +1,110 @@
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(
export const verification = async (
userId: string
): Promise<[Verification | null, string?]> {
): Promise<[Verification | null, string?]> => {
try {
const verificationResponse = await makeRequest<never, Verification>({
url: apiUrl + "/verification/" + userId,
method: "GET",
url: `${API_URL}/${userId}`,
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, "нет данных"];
}
}
export async function sendDocuments(
return [null, `Ошибка запроса верификации. ${error}`];
}
};
export const sendDocuments = async (
documents: SendDocumentsArgs
): Promise<[Verification | "OK" | null, string?]> {
): Promise<[Verification | "OK" | null, string?]> => {
try {
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification",
method: "POST",
url: API_URL,
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
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}`];
}
}
};
export async function updateDocuments(
export const updateDocuments = async (
documents: UpdateDocumentsArgs
): Promise<[Verification | "OK" | null, string? ]> {
): Promise<[Verification | "OK" | null, string?]> => {
try {
const updateDocumentsResponse = await makeRequest<FormData, Verification>({
url: apiUrl + "/verification/file",
method: "PATCH",
useToken: true,
withCredentials: true,
url: `${API_URL}/file`,
body: jsonToFormdata(
documents.inn ? { ...documents, egrule: documents.inn } : documents
),
})
useToken: true,
withCredentials: true,
});
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>({
method: "PATCH",
url: API_URL,
body,
useToken: true,
withCredentials: true,
});
return [updateDocumentResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка обновления документа. ${error}`];
}
};

@ -1,4 +1,4 @@
import makeRequest from "@api/makeRequest"
import makeRequest from "@api/makeRequest";
import { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
import { parseAxiosError } from "@root/utils/parse-error";
@ -7,14 +7,14 @@ const isStaging = (() => {
return host.includes("s") ? "s" : "";
})();
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer`;
interface PaymentBody {
type: string;
amount: number;
}
export async function sendPayment({
export const sendPayment = async ({
userId,
body,
fromSquiz,
@ -24,18 +24,8 @@ export async function sendPayment({
body: PaymentBody;
fromSquiz: boolean;
paymentPurpose: "paycart" | "replenishwallet";
}): Promise<[SendPaymentResponse | null, string?]> {
try {
const sendPaymentResponse = await makeRequest<
SendPaymentRequest,
SendPaymentResponse
>({
url: apiUrl + "/wallet",
contentType: true,
method: "POST",
useToken: true,
withCredentials: false,
body: {
}): Promise<[SendPaymentResponse | null, string?]> => {
const reqeustBody = {
currency: "RUB",
bankCard: {
number: "RUB",
@ -50,7 +40,19 @@ export async function sendPayment({
fromSquiz ? "quiz" : "hub"
}&purpose=${paymentPurpose}&userid=${userId}`,
...body,
},
};
try {
const sendPaymentResponse = await makeRequest<
SendPaymentRequest,
SendPaymentResponse
>({
method: "POST",
url: `${API_URL}/wallet`,
body: reqeustBody,
contentType: true,
useToken: true,
withCredentials: false,
});
return [sendPaymentResponse];
@ -59,22 +61,24 @@ export async function sendPayment({
return [null, `Ошибка оплаты. ${error}`];
}
}
};
export const sendRSPayment = async (money: number): Promise<string | null> => {
export const sendRSPayment = async (
money: number
): Promise<[string | null, string?]> => {
try {
await makeRequest<unknown, string>({
url: apiUrl + "/wallet/rspay",
const sendRSPaymentResponse = await makeRequest<{ money: number }, string>({
method: "POST",
url: `${API_URL}/wallet/rspay`,
body: { money },
useToken: true,
body: { money: money },
withCredentials: false,
});
return null;
return [sendRSPaymentResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return `Ошибка оплаты. ${error}`;
return [null, `Ошибка оплаты. ${error}`];
}
};

@ -10,9 +10,7 @@ import {
useMediaQuery,
useTheme,
} from "@mui/material";
import makeRequest from "@api/makeRequest"
import {
createTicket,
getMessageFromFetchError,
throttle,
TicketMessage,
@ -28,13 +26,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,
@ -56,6 +58,7 @@ import AttachFileIcon from "@mui/icons-material/AttachFile";
import ChatDocument from "./ChatDocument";
import ChatImage from "./ChatImage";
import ChatVideo from "./ChatVideo";
import { createTicket } from "@api/ticket";
type ModalWarningType =
| "errorType"
@ -76,7 +79,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(800));
const [messageField, setMessageField] = useState<string>("");
const [disableFileButton, setDisableFileButton] = useState(false);
const [disableFileButton, setDisableFileButton] = useState<boolean>(false);
const [sseEnabled, setSseEnabled] = useState(true);
const [modalWarningType, setModalWarningType] =
useState<ModalWarningType>(null);
@ -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) {
@ -199,7 +202,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
},
onFetchStateChange: () => { },
onFetchStateChange: () => {},
enabled: Boolean(user),
});
@ -228,7 +231,6 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
scrollToBottom();
}, [open]);
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
@ -265,29 +267,24 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
if (!sessionData?.ticketId) {
setIsMessageSending(true);
createTicket({
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
body: {
Title: "Unauth title",
Message: messageField,
},
useToken: Boolean(user),
})
.then((response) => {
const [createTicketresult, createTicketerror] = await createTicket(
"Unauth title",
messageField
);
if (createTicketerror) {
enqueueSnackbar(createTicketerror);
} else if (createTicketresult) {
setTicketData({
ticketId: response.Ticket,
sessionId: response.sess,
ticketId: createTicketresult.Ticket,
sessionId: createTicketresult.sess,
});
setSseEnabled(true);
})
.catch((error) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
})
.finally(() => {
}
setMessageField("");
setIsMessageSending(false);
});
} else {
setIsMessageSending(true);
@ -331,18 +328,18 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
let data;
if (!ticket.sessionData?.ticketId) {
try {
data = await createTicket({
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
body: {
Title: "Unauth title",
Message: "",
},
useToken: Boolean(user),
});
const [createTicketresult] = await createTicket("Unauth title", "");
if (createTicketresult) {
data = createTicketresult;
}
if (data) {
setTicketData({
ticketId: data.Ticket,
sessionId: data.sess,
});
}
} catch (error: any) {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
@ -353,20 +350,13 @@ 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;
}
};
@ -377,6 +367,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
enqueueSnackbar(check);
return;
}
setDisableFileButton(true);
await sendFile(file);
setDisableFileButton(false);
@ -547,8 +538,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

@ -98,7 +98,7 @@ export default function ChatDocument({
</svg>
<Link
download=""
href={`https://storage.yandexcloud.net/pair/${file}`}
href={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${file}`}
style={{
color: "#7E2AEA",
display: "flex",

@ -110,7 +110,7 @@ export default function ChatImage({
height: "217px",
width: "217px",
}}
src={`https://storage.yandexcloud.net/pair/${file}`}
src={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${file}`}
/>
</ButtonBase>
</Box>

@ -113,7 +113,9 @@ export default function ChatImage({
}}
controls
>
<source src={`https://storage.yandexcloud.net/pair/${file}`} />
<source
src={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${file}`}
/>
</Box>
</Box>
</Box>

@ -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,61 +29,65 @@ 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) {
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("Извините, произошла ошибка");
}
}
navigate("/settings")
if(verificationStatus !== VerificationStatus.VERIFICATED && !OrgName){
navigate("/settings");
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
enqueueSnackbar("Пройдите верификацию и заполните название организации");
} else if(!OrgName){
} else if (!OrgName) {
enqueueSnackbar("Заполните поле название организации");
}else if(verificationStatus !== VerificationStatus.VERIFICATED) {
} else if (verificationStatus !== VerificationStatus.VERIFICATED) {
enqueueSnackbar("Пройдите верификацию");
}
}
@ -100,13 +102,12 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
last={last}
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>)
)}
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>
))}
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,40 +255,42 @@ 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",
bgcolor:"#EEE4FC",
bgcolor: "#EEE4FC",
stroke: "#7E2AEA",
borderRadius: 2,
"&:hover": {
bgcolor:"#7E2AEA",
bgcolor: "#7E2AEA",
stroke: "white",
},
"&:active": {
bgcolor:"black",
bgcolor: "black",
stroke: "white",
}
},
}}
>
<File></File>
</IconButton>
</>
}
)}
</Box>
</Box>
{isMobile &&
{isMobile && (
<>
<IconButton
onClick={(e) => {
@ -287,40 +309,42 @@ 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",
bgcolor:"#EEE4FC",
bgcolor: "#EEE4FC",
stroke: "#7E2AEA",
borderRadius: 2,
"&:hover": {
bgcolor:"#7E2AEA",
bgcolor: "#7E2AEA",
stroke: "white",
},
"&:active": {
bgcolor:"black",
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 {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 { getDeclension } from "@utils/declension";
import { enqueueSnackbar } from "notistack";
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 { 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) {
@ -53,24 +61,23 @@ 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) {
if (verificationStatus === VerificationStatus.VERIFICATED && OrgName) {
const [, sendReportError] = await sendReport(logId);
if (!sendReportError) {
return enqueueSnackbar(
"Акт будет отправлен на почту, указанную при регистрации"
);
}
enqueueSnackbar("Извините, произошла ошибка");
}
}
navigate("/settings")
if(verificationStatus !== VerificationStatus.VERIFICATED && !OrgName){
navigate("/settings");
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
enqueueSnackbar("Пройдите верификацию и заполните название организации");
} else if(!OrgName){
} else if (!OrgName) {
enqueueSnackbar("Заполните поле название организации");
}else if(verificationStatus !== VerificationStatus.VERIFICATED) {
} else if (verificationStatus !== VerificationStatus.VERIFICATED) {
enqueueSnackbar("Пройдите верификацию");
}
}
@ -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,9 +146,11 @@ 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",
width: upMd ? "200px" : "auto",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis",
@ -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>
</>
}
)}
</>
}
/>

@ -1,25 +1,28 @@
import {useState} from "react";
import { useState } from "react";
import {
Box,
IconButton,
Typography,
useMediaQuery,
useTheme
useTheme,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import SectionWrapper from "@root/components/SectionWrapper";
import {Select} from "@root/components/Select";
import {Tabs} from "@root/components/Tabs";
import { Select } from "@root/components/Select";
import { Tabs } from "@root/components/Tabs";
import AccordionWrapper from "./AccordionWrapper";
import {useHistoryTracker} from "@root/utils/hooks/useHistoryTracker";
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 { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
import { ErrorBoundary } from "react-error-boundary";
import { handleComponentError } from "@root/utils/handleComponentError";
import { useHistoryStore } from "@root/stores/history";
import { enqueueSnackbar } from "notistack";
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,18 +44,15 @@ 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
maxWidth="lg"
@ -72,7 +72,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 +89,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 +138,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
@ -143,7 +158,7 @@ export default function History() {
mainId={(e as HistoryRecord2).id}
createdAt={e.createdAt}
tariff={e.rawDetails.tariffs[0]}
price={e.rawDetails.price/100}
price={e.rawDetails.price / 100}
/>
</Box>
);

@ -139,10 +139,12 @@ export default function Payment() {
return;
}
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
const [sendRSPaymentResponse] = await sendRSPayment(
Number(paymentValueField)
);
if (sendRSPaymentError) {
return enqueueSnackbar(sendRSPaymentError);
if (sendRSPaymentResponse) {
return enqueueSnackbar(sendRSPaymentResponse);
}
enqueueSnackbar(
@ -153,18 +155,17 @@ export default function Payment() {
}
}
function handleApplyPromocode() {
async function handleApplyPromocode() {
if (!promocodeField) return;
activatePromocode(promocodeField)
.then((response) => {
enqueueSnackbar(response);
const [greeting, activateError] = await activatePromocode(promocodeField);
if (activateError) {
return enqueueSnackbar(activateError);
}
enqueueSnackbar(greeting);
mutate("discounts");
})
.catch((error) => {
if (error.message !== "" && typeof error.message === "string")
enqueueSnackbar(error.message);
});
}
return (

@ -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");

@ -12,7 +12,7 @@ export default function ChatImageNewWindow() {
maxHeight: "100vh",
maxWidth: "100vw",
}}
src={`https://storage.yandexcloud.net/pair/${srcImage}`}
src={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${srcImage}`}
/>
</>
);

@ -1,32 +1,38 @@
import { Box, Typography, FormControl, InputBase, useMediaQuery, useTheme, Button } from "@mui/material"
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { enqueueSnackbar } from "notistack"
import { cardShadow } from "@root/utils/theme"
import { createTicket } from "@frontend/kitui"
import {
Box,
Typography,
FormControl,
InputBase,
useMediaQuery,
useTheme,
Button,
} from "@mui/material";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import { cardShadow } from "@root/utils/theme";
import { createTicket } from "@api/ticket";
export default function CreateTicket() {
const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md"))
const navigate = useNavigate()
const [ticketNameField, setTicketNameField] = useState<string>("")
const [ticketBodyField, setTicketBodyField] = useState<string>("")
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const navigate = useNavigate();
const [ticketNameField, setTicketNameField] = useState<string>("");
const [ticketBodyField, setTicketBodyField] = useState<string>("");
async function handleCreateTicket() {
if (!ticketBodyField || !ticketNameField) return
if (!ticketBodyField || !ticketNameField) return;
createTicket({
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
body: {
Title: ticketNameField,
Message: ticketBodyField,
const [createTicketresult, createTicketerror] = await createTicket(
ticketNameField,
ticketBodyField
);
if (createTicketerror) {
return enqueueSnackbar(createTicketerror);
}
}).then(result => {
navigate(`/support/${result.Ticket}`)
}).catch(error => {
enqueueSnackbar(error.message)
})
navigate(`/support/${createTicketresult?.Ticket}`);
}
return (
@ -39,9 +45,7 @@ export default function CreateTicket() {
borderRadius: "12px",
p: upMd ? "20px" : undefined,
gap: upMd ? "40px" : "20px",
boxShadow: upMd
? cardShadow
: undefined,
boxShadow: upMd ? cardShadow : undefined,
}}
>
<Box
@ -125,8 +129,10 @@ export default function CreateTicket() {
variant="pena-contained-dark"
onClick={handleCreateTicket}
disabled={!ticketBodyField || !ticketNameField}
>Отправить</Button>
>
Отправить
</Button>
</Box>
</Box>
)
);
}

@ -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;
}
};

@ -1,7 +1,17 @@
import SectionWrapper from "@components/SectionWrapper";
import { Tariff, calcTariffPrice, getMessageFromFetchError } from "@frontend/kitui";
import {
Tariff,
calcTariffPrice,
getMessageFromFetchError,
} from "@frontend/kitui";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
import {
Box,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import NumberIcon from "@root/components/NumberIcon";
import { useTariffStore } from "@root/stores/tariffs";
import { addTariffToCart, useUserStore } from "@root/stores/user";
@ -26,8 +36,6 @@ const StepperText: Record<string, string> = {
time: "Тарифы на время",
};
function TariffPage() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
@ -36,10 +44,12 @@ function TariffPage() {
const location = useLocation();
const tariffs = useTariffStore((state) => state.tariffs);
const [selectedItem, setSelectedItem] = useState<number>(0);
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
const userId = useUserStore(state => state.user?._id) ?? "";
const purchasesAmount =
useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
const userId = useUserStore((state) => state.user?._id) ?? "";
const discounts = useDiscounts(userId);
const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko";
const isUserNko =
useUserStore((state) => state.userAccount?.status) === "nko";
const currentTariffs = useCartTariffs();
const handleCustomBackNavigation = usePrevLocation(location);
@ -81,7 +91,10 @@ function TariffPage() {
return false;
});
const createTariffElements = (filteredTariffs: Tariff[], addFreeTariff = false) => {
const createTariffElements = (
filteredTariffs: Tariff[],
addFreeTariff = false
) => {
const tariffElements = filteredTariffs
.filter((tariff) => tariff.privileges.length > 0)
.map((tariff, index) => {
@ -91,17 +104,28 @@ function TariffPage() {
purchasesAmount,
currentTariffs ?? [],
isUserNko,
userId,
userId
);
return (
<TariffCard
key={tariff._id}
discount={(priceBeforeDiscounts - priceAfterDiscounts) ? `${((priceBeforeDiscounts - priceAfterDiscounts) / (priceBeforeDiscounts / 100)).toFixed(0)}%` : ""}
discount={
priceBeforeDiscounts - priceAfterDiscounts
? `${(
(priceBeforeDiscounts - priceAfterDiscounts) /
(priceBeforeDiscounts / 100)
).toFixed(0)}%`
: ""
}
icon={
<NumberIcon
number={index + 1}
color={unit === "time" ? theme.palette.purple.main : theme.palette.orange.main}
color={
unit === "time"
? theme.palette.purple.main
: theme.palette.orange.main
}
backgroundColor={unit === "time" ? "#EEE4FC" : "#FEDFD0"}
/>
}
@ -114,9 +138,17 @@ function TariffPage() {
price={
<>
{priceBeforeDiscounts !== priceAfterDiscounts && (
<Typography variant="oldPrice">{currencyFormatter.format(Math.trunc(priceBeforeDiscounts) / 100)}</Typography>
<Typography variant="oldPrice">
{currencyFormatter.format(
Math.trunc(priceBeforeDiscounts) / 100
)}
<Typography variant="price">{currencyFormatter.format(Math.trunc(priceAfterDiscounts) / 100)}</Typography>
</Typography>
)}
<Typography variant="price">
{currencyFormatter.format(
Math.trunc(priceAfterDiscounts) / 100
)}
</Typography>
</>
}
/>
@ -124,8 +156,10 @@ function TariffPage() {
});
if (addFreeTariff) {
if (tariffElements.length < 6) tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
else tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
if (tariffElements.length < 6)
tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
else
tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
}
return tariffElements;
@ -146,7 +180,13 @@ function TariffPage() {
{isMobile && (
<IconButton
onClick={handleCustomBackNavigation}
sx={{ p: 0, height: "28px", width: "28px", color: "black", marginRight: "10px" }}
sx={{
p: 0,
height: "28px",
width: "28px",
color: "black",
marginRight: "10px",
}}
>
<ArrowBackIcon />
</IconButton>
@ -186,7 +226,9 @@ function TariffPage() {
mb: "30px",
display: "grid",
gap: "40px",
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${isTablet ? "436px" : "360px"}))`,
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
isTablet ? "436px" : "360px"
}))`,
}}
>
{createTariffElements(filteredTariffs, 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;
@ -48,17 +47,15 @@ export default function RecoverDialog() {
initialValues,
validationSchema,
onSubmit: async (values, formikHelpers) => {
const [recoverResponse, recoverError] = await recover(
values.email.trim()
);
const [, recoverError] = await recover(values.email.trim());
formikHelpers.setSubmitting(false);
if (recoverError) {
enqueueSnackbar(recoverError);
return
return;
}
navigate("/")
enqueueSnackbar("Письмо придёт Вам на почту")
navigate("/");
enqueueSnackbar("Письмо придёт Вам на почту");
},
});

@ -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 = [];
}

@ -1,8 +1,8 @@
import { useEffect, useLayoutEffect, useRef } from "react"
import { devlog } from "@frontend/kitui"
import { useEffect, useLayoutEffect, useRef } from "react";
import { devlog } from "@frontend/kitui";
import { ServiceKeyToPrivilegesMap } from "@root/model/privilege"
import { getCustomTariffs } from "@root/api/tariff"
import { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
import { getCustomTariffs } from "@root/api/tariff";
export function useCustomTariffs({
onError,
@ -11,28 +11,35 @@ export function useCustomTariffs({
onNewUser: (response: ServiceKeyToPrivilegesMap) => void;
onError: (error: any) => void;
}) {
const onNewUserRef = useRef(onNewUser)
const onErrorRef = useRef(onError)
const onNewUserRef = useRef(onNewUser);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewUserRef.current = onNewUser
onErrorRef.current = onError
})
onNewUserRef.current = onNewUser;
onErrorRef.current = onError;
});
useEffect(() => {
const controller = new AbortController()
const controller = new AbortController();
getCustomTariffs(controller.signal)
.then(([customTariffs]) => {
if (customTariffs) {
onNewUserRef.current(customTariffs)
const getCustomTariffsRequest = async () => {
const [customTariffs, customTariffsError] = await getCustomTariffs(
controller.signal
);
if (customTariffsError) {
devlog("Error fetching custom tariffs", customTariffsError);
onErrorRef.current(customTariffsError);
return;
}
})
.catch(([_, error]) => {
devlog("Error fetching custom tariffs", error)
onErrorRef.current(error)
})
if (customTariffs) {
onNewUserRef.current(customTariffs);
}
};
return () => controller.abort()
}, [])
getCustomTariffsRequest();
return () => controller.abort();
}, []);
}

@ -1,8 +1,8 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react"
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { getTariffs } from "@root/api/tariff"
import { getTariffs } from "@root/api/tariff";
import type { Tariff } from "@frontend/kitui"
import type { Tariff } from "@frontend/kitui";
export function useTariffFetcher({
tariffsPerPage,
@ -16,32 +16,42 @@ export function useTariffFetcher({
onSuccess: (response: Tariff[]) => void;
onError?: (error: Error) => void;
}) {
const [fetchState, setFetchState] = useState<"fetching" | "idle" | "all fetched">("idle")
const onSuccessRef = useRef(onSuccess)
const onErrorRef = useRef(onError)
const [fetchState, setFetchState] = useState<
"fetching" | "idle" | "all fetched"
>("idle");
const onSuccessRef = useRef(onSuccess);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onSuccessRef.current = onSuccess
onErrorRef.current = onError
}, [onError, onSuccess])
onSuccessRef.current = onSuccess;
onErrorRef.current = onError;
}, [onError, onSuccess]);
useEffect(() => {
const controller = new AbortController()
const controller = new AbortController();
setFetchState("fetching")
getTariffs(apiPage, tariffsPerPage, controller.signal)
.then(([result]) => {
if (result && result.tariffs.length > 0) {
onSuccessRef.current(result.tariffs)
setFetchState("idle")
} else setFetchState("all fetched")
})
.catch(([_, error]) => {
onErrorRef.current?.(error)
})
const getTariffsRequest = async () => {
setFetchState("fetching");
const [tariffs, tariffsError] = await getTariffs(
apiPage,
tariffsPerPage,
controller.signal
);
return () => controller.abort()
}, [apiPage, tariffsPerPage])
if (tariffsError) {
return onErrorRef.current?.(new Error(tariffsError));
}
return fetchState
if (tariffs && tariffs.tariffs.length > 0) {
onSuccessRef.current(tariffs.tariffs);
setFetchState("idle");
} else setFetchState("all fetched");
};
getTariffsRequest();
return () => controller.abort();
}, [apiPage, tariffsPerPage]);
return fetchState;
}

@ -9,10 +9,10 @@ export type ServerError = {
const translateMessage: Record<string, string> = {
"user not found": "Пользователь не найден",
"invalid password": "Неправильный пароль",
"field <password> is empty": "Поле \"Пароль\" не заполнено",
"field <login> is empty": "Поле \"Логин\" не заполнено",
"field <email> is empty": "Поле \"E-mail\" не заполнено",
"field <phoneNumber> is empty": "Поле \"Номер телефона\" не заполнено",
"field <password> is empty": 'Поле "Пароль" не заполнено',
"field <login> is empty": 'Поле "Логин" не заполнено',
"field <email> is empty": 'Поле "E-mail" не заполнено',
"field <phoneNumber> is empty": 'Поле "Номер телефона" не заполнено',
"user with this email or login is exist": "Пользователь уже существует",
"user with this login is exist":
"Пользователь с таким логином уже существует",
@ -29,6 +29,10 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => {
if (error.response?.data) {
const serverError = error.response.data as ServerError;
let SEMessage;
if (typeof error.response?.data === "string") {
return [error.response?.data];
}
if ("statusCode" in (error.response?.data as ServerError)) {
SEMessage = serverError?.message.toLowerCase() || "";
}