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