diff --git a/src/api/auth.ts b/src/api/auth.ts index 603e72d..aaa65d8 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -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({ - 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({ - url: process.env.REACT_APP_DOMAIN + "/codeword/recover", + formData.append( + "RedirectionURL", + `${process.env.REACT_APP_DOMAIN}/changepwd` + ); + + const recoverResponse = await makeRequest({ + 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({ - 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}`]; } -} +}; diff --git a/src/api/cart.ts b/src/api/cart.ts index 299b932..30e2490 100644 --- a/src/api/cart.ts +++ b/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({ - url: apiUrl + `/cart?id=${tariffId}`, - method: "PATCH", - useToken: true, - }) +export const patchCart = async ( + tariffId: string +): Promise<[string[], string?]> => { + try { + const patchCartResponse = await makeRequest({ + 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({ - url: apiUrl + `/cart?id=${tariffId}`, - method: "DELETE", - useToken: true, - }) +export const deleteCart = async ( + tariffId: string +): Promise<[string[], string?]> => { + try { + const deleteCartResponse = await makeRequest({ + 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({ - url: apiUrl + "/cart/pay", - method: "POST", - useToken: true, - }) +export const payCart = async (): Promise<[UserAccount | null, string?]> => { + try { + const payCartResponse = await makeRequest({ + 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}`] - } -} \ No newline at end of file + return [null, `Не удалось изменить валюту. ${error}`]; + } +}; diff --git a/src/api/history.ts b/src/api/history.ts index df70b7f..6e21f69 100644 --- a/src/api/history.ts +++ b/src/api/history.ts @@ -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({ - 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({ + method: "POST", + url: `${API_URL}/sendReport/${tariffId}`, + }); + + return [sendReportResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Не удалось отправить отчёт. ${error}`]; + } +}; diff --git a/src/api/makeRequest.ts b/src/api/makeRequest.ts index f76e479..a356f5c 100644 --- a/src/api/makeRequest.ts +++ b/src/api/makeRequest.ts @@ -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(data: MakeRequest): Promise { - try { - const response = await KIT.makeRequest(data) +async function makeRequest( + data: MakeRequest +): Promise { + try { + const response = await KIT.makeRequest(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; diff --git a/src/api/price.ts b/src/api/price.ts index e50eb39..a411f78 100644 --- a/src/api/price.ts +++ b/src/api/price.ts @@ -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({ - 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; -} +}; diff --git a/src/api/promocode.ts b/src/api/promocode.ts index 1644bf2..eab67f2 100644 --- a/src/api/promocode.ts +++ b/src/api/promocode.ts @@ -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]; } -} +}; diff --git a/src/api/recentlyPurchasedTariffs.ts b/src/api/recentlyPurchasedTariffs.ts index d29f24c..ac3b052 100644 --- a/src/api/recentlyPurchasedTariffs.ts +++ b/src/api/recentlyPurchasedTariffs.ts @@ -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({ - 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}`] - } -} \ No newline at end of file +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}`]; + } +}; diff --git a/src/api/tariff.ts b/src/api/tariff.ts index 88668d5..a4f9e3e 100644 --- a/src/api/tariff.ts +++ b/src/api/tariff.ts @@ -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({ - 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({ - 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({ - 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({ - 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({ - 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({ + 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({ + 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({ + 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({ + 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; +}; diff --git a/src/api/ticket.ts b/src/api/ticket.ts index a5a9138..96fea3e 100644 --- a/src/api/ticket.ts +++ b/src/api/ticket.ts @@ -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({ + 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}`]; + } +}; diff --git a/src/api/user.ts b/src/api/user.ts index 13a3358..a3a62db 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -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({ - 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({ + 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}`]; + } +}; diff --git a/src/api/verification.ts b/src/api/verification.ts index 5374eca..b473884 100644 --- a/src/api/verification.ts +++ b/src/api/verification.ts @@ -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({ - 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({ + 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({ - 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({ + 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({ - 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({ + 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({ + method: "PATCH", + url: API_URL, + body, + useToken: true, + withCredentials: true, + }); + + return [updateDocumentResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка обновления документа. ${error}`]; + } +}; diff --git a/src/api/wallet.ts b/src/api/wallet.ts index e744367..1cb3745 100644 --- a/src/api/wallet.ts +++ b/src/api/wallet.ts @@ -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 => { +export const sendRSPayment = async ( + money: number +): Promise<[string | null, string?]> => { try { - await makeRequest({ - 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}`]; } }; diff --git a/src/components/FloatingSupportChat/Chat.tsx b/src/components/FloatingSupportChat/Chat.tsx index a558e64..76263cd 100644 --- a/src/components/FloatingSupportChat/Chat.tsx +++ b/src/components/FloatingSupportChat/Chat.tsx @@ -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(""); - const [disableFileButton, setDisableFileButton] = useState(false); + const [disableFileButton, setDisableFileButton] = useState(false); const [sseEnabled, setSseEnabled] = useState(true); const [modalWarningType, setModalWarningType] = useState(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 && ( - ) - } + + )} ); -} +} \ No newline at end of file diff --git a/src/components/FloatingSupportChat/ChatImage.tsx b/src/components/FloatingSupportChat/ChatImage.tsx index 8d790aa..2115341 100644 --- a/src/components/FloatingSupportChat/ChatImage.tsx +++ b/src/components/FloatingSupportChat/ChatImage.tsx @@ -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://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/angesight/${file}`} /> diff --git a/src/components/FloatingSupportChat/ChatVideo.tsx b/src/components/FloatingSupportChat/ChatVideo.tsx index 5779858..375f384 100644 --- a/src/components/FloatingSupportChat/ChatVideo.tsx +++ b/src/components/FloatingSupportChat/ChatVideo.tsx @@ -113,7 +113,9 @@ export default function ChatImage({ }} controls > - + diff --git a/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx b/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx index 05262ed..ec3530b 100644 --- a/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx +++ b/src/pages/AccountSettings/DocumentsDialog/DocumentItem.tsx @@ -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; - keyName: KeyNames + text: string; + documentUrl: string; + sx?: SxProps; + 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(null); - const fileInputRef = useRef(null) + function handleChooseFileClick() { + fileInputRef.current?.click(); + } - function handleChooseFileClick() { - fileInputRef.current?.click() - } + const downloadFile = async () => { + const { data } = await axios.get(documentUrl, { + responseType: "arraybuffer", + }); - const downloadFile = async () => { - const { data } = await axios.get(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) { + 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 - ) { - const target = e.target as HTMLInputElement; - const file = target?.files?.[0] || null; - if (file !== null) { - const readedFile = await readFile(file, "binary") - try { - await makeRequest({ - 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 ( + + + {text} + + {documentUrl && ( + <> + + + {documentUrl.split("/").pop()?.split(".")?.[0]} + + + + + + - return ( - - - {text} - - {documentUrl && ( - <> - - - {documentUrl.split("/").pop()?.split(".")?.[0]} - - - - - - - - - - - - - )} - - ) + + + + + )} + + ); } diff --git a/src/pages/History/AccordionWrapper.tsx b/src/pages/History/AccordionWrapper.tsx index 70ee3f6..6107306 100644 --- a/src/pages/History/AccordionWrapper.tsx +++ b/src/pages/History/AccordionWrapper.tsx @@ -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 ( - - ( - - {e[1].Value} - {e[5].Value} {getDeclension(Number(e[5].Value), e[7].Value.toString())} - ) - )} - header={ - <> - - - - {createdAt} - + return ( + + ( + + {e[1].Value} - {e[5].Value}{" "} + {getDeclension(Number(e[5].Value), e[7].Value.toString())} + + ))} + header={ + <> + + + + {createdAt} + - - {valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name} - - - - - Способ оплаты: ${valuesByKey.payMethod}}`} - > - {valuesByKey.payMethod && Способ оплаты: {valuesByKey.payMethod}} - - - - {Number(content[1].Value) / 100 ? Number(content[1].Value) / 100 : "nodata"} руб. - - - - {!isMobile && - <> - { - e.stopPropagation(); - sendBillByEmail(mainId); - }} - sx={{ - ml: "20px", - bgcolor: "#EEE4FC", - color: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - color: "white", - }, - "&:active": { - bgcolor: "black", - color: "white", - } - }} - > - - - { - e.stopPropagation() - handleTariffItemClick(valuesByKey.id) - }} - sx={{ - ml: "20px", - bgcolor:"#EEE4FC", - stroke: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor:"#7E2AEA", - stroke: "white", - }, - "&:active": { - bgcolor:"black", - stroke: "white", - } - }} - > - - - - - } - - - {isMobile && - <> - { - e.stopPropagation(); - sendBillByEmail(mainId); - }} - sx={{ - m: "0 10px", - bgcolor: "#EEE4FC", - color: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - color: "white", - }, - "&:active": { - bgcolor: "black", - color: "white", - } - }} - > - - - { - e.stopPropagation() - handleTariffItemClick(valuesByKey.id) - }} - sx={{ - mr: "10px", - bgcolor:"#EEE4FC", - stroke: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor:"#7E2AEA", - stroke: "white", - }, - "&:active": { - bgcolor:"black", - stroke: "white", - } - }} - > - - - - - } - - } - /> - - ) + + {valuesByKey.iscustom ? "Мой тариф" : valuesByKey.name} + + + + + Способ оплаты: ${valuesByKey.payMethod}}`} + > + {valuesByKey.payMethod && ( + + Способ оплаты: {valuesByKey.payMethod} + + )} + + + + {Number(content[1].Value) / 100 + ? Number(content[1].Value) / 100 + : "nodata"}{" "} + руб. + + + + {!isMobile && ( + <> + { + e.stopPropagation(); + sendBillByEmail(mainId); + }} + sx={{ + ml: "20px", + bgcolor: "#EEE4FC", + color: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + color: "white", + }, + "&:active": { + bgcolor: "black", + color: "white", + }, + }} + > + + + { + e.stopPropagation(); + handleTariffItemClick(valuesByKey.id); + }} + sx={{ + ml: "20px", + bgcolor: "#EEE4FC", + stroke: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + stroke: "white", + }, + "&:active": { + bgcolor: "black", + stroke: "white", + }, + }} + > + + + + )} + + + {isMobile && ( + <> + { + e.stopPropagation(); + sendBillByEmail(mainId); + }} + sx={{ + m: "0 10px", + bgcolor: "#EEE4FC", + color: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + color: "white", + }, + "&:active": { + bgcolor: "black", + color: "white", + }, + }} + > + + + { + e.stopPropagation(); + handleTariffItemClick(valuesByKey.id); + }} + sx={{ + mr: "10px", + bgcolor: "#EEE4FC", + stroke: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + stroke: "white", + }, + "&:active": { + bgcolor: "black", + stroke: "white", + }, + }} + > + + + + )} + + } + /> + + ); } diff --git a/src/pages/History/AccordionWrapper2.tsx b/src/pages/History/AccordionWrapper2.tsx index 9b7ef72..632e290 100644 --- a/src/pages/History/AccordionWrapper2.tsx +++ b/src/pages/History/AccordionWrapper2.tsx @@ -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 ( - - ( - `${privilege.description} - ${privilege.amount} ${getDeclension(Number(privilege.serviceKey), privilege.value)} ` - ))} - header={ - <> - - - - {createdAt} - + return ( + + + `${privilege.description} - ${privilege.amount} ${getDeclension( + Number(privilege.serviceKey), + privilege.value + )} ` + )} + header={ + <> + + + + {createdAt} + - - {tariff.isCustom ? "Мой тариф" : tariff.name} - - - - - {/* + {tariff.isCustom ? "Мой тариф" : tariff.name} + + + + + {/* Способ оплаты: {valuesByKey.payMethod}} */} - - - {currencyFormatter.format(price)} - - - - {!isMobile && - <> - { - e.stopPropagation(); - sendBillByEmail(mainId); - }} - sx={{ - ml: "20px", - bgcolor: "#EEE4FC", - color: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - color: "white", - }, - "&:active": { - bgcolor: "black", - color: "white", - } - }} - > - - - { - e.stopPropagation(); - handleTariffItemClick(tariff._id); - }} - sx={{ - ml: "20px", - bgcolor: "#EEE4FC", - stroke: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - stroke: "white", - }, - "&:active": { - bgcolor: "black", - stroke: "white", - } - }} - > - - - - - } - - - {isMobile && - <> - { - e.stopPropagation(); - sendBillByEmail(mainId); - }} - sx={{ - m: "0 10px", - bgcolor: "#EEE4FC", - color: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - color: "white", - }, - "&:active": { - bgcolor: "black", - color: "white", - } - }} - > - - - { - e.stopPropagation(); - handleTariffItemClick(tariff._id); - }} - sx={{ - mr: "10px", - bgcolor: "#EEE4FC", - stroke: "#7E2AEA", - borderRadius: 2, - "&:hover": { - bgcolor: "#7E2AEA", - stroke: "white", - }, - "&:active": { - bgcolor: "black", - stroke: "white", - } - }} - > - - - - - } - - } - /> - - ); + + + {currencyFormatter.format(price)} + + + + {!isMobile && ( + <> + { + e.stopPropagation(); + sendBillByEmail(mainId); + }} + sx={{ + ml: "20px", + bgcolor: "#EEE4FC", + color: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + color: "white", + }, + "&:active": { + bgcolor: "black", + color: "white", + }, + }} + > + + + { + e.stopPropagation(); + handleTariffItemClick(tariff._id); + }} + sx={{ + ml: "20px", + bgcolor: "#EEE4FC", + stroke: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + stroke: "white", + }, + "&:active": { + bgcolor: "black", + stroke: "white", + }, + }} + > + + + + )} + + + {isMobile && ( + <> + { + e.stopPropagation(); + sendBillByEmail(mainId); + }} + sx={{ + m: "0 10px", + bgcolor: "#EEE4FC", + color: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + color: "white", + }, + "&:active": { + bgcolor: "black", + color: "white", + }, + }} + > + + + { + e.stopPropagation(); + handleTariffItemClick(tariff._id); + }} + sx={{ + mr: "10px", + bgcolor: "#EEE4FC", + stroke: "#7E2AEA", + borderRadius: 2, + "&:hover": { + bgcolor: "#7E2AEA", + stroke: "white", + }, + "&:active": { + bgcolor: "black", + stroke: "white", + }, + }} + > + + + + )} + + } + /> + + ); } diff --git a/src/pages/History/index.tsx b/src/pages/History/index.tsx index 3060f45..b763dc0 100644 --- a/src/pages/History/index.tsx +++ b/src/pages/History/index.tsx @@ -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(0); + const [selectedItem, setSelectedItem] = useState(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 ( - + + {isMobile && ( + + + + )} + - - {isMobile && ( - - - - )} - - История - - - {isMobile ? ( - + ) : ( + + )} + Ошибка загрузки истории} + onError={handleComponentError} + > + {historyData?.length === 0 && ( + Нет данных + )} + {/* Для ненормального 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 ( + + { + event.stopPropagation(); + handleHistoryResponse(e.id); + }} + /> + + ); + })} + {/* Для нормального 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 ( + + + + ); + })} + + + ); } diff --git a/src/pages/Payment/Payment.tsx b/src/pages/Payment/Payment.tsx index 52feb00..e2e17f9 100644 --- a/src/pages/Payment/Payment.tsx +++ b/src/pages/Payment/Payment.tsx @@ -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 ( diff --git a/src/pages/QuizPayment/QuizPayment.tsx b/src/pages/QuizPayment/QuizPayment.tsx index 1fd55ce..db853eb 100644 --- a/src/pages/QuizPayment/QuizPayment.tsx +++ b/src/pages/QuizPayment/QuizPayment.tsx @@ -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>( - 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"); diff --git a/src/pages/Support/ChatImageNewWindow.tsx b/src/pages/Support/ChatImageNewWindow.tsx index 4138ea5..4d33853 100644 --- a/src/pages/Support/ChatImageNewWindow.tsx +++ b/src/pages/Support/ChatImageNewWindow.tsx @@ -12,7 +12,7 @@ export default function ChatImageNewWindow() { maxHeight: "100vh", maxWidth: "100vw", }} - src={`https://storage.yandexcloud.net/pair/${srcImage}`} + src={`https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/angesight/${srcImage}`} /> ); diff --git a/src/pages/Support/CreateTicket.tsx b/src/pages/Support/CreateTicket.tsx index 77e03f4..a3adcf7 100644 --- a/src/pages/Support/CreateTicket.tsx +++ b/src/pages/Support/CreateTicket.tsx @@ -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("") - const [ticketBodyField, setTicketBodyField] = useState("") + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const navigate = useNavigate(); + const [ticketNameField, setTicketNameField] = useState(""); + const [ticketBodyField, setTicketBodyField] = useState(""); - 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 ( - - - - Написать обращение - - - setTicketNameField(e.target.value)} - /> - - - - setTicketBodyField(e.target.value)} - /> - - - - - - - - ) + if (createTicketerror) { + return enqueueSnackbar(createTicketerror); + } + + navigate(`/support/${createTicketresult?.Ticket}`); + } + + return ( + + + + Написать обращение + + + setTicketNameField(e.target.value)} + /> + + + + setTicketBodyField(e.target.value)} + /> + + + + + + + + ); } diff --git a/src/pages/Support/SupportChat.tsx b/src/pages/Support/SupportChat.tsx index 6e60d83..cee479e 100644 --- a/src/pages/Support/SupportChat.tsx +++ b/src/pages/Support/SupportChat.tsx @@ -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; } }; diff --git a/src/pages/Tariffs/TariffsPage.tsx b/src/pages/Tariffs/TariffsPage.tsx index a653e46..6ce564b 100644 --- a/src/pages/Tariffs/TariffsPage.tsx +++ b/src/pages/Tariffs/TariffsPage.tsx @@ -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 = { - 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(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(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 ( + + } + buttonProps={{ + text: "Выбрать", + onClick: () => handleTariffItemClick(tariff._id), + }} + headerText={tariff.name} + text={tariff.description || ""} + price={ + <> + {priceBeforeDiscounts !== priceAfterDiscounts && ( + + {currencyFormatter.format( + Math.trunc(priceBeforeDiscounts) / 100 + )} + + )} + + {currencyFormatter.format( + Math.trunc(priceAfterDiscounts) / 100 + )} + + + } + /> + ); + }); + + if (addFreeTariff) { + if (tariffElements.length < 6) + tariffElements.push(); + else + tariffElements.splice(5, 0, ); } - 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 ( - - } - buttonProps={{ - text: "Выбрать", - onClick: () => handleTariffItemClick(tariff._id), - }} - headerText={tariff.name} - text={tariff.description || ""} - price={ - <> - {priceBeforeDiscounts !== priceAfterDiscounts && ( - {currencyFormatter.format(Math.trunc(priceBeforeDiscounts) / 100)} - )} - {currencyFormatter.format(Math.trunc(priceAfterDiscounts) / 100)} - - } - /> - ); - }); - - if (addFreeTariff) { - if (tariffElements.length < 6) tariffElements.push(); - else tariffElements.splice(5, 0, ); - } - - return tariffElements; - }; - - return ( - + + {isMobile && ( + + + + )} + - - {isMobile && ( - - - - )} - - {StepperText[unit]} - - - {unit === "time" && ( - <> - {isMobile ? ( - + ) : ( + + )} + + )} + + {createTariffElements(filteredTariffs, true)} + + {/*{recentlyPurchased.length > 0 && (*/} + {/* <>*/} + {/* */} + {/* Ранее вы*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} + + ); } export default withErrorBoundary(TariffPage, { - fallback: ( - - Ошибка загрузки тарифов - - ), - onError: handleComponentError, + fallback: ( + + Ошибка загрузки тарифов + + ), + onError: handleComponentError, }); diff --git a/src/pages/auth/Recover.tsx b/src/pages/auth/Recover.tsx index cb1c8df..e9c15c4 100644 --- a/src/pages/auth/Recover.tsx +++ b/src/pages/auth/Recover.tsx @@ -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("Письмо придёт Вам на почту"); }, }); diff --git a/src/pages/auth/RecoverPassword.tsx b/src/pages/auth/RecoverPassword.tsx index 6d39afd..77a261b 100644 --- a/src/pages/auth/RecoverPassword.tsx +++ b/src/pages/auth/RecoverPassword.tsx @@ -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(true); - const [tokenUser, setTokenUser] = useState(""); - 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({ - initialValues, - validationSchema, - onSubmit: async (values, formikHelpers) => { - if (tokenUser) { - setAuthToken(tokenUser || "") - try { - const response = await makeRequest({ - 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(true); + const [tokenUser, setTokenUser] = useState(""); + 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({ + 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 ( - - - - - - - - - - Введите новый пароль - - - - - - - - ); + return ( + + + + + + + + + + Введите новый пароль + + + + + + + ); } diff --git a/src/utils/handleComponentError.ts b/src/utils/handleComponentError.ts index 301cc42..fda2c59 100644 --- a/src/utils/handleComponentError.ts +++ b/src/utils/handleComponentError.ts @@ -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 +let errorsQueue: ComponentError[] = []; +let timeoutId: ReturnType; 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 = []; } diff --git a/src/utils/hooks/useCustomTariffs.ts b/src/utils/hooks/useCustomTariffs.ts index 0b57118..cbfaa4d 100644 --- a/src/utils/hooks/useCustomTariffs.ts +++ b/src/utils/hooks/useCustomTariffs.ts @@ -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(); + }, []); } diff --git a/src/utils/hooks/useTariffFetcher.ts b/src/utils/hooks/useTariffFetcher.ts index a099785..8013825 100644 --- a/src/utils/hooks/useTariffFetcher.ts +++ b/src/utils/hooks/useTariffFetcher.ts @@ -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; } diff --git a/src/utils/parse-error.ts b/src/utils/parse-error.ts index 3869f35..c8b5f03 100644 --- a/src/utils/parse-error.ts +++ b/src/utils/parse-error.ts @@ -9,10 +9,10 @@ export type ServerError = { const translateMessage: Record = { "user not found": "Пользователь не найден", "invalid password": "Неправильный пароль", - "field is empty": "Поле \"Пароль\" не заполнено", - "field is empty": "Поле \"Логин\" не заполнено", - "field is empty": "Поле \"E-mail\" не заполнено", - "field is empty": "Поле \"Номер телефона\" не заполнено", + "field is empty": 'Поле "Пароль" не заполнено', + "field is empty": 'Поле "Логин" не заполнено', + "field is empty": 'Поле "E-mail" не заполнено', + "field 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() || ""; }