This commit is contained in:
IlyaDoronin 2024-05-15 14:44:10 +03:00
parent 73e8a03a22
commit 17a697893d
27 changed files with 590 additions and 354 deletions

@ -44,6 +44,7 @@ import RecoverPassword from "./pages/auth/RecoverPassword";
import { InfoPrivilege } from "./pages/InfoPrivilege";
import OutdatedLink from "./pages/auth/OutdatedLink";
import { useAfterpay } from "@utils/hooks/useAfterpay";
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull"));
@ -92,65 +93,13 @@ const LazyLoading = ({ children, fallback }: SuspenseProps) => (
<Suspense fallback={fallback ?? <></>}>{children}</Suspense>
);
export function useUserAccountFetcher<T = UserAccount>({
onError,
onNewUserAccount,
url,
userId,
}: {
url: string;
userId: string | null;
onNewUserAccount: (response: T) => void;
onError?: (error: any) => void;
}) {
const onNewUserAccountRef = useRef(onNewUserAccount);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewUserAccountRef.current = onNewUserAccount;
onErrorRef.current = onError;
}, [onError, onNewUserAccount]);
useEffect(() => {
if (!userId) return;
const controller = new AbortController();
makeRequest<never, T>({
url,
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
})
.then((result) => {
devlog("User account", result);
onNewUserAccountRef.current(result);
})
.catch((error) => {
devlog("Error fetching user account", error);
if (isAxiosError(error) && error.response?.status === 404) {
createUserAccount(controller.signal, url.replace("get", "create"))
.then((result) => {
devlog("Created user account", result);
onNewUserAccountRef.current(result as T);
})
.catch((error) => {
devlog("Error creating user account", error);
onErrorRef.current?.(error);
});
} else {
onErrorRef.current?.(error);
}
});
return () => controller.abort();
}, [url, userId]);
}
export default function App() {
const userId = useUserStore((state) => state.userId);
const location = useLocation();
const navigate = useNavigate();
useUserFetcher({
url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
url: `${process.env.REACT_APP_DOMAIN}/user/${userId}`,
userId,
onNewUser: setUser,
onError: (error) => {
@ -164,7 +113,7 @@ export default function App() {
});
useUserAccountFetcher<UserAccount>({
url: process.env.REACT_APP_DOMAIN + "/customer/account",
url: `${process.env.REACT_APP_DOMAIN}/customer/account`,
userId,
onNewUserAccount: setCustomerAccount,
onError: (error) => {
@ -179,7 +128,7 @@ export default function App() {
});
useUserAccountFetcher<OriginalUserAccount>({
url: process.env.REACT_APP_DOMAIN + "/squiz/account/get",
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {

@ -8,7 +8,7 @@ import type {
} from "@frontend/kitui";
import { parseAxiosError } from "../utils/parse-error";
const API_URL = process.env.REACT_APP_DOMAIN + "/auth";
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
export const register = async (
login: string,
@ -79,11 +79,11 @@ export const recover = async (
formData.append("email", email);
formData.append(
"RedirectionURL",
process.env.REACT_APP_DOMAIN + "/changepwd",
`${process.env.REACT_APP_DOMAIN}/changepwd`,
);
const recoverResponse = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
body: formData,
useToken: false,
withCredentials: true,

@ -3,13 +3,13 @@ import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
const API_URL = process.env.REACT_APP_DOMAIN + "/customer";
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/cart/`;
export const payCart = async (): Promise<[UserAccount | null, string?]> => {
const payCart = async (): Promise<[UserAccount | null, string?]> => {
try {
const payCartResponse = await makeRequest<never, UserAccount>({
method: "POST",
url: `${API_URL}/cart/pay`,
url: `${API_URL}/pay`,
useToken: true,
});
@ -20,3 +20,41 @@ export const payCart = async (): Promise<[UserAccount | null, string?]> => {
return [null, `Не удалось оплатить товар из корзины. ${error}`];
}
};
const addCartItem = async (id: string): Promise<[unknown | null, string?]> => {
try {
const addedItem = await makeRequest<never, unknown>({
method: "PATCH",
url: `${API_URL}?id=${id}`,
});
return [addedItem];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось добавить товар в корзину. ${error}`];
}
};
const deleteCartItem = async (
id: string,
): Promise<[unknown | null, string?]> => {
try {
const deletedItem = await makeRequest<never, unknown>({
method: "DELETE",
url: `${API_URL}?id=${id}`,
});
return [deletedItem];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось удалить товар из корзины. ${error}`];
}
};
export const cartApi = {
pay: payCart,
add: addCartItem,
delete: deleteCartItem,
};

@ -2,7 +2,7 @@ import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
const API_URL = process.env.REACT_APP_DOMAIN + "/feedback";
const API_URL = `${process.env.REACT_APP_DOMAIN}/feedback`;
type SendContactFormBody = {
contact: string;

@ -3,7 +3,7 @@ import { parseAxiosError } from "@utils/parse-error";
import type { Discount } from "@model/discounts";
const API_URL = process.env.REACT_APP_DOMAIN + "/price/discount";
const API_URL = `${process.env.REACT_APP_DOMAIN}/price/discount`;
export const getDiscounts = async (
userId: string,

@ -5,23 +5,26 @@ import { parseAxiosError } from "@utils/parse-error";
type ActivatePromocodeRequest = { codeword: string } | { fastLink: string };
type ActivatePromocodeResponse = { greetings: string };
const API_URL = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
const API_URL = `${process.env.REACT_APP_DOMAIN}/codeword/promocode`;
export const activatePromocode = async (promocode: string) => {
export const activatePromocode = async (
promocode: string,
): Promise<[string | null, string?]> => {
try {
const response = await makeRequest<
ActivatePromocodeRequest,
ActivatePromocodeResponse
>({
method: "POST",
url: API_URL + "/activate",
url: `${API_URL}/activate`,
body: { codeword: promocode },
contentType: true,
});
return response.greetings;
return [response.greetings];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
throw new Error(error);
return [null, `Ошибка при активации промокода. ${error}`];
}
};

@ -20,7 +20,7 @@ import {
import { replaceSpacesToEmptyLines } from "../utils/replaceSpacesToEmptyLines";
import { parseAxiosError } from "@utils/parse-error";
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz";
const API_URL = "/squiz";
export const createQuestion = async (
body: CreateQuestionRequest,

@ -13,8 +13,8 @@ type AddedQuizImagesResponse = {
[key: string]: string;
};
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz";
const IMAGES_URL = process.env.REACT_APP_DOMAIN + "/squizstorer";
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
const IMAGES_URL = `${process.env.REACT_APP_DOMAIN}/squizstorer`;
export const createQuiz = async (
body?: Partial<CreateQuizRequest>,

@ -1,6 +1,8 @@
import { makeRequest } from "@api/makeRequest";
import { RawResult } from "@model/result/result";
import { parseAxiosError } from "@utils/parse-error";
interface IResultListBody {
to: number;
from: string;
@ -29,46 +31,102 @@ export interface IAnswerResult {
question_id: number;
}
const API_URL = process.env.REACT_APP_DOMAIN + `/squiz`;
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
const getResultList = async (quizId: number, page: number, body: any) => {
return makeRequest<IResultListBody, RawResult>({
method: "POST",
url: `${API_URL}/results/getResults/${quizId}`,
body: { page: page, limit: 10, ...body },
});
const getResultList = async (
quizId: number,
page: number,
body: any,
): Promise<[RawResult | null, string?]> => {
try {
const resultList = await makeRequest<IResultListBody, RawResult>({
method: "POST",
url: `${API_URL}/results/getResults/${quizId}`,
body: { page: page, limit: 10, ...body },
});
return [resultList];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить результат. ${error}`];
}
};
const deleteResult = async (resultId: number) => {
return makeRequest<unknown, unknown>({
method: "DELETE",
url: `${API_URL}/results/delete/${resultId}`,
body: {},
});
const deleteResult = async (
resultId: number,
): Promise<[unknown | null, string?]> => {
try {
const deletedResult = await makeRequest<unknown, unknown>({
method: "DELETE",
url: `${API_URL}/results/delete/${resultId}`,
body: {},
});
return [deletedResult];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось удалить результат. ${error}`];
}
};
const obsolescenceResult = (idResultArray: number[]) => {
return makeRequest<unknown, unknown>({
method: "PATCH",
url: `${API_URL}/result/seen`,
body: { answers: idResultArray },
});
const obsolescenceResult = async (
idResultArray: number[],
): Promise<[unknown | null, string?]> => {
try {
const obsolescencedResult = await makeRequest<unknown, unknown>({
method: "PATCH",
url: `${API_URL}/result/seen`,
body: { answers: idResultArray },
});
return [obsolescencedResult];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось изменить результат. ${error}`];
}
};
const getAnswerResultList = (resultId: number) => {
return makeRequest<unknown, IAnswerResult[]>({
method: "GET",
url: `${API_URL}/result/${resultId}`,
});
const getAnswerResultList = async (
resultId: number,
): Promise<[IAnswerResult[] | null, string?]> => {
try {
const answerResultList = await makeRequest<unknown, IAnswerResult[]>({
method: "GET",
url: `${API_URL}/result/${resultId}`,
});
return [answerResultList];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список результатов. ${error}`];
}
};
const AnswerResultListEx = (quizId: number, body: any) => {
return makeRequest<unknown, unknown>({
method: "POST",
url: `${API_URL}/results/${quizId}/export`,
body: body,
responseType: "blob",
});
const AnswerResultListEx = async (
quizId: number,
body: any,
): Promise<[unknown | null, string?]> => {
try {
const answerResultListEx = await makeRequest<unknown, unknown>({
method: "POST",
url: `${API_URL}/results/${quizId}/export`,
body: body,
responseType: "blob",
});
return [answerResultListEx];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [
null,
`Не удалось получить список устаревших результатов. ${error}`,
];
}
};
export const resultApi = {

@ -27,7 +27,7 @@ type TRequest = {
from: number;
};
const API_URL = process.env.REACT_APP_DOMAIN + "/squiz/statistic";
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/statistic`;
export const getDevices = async (
quizId: string,

23
src/api/tariff.ts Normal file

@ -0,0 +1,23 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
import type { GetTariffsResponse } from "@frontend/kitui";
const API_URL = `${process.env.REACT_APP_DOMAIN}/strator/tariff`;
export const getTariffs = async (
page: number,
): Promise<[GetTariffsResponse | null, string?]> => {
try {
const tariffs = await makeRequest<never, GetTariffsResponse>({
method: "GET",
url: `${API_URL}?page=${page}&limit=100`,
});
return [tariffs];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка при получении списка тарифов. ${error}`];
}
};

@ -1,9 +1,12 @@
import { makeRequest } from "@api/makeRequest";
import { createTicket as createTicketRequest } from "@frontend/kitui";
import { parseAxiosError } from "../utils/parse-error";
import { SendTicketMessageRequest } from "@frontend/kitui";
const API_URL = process.env.REACT_APP_DOMAIN + "/heruvym";
import type { CreateTicketResponse } from "@frontend/kitui";
const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym`;
export const sendTicketMessage = async (
ticketId: string,
@ -44,3 +47,46 @@ export const shownMessage = async (id: string): Promise<[null, string?]> => {
return [null, `Не удалось прочесть сообщение. ${error}`];
}
};
export const sendFile = async (
ticketId: string,
file: File,
): Promise<[unknown | null, string?]> => {
try {
const body = new FormData();
body.append(file.name, file);
body.append("ticket", ticketId);
const sendResponse = await makeRequest<FormData, unknown>({
method: "POST",
url: `${process.env.REACT_APP_DOMAIN}/heruvym/sendFiles`,
body,
});
return [sendResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось отправить файл. ${error}`];
}
};
export const createTicket = async (
message: string,
useToken: boolean,
): Promise<[CreateTicketResponse | null, string?]> => {
try {
const createdTicket = await createTicketRequest({
url: `${process.env.REACT_APP_DOMAIN}/heruvym/create`,
body: { Title: "Unauth title", Message: message },
useToken,
});
return [createdTicket];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось создать тикет. ${error}`];
}
};

62
src/api/user.ts Normal file

@ -0,0 +1,62 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
import type { UserAccount } from "@frontend/kitui";
import type { OriginalUserAccount } from "@root/user";
export const getUser = async (): Promise<[UserAccount | null, string?]> => {
try {
const user = await makeRequest<never, UserAccount>({
method: "GET",
url: `${process.env.REACT_APP_DOMAIN}/customer/account`,
});
return [user];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить пользователя. ${error}`];
}
};
export const getAccount = async (): Promise<
[OriginalUserAccount | null, string?]
> => {
try {
const controller = new AbortController();
const account = await makeRequest<never, OriginalUserAccount>({
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
});
return [account];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить данные аккаунта. ${error}`];
}
};
export const recoverUser = async (
password: string,
): Promise<[unknown | null, string?]> => {
try {
const recoverResponse = await makeRequest<{ password: string }, unknown>({
url: `${process.env.REACT_APP_DOMAIN}/user`,
method: "PATCH",
body: { password },
});
return [recoverResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось восстановить пароль. ${error}`];
}
};

@ -19,9 +19,10 @@ import { useQuizStore } from "@root/quizes/store";
import { useQuestionsStore } from "@root/questions/store";
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
import type { AxiosError } from "axios";
import { DeleteModal } from "./DeleteModal";
import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
interface CardAnswerProps {
isNew: boolean;
idResult: number;
@ -50,29 +51,31 @@ export const CardAnswer: FC<CardAnswerProps> = ({
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [resultsAnswer, setResultsAnswer] = useState<IAnswerResult[]>([]);
const [questionsResultState, setQuestionsResultState] = useState([]);
const [questionsResultState, setQuestionsResultState] = useState<
AnyTypedQuizQuestion[]
>([]);
const { editQuizId } = useQuizStore();
const { questions } = useQuestionsStore();
const openResults = async () => {
setIsOpen(!isOpen);
if (!isOpen) {
try {
const resAnswer = await resultApi.getAnswerList(idResult);
const resAnswerOnly = resAnswer.filter((res) => res.Result !== true);
const resQuiz = resAnswer.filter((res) => res.Result === true);
setResultsAnswer(resAnswerOnly);
const idResults = resQuiz[0].question_id;
const questionsResult = questions.filter(
(q) => q.backendId === idResults,
);
setQuestionsResultState(questionsResult);
} catch (nativeError) {
const error = nativeError as AxiosError;
const [resAnswer, answerError] = await resultApi.getAnswerList(idResult);
if (error.response?.statusText === "Payment Required") {
openPrePaymentModal();
}
if (answerError || !resAnswer) {
openPrePaymentModal();
return;
}
const resAnswerOnly = resAnswer.filter((res) => res.Result !== true);
const resQuiz = resAnswer.filter((res) => res.Result === true);
setResultsAnswer(resAnswerOnly);
const idResults = resQuiz[0].question_id;
const questionsResult = questions.filter(
(q) => q.type && q.backendId === idResults,
) as AnyTypedQuizQuestion[];
setQuestionsResultState(questionsResult);
}
};

@ -78,17 +78,20 @@ export const QuizAnswersPage: FC = () => {
const PaginationHC = async (e: ChangeEvent<unknown>, value: number) => {
setPage(value);
try {
if (editQuizId !== null) {
const result = await resultApi.getList(
editQuizId,
value - 1,
parseFilters(filterNew, filterDate),
);
setResults(result);
if (editQuizId !== null) {
const [result, resultError] = await resultApi.getList(
editQuizId,
value - 1,
parseFilters(filterNew, filterDate),
);
if (resultError || !result) {
console.error("An error occurred while receiving data: ", resultError);
return;
}
} catch (error) {
console.error("An error occurred while receiving data: ", error);
setResults(result);
}
};

@ -98,11 +98,18 @@ export const QuizSettingsMenu: FC<QuizSettingsMenuProps> = ({
}}
onclickUpdate={async () => {
if (editQuizId !== null) {
const result = await resultApi.getList(
const [result, resultError] = await resultApi.getList(
editQuizId,
page - 1,
parseFilters(filterNew, filterDate),
);
if (resultError || !result) {
console.error(resultError);
return;
}
setResults(result);
} else {
console.error("editQuizId is null");

@ -37,11 +37,18 @@ export const useGetData = (filterNew: string, filterDate: string): void => {
setQuestions(questions);
const result = await resultApi.getList(
const [result, resultError] = await resultApi.getList(
editQuizId,
0,
parseFilters(filterNew, filterDate),
);
if (resultError || !result) {
console.error(resultError);
return;
}
if (result.total_count === 0) {
console.error("No results found");
}

@ -33,6 +33,9 @@ import { currencyFormatter } from "./tariffsUtils/currencyFormatter";
import { useWallet, setCash } from "@root/cash";
import { handleLogoutClick } from "@utils/HandleLogoutClick";
import { getDiscounts } from "@api/discounts";
import { cartApi } from "@api/cart";
import { getUser } from "@api/user";
import { getTariffs } from "@api/tariff";
import type { Discount } from "@model/discounts";
@ -62,25 +65,20 @@ function TariffPage() {
const getTariffsList = async (): Promise<Tariff[]> => {
const tariffsList: Tariff[] = [];
const { tariffs, totalPages } = await makeRequest<
never,
GetTariffsResponse
>({
method: "GET",
url: process.env.REACT_APP_DOMAIN + "/strator/tariff?page=1&limit=100",
});
const [tariffsResponse, tariffsResponseError] = await getTariffs(1);
tariffsList.push(...tariffs);
if (tariffsResponseError || !tariffsResponse) {
return tariffsList;
}
for (let page = 2; page <= totalPages; page += 1) {
const tariffsResult = await makeRequest<never, GetTariffsResponse>({
method: "GET",
url:
process.env.REACT_APP_DOMAIN +
`/strator/tariff?page=${page}&limit=100`,
});
tariffsList.push(...tariffsResponse.tariffs);
tariffsList.push(...tariffsResult.tariffs);
for (let page = 2; page <= tariffsResponse.totalPages; page += 1) {
const [tariffsResult] = await getTariffs(1);
if (tariffsResult) {
tariffsList.push(...tariffsResult.tariffs);
}
}
return tariffsList;
@ -88,10 +86,12 @@ function TariffPage() {
useEffect(() => {
const get = async () => {
const user = await makeRequest({
method: "GET",
url: process.env.REACT_APP_DOMAIN + "/customer/account",
});
const [user, userError] = await getUser();
if (userError) {
return;
}
const tariffsList = await getTariffsList();
if (userId) {
@ -123,27 +123,22 @@ function TariffPage() {
outCart(user.cart);
}
//Добавляем желаемый тариф в корзину
await makeRequest({
method: "PATCH",
url: process.env.REACT_APP_DOMAIN + `/customer/cart?id=${id}`,
});
//Если нам хватает денежек - покупаем тариф
const [_, addError] = await cartApi.add(id);
try {
const data = await makeRequest({
method: "POST",
url: process.env.REACT_APP_DOMAIN + "/customer/cart/pay",
});
setCash(
currencyFormatter.format(Number(data.wallet.cash) / 100),
Number(data.wallet.cash),
Number(data.wallet.cash) / 100,
);
enqueueSnackbar("Тариф успешно приобретён");
} catch (e) {
if (addError) {
//Развращаем товары в корзину
inCart();
return;
}
//Если нам хватает денежек - покупаем тариф
const [data, payError] = await cartApi.pay();
if (payError || !data) {
//если денег не хватило
if (e.response.data.message.includes("insufficient funds")) {
let cashDif = Number(e.response.data.message.split(":")[1]);
if (addError) {
let cashDif = Number(addError.split(":")[1]);
var link = document.createElement("a");
link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=${cashDif}&data=${token}&userid=${userId}`;
document.body.appendChild(link);
@ -152,7 +147,18 @@ function TariffPage() {
}
//другая ошибка
enqueueSnackbar("Произошла ошибка. Попробуйте позже");
return;
}
setCash(
currencyFormatter.format(Number(data.wallet.cash) / 100),
Number(data.wallet.cash),
Number(data.wallet.cash) / 100,
);
enqueueSnackbar("Тариф успешно приобретён");
//Развращаем товары в корзину
inCart();
};
@ -179,31 +185,32 @@ function TariffPage() {
return tariff.privileges[0].privilegeId !== "squizHideBadge";
});
function handleApplyPromocode() {
async function handleApplyPromocode() {
if (!promocodeField) return;
activatePromocode(promocodeField)
.then(async (greetings) => {
enqueueSnackbar(greetings);
const [greetings, error] = await activatePromocode(promocodeField);
if (!userId) {
return;
}
if (error) {
enqueueSnackbar(error);
const [discounts, discountsError] = await getDiscounts(userId);
return;
}
if (discountsError) {
throw new Error(discountsError);
}
enqueueSnackbar(greetings);
if (discounts?.length) {
setDiscounts(discounts);
}
})
.catch((error) => {
if (error.message !== "" && typeof error.message === "string")
enqueueSnackbar(error.message);
});
if (!userId) {
return;
}
const [discounts, discountsError] = await getDiscounts(userId);
if (discountsError) {
throw new Error(discountsError);
}
if (discounts?.length) {
setDiscounts(discounts);
}
}
return (
@ -396,19 +403,18 @@ export const inCart = () => {
let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]");
if (Array.isArray(saveCart)) {
saveCart.forEach(async (id: string) => {
try {
await makeRequest({
method: "PATCH",
url: process.env.REACT_APP_DOMAIN + `/customer/cart?id=${id}`,
});
const [_, addError] = await cartApi.add(id);
if (addError) {
console.error(addError);
} else {
let index = saveCart.indexOf("green");
if (index !== -1) {
saveCart.splice(index, 1);
}
localStorage.setItem("saveCart", JSON.stringify(saveCart));
} catch (e) {
console.error("Я не смог добавить тариф в корзину :( " + id);
}
});
} else {
@ -418,16 +424,16 @@ export const inCart = () => {
const outCart = (cart: string[]) => {
//Сделаем муторно и подольше, зато при прерывании сессии данные потеряются минимально
cart.forEach(async (id: string) => {
try {
await makeRequest({
method: "DELETE",
url: process.env.REACT_APP_DOMAIN + `/customer/cart?id=${id}`,
});
let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]");
saveCart = saveCart.push(id);
localStorage.setItem("saveCart", JSON.stringify(saveCart));
} catch (e) {
console.error("Я не смог удалить из корзины тариф :(");
const [_, deleteError] = await cartApi.delete(id);
if (deleteError) {
console.error(deleteError);
return;
}
let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]");
saveCart = saveCart.push(id);
localStorage.setItem("saveCart", JSON.stringify(saveCart));
});
};

@ -21,6 +21,7 @@ import { useUserStore } from "@root/user";
import { makeRequest } from "@api/makeRequest";
import { setAuthToken } from "@frontend/kitui";
import { parseAxiosError } from "@utils/parse-error";
import { recoverUser } from "@api/user";
interface Values {
password: string;
}
@ -55,21 +56,17 @@ export default function RecoverPassword() {
onSubmit: async (values, formikHelpers) => {
if (tokenUser) {
setAuthToken(tokenUser || "");
try {
const response = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/user/",
method: "PATCH",
body: { password: values.password },
});
const [_, recoverError] = await recoverUser(values.password);
if (recoverError) {
setAuthToken("");
enqueueSnackbar(
`Извините, произошла ошибка, попробуйте повторить позже. ${recoverError}`,
);
} else {
setIsDialogOpen(false);
navigate("/");
enqueueSnackbar("Пароль успешно сменён");
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
setAuthToken("");
enqueueSnackbar(
`Извините, произошла ошибка, попробуйте повторить позже. ${error}`,
);
}
} else {
enqueueSnackbar("Неверный url-адрес");

@ -7,7 +7,7 @@ import {
} from "@root/user";
import { clearAuthToken, getMessageFromFetchError } from "@frontend/kitui";
import { enqueueSnackbar } from "notistack";
import { useUserAccountFetcher } from "../../App";
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
import { useNavigate } from "react-router-dom";
import moment from "moment";
@ -17,7 +17,7 @@ export default function AvailablePrivilege() {
const userId = useUserStore((state) => state.userId);
const navigate = useNavigate();
useUserAccountFetcher<OriginalUserAccount>({
url: process.env.REACT_APP_DOMAIN + "/squiz/account/get",
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {

@ -20,6 +20,7 @@ import { enqueueSnackbar } from "notistack";
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
import CopyIcon from "@icons/CopyIcon";
import ChartIcon from "@icons/ChartIcon";
import { cartApi } from "@api/cart";
interface Props {
quiz: Quiz;
@ -55,17 +56,17 @@ export default function QuizCard({
useLayoutEffect(() => {
const pay = async () => {
try {
await makeRequest({
method: "POST",
url: process.env.REACT_APP_DOMAIN + "/customer/cart/pay",
});
inCart();
} catch (e) {
const [_, payError] = await cartApi.pay();
if (payError) {
enqueueSnackbar(
"Попробуйте снова купить тариф после зачисления средств",
);
return;
}
inCart();
};
const params = new URLSearchParams(window.location.search);
const fromSquiz = params.get("action");

@ -129,7 +129,7 @@ export const quizStore = create<QuizStore>()(
config: {
noStartPage: false,
type: "", // quiz или form
logo: process.env.REACT_APP_DOMAIN + "/img/logo",
logo: `${process.env.REACT_APP_DOMAIN}/img/logo`,
startpage: {
description: "", // приветственный текст опроса
button: "", // текст на кнопке начала опроса

@ -39,15 +39,16 @@ export const deleteResult = async (resultId: number) =>
.results.find((r) => r.id === resultId);
if (!result) return;
try {
await resultApi.delete(Number(result.id));
removeResult(resultId);
} catch (error) {
devlog("Error delete result", error);
const [_, deleteError] = await resultApi.delete(Number(result.id));
const message = getMessageFromFetchError(error) ?? "";
enqueueSnackbar(`Не удалось удалить результат. ${message}`);
if (deleteError) {
devlog("Error delete result", deleteError);
enqueueSnackbar(deleteError);
return;
}
removeResult(resultId);
});
export const obsolescenceResult = async (
@ -68,17 +69,24 @@ export const obsolescenceResult = async (
if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer);
lossDebouncer = setTimeout(async () => {
//стреляем на лишение новизны
try {
await resultApi.obsolescence(lossId);
lossId = [];
} catch (error) {
devlog("Error", error);
const [_, obsolescenceError] = await resultApi.obsolescence(lossId);
const message = getMessageFromFetchError(error) ?? "";
enqueueSnackbar(`Ошибка. ${message}`);
if (obsolescenceError) {
devlog("Error", obsolescenceError);
enqueueSnackbar(obsolescenceError);
return;
}
lossId = [];
}, 3000);
const resultList = await resultApi.getList(editQuizId);
const [resultList, resultError] = await resultApi.getList(editQuizId);
if (resultError || !resultList) {
return;
}
setResults(resultList);
},
);
@ -90,30 +98,28 @@ export const ExportResults = async (
openPrePaymentModal: () => void,
editQuizId: number,
) => {
try {
const data = await resultApi.export(
editQuizId,
parseFilters(filterNew, filterDate),
);
const [data, resultError] = await resultApi.export(
editQuizId,
parseFilters(filterNew, filterDate),
);
console.log(typeof data);
if (resultError) {
openPrePaymentModal();
const blob = new Blob([data as BlobPart], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8",
});
const link = document.createElement("a");
link.href = window.URL.createObjectURL(data as Blob);
console.log(link);
link.download = `report_${new Date().getTime()}.xlsx`;
link.click();
} catch (nativeError) {
const error = nativeError as AxiosError;
if (error.response?.statusText === "Payment Required") {
openPrePaymentModal();
}
return;
}
console.log(typeof data);
const blob = new Blob([data as BlobPart], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8",
});
const link = document.createElement("a");
link.href = window.URL.createObjectURL(data as Blob);
console.log(link);
link.download = `report_${new Date().getTime()}.xlsx`;
link.click();
};
function setProducedState<A extends string | { type: string }>(

@ -13,7 +13,7 @@ import {
useUserStore,
} from "@root/user";
import { parseAxiosError } from "@utils/parse-error";
import { useUserAccountFetcher } from "../App";
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
import type { Discount } from "@model/discounts";
import {
clearAuthToken,
@ -23,6 +23,8 @@ import {
} from "@frontend/kitui";
import { useNavigate } from "react-router-dom";
import { isAxiosError } from "axios";
import { activatePromocode } from "@api/promocode";
import { getAccount } from "@api/user";
export function CheckFastlink() {
const user = useUserStore();
@ -50,40 +52,28 @@ export function CheckFastlink() {
const fetchPromocode = async () => {
if (promocode.length > 0) {
try {
const response = await makeRequest<
{ codeword: string } | { fastLink: string },
{ greetings: string }
>({
url:
process.env.REACT_APP_DOMAIN + "/codeword/promocode" + "/activate",
method: "POST",
contentType: true,
body: { fastLink: promocode },
});
enqueueSnackbar(
response.greetings !== ""
? response.greetings
: "Промокод успешно активирован",
);
localStorage.setItem("fl", "");
const controller = new AbortController();
const responseAccount = await makeRequest<never, any>({
url: process.env.REACT_APP_DOMAIN + "/squiz/account/get",
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
});
setUserAccount(responseAccount);
mutate("discounts");
return response.greetings;
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
enqueueSnackbar(error);
const [greetings, activationError] = await activatePromocode(promocode);
if (activationError || !greetings) {
enqueueSnackbar(activationError);
localStorage.setItem("fl", "");
return;
}
enqueueSnackbar(
greetings !== "" ? greetings : "Промокод успешно активирован",
);
localStorage.setItem("fl", "");
const [responseAccount, accountError] = await getAccount();
if (accountError || !responseAccount) {
return;
}
setUserAccount(responseAccount);
mutate("discounts");
return greetings;
}
};

@ -1,5 +1,4 @@
import {
createTicket,
TicketMessage,
useSSESubscription,
useTicketMessages,
@ -23,6 +22,7 @@ import {
} from "@root/ticket";
import { enqueueSnackbar } from "notistack";
import { getMessageFromFetchError } from "@utils/backendMessageHandler";
import { createTicket, sendFile as sendFileRequest } from "@api/ticket";
type ModalWarningType =
| "errorType"
@ -100,7 +100,7 @@ export default () => {
}, [isChatOpened]);
useTicketsFetcher({
url: process.env.REACT_APP_DOMAIN + "/heruvym/getTickets",
url: `${process.env.REACT_APP_DOMAIN}/heruvym/getTickets`,
ticketsPerPage: 10,
ticketApiPage: 0,
onSuccess: (result) => {
@ -128,7 +128,7 @@ export default () => {
});
useTicketMessages({
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
url: `${process.env.REACT_APP_DOMAIN}/heruvym/getMessages`,
isUnauth: true,
ticketId: ticket.sessionData?.ticketId,
messagesPerPage: ticket.messagesPerPage,
@ -146,9 +146,7 @@ export default () => {
useSSESubscription<TicketMessage>({
enabled:
sseEnabled && isActiveSSETab && Boolean(ticket.sessionData?.sessionId),
url:
process.env.REACT_APP_DOMAIN +
`/heruvym/ticket?ticket=${ticket.sessionData?.ticketId}&s=${ticket.sessionData?.sessionId}`,
url: `${process.env.REACT_APP_DOMAIN}/heruvym/ticket?ticket=${ticket.sessionData?.ticketId}&s=${ticket.sessionData?.sessionId}`,
onNewData: (ticketMessages) => {
const isTicketClosed = ticketMessages.some(
(message) => message.session_id === "close",
@ -195,25 +193,21 @@ export default () => {
let successful = false;
setIsMessageSending(true);
if (!ticket.sessionData?.ticketId) {
try {
const data = await createTicket({
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
body: {
Title: "Unauth title",
Message: messageField,
},
useToken: Boolean(user),
});
successful = true;
setTicketData({
ticketId: data.Ticket,
sessionId: data.sess,
});
} catch (error: any) {
const [data, createError] = await createTicket(
messageField,
Boolean(user),
);
if (createError || !data) {
successful = false;
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
enqueueSnackbar(createError);
} else {
successful = true;
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
}
setIsMessageSending(false);
} else {
const [_, sendTicketMessageError] = await sendTicketMessage(
@ -234,45 +228,29 @@ export default () => {
const sendFile = async (file: File) => {
if (file === undefined) return true;
let data;
let ticketId = ticket.sessionData?.ticketId;
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,
});
} catch (error: any) {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
const [data, createError] = await createTicket("", Boolean(user));
ticketId = data?.Ticket;
if (createError || !data) {
enqueueSnackbar(createError);
} else {
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
}
setIsMessageSending(false);
}
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;
}
};

@ -2,7 +2,7 @@ import { useNavigate } from "react-router-dom";
import { setCash } from "@root/cash";
import { useUserStore } from "@root/user";
import { payCart } from "@api/cart";
import { cartApi } from "@api/cart";
import { currencyFormatter } from "../../pages/Tariffs/tariffsUtils/currencyFormatter";
const MINUTE = 1000 * 60;
@ -36,7 +36,7 @@ export const useAfterpay = () => {
async function tryPayCart() {
tryCount += 1;
const [data, payCartError] = await payCart();
const [data, payCartError] = await cartApi.pay();
if (data !== null)
setCash(
currencyFormatter.format(Number(data.wallet.cash) / 100),

@ -0,0 +1,59 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { createUserAccount, devlog } from "@frontend/kitui";
import { isAxiosError } from "axios";
import { makeRequest } from "@api/makeRequest";
import type { UserAccount } from "@frontend/kitui";
export const useUserAccountFetcher = <T = UserAccount>({
onError,
onNewUserAccount,
url,
userId,
}: {
url: string;
userId: string | null;
onNewUserAccount: (response: T) => void;
onError?: (error: any) => void;
}) => {
const onNewUserAccountRef = useRef(onNewUserAccount);
const onErrorRef = useRef(onError);
useLayoutEffect(() => {
onNewUserAccountRef.current = onNewUserAccount;
onErrorRef.current = onError;
}, [onError, onNewUserAccount]);
useEffect(() => {
if (!userId) return;
const controller = new AbortController();
makeRequest<never, T>({
url,
contentType: true,
method: "GET",
useToken: true,
withCredentials: false,
signal: controller.signal,
})
.then((result) => {
devlog("User account", result);
onNewUserAccountRef.current(result);
})
.catch((error) => {
devlog("Error fetching user account", error);
if (isAxiosError(error) && error.response?.status === 404) {
createUserAccount(controller.signal, url.replace("get", "create"))
.then((result) => {
devlog("Created user account", result);
onNewUserAccountRef.current(result as T);
})
.catch((error) => {
devlog("Error creating user account", error);
onErrorRef.current?.(error);
});
} else {
onErrorRef.current?.(error);
}
});
return () => controller.abort();
}, [url, userId]);
};