Merge remote-tracking branch 'origin/staging'
@ -1,4 +1,4 @@
|
||||
FROM node:20.10-alpine3.18 as build
|
||||
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/node as build
|
||||
|
||||
RUN apk update && rm -rf /var/cache/apk/*
|
||||
WORKDIR /usr/app
|
||||
@ -13,7 +13,7 @@ RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn ca
|
||||
RUN yarn build
|
||||
|
||||
|
||||
FROM nginx:latest as result
|
||||
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/nginx as result
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=build /usr/app/build/ /usr/share/nginx/html
|
||||
COPY hub.conf /etc/nginx/conf.d/default.conf
|
||||
|
@ -1,3 +1,4 @@
|
||||
version: "3"
|
||||
services:
|
||||
squiz:
|
||||
container_name: squiz
|
||||
|
20
package.json
@ -7,7 +7,7 @@
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.82",
|
||||
"@frontend/squzanswerer": "^1.0.38",
|
||||
"@frontend/squzanswerer": "^1.0.51",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
@ -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",
|
||||
@ -41,6 +40,7 @@
|
||||
"notistack": "^3.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-cytoscapejs": "^2.0.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
@ -55,6 +55,7 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"react-slick": "^0.29.0",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"swiper": "^11.1.4",
|
||||
"swr": "^2.2.4",
|
||||
"typescript": "^5.2.2",
|
||||
"use-debounce": "^9.0.4",
|
||||
@ -68,7 +69,7 @@
|
||||
"test": "craco test",
|
||||
"eject": "craco eject",
|
||||
"cypress:open": "cypress open",
|
||||
"code:format": "prettier ./src --write --ignore-unknown",
|
||||
"code:format": "prettier --write --ignore-unknown",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"browserslist": {
|
||||
@ -97,5 +98,18 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*": "yarn code:format"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "auto",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"jsxSingleQuote": false,
|
||||
"singleAttributePerLine": true
|
||||
}
|
||||
}
|
||||
|
12
prettierrc
@ -1,12 +0,0 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "auto",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"jsxSingleQuote": false
|
||||
}
|
@ -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>
|
||||
|
205
src/App.tsx
@ -1,68 +1,39 @@
|
||||
import type { SuspenseProps } from "react";
|
||||
import { lazy, Suspense, useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { lazily } from "react-lazily";
|
||||
import { clearAuthToken, getMessageFromFetchError, UserAccount, useUserFetcher } from "@frontend/kitui";
|
||||
import type { OriginalUserAccount } from "@root/user";
|
||||
import { clearUserData, setCustomerAccount, setUser, setUserAccount, useUserStore } from "@root/user";
|
||||
import ContactFormModal from "@ui_kit/ContactForm";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/ru";
|
||||
import FloatingSupportChat from "@ui_kit/FloatingSupportChat";
|
||||
import PrivateRoute from "@ui_kit/PrivateRoute";
|
||||
import { useAfterpay } from "@utils/hooks/useAfterpay";
|
||||
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import type { SuspenseProps } from "react";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { lazily } from "react-lazily";
|
||||
import { Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useAmoAccount } from "./api/integration";
|
||||
import ListPageDummy from "./components/Dummys/pageDummys/listPageDummy";
|
||||
import "./index.css";
|
||||
import OutdatedLink from "./pages/auth/OutdatedLink";
|
||||
import RecoverPassword from "./pages/auth/RecoverPassword";
|
||||
import { Restore } from "./pages/auth/Restore";
|
||||
import SigninDialog from "./pages/auth/Signin";
|
||||
import SignupDialog from "./pages/auth/Signup";
|
||||
import {
|
||||
Navigate,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "react-router-dom";
|
||||
import "./index.css";
|
||||
import { InfoPrivilege } from "./pages/InfoPrivilege";
|
||||
import AmoTokenExpiredDialog from "./pages/IntegrationsPage/IntegrationsModal/AmoTokenExpiredDialog";
|
||||
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,
|
||||
setCustomerAccount,
|
||||
setUser,
|
||||
setUserAccount,
|
||||
useUserStore,
|
||||
} from "@root/user";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import PrivateRoute from "@ui_kit/PrivateRoute";
|
||||
import FloatingSupportChat from "@ui_kit/FloatingSupportChat";
|
||||
|
||||
import { Restore } from "./pages/auth/Restore";
|
||||
|
||||
import { isAxiosError } from "axios";
|
||||
import RecoverPassword from "./pages/auth/RecoverPassword";
|
||||
import { InfoPrivilege } from "./pages/InfoPrivilege";
|
||||
import OutdatedLink from "./pages/auth/OutdatedLink";
|
||||
import { useAfterpay } from "@utils/hooks/useAfterpay";
|
||||
|
||||
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"));
|
||||
const { Tariffs } = lazily(() => import("./pages/Tariffs/Tariffs"));
|
||||
const { DesignPage } = lazily(() => import("./pages/DesignPage/DesignPage"));
|
||||
const { IntegrationsPage } = lazily(
|
||||
() => import("./pages/IntegrationsPage/IntegrationsPage"),
|
||||
);
|
||||
const { QuizAnswersPage } = lazily(
|
||||
() => import("./pages/QuizAnswersPage/QuizAnswersPage"),
|
||||
);
|
||||
const ChatImageNewWindow = lazy(
|
||||
() => import("@ui_kit/FloatingSupportChat/ChatImageNewWindow"),
|
||||
);
|
||||
|
||||
dayjs.locale("ru");
|
||||
const { IntegrationsPage } = lazily(() => import("./pages/IntegrationsPage/IntegrationsPage"));
|
||||
const { QuizAnswersPage } = lazily(() => import("./pages/QuizAnswersPage/QuizAnswersPage"));
|
||||
const ChatImageNewWindow = lazy(() => import("@ui_kit/FloatingSupportChat/ChatImageNewWindow"));
|
||||
|
||||
const routeslink = [
|
||||
{
|
||||
@ -92,65 +63,14 @@ 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();
|
||||
const { data: amoAccount } = useAmoAccount();
|
||||
|
||||
useUserFetcher({
|
||||
url: process.env.REACT_APP_DOMAIN + `/user/${userId}`,
|
||||
url: `${process.env.REACT_APP_DOMAIN}/user/${userId}`,
|
||||
userId,
|
||||
onNewUser: setUser,
|
||||
onError: (error) => {
|
||||
@ -164,7 +84,7 @@ export default function App() {
|
||||
});
|
||||
|
||||
useUserAccountFetcher<UserAccount>({
|
||||
url: process.env.REACT_APP_DOMAIN + "/customer/account",
|
||||
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0/account`,
|
||||
userId,
|
||||
onNewUserAccount: setCustomerAccount,
|
||||
onError: (error) => {
|
||||
@ -179,7 +99,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) => {
|
||||
@ -193,6 +113,8 @@ export default function App() {
|
||||
},
|
||||
});
|
||||
|
||||
useAfterpay();
|
||||
|
||||
if (location.state?.redirectTo)
|
||||
return (
|
||||
<Navigate
|
||||
@ -202,39 +124,68 @@ export default function App() {
|
||||
/>
|
||||
);
|
||||
|
||||
useAfterpay();
|
||||
|
||||
return (
|
||||
<>
|
||||
{amoAccount && <AmoTokenExpiredDialog isAmoTokenExpired={amoAccount.stale} />}
|
||||
<ContactFormModal />
|
||||
<FloatingSupportChat />
|
||||
{location.state?.backgroundLocation && (
|
||||
<Routes>
|
||||
<Route path="/signin" element={<SigninDialog />} />
|
||||
<Route path="/signup" element={<SignupDialog />} />
|
||||
<Route path="/recover" element={<Restore />} />
|
||||
<Route path="/changepwd" element={<RecoverPassword />} />
|
||||
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
||||
<Route
|
||||
path="/signin"
|
||||
element={<SigninDialog />}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={<SignupDialog />}
|
||||
/>
|
||||
<Route
|
||||
path="/recover"
|
||||
element={<Restore />}
|
||||
/>
|
||||
<Route
|
||||
path="/changepwd"
|
||||
element={<RecoverPassword />}
|
||||
/>
|
||||
<Route
|
||||
path="/changepwd/expired"
|
||||
element={<OutdatedLink />}
|
||||
/>
|
||||
</Routes>
|
||||
)}
|
||||
<Routes location={location.state?.backgroundLocation || location}>
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={<Landing />}
|
||||
/>
|
||||
<Route
|
||||
path="/signin"
|
||||
element={
|
||||
<Navigate to="/" replace state={{ redirectTo: "/signin" }} />
|
||||
<Navigate
|
||||
to="/"
|
||||
replace
|
||||
state={{ redirectTo: "/signin" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={
|
||||
<Navigate to="/" replace state={{ redirectTo: "/signup" }} />
|
||||
<Navigate
|
||||
to="/"
|
||||
replace
|
||||
state={{ redirectTo: "/signup" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/recover"
|
||||
element={
|
||||
<Navigate to="/" replace state={{ redirectTo: "/recover" }} />
|
||||
<Navigate
|
||||
to="/"
|
||||
replace
|
||||
state={{ redirectTo: "/recover" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
@ -259,9 +210,18 @@ export default function App() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/gallery"
|
||||
element={<LazyLoading children={<QuizGallery />} />}
|
||||
/>
|
||||
<Route
|
||||
path="/list"
|
||||
element={<LazyLoading children={<MyQuizzesFull />} />}
|
||||
element={
|
||||
<LazyLoading
|
||||
children={<MyQuizzesFull />}
|
||||
fallback={<ListPageDummy />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={"/view/:quizId"}
|
||||
@ -283,7 +243,10 @@ export default function App() {
|
||||
path={"/qaz"}
|
||||
element={<LazyLoading children={<InfoPrivilege />} />}
|
||||
/>
|
||||
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||
<Route
|
||||
path={"/image/:srcImage"}
|
||||
element={<ChatImageNewWindow />}
|
||||
/>
|
||||
<Route element={<PrivateRoute />}>
|
||||
{routeslink.map((e, i) => (
|
||||
<Route
|
||||
|
@ -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,22 +1,63 @@
|
||||
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/v1.0.0/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,
|
||||
});
|
||||
|
||||
return [payCartResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
const error = parseAxiosError(nativeError);
|
||||
|
||||
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}`];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
377
src/api/integration.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import { QuestionKeys } from "@/pages/IntegrationsPage/IntegrationsModal/types";
|
||||
import { makeRequest } from "@api/makeRequest";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
import useSWR from "swr";
|
||||
|
||||
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;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
subdomain: string;
|
||||
country: string;
|
||||
driveURL: string;
|
||||
stale: boolean;
|
||||
};
|
||||
|
||||
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, ""];
|
||||
// return [null, `Не удалось получить информацию об аккаунте. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
export function useAmoAccount() {
|
||||
return useSWR("amoAccount", () =>
|
||||
makeRequest<void, AccountResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/account`,
|
||||
useToken: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// подключить 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;
|
||||
amoID: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: number;
|
||||
group: number;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
amoUserID: number;
|
||||
|
||||
// Subdomain: string;
|
||||
// AccountID: 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 QuestionID = Record<string, number>;
|
||||
|
||||
export type IntegrationRules = {
|
||||
PipelineID: number;
|
||||
StepID: number;
|
||||
PerformerID?: number;
|
||||
FieldsRule: FieldsRule;
|
||||
TagsToAdd: {
|
||||
Lead: number[] | null;
|
||||
Contact: number[] | null;
|
||||
Company: number[] | null;
|
||||
Customer: number[] | null;
|
||||
};
|
||||
};
|
||||
export type FieldsRule = Record<Partial<QuestionKeys>, null | [{ QuestionID: QuestionID }]>;
|
||||
|
||||
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 || null];
|
||||
} catch (nativeError) {
|
||||
if (nativeError.response.status === 404) return [null, "first"];
|
||||
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 setIntegrationRules = async (
|
||||
quizID: string,
|
||||
settings: IntegrationRulesUpdate
|
||||
): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||
method: "POST",
|
||||
url: `${API_URL}/rules/${quizID}`,
|
||||
body: settings,
|
||||
});
|
||||
return [updateResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Failed to update integration settings. ${error}`];
|
||||
}
|
||||
};
|
||||
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 Field = {
|
||||
ID: number;
|
||||
AmoID: number;
|
||||
Code: string;
|
||||
AccountID: number;
|
||||
Name: string;
|
||||
Entity: string;
|
||||
Type: string;
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
};
|
||||
|
||||
export type CustomFieldsResponse = {
|
||||
count: number;
|
||||
items: CustomField[];
|
||||
};
|
||||
export type FieldsResponse = {
|
||||
count: number;
|
||||
items: Field[];
|
||||
};
|
||||
|
||||
export const getCustomFields = async (
|
||||
pagination: PaginationRequest
|
||||
): Promise<[CustomFieldsResponse | null, string?]> => {
|
||||
try {
|
||||
const customFieldsResponse = await makeRequest<PaginationRequest, CustomFieldsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||
});
|
||||
return [customFieldsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список кастомных полей. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//Отвязать аккаунт амо от публикации
|
||||
|
||||
export const removeAmoAccount = async (): Promise<[void | null, string?]> => {
|
||||
try {
|
||||
await makeRequest<void>({
|
||||
method: "DELETE",
|
||||
url: `${API_URL}/account`,
|
||||
});
|
||||
return [null, ""];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось отвязать аккаунт. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getFields = async ( pagination: PaginationRequest ): Promise<[FieldsResponse | null, string?]> => {
|
||||
try {
|
||||
const fieldsResponse = await makeRequest<PaginationRequest, FieldsResponse>({
|
||||
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,
|
||||
|
240
src/api/quiz.ts
@ -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/v1.0.0`;
|
||||
|
||||
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>;
|
||||
@ -23,12 +21,15 @@ export type QuestionsResponse = {
|
||||
Results: Record<string, number>;
|
||||
Questions: Record<string, Record<string, number>>;
|
||||
};
|
||||
export type GraphicsResponse = unknown;
|
||||
|
||||
type TRequest = {
|
||||
to: number;
|
||||
from: number;
|
||||
};
|
||||
|
||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/statistic`;
|
||||
|
||||
export const getDevices = async (
|
||||
quizId: string,
|
||||
to: number,
|
||||
@ -37,7 +38,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 +59,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 +80,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 },
|
||||
});
|
||||
@ -91,3 +92,24 @@ export const getQuestions = async (
|
||||
return [null, `Не удалось получить статистику по результатам. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
export const getGraphics = async (
|
||||
quizId: string,
|
||||
to: number,
|
||||
from: number,
|
||||
): Promise<[GraphicsResponse | null, string?]> => {
|
||||
try {
|
||||
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
||||
method: "get",
|
||||
url: `${API_URL}s/${quizId}/pipelines?from=${from}&to=${to}`,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
console.log(questionsResponse)
|
||||
return [questionsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось получить статистику. ${error}`];
|
||||
}
|
||||
};
|
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/v1.0.0`;
|
||||
|
||||
export const sendTicketMessage = async (
|
||||
ticketId: string,
|
||||
message: string,
|
||||
): Promise<[null, string?]> {
|
||||
): Promise<[null, string?]> => {
|
||||
try {
|
||||
const sendTicketMessageResponse = await makeRequest<
|
||||
SendTicketMessageRequest,
|
||||
null
|
||||
>({
|
||||
url: `${apiUrl}/send`,
|
||||
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: `${API_URL}/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/v1.0.0/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/v1.0.0/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}`];
|
||||
}
|
||||
};
|
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 49 KiB |
BIN
src/assets/icons/Amologo.png
Normal file
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 (
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default function NumberThree({ color }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.1875C16.9706 21.1875 21 17.1581 21 12.1875C21 7.21694 16.9706 3.1875 12 3.1875C7.02944 3.1875 3 7.21694 3 12.1875C3 17.1581 7.02944 21.1875 12 21.1875Z"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
d="M9.75 8.0625H14.25L11.625 11.8125C12.0567 11.8125 12.4817 11.919 12.8624 12.1225C13.243 12.326 13.5677 12.6203 13.8075 12.9792C14.0473 13.3381 14.1949 13.7507 14.2372 14.1803C14.2795 14.6099 14.2152 15.0433 14.05 15.4421C13.8848 15.8409 13.6238 16.1928 13.2901 16.4666C12.9564 16.7405 12.5603 16.9278 12.137 17.0121C11.7136 17.0963 11.276 17.0748 10.8629 16.9495C10.4498 16.8242 10.074 16.599 9.76875 16.2937"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default function NumberTwo({ color }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="25"
|
||||
viewBox="0 0 24 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.1875C16.9706 21.1875 21 17.1581 21 12.1875C21 7.21694 16.9706 3.1875 12 3.1875C7.02944 3.1875 3 7.21694 3 12.1875C3 17.1581 7.02944 21.1875 12 21.1875Z"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
d="M9.92813 9.06402C10.1303 8.58652 10.4913 8.19352 10.9499 7.9515C11.4085 7.70948 11.9366 7.6333 12.4449 7.73584C12.9533 7.83839 13.4106 8.11336 13.7395 8.51426C14.0684 8.91515 14.2487 9.41735 14.25 9.9359C14.252 10.3839 14.118 10.822 13.8656 11.1921V11.1921L9.75 16.6859H14.25"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default function OneIconBorder({ color }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<path
|
||||
d="M10.125 9.375L12.375 7.875V16.5"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
22
src/assets/icons/questionsPage/AlignIcon.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { FC } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const AlignIcon: FC = () => (
|
||||
<Box sx={{ width: `20px`, height: "20px" }}>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 8.18348L11.8 8.22299V1M8.2 19V11.8165L1 11.7752"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
35
src/assets/icons/questionsPage/EditIcon.tsx
Normal file
29
src/assets/icons/questionsPage/ExpandIcon.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { FC } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const ExpandIcon: FC = () => (
|
||||
<Box sx={{ width: `24px`, height: "24px" }}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M18 6.58545L12.4984 2.17075C12.434 2.11677 12.3566 2.07382 12.271 2.04447C12.1853 2.01513 12.0931 2 12 2C11.9069 2 11.8147 2.01513 11.729 2.04447C11.6434 2.07382 11.566 2.11677 11.5016 2.17075L6 6.58545M18 17.4146L12.4984 21.8293C12.434 21.8832 12.3566 21.9262 12.271 21.9555C12.1853 21.9849 12.0931 22 12 22C11.9069 22 11.8147 21.9849 11.729 21.9555C11.6434 21.9262 11.566 21.8832 11.5016 21.8293L6 17.4146"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.9988 14.0054C13.0682 14.0054 13.9351 13.1385 13.9351 12.0691C13.9351 10.9997 13.0682 10.1328 11.9988 10.1328C10.9294 10.1328 10.0625 10.9997 10.0625 12.0691C10.0625 13.1385 10.9294 14.0054 11.9988 14.0054Z"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
29
src/assets/icons/questionsPage/GrayPlus.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { FC } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const GrayPlus: FC = () => (
|
||||
<Box sx={{ width: `32px` }}>
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M16.0029 1V31"
|
||||
stroke="#9A9AAF"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M31 15.9941L1 15.9941"
|
||||
stroke="#9A9AAF"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
38
src/assets/icons/questionsPage/RoundedCheckedIcon.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { FC } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const RoundedCheckedIcon: FC = () => (
|
||||
<Box
|
||||
sx={{
|
||||
width: `26px`,
|
||||
height: "26px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="26"
|
||||
height="27"
|
||||
viewBox="0 0 26 27"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="1"
|
||||
width="25"
|
||||
height="25"
|
||||
rx="12.5"
|
||||
fill="#F2F3F7"
|
||||
stroke="#F2F3F7"
|
||||
/>
|
||||
<path
|
||||
d="M8 13.5L12.2857 17.5L18 10"
|
||||
stroke="#9A9AAF"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 86 KiB |
BIN
src/assets/quiz-templates/auto/auto-1.jpg
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
src/assets/quiz-templates/auto/auto-10.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/quiz-templates/auto/auto-2.jpg
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
src/assets/quiz-templates/auto/auto-3.jpg
Normal file
After Width: | Height: | Size: 861 KiB |
BIN
src/assets/quiz-templates/auto/auto-4.jpg
Normal file
After Width: | Height: | Size: 514 KiB |
BIN
src/assets/quiz-templates/auto/auto-5.jpg
Normal file
After Width: | Height: | Size: 642 KiB |
BIN
src/assets/quiz-templates/auto/auto-6.jpg
Normal file
After Width: | Height: | Size: 667 KiB |
BIN
src/assets/quiz-templates/auto/auto-7.jpg
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
src/assets/quiz-templates/auto/auto-8.jpg
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
src/assets/quiz-templates/auto/auto-9.jpg
Normal file
After Width: | Height: | Size: 600 KiB |
BIN
src/assets/quiz-templates/education/education-1.jpg
Normal file
After Width: | Height: | Size: 618 KiB |
BIN
src/assets/quiz-templates/education/education-10.jpg
Normal file
After Width: | Height: | Size: 442 KiB |
BIN
src/assets/quiz-templates/education/education-2.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/quiz-templates/education/education-3.jpg
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
src/assets/quiz-templates/education/education-4.jpg
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
src/assets/quiz-templates/education/education-5.jpg
Normal file
After Width: | Height: | Size: 600 KiB |
BIN
src/assets/quiz-templates/education/education-6.jpg
Normal file
After Width: | Height: | Size: 295 KiB |
BIN
src/assets/quiz-templates/education/education-7.jpg
Normal file
After Width: | Height: | Size: 645 KiB |
BIN
src/assets/quiz-templates/education/education-8.jpg
Normal file
After Width: | Height: | Size: 847 KiB |
BIN
src/assets/quiz-templates/education/education-9.jpg
Normal file
After Width: | Height: | Size: 746 KiB |
BIN
src/assets/quiz-templates/health/health-1.jpg
Normal file
After Width: | Height: | Size: 431 KiB |
BIN
src/assets/quiz-templates/health/health-10.jpg
Normal file
After Width: | Height: | Size: 752 KiB |
BIN
src/assets/quiz-templates/health/health-11.jpg
Normal file
After Width: | Height: | Size: 424 KiB |
BIN
src/assets/quiz-templates/health/health-12.jpg
Normal file
After Width: | Height: | Size: 584 KiB |
BIN
src/assets/quiz-templates/health/health-13.jpg
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
src/assets/quiz-templates/health/health-14.jpg
Normal file
After Width: | Height: | Size: 517 KiB |
BIN
src/assets/quiz-templates/health/health-15.jpg
Normal file
After Width: | Height: | Size: 442 KiB |
BIN
src/assets/quiz-templates/health/health-16.jpg
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
src/assets/quiz-templates/health/health-17.jpg
Normal file
After Width: | Height: | Size: 719 KiB |
BIN
src/assets/quiz-templates/health/health-18.jpg
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
src/assets/quiz-templates/health/health-19.jpg
Normal file
After Width: | Height: | Size: 870 KiB |
BIN
src/assets/quiz-templates/health/health-2.jpg
Normal file
After Width: | Height: | Size: 516 KiB |
BIN
src/assets/quiz-templates/health/health-20.jpg
Normal file
After Width: | Height: | Size: 522 KiB |
BIN
src/assets/quiz-templates/health/health-3.jpg
Normal file
After Width: | Height: | Size: 310 KiB |
BIN
src/assets/quiz-templates/health/health-4.jpg
Normal file
After Width: | Height: | Size: 572 KiB |
BIN
src/assets/quiz-templates/health/health-5.jpg
Normal file
After Width: | Height: | Size: 758 KiB |
BIN
src/assets/quiz-templates/health/health-6.jpg
Normal file
After Width: | Height: | Size: 1013 KiB |
BIN
src/assets/quiz-templates/health/health-7.jpg
Normal file
After Width: | Height: | Size: 436 KiB |
BIN
src/assets/quiz-templates/health/health-8.jpg
Normal file
After Width: | Height: | Size: 571 KiB |
BIN
src/assets/quiz-templates/health/health-9.jpg
Normal file
After Width: | Height: | Size: 669 KiB |
BIN
src/assets/quiz-templates/production/production-1.jpg
Normal file
After Width: | Height: | Size: 425 KiB |
BIN
src/assets/quiz-templates/production/production-10.jpg
Normal file
After Width: | Height: | Size: 865 KiB |
BIN
src/assets/quiz-templates/production/production-2.jpg
Normal file
After Width: | Height: | Size: 378 KiB |
BIN
src/assets/quiz-templates/production/production-3.jpg
Normal file
After Width: | Height: | Size: 568 KiB |
BIN
src/assets/quiz-templates/production/production-4.jpg
Normal file
After Width: | Height: | Size: 550 KiB |
BIN
src/assets/quiz-templates/production/production-5.jpg
Normal file
After Width: | Height: | Size: 512 KiB |
BIN
src/assets/quiz-templates/production/production-6.jpg
Normal file
After Width: | Height: | Size: 410 KiB |
BIN
src/assets/quiz-templates/production/production-7.jpg
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/quiz-templates/production/production-8.jpg
Normal file
After Width: | Height: | Size: 351 KiB |
BIN
src/assets/quiz-templates/production/production-9.jpg
Normal file
After Width: | Height: | Size: 658 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-1.jpg
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-10.jpg
Normal file
After Width: | Height: | Size: 297 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-2.jpg
Normal file
After Width: | Height: | Size: 698 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-3.jpg
Normal file
After Width: | Height: | Size: 356 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-4.jpg
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-5.jpg
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-6.jpg
Normal file
After Width: | Height: | Size: 788 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-7.jpg
Normal file
After Width: | Height: | Size: 533 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-8.jpg
Normal file
After Width: | Height: | Size: 656 KiB |
BIN
src/assets/quiz-templates/real-estate/real-estate-9.jpg
Normal file
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/quiz-templates/repair/repair-1.jpg
Normal file
After Width: | Height: | Size: 972 KiB |
BIN
src/assets/quiz-templates/repair/repair-10.jpg
Normal file
After Width: | Height: | Size: 488 KiB |