Merge branch 'api' into 'dev'
refactor: makeRequests decomposed See merge request frontend/marketplace!202
This commit is contained in:
commit
5606fffc25
@ -9,19 +9,23 @@ import type {
|
|||||||
RegisterResponse,
|
RegisterResponse,
|
||||||
} from "@frontend/kitui";
|
} 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,
|
login: string,
|
||||||
password: string,
|
password: string,
|
||||||
phoneNumber: string
|
phoneNumber: string
|
||||||
): Promise<[RegisterResponse | null, string?]> {
|
): Promise<[RegisterResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const registerResponse = await makeRequest<
|
const registerResponse = await makeRequest<
|
||||||
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,
|
||||||
@ -33,15 +37,15 @@ export async function register(
|
|||||||
|
|
||||||
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function login(
|
export const login = async (
|
||||||
login: string,
|
login: string,
|
||||||
password: string
|
password: string
|
||||||
): 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,
|
||||||
@ -53,34 +57,40 @@ export async function login(
|
|||||||
|
|
||||||
return [null, `Не удалось войти. ${error}`];
|
return [null, `Не удалось войти. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function recover(
|
export const recover = async (
|
||||||
email: string
|
email: string
|
||||||
): Promise<[unknown | null, string?]> {
|
): Promise<[RecoverResponse | 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(
|
||||||
const recoverResponse = await makeRequest<unknown, unknown>({
|
"RedirectionURL",
|
||||||
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
|
`${process.env.REACT_APP_DOMAIN}/changepwd`
|
||||||
|
);
|
||||||
|
|
||||||
|
const recoverResponse = await makeRequest<FormData, RecoverResponse>({
|
||||||
|
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);
|
||||||
|
|
||||||
return [null, `Не удалось восстановить пароль. ${error}`];
|
return [null, `Не удалось восстановить пароль. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function logout(): Promise<[unknown, string?]> {
|
export const logout = async (): Promise<[void | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const logoutResponse = await makeRequest<never, void>({
|
const logoutResponse = await makeRequest<never, void>({
|
||||||
url: apiUrl + "/logout",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/logout`,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
@ -91,4 +101,4 @@ export async function logout(): Promise<[unknown, string?]> {
|
|||||||
|
|
||||||
return [null, `Не удалось выйти. ${error}`];
|
return [null, `Не удалось выйти. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
131
src/api/cart.ts
131
src/api/cart.ts
@ -1,83 +1,82 @@
|
|||||||
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 const patchCart = async (
|
||||||
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}`,
|
method: "PATCH",
|
||||||
method: "PATCH",
|
url: `${API_URL}/cart?id=${tariffId}`,
|
||||||
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}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function deleteCart(
|
export const deleteCart = async (
|
||||||
tariffId: string
|
tariffId: string
|
||||||
): 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}`,
|
method: "DELETE",
|
||||||
method: "DELETE",
|
url: `${API_URL}/cart?id=${tariffId}`,
|
||||||
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 const payCart = async (): Promise<[UserAccount | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const payCartResponse = await makeRequest<never, UserAccount>({
|
const payCartResponse = await makeRequest<never, UserAccount>({
|
||||||
url: apiUrl + "/cart/pay",
|
method: "POST",
|
||||||
method: "POST",
|
url: `${API_URL}/cart/pay`,
|
||||||
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}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function patchCurrency(
|
export const patchCurrency = async (
|
||||||
currency: string
|
currency: string
|
||||||
): Promise<[UserAccount | null, string?]> {
|
): Promise<[UserAccount | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const patchCurrencyResponse = await makeRequest<
|
const patchCurrencyResponse = await makeRequest<
|
||||||
{ currency: string },
|
{ currency: string },
|
||||||
UserAccount
|
UserAccount
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/wallet",
|
method: "PATCH",
|
||||||
method: "PATCH",
|
url: `${API_URL}/wallet`,
|
||||||
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,84 +1,123 @@
|
|||||||
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;
|
||||||
records: HistoryRecord[];
|
records: HistoryRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HistoryRecord = {
|
export type HistoryRecord = {
|
||||||
comment: string;
|
comment: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
id: string;
|
id: string;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
key: string;
|
key: string;
|
||||||
rawDetails: [RawDetails, KeyValue];
|
rawDetails: [RawDetails, KeyValue];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GetHistoryResponse2 {
|
export interface GetHistoryResponse2 {
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
records: HistoryRecord2[];
|
records: HistoryRecord2[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HistoryRecord2 = {
|
export type HistoryRecord2 = {
|
||||||
comment: string;
|
comment: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
id: string;
|
id: string;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
key: string;
|
key: string;
|
||||||
rawDetails: {
|
rawDetails: {
|
||||||
price: number;
|
price: number;
|
||||||
tariffs: Tariff[];
|
tariffs: Tariff[];
|
||||||
};
|
};
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
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`;
|
||||||
try {
|
|
||||||
const historyResponse = await makeRequest<never, GetHistoryResponse|GetHistoryResponse2>({
|
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/history?page=1&limit=100&type=payCart",
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
|
export const getHistory = async (): Promise<
|
||||||
return [historyResponse] as [GetHistoryResponse2]
|
[GetHistoryResponse | GetHistoryResponse2 | null, string?]
|
||||||
}
|
> => {
|
||||||
|
try {
|
||||||
|
const historyResponse = await makeRequest<
|
||||||
|
never,
|
||||||
|
GetHistoryResponse | GetHistoryResponse2
|
||||||
|
>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/history?page=1&limit=100&type=payCart`,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
|
||||||
const checked = historyResponse.records.map((data) => {
|
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
|
||||||
//const buffer:RawDetails[] = [];
|
return [historyResponse] as [GetHistoryResponse2];
|
||||||
/*(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
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const checked = historyResponse.records.map((data) => {
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
historyResponse.records = checked || []
|
return { ...data, rawDetails: checkedRowDetails } as HistoryRecord;
|
||||||
return [historyResponse]
|
});
|
||||||
} catch (nativeError) {
|
|
||||||
const [error] = parseAxiosError(nativeError)
|
|
||||||
|
|
||||||
return [null, `Не удалось получить историю. ${error}`]
|
historyResponse.records = checked || [];
|
||||||
}
|
return [historyResponse];
|
||||||
}
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
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 { 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>(
|
||||||
try {
|
data: MakeRequest
|
||||||
const response = await KIT.makeRequest<unknown>(data)
|
): Promise<TResponse> {
|
||||||
|
try {
|
||||||
|
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 &&
|
||||||
clearAuthToken();
|
(error.response?.data as ErrorResponseData)?.message ===
|
||||||
clearUserData();
|
"refreshToken is empty"
|
||||||
clearCustomTariffs();
|
) {
|
||||||
clearTickets();
|
clearAuthToken();
|
||||||
setNotEnoughMoneyAmount(0)
|
clearUserData();
|
||||||
redirect("/");
|
clearCustomTariffs();
|
||||||
}
|
clearTickets();
|
||||||
throw e
|
setNotEnoughMoneyAmount(0);
|
||||||
};
|
redirect("/");
|
||||||
};
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
export default makeRequest;
|
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 type { GetDiscountsResponse } from "@root/model/discount";
|
||||||
import { useUserStore } from "@root/stores/user";
|
import { useUserStore } from "@root/stores/user";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
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 const getDiscounts = async (userId: string | null) => {
|
||||||
if (userId === null) {
|
if (userId === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const discountsResponse = await makeRequest<never, GetDiscountsResponse>({
|
const discountsResponse = await makeRequest<never, GetDiscountsResponse>({
|
||||||
url: `${apiUrl}/discount/user/${userId}`,
|
method: "GET",
|
||||||
method: "get",
|
url: `${API_URL}/discount/user/${userId}`,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ export async function getDiscounts(userId: string | null) {
|
|||||||
|
|
||||||
throw new Error(`Ошибка получения списка скидок. ${error}`);
|
throw new Error(`Ошибка получения списка скидок. ${error}`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export function useDiscounts(userId: string | null) {
|
export const useDiscounts = (userId: string | null) => {
|
||||||
const { data } = useSWR("discounts", () => getDiscounts(userId), {
|
const { data } = useSWR("discounts", () => getDiscounts(userId), {
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -38,4 +38,4 @@ export function useDiscounts(userId: string | null) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
|
@ -1,34 +1,27 @@
|
|||||||
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 const activatePromocode = async (
|
||||||
|
promocode: string
|
||||||
|
): Promise<[string | null, 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",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/activate`,
|
||||||
|
body: { codeword: promocode },
|
||||||
contentType: true,
|
contentType: true,
|
||||||
body: {
|
|
||||||
codeword: promocode,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.greetings;
|
return [response.greetings];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
throw new Error(error);
|
return [null, error];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,17 +1,30 @@
|
|||||||
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`;
|
||||||
try {
|
|
||||||
const recentlyPurchased = await makeRequest<never, any>({
|
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/recent",
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
})
|
|
||||||
return [recentlyPurchased]
|
|
||||||
} catch (nativeError) {
|
|
||||||
const [error] = parseAxiosError(nativeError)
|
|
||||||
|
|
||||||
return [null, `Не удалось получить историю. ${error}`]
|
type GetRecentlyPurchasedTariffsResponse = {
|
||||||
}
|
id: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const getRecentlyPurchasedTariffs = async (): Promise<
|
||||||
|
[GetRecentlyPurchasedTariffsResponse[] | null, string?]
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
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}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,124 +1,136 @@
|
|||||||
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"
|
|
||||||
|
|
||||||
export async function getTariffs(
|
|
||||||
apiPage: number,
|
|
||||||
tariffsPerPage: number,
|
|
||||||
signal: AbortSignal | undefined
|
|
||||||
): Promise<[GetTariffsResponse | null, string?]> {
|
|
||||||
try {
|
|
||||||
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
|
|
||||||
url: apiUrl + `/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
return [tariffsResponse];
|
|
||||||
} catch (nativeError) {
|
|
||||||
const [error] = parseAxiosError(nativeError);
|
|
||||||
|
|
||||||
return [null, `Не удалось получить список тарифов. ${error}`];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CreateTariffBody {
|
interface CreateTariffBody {
|
||||||
name: string;
|
name: string;
|
||||||
price?: number;
|
price?: number;
|
||||||
isCustom: boolean;
|
isCustom: boolean;
|
||||||
privileges: PrivilegeWithoutPrice[];
|
privileges: PrivilegeWithoutPrice[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTariff(tariff: CreateTariffBody): Promise<[Tariff | null, string?]> {
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/strator`;
|
||||||
try {
|
|
||||||
const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({
|
|
||||||
url: `${apiUrl}/tariff`,
|
|
||||||
method: "post",
|
|
||||||
useToken: true,
|
|
||||||
body: tariff,
|
|
||||||
});
|
|
||||||
|
|
||||||
return [createTariffResponse];
|
export const getTariffs = async (
|
||||||
} catch (nativeError) {
|
apiPage: number,
|
||||||
const [error] = parseAxiosError(nativeError);
|
tariffsPerPage: number,
|
||||||
|
signal: AbortSignal | undefined
|
||||||
return [null, `Не удалось создать тариф. ${error}`];
|
): Promise<[GetTariffsResponse | null, string?]> => {
|
||||||
}
|
try {
|
||||||
}
|
const tariffsResponse = await makeRequest<never, GetTariffsResponse>({
|
||||||
|
method: "GET",
|
||||||
export async function getTariffById(tariffId: string): Promise<[Tariff | null, string?, number?]> {
|
url: `${API_URL}/tariff?page=${apiPage}&limit=${tariffsPerPage}`,
|
||||||
try {
|
useToken: true,
|
||||||
const getTariffByIdResponse = await makeRequest<never, Tariff>({
|
signal,
|
||||||
url: `${apiUrl}/tariff/${tariffId}`,
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return [getTariffByIdResponse];
|
|
||||||
} catch (nativeError) {
|
|
||||||
const [error, status] = parseAxiosError(nativeError);
|
|
||||||
|
|
||||||
return [null, `Не удалось получить тарифы. ${error}`, status];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCustomTariffs(
|
|
||||||
signal: AbortSignal | undefined
|
|
||||||
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> {
|
|
||||||
try {
|
|
||||||
const customTariffsResponse = await makeRequest<null, ServiceKeyToPrivilegesMap>({
|
|
||||||
url: apiUrl + "/privilege/service",
|
|
||||||
signal,
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const tempCustomTariffsResponse = {
|
|
||||||
...customTariffsResponse,
|
|
||||||
squiz: customTariffsResponse.squiz
|
|
||||||
};
|
|
||||||
|
|
||||||
return [tempCustomTariffsResponse];
|
|
||||||
} catch (nativeError) {
|
|
||||||
const [error] = parseAxiosError(nativeError);
|
|
||||||
|
|
||||||
return [null, `Не удалось получить мои тарифы. ${error}`];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTariffArray(tariffIds: string[] | undefined) {
|
|
||||||
if (!tariffIds) return null;
|
|
||||||
|
|
||||||
const responses = await Promise.allSettled(tariffIds.map(tariffId =>
|
|
||||||
makeRequest<never, Tariff>({
|
|
||||||
url: `${apiUrl}/tariff/${tariffId}`,
|
|
||||||
method: "get",
|
|
||||||
useToken: true,
|
|
||||||
})
|
|
||||||
));
|
|
||||||
|
|
||||||
const tariffs: Tariff[] = [];
|
|
||||||
|
|
||||||
responses.forEach((response, index) => {
|
|
||||||
switch (response.status) {
|
|
||||||
case "fulfilled": {
|
|
||||||
tariffs.push(response.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "rejected": {
|
|
||||||
const [, status] = parseAxiosError(response.reason);
|
|
||||||
if (status === 404) removeTariffFromCart(tariffIds[index]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return tariffs;
|
return [tariffsResponse];
|
||||||
}
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить список тарифов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTariff = async (
|
||||||
|
tariff: CreateTariffBody
|
||||||
|
): Promise<[Tariff | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const createTariffResponse = await makeRequest<CreateTariffBody, Tariff>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/tariff`,
|
||||||
|
body: tariff,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [createTariffResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось создать тариф. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTariffById = async (
|
||||||
|
tariffId: string
|
||||||
|
): Promise<[Tariff | null, string?, number?]> => {
|
||||||
|
try {
|
||||||
|
const getTariffByIdResponse = await makeRequest<never, Tariff>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/tariff/${tariffId}`,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [getTariffByIdResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error, status] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить тарифы. ${error}`, status];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomTariffs = async (
|
||||||
|
signal: AbortSignal | undefined
|
||||||
|
): Promise<[ServiceKeyToPrivilegesMap | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const customTariffsResponse = await makeRequest<
|
||||||
|
null,
|
||||||
|
ServiceKeyToPrivilegesMap
|
||||||
|
>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/privilege/service`,
|
||||||
|
signal,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tempCustomTariffsResponse = {
|
||||||
|
...customTariffsResponse,
|
||||||
|
squiz: customTariffsResponse.squiz,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [tempCustomTariffsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить мои тарифы. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTariffArray = async (tariffIds: string[] | undefined) => {
|
||||||
|
if (!tariffIds) return null;
|
||||||
|
|
||||||
|
const responses = await Promise.allSettled(
|
||||||
|
tariffIds.map((tariffId) =>
|
||||||
|
makeRequest<never, Tariff>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/tariff/${tariffId}`,
|
||||||
|
useToken: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const tariffs: Tariff[] = [];
|
||||||
|
|
||||||
|
responses.forEach((response, index) => {
|
||||||
|
switch (response.status) {
|
||||||
|
case "fulfilled": {
|
||||||
|
tariffs.push(response.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rejected": {
|
||||||
|
const [, status] = parseAxiosError(response.reason);
|
||||||
|
if (status === 404) removeTariffFromCart(tariffIds[index]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tariffs;
|
||||||
|
};
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
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 { createTicket as createTicketRequest } from "@frontend/kitui";
|
||||||
|
import type { CreateTicketResponse } from "@frontend/kitui";
|
||||||
|
|
||||||
import { SendTicketMessageRequest } 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,
|
ticketId: string,
|
||||||
message: string
|
message: string
|
||||||
): Promise<[null, string?]> {
|
): Promise<[null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const sendTicketMessageResponse = await makeRequest<
|
const sendTicketMessageResponse = await makeRequest<
|
||||||
SendTicketMessageRequest,
|
SendTicketMessageRequest,
|
||||||
null
|
null
|
||||||
>({
|
>({
|
||||||
url: `${apiUrl}/send`,
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
url: `${API_URL}/send`,
|
||||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||||
|
useToken: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [sendTicketMessageResponse];
|
return [sendTicketMessageResponse];
|
||||||
@ -26,15 +32,15 @@ export async function sendTicketMessage(
|
|||||||
|
|
||||||
return [null, `Не удалось отправить сообщение. ${error}`];
|
return [null, `Не удалось отправить сообщение. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function shownMessage(id: string): Promise<[null, string?]> {
|
export const shownMessage = async (id: string): Promise<[null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
||||||
url: apiUrl + "/shown",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
url: `${API_URL}/shown`,
|
||||||
body: { id },
|
body: { id },
|
||||||
|
useToken: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [shownMessageResponse];
|
return [shownMessageResponse];
|
||||||
@ -43,4 +49,46 @@ 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: `${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 { 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 const patchUser = async (
|
||||||
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+"/",
|
method: "PATCH",
|
||||||
contentType: true,
|
url: `${API_URL}/`,
|
||||||
method: "PATCH",
|
body: user,
|
||||||
useToken: true,
|
contentType: true,
|
||||||
withCredentials: false,
|
useToken: true,
|
||||||
body: user,
|
withCredentials: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
return [patchUserResponse]
|
return [patchUserResponse];
|
||||||
} catch (nativeError) {
|
} 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 { 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 const verification = async (
|
||||||
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,
|
method: "GET",
|
||||||
method: "GET",
|
url: `${API_URL}/${userId}`,
|
||||||
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, "нет данных"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendDocuments(
|
return [null, `Ошибка запроса верификации. ${error}`];
|
||||||
documents: SendDocumentsArgs
|
}
|
||||||
): Promise<[Verification | "OK" | null, string?]> {
|
};
|
||||||
try {
|
|
||||||
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
|
|
||||||
url: apiUrl + "/verification",
|
|
||||||
method: "POST",
|
|
||||||
useToken: true,
|
|
||||||
withCredentials: true,
|
|
||||||
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
|
|
||||||
})
|
|
||||||
|
|
||||||
return [sendDocumentsResponse]
|
export const sendDocuments = async (
|
||||||
} catch (nativeError) {
|
documents: SendDocumentsArgs
|
||||||
const [error] = parseAxiosError(nativeError)
|
): Promise<[Verification | "OK" | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const sendDocumentsResponse = await makeRequest<FormData, Verification>({
|
||||||
|
method: "POST",
|
||||||
|
url: API_URL,
|
||||||
|
body: jsonToFormdata({ ...documents, egrule: documents.inn }),
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
return [null, `Ошибка отправки документов. ${error}`]
|
return [sendDocumentsResponse];
|
||||||
}
|
} catch (nativeError) {
|
||||||
}
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
export async function updateDocuments(
|
return [null, `Ошибка отправки документов. ${error}`];
|
||||||
documents: UpdateDocumentsArgs
|
}
|
||||||
): Promise<[Verification | "OK" | null, string? ]> {
|
};
|
||||||
try {
|
|
||||||
const updateDocumentsResponse = await makeRequest<FormData, Verification>({
|
|
||||||
url: apiUrl + "/verification/file",
|
|
||||||
method: "PATCH",
|
|
||||||
useToken: true,
|
|
||||||
withCredentials: true,
|
|
||||||
body: jsonToFormdata(
|
|
||||||
documents.inn ? { ...documents, egrule: documents.inn } : documents
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
return [updateDocumentsResponse]
|
export const updateDocuments = async (
|
||||||
} catch (nativeError) {
|
documents: UpdateDocumentsArgs
|
||||||
const [error] = parseAxiosError(nativeError)
|
): Promise<[Verification | "OK" | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const updateDocumentsResponse = await makeRequest<FormData, Verification>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/file`,
|
||||||
|
body: jsonToFormdata(
|
||||||
|
documents.inn ? { ...documents, egrule: documents.inn } : documents
|
||||||
|
),
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
return [null, `Ошибка обновления документов. ${error}`]
|
return [updateDocumentsResponse];
|
||||||
}
|
} catch (nativeError) {
|
||||||
}
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
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 { SendPaymentRequest, SendPaymentResponse } from "@root/model/wallet";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error";
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
@ -7,14 +7,14 @@ const isStaging = (() => {
|
|||||||
return host.includes("s") ? "s" : "";
|
return host.includes("s") ? "s" : "";
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer`;
|
||||||
|
|
||||||
interface PaymentBody {
|
interface PaymentBody {
|
||||||
type: string;
|
type: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendPayment({
|
export const sendPayment = async ({
|
||||||
userId,
|
userId,
|
||||||
body,
|
body,
|
||||||
fromSquiz,
|
fromSquiz,
|
||||||
@ -24,33 +24,35 @@ export async function sendPayment({
|
|||||||
body: PaymentBody;
|
body: PaymentBody;
|
||||||
fromSquiz: boolean;
|
fromSquiz: boolean;
|
||||||
paymentPurpose: "paycart" | "replenishwallet";
|
paymentPurpose: "paycart" | "replenishwallet";
|
||||||
}): Promise<[SendPaymentResponse | null, string?]> {
|
}): Promise<[SendPaymentResponse | null, string?]> => {
|
||||||
|
const reqeustBody = {
|
||||||
|
currency: "RUB",
|
||||||
|
bankCard: {
|
||||||
|
number: "RUB",
|
||||||
|
expiryYear: "2021",
|
||||||
|
expiryMonth: "05",
|
||||||
|
csc: "05",
|
||||||
|
cardholder: "IVAN IVANOV",
|
||||||
|
},
|
||||||
|
phoneNumber: "79000000000",
|
||||||
|
login: "login_test",
|
||||||
|
returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${
|
||||||
|
fromSquiz ? "quiz" : "hub"
|
||||||
|
}&purpose=${paymentPurpose}&userid=${userId}`,
|
||||||
|
...body,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sendPaymentResponse = await makeRequest<
|
const sendPaymentResponse = await makeRequest<
|
||||||
SendPaymentRequest,
|
SendPaymentRequest,
|
||||||
SendPaymentResponse
|
SendPaymentResponse
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/wallet",
|
|
||||||
contentType: true,
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/wallet`,
|
||||||
|
body: reqeustBody,
|
||||||
|
contentType: true,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
body: {
|
|
||||||
currency: "RUB",
|
|
||||||
bankCard: {
|
|
||||||
number: "RUB",
|
|
||||||
expiryYear: "2021",
|
|
||||||
expiryMonth: "05",
|
|
||||||
csc: "05",
|
|
||||||
cardholder: "IVAN IVANOV",
|
|
||||||
},
|
|
||||||
phoneNumber: "79000000000",
|
|
||||||
login: "login_test",
|
|
||||||
returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${
|
|
||||||
fromSquiz ? "quiz" : "hub"
|
|
||||||
}&purpose=${paymentPurpose}&userid=${userId}`,
|
|
||||||
...body,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [sendPaymentResponse];
|
return [sendPaymentResponse];
|
||||||
@ -59,22 +61,24 @@ export async function sendPayment({
|
|||||||
|
|
||||||
return [null, `Ошибка оплаты. ${error}`];
|
return [null, `Ошибка оплаты. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const sendRSPayment = async (money: number): Promise<string | null> => {
|
export const sendRSPayment = async (
|
||||||
|
money: number
|
||||||
|
): Promise<[string | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
await makeRequest<unknown, string>({
|
const sendRSPaymentResponse = await makeRequest<{ money: number }, string>({
|
||||||
url: apiUrl + "/wallet/rspay",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/wallet/rspay`,
|
||||||
|
body: { money },
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: { money: money },
|
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return [sendRSPaymentResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return `Ошибка оплаты. ${error}`;
|
return [null, `Ошибка оплаты. ${error}`];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -10,9 +10,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import makeRequest from "@api/makeRequest"
|
|
||||||
import {
|
import {
|
||||||
createTicket,
|
|
||||||
getMessageFromFetchError,
|
getMessageFromFetchError,
|
||||||
throttle,
|
throttle,
|
||||||
TicketMessage,
|
TicketMessage,
|
||||||
@ -28,13 +26,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,
|
||||||
@ -56,6 +58,7 @@ import AttachFileIcon from "@mui/icons-material/AttachFile";
|
|||||||
import ChatDocument from "./ChatDocument";
|
import ChatDocument from "./ChatDocument";
|
||||||
import ChatImage from "./ChatImage";
|
import ChatImage from "./ChatImage";
|
||||||
import ChatVideo from "./ChatVideo";
|
import ChatVideo from "./ChatVideo";
|
||||||
|
import { createTicket } from "@api/ticket";
|
||||||
|
|
||||||
type ModalWarningType =
|
type ModalWarningType =
|
||||||
| "errorType"
|
| "errorType"
|
||||||
@ -76,7 +79,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
||||||
const [messageField, setMessageField] = useState<string>("");
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
const [disableFileButton, setDisableFileButton] = useState(false);
|
const [disableFileButton, setDisableFileButton] = useState<boolean>(false);
|
||||||
const [sseEnabled, setSseEnabled] = useState(true);
|
const [sseEnabled, setSseEnabled] = useState(true);
|
||||||
const [modalWarningType, setModalWarningType] =
|
const [modalWarningType, setModalWarningType] =
|
||||||
useState<ModalWarningType>(null);
|
useState<ModalWarningType>(null);
|
||||||
@ -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;
|
||||||
@ -265,29 +267,24 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
|
|
||||||
if (!sessionData?.ticketId) {
|
if (!sessionData?.ticketId) {
|
||||||
setIsMessageSending(true);
|
setIsMessageSending(true);
|
||||||
createTicket({
|
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
const [createTicketresult, createTicketerror] = await createTicket(
|
||||||
body: {
|
"Unauth title",
|
||||||
Title: "Unauth title",
|
messageField
|
||||||
Message: messageField,
|
);
|
||||||
},
|
|
||||||
useToken: Boolean(user),
|
if (createTicketerror) {
|
||||||
})
|
enqueueSnackbar(createTicketerror);
|
||||||
.then((response) => {
|
} else if (createTicketresult) {
|
||||||
setTicketData({
|
setTicketData({
|
||||||
ticketId: response.Ticket,
|
ticketId: createTicketresult.Ticket,
|
||||||
sessionId: response.sess,
|
sessionId: createTicketresult.sess,
|
||||||
});
|
|
||||||
setSseEnabled(true);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
const errorMessage = getMessageFromFetchError(error);
|
|
||||||
if (errorMessage) enqueueSnackbar(errorMessage);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setMessageField("");
|
|
||||||
setIsMessageSending(false);
|
|
||||||
});
|
});
|
||||||
|
setSseEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessageField("");
|
||||||
|
setIsMessageSending(false);
|
||||||
} else {
|
} else {
|
||||||
setIsMessageSending(true);
|
setIsMessageSending(true);
|
||||||
|
|
||||||
@ -331,18 +328,18 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
let data;
|
let data;
|
||||||
if (!ticket.sessionData?.ticketId) {
|
if (!ticket.sessionData?.ticketId) {
|
||||||
try {
|
try {
|
||||||
data = await createTicket({
|
const [createTicketresult] = await createTicket("Unauth title", "");
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
|
||||||
body: {
|
if (createTicketresult) {
|
||||||
Title: "Unauth title",
|
data = createTicketresult;
|
||||||
Message: "",
|
}
|
||||||
},
|
|
||||||
useToken: Boolean(user),
|
if (data) {
|
||||||
});
|
setTicketData({
|
||||||
setTicketData({
|
ticketId: data.Ticket,
|
||||||
ticketId: data.Ticket,
|
sessionId: data.sess,
|
||||||
sessionId: data.sess,
|
});
|
||||||
});
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = getMessageFromFetchError(error);
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
if (errorMessage) enqueueSnackbar(errorMessage);
|
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||||
@ -353,20 +350,13 @@ 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(ticketId, file);
|
||||||
body.append("ticket", ticketId);
|
|
||||||
await makeRequest({
|
if (sendFileError) {
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
|
enqueueSnackbar(sendFileError);
|
||||||
body: body,
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = getMessageFromFetchError(error);
|
|
||||||
if (errorMessage) enqueueSnackbar(errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -377,6 +367,7 @@ export default function Chat({ open = false, onclickArrow, sx }: Props) {
|
|||||||
enqueueSnackbar(check);
|
enqueueSnackbar(check);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisableFileButton(true);
|
setDisableFileButton(true);
|
||||||
await sendFile(file);
|
await sendFile(file);
|
||||||
setDisableFileButton(false);
|
setDisableFileButton(false);
|
||||||
@ -547,8 +538,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
|
||||||
|
@ -21,8 +21,8 @@ export default function ChatDocument({
|
|||||||
const messageBackgroundColor = isSelf
|
const messageBackgroundColor = isSelf
|
||||||
? "white"
|
? "white"
|
||||||
: unAuthenticated
|
: unAuthenticated
|
||||||
? "#EFF0F5"
|
? "#EFF0F5"
|
||||||
: "#434657";
|
: "#434657";
|
||||||
|
|
||||||
const date = new Date(createdAt);
|
const date = new Date(createdAt);
|
||||||
const today = isDateToday(date);
|
const today = isDateToday(date);
|
||||||
@ -98,7 +98,7 @@ export default function ChatDocument({
|
|||||||
</svg>
|
</svg>
|
||||||
<Link
|
<Link
|
||||||
download=""
|
download=""
|
||||||
href={`https://storage.yandexcloud.net/pair/${file}`}
|
href={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${file}`}
|
||||||
style={{
|
style={{
|
||||||
color: "#7E2AEA",
|
color: "#7E2AEA",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -28,8 +28,8 @@ export default function ChatImage({
|
|||||||
const messageBackgroundColor = isSelf
|
const messageBackgroundColor = isSelf
|
||||||
? "white"
|
? "white"
|
||||||
: unAuthenticated
|
: unAuthenticated
|
||||||
? "#EFF0F5"
|
? "#EFF0F5"
|
||||||
: "#434657";
|
: "#434657";
|
||||||
|
|
||||||
const date = new Date(createdAt);
|
const date = new Date(createdAt);
|
||||||
const today = isDateToday(date);
|
const today = isDateToday(date);
|
||||||
@ -110,7 +110,7 @@ export default function ChatImage({
|
|||||||
height: "217px",
|
height: "217px",
|
||||||
width: "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>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -113,7 +113,9 @@ export default function ChatImage({
|
|||||||
}}
|
}}
|
||||||
controls
|
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>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,135 +1,134 @@
|
|||||||
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() {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
}
|
||||||
|
|
||||||
function handleChooseFileClick() {
|
const downloadFile = async () => {
|
||||||
fileInputRef.current?.click()
|
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
|
||||||
}
|
responseType: "arraybuffer",
|
||||||
|
});
|
||||||
|
|
||||||
const downloadFile = async () => {
|
if (!data) {
|
||||||
const { data } = await axios.get<ArrayBuffer>(documentUrl, {
|
return;
|
||||||
responseType: "arraybuffer",
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (!data) {
|
downloadFileToDevice(
|
||||||
return
|
`${documentUrl.split("/").pop()?.split(".")?.[0] || "document"}.pdf`,
|
||||||
}
|
Buffer.from(data)
|
||||||
|
);
|
||||||
|
|
||||||
downloadFileToDevice(
|
return;
|
||||||
`${documentUrl.split("/").pop()?.split(".")?.[0] || "document"}.pdf`,
|
};
|
||||||
Buffer.from(data)
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
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");
|
||||||
|
const [, updateDocumentError] = await updateDocument(
|
||||||
|
jsonToFormdata({ [keyName]: readedFile })
|
||||||
|
);
|
||||||
|
|
||||||
async function sendDocument(
|
if (updateDocumentError) {
|
||||||
e: ChangeEvent<HTMLInputElement>
|
return enqueueSnackbar(
|
||||||
) {
|
`Ошибка отправки документов. ${updateDocumentError}`
|
||||||
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 }),
|
|
||||||
})
|
|
||||||
|
|
||||||
enqueueSnackbar("Данные обновлены")
|
enqueueSnackbar("Данные обновлены");
|
||||||
} catch (nativeError) {
|
}
|
||||||
const [error] = parseAxiosError(nativeError)
|
}
|
||||||
|
|
||||||
enqueueSnackbar(`Ошибка отправки документов. ${error}`)
|
return (
|
||||||
}
|
<Box
|
||||||
}
|
sx={{
|
||||||
}
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "start",
|
||||||
|
gap: "10px",
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#4D4D4D",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontVariantNumeric: "tabular-nums",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
{documentUrl && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{ color: theme.palette.purple.main, cursor: "pointer" }}
|
||||||
|
onClick={downloadFile}
|
||||||
|
>
|
||||||
|
{documentUrl.split("/").pop()?.split(".")?.[0]}
|
||||||
|
</Typography>
|
||||||
|
<IconButton onClick={handleChooseFileClick}>
|
||||||
|
<EditIcon sx={{ color: theme.palette.purple.main }} />
|
||||||
|
</IconButton>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={sendDocument}
|
||||||
|
type="file"
|
||||||
|
id="image-file"
|
||||||
|
multiple
|
||||||
|
accept={"application/pdf"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
return (
|
<Document file={documentUrl}>
|
||||||
<Box
|
<Page
|
||||||
sx={{
|
pageNumber={1}
|
||||||
display: "flex",
|
width={200}
|
||||||
flexDirection: "column",
|
renderTextLayer={false}
|
||||||
alignItems: "start",
|
renderAnnotationLayer={false}
|
||||||
gap: "10px",
|
/>
|
||||||
...sx,
|
</Document>
|
||||||
}}
|
</>
|
||||||
>
|
)}
|
||||||
<Typography
|
</Box>
|
||||||
sx={{
|
);
|
||||||
color: "#4D4D4D",
|
|
||||||
fontWeight: 500,
|
|
||||||
fontVariantNumeric: "tabular-nums",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Typography>
|
|
||||||
{documentUrl && (
|
|
||||||
<>
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{ color: theme.palette.purple.main, cursor: "pointer" }}
|
|
||||||
onClick={downloadFile}
|
|
||||||
>
|
|
||||||
{documentUrl.split("/").pop()?.split(".")?.[0]}
|
|
||||||
</Typography>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleChooseFileClick}
|
|
||||||
>
|
|
||||||
<EditIcon sx={{ color: theme.palette.purple.main }} />
|
|
||||||
</IconButton>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={sendDocument}
|
|
||||||
type="file"
|
|
||||||
id="image-file"
|
|
||||||
multiple
|
|
||||||
accept={"application/pdf"}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Document file={documentUrl}>
|
|
||||||
|
|
||||||
<Page
|
|
||||||
pageNumber={1}
|
|
||||||
width={200}
|
|
||||||
renderTextLayer={false}
|
|
||||||
renderAnnotationLayer={false}
|
|
||||||
/>
|
|
||||||
</Document>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
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,296 +29,322 @@ 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,
|
||||||
const navigate = useNavigate();
|
mainId,
|
||||||
const verificationStatus = useUserStore((state) => state.verificationStatus)
|
}: AccordionWrapperProps) {
|
||||||
const OrgName = useUserStore((state) => state.userAccount?.name.orgname)
|
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 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) {
|
||||||
|
const { patchCartError } = await addTariffToCart(tariffId);
|
||||||
|
if (patchCartError) {
|
||||||
|
enqueueSnackbar(patchCartError);
|
||||||
|
} else {
|
||||||
|
enqueueSnackbar("Тариф добавлен в корзину");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleTariffItemClick(tariffId: string) {
|
async function sendBillByEmail(logId: string) {
|
||||||
const { patchCartError } = await addTariffToCart(tariffId)
|
if (verificationStatus === VerificationStatus.VERIFICATED && OrgName) {
|
||||||
if (patchCartError) {
|
const [, sendReportError] = await sendReport(logId);
|
||||||
enqueueSnackbar(patchCartError)
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("Тариф добавлен в корзину")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendBillByEmail(logId: string) {
|
if (!sendReportError) {
|
||||||
|
return enqueueSnackbar(
|
||||||
|
"Акт будет отправлен на почту, указанную при регистрации"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(verificationStatus === VerificationStatus.VERIFICATED && OrgName){
|
enqueueSnackbar("Извините, произошла ошибка");
|
||||||
try {
|
}
|
||||||
await makeRequest({
|
navigate("/settings");
|
||||||
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport`,
|
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
|
||||||
body: {id: logId},
|
enqueueSnackbar("Пройдите верификацию и заполните название организации");
|
||||||
method: "POST",
|
} else if (!OrgName) {
|
||||||
});
|
enqueueSnackbar("Заполните поле название организации");
|
||||||
return enqueueSnackbar("Акт будет отправлен на почту, указанную при регистрации");
|
} else if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
} catch (e) {
|
enqueueSnackbar("Пройдите верификацию");
|
||||||
enqueueSnackbar("Извините, произошла ошибка");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
navigate("/settings")
|
|
||||||
if(verificationStatus !== VerificationStatus.VERIFICATED && !OrgName){
|
|
||||||
enqueueSnackbar("Пройдите верификацию и заполните название организации");
|
|
||||||
} else if(!OrgName){
|
|
||||||
enqueueSnackbar("Заполните поле название организации");
|
|
||||||
}else if(verificationStatus !== VerificationStatus.VERIFICATED) {
|
|
||||||
enqueueSnackbar("Пройдите верификацию");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomAccordion
|
<CustomAccordion
|
||||||
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
|
sx={{
|
||||||
sx={{
|
width: "100%",
|
||||||
width: "100%",
|
height: upMd ? "72px" : undefined,
|
||||||
height: upMd ? "72px" : undefined,
|
padding: "20px 20px 20px 0",
|
||||||
padding: "20px 20px 20px 0",
|
display: "flex",
|
||||||
display: "flex",
|
justifyContent: "space-between",
|
||||||
justifyContent: "space-between",
|
cursor: "pointer",
|
||||||
cursor: "pointer",
|
userSelect: "none",
|
||||||
userSelect: "none",
|
gap: "20px",
|
||||||
gap: "20px",
|
alignItems: upSm ? "center" : undefined,
|
||||||
alignItems: upSm ? "center" : undefined,
|
flexDirection: upSm ? undefined : "column",
|
||||||
flexDirection: upSm ? undefined : "column",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Box
|
||||||
<Box
|
sx={{
|
||||||
sx={{
|
display: "flex",
|
||||||
display: "flex",
|
alignItems: upSm ? "center" : undefined,
|
||||||
alignItems: upSm ? "center" : undefined,
|
justifyContent: "space-between",
|
||||||
justifyContent: "space-between",
|
flexDirection: upSm ? undefined : "column",
|
||||||
flexDirection: upSm ? undefined : "column",
|
gap: upMd ? "51px" : "10px",
|
||||||
gap: upMd ? "51px" : "10px",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Typography
|
||||||
<Typography
|
sx={{
|
||||||
sx={{
|
width: "110px",
|
||||||
width: "110px",
|
fontSize: upMd ? "20px" : "18px",
|
||||||
fontSize: upMd ? "20px" : "18px",
|
lineHeight: upMd ? undefined : "19px",
|
||||||
lineHeight: upMd ? undefined : "19px",
|
fontWeight: 500,
|
||||||
fontWeight: 500,
|
color: valuesByKey.expired
|
||||||
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.text.secondary,
|
? theme.palette.text.disabled
|
||||||
px: 0,
|
: theme.palette.text.secondary,
|
||||||
whiteSpace: "nowrap",
|
px: 0,
|
||||||
}}
|
whiteSpace: "nowrap",
|
||||||
>
|
}}
|
||||||
{createdAt}
|
>
|
||||||
</Typography>
|
{createdAt}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
title={valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name}
|
title={valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name}
|
||||||
sx={{
|
sx={{
|
||||||
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
|
||||||
px: 0,
|
? theme.palette.text.disabled
|
||||||
width: "200px",
|
: theme.palette.gray.dark,
|
||||||
maxWidth: "200px",
|
px: 0,
|
||||||
overflow: "hidden",
|
width: "200px",
|
||||||
textOverflow: "ellipsis"
|
maxWidth: "200px",
|
||||||
}}
|
overflow: "hidden",
|
||||||
>
|
textOverflow: "ellipsis",
|
||||||
{valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name}
|
}}
|
||||||
</Typography>
|
>
|
||||||
</Box>
|
{valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name}
|
||||||
<Box
|
</Typography>
|
||||||
sx={{
|
</Box>
|
||||||
display: "flex",
|
<Box
|
||||||
justifyContent: "space-between",
|
sx={{
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
flexFlow: "1",
|
justifyContent: "space-between",
|
||||||
flexBasis: "60%",
|
alignItems: "center",
|
||||||
}}
|
flexFlow: "1",
|
||||||
>
|
flexBasis: "60%",
|
||||||
<Box display="flex" width="100%" justifyContent="space-between">
|
}}
|
||||||
<Typography
|
>
|
||||||
sx={{
|
<Box display="flex" width="100%" justifyContent="space-between">
|
||||||
display: upMd ? undefined : "none",
|
<Typography
|
||||||
fontSize: upMd ? "18px" : "16px",
|
sx={{
|
||||||
lineHeight: upMd ? undefined : "19px",
|
display: upMd ? undefined : "none",
|
||||||
fontWeight: 400,
|
fontSize: upMd ? "18px" : "16px",
|
||||||
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
|
lineHeight: upMd ? undefined : "19px",
|
||||||
px: 0,
|
fontWeight: 400,
|
||||||
}}
|
color: valuesByKey.expired
|
||||||
title={`>Способ оплаты: ${valuesByKey.payMethod}</Typography>}`}
|
? theme.palette.text.disabled
|
||||||
>
|
: theme.palette.gray.dark,
|
||||||
{valuesByKey.payMethod && <Typography
|
px: 0,
|
||||||
sx={{
|
}}
|
||||||
maxWidth: "300px",
|
title={`>Способ оплаты: ${valuesByKey.payMethod}</Typography>}`}
|
||||||
width: "300px",
|
>
|
||||||
overflow: "hidden",
|
{valuesByKey.payMethod && (
|
||||||
textOverflow: "ellipsis"
|
<Typography
|
||||||
}}
|
sx={{
|
||||||
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
|
maxWidth: "300px",
|
||||||
</Typography>
|
width: "300px",
|
||||||
<Box
|
overflow: "hidden",
|
||||||
sx={{
|
textOverflow: "ellipsis",
|
||||||
display: "flex",
|
}}
|
||||||
height: "100%",
|
>
|
||||||
alignItems: "center",
|
Способ оплаты: {valuesByKey.payMethod}
|
||||||
gap: upSm ? "111px" : "17px",
|
</Typography>
|
||||||
width: "100%",
|
)}
|
||||||
maxWidth: isTablet ? null : "160px",
|
</Typography>
|
||||||
}}
|
<Box
|
||||||
>
|
sx={{
|
||||||
<Typography
|
display: "flex",
|
||||||
sx={{
|
height: "100%",
|
||||||
marginLeft: isTablet ? (isMobile ? null : "auto") : null,
|
alignItems: "center",
|
||||||
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
|
gap: upSm ? "111px" : "17px",
|
||||||
fontSize: upSm ? "20px" : "16px",
|
width: "100%",
|
||||||
fontWeight: 500,
|
maxWidth: isTablet ? null : "160px",
|
||||||
textAlign: "left",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Typography
|
||||||
{Number(content[1].Value) / 100 ? Number(content[1].Value) / 100 : "nodata"} руб.
|
sx={{
|
||||||
</Typography>
|
marginLeft: isTablet
|
||||||
</Box>
|
? isMobile
|
||||||
</Box>
|
? null
|
||||||
{!isMobile &&
|
: "auto"
|
||||||
<>
|
: null,
|
||||||
<IconButton
|
color: valuesByKey.expired
|
||||||
onClick={(e) => {
|
? theme.palette.text.disabled
|
||||||
e.stopPropagation();
|
: theme.palette.gray.dark,
|
||||||
sendBillByEmail(mainId);
|
fontSize: upSm ? "20px" : "16px",
|
||||||
}}
|
fontWeight: 500,
|
||||||
sx={{
|
textAlign: "left",
|
||||||
ml: "20px",
|
}}
|
||||||
bgcolor: "#EEE4FC",
|
>
|
||||||
color: "#7E2AEA",
|
{Number(content[1].Value) / 100
|
||||||
borderRadius: 2,
|
? Number(content[1].Value) / 100
|
||||||
"&:hover": {
|
: "nodata"}{" "}
|
||||||
bgcolor: "#7E2AEA",
|
руб.
|
||||||
color: "white",
|
</Typography>
|
||||||
},
|
</Box>
|
||||||
"&:active": {
|
</Box>
|
||||||
bgcolor: "black",
|
{!isMobile && (
|
||||||
color: "white",
|
<>
|
||||||
}
|
<IconButton
|
||||||
}}
|
onClick={(e) => {
|
||||||
>
|
e.stopPropagation();
|
||||||
<ForwardToInboxOutlinedIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
|
sendBillByEmail(mainId);
|
||||||
</IconButton>
|
}}
|
||||||
<IconButton
|
sx={{
|
||||||
title="Добавить в корзину тариф"
|
ml: "20px",
|
||||||
onClick={(e) => {
|
bgcolor: "#EEE4FC",
|
||||||
e.stopPropagation()
|
color: "#7E2AEA",
|
||||||
handleTariffItemClick(valuesByKey.id)
|
borderRadius: 2,
|
||||||
}}
|
"&:hover": {
|
||||||
sx={{
|
bgcolor: "#7E2AEA",
|
||||||
ml: "20px",
|
color: "white",
|
||||||
bgcolor:"#EEE4FC",
|
},
|
||||||
stroke: "#7E2AEA",
|
"&:active": {
|
||||||
borderRadius: 2,
|
bgcolor: "black",
|
||||||
"&:hover": {
|
color: "white",
|
||||||
bgcolor:"#7E2AEA",
|
},
|
||||||
stroke: "white",
|
}}
|
||||||
},
|
>
|
||||||
"&:active": {
|
<ForwardToInboxOutlinedIcon
|
||||||
bgcolor:"black",
|
fontSize={"medium"}
|
||||||
stroke: "white",
|
sx={{ opacity: 0.9 }}
|
||||||
}
|
/>
|
||||||
}}
|
</IconButton>
|
||||||
>
|
<IconButton
|
||||||
<File></File>
|
title="Добавить в корзину тариф"
|
||||||
</IconButton>
|
onClick={(e) => {
|
||||||
</>
|
e.stopPropagation();
|
||||||
|
handleTariffItemClick(valuesByKey.id);
|
||||||
}
|
}}
|
||||||
</Box>
|
sx={{
|
||||||
</Box>
|
ml: "20px",
|
||||||
{isMobile &&
|
bgcolor: "#EEE4FC",
|
||||||
<>
|
stroke: "#7E2AEA",
|
||||||
<IconButton
|
borderRadius: 2,
|
||||||
onClick={(e) => {
|
"&:hover": {
|
||||||
e.stopPropagation();
|
bgcolor: "#7E2AEA",
|
||||||
sendBillByEmail(mainId);
|
stroke: "white",
|
||||||
}}
|
},
|
||||||
sx={{
|
"&:active": {
|
||||||
m: "0 10px",
|
bgcolor: "black",
|
||||||
bgcolor: "#EEE4FC",
|
stroke: "white",
|
||||||
color: "#7E2AEA",
|
},
|
||||||
borderRadius: 2,
|
}}
|
||||||
"&:hover": {
|
>
|
||||||
bgcolor: "#7E2AEA",
|
<File></File>
|
||||||
color: "white",
|
</IconButton>
|
||||||
},
|
</>
|
||||||
"&:active": {
|
)}
|
||||||
bgcolor: "black",
|
</Box>
|
||||||
color: "white",
|
</Box>
|
||||||
}
|
{isMobile && (
|
||||||
}}
|
<>
|
||||||
>
|
<IconButton
|
||||||
<ForwardToInboxOutlinedIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
|
onClick={(e) => {
|
||||||
</IconButton>
|
e.stopPropagation();
|
||||||
<IconButton
|
sendBillByEmail(mainId);
|
||||||
title="Добавить в корзину тариф"
|
}}
|
||||||
onClick={(e) => {
|
sx={{
|
||||||
e.stopPropagation()
|
m: "0 10px",
|
||||||
handleTariffItemClick(valuesByKey.id)
|
bgcolor: "#EEE4FC",
|
||||||
}}
|
color: "#7E2AEA",
|
||||||
sx={{
|
borderRadius: 2,
|
||||||
mr: "10px",
|
"&:hover": {
|
||||||
bgcolor:"#EEE4FC",
|
bgcolor: "#7E2AEA",
|
||||||
stroke: "#7E2AEA",
|
color: "white",
|
||||||
borderRadius: 2,
|
},
|
||||||
"&:hover": {
|
"&:active": {
|
||||||
bgcolor:"#7E2AEA",
|
bgcolor: "black",
|
||||||
stroke: "white",
|
color: "white",
|
||||||
},
|
},
|
||||||
"&:active": {
|
}}
|
||||||
bgcolor:"black",
|
>
|
||||||
stroke: "white",
|
<ForwardToInboxOutlinedIcon
|
||||||
}
|
fontSize={"medium"}
|
||||||
}}
|
sx={{ opacity: 0.9 }}
|
||||||
>
|
/>
|
||||||
<File></File>
|
</IconButton>
|
||||||
</IconButton>
|
<IconButton
|
||||||
</>
|
title="Добавить в корзину тариф"
|
||||||
|
onClick={(e) => {
|
||||||
}
|
e.stopPropagation();
|
||||||
</>
|
handleTariffItemClick(valuesByKey.id);
|
||||||
}
|
}}
|
||||||
/>
|
sx={{
|
||||||
</Box>
|
mr: "10px",
|
||||||
)
|
bgcolor: "#EEE4FC",
|
||||||
|
stroke: "#7E2AEA",
|
||||||
|
borderRadius: 2,
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "#7E2AEA",
|
||||||
|
stroke: "white",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
bgcolor: "black",
|
||||||
|
stroke: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<File></File>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,160 +1,175 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
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;
|
||||||
date: string;
|
date: string;
|
||||||
info: string;
|
info: string;
|
||||||
description: string;
|
description: string;
|
||||||
payMethod?: string;
|
payMethod?: string;
|
||||||
expired?: boolean;
|
expired?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AccordionWrapperProps {
|
interface AccordionWrapperProps {
|
||||||
tariff: Tariff;
|
tariff: Tariff;
|
||||||
price: number;
|
price: number;
|
||||||
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({
|
||||||
const theme = useTheme();
|
tariff,
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
price,
|
||||||
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
last,
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
first,
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(560));
|
createdAt,
|
||||||
const navigate = useNavigate();
|
mainId,
|
||||||
const verificationStatus = useUserStore((state) => state.verificationStatus)
|
}: AccordionWrapperProps) {
|
||||||
const OrgName = useUserStore((state) => state.userAccount?.name.orgname)
|
const theme = useTheme();
|
||||||
async function handleTariffItemClick(tariffId: string) {
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const { patchCartError } = await addTariffToCart(tariffId);
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
if (patchCartError) {
|
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||||
enqueueSnackbar(patchCartError);
|
const isMobile = useMediaQuery(theme.breakpoints.down(560));
|
||||||
} else {
|
const navigate = useNavigate();
|
||||||
enqueueSnackbar("Тариф добавлен в корзину");
|
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) {
|
||||||
|
enqueueSnackbar(patchCartError);
|
||||||
|
} else {
|
||||||
|
enqueueSnackbar("Тариф добавлен в корзину");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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")
|
|
||||||
if(verificationStatus !== VerificationStatus.VERIFICATED && !OrgName){
|
|
||||||
enqueueSnackbar("Пройдите верификацию и заполните название организации");
|
|
||||||
} else if(!OrgName){
|
|
||||||
enqueueSnackbar("Заполните поле название организации");
|
|
||||||
}else if(verificationStatus !== VerificationStatus.VERIFICATED) {
|
|
||||||
enqueueSnackbar("Пройдите верификацию");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
navigate("/settings");
|
||||||
|
if (verificationStatus !== VerificationStatus.VERIFICATED && !OrgName) {
|
||||||
|
enqueueSnackbar("Пройдите верификацию и заполните название организации");
|
||||||
|
} else if (!OrgName) {
|
||||||
|
enqueueSnackbar("Заполните поле название организации");
|
||||||
|
} else if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
|
enqueueSnackbar("Пройдите верификацию");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomAccordion
|
<CustomAccordion
|
||||||
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(
|
||||||
header={
|
Number(privilege.serviceKey),
|
||||||
<>
|
privilege.value
|
||||||
<Box
|
)} `
|
||||||
sx={{
|
)}
|
||||||
width: "100%",
|
header={
|
||||||
height: upMd ? "72px" : undefined,
|
<>
|
||||||
padding: "20px 20px 20px 0",
|
<Box
|
||||||
display: "flex",
|
sx={{
|
||||||
justifyContent: "space-between",
|
width: "100%",
|
||||||
cursor: "pointer",
|
height: upMd ? "72px" : undefined,
|
||||||
userSelect: "none",
|
padding: "20px 20px 20px 0",
|
||||||
gap: "20px",
|
display: "flex",
|
||||||
alignItems: upSm ? "center" : undefined,
|
justifyContent: "space-between",
|
||||||
flexDirection: upSm ? undefined : "column",
|
cursor: "pointer",
|
||||||
}}
|
userSelect: "none",
|
||||||
>
|
gap: "20px",
|
||||||
<Box
|
alignItems: upSm ? "center" : undefined,
|
||||||
sx={{
|
flexDirection: upSm ? undefined : "column",
|
||||||
display: "flex",
|
}}
|
||||||
alignItems: upSm ? "center" : undefined,
|
>
|
||||||
justifyContent: "space-between",
|
<Box
|
||||||
flexDirection: upSm ? undefined : "column",
|
sx={{
|
||||||
gap: upMd ? "51px" : "10px",
|
display: "flex",
|
||||||
}}
|
alignItems: upSm ? "center" : undefined,
|
||||||
>
|
justifyContent: "space-between",
|
||||||
<Typography
|
flexDirection: upSm ? undefined : "column",
|
||||||
sx={{
|
gap: upMd ? "51px" : "10px",
|
||||||
width: "110px",
|
}}
|
||||||
fontSize: upMd ? "20px" : "18px",
|
>
|
||||||
lineHeight: upMd ? undefined : "19px",
|
<Typography
|
||||||
fontWeight: 500,
|
sx={{
|
||||||
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.text.secondary,
|
width: "110px",
|
||||||
px: 0,
|
fontSize: upMd ? "20px" : "18px",
|
||||||
whiteSpace: "nowrap",
|
lineHeight: upMd ? undefined : "19px",
|
||||||
}}
|
fontWeight: 500,
|
||||||
>
|
color: /* valuesByKey.expired */ false
|
||||||
{createdAt}
|
? theme.palette.text.disabled
|
||||||
</Typography>
|
: theme.palette.text.secondary,
|
||||||
|
px: 0,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createdAt}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
title={tariff.isCustom ? "Мой тариф" : tariff.name}
|
title={tariff.isCustom ? "Мой тариф" : tariff.name}
|
||||||
sx={{
|
sx={{
|
||||||
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
|
||||||
px: 0,
|
? theme.palette.text.disabled
|
||||||
width: upMd? "200px" : "auto",
|
: theme.palette.gray.dark,
|
||||||
maxWidth: "200px",
|
px: 0,
|
||||||
overflow: "hidden",
|
width: upMd ? "200px" : "auto",
|
||||||
textOverflow: "ellipsis",
|
maxWidth: "200px",
|
||||||
}}
|
overflow: "hidden",
|
||||||
>
|
textOverflow: "ellipsis",
|
||||||
{tariff.isCustom ? "Мой тариф" : tariff.name}
|
}}
|
||||||
</Typography>
|
>
|
||||||
</Box>
|
{tariff.isCustom ? "Мой тариф" : tariff.name}
|
||||||
<Box
|
</Typography>
|
||||||
sx={{
|
</Box>
|
||||||
display: "flex",
|
<Box
|
||||||
justifyContent: "space-between",
|
sx={{
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
flexFlow: "1",
|
justifyContent: "space-between",
|
||||||
flexBasis: "60%",
|
alignItems: "center",
|
||||||
}}
|
flexFlow: "1",
|
||||||
>
|
flexBasis: "60%",
|
||||||
<Box display="flex" width="100%" justifyContent="space-between">
|
}}
|
||||||
{/* <Typography
|
>
|
||||||
|
<Box display="flex" width="100%" justifyContent="space-between">
|
||||||
|
{/* <Typography
|
||||||
sx={{
|
sx={{
|
||||||
display: upMd ? undefined : "none",
|
display: upMd ? undefined : "none",
|
||||||
fontSize: upMd ? "18px" : "16px",
|
fontSize: upMd ? "18px" : "16px",
|
||||||
@ -173,134 +188,144 @@ export default function AccordionWrapper2({ tariff, price, last, first, createdA
|
|||||||
}}
|
}}
|
||||||
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
|
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
|
||||||
</Typography> */}
|
</Typography> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: upSm ? "111px" : "17px",
|
gap: upSm ? "111px" : "17px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: isTablet ? null : "160px",
|
maxWidth: isTablet ? null : "160px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<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
|
||||||
fontSize: upSm ? "20px" : "16px",
|
? null
|
||||||
fontWeight: 500,
|
: "auto"
|
||||||
textAlign: "left",
|
: null,
|
||||||
}}
|
color: /* valuesByKey.expired */ false
|
||||||
>
|
? theme.palette.text.disabled
|
||||||
{currencyFormatter.format(price)}
|
: theme.palette.gray.dark,
|
||||||
</Typography>
|
fontSize: upSm ? "20px" : "16px",
|
||||||
</Box>
|
fontWeight: 500,
|
||||||
</Box>
|
textAlign: "left",
|
||||||
{!isMobile &&
|
}}
|
||||||
<>
|
>
|
||||||
<IconButton
|
{currencyFormatter.format(price)}
|
||||||
onClick={(e) => {
|
</Typography>
|
||||||
e.stopPropagation();
|
</Box>
|
||||||
sendBillByEmail(mainId);
|
</Box>
|
||||||
}}
|
{!isMobile && (
|
||||||
sx={{
|
<>
|
||||||
ml: "20px",
|
<IconButton
|
||||||
bgcolor: "#EEE4FC",
|
onClick={(e) => {
|
||||||
color: "#7E2AEA",
|
e.stopPropagation();
|
||||||
borderRadius: 2,
|
sendBillByEmail(mainId);
|
||||||
"&:hover": {
|
}}
|
||||||
bgcolor: "#7E2AEA",
|
sx={{
|
||||||
color: "white",
|
ml: "20px",
|
||||||
},
|
bgcolor: "#EEE4FC",
|
||||||
"&:active": {
|
color: "#7E2AEA",
|
||||||
bgcolor: "black",
|
borderRadius: 2,
|
||||||
color: "white",
|
"&:hover": {
|
||||||
}
|
bgcolor: "#7E2AEA",
|
||||||
}}
|
color: "white",
|
||||||
>
|
},
|
||||||
<ForwardToInboxIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
|
"&:active": {
|
||||||
</IconButton>
|
bgcolor: "black",
|
||||||
<IconButton
|
color: "white",
|
||||||
title="Добавить в корзину тариф"
|
},
|
||||||
onClick={(e) => {
|
}}
|
||||||
e.stopPropagation();
|
>
|
||||||
handleTariffItemClick(tariff._id);
|
<ForwardToInboxIcon
|
||||||
}}
|
fontSize={"medium"}
|
||||||
sx={{
|
sx={{ opacity: 0.9 }}
|
||||||
ml: "20px",
|
/>
|
||||||
bgcolor: "#EEE4FC",
|
</IconButton>
|
||||||
stroke: "#7E2AEA",
|
<IconButton
|
||||||
borderRadius: 2,
|
title="Добавить в корзину тариф"
|
||||||
"&:hover": {
|
onClick={(e) => {
|
||||||
bgcolor: "#7E2AEA",
|
e.stopPropagation();
|
||||||
stroke: "white",
|
handleTariffItemClick(tariff._id);
|
||||||
},
|
}}
|
||||||
"&:active": {
|
sx={{
|
||||||
bgcolor: "black",
|
ml: "20px",
|
||||||
stroke: "white",
|
bgcolor: "#EEE4FC",
|
||||||
}
|
stroke: "#7E2AEA",
|
||||||
}}
|
borderRadius: 2,
|
||||||
>
|
"&:hover": {
|
||||||
<File></File>
|
bgcolor: "#7E2AEA",
|
||||||
</IconButton>
|
stroke: "white",
|
||||||
</>
|
},
|
||||||
|
"&:active": {
|
||||||
}
|
bgcolor: "black",
|
||||||
</Box>
|
stroke: "white",
|
||||||
</Box>
|
},
|
||||||
{isMobile &&
|
}}
|
||||||
<>
|
>
|
||||||
<IconButton
|
<File></File>
|
||||||
onClick={(e) => {
|
</IconButton>
|
||||||
e.stopPropagation();
|
</>
|
||||||
sendBillByEmail(mainId);
|
)}
|
||||||
}}
|
</Box>
|
||||||
sx={{
|
</Box>
|
||||||
m: "0 10px",
|
{isMobile && (
|
||||||
bgcolor: "#EEE4FC",
|
<>
|
||||||
color: "#7E2AEA",
|
<IconButton
|
||||||
borderRadius: 2,
|
onClick={(e) => {
|
||||||
"&:hover": {
|
e.stopPropagation();
|
||||||
bgcolor: "#7E2AEA",
|
sendBillByEmail(mainId);
|
||||||
color: "white",
|
}}
|
||||||
},
|
sx={{
|
||||||
"&:active": {
|
m: "0 10px",
|
||||||
bgcolor: "black",
|
bgcolor: "#EEE4FC",
|
||||||
color: "white",
|
color: "#7E2AEA",
|
||||||
}
|
borderRadius: 2,
|
||||||
}}
|
"&:hover": {
|
||||||
>
|
bgcolor: "#7E2AEA",
|
||||||
<ForwardToInboxIcon fontSize={"medium"} sx={{ opacity: 0.9 }}/>
|
color: "white",
|
||||||
</IconButton>
|
},
|
||||||
<IconButton
|
"&:active": {
|
||||||
title="Добавить в корзину тариф"
|
bgcolor: "black",
|
||||||
onClick={(e) => {
|
color: "white",
|
||||||
e.stopPropagation();
|
},
|
||||||
handleTariffItemClick(tariff._id);
|
}}
|
||||||
}}
|
>
|
||||||
sx={{
|
<ForwardToInboxIcon
|
||||||
mr: "10px",
|
fontSize={"medium"}
|
||||||
bgcolor: "#EEE4FC",
|
sx={{ opacity: 0.9 }}
|
||||||
stroke: "#7E2AEA",
|
/>
|
||||||
borderRadius: 2,
|
</IconButton>
|
||||||
"&:hover": {
|
<IconButton
|
||||||
bgcolor: "#7E2AEA",
|
title="Добавить в корзину тариф"
|
||||||
stroke: "white",
|
onClick={(e) => {
|
||||||
},
|
e.stopPropagation();
|
||||||
"&:active": {
|
handleTariffItemClick(tariff._id);
|
||||||
bgcolor: "black",
|
}}
|
||||||
stroke: "white",
|
sx={{
|
||||||
}
|
mr: "10px",
|
||||||
}}
|
bgcolor: "#EEE4FC",
|
||||||
>
|
stroke: "#7E2AEA",
|
||||||
<File></File>
|
borderRadius: 2,
|
||||||
</IconButton>
|
"&:hover": {
|
||||||
</>
|
bgcolor: "#7E2AEA",
|
||||||
|
stroke: "white",
|
||||||
}
|
},
|
||||||
</>
|
"&:active": {
|
||||||
}
|
bgcolor: "black",
|
||||||
/>
|
stroke: "white",
|
||||||
</Box>
|
},
|
||||||
);
|
}}
|
||||||
|
>
|
||||||
|
<File></File>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,154 +1,169 @@
|
|||||||
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 = ["Платежи"];
|
||||||
// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
||||||
|
|
||||||
export default function History() {
|
export default function History() {
|
||||||
const [selectedItem, setSelectedItem] = useState<number>(0);
|
const [selectedItem, setSelectedItem] = useState<number>(0);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.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) => {
|
||||||
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
|
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
|
||||||
return dateMatch ? dateMatch[0] : "";
|
return dateMatch ? dateMatch[0] : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
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("Запрос отправлен");
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("извините, произошла ошибка");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<SectionWrapper
|
enqueueSnackbar("Запрос отправлен");
|
||||||
maxWidth="lg"
|
}
|
||||||
sx={{
|
|
||||||
mt: upMd ? "25px" : "20px",
|
return (
|
||||||
mb: upMd ? "70px" : "37px",
|
<SectionWrapper
|
||||||
px: isTablet ? (isTablet ? "18px" : "40px") : "20px",
|
maxWidth="lg"
|
||||||
}}
|
sx={{
|
||||||
|
mt: upMd ? "25px" : "20px",
|
||||||
|
mb: upMd ? "70px" : "37px",
|
||||||
|
px: isTablet ? (isTablet ? "18px" : "40px") : "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: "20px",
|
||||||
|
mb: isTablet ? "38px" : "20px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isMobile && (
|
||||||
|
<IconButton
|
||||||
|
onClick={handleCustomBackNavigation}
|
||||||
|
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "24px" : "36px",
|
||||||
|
fontWeight: "500",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
История
|
||||||
sx={{
|
</Typography>
|
||||||
mt: "20px",
|
</Box>
|
||||||
mb: isTablet ? "38px" : "20px",
|
{isMobile ? (
|
||||||
display: "flex",
|
<Select
|
||||||
alignItems: "center",
|
items={subPages}
|
||||||
gap: "10px",
|
selectedItem={selectedItem}
|
||||||
}}
|
setSelectedItem={setSelectedItem}
|
||||||
>
|
/>
|
||||||
{isMobile && (
|
) : (
|
||||||
<IconButton onClick={handleCustomBackNavigation} sx={{ p: 0, height: "28px", width: "28px", color: "black" }}>
|
<Tabs
|
||||||
<ArrowBackIcon />
|
items={subPages}
|
||||||
</IconButton>
|
selectedItem={selectedItem}
|
||||||
)}
|
setSelectedItem={setSelectedItem}
|
||||||
<Typography
|
/>
|
||||||
sx={{
|
)}
|
||||||
fontSize: isMobile ? "24px" : "36px",
|
<ErrorBoundary
|
||||||
fontWeight: "500",
|
fallback={<Typography mt="8px">Ошибка загрузки истории</Typography>}
|
||||||
}}
|
onError={handleComponentError}
|
||||||
>
|
>
|
||||||
История
|
{historyData?.length === 0 && (
|
||||||
</Typography>
|
<Typography textAlign="center">Нет данных</Typography>
|
||||||
</Box>
|
)}
|
||||||
{isMobile ? (
|
{/* Для ненормального rawDetails */}
|
||||||
<Select items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
{historyData
|
||||||
) : (
|
?.filter((e): e is HistoryRecord => {
|
||||||
<Tabs items={subPages} selectedItem={selectedItem} setSelectedItem={setSelectedItem} />
|
e.createdAt = extractDateFromString(e.createdAt);
|
||||||
)}
|
return (
|
||||||
<ErrorBoundary
|
!e.isDeleted &&
|
||||||
fallback={
|
e.key === "payCart" &&
|
||||||
<Typography mt="8px">Ошибка загрузки истории</Typography>
|
Array.isArray(e.rawDetails) &&
|
||||||
}
|
Array.isArray(e.rawDetails[0].Value)
|
||||||
onError={handleComponentError}
|
);
|
||||||
>
|
})
|
||||||
{historyData?.length === 0 && <Typography textAlign="center">Нет данных</Typography>}
|
.map((e, index) => {
|
||||||
{/* Для ненормального rawDetails */}
|
return (
|
||||||
{historyData?.filter((e): e is HistoryRecord => {
|
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
||||||
e.createdAt = extractDateFromString(e.createdAt);
|
<AccordionWrapper
|
||||||
return (
|
first={index === 0}
|
||||||
!e.isDeleted
|
last={index === historyData?.length - 1}
|
||||||
&& e.key === "payCart"
|
content={(e as HistoryRecord).rawDetails}
|
||||||
&& Array.isArray(e.rawDetails)
|
mainId={(e as HistoryRecord).id}
|
||||||
&& Array.isArray(e.rawDetails[0].Value)
|
key={e.id}
|
||||||
);
|
createdAt={e.createdAt}
|
||||||
}).map((e, index) => {
|
onClickMail={(event: any) => {
|
||||||
return (
|
event.stopPropagation();
|
||||||
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
handleHistoryResponse(e.id);
|
||||||
<AccordionWrapper
|
}}
|
||||||
first={index === 0}
|
/>
|
||||||
last={index === historyData?.length - 1}
|
</Box>
|
||||||
content={(e as HistoryRecord).rawDetails}
|
);
|
||||||
mainId={(e as HistoryRecord).id}
|
})}
|
||||||
key={e.id}
|
{/* Для нормального rawDetails */}
|
||||||
createdAt={e.createdAt}
|
{historyData
|
||||||
onClickMail={(event: any) => {
|
?.filter((e): e is HistoryRecord2 => {
|
||||||
event.stopPropagation();
|
e.createdAt = extractDateFromString(e.createdAt);
|
||||||
handleHistoryResponse(e.id);
|
return (
|
||||||
}}
|
!e.isDeleted &&
|
||||||
/>
|
e.key === "payCart" &&
|
||||||
</Box>
|
!Array.isArray(e.rawDetails) &&
|
||||||
);
|
!!e.rawDetails.tariffs[0]
|
||||||
})}
|
);
|
||||||
{/* Для нормального rawDetails */}
|
})
|
||||||
{historyData?.filter((e): e is HistoryRecord2 => {
|
.map((e, index) => {
|
||||||
e.createdAt = extractDateFromString(e.createdAt);
|
return (
|
||||||
return (
|
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
||||||
!e.isDeleted
|
<AccordionWrapper2
|
||||||
&& e.key === "payCart"
|
key={e.id}
|
||||||
&& !Array.isArray(e.rawDetails)
|
first={index === 0}
|
||||||
&& !!e.rawDetails.tariffs[0]
|
last={index === historyData?.length - 1}
|
||||||
);
|
mainId={(e as HistoryRecord2).id}
|
||||||
}).map((e, index) => {
|
createdAt={e.createdAt}
|
||||||
return (
|
tariff={e.rawDetails.tariffs[0]}
|
||||||
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
price={e.rawDetails.price / 100}
|
||||||
<AccordionWrapper2
|
/>
|
||||||
key={e.id}
|
</Box>
|
||||||
first={index === 0}
|
);
|
||||||
last={index === historyData?.length - 1}
|
})}
|
||||||
mainId={(e as HistoryRecord2).id}
|
</ErrorBoundary>
|
||||||
createdAt={e.createdAt}
|
</SectionWrapper>
|
||||||
tariff={e.rawDetails.tariffs[0]}
|
);
|
||||||
price={e.rawDetails.price/100}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ErrorBoundary>
|
|
||||||
</SectionWrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -139,10 +139,12 @@ export default function Payment() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
|
const [sendRSPaymentResponse] = await sendRSPayment(
|
||||||
|
Number(paymentValueField)
|
||||||
|
);
|
||||||
|
|
||||||
if (sendRSPaymentError) {
|
if (sendRSPaymentResponse) {
|
||||||
return enqueueSnackbar(sendRSPaymentError);
|
return enqueueSnackbar(sendRSPaymentResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueSnackbar(
|
enqueueSnackbar(
|
||||||
@ -153,18 +155,17 @@ export default function Payment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleApplyPromocode() {
|
async function handleApplyPromocode() {
|
||||||
if (!promocodeField) return;
|
if (!promocodeField) return;
|
||||||
|
|
||||||
activatePromocode(promocodeField)
|
const [greeting, activateError] = await activatePromocode(promocodeField);
|
||||||
.then((response) => {
|
|
||||||
enqueueSnackbar(response);
|
if (activateError) {
|
||||||
mutate("discounts");
|
return enqueueSnackbar(activateError);
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
|
||||||
if (error.message !== "" && typeof error.message === "string")
|
enqueueSnackbar(greeting);
|
||||||
enqueueSnackbar(error.message);
|
mutate("discounts");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -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");
|
||||||
|
@ -12,7 +12,7 @@ export default function ChatImageNewWindow() {
|
|||||||
maxHeight: "100vh",
|
maxHeight: "100vh",
|
||||||
maxWidth: "100vw",
|
maxWidth: "100vw",
|
||||||
}}
|
}}
|
||||||
src={`https://storage.yandexcloud.net/pair/${srcImage}`}
|
src={`https://3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b.s3.timeweb.cloud/angesight/pair/${srcImage}`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,132 +1,138 @@
|
|||||||
import { Box, Typography, FormControl, InputBase, useMediaQuery, useTheme, Button } from "@mui/material"
|
import {
|
||||||
import { useState } from "react"
|
Box,
|
||||||
import { useNavigate } from "react-router-dom"
|
Typography,
|
||||||
import { enqueueSnackbar } from "notistack"
|
FormControl,
|
||||||
import { cardShadow } from "@root/utils/theme"
|
InputBase,
|
||||||
import { createTicket } from "@frontend/kitui"
|
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() {
|
export default function CreateTicket() {
|
||||||
const theme = useTheme()
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"))
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const [ticketNameField, setTicketNameField] = useState<string>("")
|
const [ticketNameField, setTicketNameField] = useState<string>("");
|
||||||
const [ticketBodyField, setTicketBodyField] = useState<string>("")
|
const [ticketBodyField, setTicketBodyField] = useState<string>("");
|
||||||
|
|
||||||
async function handleCreateTicket() {
|
async function handleCreateTicket() {
|
||||||
if (!ticketBodyField || !ticketNameField) return
|
if (!ticketBodyField || !ticketNameField) return;
|
||||||
|
|
||||||
createTicket({
|
const [createTicketresult, createTicketerror] = await createTicket(
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
ticketNameField,
|
||||||
body: {
|
ticketBodyField
|
||||||
Title: ticketNameField,
|
);
|
||||||
Message: ticketBodyField,
|
|
||||||
}
|
|
||||||
}).then(result => {
|
|
||||||
navigate(`/support/${result.Ticket}`)
|
|
||||||
}).catch(error => {
|
|
||||||
enqueueSnackbar(error.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
if (createTicketerror) {
|
||||||
<Box
|
return enqueueSnackbar(createTicketerror);
|
||||||
sx={{
|
}
|
||||||
backgroundColor: upMd ? "white" : undefined,
|
|
||||||
display: "flex",
|
navigate(`/support/${createTicketresult?.Ticket}`);
|
||||||
flexDirection: upMd ? "row" : "column",
|
}
|
||||||
maxHeight: upMd ? "443px" : undefined,
|
|
||||||
borderRadius: "12px",
|
return (
|
||||||
p: upMd ? "20px" : undefined,
|
<Box
|
||||||
gap: upMd ? "40px" : "20px",
|
sx={{
|
||||||
boxShadow: upMd
|
backgroundColor: upMd ? "white" : undefined,
|
||||||
? cardShadow
|
display: "flex",
|
||||||
: undefined,
|
flexDirection: upMd ? "row" : "column",
|
||||||
}}
|
maxHeight: upMd ? "443px" : undefined,
|
||||||
>
|
borderRadius: "12px",
|
||||||
<Box
|
p: upMd ? "20px" : undefined,
|
||||||
sx={{
|
gap: upMd ? "40px" : "20px",
|
||||||
display: "flex",
|
boxShadow: upMd ? cardShadow : undefined,
|
||||||
alignItems: "start",
|
}}
|
||||||
flexDirection: "column",
|
>
|
||||||
flexGrow: 1,
|
<Box
|
||||||
}}
|
sx={{
|
||||||
>
|
display: "flex",
|
||||||
<Typography variant={upMd ? "h5" : "body2"} mb={upMd ? "30px" : "20px"}>
|
alignItems: "start",
|
||||||
Написать обращение
|
flexDirection: "column",
|
||||||
</Typography>
|
flexGrow: 1,
|
||||||
<FormControl sx={{ width: "100%" }}>
|
}}
|
||||||
<InputBase
|
>
|
||||||
value={ticketNameField}
|
<Typography variant={upMd ? "h5" : "body2"} mb={upMd ? "30px" : "20px"}>
|
||||||
fullWidth
|
Написать обращение
|
||||||
placeholder="Заголовок обращения"
|
</Typography>
|
||||||
id="ticket-header"
|
<FormControl sx={{ width: "100%" }}>
|
||||||
sx={{
|
<InputBase
|
||||||
backgroundColor: theme.palette.background.default,
|
value={ticketNameField}
|
||||||
border: `1px solid ${theme.palette.gray.main}`,
|
fullWidth
|
||||||
borderRadius: "10px",
|
placeholder="Заголовок обращения"
|
||||||
p: 0,
|
id="ticket-header"
|
||||||
}}
|
sx={{
|
||||||
inputProps={{
|
backgroundColor: theme.palette.background.default,
|
||||||
sx: {
|
border: `1px solid ${theme.palette.gray.main}`,
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
fontWeight: 400,
|
p: 0,
|
||||||
fontSize: "16px",
|
}}
|
||||||
lineHeight: "19px",
|
inputProps={{
|
||||||
pt: "10px",
|
sx: {
|
||||||
pb: "10px",
|
borderRadius: "10px",
|
||||||
px: "19px",
|
fontWeight: 400,
|
||||||
},
|
fontSize: "16px",
|
||||||
}}
|
lineHeight: "19px",
|
||||||
onChange={(e) => setTicketNameField(e.target.value)}
|
pt: "10px",
|
||||||
/>
|
pb: "10px",
|
||||||
</FormControl>
|
px: "19px",
|
||||||
<FormControl sx={{ width: "100%" }}>
|
},
|
||||||
<Box
|
}}
|
||||||
sx={{
|
onChange={(e) => setTicketNameField(e.target.value)}
|
||||||
overflow: "hidden",
|
/>
|
||||||
mt: "16px",
|
</FormControl>
|
||||||
backgroundColor: theme.palette.background.default,
|
<FormControl sx={{ width: "100%" }}>
|
||||||
border: `1px solid ${theme.palette.gray.main}`,
|
<Box
|
||||||
borderRadius: "10px",
|
sx={{
|
||||||
}}
|
overflow: "hidden",
|
||||||
>
|
mt: "16px",
|
||||||
<InputBase
|
backgroundColor: theme.palette.background.default,
|
||||||
value={ticketBodyField}
|
border: `1px solid ${theme.palette.gray.main}`,
|
||||||
fullWidth
|
borderRadius: "10px",
|
||||||
placeholder="Текст обращения"
|
}}
|
||||||
id="ticket-body"
|
>
|
||||||
multiline
|
<InputBase
|
||||||
sx={{
|
value={ticketBodyField}
|
||||||
p: 0,
|
fullWidth
|
||||||
height: "284px",
|
placeholder="Текст обращения"
|
||||||
alignItems: "start",
|
id="ticket-body"
|
||||||
overflow: "auto",
|
multiline
|
||||||
}}
|
sx={{
|
||||||
inputProps={{
|
p: 0,
|
||||||
sx: {
|
height: "284px",
|
||||||
borderRadius: "10px",
|
alignItems: "start",
|
||||||
fontWeight: 400,
|
overflow: "auto",
|
||||||
fontSize: "16px",
|
}}
|
||||||
lineHeight: "19px",
|
inputProps={{
|
||||||
pt: "13px",
|
sx: {
|
||||||
pb: "13px",
|
borderRadius: "10px",
|
||||||
px: "19px",
|
fontWeight: 400,
|
||||||
height: "300px",
|
fontSize: "16px",
|
||||||
},
|
lineHeight: "19px",
|
||||||
}}
|
pt: "13px",
|
||||||
onChange={(e) => setTicketBodyField(e.target.value)}
|
pb: "13px",
|
||||||
/>
|
px: "19px",
|
||||||
</Box>
|
height: "300px",
|
||||||
</FormControl>
|
},
|
||||||
</Box>
|
}}
|
||||||
<Box sx={{ alignSelf: upMd ? "end" : "start" }}>
|
onChange={(e) => setTicketBodyField(e.target.value)}
|
||||||
<Button
|
/>
|
||||||
variant="pena-contained-dark"
|
</Box>
|
||||||
onClick={handleCreateTicket}
|
</FormControl>
|
||||||
disabled={!ticketBodyField || !ticketNameField}
|
</Box>
|
||||||
>Отправить</Button>
|
<Box sx={{ alignSelf: upMd ? "end" : "start" }}>
|
||||||
</Box>
|
<Button
|
||||||
</Box>
|
variant="pena-contained-dark"
|
||||||
)
|
onClick={handleCreateTicket}
|
||||||
|
disabled={!ticketBodyField || !ticketNameField}
|
||||||
|
>
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
import SectionWrapper from "@components/SectionWrapper";
|
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 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 NumberIcon from "@root/components/NumberIcon";
|
||||||
import { useTariffStore } from "@root/stores/tariffs";
|
import { useTariffStore } from "@root/stores/tariffs";
|
||||||
import { addTariffToCart, useUserStore } from "@root/stores/user";
|
import { addTariffToCart, useUserStore } from "@root/stores/user";
|
||||||
@ -22,198 +32,230 @@ import { Tabs } from "@components/Tabs";
|
|||||||
const subPages = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"'];
|
const subPages = ["Базовый тариф PenaQuiz", 'Убрать логотип "PenaQuiz"'];
|
||||||
|
|
||||||
const StepperText: Record<string, string> = {
|
const StepperText: Record<string, string> = {
|
||||||
volume: "Тарифы на объём",
|
volume: "Тарифы на объём",
|
||||||
time: "Тарифы на время",
|
time: "Тарифы на время",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function TariffPage() {
|
function TariffPage() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const tariffs = useTariffStore((state) => state.tariffs);
|
const tariffs = useTariffStore((state) => state.tariffs);
|
||||||
const [selectedItem, setSelectedItem] = useState<number>(0);
|
const [selectedItem, setSelectedItem] = useState<number>(0);
|
||||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
const purchasesAmount =
|
||||||
const userId = useUserStore(state => state.user?._id) ?? "";
|
useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
||||||
const discounts = useDiscounts(userId);
|
const userId = useUserStore((state) => state.user?._id) ?? "";
|
||||||
const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko";
|
const discounts = useDiscounts(userId);
|
||||||
const currentTariffs = useCartTariffs();
|
const isUserNko =
|
||||||
|
useUserStore((state) => state.userAccount?.status) === "nko";
|
||||||
|
const currentTariffs = useCartTariffs();
|
||||||
|
|
||||||
const handleCustomBackNavigation = usePrevLocation(location);
|
const handleCustomBackNavigation = usePrevLocation(location);
|
||||||
|
|
||||||
const unit: string = String(location.pathname).slice(9);
|
const unit: string = String(location.pathname).slice(9);
|
||||||
|
|
||||||
function handleTariffItemClick(tariffId: string) {
|
function handleTariffItemClick(tariffId: string) {
|
||||||
addTariffToCart(tariffId)
|
addTariffToCart(tariffId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
enqueueSnackbar("Тариф добавлен в корзину");
|
enqueueSnackbar("Тариф добавлен в корзину");
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const message = getMessageFromFetchError(error);
|
const message = getMessageFromFetchError(error);
|
||||||
if (message) enqueueSnackbar(message);
|
if (message) enqueueSnackbar(message);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredTariffs = tariffs.filter((tariff) => {
|
||||||
|
if (tariff.privileges[0] === undefined) return false;
|
||||||
|
if (
|
||||||
|
(tariff.privileges[0].type === "day") === (unit === "time") &&
|
||||||
|
!tariff.isDeleted &&
|
||||||
|
!tariff.isCustom
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
((selectedItem === 0 && unit === "time") || unit !== "time") &&
|
||||||
|
tariff.privileges[0].serviceKey === "squiz" &&
|
||||||
|
tariff.privileges[0].privilegeId !== "squizHideBadge"
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selectedItem === 1 &&
|
||||||
|
unit === "time" &&
|
||||||
|
tariff.privileges[0].privilegeId === "squizHideBadge"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const createTariffElements = (
|
||||||
|
filteredTariffs: Tariff[],
|
||||||
|
addFreeTariff = false
|
||||||
|
) => {
|
||||||
|
const tariffElements = filteredTariffs
|
||||||
|
.filter((tariff) => tariff.privileges.length > 0)
|
||||||
|
.map((tariff, index) => {
|
||||||
|
const { priceBeforeDiscounts, priceAfterDiscounts } = calcTariffPrice(
|
||||||
|
tariff,
|
||||||
|
discounts ?? [],
|
||||||
|
purchasesAmount,
|
||||||
|
currentTariffs ?? [],
|
||||||
|
isUserNko,
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TariffCard
|
||||||
|
key={tariff._id}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
backgroundColor={unit === "time" ? "#EEE4FC" : "#FEDFD0"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
buttonProps={{
|
||||||
|
text: "Выбрать",
|
||||||
|
onClick: () => handleTariffItemClick(tariff._id),
|
||||||
|
}}
|
||||||
|
headerText={tariff.name}
|
||||||
|
text={tariff.description || ""}
|
||||||
|
price={
|
||||||
|
<>
|
||||||
|
{priceBeforeDiscounts !== priceAfterDiscounts && (
|
||||||
|
<Typography variant="oldPrice">
|
||||||
|
{currencyFormatter.format(
|
||||||
|
Math.trunc(priceBeforeDiscounts) / 100
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Typography variant="price">
|
||||||
|
{currencyFormatter.format(
|
||||||
|
Math.trunc(priceAfterDiscounts) / 100
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (addFreeTariff) {
|
||||||
|
if (tariffElements.length < 6)
|
||||||
|
tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
|
||||||
|
else
|
||||||
|
tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredTariffs = tariffs.filter((tariff) => {
|
return tariffElements;
|
||||||
if (tariff.privileges[0] === undefined) return false;
|
};
|
||||||
if (
|
|
||||||
(tariff.privileges[0].type === "day") === (unit === "time") &&
|
|
||||||
!tariff.isDeleted &&
|
|
||||||
!tariff.isCustom
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
((selectedItem === 0 && unit === "time") || unit !== "time") &&
|
|
||||||
tariff.privileges[0].serviceKey === "squiz" &&
|
|
||||||
tariff.privileges[0].privilegeId !== "squizHideBadge"
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selectedItem === 1 &&
|
|
||||||
unit === "time" &&
|
|
||||||
tariff.privileges[0].privilegeId === "squizHideBadge"
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const createTariffElements = (filteredTariffs: Tariff[], addFreeTariff = false) => {
|
return (
|
||||||
const tariffElements = filteredTariffs
|
<SectionWrapper
|
||||||
.filter((tariff) => tariff.privileges.length > 0)
|
maxWidth="lg"
|
||||||
.map((tariff, index) => {
|
sx={{
|
||||||
const { priceBeforeDiscounts, priceAfterDiscounts } = calcTariffPrice(
|
mt: "20px",
|
||||||
tariff,
|
mb: upMd ? "100px" : "63px",
|
||||||
discounts ?? [],
|
px: isTablet ? (isMobile ? "18px" : "40px") : "20px",
|
||||||
purchasesAmount,
|
display: "flex",
|
||||||
currentTariffs ?? [],
|
flexDirection: "column",
|
||||||
isUserNko,
|
}}
|
||||||
userId,
|
>
|
||||||
);
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{isMobile && (
|
||||||
return (
|
<IconButton
|
||||||
<TariffCard
|
onClick={handleCustomBackNavigation}
|
||||||
key={tariff._id}
|
|
||||||
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}
|
|
||||||
backgroundColor={unit === "time" ? "#EEE4FC" : "#FEDFD0"}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
buttonProps={{
|
|
||||||
text: "Выбрать",
|
|
||||||
onClick: () => handleTariffItemClick(tariff._id),
|
|
||||||
}}
|
|
||||||
headerText={tariff.name}
|
|
||||||
text={tariff.description || ""}
|
|
||||||
price={
|
|
||||||
<>
|
|
||||||
{priceBeforeDiscounts !== priceAfterDiscounts && (
|
|
||||||
<Typography variant="oldPrice">{currencyFormatter.format(Math.trunc(priceBeforeDiscounts) / 100)}</Typography>
|
|
||||||
)}
|
|
||||||
<Typography variant="price">{currencyFormatter.format(Math.trunc(priceAfterDiscounts) / 100)}</Typography>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (addFreeTariff) {
|
|
||||||
if (tariffElements.length < 6) tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
|
|
||||||
else tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tariffElements;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SectionWrapper
|
|
||||||
maxWidth="lg"
|
|
||||||
sx={{
|
sx={{
|
||||||
mt: "20px",
|
p: 0,
|
||||||
mb: upMd ? "100px" : "63px",
|
height: "28px",
|
||||||
px: isTablet ? (isMobile ? "18px" : "40px") : "20px",
|
width: "28px",
|
||||||
display: "flex",
|
color: "black",
|
||||||
flexDirection: "column",
|
marginRight: "10px",
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
marginBottom: "23px",
|
||||||
|
mt: "20px",
|
||||||
|
fontSize: isMobile ? "24px" : "36px",
|
||||||
|
fontWeight: "500",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
{StepperText[unit]}
|
||||||
{isMobile && (
|
</Typography>
|
||||||
<IconButton
|
</Box>
|
||||||
onClick={handleCustomBackNavigation}
|
{unit === "time" && (
|
||||||
sx={{ p: 0, height: "28px", width: "28px", color: "black", marginRight: "10px" }}
|
<>
|
||||||
>
|
{isMobile ? (
|
||||||
<ArrowBackIcon />
|
<Select
|
||||||
</IconButton>
|
items={subPages}
|
||||||
)}
|
selectedItem={selectedItem}
|
||||||
<Typography
|
setSelectedItem={setSelectedItem}
|
||||||
sx={{
|
/>
|
||||||
marginBottom: "23px",
|
) : (
|
||||||
mt: "20px",
|
<Tabs
|
||||||
fontSize: isMobile ? "24px" : "36px",
|
items={subPages}
|
||||||
fontWeight: "500",
|
selectedItem={selectedItem}
|
||||||
}}
|
setSelectedItem={setSelectedItem}
|
||||||
>
|
/>
|
||||||
{StepperText[unit]}
|
)}
|
||||||
</Typography>
|
</>
|
||||||
</Box>
|
)}
|
||||||
{unit === "time" && (
|
<Box
|
||||||
<>
|
sx={{
|
||||||
{isMobile ? (
|
justifyContent: "left",
|
||||||
<Select
|
mt: "40px",
|
||||||
items={subPages}
|
mb: "30px",
|
||||||
selectedItem={selectedItem}
|
display: "grid",
|
||||||
setSelectedItem={setSelectedItem}
|
gap: "40px",
|
||||||
/>
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
|
||||||
) : (
|
isTablet ? "436px" : "360px"
|
||||||
<Tabs
|
}))`,
|
||||||
items={subPages}
|
}}
|
||||||
selectedItem={selectedItem}
|
>
|
||||||
setSelectedItem={setSelectedItem}
|
{createTariffElements(filteredTariffs, true)}
|
||||||
/>
|
</Box>
|
||||||
)}
|
{/*{recentlyPurchased.length > 0 && (*/}
|
||||||
</>
|
{/* <>*/}
|
||||||
)}
|
{/* <Typography*/}
|
||||||
<Box
|
{/* sx={{*/}
|
||||||
sx={{
|
{/* mt: "40px",*/}
|
||||||
justifyContent: "left",
|
{/* fontSize: isMobile ? "24px" : "36px",*/}
|
||||||
mt: "40px",
|
{/* fontWeight: "500",*/}
|
||||||
mb: "30px",
|
{/* }}*/}
|
||||||
display: "grid",
|
{/* >*/}
|
||||||
gap: "40px",
|
{/* Ранее вы*/}
|
||||||
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${isTablet ? "436px" : "360px"}))`,
|
{/* </Typography>*/}
|
||||||
}}
|
{/* <Slider items={createTariffElements(recentlyPurchased)} />*/}
|
||||||
>
|
{/* </>*/}
|
||||||
{createTariffElements(filteredTariffs, true)}
|
{/*)}*/}
|
||||||
</Box>
|
</SectionWrapper>
|
||||||
{/*{recentlyPurchased.length > 0 && (*/}
|
);
|
||||||
{/* <>*/}
|
|
||||||
{/* <Typography*/}
|
|
||||||
{/* sx={{*/}
|
|
||||||
{/* mt: "40px",*/}
|
|
||||||
{/* fontSize: isMobile ? "24px" : "36px",*/}
|
|
||||||
{/* fontWeight: "500",*/}
|
|
||||||
{/* }}*/}
|
|
||||||
{/* >*/}
|
|
||||||
{/* Ранее вы*/}
|
|
||||||
{/* </Typography>*/}
|
|
||||||
{/* <Slider items={createTariffElements(recentlyPurchased)} />*/}
|
|
||||||
{/* </>*/}
|
|
||||||
{/*)}*/}
|
|
||||||
</SectionWrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withErrorBoundary(TariffPage, {
|
export default withErrorBoundary(TariffPage, {
|
||||||
fallback: (
|
fallback: (
|
||||||
<Typography mt="8px" textAlign="center">
|
<Typography mt="8px" textAlign="center">
|
||||||
Ошибка загрузки тарифов
|
Ошибка загрузки тарифов
|
||||||
</Typography>
|
</Typography>
|
||||||
),
|
),
|
||||||
onError: handleComponentError,
|
onError: handleComponentError,
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
@ -48,17 +47,15 @@ export default function RecoverDialog() {
|
|||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: async (values, formikHelpers) => {
|
onSubmit: async (values, formikHelpers) => {
|
||||||
const [recoverResponse, recoverError] = await recover(
|
const [, recoverError] = await recover(values.email.trim());
|
||||||
values.email.trim()
|
|
||||||
);
|
|
||||||
|
|
||||||
formikHelpers.setSubmitting(false);
|
formikHelpers.setSubmitting(false);
|
||||||
if (recoverError) {
|
if (recoverError) {
|
||||||
enqueueSnackbar(recoverError);
|
enqueueSnackbar(recoverError);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
navigate("/")
|
navigate("/");
|
||||||
enqueueSnackbar("Письмо придёт Вам на почту")
|
enqueueSnackbar("Письмо придёт Вам на почту");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Dialog,
|
Dialog,
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
Button,
|
Button,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
@ -19,175 +19,180 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
password: string()
|
password: string()
|
||||||
.min(8, "Минимум 8 символов")
|
.min(8, "Минимум 8 символов")
|
||||||
.matches(/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/, "Некорректные символы")
|
.matches(
|
||||||
.required("Поле обязательно"),
|
/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/,
|
||||||
|
"Некорректные символы"
|
||||||
|
)
|
||||||
|
.required("Поле обязательно"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function RecoverPassword() {
|
export default function RecoverPassword() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||||
const [tokenUser, setTokenUser] = useState<string | null>("");
|
const [tokenUser, setTokenUser] = useState<string | null>("");
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const formik = useFormik<Values>({
|
const formik = useFormik<Values>({
|
||||||
initialValues,
|
initialValues,
|
||||||
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("Извините, произошла ошибка, попробуйте повторить позже")}
|
|
||||||
|
|
||||||
} else {
|
if (!patchUserError) {
|
||||||
enqueueSnackbar("Неверный url-адрес")
|
setIsDialogOpen(false);
|
||||||
}
|
navigate("/");
|
||||||
},
|
enqueueSnackbar("Пароль успешно сменён");
|
||||||
});
|
} else {
|
||||||
useEffect(() => {
|
setAuthToken("");
|
||||||
const params = new URLSearchParams(window.location.search)
|
enqueueSnackbar(
|
||||||
const authToken = params.get("auth")
|
"Извините, произошла ошибка, попробуйте повторить позже"
|
||||||
setTokenUser(authToken)
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enqueueSnackbar("Неверный url-адрес");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const authToken = params.get("auth");
|
||||||
|
setTokenUser(authToken);
|
||||||
|
|
||||||
history.pushState(null, document.title, "/changepwd");
|
history.pushState(null, document.title, "/changepwd");
|
||||||
return () => {setAuthToken("")}
|
return () => {
|
||||||
}, []);
|
setAuthToken("");
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isDialogOpen}
|
open={isDialogOpen}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
width: "600px",
|
width: "600px",
|
||||||
maxWidth: "600px",
|
maxWidth: "600px",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
backdrop: {
|
backdrop: {
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
noValidate
|
noValidate
|
||||||
sx={{
|
sx={{
|
||||||
position: "relative",
|
position: "relative",
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
p: upMd ? "50px" : "18px",
|
p: upMd ? "50px" : "18px",
|
||||||
pb: upMd ? "40px" : "30px",
|
pb: upMd ? "40px" : "30px",
|
||||||
gap: "15px",
|
gap: "15px",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: cardShadow,
|
boxShadow: cardShadow,
|
||||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
||||||
{
|
{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "46px",
|
top: "46px",
|
||||||
margin: "0",
|
margin: "0",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: "7px",
|
right: "7px",
|
||||||
top: "7px",
|
top: "7px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Box>
|
<Box>
|
||||||
<PenaLogo width={upMd ? 233 : 196} color="black" />
|
<PenaLogo width={upMd ? 233 : 196} color="black" />
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.gray.dark,
|
color: theme.palette.gray.dark,
|
||||||
mt: "5px",
|
mt: "5px",
|
||||||
mb: upMd ? "10px" : "33px",
|
mb: upMd ? "10px" : "33px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Введите новый пароль
|
Введите новый пароль
|
||||||
</Typography>
|
</Typography>
|
||||||
<InputTextfield
|
<InputTextfield
|
||||||
TextfieldProps={{
|
TextfieldProps={{
|
||||||
value: formik.values.password,
|
value: formik.values.password,
|
||||||
placeholder: "введите пароль",
|
placeholder: "введите пароль",
|
||||||
onBlur: formik.handleBlur,
|
onBlur: formik.handleBlur,
|
||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
id="password"
|
id="password"
|
||||||
label="Новый пароль"
|
label="Новый пароль"
|
||||||
gap={upMd ? "10px" : "10px"}
|
gap={upMd ? "10px" : "10px"}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="pena-contained-dark"
|
variant="pena-contained-dark"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
sx={{
|
sx={{
|
||||||
py: "12px",
|
py: "12px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.purple.dark,
|
backgroundColor: theme.palette.purple.dark,
|
||||||
},
|
},
|
||||||
"&:active": {
|
"&:active": {
|
||||||
color: "white",
|
color: "white",
|
||||||
backgroundColor: "black",
|
backgroundColor: "black",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Восстановить
|
Восстановить
|
||||||
</Button>
|
</Button>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
mt: "auto",
|
mt: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Dialog>
|
||||||
</Dialog>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,35 @@
|
|||||||
import { ErrorInfo } from "react"
|
import { ErrorInfo } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface ComponentError {
|
interface ComponentError {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
message: string;
|
message: string;
|
||||||
callStack: string | undefined;
|
callStack: string | undefined;
|
||||||
componentStack: string | null | undefined;
|
componentStack: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleComponentError(error: Error, info: ErrorInfo) {
|
export function handleComponentError(error: Error, info: ErrorInfo) {
|
||||||
const componentError: ComponentError = {
|
const componentError: ComponentError = {
|
||||||
timestamp: Math.floor(Date.now() / 1000),
|
timestamp: Math.floor(Date.now() / 1000),
|
||||||
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 = []
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,45 @@
|
|||||||
import { useEffect, useLayoutEffect, useRef } from "react"
|
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||||
import { devlog } from "@frontend/kitui"
|
import { devlog } from "@frontend/kitui";
|
||||||
|
|
||||||
import { ServiceKeyToPrivilegesMap } from "@root/model/privilege"
|
import { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
|
||||||
import { getCustomTariffs } from "@root/api/tariff"
|
import { getCustomTariffs } from "@root/api/tariff";
|
||||||
|
|
||||||
export function useCustomTariffs({
|
export function useCustomTariffs({
|
||||||
onError,
|
onError,
|
||||||
onNewUser,
|
onNewUser,
|
||||||
}: {
|
}: {
|
||||||
onNewUser: (response: ServiceKeyToPrivilegesMap) => void;
|
onNewUser: (response: ServiceKeyToPrivilegesMap) => void;
|
||||||
onError: (error: any) => void;
|
onError: (error: any) => void;
|
||||||
}) {
|
}) {
|
||||||
const onNewUserRef = useRef(onNewUser)
|
const onNewUserRef = useRef(onNewUser);
|
||||||
const onErrorRef = useRef(onError)
|
const onErrorRef = useRef(onError);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
onNewUserRef.current = onNewUser
|
onNewUserRef.current = onNewUser;
|
||||||
onErrorRef.current = onError
|
onErrorRef.current = onError;
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController();
|
||||||
|
|
||||||
getCustomTariffs(controller.signal)
|
const getCustomTariffsRequest = async () => {
|
||||||
.then(([customTariffs]) => {
|
const [customTariffs, customTariffsError] = await getCustomTariffs(
|
||||||
if (customTariffs) {
|
controller.signal
|
||||||
onNewUserRef.current(customTariffs)
|
);
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(([_, error]) => {
|
|
||||||
devlog("Error fetching custom tariffs", error)
|
|
||||||
onErrorRef.current(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => controller.abort()
|
if (customTariffsError) {
|
||||||
}, [])
|
devlog("Error fetching custom tariffs", customTariffsError);
|
||||||
|
onErrorRef.current(customTariffsError);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (customTariffs) {
|
||||||
|
onNewUserRef.current(customTariffs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getCustomTariffsRequest();
|
||||||
|
|
||||||
|
return () => controller.abort();
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
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({
|
export function useTariffFetcher({
|
||||||
tariffsPerPage,
|
tariffsPerPage,
|
||||||
apiPage,
|
apiPage,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onError,
|
onError,
|
||||||
}: {
|
}: {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
tariffsPerPage: number;
|
tariffsPerPage: number;
|
||||||
@ -16,32 +16,42 @@ export function useTariffFetcher({
|
|||||||
onSuccess: (response: Tariff[]) => void;
|
onSuccess: (response: Tariff[]) => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
}) {
|
}) {
|
||||||
const [fetchState, setFetchState] = useState<"fetching" | "idle" | "all fetched">("idle")
|
const [fetchState, setFetchState] = useState<
|
||||||
const onSuccessRef = useRef(onSuccess)
|
"fetching" | "idle" | "all fetched"
|
||||||
const onErrorRef = useRef(onError)
|
>("idle");
|
||||||
|
const onSuccessRef = useRef(onSuccess);
|
||||||
|
const onErrorRef = useRef(onError);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
onSuccessRef.current = onSuccess
|
onSuccessRef.current = onSuccess;
|
||||||
onErrorRef.current = onError
|
onErrorRef.current = onError;
|
||||||
}, [onError, onSuccess])
|
}, [onError, onSuccess]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController();
|
||||||
|
|
||||||
setFetchState("fetching")
|
const getTariffsRequest = async () => {
|
||||||
getTariffs(apiPage, tariffsPerPage, controller.signal)
|
setFetchState("fetching");
|
||||||
.then(([result]) => {
|
const [tariffs, tariffsError] = await getTariffs(
|
||||||
if (result && result.tariffs.length > 0) {
|
apiPage,
|
||||||
onSuccessRef.current(result.tariffs)
|
tariffsPerPage,
|
||||||
setFetchState("idle")
|
controller.signal
|
||||||
} else setFetchState("all fetched")
|
);
|
||||||
})
|
|
||||||
.catch(([_, error]) => {
|
|
||||||
onErrorRef.current?.(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => controller.abort()
|
if (tariffsError) {
|
||||||
}, [apiPage, tariffsPerPage])
|
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> = {
|
const translateMessage: Record<string, string> = {
|
||||||
"user not found": "Пользователь не найден",
|
"user not found": "Пользователь не найден",
|
||||||
"invalid password": "Неправильный пароль",
|
"invalid password": "Неправильный пароль",
|
||||||
"field <password> is empty": "Поле \"Пароль\" не заполнено",
|
"field <password> is empty": 'Поле "Пароль" не заполнено',
|
||||||
"field <login> is empty": "Поле \"Логин\" не заполнено",
|
"field <login> is empty": 'Поле "Логин" не заполнено',
|
||||||
"field <email> is empty": "Поле \"E-mail\" не заполнено",
|
"field <email> is empty": 'Поле "E-mail" не заполнено',
|
||||||
"field <phoneNumber> is empty": "Поле \"Номер телефона\" не заполнено",
|
"field <phoneNumber> is empty": 'Поле "Номер телефона" не заполнено',
|
||||||
"user with this email or login is exist": "Пользователь уже существует",
|
"user with this email or login is exist": "Пользователь уже существует",
|
||||||
"user with this login is exist":
|
"user with this login is exist":
|
||||||
"Пользователь с таким логином уже существует",
|
"Пользователь с таким логином уже существует",
|
||||||
@ -29,12 +29,16 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => {
|
|||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
const serverError = error.response.data as ServerError;
|
const serverError = error.response.data as ServerError;
|
||||||
let SEMessage;
|
let SEMessage;
|
||||||
|
|
||||||
|
if (typeof error.response?.data === "string") {
|
||||||
|
return [error.response?.data];
|
||||||
|
}
|
||||||
if ("statusCode" in (error.response?.data as ServerError)) {
|
if ("statusCode" in (error.response?.data as ServerError)) {
|
||||||
SEMessage = serverError?.message.toLowerCase() || "";
|
SEMessage = serverError?.message.toLowerCase() || "";
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
"error" in (error.response?.data as ServerError) &&
|
"error" in (error.response?.data as ServerError) &&
|
||||||
!("statusCode" in (error.response?.data as ServerError))
|
!("statusCode" in (error.response?.data as ServerError))
|
||||||
) {
|
) {
|
||||||
SEMessage = serverError?.error.toLowerCase() || "";
|
SEMessage = serverError?.error.toLowerCase() || "";
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user