Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squiz!326
This commit is contained in:
Nastya 2024-05-25 19:15:52 +00:00
commit 5bc8bb3d9b
221 changed files with 4464 additions and 2635 deletions

@ -29,7 +29,6 @@
"cytoscape": "^3.26.0",
"cytoscape-popper": "^2.0.0",
"date-fns": "^3.0.6",
"dayjs": "^1.11.10",
"emoji-mart": "^5.5.2",
"file-saver": "^2.0.5",
"formik": "^2.4.5",

@ -1,12 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<head>
<meta charset="utf-8" />
<title>Pena Quiz</title>
<meta name="description" content="Веб-сервис с инструментами для повышения эффективности маркетологов." />
<meta name="keywords" content=" Экосистема маркетинговых инструментов,
<title>Pena Quiz</title>
<meta
name="description"
content="Веб-сервис с инструментами для повышения эффективности маркетологов."
/>
<meta
name="keywords"
content=" Экосистема маркетинговых инструментов,
Инструменты для социальных исследований,
Малый бизнес,
Маркетинговые инструменты,
@ -26,82 +30,140 @@
Анализ данных,
Улучшение результатов,
Увеличение прибыли,
Повышение конкурентоспособности " />
Повышение конкурентоспособности "
/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="any" /><!-- 32×32 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.png" /><!-- 180×180 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="any" />
<!-- 32×32 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.png" />
<!-- 180×180 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta http-equiv="Pragma" content="no-cache" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap" rel="stylesheet" />
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function (m, e, t, r, i, k, a) {
m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) };
m[i].l = 1 * new Date();
for (var j = 0; j < document.scripts.length; j++) { if (document.scripts[j].src === r) { return; } }
k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a)
})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta http-equiv="Pragma" content="no-cache" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function (m, e, t, r, i, k, a) {
m[i] =
m[i] ||
function () {
(m[i].a = m[i].a || []).push(arguments);
};
m[i].l = 1 * new Date();
for (var j = 0; j < document.scripts.length; j++) {
if (document.scripts[j].src === r) {
return;
}
}
(k = e.createElement(t)),
(a = e.getElementsByTagName(t)[0]),
(k.async = 1),
(k.src = r),
a.parentNode.insertBefore(k, a);
})(
window,
document,
"script",
"https://mc.yandex.ru/metrika/tag.js",
"ym",
);
const domain = location.hostname
if (domain === "quiz.pena.digital") {
ym(96979576, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
const domain = location.hostname;
if (domain === "quiz.pena.digital") {
ym(96979576, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
});
// <!-- Top.Mail.Ru counter -->
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({ id: "3513005", type: "pageView", start: (new Date()).getTime() });
(function (d, w, id) {
if (d.getElementById(id)) return;
var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
ts.src = "https://top-fwz1.mail.ru/js/code.js";
var f = function () { var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s); };
if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); }
})(document, window, "tmr-code");
// <!-- /Top.Mail.Ru counter -->
};
if (domain === "squiz.pena.digital") {
ym(96979625, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
};
if (domain === "penaquiz.online" || domain === "penaquiz.ru") {
ym(97241101, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
};
</script>
<noscript>
<div><img src="https://mc.yandex.ru/watch/96979576" style="position:absolute; left:-9999px;" alt="" /></div>
<div><img src="https://mc.yandex.ru/watch/96979625" style="position:absolute; left:-9999px;" alt="" /></div>
<div><img src="https://mc.yandex.ru/watch/97241101" style="position:absolute; left:-9999px;" alt="" /></div>
<div><img src="https://top-fwz1.mail.ru/counter?id=3513005;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" /></div>
</noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
// <!-- Top.Mail.Ru counter -->
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({
id: "3513005",
type: "pageView",
start: new Date().getTime(),
});
(function (d, w, id) {
if (d.getElementById(id)) return;
var ts = d.createElement("script");
ts.type = "text/javascript";
ts.async = true;
ts.id = id;
ts.src = "https://top-fwz1.mail.ru/js/code.js";
var f = function () {
var s = d.getElementsByTagName("script")[0];
s.parentNode.insertBefore(ts, s);
};
if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false);
} else {
f();
}
})(document, window, "tmr-code");
// <!-- /Top.Mail.Ru counter -->
}
if (domain === "squiz.pena.digital") {
ym(96979625, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
});
}
if (domain === "penaquiz.online" || domain === "penaquiz.ru") {
ym(97241101, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true,
});
}
</script>
<noscript>
<div>
<img
src="https://mc.yandex.ru/watch/96979576"
style="position: absolute; left: -9999px"
alt=""
/>
</div>
<div>
<img
src="https://mc.yandex.ru/watch/96979625"
style="position: absolute; left: -9999px"
alt=""
/>
</div>
<div>
<img
src="https://mc.yandex.ru/watch/97241101"
style="position: absolute; left: -9999px"
alt=""
/>
</div>
<div>
<img
src="https://top-fwz1.mail.ru/counter?id=3513005;js=na"
style="position: absolute; left: -9999px"
alt="Top.Mail.Ru"
/>
</div>
</noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

@ -1,9 +1,7 @@
import type { SuspenseProps } from "react";
import { lazy, Suspense, useEffect, useLayoutEffect, useRef } from "react";
import { lazy, Suspense } from "react";
import { lazily } from "react-lazily";
import ContactFormModal from "@ui_kit/ContactForm";
import dayjs from "dayjs";
import "dayjs/locale/ru";
import SigninDialog from "./pages/auth/Signin";
import SignupDialog from "./pages/auth/Signup";
import {
@ -18,13 +16,10 @@ import Landing from "./pages/Landing/Landing";
import Main from "./pages/main";
import {
clearAuthToken,
createUserAccount,
devlog,
getMessageFromFetchError,
UserAccount,
useUserFetcher,
} from "@frontend/kitui";
import makeRequest from "@api/makeRequest";
import type { OriginalUserAccount } from "@root/user";
import {
clearUserData,
@ -44,9 +39,11 @@ 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"));
const QuizGallery = lazy(() => import("./pages/createQuize/QuizGallery"));
const ViewPage = lazy(() => import("./pages/ViewPublicationPage"));
const Analytics = lazy(() => import("./pages/Analytics/Analytics"));
const EditPage = lazy(() => import("./pages/startPage/EditPage"));
@ -62,8 +59,6 @@ const ChatImageNewWindow = lazy(
() => import("@ui_kit/FloatingSupportChat/ChatImageNewWindow"),
);
dayjs.locale("ru");
const routeslink = [
{
path: "/edit",
@ -92,65 +87,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 +107,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 +122,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) => {
@ -259,6 +202,10 @@ export default function App() {
/>
}
/>
<Route
path="/gallery"
element={<LazyLoading children={<QuizGallery />} />}
/>
<Route
path="/list"
element={<LazyLoading children={<MyQuizzesFull />} />}

@ -1,4 +1,6 @@
import makeRequest from "@api/makeRequest";
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
import type {
LoginRequest,
@ -6,21 +8,24 @@ import type {
RegisterRequest,
RegisterResponse,
} from "@frontend/kitui";
import { parseAxiosError } from "../utils/parse-error";
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,
@ -32,15 +37,15 @@ export async function register(
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
}
}
};
export async function login(
export const login = async (
login: string,
password: string,
): Promise<[LoginResponse | null, string?]> {
): Promise<[LoginResponse | null, string?]> => {
try {
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
url: apiUrl + "/login",
url: `${API_URL}/login`,
body: { login, password },
useToken: false,
withCredentials: true,
@ -52,13 +57,13 @@ export async function login(
return [null, `Не удалось войти. ${error}`];
}
}
};
export async function logout(): Promise<[unknown, string?]> {
export const logout = async (): Promise<[void | null, string?]> => {
try {
const logoutResponse = await makeRequest<never, void>({
url: apiUrl + "/logout",
method: "POST",
url: `${API_URL}/logout`,
useToken: true,
withCredentials: true,
});
@ -67,30 +72,32 @@ export async function logout(): Promise<[unknown, string?]> {
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null];
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",
`${process.env.REACT_APP_DOMAIN}/changepwd`,
);
const recoverResponse = await makeRequest<unknown, unknown>({
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
const recoverResponse = await makeRequest<FormData, RecoverResponse>({
url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
body: formData,
useToken: false,
withCredentials: true,
});
return [recoverResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось восстановить пароль. ${error}`];
}
}
};

@ -1,15 +1,16 @@
import { UserAccount } from "@frontend/kitui";
import makeRequest from "@api/makeRequest";
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
const apiUrl = process.env.REACT_APP_DOMAIN + "/customer";
import type { UserAccount } from "@frontend/kitui";
export async function payCart(): Promise<[UserAccount | null, string?]> {
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/cart`;
const payCart = async (): Promise<[UserAccount | null, string?]> => {
try {
const payCartResponse = await makeRequest<never, UserAccount>({
url: apiUrl + "/cart/pay",
method: "POST",
url: `${API_URL}/pay`,
useToken: true,
});
@ -19,4 +20,44 @@ export async function payCart(): Promise<[UserAccount | null, string?]> {
return [null, `Не удалось оплатить товар из корзины. ${error}`];
}
}
};
const addCartItem = async (
id: string,
): Promise<[UserAccount | null, string?]> => {
try {
const addedItem = await makeRequest<never, UserAccount>({
method: "PATCH",
url: `${API_URL}?id=${id}`,
});
return [addedItem];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось добавить товар в корзину. ${error}`];
}
};
const deleteCartItem = async (
id: string,
): Promise<[UserAccount | null, string?]> => {
try {
const deletedItem = await makeRequest<never, UserAccount>({
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,
};

@ -1,16 +1,31 @@
import axios from "axios";
import { makeRequest } from "@api/makeRequest";
const domen = process.env.REACT_APP_DOMAIN;
import { parseAxiosError } from "@utils/parse-error";
export function sendContactFormRequest(body: {
const API_URL = `${process.env.REACT_APP_DOMAIN}/feedback`;
type SendContactFormBody = {
contact: string;
whoami: string;
}) {
return axios(`${domen}/feedback/callme`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
});
}
};
export const sendContactFormRequest = async (
body: SendContactFormBody,
): Promise<[unknown | null, string?, number?]> => {
try {
const sendContactFormResponse = await makeRequest<
SendContactFormBody,
unknown
>({
method: "POST",
url: `${API_URL}/callme`,
body,
});
return [sendContactFormResponse];
} catch (nativeError) {
const [error, status] = parseAxiosError(nativeError);
return [null, `Не удалось отправить контакты. ${error}`, status];
}
};

@ -1,17 +1,19 @@
import { makeRequest } from "@frontend/kitui";
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 async function getDiscounts(
export const getDiscounts = async (
userId: string,
): Promise<[Discount[] | null, string?]> {
): Promise<[Discount[] | null, string?]> => {
try {
const { Discounts } = await makeRequest<unknown, { Discounts: Discount[] }>(
{ method: "GET", url: `${API_URL}/user/${userId}` },
);
const { Discounts } = await makeRequest<never, { Discounts: Discount[] }>({
method: "GET",
url: `${API_URL}/user/${userId}`,
});
return [Discounts];
} catch (nativeError) {
@ -19,4 +21,4 @@ export async function getDiscounts(
return [null, `Не удалось получить скидки. ${error}`];
}
}
};

332
src/api/integration.ts Normal file

@ -0,0 +1,332 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
export type PaginationRequest = {
page: number;
size: number;
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`;
// получение информации об аккаунте
export type AccountResponse = {
ID: number;
AccountID: string;
AmoID: number;
Name: string;
Email: string;
Role: string;
Group: number;
Deleted: boolean;
CreatedAt: number;
Subdomain: string;
Amoiserid: number;
Country: string;
};
export const getAccount = async (): Promise<
[AccountResponse | null, string?]
> => {
try {
const response = await makeRequest<void, AccountResponse>({
method: "GET",
url: `${API_URL}/account`,
useToken: true,
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить информацию об аккаунте. ${error}`];
}
};
// подключить Amo
export const connectAmo = async (): Promise<[string | null, string?]> => {
try {
const response = await makeRequest<void, { link: string }>({
method: "POST",
url: `${API_URL}/account`,
useToken: true,
withCredentials: true,
});
return [response.link];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось подключить аккаунт. ${error}`];
}
};
// получение токена
export type TokenPair = {
accessToken: string;
refreshToken: string;
};
export const getTokens = async (): Promise<[TokenPair | null, string?]> => {
try {
const response = await makeRequest<void, TokenPair>({
method: "GET",
url: `${API_URL}/webhook/create`,
useToken: true,
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Failed to get tokens. ${error}`];
}
};
//получение списка тегов
export type Tag = {
ID: number;
AmoID: number;
AccountID: number;
Entity: string;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type TagsResponse = {
count: number;
items: Tag[];
};
export const getTags = async ({
page,
size,
}: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
try {
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
method: "GET",
url: `${API_URL}/tags?page=${page}&size=${size}`,
});
return [tagsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список тегов. ${error}`];
}
};
//получение списка пользователей
export type User = {
ID: number;
AccountID: string;
AmoID: number;
Name: string;
Email: string;
Role: string;
Group: number;
Deleted: boolean;
CreatedAt: number;
Subdomain: string;
Amoiserid: number;
Country: string;
};
export type UsersResponse = {
count: number;
items: User[];
};
export const getUsers = async ({
page,
size,
}: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
try {
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
method: "GET",
url: `${API_URL}/users?page=${page}&size=${size}`,
});
return [usersResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список пользователей. ${error}`];
}
};
//получение списка шагов
export type Step = {
ID: number;
AmoID: number;
PipelineID: number;
AccountID: number;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type StepsResponse = {
count: number;
items: Step[];
};
export const getSteps = async ({
page,
size,
pipelineId,
}: PaginationRequest & { pipelineId: number }): Promise<
[StepsResponse | null, string?]
> => {
try {
const stepsResponse = await makeRequest<
PaginationRequest & { pipelineId: number },
StepsResponse
>({
method: "GET",
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
});
return [stepsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список шагов. ${error}`];
}
};
//получение списка воронок
export type Pipeline = {
ID: number;
AmoID: number;
AccountID: number;
Name: string;
IsArchive: boolean;
Deleted: boolean;
CreatedAt: number;
};
export type PipelinesResponse = {
count: number;
items: Pipeline[];
};
export const getPipelines = async ({
page,
size,
}: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
try {
const pipelinesResponse = await makeRequest<
PaginationRequest,
PipelinesResponse
>({
method: "GET",
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
});
return [pipelinesResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список воронок. ${error}`];
}
};
//получение настроек интеграции
export type IntegrationRules = {
ID: number;
AccountID: number;
QuizID: number;
PerformerID: number;
PipelineID: number;
StepID: number;
UTMs: number[];
FieldsRule: {
lead: { QuestionID: number }[];
contact: { ContactRuleMap: string }[];
company: { QuestionID: number }[];
customer: { QuestionID: number }[];
};
Deleted: boolean;
CreatedAt: number;
};
export const getIntegrationRules = async (
quizID: string,
): Promise<[IntegrationRules | null, string?]> => {
try {
const settingsResponse = await makeRequest<void, IntegrationRules>({
method: "GET",
url: `${API_URL}/rules/${quizID}`,
});
return [settingsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить настройки интеграции. ${error}`];
}
};
//обновление настроек интеграции
export type IntegrationRulesUpdate = {
PerformerID: number;
PipelineID: number;
StepID: number;
Utms: number[];
Fieldsrule: {
Lead: { QuestionID: number }[];
Contact: { ContactRuleMap: string }[];
Company: { QuestionID: number }[];
Customer: { QuestionID: number }[];
};
};
export const updateIntegrationRules = async (
quizID: string,
settings: IntegrationRulesUpdate,
): Promise<[string | null, string?]> => {
try {
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
method: "PATCH",
url: `${API_URL}/rules/${quizID}`,
body: settings,
});
return [updateResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Failed to update integration settings. ${error}`];
}
};
// Получение кастомных полей
export type CustomField = {
ID: number;
AmoID: number;
Code: string;
AccountID: number;
Name: string;
EntityType: string;
Type: string;
Deleted: boolean;
CreatedAt: number;
};
export type CustomFieldsResponse = {
count: number;
items: CustomField[];
};
export const getCustomFields = async (
pagination: PaginationRequest,
): Promise<[CustomFieldsResponse | null, string?]> => {
try {
const fieldsResponse = await makeRequest<
PaginationRequest,
CustomFieldsResponse
>({
method: "GET",
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
});
return [fieldsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список кастомных полей. ${error}`];
}
};

@ -1,10 +1,13 @@
import * as KIT from "@frontend/kitui";
import { Method, ResponseType, AxiosError } from "axios";
import { redirect } from "react-router-dom";
import { clearAuthToken } from "@frontend/kitui";
import { cleanAuthTicketData } from "@root/ticket";
import { clearUserData } from "@root/user";
import { clearQuizData } from "@root/quizes/store";
import { redirect } from "react-router-dom";
import type { AxiosResponse } from "axios";
interface MakeRequest {
method?: Method | undefined;
@ -17,18 +20,22 @@ interface MakeRequest {
withCredentials?: boolean | undefined;
}
async function makeRequest<TRequest = unknown, TResponse = unknown>(
data: MakeRequest,
): Promise<TResponse> {
try {
const response = await KIT.makeRequest<unknown>(data);
type ExtendedAxiosResponse = AxiosResponse & { message: string };
export const makeRequest = async <TRequest = unknown, TResponse = unknown>(
data: MakeRequest,
): Promise<TResponse> => {
try {
const response = await KIT.makeRequest<unknown, TResponse>(data);
return response;
} catch (nativeError) {
const error = nativeError as AxiosError;
return response as TResponse;
} catch (e) {
const error = e as AxiosError;
if (
error.response?.status === 400 &&
error.response?.data?.message === "refreshToken is empty"
(error.response?.data as ExtendedAxiosResponse)?.message ===
"refreshToken is empty"
) {
cleanAuthTicketData();
clearAuthToken();
@ -36,7 +43,7 @@ async function makeRequest<TRequest = unknown, TResponse = unknown>(
clearQuizData();
redirect("/");
}
throw e;
throw nativeError;
}
}
export default makeRequest;
};

@ -1,25 +1,30 @@
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";
type ActivatePromocodeRequest = { codeword: string } | { fastLink: string };
type ActivatePromocodeResponse = { greetings: string };
export async function activatePromocode(promocode: string) {
const API_URL = `${process.env.REACT_APP_DOMAIN}/codeword/promocode`;
export const activatePromocode = async (
promocode: string,
): Promise<[string | null, string?]> => {
try {
const response = await makeRequest<
{ codeword: string } | { fastLink: string },
{ greetings: string }
ActivatePromocodeRequest,
ActivatePromocodeResponse
>({
url: apiUrl + "/activate",
method: "POST",
contentType: true,
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}`];
}
}
};

@ -1,84 +1,155 @@
import makeRequest from "@api/makeRequest";
import { CreateQuestionRequest } from "model/question/create";
import { RawQuestion } from "model/question/question";
import {
import { makeRequest } from "@api/makeRequest";
import { replaceSpacesToEmptyLines } from "@utils/replaceSpacesToEmptyLines";
import { parseAxiosError } from "@utils/parse-error";
import type { CreateQuestionRequest } from "model/question/create";
import type { RawQuestion } from "model/question/question";
import type {
GetQuestionListRequest,
GetQuestionListResponse,
} from "@model/question/getList";
import {
import type {
EditQuestionRequest,
EditQuestionResponse,
} from "@model/question/edit";
import {
import type {
DeleteQuestionRequest,
DeleteQuestionResponse,
} from "@model/question/delete";
import {
import type {
CopyQuestionRequest,
CopyQuestionResponse,
} from "@model/question/copy";
import { replaceSpacesToEmptyLines } from "../utils/replaceSpacesToEmptyLines";
const baseUrl = process.env.REACT_APP_DOMAIN + "/squiz";
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
function createQuestion(body: CreateQuestionRequest) {
return makeRequest<CreateQuestionRequest, RawQuestion>({
url: `${baseUrl}/question/create`,
body,
method: "POST",
});
}
export const createQuestion = async (
body: CreateQuestionRequest,
): Promise<[RawQuestion | null, string?]> => {
try {
const createdQuestion = await makeRequest<
CreateQuestionRequest,
RawQuestion
>({
method: "POST",
url: `${API_URL}/question/create`,
body,
});
async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
if (!body?.quiz_id) return null;
return [createdQuestion];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
const response = await makeRequest<
GetQuestionListRequest,
GetQuestionListResponse
>({
url: `${baseUrl}/question/getList`,
body: { ...defaultGetQuestionListBody, ...body },
method: "POST",
});
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
(question) => {
let data = question;
for (let key in question) {
const k = key as keyof RawQuestion;
//@ts-ignore
if (question[key] === " ") data[key] = "";
}
return data;
},
);
return [null, `Не удалось создать вопрос. ${error}`];
}
};
return replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue);
}
const getQuestionList = async (
body?: Partial<GetQuestionListRequest>,
): Promise<[RawQuestion[] | null, string?]> => {
try {
if (!body?.quiz_id) return [null, "Квиз не найден"];
function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) {
return makeRequest<EditQuestionRequest, EditQuestionResponse>({
url: `${baseUrl}/question/edit`,
body,
method: "PATCH",
signal,
});
}
const response = await makeRequest<
GetQuestionListRequest,
GetQuestionListResponse
>({
method: "POST",
url: `${API_URL}/question/getList`,
body: { ...defaultGetQuestionListBody, ...body },
});
function copyQuestion(questionId: number, quizId: number) {
return makeRequest<CopyQuestionRequest, CopyQuestionResponse>({
url: `${baseUrl}/question/copy`,
body: { id: questionId, quiz_id: quizId },
method: "POST",
});
}
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
(question) => {
let data = question;
function deleteQuestion(id: number) {
return makeRequest<DeleteQuestionRequest, DeleteQuestionResponse>({
url: `${baseUrl}/question/delete`,
body: { id },
method: "DELETE",
});
}
for (let key in question) {
if (question[key as keyof RawQuestion] === " ") {
//@ts-ignore
data[key] = "";
}
}
return data;
},
);
return [
replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue) ?? null,
];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список вопросов. ${error}`];
}
};
export const editQuestion = async (
body: EditQuestionRequest,
signal?: AbortSignal,
): Promise<[EditQuestionResponse | null, string?]> => {
try {
const editedQuestion = await makeRequest<
EditQuestionRequest,
EditQuestionResponse
>({
method: "PATCH",
url: `${API_URL}/question/edit`,
body,
signal,
});
return [editedQuestion];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось изменить вопрос. ${error}`];
}
};
export const copyQuestion = async (
questionId: number,
quizId: number,
): Promise<[CopyQuestionResponse | null, string?]> => {
try {
const copiedQuestion = await makeRequest<
CopyQuestionRequest,
CopyQuestionResponse
>({
method: "POST",
url: `${API_URL}/question/copy`,
body: { id: questionId, quiz_id: quizId },
});
return [copiedQuestion];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось скопировать вопрос. ${error}`];
}
};
export const deleteQuestion = async (
id: number,
): Promise<[DeleteQuestionResponse | null, string?]> => {
try {
const deletedQuestion = await makeRequest<
DeleteQuestionRequest,
DeleteQuestionResponse
>({
url: `${API_URL}/question/delete`,
body: { id },
method: "DELETE",
});
return [deletedQuestion];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось удалить вопрос. ${error}`];
}
};
export const questionApi = {
create: createQuestion,

@ -1,79 +1,190 @@
import makeRequest from "@api/makeRequest";
import { makeRequest } from "@api/makeRequest";
import { defaultQuizConfig } from "@model/quizSettings";
import { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
import { CreateQuizRequest } from "model/quiz/create";
import { DeleteQuizRequest, DeleteQuizResponse } from "model/quiz/delete";
import { EditQuizRequest, EditQuizResponse } from "model/quiz/edit";
import { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
import { GetQuizListRequest, GetQuizListResponse } from "model/quiz/getList";
import { RawQuiz } from "model/quiz/quiz";
const baseUrl = process.env.REACT_APP_DOMAIN + "/squiz";
const imagesUrl = process.env.REACT_APP_DOMAIN + "/squizstorer";
import { parseAxiosError } from "@utils/parse-error";
function createQuiz(body?: Partial<CreateQuizRequest>) {
return makeRequest<CreateQuizRequest, RawQuiz>({
url: `${baseUrl}/quiz/create`,
body: { ...defaultCreateQuizBody, ...body },
method: "POST",
});
}
import type { RawQuiz } from "model/quiz/quiz";
import type { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
import type { CreateQuizRequest } from "model/quiz/create";
import type { DeleteQuizRequest, DeleteQuizResponse } from "model/quiz/delete";
import type { EditQuizRequest, EditQuizResponse } from "model/quiz/edit";
import type { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
import type {
GetQuizListRequest,
GetQuizListResponse,
} from "model/quiz/getList";
async function getQuizList(body?: Partial<GetQuizListRequest>) {
const response = await makeRequest<GetQuizListRequest, GetQuizListResponse>({
url: `${baseUrl}/quiz/getList`,
body: { ...defaultGetQuizListBody, ...body },
method: "POST",
});
type AddedQuizImagesResponse = {
[key: string]: string;
};
return response.items;
}
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
const IMAGES_URL = `${process.env.REACT_APP_DOMAIN}/squizstorer`;
function getQuiz(body?: Partial<GetQuizRequest>) {
return makeRequest<GetQuizRequest, GetQuizResponse>({
url: `${baseUrl}/quiz/get`,
body: { ...defaultGetQuizBody, ...body },
method: "GET",
});
}
export const createQuiz = async (
body?: Partial<CreateQuizRequest>,
): Promise<[RawQuiz | null, string?]> => {
try {
const createdQuiz = await makeRequest<CreateQuizRequest, RawQuiz>({
method: "POST",
url: `${API_URL}/quiz/create`,
body: { ...defaultCreateQuizBody, ...body },
});
async function editQuiz(body: EditQuizRequest, signal?: AbortSignal) {
return makeRequest<EditQuizRequest, EditQuizResponse>({
url: `${baseUrl}/quiz/edit`,
body,
method: "PATCH",
signal,
});
}
return [createdQuiz];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
function copyQuiz(id: number) {
return makeRequest<CopyQuizRequest, CopyQuizResponse>({
url: `${baseUrl}/quiz/copy`,
body: { id },
method: "POST",
});
}
return [null, `Не удалось создать квиз. ${error}`];
}
};
function deleteQuiz(id: number) {
return makeRequest<DeleteQuizRequest, DeleteQuizResponse>({
url: `${baseUrl}/quiz/delete`,
body: { id },
method: "DELETE",
});
}
export const getQuizList = async (
body?: Partial<CreateQuizRequest>,
): Promise<[RawQuiz[] | null, string?]> => {
try {
const { items } = await makeRequest<
GetQuizListRequest,
GetQuizListResponse
>({
method: "POST",
url: `${API_URL}/quiz/getList`,
body: { ...defaultGetQuizListBody, ...body },
});
function addQuizImages(quizId: number, image: Blob) {
const formData = new FormData();
return [items];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
formData.append("quiz", quizId.toString());
formData.append("image", image);
return [null, `Не удалось получить список квизов. ${error}`];
}
};
return makeRequest<FormData, { [key: string]: string }>({
url: `${imagesUrl}/quiz/putImages`,
body: formData,
method: "PUT",
});
}
export const getQuiz = async (
body?: Partial<GetQuizRequest>,
): Promise<[GetQuizResponse | null, string?]> => {
try {
const quiz = await makeRequest<GetQuizRequest, GetQuizResponse>({
method: "GET",
url: `${API_URL}/quiz/get`,
body: { ...defaultGetQuizBody, ...body },
});
return [quiz];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить квиз. ${error}`];
}
};
export const editQuiz = async (
body: EditQuizRequest,
signal?: AbortSignal,
): Promise<[EditQuizResponse | null, string?]> => {
try {
const editedQuiz = await makeRequest<EditQuizRequest, EditQuizResponse>({
method: "PATCH",
url: `${API_URL}/quiz/edit`,
body,
signal,
});
return [editedQuiz];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось изменить квиз. ${error}`];
}
};
export const copyQuiz = async (
id: number,
): Promise<[EditQuizResponse | null, string?]> => {
try {
const copiedQuiz = await makeRequest<CopyQuizRequest, CopyQuizResponse>({
method: "POST",
url: `${API_URL}/quiz/copy`,
body: { id },
});
return [copiedQuiz];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось скопировать квиз. ${error}`];
}
};
export const deleteQuiz = async (
id: number,
): Promise<[DeleteQuizResponse | null, string?]> => {
try {
const deletedQuiz = await makeRequest<
DeleteQuizRequest,
DeleteQuizResponse
>({
method: "DELETE",
url: `${API_URL}/quiz/delete`,
body: { id },
});
return [deletedQuiz];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось удалить квиз. ${error}`];
}
};
export const addQuizImages = async (
quizId: number,
image: Blob,
): Promise<[AddedQuizImagesResponse | null, string?]> => {
try {
const formData = new FormData();
formData.append("quiz", quizId.toString());
formData.append("image", image);
const addedQuizImages = await makeRequest<
FormData,
AddedQuizImagesResponse
>({
url: `${IMAGES_URL}/quiz/putImages`,
body: formData,
method: "PUT",
});
return [addedQuizImages];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось добавить изображение. ${error}`];
}
};
export const copyQuizTemplate = async (
qid: string,
): Promise<[number | null, string?]> => {
try {
const { id } = await makeRequest<{ Qid: string }, { id: number }>({
method: "POST",
url: `${API_URL}/quiz/template`,
body: { Qid: qid },
});
if (!id) {
return [null, `Не удалось скопировать шаблон квиза.`];
}
return [id];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось скопировать шаблон квиза. ${error}`];
}
};
export const quizApi = {
create: createQuiz,
@ -83,6 +194,7 @@ export const quizApi = {
copy: copyQuiz,
delete: deleteQuiz,
addImages: addQuizImages,
copyTemplate: copyQuizTemplate,
};
const defaultCreateQuizBody: CreateQuizRequest = {

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

@ -1,9 +1,7 @@
import makeRequest from "@api/makeRequest";
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
const apiUrl = process.env.REACT_APP_DOMAIN + "/squiz/statistic";
export type DevicesResponse = {
Device: Record<string, number>;
OS: Record<string, number>;
@ -29,6 +27,8 @@ type TRequest = {
from: number;
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/statistic`;
export const getDevices = async (
quizId: string,
to: number,
@ -37,7 +37,7 @@ export const getDevices = async (
try {
const devicesResponse = await makeRequest<TRequest, DevicesResponse>({
method: "POST",
url: `${apiUrl}/${quizId}/devices`,
url: `${API_URL}/${quizId}/devices`,
withCredentials: true,
body: { to, from },
});
@ -58,7 +58,7 @@ export const getGeneral = async (
try {
const generalResponse = await makeRequest<TRequest, GeneralResponse>({
method: "POST",
url: `${apiUrl}/${quizId}/general`,
url: `${API_URL}/${quizId}/general`,
withCredentials: true,
body: { to, from },
});
@ -79,7 +79,7 @@ export const getQuestions = async (
try {
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
method: "POST",
url: `${apiUrl}/${quizId}/questions`,
url: `${API_URL}/${quizId}/questions`,
withCredentials: true,
body: { to, from },
});

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,20 +1,30 @@
import makeRequest from "@api/makeRequest";
import { parseAxiosError } from "../utils/parse-error";
import { createTicket as createTicketRequest } from "@frontend/kitui";
import { SendTicketMessageRequest } from "@frontend/kitui";
import { makeRequest } from "@api/makeRequest";
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
import { parseAxiosError } from "@utils/parse-error";
export async function sendTicketMessage(
import type {
SendTicketMessageRequest,
CreateTicketResponse,
} from "@frontend/kitui";
type SendFileResponse = {
message: string;
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym`;
export const sendTicketMessage = async (
ticketId: string,
message: string,
): Promise<[null, string?]> {
): Promise<[null, string?]> => {
try {
const sendTicketMessageResponse = await makeRequest<
SendTicketMessageRequest,
null
>({
url: `${apiUrl}/send`,
url: `${API_URL}/send`,
method: "POST",
useToken: true,
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
@ -26,12 +36,12 @@ 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",
url: `${API_URL}/shown`,
method: "POST",
useToken: true,
body: { id },
@ -43,4 +53,47 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
return [null, `Не удалось прочесть сообщение. ${error}`];
}
}
};
export const sendFile = async (
ticketId: string,
file: File,
): Promise<[SendFileResponse | null, string?]> => {
try {
const body = new FormData();
body.append(file.name, file);
body.append("ticket", ticketId);
const sendResponse = await makeRequest<FormData, SendFileResponse>({
method: "POST",
url: `${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}`];
}
};

66
src/api/user.ts Normal file

@ -0,0 +1,66 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
import type { UserAccount } from "@frontend/kitui";
import type { OriginalUserAccount } from "@root/user";
type RecoverUserRequest = {
password: string;
};
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<RecoverUserRequest, unknown>({
url: `${process.env.REACT_APP_DOMAIN}/user`,
method: "PATCH",
body: { password },
});
return [recoverResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось восстановить пароль. ${error}`];
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

@ -4,10 +4,15 @@ interface Props {
color?: string;
bgcolor?: string;
marL?: string;
width?: string
width?: string;
}
export default function CopyIcon({ color, bgcolor, marL, width = "36px" }: Props) {
export default function CopyIcon({
color,
bgcolor,
marL,
width = "36px",
}: Props) {
const theme = useTheme();
return (

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Some files were not shown because too many files have changed in this diff Show More