diff --git a/src/App.tsx b/src/App.tsx
index 72daabc1..6ae36c59 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -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) => (
>}>{children}
);
-export function useUserAccountFetcher({
- 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({
- 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({
- 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({
- url: process.env.REACT_APP_DOMAIN + "/squiz/account/get",
+ url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {
diff --git a/src/api/auth.ts b/src/api/auth.ts
index 67b19907..194ece1b 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -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({
- url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
+ url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
body: formData,
useToken: false,
withCredentials: true,
diff --git a/src/api/cart.ts b/src/api/cart.ts
index 80d46f27..257a5035 100644
--- a/src/api/cart.ts
+++ b/src/api/cart.ts
@@ -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({
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({
+ 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({
+ 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,
+};
diff --git a/src/api/contactForm.ts b/src/api/contactForm.ts
index 00ac2b77..6ed0f71d 100644
--- a/src/api/contactForm.ts
+++ b/src/api/contactForm.ts
@@ -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;
diff --git a/src/api/discounts.ts b/src/api/discounts.ts
index 2cd1fdf1..c025ec7f 100644
--- a/src/api/discounts.ts
+++ b/src/api/discounts.ts
@@ -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,
diff --git a/src/api/promocode.ts b/src/api/promocode.ts
index 2da8fd5b..87e90a01 100644
--- a/src/api/promocode.ts
+++ b/src/api/promocode.ts
@@ -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}`];
}
};
diff --git a/src/api/question.ts b/src/api/question.ts
index fb5fecf1..7a62485f 100644
--- a/src/api/question.ts
+++ b/src/api/question.ts
@@ -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,
diff --git a/src/api/quiz.ts b/src/api/quiz.ts
index f4dc5637..476563ff 100644
--- a/src/api/quiz.ts
+++ b/src/api/quiz.ts
@@ -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,
diff --git a/src/api/result.ts b/src/api/result.ts
index edada028..bb176219 100644
--- a/src/api/result.ts
+++ b/src/api/result.ts
@@ -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({
- 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({
+ 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({
- method: "DELETE",
- url: `${API_URL}/results/delete/${resultId}`,
- body: {},
- });
+const deleteResult = async (
+ resultId: number,
+): Promise<[unknown | null, string?]> => {
+ try {
+ const deletedResult = await makeRequest({
+ 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({
- method: "PATCH",
- url: `${API_URL}/result/seen`,
- body: { answers: idResultArray },
- });
+const obsolescenceResult = async (
+ idResultArray: number[],
+): Promise<[unknown | null, string?]> => {
+ try {
+ const obsolescencedResult = await makeRequest({
+ 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({
- method: "GET",
- url: `${API_URL}/result/${resultId}`,
- });
+const getAnswerResultList = async (
+ resultId: number,
+): Promise<[IAnswerResult[] | null, string?]> => {
+ try {
+ const answerResultList = await makeRequest({
+ 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({
- 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({
+ 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 = {
diff --git a/src/api/statistic.ts b/src/api/statistic.ts
index 110d637f..b085b64b 100644
--- a/src/api/statistic.ts
+++ b/src/api/statistic.ts
@@ -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,
diff --git a/src/api/tariff.ts b/src/api/tariff.ts
new file mode 100644
index 00000000..0ddaceee
--- /dev/null
+++ b/src/api/tariff.ts
@@ -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({
+ method: "GET",
+ url: `${API_URL}?page=${page}&limit=100`,
+ });
+ return [tariffs];
+ } catch (nativeError) {
+ const [error] = parseAxiosError(nativeError);
+
+ return [null, `Ошибка при получении списка тарифов. ${error}`];
+ }
+};
diff --git a/src/api/ticket.ts b/src/api/ticket.ts
index bdbe2f83..eb681604 100644
--- a/src/api/ticket.ts
+++ b/src/api/ticket.ts
@@ -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({
+ 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}`];
+ }
+};
diff --git a/src/api/user.ts b/src/api/user.ts
new file mode 100644
index 00000000..f7bf30d2
--- /dev/null
+++ b/src/api/user.ts
@@ -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({
+ 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({
+ 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}`];
+ }
+};
diff --git a/src/pages/QuizAnswersPage/CardAnswer.tsx b/src/pages/QuizAnswersPage/CardAnswer.tsx
index d232bb9a..00df02d0 100644
--- a/src/pages/QuizAnswersPage/CardAnswer.tsx
+++ b/src/pages/QuizAnswersPage/CardAnswer.tsx
@@ -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 = ({
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [resultsAnswer, setResultsAnswer] = useState([]);
- 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);
}
};
diff --git a/src/pages/QuizAnswersPage/QuizAnswersPage.tsx b/src/pages/QuizAnswersPage/QuizAnswersPage.tsx
index c2895ea5..8355218c 100644
--- a/src/pages/QuizAnswersPage/QuizAnswersPage.tsx
+++ b/src/pages/QuizAnswersPage/QuizAnswersPage.tsx
@@ -78,17 +78,20 @@ export const QuizAnswersPage: FC = () => {
const PaginationHC = async (e: ChangeEvent, 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);
}
};
diff --git a/src/pages/QuizAnswersPage/QuizSettingsMenu.tsx b/src/pages/QuizAnswersPage/QuizSettingsMenu.tsx
index d2167597..9499298a 100644
--- a/src/pages/QuizAnswersPage/QuizSettingsMenu.tsx
+++ b/src/pages/QuizAnswersPage/QuizSettingsMenu.tsx
@@ -98,11 +98,18 @@ export const QuizSettingsMenu: FC = ({
}}
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");
diff --git a/src/pages/QuizAnswersPage/useGetData.ts b/src/pages/QuizAnswersPage/useGetData.ts
index 4fbbcfac..184fe500 100644
--- a/src/pages/QuizAnswersPage/useGetData.ts
+++ b/src/pages/QuizAnswersPage/useGetData.ts
@@ -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");
}
diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx
index b5676b2d..44dc051c 100644
--- a/src/pages/Tariffs/Tariffs.tsx
+++ b/src/pages/Tariffs/Tariffs.tsx
@@ -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 => {
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({
- 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));
});
};
diff --git a/src/pages/auth/RecoverPassword.tsx b/src/pages/auth/RecoverPassword.tsx
index 4ee52f27..8afd81ca 100644
--- a/src/pages/auth/RecoverPassword.tsx
+++ b/src/pages/auth/RecoverPassword.tsx
@@ -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({
- 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-адрес");
diff --git a/src/pages/createQuize/AvailablePrivilege.tsx b/src/pages/createQuize/AvailablePrivilege.tsx
index abcdd9e0..c1a0e905 100644
--- a/src/pages/createQuize/AvailablePrivilege.tsx
+++ b/src/pages/createQuize/AvailablePrivilege.tsx
@@ -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({
- url: process.env.REACT_APP_DOMAIN + "/squiz/account/get",
+ url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {
diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx
index dfd083cf..ff114d46 100755
--- a/src/pages/createQuize/QuizCard.tsx
+++ b/src/pages/createQuize/QuizCard.tsx
@@ -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");
diff --git a/src/stores/quizes.ts b/src/stores/quizes.ts
index 1f481f58..cdee3acc 100644
--- a/src/stores/quizes.ts
+++ b/src/stores/quizes.ts
@@ -129,7 +129,7 @@ export const quizStore = create()(
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: "", // текст на кнопке начала опроса
diff --git a/src/stores/results/actions.ts b/src/stores/results/actions.ts
index d1d09ef9..c88a2984 100644
--- a/src/stores/results/actions.ts
+++ b/src/stores/results/actions.ts
@@ -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(
diff --git a/src/ui_kit/CheckFastlink.tsx b/src/ui_kit/CheckFastlink.tsx
index f646cba9..65c0a820 100644
--- a/src/ui_kit/CheckFastlink.tsx
+++ b/src/ui_kit/CheckFastlink.tsx
@@ -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({
- 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;
}
};
diff --git a/src/ui_kit/FloatingSupportChat/index.tsx b/src/ui_kit/FloatingSupportChat/index.tsx
index 5e1bc3ec..3a93efbc 100644
--- a/src/ui_kit/FloatingSupportChat/index.tsx
+++ b/src/ui_kit/FloatingSupportChat/index.tsx
@@ -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({
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;
}
};
diff --git a/src/utils/hooks/useAfterpay.ts b/src/utils/hooks/useAfterpay.ts
index 9f500764..2a481619 100644
--- a/src/utils/hooks/useAfterpay.ts
+++ b/src/utils/hooks/useAfterpay.ts
@@ -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),
diff --git a/src/utils/hooks/useUserAccountFetcher.ts b/src/utils/hooks/useUserAccountFetcher.ts
new file mode 100644
index 00000000..7464e073
--- /dev/null
+++ b/src/utils/hooks/useUserAccountFetcher.ts
@@ -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 = ({
+ 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({
+ 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]);
+};